summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHui Wang <hui.wang@canonical.com>2023-12-25 19:42:07 +0800
committerHui Wang <hui.wang@canonical.com>2024-01-05 23:12:18 +0800
commitd7dc04e8f5c404b1fa16409f69dcde7c56312f02 (patch)
tree4d067f9250724c5aaa0642bdc433aaaacc5a150a
parent81a6cc4967d1f19cef800932b10ade7f896ee2ea (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.c176
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;