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.
This commit is contained in:
Frédéric Danis 2024-12-11 10:56:20 +01:00
parent fc45aba032
commit 62645214a7

View file

@ -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);