diff options
author | Hui Wang <hui.wang@canonical.com> | 2023-12-25 19:42:07 +0800 |
---|---|---|
committer | Hui Wang <hui.wang@canonical.com> | 2024-01-05 23:12:18 +0800 |
commit | d7dc04e8f5c404b1fa16409f69dcde7c56312f02 (patch) | |
tree | 4d067f9250724c5aaa0642bdc433aaaacc5a150a | |
parent | 81a6cc4967d1f19cef800932b10ade7f896ee2ea (diff) |
backend-native: Handle multi AT commands in a buffer
When we connect Lenovo XT99 bt headset in the Ubuntu 22.04, this
headset could only work in A2DP profile, couldn't work in HFP profile
with a high chance.
This headset supports mSBC, after pulseaudio replies "+BCS:2" to
headset, we expect to receive a "AT+BCS=2\r" from the headset, but
with a high chance, it will receive 2 AT commands in a buffer like
this "AT+CHLD=?\rAT+BCS=2\r", and we also observed other 2 AT commands
in a buffer like this "AT+NREC=0\rAT+CGMI?\r".
Here we don't suppose there is only one AT command in a buffer, we
will find each command by the delimiter "\r" and handle each command
by sequence.
Signed-off-by: Hui Wang <hui.wang@canonical.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/804>
-rw-r--r-- | src/modules/bluetooth/backend-native.c | 176 |
1 files changed, 95 insertions, 81 deletions
diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c index 829d7bf82..263736ad4 100644 --- a/src/modules/bluetooth/backend-native.c +++ b/src/modules/bluetooth/backend-native.c @@ -889,106 +889,120 @@ static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_i } if (events & PA_IO_EVENT_INPUT) { - char buf[512]; + char rbuf[512]; ssize_t len; int gain, dummy; bool do_reply = false; int vendor, product, version, features; + char *buf = rbuf; int num; - len = pa_read(fd, buf, 511, NULL); + len = pa_read(fd, rbuf, 511, NULL); if (len < 0) { pa_log_error("RFCOMM read error: %s", pa_cstrerror(errno)); goto fail; } - buf[len] = 0; - pa_log_debug("RFCOMM << %s", buf); - - /* There are only four HSP AT commands: - * AT+VGS=value: value between 0 and 15, sent by the HS to AG to set the speaker gain. - * +VGS=value is sent by AG to HS as a response to an AT+VGS command or when the gain - * is changed on the AG side. - * AT+VGM=value: value between 0 and 15, sent by the HS to AG to set the microphone gain. - * +VGM=value is sent by AG to HS as a response to an AT+VGM command or when the gain - * is changed on the AG side. - * AT+CKPD=200: Sent by HS when headset button is pressed. - * RING: Sent by AG to HS to notify of an incoming call. It can safely be ignored because - * it does not expect a reply. */ - if (sscanf(buf, "AT+VGS=%d", &gain) == 1 || sscanf(buf, "\r\n+VGM%*[=:]%d\r\n", &gain) == 1) { - if (!t->set_sink_volume) { - pa_log_debug("HS/HF peer supports speaker gain control"); - t->set_sink_volume = set_sink_volume; - } + rbuf[len] = 0; + pa_log_debug("RFCOMM << %s", rbuf); + + while (buf[0]) { + /* There are only four HSP AT commands: + * AT+VGS=value: value between 0 and 15, sent by the HS to AG to set the speaker gain. + * +VGS=value is sent by AG to HS as a response to an AT+VGS command or when the gain + * is changed on the AG side. + * AT+VGM=value: value between 0 and 15, sent by the HS to AG to set the microphone gain. + * +VGM=value is sent by AG to HS as a response to an AT+VGM command or when the gain + * is changed on the AG side. + * AT+CKPD=200: Sent by HS when headset button is pressed. + * RING: Sent by AG to HS to notify of an incoming call. It can safely be ignored because + * it does not expect a reply. */ + if (sscanf(buf, "AT+VGS=%d", &gain) == 1 || sscanf(buf, "\r\n+VGM%*[=:]%d\r\n", &gain) == 1) { + if (!t->set_sink_volume) { + pa_log_debug("HS/HF peer supports speaker gain control"); + t->set_sink_volume = set_sink_volume; + } - t->sink_volume = hsp_gain_to_volume(gain); - pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_SINK_VOLUME_CHANGED), t); - do_reply = true; + t->sink_volume = hsp_gain_to_volume(gain); + pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_SINK_VOLUME_CHANGED), t); + do_reply = true; - } else if (sscanf(buf, "AT+VGM=%d", &gain) == 1 || sscanf(buf, "\r\n+VGS%*[=:]%d\r\n", &gain) == 1) { - if (!t->set_source_volume) { - pa_log_debug("HS/HF peer supports microphone gain control"); - t->set_source_volume = set_source_volume; - } - - t->source_volume = hsp_gain_to_volume(gain); - pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_SOURCE_VOLUME_CHANGED), t); - do_reply = true; - } else if (sscanf(buf, "AT+CKPD=%d", &dummy) == 1) { - do_reply = true; - } else if (sscanf(buf, "AT+XAPL=%04x-%04x-%04x,%d", &vendor, &product, &version, &features) == 4) { - if (features & 0x2) - /* claim, that we support battery status reports */ - rfcomm_write_response(fd, "+XAPL=iPhone,6"); - do_reply = true; - } else if (sscanf(buf, "AT+IPHONEACCEV=%d", &num) == 1) { - char *substr = buf, *keystr; - int key, val, i; - - do_reply = true; - - for (i = 0; i < num; ++i) { - keystr = strchr(substr, ','); - if (!keystr) { - pa_log_warn("%s misses key for argument #%d", buf, i); - do_reply = false; - break; - } - keystr++; - substr = strchr(keystr, ','); - if (!substr) { - pa_log_warn("%s misses value for argument #%d", buf, i); - do_reply = false; - break; + } else if (sscanf(buf, "AT+VGM=%d", &gain) == 1 || sscanf(buf, "\r\n+VGS%*[=:]%d\r\n", &gain) == 1) { + if (!t->set_source_volume) { + pa_log_debug("HS/HF peer supports microphone gain control"); + t->set_source_volume = set_source_volume; } - substr++; - key = atoi(keystr); - val = atoi(substr); - - switch (key) { - case 1: - pa_log_notice("Battery Level: %d0%%", val + 1); - pa_bluetooth_device_report_battery_level(t->device, (val + 1) * 10, "Apple accessory indication"); - break; - case 2: - pa_log_notice("Dock Status: %s", val ? "docked" : "undocked"); + t->source_volume = hsp_gain_to_volume(gain); + pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_SOURCE_VOLUME_CHANGED), t); + do_reply = true; + } else if (sscanf(buf, "AT+CKPD=%d", &dummy) == 1) { + do_reply = true; + } else if (sscanf(buf, "AT+XAPL=%04x-%04x-%04x,%d", &vendor, &product, &version, &features) == 4) { + if (features & 0x2) + /* claim, that we support battery status reports */ + rfcomm_write_response(fd, "+XAPL=iPhone,6"); + do_reply = true; + } else if (sscanf(buf, "AT+IPHONEACCEV=%d", &num) == 1) { + char *substr = buf, *keystr; + int key, val, i; + + do_reply = true; + + for (i = 0; i < num; ++i) { + keystr = strchr(substr, ','); + if (!keystr) { + pa_log_warn("%s misses key for argument #%d", buf, i); + do_reply = false; break; - default: - pa_log_debug("Unexpected IPHONEACCEV key %#x", key); + } + keystr++; + substr = strchr(keystr, ','); + if (!substr) { + pa_log_warn("%s misses value for argument #%d", buf, i); + do_reply = false; break; + } + substr++; + + key = atoi(keystr); + val = atoi(substr); + + switch (key) { + case 1: + pa_log_notice("Battery Level: %d0%%", val + 1); + pa_bluetooth_device_report_battery_level(t->device, (val + 1) * 10, "Apple accessory indication"); + break; + case 2: + pa_log_notice("Dock Status: %s", val ? "docked" : "undocked"); + break; + default: + pa_log_debug("Unexpected IPHONEACCEV key %#x", key); + break; + } } - } - if (!do_reply) + if (!do_reply) + rfcomm_write_response(fd, "ERROR"); + } else if (t->config) { /* t->config is only non-null for hfp profile */ + do_reply = hfp_rfcomm_handle(fd, t, buf); + } else { rfcomm_write_response(fd, "ERROR"); - } else if (t->config) { /* t->config is only non-null for hfp profile */ - do_reply = hfp_rfcomm_handle(fd, t, buf); - } else { - rfcomm_write_response(fd, "ERROR"); - do_reply = false; - } + do_reply = false; + } - if (do_reply) - rfcomm_write_response(fd, "OK"); + if (do_reply) + rfcomm_write_response(fd, "OK"); + + if (buf[0] == '\r') /* in case it is the command with format \r\nCOMMAND\r\n, skip the starting \r */ + buf = buf + 1; + + buf = strstr(buf, "\r"); /* try to find the next AT command in the buf */ + if (!buf) + break; + else if (buf[1] == '\n') + buf = buf + 2; /* skip \r\n */ + else + buf = buf + 1; /* skip \r */ + } } return; |