From 62645214a70aaac423d3b012319a93ce8cff8d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Wed, 11 Dec 2024 10:56:20 +0100 Subject: [PATCH] bluez5: telephony: Postpone AT commands if one is already in progress To be sure that the AG reply correspond to the command sent, this postpone the new command if previous reply (OK, ERROR or +CME) has not yet been received. The postponed command is sent on reception of the reply. --- spa/plugins/bluez5/backend-native.c | 250 ++++++++++++++++------------ 1 file changed, 147 insertions(+), 103 deletions(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 50be48525..5efde6a86 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -159,6 +159,11 @@ struct rfcomm_call_data { struct spa_bt_telephony_call *call; }; +struct rfcomm_cmd { + struct spa_list link; + char* cmd; +}; + struct rfcomm { struct spa_list link; struct spa_source source; @@ -189,6 +194,7 @@ struct rfcomm { unsigned int hfp_hf_nrec:1; unsigned int hfp_hf_clcc:1; unsigned int hfp_hf_cme:1; + unsigned int hfp_hf_cmd_in_progress:1; unsigned int hfp_hf_in_progress:1; unsigned int chld_supported:1; enum hfp_hf_state hf_state; @@ -197,6 +203,7 @@ struct rfcomm { uint32_t cind_enabled_indicators; char *hf_indicators[MAX_HF_INDICATORS]; struct spa_bt_telephony_ag *telephony_ag; + struct spa_list hfp_hf_commands; #endif }; @@ -345,7 +352,7 @@ static void rfcomm_free(struct rfcomm *rfcomm) /* from HF/HS to AG */ SPA_PRINTF_FUNC(2, 3) -static ssize_t rfcomm_send_cmd(const struct rfcomm *rfcomm, const char *format, ...) +static ssize_t rfcomm_send_cmd(struct rfcomm *rfcomm, const char *format, ...) { struct impl *backend = rfcomm->backend; char message[RFCOMM_MESSAGE_MAX_LENGTH + 1]; @@ -362,6 +369,14 @@ static ssize_t rfcomm_send_cmd(const struct rfcomm *rfcomm, const char *format, if (len > RFCOMM_MESSAGE_MAX_LENGTH) return -E2BIG; + if (rfcomm->hfp_hf_cmd_in_progress) { + spa_log_debug(backend->log, "Command in progress, postponing: %s", message); + struct rfcomm_cmd *cmd = calloc(1, sizeof(struct rfcomm_cmd)); + cmd->cmd = strndup(message, len); + spa_list_append(&rfcomm->hfp_hf_commands, &cmd->link); + return 0; + } + spa_log_debug(backend->log, "RFCOMM >> %s", message); /* @@ -382,6 +397,8 @@ static ssize_t rfcomm_send_cmd(const struct rfcomm *rfcomm, const char *format, spa_log_error(backend->log, "RFCOMM write error: %s", strerror(errno)); } + rfcomm->hfp_hf_cmd_in_progress = true; + return len; } @@ -1283,23 +1300,23 @@ static bool hfp_hf_wait_for_reply(struct rfcomm *rfcomm, char *buf, size_t len) ret = poll(fds, 1, 2000); if (ret < 0) { spa_log_error(backend->log, "RFCOMM poll error: %s", strerror(errno)); - return false; + goto done; } else if (ret == 0) { spa_log_error(backend->log, "RFCOMM poll timeout"); - return false; + goto done; } if (fds[0].revents & (POLLHUP | POLLERR)) { spa_log_info(backend->log, "lost RFCOMM connection."); rfcomm_free(rfcomm); - return false; + goto done; } if (fds[0].revents & POLLIN) { tmp_len = read(rfcomm->source.fd, tmp_buf, sizeof(tmp_buf) - 1); if (tmp_len < 0) { spa_log_error(backend->log, "RFCOMM read error: %s", strerror(errno)); - return false; + goto done; } tmp_buf[tmp_len] = '\0'; @@ -1332,6 +1349,18 @@ static bool hfp_hf_wait_for_reply(struct rfcomm *rfcomm, char *buf, size_t len) } } +done: + rfcomm->hfp_hf_cmd_in_progress = false; + if (!spa_list_is_empty(&rfcomm->hfp_hf_commands)) { + struct rfcomm_cmd *cmd; + cmd = spa_list_first(&rfcomm->hfp_hf_commands, struct rfcomm_cmd, link); + spa_list_remove(&cmd->link); + spa_log_debug(backend->log, "Sending postponed command: %s", cmd->cmd); + rfcomm_send_cmd(rfcomm, "%s", cmd->cmd); + free(cmd->cmd); + free(cmd); + } + return reply_found; } @@ -2201,114 +2230,128 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) } rfcomm->hfp_hf_in_progress = false; - } else if (spa_strstartswith(token, "OK")) { - switch(rfcomm->hf_state) { - case hfp_hf_brsf: - if (rfcomm->codec_negotiation_supported) { - char buf[64]; - struct spa_strbuf str; + } else if (spa_strstartswith(token, "OK") || spa_strstartswith(token, "ERROR") || + spa_strstartswith(token, "+CME ERROR:")) { + rfcomm->hfp_hf_cmd_in_progress = false; + if (!spa_list_is_empty(&rfcomm->hfp_hf_commands)) { + struct rfcomm_cmd *cmd; + cmd = spa_list_first(&rfcomm->hfp_hf_commands, struct rfcomm_cmd, link); + spa_list_remove(&cmd->link); + spa_log_debug(backend->log, "Sending postponed command: %s", cmd->cmd); + rfcomm_send_cmd(rfcomm, "%s", cmd->cmd); + free(cmd->cmd); + free(cmd); + } - spa_strbuf_init(&str, buf, sizeof(buf)); - spa_strbuf_append(&str, "1"); - if (rfcomm->msbc_supported_by_hfp) - spa_strbuf_append(&str, ",2"); - if (rfcomm->lc3_supported_by_hfp) - spa_strbuf_append(&str, ",3"); + if (spa_strstartswith(token, "OK")) { + switch(rfcomm->hf_state) { + case hfp_hf_brsf: + if (rfcomm->codec_negotiation_supported) { + char buf[64]; + struct spa_strbuf str; - rfcomm_send_cmd(rfcomm, "AT+BAC=%s", buf); - rfcomm->hf_state = hfp_hf_bac; - } else { + spa_strbuf_init(&str, buf, sizeof(buf)); + spa_strbuf_append(&str, "1"); + if (rfcomm->msbc_supported_by_hfp) + spa_strbuf_append(&str, ",2"); + if (rfcomm->lc3_supported_by_hfp) + spa_strbuf_append(&str, ",3"); + + rfcomm_send_cmd(rfcomm, "AT+BAC=%s", buf); + rfcomm->hf_state = hfp_hf_bac; + } else { + rfcomm_send_cmd(rfcomm, "AT+CIND=?"); + rfcomm->hf_state = hfp_hf_cind1; + } + break; + case hfp_hf_bac: rfcomm_send_cmd(rfcomm, "AT+CIND=?"); rfcomm->hf_state = hfp_hf_cind1; - } - break; - case hfp_hf_bac: - rfcomm_send_cmd(rfcomm, "AT+CIND=?"); - rfcomm->hf_state = hfp_hf_cind1; - break; - case hfp_hf_cind1: - rfcomm_send_cmd(rfcomm, "AT+CIND?"); - rfcomm->hf_state = hfp_hf_cind2; - break; - case hfp_hf_cind2: - rfcomm_send_cmd(rfcomm, "AT+CMER=3,0,0,1"); - rfcomm->hf_state = hfp_hf_cmer; - break; - case hfp_hf_cmer: - if (rfcomm->hfp_hf_3way) { - rfcomm_send_cmd(rfcomm, "AT+CHLD=?"); - rfcomm->hf_state = hfp_hf_chld; break; - } - SPA_FALLTHROUGH; - case hfp_hf_chld: - rfcomm_send_cmd(rfcomm, "AT+CLIP=1"); - rfcomm->hf_state = hfp_hf_clip; - break; - case hfp_hf_clip: - if (rfcomm->chld_supported) { - rfcomm_send_cmd(rfcomm, "AT+CCWA=1"); - rfcomm->hf_state = hfp_hf_ccwa; + case hfp_hf_cind1: + rfcomm_send_cmd(rfcomm, "AT+CIND?"); + rfcomm->hf_state = hfp_hf_cind2; break; - } - SPA_FALLTHROUGH; - case hfp_hf_ccwa: - if (rfcomm->hfp_hf_cme) { - rfcomm_send_cmd(rfcomm, "AT+CMEE=1"); - rfcomm->hf_state = hfp_hf_cmee; + case hfp_hf_cind2: + rfcomm_send_cmd(rfcomm, "AT+CMER=3,0,0,1"); + rfcomm->hf_state = hfp_hf_cmer; break; - } - SPA_FALLTHROUGH; - case hfp_hf_cmee: - if (backend->hfp_disable_nrec && rfcomm->hfp_hf_nrec) { - rfcomm_send_cmd(rfcomm, "AT+NREC=0"); - rfcomm->hf_state = hfp_hf_nrec; - break; - } - SPA_FALLTHROUGH; - case hfp_hf_nrec: - rfcomm->hf_state = hfp_hf_slc1; - rfcomm->slc_configured = true; - - if (!rfcomm->codec_negotiation_supported) { - if (rfcomm_new_transport(rfcomm, HFP_AUDIO_CODEC_CVSD) < 0) { - // TODO: We should manage the missing transport - } else { - spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile); + case hfp_hf_cmer: + if (rfcomm->hfp_hf_3way) { + rfcomm_send_cmd(rfcomm, "AT+CHLD=?"); + rfcomm->hf_state = hfp_hf_chld; + break; } - } - - rfcomm->telephony_ag = telephony_ag_new(backend->telephony, 0); - rfcomm->telephony_ag->address = strdup(rfcomm->device->address); - telephony_ag_set_callbacks(rfcomm->telephony_ag, - &telephony_ag_callbacks, rfcomm); - if (rfcomm->transport) { - rfcomm->telephony_ag->transport.codec = rfcomm->transport->codec; - rfcomm->telephony_ag->transport.state = rfcomm->transport->state; - } - telephony_ag_register(rfcomm->telephony_ag); - - if (rfcomm->hfp_hf_clcc) { - rfcomm_send_cmd(rfcomm, "AT+CLCC"); - rfcomm->hf_state = hfp_hf_slc2; + SPA_FALLTHROUGH; + case hfp_hf_chld: + rfcomm_send_cmd(rfcomm, "AT+CLIP=1"); + rfcomm->hf_state = hfp_hf_clip; break; - } else { - // TODO: Create calls if CIND reports one during SLC setup - } + case hfp_hf_clip: + if (rfcomm->chld_supported) { + rfcomm_send_cmd(rfcomm, "AT+CCWA=1"); + rfcomm->hf_state = hfp_hf_ccwa; + break; + } + SPA_FALLTHROUGH; + case hfp_hf_ccwa: + if (rfcomm->hfp_hf_cme) { + rfcomm_send_cmd(rfcomm, "AT+CMEE=1"); + rfcomm->hf_state = hfp_hf_cmee; + break; + } + SPA_FALLTHROUGH; + case hfp_hf_cmee: + if (backend->hfp_disable_nrec && rfcomm->hfp_hf_nrec) { + rfcomm_send_cmd(rfcomm, "AT+NREC=0"); + rfcomm->hf_state = hfp_hf_nrec; + break; + } + SPA_FALLTHROUGH; + case hfp_hf_nrec: + rfcomm->hf_state = hfp_hf_slc1; + rfcomm->slc_configured = true; - /* Report volume on SLC establishment */ - SPA_FALLTHROUGH; - case hfp_hf_slc2: - if (rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX)) - rfcomm->hf_state = hfp_hf_vgs; - break; - case hfp_hf_vgs: - rfcomm->hf_state = hfp_hf_slc1; - if (rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_TX)) - rfcomm->hf_state = hfp_hf_vgm; - break; - default: - break; + if (!rfcomm->codec_negotiation_supported) { + if (rfcomm_new_transport(rfcomm, HFP_AUDIO_CODEC_CVSD) < 0) { + // TODO: We should manage the missing transport + } else { + spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile); + } + } + + rfcomm->telephony_ag = telephony_ag_new(backend->telephony, 0); + rfcomm->telephony_ag->address = strdup(rfcomm->device->address); + telephony_ag_set_callbacks(rfcomm->telephony_ag, + &telephony_ag_callbacks, rfcomm); + if (rfcomm->transport) { + rfcomm->telephony_ag->transport.codec = rfcomm->transport->codec; + rfcomm->telephony_ag->transport.state = rfcomm->transport->state; + } + telephony_ag_register(rfcomm->telephony_ag); + + if (rfcomm->hfp_hf_clcc) { + rfcomm_send_cmd(rfcomm, "AT+CLCC"); + rfcomm->hf_state = hfp_hf_slc2; + break; + } else { + // TODO: Create calls if CIND reports one during SLC setup + } + + /* Report volume on SLC establishment */ + SPA_FALLTHROUGH; + case hfp_hf_slc2: + if (rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX)) + rfcomm->hf_state = hfp_hf_vgs; + break; + case hfp_hf_vgs: + rfcomm->hf_state = hfp_hf_slc1; + if (rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_TX)) + rfcomm->hf_state = hfp_hf_vgm; + break; + default: + break; + } } } @@ -3241,6 +3284,7 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag rfcomm->source.fd = spa_steal_fd(fd); rfcomm->source.mask = SPA_IO_IN; rfcomm->source.rmask = 0; + spa_list_init(&rfcomm->hfp_hf_commands); /* By default all indicators are enabled */ rfcomm->cind_enabled_indicators = 0xFFFFFFFF; memset(rfcomm->hf_indicators, 0, sizeof rfcomm->hf_indicators);