From c4e756b025c79637f952be3dfaf50df428abdaae Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Thu, 25 Jul 2024 13:36:07 +0800 Subject: [PATCH 01/76] Bluetooth: HFP_HF: Improve the feature configuration Add Kconfig to configure the HF `CLI` and `VOLUME` features. Add a macro `BT_HFP_HF_SDP_SUPPORTED_FEATURES` for SDP HF record. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/Kconfig | 14 ++++++++ subsys/bluetooth/host/classic/hfp_hf.c | 2 +- subsys/bluetooth/host/classic/hfp_internal.h | 34 +++++++++++++++++++- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/subsys/bluetooth/host/classic/Kconfig b/subsys/bluetooth/host/classic/Kconfig index 9a4fbc47ef03e..c9f7738b5ec2e 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -174,6 +174,20 @@ config BT_HFP_AG help This option enables Bluetooth AG support +if BT_HFP_HF +config BT_HFP_HF_CLI + bool "CLI presentation capability for HFP HF [EXPERIMENTAL]" + default y + help + This option enables CLI presentation capability for HFP HF + +config BT_HFP_HF_VOLUME + bool "Remote audio volume control for HFP HF [EXPERIMENTAL]" + default y + help + This option enables Remote audio volume control for HFP HF +endif # BT_HFP_HF + if BT_HFP_AG config BT_HFP_AG_TX_BUF_COUNT int "Maximum number of TX buffers for HFP AG [EXPERIMENTAL]" diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 54d90b876f187..6307181cd6608 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -119,7 +119,7 @@ static struct bt_sdp_attribute hfp_attrs[] = { /* The values of the “SupportedFeatures” bitmap shall be the same as the * values of the Bits 0 to 4 of the AT-command AT+BRSF (see Section 5.3). */ - BT_SDP_SUPPORTED_FEATURES(BT_HFP_HF_SUPPORTED_FEATURES & 0x1f), + BT_SDP_SUPPORTED_FEATURES(BT_HFP_HF_SDP_SUPPORTED_FEATURES), }; static struct bt_sdp_record hfp_rec = BT_SDP_RECORD(hfp_attrs); diff --git a/subsys/bluetooth/host/classic/hfp_internal.h b/subsys/bluetooth/host/classic/hfp_internal.h index 116807187046e..087f5fe2461ad 100644 --- a/subsys/bluetooth/host/classic/hfp_internal.h +++ b/subsys/bluetooth/host/classic/hfp_internal.h @@ -41,8 +41,40 @@ #define BT_HFP_HF_FEATURE_ENH_VOICE_RECG 0x00000400 /* Enhanced Voice Recognition Status */ #define BT_HFP_HF_FEATURE_VOICE_RECG_TEXT 0x00000800 /* Voice Recognition Text */ +/* HFP HF Features in SDP */ +#define BT_HFP_HF_SDP_FEATURE_ECNR BIT(0) /* EC and/or NR function */ +#define BT_HFP_HF_SDP_FEATURE_3WAY_CALL BIT(1) /* Three-way calling */ +#define BT_HFP_HF_SDP_FEATURE_CLI BIT(2) /* CLI presentation */ +#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG BIT(3) /* Voice recognition */ +#define BT_HFP_HF_SDP_FEATURE_VOLUME BIT(4) /* Remote volume control */ +#define BT_HFP_HF_SDP_FEATURE_WBS BIT(5) /* Wide Band Speech */ +#define BT_HFP_HF_SDP_FEATURE_ENH_VOICE_RECG BIT(6) /* Enhanced Voice Recognition Status */ +#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT BIT(7) /* Voice Recognition Text */ +#define BT_HFP_HF_SDP_FEATURE_SUPER_WBS BIT(7) /* Super Wide Band Speech */ + +#if defined(CONFIG_BT_HFP_HF_CLI) +#define BT_HFP_HF_FEATURE_CLI_ENABLE BT_HFP_HF_FEATURE_CLI +#define BT_HFP_HF_SDP_FEATURE_CLI_ENABLE BT_HFP_HF_SDP_FEATURE_CLI +#else +#define BT_HFP_HF_FEATURE_CLI_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_CLI_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_CLI */ + +#if defined(CONFIG_BT_HFP_HF_VOLUME) +#define BT_HFP_HF_FEATURE_VOLUME_ENABLE BT_HFP_HF_FEATURE_VOLUME +#define BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE BT_HFP_HF_SDP_FEATURE_VOLUME +#else +#define BT_HFP_HF_FEATURE_VOLUME_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + /* HFP HF Supported features */ -#define BT_HFP_HF_SUPPORTED_FEATURES (BT_HFP_HF_FEATURE_CLI | BT_HFP_HF_FEATURE_VOLUME) +#define BT_HFP_HF_SUPPORTED_FEATURES \ +(BT_HFP_HF_FEATURE_CLI_ENABLE | BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE) + +/* HFP HF Supported features in SDP */ +#define BT_HFP_HF_SDP_SUPPORTED_FEATURES \ +(BT_HFP_HF_SDP_FEATURE_CLI_ENABLE | BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE) #define HF_MAX_BUF_LEN BT_HF_CLIENT_MAX_PDU #define HF_MAX_AG_INDICATORS 20 From 0fb1830a2d1573ba33b0448a8d245e443fe82716 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Thu, 25 Jul 2024 14:34:18 +0800 Subject: [PATCH 02/76] Bluetooth: BR: at: add function at_get_string Add a function at_get_string to get the string from the received AT result code. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/at.c | 32 ++++++++++++++++++++++++++++++ subsys/bluetooth/host/classic/at.h | 1 + 2 files changed, 33 insertions(+) diff --git a/subsys/bluetooth/host/classic/at.c b/subsys/bluetooth/host/classic/at.c index fadeeab10fb7c..fb0193920b3dd 100644 --- a/subsys/bluetooth/host/classic/at.c +++ b/subsys/bluetooth/host/classic/at.c @@ -535,3 +535,35 @@ void at_register(struct at_client *at, at_resp_cb_t resp, at_finish_cb_t finish) at->finish = finish; at->state = AT_STATE_START; } + +char *at_get_string(struct at_client *at) +{ + uint8_t pos = at->pos; + char *string; + + skip_space(at); + + if (at->buf[at->pos] != '"') { + at->pos = pos; + return NULL; + } + at->pos++; + string = &at->buf[at->pos]; + + while (at->buf[at->pos] != '\0' && at->buf[at->pos] != '"') { + at->pos++; + } + + if (at->buf[at->pos] != '"') { + at->pos = pos; + return NULL; + } + + at->buf[at->pos] = '\0'; + at->pos++; + + skip_space(at); + next_list(at); + + return string; +} diff --git a/subsys/bluetooth/host/classic/at.h b/subsys/bluetooth/host/classic/at.h index ae2c7f95d8049..6b75725f83f19 100644 --- a/subsys/bluetooth/host/classic/at.h +++ b/subsys/bluetooth/host/classic/at.h @@ -116,3 +116,4 @@ int at_list_get_string(struct at_client *at, char *name, uint8_t len); int at_close_list(struct at_client *at); int at_open_list(struct at_client *at); int at_has_next_list(struct at_client *at); +char *at_get_string(struct at_client *at); From f2bd690ace938972a90c76b3a687fee0b224100f Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Thu, 25 Jul 2024 14:38:29 +0800 Subject: [PATCH 03/76] Bluetooth: HFP_HF: Support unsolicited result code +CLIP Handle the unsolicited result code +CLIP. Add a callback `clip` to notify the application if the configuration `CONFIG_BT_HFP_HF_CLI` is enabled. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 13 ++++++++++ subsys/bluetooth/host/classic/hfp_hf.c | 31 ++++++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index ded881351ca29..1a75c81d47023 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -155,6 +155,19 @@ struct bt_hfp_hf_cb { */ void (*cmd_complete_cb)(struct bt_conn *conn, struct bt_hfp_hf_cmd_complete *cmd); + /** HF calling line identification notification callback to application + * + * If this callback is provided it will be called whenever there + * is a unsolicited result code +CLIP. + * If @kconfig{CONFIG_BT_HFP_HF_CLI} is not enabled, the unsolicited + * result code +CLIP will be ignored. And the callback will not be + * notified. + * + * @param conn Connection object. + * @param number Notified phone number. + * @param type Specify the format of the phone number. + */ + void (*clip)(struct bt_conn *conn, char *number, uint8_t type); }; /** @brief Register HFP HF profile diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 6307181cd6608..f5a95bbc18dd4 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -431,13 +431,42 @@ int ring_handle(struct at_client *hf_at) return 0; } +#if defined(CONFIG_BT_HFP_HF_CLI) +int clip_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + struct bt_conn *conn = hf->acl; + char *number; + uint32_t type; + int err; + + number = at_get_string(hf_at); + + err = at_get_number(hf_at, &type); + if (err) { + LOG_WRN("could not get the type"); + } else { + type = 0; + } + + if (bt_hf->clip) { + bt_hf->clip(conn, number, (uint8_t)type); + } + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_CLI */ + static const struct unsolicited { const char *cmd; enum at_cmd_type type; int (*func)(struct at_client *hf_at); } handlers[] = { { "CIEV", AT_CMD_TYPE_UNSOLICITED, ciev_handle }, - { "RING", AT_CMD_TYPE_OTHER, ring_handle } + { "RING", AT_CMD_TYPE_OTHER, ring_handle }, +#if defined(CONFIG_BT_HFP_HF_CLI) + { "CLIP", AT_CMD_TYPE_UNSOLICITED, clip_handle }, +#endif /* CONFIG_BT_HFP_HF_CLI */ }; static const struct unsolicited *hfp_hf_unsol_lookup(struct at_client *hf_at) From 8a4a5eca1aa367acd7ceb293b93853d745aac75a Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Thu, 25 Jul 2024 18:08:34 +0800 Subject: [PATCH 04/76] Bluetooth: HFP_HF: Add function bt_hfp_hf_cli Add tx pending queue to queue call AT commands. Add a function bt_hfp_hf_cli to enable/disable CLI notification. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 15 ++ subsys/bluetooth/host/classic/hfp_hf.c | 170 ++++++++++++++++++- subsys/bluetooth/host/classic/hfp_internal.h | 14 ++ 3 files changed, 191 insertions(+), 8 deletions(-) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index 1a75c81d47023..0a69e18af9273 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -192,6 +192,21 @@ int bt_hfp_hf_register(struct bt_hfp_hf_cb *cb); */ int bt_hfp_hf_send_cmd(struct bt_conn *conn, enum bt_hfp_hf_at_cmd cmd); +/** @brief Handsfree HF enable/disable Calling Line Identification (CLI) Notification + * + * Enable/disable Calling Line Identification (CLI) Notification. + * The AT command `AT+CLIP` will be sent to the AG to enable/disable the CLI + * unsolicited result code +CLIP when calling the function. + * If @kconfig{CONFIG_BT_HFP_HF_CLI} is not enabled, the error `-ENOTSUP` will + * be returned if the function called. + * + * @param conn Connection object. + * @param enable Enable/disable CLI. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_cli(struct bt_conn *conn, bool enable); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index f5a95bbc18dd4..3c2c5ff41db86 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -42,6 +42,27 @@ NET_BUF_POOL_FIXED_DEFINE(hf_pool, CONFIG_BT_MAX_CONN + 1, static struct bt_hfp_hf bt_hfp_hf_pool[CONFIG_BT_MAX_CONN]; +struct at_callback_set { + void *resp; + void *finish; +} __packed; + +static inline void make_at_callback_set(void *storage, void *resp, void *finish) +{ + ((struct at_callback_set *)storage)->resp = resp; + ((struct at_callback_set *)storage)->finish = finish; +} + +static inline void *at_callback_set_resp(void *storage) +{ + return ((struct at_callback_set *)storage)->resp; +} + +static inline void *at_callback_set_finish(void *storage) +{ + return ((struct at_callback_set *)storage)->finish; +} + /* The order should follow the enum hfp_hf_ag_indicators */ static const struct { char *name; @@ -136,6 +157,85 @@ void hf_slc_error(struct at_client *hf_at) } } +static void hfp_hf_send_failed(struct bt_hfp_hf *hf) +{ + int err; + + LOG_ERR("SLC error: disconnecting"); + + err = bt_rfcomm_dlc_disconnect(&hf->rfcomm_dlc); + if (err) { + LOG_ERR("Fail to disconnect: %d", err); + } +} + +static void hfp_hf_send_data(struct bt_hfp_hf *hf); + +static int hfp_hf_common_finish(struct at_client *at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(at, struct bt_hfp_hf, at); + int err = 0; + + if (result != AT_RESULT_OK) { + LOG_WRN("Fail to send AT command (result %d, cme err %d) on %p", + result, cme_err, hf); + } + + if (hf->backup_finish) { + err = hf->backup_finish(at, result, cme_err); + hf->backup_finish = NULL; + } + + if (atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_TX_ONGOING)) { + LOG_DBG("Remove completed buf %p on %p", k_fifo_peek_head(&hf->tx_pending), hf); + (void)k_fifo_get(&hf->tx_pending, K_NO_WAIT); + } else { + LOG_WRN("Tx is not ongoing on %p", hf); + } + + hfp_hf_send_data(hf); + return err; +} + +static void hfp_hf_send_data(struct bt_hfp_hf *hf) +{ + struct net_buf *buf; + at_resp_cb_t resp; + at_finish_cb_t finish; + int err; + + if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_TX_ONGOING)) { + return; + } + + buf = k_fifo_peek_head(&hf->tx_pending); + if (!buf) { + return; + } + + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_TX_ONGOING); + + resp = (at_resp_cb_t)at_callback_set_resp(buf->user_data); + finish = (at_finish_cb_t)at_callback_set_finish(buf->user_data); + + /* + * Backup the `finish` callback. + * Provide a default finish callback to drive the next sending. + */ + hf->backup_finish = finish; + finish = hfp_hf_common_finish; + + make_at_callback_set(buf->user_data, NULL, NULL); + at_register(&hf->at, resp, finish); + + err = bt_rfcomm_dlc_send(&hf->rfcomm_dlc, buf); + if (err < 0) { + LOG_ERR("Rfcomm send error :(%d)", err); + hfp_hf_send_failed(hf); + } +} + int hfp_hf_send_cmd(struct bt_hfp_hf *hf, at_resp_cb_t resp, at_finish_cb_t finish, const char *format, ...) { @@ -143,15 +243,14 @@ int hfp_hf_send_cmd(struct bt_hfp_hf *hf, at_resp_cb_t resp, va_list vargs; int ret; - /* register the callbacks */ - at_register(&hf->at, resp, finish); - buf = bt_rfcomm_create_pdu(&hf_pool); if (!buf) { LOG_ERR("No Buffers!"); return -ENOMEM; } + make_at_callback_set(buf->user_data, resp, finish); + va_start(vargs, format); ret = vsnprintk(buf->data, (net_buf_tailroom(buf) - 1), format, vargs); if (ret < 0) { @@ -165,11 +264,8 @@ int hfp_hf_send_cmd(struct bt_hfp_hf *hf, at_resp_cb_t resp, LOG_DBG("HF %p, DLC %p sending buf %p", hf, &hf->rfcomm_dlc, buf); - ret = bt_rfcomm_dlc_send(&hf->rfcomm_dlc, buf); - if (ret < 0) { - LOG_ERR("Rfcomm send error :(%d)", ret); - return ret; - } + k_fifo_put(&hf->tx_pending, buf); + hfp_hf_send_data(hf); return 0; } @@ -554,9 +650,15 @@ static void slc_completed(struct at_client *hf_at) bt_hf->connected(conn); } + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED); + if (hfp_hf_send_cmd(hf, NULL, cmee_finish, "AT+CMEE=1") < 0) { LOG_ERR("Error Sending AT+CMEE"); } + +#if defined(CONFIG_BT_HFP_HF_CLI) + (void)bt_hfp_hf_cli(conn, true); +#endif /* CONFIG_BT_HFP_HF_CLI */ } int cmer_finish(struct at_client *hf_at, enum at_result result, @@ -710,6 +812,54 @@ int bt_hfp_hf_send_cmd(struct bt_conn *conn, enum bt_hfp_hf_at_cmd cmd) return 0; } +#if defined(CONFIG_BT_HFP_HF_CLI) +static int cli_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("CLIP set (result %d) on %p", result, hf); + + /* AT+CLI is done. */ + return 0; +} +#endif /* CONFIG_BT_HFP_HF_CLI */ + +int bt_hfp_hf_cli(struct bt_conn *conn, bool enable) +{ +#if defined(CONFIG_BT_HFP_HF_CLI) + struct bt_hfp_hf *hf; + int err; + + LOG_DBG(""); + + if (!conn) { + LOG_ERR("Invalid connection"); + return -ENOTCONN; + } + + hf = bt_hfp_hf_lookup_bt_conn(conn); + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + LOG_ERR("SLC is not established on %p", hf); + return -EINVAL; + } + + err = hfp_hf_send_cmd(hf, NULL, cli_finish, "AT+CLIP=%d", enable ? 1 : 0); + if (err < 0) { + LOG_ERR("HFP HF CLI set failed on %p", hf); + } + + return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_CLI */ +} + static void hfp_hf_connected(struct bt_rfcomm_dlc *dlc) { struct bt_hfp_hf *hf = CONTAINER_OF(dlc, struct bt_hfp_hf, rfcomm_dlc); @@ -766,6 +916,8 @@ static int bt_hfp_hf_accept(struct bt_conn *conn, struct bt_rfcomm_server *serve continue; } + memset(hf, 0, sizeof(*hf)); + hf->acl = conn; hf->at.buf = hf->hf_buffer; hf->at.buf_max_len = HF_MAX_BUF_LEN; @@ -778,6 +930,8 @@ static int bt_hfp_hf_accept(struct bt_conn *conn, struct bt_rfcomm_server *serve /* Set the supported features*/ hf->hf_features = BT_HFP_HF_SUPPORTED_FEATURES; + k_fifo_init(&hf->tx_pending); + for (j = 0; j < HF_MAX_AG_INDICATORS; j++) { hf->ind_table[j] = -1; } diff --git a/subsys/bluetooth/host/classic/hfp_internal.h b/subsys/bluetooth/host/classic/hfp_internal.h index 087f5fe2461ad..3a9a00c208ac1 100644 --- a/subsys/bluetooth/host/classic/hfp_internal.h +++ b/subsys/bluetooth/host/classic/hfp_internal.h @@ -79,10 +79,22 @@ #define HF_MAX_BUF_LEN BT_HF_CLIENT_MAX_PDU #define HF_MAX_AG_INDICATORS 20 + +/* bt_hfp_hf flags: the flags defined here represent hfp hf parameters */ +enum { + BT_HFP_HF_FLAG_CONNECTED, /* HFP HF SLC Established */ + BT_HFP_HF_FLAG_TX_ONGOING, /* HFP HF TX is ongoing */ + /* Total number of flags - must be at the end of the enum */ + BT_HFP_HF_NUM_FLAGS, +}; + struct bt_hfp_hf { struct bt_rfcomm_dlc rfcomm_dlc; /* ACL connection handle */ struct bt_conn *acl; + /* AT command sending queue */ + at_finish_cb_t backup_finish; + struct k_fifo tx_pending; /* SCO Channel */ struct bt_sco_chan chan; char hf_buffer[HF_MAX_BUF_LEN]; @@ -90,6 +102,8 @@ struct bt_hfp_hf { uint32_t hf_features; uint32_t ag_features; int8_t ind_table[HF_MAX_AG_INDICATORS]; + + ATOMIC_DEFINE(flags, BT_HFP_HF_NUM_FLAGS); }; enum hfp_hf_ag_indicators { From 2754d4d21fc40686ce18c40f56a09bda349613d7 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Fri, 26 Jul 2024 19:32:24 +0800 Subject: [PATCH 05/76] Bluetooth: HFP_HF: Only send AT+CMEE=1 if AG supports Only send AT command `AT+CMEE=1` if HFP_AG supports "Extended Error result code". Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/hfp_hf.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 3c2c5ff41db86..8f328ac2a4f21 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -652,8 +652,10 @@ static void slc_completed(struct at_client *hf_at) atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED); - if (hfp_hf_send_cmd(hf, NULL, cmee_finish, "AT+CMEE=1") < 0) { - LOG_ERR("Error Sending AT+CMEE"); + if (hf->ag_features & BT_HFP_AG_FEATURE_EXT_ERR) { + if (hfp_hf_send_cmd(hf, NULL, cmee_finish, "AT+CMEE=1") < 0) { + LOG_ERR("Error Sending AT+CMEE"); + } } #if defined(CONFIG_BT_HFP_HF_CLI) From 2dad6d493145f614962128561c6659a90556c8ae Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Mon, 2 Sep 2024 10:30:23 +0800 Subject: [PATCH 06/76] Bluetooth: HFP_HF: handle unsolicited result code +VGS/+VGM Handle the unsolicited result code +VGS and +VGM if the configuration `CONFIG_BT_HFP_HF_VOLUME` is enabled. Add a callback `vgm` to notify the application speaker microphone notification if the configuration `CONFIG_BT_HFP_HF_VOLUME` is enabled. Add a callback `vgs` to notify the application speaker gain notification if the configuration `CONFIG_BT_HFP_HF_VOLUME` is enabled. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 24 +++++++++ subsys/bluetooth/host/classic/hfp_hf.c | 56 ++++++++++++++++++++ subsys/bluetooth/host/classic/hfp_internal.h | 5 ++ 3 files changed, 85 insertions(+) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index 0a69e18af9273..0fe676cb96a52 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -168,6 +168,30 @@ struct bt_hfp_hf_cb { * @param type Specify the format of the phone number. */ void (*clip)(struct bt_conn *conn, char *number, uint8_t type); + /** HF microphone gain notification callback to application + * + * If this callback is provided it will be called whenever there + * is a unsolicited result code +VGM. + * If @kconfig{CONFIG_BT_HFP_HF_VOLUME} is not enabled, the unsolicited + * result code +VGM will be ignored. And the callback will not be + * notified. + * + * @param conn Connection object. + * @param gain Microphone gain. + */ + void (*vgm)(struct bt_conn *conn, uint8_t gain); + /** HF speaker gain notification callback to application + * + * If this callback is provided it will be called whenever there + * is a unsolicited result code +VGS. + * If @kconfig{CONFIG_BT_HFP_HF_VOLUME} is not enabled, the unsolicited + * result code +VGS will be ignored. And the callback will not be + * notified. + * + * @param conn Connection object. + * @param gain Speaker gain. + */ + void (*vgs)(struct bt_conn *conn, uint8_t gain); }; /** @brief Register HFP HF profile diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 8f328ac2a4f21..a42652212c78c 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -553,6 +553,58 @@ int clip_handle(struct at_client *hf_at) } #endif /* CONFIG_BT_HFP_HF_CLI */ +#if defined(CONFIG_BT_HFP_HF_VOLUME) +int vgm_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + struct bt_conn *conn = hf->acl; + uint32_t gain; + int err; + + err = at_get_number(hf_at, &gain); + if (err) { + LOG_ERR("could not get the microphone gain"); + return err; + } + + if (gain > BT_HFP_HF_VGM_GAIN_MAX) { + LOG_ERR("Invalid microphone gain (%d > %d)", gain, BT_HFP_HF_VGM_GAIN_MAX); + return -EINVAL; + } + + if (bt_hf->vgm) { + bt_hf->vgm(conn, (uint8_t)gain); + } + + return 0; +} + +int vgs_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + struct bt_conn *conn = hf->acl; + uint32_t gain; + int err; + + err = at_get_number(hf_at, &gain); + if (err) { + LOG_ERR("could not get the speaker gain"); + return err; + } + + if (gain > BT_HFP_HF_VGS_GAIN_MAX) { + LOG_ERR("Invalid speaker gain (%d > %d)", gain, BT_HFP_HF_VGS_GAIN_MAX); + return -EINVAL; + } + + if (bt_hf->vgs) { + bt_hf->vgs(conn, (uint8_t)gain); + } + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + static const struct unsolicited { const char *cmd; enum at_cmd_type type; @@ -563,6 +615,10 @@ static const struct unsolicited { #if defined(CONFIG_BT_HFP_HF_CLI) { "CLIP", AT_CMD_TYPE_UNSOLICITED, clip_handle }, #endif /* CONFIG_BT_HFP_HF_CLI */ +#if defined(CONFIG_BT_HFP_HF_VOLUME) + { "VGM", AT_CMD_TYPE_UNSOLICITED, vgm_handle }, + { "VGS", AT_CMD_TYPE_UNSOLICITED, vgs_handle }, +#endif /* CONFIG_BT_HFP_HF_VOLUME */ }; static const struct unsolicited *hfp_hf_unsol_lookup(struct at_client *hf_at) diff --git a/subsys/bluetooth/host/classic/hfp_internal.h b/subsys/bluetooth/host/classic/hfp_internal.h index 3a9a00c208ac1..09252ed699bcd 100644 --- a/subsys/bluetooth/host/classic/hfp_internal.h +++ b/subsys/bluetooth/host/classic/hfp_internal.h @@ -79,6 +79,11 @@ #define HF_MAX_BUF_LEN BT_HF_CLIENT_MAX_PDU #define HF_MAX_AG_INDICATORS 20 +#define BT_HFP_HF_VGM_GAIN_MAX 15 +#define BT_HFP_HF_VGM_GAIN_MIN 0 + +#define BT_HFP_HF_VGS_GAIN_MAX 15 +#define BT_HFP_HF_VGS_GAIN_MIN 0 /* bt_hfp_hf flags: the flags defined here represent hfp hf parameters */ enum { From 968e106e19e801e4f4438c192546b94c0e37d5ce Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Thu, 25 Jul 2024 19:16:29 +0800 Subject: [PATCH 07/76] Bluetooth: HFP_HF: Add function to set vgm and vgs Add function bt_hfp_hf_vgm to configure Gain of Microphone. Add function bt_hfp_hf_vgs to configure Gain of Speaker. These functions are controlled by configuration `CONFIG_BT_HFP_HF_VOLUME`. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 38 +++++++ subsys/bluetooth/host/classic/hfp_hf.c | 108 +++++++++++++++++++ subsys/bluetooth/host/classic/hfp_internal.h | 2 + 3 files changed, 148 insertions(+) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index 0fe676cb96a52..f94d2213775e5 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -231,6 +231,44 @@ int bt_hfp_hf_send_cmd(struct bt_conn *conn, enum bt_hfp_hf_at_cmd cmd); */ int bt_hfp_hf_cli(struct bt_conn *conn, bool enable); +/** @brief Handsfree HF set Gain of Microphone (VGM) + * + * set Gain of Microphone (VGM). + * The AT command `AT+VGM=` will be sent to the AG to report its + * current microphone gain level setting to the AG. + * `` is a decimal numeric constant, relating to a particular + * (implementation dependent) volume level controlled by the HF. + * This command does not change the microphone gain of the AG; it simply + * indicates the current value of the microphone gain in the HF. + * If @kconfig{CONFIG_BT_HFP_HF_VOLUME} is not enabled, the error `-ENOTSUP` + * will be returned if the function called. + * + * @param conn Connection object. + * @param gain Gain of microphone. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_vgm(struct bt_conn *conn, uint8_t gain); + +/** @brief Handsfree HF set Gain of Speaker (VGS) + * + * set Gain of Speaker (VGS). + * The AT command `AT+VGS=` will be sent to the AG to report its + * current speaker gain level setting to the AG. + * `` is a decimal numeric constant, relating to a particular + * (implementation dependent) volume level controlled by the HF. + * This command does not change the speaker gain of the AG; it simply + * indicates the current value of the speaker gain in the HF. + * If @kconfig{CONFIG_BT_HFP_HF_VOLUME} is not enabled, the error `-ENOTSUP` + * will be returned if the function called. + * + * @param conn Connection object. + * @param gain Gain of speaker. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_vgs(struct bt_conn *conn, uint8_t gain); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index a42652212c78c..a002221bbad21 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -918,6 +918,114 @@ int bt_hfp_hf_cli(struct bt_conn *conn, bool enable) #endif /* CONFIG_BT_HFP_HF_CLI */ } +#if defined(CONFIG_BT_HFP_HF_VOLUME) +static int vgm_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("VGM set (result %d) on %p", result, hf); + + /* AT+VGM is done. */ + return 0; +} +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + +int bt_hfp_hf_vgm(struct bt_conn *conn, uint8_t gain) +{ +#if defined(CONFIG_BT_HFP_HF_VOLUME) + struct bt_hfp_hf *hf; + int err; + + LOG_DBG(""); + + if (gain > BT_HFP_HF_VGM_GAIN_MAX) { + LOG_ERR("Invalid gain %d>%d", gain, BT_HFP_HF_VGM_GAIN_MAX); + return -EINVAL; + } + + if (!conn) { + LOG_ERR("Invalid connection"); + return -ENOTCONN; + } + + hf = bt_hfp_hf_lookup_bt_conn(conn); + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + hf->vgm = gain; + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + return 0; + } + + err = hfp_hf_send_cmd(hf, NULL, vgm_finish, "AT+VGM=%d", gain); + if (err < 0) { + LOG_ERR("HFP HF VGM set failed on %p", hf); + } + + return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_VOLUME */ +} + +#if defined(CONFIG_BT_HFP_HF_VOLUME) +static int vgs_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("VGS set (result %d) on %p", result, hf); + + /* AT+VGS is done. */ + return 0; +} +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + +int bt_hfp_hf_vgs(struct bt_conn *conn, uint8_t gain) +{ +#if defined(CONFIG_BT_HFP_HF_VOLUME) + struct bt_hfp_hf *hf; + int err; + + LOG_DBG(""); + + if (gain > BT_HFP_HF_VGM_GAIN_MAX) { + LOG_ERR("Invalid gain %d>%d", gain, BT_HFP_HF_VGM_GAIN_MAX); + return -EINVAL; + } + + if (!conn) { + LOG_ERR("Invalid connection"); + return -ENOTCONN; + } + + hf = bt_hfp_hf_lookup_bt_conn(conn); + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + hf->vgs = gain; + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + return 0; + } + + err = hfp_hf_send_cmd(hf, NULL, vgs_finish, "AT+VGS=%d", gain); + if (err < 0) { + LOG_ERR("HFP HF VGS set failed on %p", hf); + } + + return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_VOLUME */ +} + static void hfp_hf_connected(struct bt_rfcomm_dlc *dlc) { struct bt_hfp_hf *hf = CONTAINER_OF(dlc, struct bt_hfp_hf, rfcomm_dlc); diff --git a/subsys/bluetooth/host/classic/hfp_internal.h b/subsys/bluetooth/host/classic/hfp_internal.h index 09252ed699bcd..0a09ec737a2f2 100644 --- a/subsys/bluetooth/host/classic/hfp_internal.h +++ b/subsys/bluetooth/host/classic/hfp_internal.h @@ -106,6 +106,8 @@ struct bt_hfp_hf { struct at_client at; uint32_t hf_features; uint32_t ag_features; + uint8_t vgm; + uint8_t vgs; int8_t ind_table[HF_MAX_AG_INDICATORS]; ATOMIC_DEFINE(flags, BT_HFP_HF_NUM_FLAGS); From 5ce5353b46620b73e6950774d9a9bfc11c16cdbf Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Fri, 26 Jul 2024 09:51:17 +0800 Subject: [PATCH 08/76] Bluetooth: HFP_AG: Fix AT+CLIP parse issue The AT command `AT+CLIP=1` or `AT+CLIP=0` cannot be parsed correctly. Check `=` before get AT command value. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/hfp_ag.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index c9f92f1618cf4..f19b7b417a520 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -1421,6 +1421,10 @@ static int bt_hfp_ag_clip_handler(struct bt_hfp_ag *ag, struct net_buf *buf) int err; uint32_t clip; + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + err = get_number(buf, &clip); if (err != 0) { return -ENOTSUP; From 5bb64b21d478a2c88fbc189e1f906269e1b2e00b Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Fri, 26 Jul 2024 10:10:23 +0800 Subject: [PATCH 09/76] Bluetooth: HFP_AG: Change log message to LOG_DBG It is a debug log message. It should be logged by LOG_DBG instead of LOG_ERR. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/hfp_ag.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index f19b7b417a520..f4c5935c26524 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -1601,7 +1601,7 @@ static void hfp_ag_sent(struct bt_rfcomm_dlc *dlc, int err) } tx = CONTAINER_OF(node, struct bt_ag_tx, node); - LOG_ERR("Completed pending tx %p", tx); + LOG_DBG("Completed pending tx %p", tx); /* Restart the tx work */ k_work_reschedule(&ag->tx_work, K_NO_WAIT); From e7411b5ec9e5f0552da7a6d7049346bba6266d44 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Fri, 26 Jul 2024 11:54:35 +0800 Subject: [PATCH 10/76] Bluetooth: HFP_AG: Improve the call terminate All set flags are cleared when call is terminated. But for case that SLC is not disconnected, only call related flags should be cleared. Just clear call related flags if the call is terminated. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/hfp_ag.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index f4c5935c26524..043b3c1eff3cc 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -176,7 +176,8 @@ static void bt_hfp_ag_set_call_state(struct bt_hfp_ag *ag, bt_hfp_call_state_t c switch (call_state) { case BT_HFP_CALL_TERMINATE: - atomic_clear(ag->flags); + atomic_clear_bit(ag->flags, BT_HFP_AG_CREATING_SCO); + atomic_clear_bit(ag->flags, BT_HFP_AG_INCOMING_CALL); k_work_cancel_delayable(&ag->deferred_work); break; case BT_HFP_CALL_OUTGOING: From 9ec398d74f7f11c916a8cdd42e18ad403a67fb6b Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Mon, 2 Sep 2024 10:38:07 +0800 Subject: [PATCH 11/76] Bluetooth: HFP_HF: Notify in-band ring setting Handle the unsolicited result code +BSIR issued by the AG to indicate to the HF that the in-band ring tone setting has been locally changed. Add a callback `inband_ring` to notify the in-band ring tone setting. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 11 ++++++++++ subsys/bluetooth/host/classic/hfp_hf.c | 26 +++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index f94d2213775e5..fd9d505067f70 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -192,6 +192,17 @@ struct bt_hfp_hf_cb { * @param gain Speaker gain. */ void (*vgs)(struct bt_conn *conn, uint8_t gain); + /** HF in-band ring tone notification callback to application + * + * If this callback is provided it will be called whenever there + * is a unsolicited result code +BSIR issued by the AG to + * indicate to the HF that the in-band ring tone setting + * has been locally changed. + * + * @param conn Connection object. + * @param inband In-band ring tone status from the AG. + */ + void (*inband_ring)(struct bt_conn *conn, bool inband); }; /** @brief Register HFP HF profile diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index a002221bbad21..7691a31c17ab5 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -605,6 +605,31 @@ int vgs_handle(struct at_client *hf_at) } #endif /* CONFIG_BT_HFP_HF_VOLUME */ +int bsir_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + struct bt_conn *conn = hf->acl; + uint32_t inband; + int err; + + err = at_get_number(hf_at, &inband); + if (err) { + LOG_ERR("could not get bsir value"); + return err; + } + + if (inband > 1) { + LOG_ERR("Invalid %d bsir value", inband); + return -EINVAL; + } + + if (bt_hf->inband_ring) { + bt_hf->inband_ring(conn, (bool)inband); + } + + return 0; +} + static const struct unsolicited { const char *cmd; enum at_cmd_type type; @@ -619,6 +644,7 @@ static const struct unsolicited { { "VGM", AT_CMD_TYPE_UNSOLICITED, vgm_handle }, { "VGS", AT_CMD_TYPE_UNSOLICITED, vgs_handle }, #endif /* CONFIG_BT_HFP_HF_VOLUME */ + { "BSIR", AT_CMD_TYPE_UNSOLICITED, bsir_handle }, }; static const struct unsolicited *hfp_hf_unsol_lookup(struct at_client *hf_at) From 106343546d0496ad4118af1b73848e0b3a631acd Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Fri, 26 Jul 2024 14:28:40 +0800 Subject: [PATCH 12/76] Bluetooth: HFP_AG: Support remote audio volume control Handle AT commands AT+VGM and AT+VGS. Add function to notify microphone and speaker gain. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_ag.h | 42 +++++++ subsys/bluetooth/host/classic/hfp_ag.c | 133 +++++++++++++++++++++- 2 files changed, 174 insertions(+), 1 deletion(-) diff --git a/include/zephyr/bluetooth/classic/hfp_ag.h b/include/zephyr/bluetooth/classic/hfp_ag.h index 6e5173d9fa4c1..37a47a765ae6d 100644 --- a/include/zephyr/bluetooth/classic/hfp_ag.h +++ b/include/zephyr/bluetooth/classic/hfp_ag.h @@ -160,6 +160,26 @@ struct bt_hfp_ag_cb { * @param ag HFP AG object. */ void (*codec)(struct bt_hfp_ag *ag, uint32_t ids); + + /** HF VGM setting callback + * + * If this callback is provided it will be called whenever the + * VGM gain setting is informed from HF. + * + * @param ag HFP AG object. + * @param gain HF microphone gain value. + */ + void (*vgm)(struct bt_hfp_ag *ag, uint8_t gain); + + /** HF VGS setting callback + * + * If this callback is provided it will be called whenever the + * VGS gain setting is informed from HF. + * + * @param ag HFP AG object. + * @param gain HF speaker gain value. + */ + void (*vgs)(struct bt_hfp_ag *ag, uint8_t gain); }; /** @brief Register HFP AG profile @@ -287,6 +307,28 @@ int bt_hfp_ag_remote_accept(struct bt_hfp_ag *ag); */ int bt_hfp_ag_remote_terminate(struct bt_hfp_ag *ag); +/** @brief Set the HF microphone gain + * + * Set the HF microphone gain + * + * @param ag HFP AG object. + * @param vgm Microphone gain value. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_vgm(struct bt_hfp_ag *ag, uint8_t vgm); + +/** @brief Set the HF speaker gain + * + * Set the HF speaker gain + * + * @param ag HFP AG object. + * @param vgs Speaker gain value. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_vgs(struct bt_hfp_ag *ag, uint8_t vgs); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index 043b3c1eff3cc..0b55d94f1197c 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -1444,6 +1444,64 @@ static int bt_hfp_ag_clip_handler(struct bt_hfp_ag *ag, struct net_buf *buf) return err; } +static int bt_hfp_ag_vgm_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t vgm; + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + err = get_number(buf, &vgm); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + if (vgm > BT_HFP_HF_VGM_GAIN_MAX) { + LOG_ERR("Invalid vgm (%d>%d)", vgm, BT_HFP_HF_VGM_GAIN_MAX); + return -ENOTSUP; + } + + if (bt_ag && bt_ag->vgm) { + bt_ag->vgm(ag, (uint8_t)vgm); + } + return err; +} + +static int bt_hfp_ag_vgs_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t vgs; + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + err = get_number(buf, &vgs); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + if (vgs > BT_HFP_HF_VGS_GAIN_MAX) { + LOG_ERR("Invalid vgs (%d>%d)", vgs, BT_HFP_HF_VGS_GAIN_MAX); + return -ENOTSUP; + } + + if (bt_ag && bt_ag->vgs) { + bt_ag->vgs(ag, (uint8_t)vgs); + } + return err; +} + static struct bt_hfp_ag_at_cmd_handler cmd_handlers[] = { {"AT+BRSF", bt_hfp_ag_brsf_handler}, {"AT+BAC", bt_hfp_ag_bac_handler}, {"AT+CIND", bt_hfp_ag_cind_handler}, {"AT+CMER", bt_hfp_ag_cmer_handler}, @@ -1453,7 +1511,8 @@ static struct bt_hfp_ag_at_cmd_handler cmd_handlers[] = { {"ATA", bt_hfp_ag_ata_handler}, {"AT+COPS", bt_hfp_ag_cops_handler}, {"AT+BCC", bt_hfp_ag_bcc_handler}, {"AT+BCS", bt_hfp_ag_bcs_handler}, {"ATD", bt_hfp_ag_atd_handler}, {"AT+BLDN", bt_hfp_ag_bldn_handler}, - {"AT+CLIP", bt_hfp_ag_clip_handler}, + {"AT+CLIP", bt_hfp_ag_clip_handler}, {"AT+VGM", bt_hfp_ag_vgm_handler}, + {"AT+VGS", bt_hfp_ag_vgs_handler}, }; static void hfp_ag_connected(struct bt_rfcomm_dlc *dlc) @@ -2362,3 +2421,75 @@ int bt_hfp_ag_select_codec(struct bt_hfp_ag *ag, uint8_t id) return err; } + +int bt_hfp_ag_vgm(struct bt_hfp_ag *ag, uint8_t vgm) +{ + int err; + + LOG_DBG(""); + + if (ag == NULL) { + return -EINVAL; + } + + if (vgm > BT_HFP_HF_VGM_GAIN_MAX) { + LOG_ERR("Invalid VGM (%d>%d)", vgm, BT_HFP_HF_VGM_GAIN_MAX); + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + if (!(ag->hf_features & BT_HFP_HF_FEATURE_VOLUME)) { + hfp_ag_unlock(ag); + LOG_ERR("Remote Audio Volume Control is unsupported"); + return -ENOTSUP; + } + hfp_ag_unlock(ag); + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+VGM=%d\r\n", vgm); + if (err) { + LOG_ERR("Fail to notify vgm err :(%d)", err); + } + + return err; +} + +int bt_hfp_ag_vgs(struct bt_hfp_ag *ag, uint8_t vgs) +{ + int err; + + LOG_DBG(""); + + if (ag == NULL) { + return -EINVAL; + } + + if (vgs > BT_HFP_HF_VGS_GAIN_MAX) { + LOG_ERR("Invalid VGM (%d>%d)", vgs, BT_HFP_HF_VGS_GAIN_MAX); + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + if (!(ag->hf_features & BT_HFP_HF_FEATURE_VOLUME)) { + hfp_ag_unlock(ag); + LOG_ERR("Remote Audio Volume Control is unsupported"); + return -ENOTSUP; + } + hfp_ag_unlock(ag); + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+VGS=%d\r\n", vgs); + if (err) { + LOG_ERR("Fail to notify vgs err :(%d)", err); + } + + return err; +} From c7dc90f9a9b1261f55e31270f9d2fb9f2f322d35 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Fri, 26 Jul 2024 16:23:27 +0800 Subject: [PATCH 13/76] Bluetooth: at: support separator "=" Support separator "=". If the separator is "=", only when result code is VGS" or "VGM", the result code is valid. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/at.c | 32 +++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/subsys/bluetooth/host/classic/at.c b/subsys/bluetooth/host/classic/at.c index fb0193920b3dd..a2196ade5df47 100644 --- a/subsys/bluetooth/host/classic/at.c +++ b/subsys/bluetooth/host/classic/at.c @@ -119,24 +119,46 @@ static int get_cmd_value(struct at_client *at, struct net_buf *buf, return 0; } -static int get_response_string(struct at_client *at, struct net_buf *buf, - char stop_byte, enum at_state state) +static bool is_stop_byte(char target, char *stop_string) +{ + return (strchr(stop_string, target) != NULL); +} + +static bool is_vgm_or_vgs(struct at_client *at) +{ + if (!strcmp(at->buf, "VGM")) { + return true; + } + + if (!strcmp(at->buf, "VGS")) { + return true; + } + return false; +} + +static int get_response_string(struct at_client *at, struct net_buf *buf, char *stop_string, + enum at_state state) { int cmd_len = 0; uint8_t pos = at->pos; const char *str = (char *)buf->data; while (cmd_len < buf->len && at->pos != at->buf_max_len) { - if (*str != stop_byte) { + if (!is_stop_byte(*str, stop_string)) { at->buf[at->pos++] = *str; cmd_len++; str++; pos = at->pos; } else { + char stop_byte = at->buf[at->pos]; + cmd_len++; at->buf[at->pos] = '\0'; at->pos = 0U; at->state = state; + if ((stop_byte == '=') && !is_vgm_or_vgs(at)) { + return -EINVAL; + } break; } } @@ -197,7 +219,7 @@ static int at_state_start_lf(struct at_client *at, struct net_buf *buf) static int at_state_get_cmd_string(struct at_client *at, struct net_buf *buf) { - return get_response_string(at, buf, ':', AT_STATE_PROCESS_CMD); + return get_response_string(at, buf, ":=", AT_STATE_PROCESS_CMD); } static bool is_cmer(struct at_client *at) @@ -227,7 +249,7 @@ static int at_state_process_cmd(struct at_client *at, struct net_buf *buf) static int at_state_get_result_string(struct at_client *at, struct net_buf *buf) { - return get_response_string(at, buf, '\r', AT_STATE_PROCESS_RESULT); + return get_response_string(at, buf, "\r", AT_STATE_PROCESS_RESULT); } static bool is_ring(struct at_client *at) From a54182c4ca9f75c12c907e7111538b5857db1cbb Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Fri, 26 Jul 2024 19:38:20 +0800 Subject: [PATCH 14/76] Bluetooth: HFP_HF: Optimize initialization after SLC established Add AT command sending structure to queue all AT commander needed to be sent after the SLC established. After the SLC established, send AT command line one by one. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/hfp_hf.c | 134 +++++++++++++++++-- subsys/bluetooth/host/classic/hfp_internal.h | 3 + 2 files changed, 125 insertions(+), 12 deletions(-) diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 7691a31c17ab5..d797b6056f068 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -712,17 +712,131 @@ int cmd_complete(struct at_client *hf_at, enum at_result result, return 0; } -int cmee_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) +static int send_at_cmee(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + if (hf->ag_features & BT_HFP_AG_FEATURE_EXT_ERR) { + return hfp_hf_send_cmd(hf, NULL, cb, "AT+CMEE=1"); + } else { + return -ENOTSUP; + } +} + +static int at_cmee_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("CMEE set (result %d) on %p", result, hf); + + return 0; +} + +static int send_at_cops(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, NULL, cb, "AT+COPS=3,0"); +} + +static int at_cops_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) { + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("COPS set (result %d) on %p", result, hf); + + return 0; +} + +#if defined(CONFIG_BT_HFP_HF_CLI) +static int send_at_clip(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, NULL, cb, "AT+CLIP=1"); +} + +static int at_clip_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("CLIP set (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_CLI */ + +typedef int (*at_send_t)(struct bt_hfp_hf *hf, at_finish_cb_t cb); + +static struct at_cmd_init +{ + at_send_t send; + at_finish_cb_t finish; + bool disconnect; /* Disconnect if command failed. */ +} cmd_init_list[] = { + {send_at_cmee, at_cmee_finish, false}, + {send_at_cops, at_cops_finish, false}, +#if defined(CONFIG_BT_HFP_HF_CLI) + {send_at_clip, at_clip_finish, false}, +#endif /* CONFIG_BT_HFP_HF_CLI */ +}; + +static int at_cmd_init_start(struct bt_hfp_hf *hf); + +static int at_cmd_init_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + at_finish_cb_t finish; + if (result != AT_RESULT_OK) { - LOG_ERR("SLC Connection ERROR in response"); - return -EINVAL; + LOG_WRN("It is ERROR response of AT command %d.", hf->cmd_init_seq); + } + + if (ARRAY_SIZE(cmd_init_list) > hf->cmd_init_seq) { + finish = cmd_init_list[hf->cmd_init_seq].finish; + if (finish) { + (void)finish(hf_at, result, cme_err); + } + } else { + LOG_ERR("Invalid indicator (%d>=%d)", hf->cmd_init_seq, + ARRAY_SIZE(cmd_init_list)); } + /* Goto next AT command */ + hf->cmd_init_seq++; + (void)at_cmd_init_start(hf); return 0; } +static int at_cmd_init_start(struct bt_hfp_hf *hf) +{ + at_send_t send; + int err = -EINVAL; + + while (ARRAY_SIZE(cmd_init_list) > hf->cmd_init_seq) { + LOG_DBG("Fetch AT command (%d)", hf->cmd_init_seq); + send = cmd_init_list[hf->cmd_init_seq].send; + if (send) { + LOG_DBG("Send AT command"); + err = send(hf, at_cmd_init_finish); + } else { + LOG_WRN("Invalid send func of AT command"); + } + + if (!err) { + break; + } + + LOG_WRN("AT command sending failed"); + if (cmd_init_list[hf->cmd_init_seq].disconnect) { + hfp_hf_send_failed(hf); + break; + } + /* Goto next AT command */ + LOG_WRN("Send next AT command"); + hf->cmd_init_seq++; + } + return err; +} + static void slc_completed(struct at_client *hf_at) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); @@ -734,15 +848,11 @@ static void slc_completed(struct at_client *hf_at) atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED); - if (hf->ag_features & BT_HFP_AG_FEATURE_EXT_ERR) { - if (hfp_hf_send_cmd(hf, NULL, cmee_finish, "AT+CMEE=1") < 0) { - LOG_ERR("Error Sending AT+CMEE"); - } + /* Start with first AT command */ + hf->cmd_init_seq = 0; + if (at_cmd_init_start(hf)) { + LOG_ERR("Fail to start AT command initialization"); } - -#if defined(CONFIG_BT_HFP_HF_CLI) - (void)bt_hfp_hf_cli(conn, true); -#endif /* CONFIG_BT_HFP_HF_CLI */ } int cmer_finish(struct at_client *hf_at, enum at_result result, diff --git a/subsys/bluetooth/host/classic/hfp_internal.h b/subsys/bluetooth/host/classic/hfp_internal.h index 0a09ec737a2f2..ddca2a551c415 100644 --- a/subsys/bluetooth/host/classic/hfp_internal.h +++ b/subsys/bluetooth/host/classic/hfp_internal.h @@ -110,6 +110,9 @@ struct bt_hfp_hf { uint8_t vgs; int8_t ind_table[HF_MAX_AG_INDICATORS]; + /* AT command initialization indicator */ + uint8_t cmd_init_seq; + ATOMIC_DEFINE(flags, BT_HFP_HF_NUM_FLAGS); }; From 493752416b591cf9c5373409208ad127bf60b0b2 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Mon, 2 Sep 2024 10:43:29 +0800 Subject: [PATCH 15/76] Bluetooth: HFP_HF: Send AT+VGM and AT+VGS after SLC established Send AT command AT+VGM and AT+VGS after SLC established. The `VGM` and `VGS` can be set by calling function `bt_hfp_hf_vgs` and `bt_hfp_hf_vgs` in HF connection callback. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 16 +++++++--- subsys/bluetooth/host/classic/hfp_hf.c | 36 +++++++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index fd9d505067f70..4d19bcb35b2d8 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -242,9 +242,9 @@ int bt_hfp_hf_send_cmd(struct bt_conn *conn, enum bt_hfp_hf_at_cmd cmd); */ int bt_hfp_hf_cli(struct bt_conn *conn, bool enable); -/** @brief Handsfree HF set Gain of Microphone (VGM) +/** @brief Handsfree HF report Gain of Microphone (VGM) * - * set Gain of Microphone (VGM). + * Report Gain of Microphone (VGM). * The AT command `AT+VGM=` will be sent to the AG to report its * current microphone gain level setting to the AG. * `` is a decimal numeric constant, relating to a particular @@ -253,6 +253,10 @@ int bt_hfp_hf_cli(struct bt_conn *conn, bool enable); * indicates the current value of the microphone gain in the HF. * If @kconfig{CONFIG_BT_HFP_HF_VOLUME} is not enabled, the error `-ENOTSUP` * will be returned if the function called. + * For "Volume Level Synchronization", the HF application could call + * the function to set VGM gain value in HF connection callback + * function. Then after the HF connection callback returned, VGM gain + * will be sent to HFP AG. * * @param conn Connection object. * @param gain Gain of microphone. @@ -261,9 +265,9 @@ int bt_hfp_hf_cli(struct bt_conn *conn, bool enable); */ int bt_hfp_hf_vgm(struct bt_conn *conn, uint8_t gain); -/** @brief Handsfree HF set Gain of Speaker (VGS) +/** @brief Handsfree HF report Gain of Speaker (VGS) * - * set Gain of Speaker (VGS). + * Report Gain of Speaker (VGS). * The AT command `AT+VGS=` will be sent to the AG to report its * current speaker gain level setting to the AG. * `` is a decimal numeric constant, relating to a particular @@ -272,6 +276,10 @@ int bt_hfp_hf_vgm(struct bt_conn *conn, uint8_t gain); * indicates the current value of the speaker gain in the HF. * If @kconfig{CONFIG_BT_HFP_HF_VOLUME} is not enabled, the error `-ENOTSUP` * will be returned if the function called. + * For "Volume Level Synchronization", the HF application could call + * the function to set VGS gain value in HF connection callback + * function. Then after the HF connection callback returned, VGS gain + * will be sent to HFP AG. * * @param conn Connection object. * @param gain Gain of speaker. diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index d797b6056f068..843eb59cef6e7 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -763,6 +763,38 @@ static int at_clip_finish(struct at_client *hf_at, enum at_result result, } #endif /* CONFIG_BT_HFP_HF_CLI */ +#if defined(CONFIG_BT_HFP_HF_VOLUME) +static int send_at_vgm(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, NULL, cb, "AT+VGM=%d", hf->vgm); +} + +static int at_vgm_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("VGM set (result %d) on %p", result, hf); + + return 0; +} + +static int send_at_vgs(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, NULL, cb, "AT+VGS=%d", hf->vgs); +} + +static int at_vgs_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("VGS set (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + typedef int (*at_send_t)(struct bt_hfp_hf *hf, at_finish_cb_t cb); static struct at_cmd_init @@ -771,6 +803,10 @@ static struct at_cmd_init at_finish_cb_t finish; bool disconnect; /* Disconnect if command failed. */ } cmd_init_list[] = { +#if defined(CONFIG_BT_HFP_HF_VOLUME) + {send_at_vgm, at_vgm_finish, false}, + {send_at_vgs, at_vgs_finish, false}, +#endif /* CONFIG_BT_HFP_HF_VOLUME */ {send_at_cmee, at_cmee_finish, false}, {send_at_cops, at_cops_finish, false}, #if defined(CONFIG_BT_HFP_HF_CLI) From 14600af7534a558d815c964b46bded4b5b5617df Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Fri, 26 Jul 2024 21:03:25 +0800 Subject: [PATCH 16/76] Bluetooth: HHF_HF: Support read network operator Add function `bt_hfp_hf_get_operator` to read the currently network operator of AG. Add callback `operator` to notify the application of the response. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 27 +++++++ subsys/bluetooth/host/classic/hfp_hf.c | 85 +++++++++++++++++++++++ 2 files changed, 112 insertions(+) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index 4d19bcb35b2d8..41cb0b52d0cbf 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -203,6 +203,22 @@ struct bt_hfp_hf_cb { * @param inband In-band ring tone status from the AG. */ void (*inband_ring)(struct bt_conn *conn, bool inband); + /** HF network operator notification callback to application + * + * If this callback is provided it will be called whenever there + * is a response code +COPS issued by the AG to + * response the AT+COPS? command issued by the HF by calling + * function `bt_hfp_hf_get_operator`. + * + * @param conn Connection object. + * @param mode Current mode. + * @param format Format of the `operator` parameter string. + * It should be zero. + * @param operator A string in alphanumeric format + * representing the name of the network + * operator. + */ + void (*operator)(struct bt_conn *conn, uint8_t mode, uint8_t format, char *operator); }; /** @brief Register HFP HF profile @@ -288,6 +304,17 @@ int bt_hfp_hf_vgm(struct bt_conn *conn, uint8_t gain); */ int bt_hfp_hf_vgs(struct bt_conn *conn, uint8_t gain); +/** @brief Handsfree HF requests currently selected operator + * + * Send the AT+COPS? (Read) command to find the currently + * selected operator. + * + * @param conn Connection object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_get_operator(struct bt_conn *conn); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 843eb59cef6e7..002cac4c08f73 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -1198,6 +1198,91 @@ int bt_hfp_hf_vgs(struct bt_conn *conn, uint8_t gain) #endif /* CONFIG_BT_HFP_HF_VOLUME */ } +static int cops_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + uint32_t mode; + uint32_t format; + char *operator; + int err; + + err = at_get_number(hf_at, &mode); + if (err < 0) { + LOG_ERR("Error getting value"); + return err; + } + + err = at_get_number(hf_at, &format); + if (err < 0) { + LOG_ERR("Error getting value"); + return err; + } + + operator = at_get_string(hf_at); + + if (bt_hf && bt_hf->operator) { + bt_hf->operator(hf->acl, (uint8_t)mode, (uint8_t)format, operator); + } + + return 0; +} + +static int cops_resp(struct at_client *hf_at, struct net_buf *buf) +{ + int err; + + LOG_DBG(""); + + err = at_parse_cmd_input(hf_at, buf, "COPS", cops_handle, + AT_CMD_TYPE_NORMAL); + if (err < 0) { + LOG_ERR("Cannot parse response of AT+COPS?"); + return err; + } + + return 0; +} + +static int cops_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+COPS? (result %d) on %p", result, hf); + + return 0; +} + +int bt_hfp_hf_get_operator(struct bt_conn *conn) +{ + struct bt_hfp_hf *hf; + int err; + + LOG_DBG(""); + + if (!conn) { + LOG_ERR("Invalid connection"); + return -ENOTCONN; + } + + hf = bt_hfp_hf_lookup_bt_conn(conn); + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + return 0; + } + + err = hfp_hf_send_cmd(hf, cops_resp, cops_finish, "AT+COPS?"); + if (err < 0) { + LOG_ERR("Fail to read the currently selected operator on %p", hf); + } + + return err; +} + static void hfp_hf_connected(struct bt_rfcomm_dlc *dlc) { struct bt_hfp_hf *hf = CONTAINER_OF(dlc, struct bt_hfp_hf, rfcomm_dlc); From 81a67493c50d1fceb3b9d35024e0bacb58d2cb6e Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Fri, 26 Jul 2024 21:18:25 +0800 Subject: [PATCH 17/76] Bluetooth: HFP_AG: Support network operator update Expose a function to set the network operator and mode. Response the AT+COPS? command with set mode and operator. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_ag.h | 15 +++++++++++++++ subsys/bluetooth/host/classic/hfp_ag.c | 6 ++++-- subsys/bluetooth/host/classic/hfp_ag_internal.h | 1 + 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/include/zephyr/bluetooth/classic/hfp_ag.h b/include/zephyr/bluetooth/classic/hfp_ag.h index 37a47a765ae6d..65bb39327de86 100644 --- a/include/zephyr/bluetooth/classic/hfp_ag.h +++ b/include/zephyr/bluetooth/classic/hfp_ag.h @@ -329,6 +329,21 @@ int bt_hfp_ag_vgm(struct bt_hfp_ag *ag, uint8_t vgm); */ int bt_hfp_ag_vgs(struct bt_hfp_ag *ag, uint8_t vgs); +/** @brief Set currently network operator + * + * Set currently network operator. + * + * @param ag HFP AG object. + * @param mode Current mode and provides no information with regard + * to the name of the operator. + * @param name A string in alphanumeric format representing the + * name of the network operator. This string shall + * not exceed 16 characters. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_set_operator(struct bt_hfp_ag *ag, uint8_t mode, char *name); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index 0b55d94f1197c..c066ad95d977a 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -1232,7 +1232,8 @@ static int bt_hfp_ag_cops_handler(struct bt_hfp_ag *ag, struct net_buf *buf) return -ENOTSUP; } - err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+COPS:%d,%d,\"%s\"\r\n", 0, 0, ag->operator); + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+COPS:%d,%d,\"%s\"\r\n", + ag->mode, 0, ag->operator); if (err != 0) { LOG_ERR("Fail to send err :(%d)", err); } @@ -2360,7 +2361,7 @@ int bt_hfp_ag_set_indicator(struct bt_hfp_ag *ag, enum bt_hfp_ag_indicator index return err; } -int bt_hfp_ag_set_operator(struct bt_hfp_ag *ag, char *name) +int bt_hfp_ag_set_operator(struct bt_hfp_ag *ag, uint8_t mode, char *name) { int len; @@ -2380,6 +2381,7 @@ int bt_hfp_ag_set_operator(struct bt_hfp_ag *ag, char *name) len = MIN(sizeof(ag->operator) - 1, len); memcpy(ag->operator, name, len); ag->operator[len] = '\0'; + ag->mode = mode; hfp_ag_unlock(ag); return 0; diff --git a/subsys/bluetooth/host/classic/hfp_ag_internal.h b/subsys/bluetooth/host/classic/hfp_ag_internal.h index 94191140da0a9..4bca41b876518 100644 --- a/subsys/bluetooth/host/classic/hfp_ag_internal.h +++ b/subsys/bluetooth/host/classic/hfp_ag_internal.h @@ -102,6 +102,7 @@ struct bt_hfp_ag { uint8_t hf_indicator_value[HFP_HF_IND_MAX]; /* operator */ + uint8_t mode; char operator[AT_COPS_OPERATOR_MAX_LEN + 1]; /* SCO Connection Object */ From 3bf2b610b148c2f9269d3f5f8cd65ca2d69f067e Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Mon, 29 Jul 2024 10:11:35 +0800 Subject: [PATCH 18/76] Bluetooth: HFP_HF: Add a function to accept the call Add a function `bt_hfp_hf_accept` to accept the incoming call. Add a flag BT_HFP_HF_FLAG_INCOMING to identify the incoming call. Set the flag if the call_setup value is 1. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 10 ++++ subsys/bluetooth/host/classic/hfp_hf.c | 50 ++++++++++++++++++-- subsys/bluetooth/host/classic/hfp_internal.h | 1 + 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index 41cb0b52d0cbf..054ccf771eb38 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -315,6 +315,16 @@ int bt_hfp_hf_vgs(struct bt_conn *conn, uint8_t gain); */ int bt_hfp_hf_get_operator(struct bt_conn *conn); +/** @brief Handsfree HF accept the incoming call + * + * Send the ATA command to accept the incoming call. + * + * @param conn Connection object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_accept(struct bt_conn *conn); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 002cac4c08f73..bcfe7a1cce9d7 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -422,6 +422,9 @@ void ag_indicator_handle_values(struct at_client *hf_at, uint32_t index, } break; case HF_CALL_SETUP_IND: + atomic_set_bit_to(hf->flags, BT_HFP_HF_FLAG_INCOMING, + value == BT_HFP_CALL_SETUP_INCOMING); + if (bt_hf->call_setup) { bt_hf->call_setup(conn, value); } @@ -1283,6 +1286,47 @@ int bt_hfp_hf_get_operator(struct bt_conn *conn) return err; } +static int ata_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("ATA (result %d) on %p", result, hf); + + return 0; +} + +int bt_hfp_hf_accept(struct bt_conn *conn) +{ + struct bt_hfp_hf *hf; + int err; + + LOG_DBG(""); + + if (!conn) { + LOG_ERR("Invalid connection"); + return -ENOTCONN; + } + + hf = bt_hfp_hf_lookup_bt_conn(conn); + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING)) { + LOG_ERR("No incoming call setup in progress"); + return -EINVAL; + } + + err = hfp_hf_send_cmd(hf, NULL, ata_finish, "ATA"); + if (err < 0) { + LOG_ERR("Fail to accept the incoming call on %p", hf); + } + + return err; +} + static void hfp_hf_connected(struct bt_rfcomm_dlc *dlc) { struct bt_hfp_hf *hf = CONTAINER_OF(dlc, struct bt_hfp_hf, rfcomm_dlc); @@ -1318,8 +1362,8 @@ static void hfp_hf_sent(struct bt_rfcomm_dlc *dlc, int err) LOG_DBG("DLC %p sent cb (err %d)", dlc, err); } -static int bt_hfp_hf_accept(struct bt_conn *conn, struct bt_rfcomm_server *server, - struct bt_rfcomm_dlc **dlc) +static int hfp_hf_accept(struct bt_conn *conn, struct bt_rfcomm_server *server, + struct bt_rfcomm_dlc **dlc) { int i; static struct bt_rfcomm_dlc_ops ops = { @@ -1419,7 +1463,7 @@ static void hfp_hf_init(void) { static struct bt_rfcomm_server chan = { .channel = BT_RFCOMM_CHAN_HFP_HF, - .accept = bt_hfp_hf_accept, + .accept = hfp_hf_accept, }; bt_rfcomm_server_register(&chan); diff --git a/subsys/bluetooth/host/classic/hfp_internal.h b/subsys/bluetooth/host/classic/hfp_internal.h index ddca2a551c415..9d080842f960a 100644 --- a/subsys/bluetooth/host/classic/hfp_internal.h +++ b/subsys/bluetooth/host/classic/hfp_internal.h @@ -89,6 +89,7 @@ enum { BT_HFP_HF_FLAG_CONNECTED, /* HFP HF SLC Established */ BT_HFP_HF_FLAG_TX_ONGOING, /* HFP HF TX is ongoing */ + BT_HFP_HF_FLAG_INCOMING, /* HFP HF call incoming */ /* Total number of flags - must be at the end of the enum */ BT_HFP_HF_NUM_FLAGS, }; From 265c247d9b210469c8e3f33fb9efda289c82e017 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Mon, 29 Jul 2024 10:38:56 +0800 Subject: [PATCH 19/76] Bluetooth: HFP_AG: Add dedicated SDP features definition Add a dedicated SDP features definition. Set feature "Three-way calling" by default both in SDP features definition and AT command features definition. Because it is set by default in Spec. Signed-off-by: Lyle Zhu --- .../bluetooth/host/classic/hfp_ag_internal.h | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/subsys/bluetooth/host/classic/hfp_ag_internal.h b/subsys/bluetooth/host/classic/hfp_ag_internal.h index 4bca41b876518..1a89fe74abeb1 100644 --- a/subsys/bluetooth/host/classic/hfp_ag_internal.h +++ b/subsys/bluetooth/host/classic/hfp_ag_internal.h @@ -9,8 +9,27 @@ * SPDX-License-Identifier: Apache-2.0 */ +/* HFP AG Features in SDP */ +#define BT_HFP_AG_SDP_FEATURE_3WAY_CALL BIT(0) /* Three-way calling */ +#define BT_HFP_AG_SDP_FEATURE_ECNR BIT(1) /* EC and/or NR function */ +#define BT_HFP_AG_SDP_FEATURE_VOICE_RECG BIT(2) /* Voice recognition */ +#define BT_HFP_AG_SDP_FEATURE_INBAND_RINGTONE BIT(3) /* In-band ring tone capability */ +#define BT_HFP_AG_SDP_FEATURE_VOICE_TAG BIT(4) /* Attach no. to voice tag */ +#define BT_HFP_AG_SDP_FEATURE_WBS BIT(5) /* Wide Band Speech */ +#define BT_HFP_AG_SDP_FEATURE_ENH_VOICE_RECG BIT(6) /* Enhanced Voice Recognition Status */ +#define BT_HFP_AG_SDP_FEATURE_VOICE_RECG_TEXT BIT(7) /* Voice Recognition Text */ +#define BT_HFP_AG_SDP_FEATURE_SUPER_WBS BIT(8) /* Super Wide Band Speech */ + /* HFP AG Supported features */ -#define BT_HFP_AG_SUPPORTED_FEATURES (BT_HFP_AG_FEATURE_INBAND_RINGTONE | BT_HFP_AG_FEATURE_EXT_ERR) +#define BT_HFP_AG_SUPPORTED_FEATURES (\ + BT_HFP_AG_FEATURE_3WAY_CALL | \ + BT_HFP_AG_FEATURE_INBAND_RINGTONE | \ + BT_HFP_AG_FEATURE_EXT_ERR) + +/* HFP AG Supported features in SDP */ +#define BT_HFP_AG_SDP_SUPPORTED_FEATURES (\ + BT_HFP_AG_SDP_FEATURE_3WAY_CALL | \ + BT_HFP_AG_SDP_FEATURE_INBAND_RINGTONE) /* bt_hfp_ag flags: the flags defined here represent HFP AG parameters */ enum { From afd5b3be3848ae9cd8d69bab07e30a6f27186d1c Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Mon, 29 Jul 2024 10:40:51 +0800 Subject: [PATCH 20/76] Bluetooth: HFP_AG: Improve extended error result codes Add a configuration BT_HFP_AG_EXT_ERR for this feature. If the feature is not set, response the ERROR to the peer if the AT command AT+CMEE received. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/Kconfig | 6 ++++++ subsys/bluetooth/host/classic/hfp_ag.c | 7 +++++-- subsys/bluetooth/host/classic/hfp_ag_internal.h | 8 +++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/subsys/bluetooth/host/classic/Kconfig b/subsys/bluetooth/host/classic/Kconfig index c9f7738b5ec2e..792907e93679c 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -189,6 +189,12 @@ config BT_HFP_HF_VOLUME endif # BT_HFP_HF if BT_HFP_AG +config BT_HFP_AG_EXT_ERR + bool "Extended Error Result Codes for HFP AG [EXPERIMENTAL]" + default y + help + This option enables Extended Error Result Codes for HFP AG + config BT_HFP_AG_TX_BUF_COUNT int "Maximum number of TX buffers for HFP AG [EXPERIMENTAL]" default BT_RFCOMM_TX_MAX diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index c066ad95d977a..7299b62d25820 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -751,9 +751,12 @@ static int bt_hfp_ag_cmee_handler(struct bt_hfp_ag *ag, struct net_buf *buf) return -ENOTSUP; } - atomic_set_bit_to(ag->flags, BT_HFP_AG_CMEE_ENABLE, cmee == 1); + if (BT_HFP_AG_FEATURE_EXT_ERR_ENABLE) { + atomic_set_bit_to(ag->flags, BT_HFP_AG_CMEE_ENABLE, cmee == 1); + return 0; + } - return 0; + return -ENOTSUP; } static int hfp_ag_update_indicator(struct bt_hfp_ag *ag, enum bt_hfp_ag_indicator index, diff --git a/subsys/bluetooth/host/classic/hfp_ag_internal.h b/subsys/bluetooth/host/classic/hfp_ag_internal.h index 1a89fe74abeb1..d5f6977454ac1 100644 --- a/subsys/bluetooth/host/classic/hfp_ag_internal.h +++ b/subsys/bluetooth/host/classic/hfp_ag_internal.h @@ -20,11 +20,17 @@ #define BT_HFP_AG_SDP_FEATURE_VOICE_RECG_TEXT BIT(7) /* Voice Recognition Text */ #define BT_HFP_AG_SDP_FEATURE_SUPER_WBS BIT(8) /* Super Wide Band Speech */ +#if defined(CONFIG_BT_HFP_AG_EXT_ERR) +#define BT_HFP_AG_FEATURE_EXT_ERR_ENABLE BT_HFP_AG_FEATURE_EXT_ERR +#else +#define BT_HFP_AG_FEATURE_EXT_ERR_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_EXT_ERR */ + /* HFP AG Supported features */ #define BT_HFP_AG_SUPPORTED_FEATURES (\ BT_HFP_AG_FEATURE_3WAY_CALL | \ BT_HFP_AG_FEATURE_INBAND_RINGTONE | \ - BT_HFP_AG_FEATURE_EXT_ERR) + BT_HFP_AG_FEATURE_EXT_ERR_ENABLE) /* HFP AG Supported features in SDP */ #define BT_HFP_AG_SDP_SUPPORTED_FEATURES (\ From 014cc50a2789590090f647a4663266baa6e9a132 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Mon, 29 Jul 2024 12:51:49 +0800 Subject: [PATCH 21/76] Bluetooth: HFP_AG: Improve codec negotiation Add a configuration BT_HFP_AG_CODEC_NEG to configure the feature. Add a function bt_hfp_ag_audio_connect to setup the codec negotiation procedure. Improve AT+BAC and AT+BCC handle. Add callback codec_negotiate to notify the application of codec negotiation result. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_ag.h | 49 +++++++++ subsys/bluetooth/host/classic/Kconfig | 5 + subsys/bluetooth/host/classic/hfp_ag.c | 101 ++++++++++++++---- .../bluetooth/host/classic/hfp_ag_internal.h | 10 +- 4 files changed, 145 insertions(+), 20 deletions(-) diff --git a/include/zephyr/bluetooth/classic/hfp_ag.h b/include/zephyr/bluetooth/classic/hfp_ag.h index 65bb39327de86..07f42908e0b44 100644 --- a/include/zephyr/bluetooth/classic/hfp_ag.h +++ b/include/zephyr/bluetooth/classic/hfp_ag.h @@ -161,6 +161,33 @@ struct bt_hfp_ag_cb { */ void (*codec)(struct bt_hfp_ag *ag, uint32_t ids); + /** Codec negotiate callback + * + * If this callback is provided it will be called whenever the + * codec negotiation succeeded or failed. + * + * @param ag HFP AG object. + * @param err Result of codec negotiation. + */ + void (*codec_negotiate)(struct bt_hfp_ag *ag, int err); + + /** Audio connection request callback + * + * If this callback is provided it will be called whenever the + * audio conenction request is triggered by HF. + * When AT+BCC AT command received, it means the procedure of + * establishment of audio connection is triggered by HF. + * If the callback is provided by application, AG needs to + * start the codec connection procedure by calling + * function `bt_hfp_ag_audio_connect` in application layer. + * Or, the codec conenction procedure will be started with + * default codec id `BT_HFP_AG_CODEC_CVSD`. + * + * @param ag HFP AG object. + * @param err Result of codec negotiation. + */ + void (*audio_connect_req)(struct bt_hfp_ag *ag); + /** HF VGM setting callback * * If this callback is provided it will be called whenever the @@ -344,6 +371,28 @@ int bt_hfp_ag_vgs(struct bt_hfp_ag *ag, uint8_t vgs); */ int bt_hfp_ag_set_operator(struct bt_hfp_ag *ag, uint8_t mode, char *name); +/** @brief Create audio connection + * + * Create audio conenction by HFP AG. There are two setups included, + * Codec connection and audio connection. + * The codec connection will be established firstly if the codec + * negotiation are supported by both side. If the passed codec id + * is not same as the last codec connection, the codec connection + * procedure will be triggered. + * After the codec conenction is established, the audio conenction + * will be started. + * The passed codec id could be one of BT_HFP_AG_CODEC_XXX. If the + * codec negotiation feature is supported by both side, the codec id + * could be one of the bitmaps of `ids` notified by callback `codec`. + * Or, the `id` should be BT_HFP_AG_CODEC_CVSD. + * + * @param ag HFP AG object. + * @param id Codec Id. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_audio_connect(struct bt_hfp_ag *ag, uint8_t id); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/Kconfig b/subsys/bluetooth/host/classic/Kconfig index 792907e93679c..35fc0d5af2eab 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -195,6 +195,11 @@ config BT_HFP_AG_EXT_ERR help This option enables Extended Error Result Codes for HFP AG +config BT_HFP_AG_CODEC_NEG + bool "Codec Negotiation for HFP AG [EXPERIMENTAL]" + help + This option enables Codec Negotiation for HFP AG + config BT_HFP_AG_TX_BUF_COUNT int "Maximum number of TX buffers for HFP AG [EXPERIMENTAL]" default BT_RFCOMM_TX_MAX diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index 7299b62d25820..2679c0cb2df12 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -491,10 +491,25 @@ static int bt_hfp_ag_bac_handler(struct bt_hfp_ag *ag, struct net_buf *buf) } } + if (!(codec_ids & BIT(BT_HFP_AG_CODEC_CVSD))) { + return -EOPNOTSUPP; + } + hfp_ag_lock(ag); ag->hf_codec_ids = codec_ids; + ag->selected_codec_id = 0; hfp_ag_unlock(ag); + atomic_set_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED); + + if (atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_CODEC_CONN)) { + /* Codec connection is ended. It needs to be restarted. */ + LOG_DBG("Codec connection is ended. It needs to be restarted."); + if (bt_ag && bt_ag->codec_negotiate) { + bt_ag->codec_negotiate(ag, -EAGAIN); + } + } + if (bt_ag && bt_ag->codec) { bt_ag->codec(ag, ag->hf_codec_ids); } @@ -808,7 +823,11 @@ static void hfp_ag_close_sco(struct bt_hfp_ag *ag) static void bt_hfp_ag_reject_cb(struct bt_hfp_ag *ag, void *user_data) { - hfp_ag_close_sco(ag); + if (!atomic_test_bit(ag->flags, BT_HFP_AG_AUDIO_CONN)) { + LOG_DBG("It is not audio connection"); + hfp_ag_close_sco(ag); + } + bt_hfp_ag_set_call_state(ag, BT_HFP_CALL_TERMINATE); if (bt_ag && bt_ag->reject) { @@ -827,7 +846,11 @@ static void bt_hfp_ag_call_reject(struct bt_hfp_ag *ag, void *user_data) static void bt_hfp_ag_terminate_cb(struct bt_hfp_ag *ag, void *user_data) { - hfp_ag_close_sco(ag); + if (!atomic_test_bit(ag->flags, BT_HFP_AG_AUDIO_CONN)) { + LOG_DBG("It is not audio connection"); + hfp_ag_close_sco(ag); + } + bt_hfp_ag_set_call_state(ag, BT_HFP_CALL_TERMINATE); if (bt_ag && bt_ag->terminate) { @@ -1069,6 +1092,7 @@ static struct bt_conn *bt_hfp_ag_create_sco(struct bt_hfp_ag *ag) static int hfp_ag_open_sco(struct bt_hfp_ag *ag) { bool create_sco; + bt_hfp_call_state_t call_state; if (atomic_test_bit(ag->flags, BT_HFP_AG_CREATING_SCO)) { LOG_WRN("SCO connection is creating!"); @@ -1076,6 +1100,7 @@ static int hfp_ag_open_sco(struct bt_hfp_ag *ag) } hfp_ag_lock(ag); + call_state = ag->call_state; create_sco = (ag->sco_chan.sco == NULL) ? true : false; if (create_sco) { atomic_set_bit(ag->flags, BT_HFP_AG_CREATING_SCO); @@ -1086,13 +1111,21 @@ static int hfp_ag_open_sco(struct bt_hfp_ag *ag) struct bt_conn *sco_conn = bt_hfp_ag_create_sco(ag); atomic_clear_bit(ag->flags, BT_HFP_AG_CREATING_SCO); + atomic_clear_bit(ag->flags, BT_HFP_AG_AUDIO_CONN); if (sco_conn == NULL) { LOG_ERR("Fail to create sco connection!"); return -ENOTCONN; } + atomic_set_bit_to(ag->flags, BT_HFP_AG_AUDIO_CONN, call_state == BT_HFP_CALL_TERMINATE); + LOG_DBG("SCO connection created (%p)", sco_conn); + } else { + if (call_state == BT_HFP_CALL_INCOMING) { + bt_hfp_ag_set_call_state(ag, BT_HFP_CALL_ALERTING); + bt_hfp_ag_call_ringing_cb(ag, true); + } } return 0; @@ -1106,9 +1139,8 @@ static int bt_hfp_ag_codec_select(struct bt_hfp_ag *ag) hfp_ag_lock(ag); if (ag->selected_codec_id == 0) { - LOG_ERR("Codec is invalid"); - hfp_ag_unlock(ag); - return -EINVAL; + LOG_WRN("Codec is invalid, set default value"); + ag->selected_codec_id = BT_HFP_AG_CODEC_CVSD; } if (!(ag->hf_codec_ids & BIT(ag->selected_codec_id))) { @@ -1135,8 +1167,8 @@ static int bt_hfp_ag_create_audio_connection(struct bt_hfp_ag *ag) hfp_ag_unlock(ag); if ((hf_codec_ids != 0) && atomic_test_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED)) { - atomic_set_bit(ag->flags, BT_HFP_AG_CODEC_CONN); err = bt_hfp_ag_codec_select(ag); + atomic_set_bit_to(ag->flags, BT_HFP_AG_CODEC_CONN, err == 0); } else { err = hfp_ag_open_sco(ag); } @@ -1246,6 +1278,7 @@ static int bt_hfp_ag_cops_handler(struct bt_hfp_ag *ag, struct net_buf *buf) static int bt_hfp_ag_bcc_handler(struct bt_hfp_ag *ag, struct net_buf *buf) { +#if BT_HFP_AG_FEATURE_CODEC_NEG_ENABLE int err; if (!is_char(buf, '\r')) { @@ -1253,18 +1286,28 @@ static int bt_hfp_ag_bcc_handler(struct bt_hfp_ag *ag, struct net_buf *buf) } hfp_ag_lock(ag); - if ((ag->selected_codec_id == 0) || - (!(ag->hf_codec_ids & BIT(ag->selected_codec_id))) || - (ag->call_state == BT_HFP_CALL_TERMINATE) || - (ag->sco_chan.sco != NULL)) { + if (ag->selected_codec_id && (!(ag->hf_codec_ids & BIT(ag->selected_codec_id)))) { hfp_ag_unlock(ag); return -ENOTSUP; } + + if (ag->sco_chan.sco) { + hfp_ag_unlock(ag); + return -ECONNREFUSED; + } hfp_ag_unlock(ag); + if (bt_ag && bt_ag->audio_connect_req) { + bt_ag->audio_connect_req(ag); + return 0; + } + err = hfp_ag_next_step(ag, bt_hfp_ag_audio_connection, NULL); - return 0; + return err; +#else + return -ENOTSUP; +#endif /* BT_HFP_AG_FEATURE_CODEC_NEG_ENABLE */ } static void bt_hfp_ag_unit_codec_conn_setup(struct bt_hfp_ag *ag, void *user_data) @@ -1280,6 +1323,7 @@ static int bt_hfp_ag_bcs_handler(struct bt_hfp_ag *ag, struct net_buf *buf) { int err; uint32_t number; + bool codec_conn; if (!is_char(buf, '=')) { return -ENOTSUP; @@ -1310,14 +1354,21 @@ static int bt_hfp_ag_bcs_handler(struct bt_hfp_ag *ag, struct net_buf *buf) } hfp_ag_unlock(ag); - atomic_clear_bit(ag->flags, BT_HFP_AG_CODEC_CONN); + codec_conn = atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_CODEC_CONN); atomic_clear_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED); if (err == 0) { + if (codec_conn && bt_ag && bt_ag->codec_negotiate) { + bt_ag->codec_negotiate(ag, err); + } err = hfp_ag_next_step(ag, bt_hfp_ag_unit_codec_conn_setup, NULL); } else { bt_hfp_call_state_t call_state; + if (codec_conn && bt_ag && bt_ag->codec_negotiate) { + bt_ag->codec_negotiate(ag, err); + } + hfp_ag_lock(ag); call_state = ag->call_state; hfp_ag_unlock(ag); @@ -1880,8 +1931,6 @@ int bt_hfp_ag_connect(struct bt_conn *conn, struct bt_hfp_ag **ag, uint8_t chann k_work_init_delayable(&_ag->tx_work, bt_ag_tx_work); - atomic_set_bit(_ag->flags, BT_HFP_AG_CODEC_CHANGED); - *ag = _ag; } @@ -2390,7 +2439,7 @@ int bt_hfp_ag_set_operator(struct bt_hfp_ag *ag, uint8_t mode, char *name) return 0; } -int bt_hfp_ag_select_codec(struct bt_hfp_ag *ag, uint8_t id) +int bt_hfp_ag_audio_connect(struct bt_hfp_ag *ag, uint8_t id) { int err; @@ -2406,9 +2455,22 @@ int bt_hfp_ag_select_codec(struct bt_hfp_ag *ag, uint8_t id) return -ENOTCONN; } - if (!(ag->hf_codec_ids && BIT(id))) { + if (ag->hf_codec_ids) { + if (!(ag->hf_codec_ids & BIT(id))) { + hfp_ag_unlock(ag); + return -EINVAL; + } + } else { + if (id != BT_HFP_AG_CODEC_CVSD) { + hfp_ag_unlock(ag); + return -ENOTSUP; + } + } + + if (ag->sco_chan.sco) { + LOG_ERR("Audio conenction has been connected"); hfp_ag_unlock(ag); - return -ENOTSUP; + return -ECONNREFUSED; } hfp_ag_unlock(ag); @@ -2417,11 +2479,12 @@ int bt_hfp_ag_select_codec(struct bt_hfp_ag *ag, uint8_t id) } hfp_ag_lock(ag); + if (ag->selected_codec_id != id) { + atomic_set_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED); + } ag->selected_codec_id = id; hfp_ag_unlock(ag); - atomic_set_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED); - err = bt_hfp_ag_create_audio_connection(ag); return err; diff --git a/subsys/bluetooth/host/classic/hfp_ag_internal.h b/subsys/bluetooth/host/classic/hfp_ag_internal.h index d5f6977454ac1..940ee6f0421e8 100644 --- a/subsys/bluetooth/host/classic/hfp_ag_internal.h +++ b/subsys/bluetooth/host/classic/hfp_ag_internal.h @@ -26,11 +26,18 @@ #define BT_HFP_AG_FEATURE_EXT_ERR_ENABLE 0 #endif /* CONFIG_BT_HFP_AG_EXT_ERR */ +#if defined(CONFIG_BT_HFP_AG_CODEC_NEG) +#define BT_HFP_AG_FEATURE_CODEC_NEG_ENABLE BT_HFP_AG_FEATURE_CODEC_NEG +#else +#define BT_HFP_AG_FEATURE_CODEC_NEG_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_CODEC_NEG */ + /* HFP AG Supported features */ #define BT_HFP_AG_SUPPORTED_FEATURES (\ BT_HFP_AG_FEATURE_3WAY_CALL | \ BT_HFP_AG_FEATURE_INBAND_RINGTONE | \ - BT_HFP_AG_FEATURE_EXT_ERR_ENABLE) + BT_HFP_AG_FEATURE_EXT_ERR_ENABLE | \ + BT_HFP_AG_FEATURE_CODEC_NEG_ENABLE) /* HFP AG Supported features in SDP */ #define BT_HFP_AG_SDP_SUPPORTED_FEATURES (\ @@ -45,6 +52,7 @@ enum { BT_HFP_AG_INBAND_RING, /* In-band ring */ BT_HFP_AG_COPS_SET, /* Query Operator selection */ BT_HFP_AG_INCOMING_CALL, /* Incoming call */ + BT_HFP_AG_AUDIO_CONN, /* Audio connection */ BT_HFP_AG_CODEC_CONN, /* Codec connection is ongoing */ BT_HFP_AG_CODEC_CHANGED, /* Codec Id Changed */ BT_HFP_AG_TX_ONGOING, /* TX is ongoing */ From ab5de717e710a4d20c4e0249ed79cb8d94462a1d Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Thu, 29 Aug 2024 14:48:35 +0800 Subject: [PATCH 22/76] Bluetooth: HFP_HF: Improve codec negotiation Add the configuration `BT_HFP_HF_CODEC_NEG` to configure the codec negotiation feature. Add a configuration `BT_HFP_HF_CODEC_MSBC` to support mSBC codec. Add a configuration `BT_HFP_HF_CODEC_LC3_SWB` to support LC3 SWB codec. Add a function `bt_hfp_hf_audio_connect` to trigger audio connection sequence. Add a function `bt_hfp_hf_select_codec` to response the codec negotiation request. Add a function `bt_hfp_hf_set_codecs` to notify the AG supported Codec IDs of HF. Handle unsolicited response `+BCS` sent from AG. Send supported Codec IDs supported by HF if the SLC is established. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 72 ++++++ subsys/bluetooth/host/classic/Kconfig | 18 ++ subsys/bluetooth/host/classic/hfp_hf.c | 225 +++++++++++++++++++ subsys/bluetooth/host/classic/hfp_internal.h | 34 ++- 4 files changed, 347 insertions(+), 2 deletions(-) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index 054ccf771eb38..ba5ecd85b691d 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -45,6 +45,11 @@ struct bt_hfp_hf_cmd_complete { uint8_t cme; }; +/* HFP CODEC IDs */ +#define BT_HFP_HF_CODEC_CVSD 0x01 +#define BT_HFP_HF_CODEC_MSBC 0x02 +#define BT_HFP_HF_CODEC_LC3_SWB 0x03 + /** @brief HFP profile application callback */ struct bt_hfp_hf_cb { /** HF connected callback to application @@ -219,6 +224,29 @@ struct bt_hfp_hf_cb { * operator. */ void (*operator)(struct bt_conn *conn, uint8_t mode, uint8_t format, char *operator); + /** Codec negotiate callback + * + * If this callback is provided it will be called whenever the + * unsolicited codec negotiation response received. + * There are two cases when the callback triggered, + * Case 1, the codec id can be accepted, the function + * `bt_hfp_hf_select_codec` should be called to accept the codec + * id. + * Case 2, the codec id can not be accepted, the function + * `bt_hfp_hf_set_codecs` should be called to trigger codec ID + * to be re-selected. + * If the callback is not provided by application, the function + * `bt_hfp_hf_select_codec` will be called to accept the codec + * id. + * Refers to BT_HFP_HF_CODEC_XXX for codec id value. + * If @kconfig{CONFIG_BT_HFP_HF_CODEC_NEG} is not enabled, the + * unsolicited result code +BCS will be ignored. And the callback + * will not be notified. + * + * @param conn Connection object. + * @param id Negotiated Codec ID. + */ + void (*codec_negotiate)(struct bt_conn *conn, uint8_t id); }; /** @brief Register HFP HF profile @@ -325,6 +353,50 @@ int bt_hfp_hf_get_operator(struct bt_conn *conn); */ int bt_hfp_hf_accept(struct bt_conn *conn); +/** @brief Handsfree HF setup audio connection + * + * Setup audio conenction by sending AT+BCC. + * If @kconfig{CONFIG_BT_HFP_HF_CODEC_NEG} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param conn Connection object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_audio_connect(struct bt_conn *conn); + +/** @brief Handsfree HF set selected codec id + * + * Set selected codec id by sending AT+BCS. The function is used to + * response the codec negotiation request notified by callback + * `codec_negotiate`. The parameter `codec_id` should be same as + * `id` of callback `codec_negotiate` if the id could be supported. + * Or, call `bt_hfp_hf_set_codecs` to notify the AG Codec IDs supported + * by HFP HF. + * If @kconfig{CONFIG_BT_HFP_HF_CODEC_NEG} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param conn Connection object. + * @param codec_id Selected codec id. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_select_codec(struct bt_conn *conn, uint8_t codec_id); + +/** @brief Handsfree HF set supported codec ids + * + * Set supported codec ids by sending AT+BAC. This function is used + * to notify AG the supported Codec IDs of HF. + * If @kconfig{CONFIG_BT_HFP_HF_CODEC_NEG} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param conn Connection object. + * @param codec_ids Supported codec IDs. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_set_codecs(struct bt_conn *conn, uint8_t codec_ids); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/Kconfig b/subsys/bluetooth/host/classic/Kconfig index 35fc0d5af2eab..82ee97aa8c1b2 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -186,6 +186,24 @@ config BT_HFP_HF_VOLUME default y help This option enables Remote audio volume control for HFP HF + +config BT_HFP_HF_CODEC_NEG + bool "Codec Negotiation for HFP HF [EXPERIMENTAL]" + help + This option enables Codec Negotiation for HFP HF + +if BT_HFP_HF_CODEC_NEG +config BT_HFP_HF_CODEC_MSBC + bool "Support Codec mSBC for HFP HF [EXPERIMENTAL]" + help + This option enables Codec mSBC for HFP HF + +config BT_HFP_HF_CODEC_LC3_SWB + bool "Support Codec LC3 SWB for HFP HF [EXPERIMENTAL]" + help + This option enables Codec LC3 SWB for HFP HF +endif # BT_HFP_HF_CODEC_NEG + endif # BT_HFP_HF if BT_HFP_AG diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index bcfe7a1cce9d7..4db5dc7344ebd 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -633,6 +633,38 @@ int bsir_handle(struct at_client *hf_at) return 0; } +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +int bcs_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + struct bt_conn *conn = hf->acl; + uint32_t codec_id; + int err; + + err = at_get_number(hf_at, &codec_id); + if (err) { + LOG_ERR("could not get bcs value"); + return err; + } + + if (!(hf->hf_codec_ids & BIT(codec_id))) { + LOG_ERR("Invalid codec id %d", codec_id); + err = bt_hfp_hf_set_codecs(conn, hf->hf_codec_ids); + return err; + } + + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_CODEC_CONN); + + if (bt_hf->codec_negotiate) { + bt_hf->codec_negotiate(conn, codec_id); + return 0; + } + + err = bt_hfp_hf_select_codec(conn, codec_id); + return err; +} +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + static const struct unsolicited { const char *cmd; enum at_cmd_type type; @@ -648,6 +680,9 @@ static const struct unsolicited { { "VGS", AT_CMD_TYPE_UNSOLICITED, vgs_handle }, #endif /* CONFIG_BT_HFP_HF_VOLUME */ { "BSIR", AT_CMD_TYPE_UNSOLICITED, bsir_handle }, +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) + { "BCS", AT_CMD_TYPE_UNSOLICITED, bcs_handle }, +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ }; static const struct unsolicited *hfp_hf_unsol_lookup(struct at_client *hf_at) @@ -798,6 +833,51 @@ static int at_vgs_finish(struct at_client *hf_at, enum at_result result, } #endif /* CONFIG_BT_HFP_HF_VOLUME */ +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +static void get_codec_ids(struct bt_hfp_hf *hf, char *buffer, size_t buffer_len) +{ + size_t len = 0; + uint8_t ids = hf->hf_codec_ids; + int index = 0; + + while (ids && (len < (buffer_len-2))) { + if (ids & 0x01) { + buffer[len++] = index + '0'; + buffer[len++] = ','; + } + index ++; + ids = ids >> 1; + } + + if (len > 0) { + len --; + } + + buffer[len] = '\0'; +} + +static int send_at_bac(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + if (hf->ag_features & BT_HFP_AG_FEATURE_CODEC_NEG) { + char ids[sizeof(hf->hf_codec_ids)*2*8 + 1]; + get_codec_ids(hf, &ids[0], ARRAY_SIZE(ids)); + return hfp_hf_send_cmd(hf, NULL, cb, "AT+BAC=%s", ids); + } + + return -ENOTSUP; +} + +static int at_bac_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("BAC set (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + typedef int (*at_send_t)(struct bt_hfp_hf *hf, at_finish_cb_t cb); static struct at_cmd_init @@ -815,6 +895,9 @@ static struct at_cmd_init #if defined(CONFIG_BT_HFP_HF_CLI) {send_at_clip, at_clip_finish, false}, #endif /* CONFIG_BT_HFP_HF_CLI */ +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) + {send_at_bac, at_bac_finish, false}, +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ }; static int at_cmd_init_start(struct bt_hfp_hf *hf); @@ -1327,6 +1410,145 @@ int bt_hfp_hf_accept(struct bt_conn *conn) return err; } +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +static int bcc_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("BCC (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +int bt_hfp_hf_audio_connect(struct bt_conn *conn) +{ +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) + struct bt_hfp_hf *hf; + int err; + + LOG_DBG(""); + + if (!conn) { + LOG_ERR("Invalid connection"); + return -ENOTCONN; + } + + hf = bt_hfp_hf_lookup_bt_conn(conn); + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (hf->chan.sco) { + LOG_ERR("Audio conenction has been connected"); + return -ECONNREFUSED; + } + + err = hfp_hf_send_cmd(hf, NULL, bcc_finish, "AT+BCC"); + if (err < 0) { + LOG_ERR("Fail to setup audio connection on %p", hf); + } + + return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ +} + +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +static int bcs_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("BCC (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +int bt_hfp_hf_select_codec(struct bt_conn *conn, uint8_t codec_id) +{ +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) + struct bt_hfp_hf *hf; + + LOG_DBG(""); + + if (!conn) { + LOG_ERR("Invalid connection"); + return -ENOTCONN; + } + + hf = bt_hfp_hf_lookup_bt_conn(conn); + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->hf_codec_ids & BIT(codec_id))) { + LOG_ERR("Codec ID is unsupported"); + return -ENOTSUP; + } + + if (!atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_CODEC_CONN)) { + LOG_ERR("Invalid context"); + return -ESRCH; + } + + return hfp_hf_send_cmd(hf, NULL, bcs_finish, "AT+BCS=%d", codec_id); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ +} + +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +static int bac_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("BCC (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +int bt_hfp_hf_set_codecs(struct bt_conn *conn, uint8_t codec_ids) +{ +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) + struct bt_hfp_hf *hf; + char ids[sizeof(hf->hf_codec_ids)*2*8 + 1]; + + LOG_DBG(""); + + if (!conn) { + LOG_ERR("Invalid connection"); + return -ENOTCONN; + } + + hf = bt_hfp_hf_lookup_bt_conn(conn); + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (codec_ids & BT_HFP_HF_CODEC_CVSD) { + LOG_ERR("CVSD should be supported"); + return -EINVAL; + } + + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_CODEC_CONN); + hf->hf_codec_ids = codec_ids; + + get_codec_ids(hf, &ids[0], ARRAY_SIZE(ids)); + return hfp_hf_send_cmd(hf, NULL, bac_finish, "AT+BAC=%s", ids); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ +} + static void hfp_hf_connected(struct bt_rfcomm_dlc *dlc) { struct bt_hfp_hf *hf = CONTAINER_OF(dlc, struct bt_hfp_hf, rfcomm_dlc); @@ -1397,6 +1619,9 @@ static int hfp_hf_accept(struct bt_conn *conn, struct bt_rfcomm_server *server, /* Set the supported features*/ hf->hf_features = BT_HFP_HF_SUPPORTED_FEATURES; + /* Set supported codec ids */ + hf->hf_codec_ids = BT_HFP_HF_SUPPORTED_CODEC_IDS; + k_fifo_init(&hf->tx_pending); for (j = 0; j < HF_MAX_AG_INDICATORS; j++) { diff --git a/subsys/bluetooth/host/classic/hfp_internal.h b/subsys/bluetooth/host/classic/hfp_internal.h index 9d080842f960a..5d196b729b6db 100644 --- a/subsys/bluetooth/host/classic/hfp_internal.h +++ b/subsys/bluetooth/host/classic/hfp_internal.h @@ -68,14 +68,42 @@ #define BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE 0 #endif /* CONFIG_BT_HFP_HF_VOLUME */ +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +#define BT_HFP_HF_CODEC_NEG_ENABLE BT_HFP_HF_FEATURE_CODEC_NEG +#else +#define BT_HFP_HF_CODEC_NEG_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + /* HFP HF Supported features */ -#define BT_HFP_HF_SUPPORTED_FEATURES \ -(BT_HFP_HF_FEATURE_CLI_ENABLE | BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE) +#define BT_HFP_HF_SUPPORTED_FEATURES (\ + BT_HFP_HF_FEATURE_CLI_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE |\ + BT_HFP_HF_CODEC_NEG_ENABLE) /* HFP HF Supported features in SDP */ #define BT_HFP_HF_SDP_SUPPORTED_FEATURES \ (BT_HFP_HF_SDP_FEATURE_CLI_ENABLE | BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE) +#define BT_HFP_HF_CODEC_CVSD_MASK BIT(BT_HFP_HF_CODEC_CVSD) + +#if defined(CONFIG_BT_HFP_HF_CODEC_MSBC) +#define BT_HFP_HF_CODEC_MSBC_ENABLE BIT(BT_HFP_HF_CODEC_MSBC) +#else +#define BT_HFP_HF_CODEC_MSBC_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_CODEC_MSBC */ + +#if defined(CONFIG_BT_HFP_HF_CODEC_LC3_SWB) +#define BT_HFP_HF_CODEC_LC3_SWB_ENABLE BIT(BT_HFP_HF_CODEC_LC3_SWB) +#else +#define BT_HFP_HF_CODEC_LC3_SWB_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_CODEC_LC3_SWB */ + +/* HFP HF Supported Codec IDs*/ +#define BT_HFP_HF_SUPPORTED_CODEC_IDS \ + BT_HFP_HF_CODEC_CVSD_MASK | \ + BT_HFP_HF_CODEC_MSBC_ENABLE | \ + BT_HFP_HF_CODEC_LC3_SWB_ENABLE + #define HF_MAX_BUF_LEN BT_HF_CLIENT_MAX_PDU #define HF_MAX_AG_INDICATORS 20 @@ -90,6 +118,7 @@ enum { BT_HFP_HF_FLAG_CONNECTED, /* HFP HF SLC Established */ BT_HFP_HF_FLAG_TX_ONGOING, /* HFP HF TX is ongoing */ BT_HFP_HF_FLAG_INCOMING, /* HFP HF call incoming */ + BT_HFP_HF_FLAG_CODEC_CONN, /* HFP HF codec connection setup */ /* Total number of flags - must be at the end of the enum */ BT_HFP_HF_NUM_FLAGS, }; @@ -107,6 +136,7 @@ struct bt_hfp_hf { struct at_client at; uint32_t hf_features; uint32_t ag_features; + uint8_t hf_codec_ids; uint8_t vgm; uint8_t vgs; int8_t ind_table[HF_MAX_AG_INDICATORS]; From 83bf9d14ab88568151a58d1832b6f36c0df07b3e Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Mon, 29 Jul 2024 20:22:07 +0800 Subject: [PATCH 23/76] Bluetooth: HFP_AG: Improve inband ringtone setting Add a function `bt_hfp_ag_inband_ringtone` to enable/disable inband ringtone. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_ag.h | 11 +++++++++ subsys/bluetooth/host/classic/hfp_ag.c | 27 +++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/include/zephyr/bluetooth/classic/hfp_ag.h b/include/zephyr/bluetooth/classic/hfp_ag.h index 07f42908e0b44..c7ef457cd31a3 100644 --- a/include/zephyr/bluetooth/classic/hfp_ag.h +++ b/include/zephyr/bluetooth/classic/hfp_ag.h @@ -393,6 +393,17 @@ int bt_hfp_ag_set_operator(struct bt_hfp_ag *ag, uint8_t mode, char *name); */ int bt_hfp_ag_audio_connect(struct bt_hfp_ag *ag, uint8_t id); +/** @brief Set In-Band Ring Tone + * + * Set In-Band Ring Tone. + * + * @param ag HFP AG object. + * @param inband In-band or no in-band. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_inband_ringtone(struct bt_hfp_ag *ag, bool inband); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index 2679c0cb2df12..3ad41a20a48ba 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -2561,3 +2561,30 @@ int bt_hfp_ag_vgs(struct bt_hfp_ag *ag, uint8_t vgs) return err; } + +int bt_hfp_ag_inband_ringtone(struct bt_hfp_ag *ag, bool inband) +{ + int err; + + LOG_DBG(""); + + if (ag == NULL) { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + hfp_ag_unlock(ag); + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BSIR=%d\r\n", inband ? 1 : 0); + if (err) { + LOG_ERR("Fail to set inband ringtone err :(%d)", err); + return err; + } + + atomic_set_bit_to(ag->flags, BT_HFP_AG_INBAND_RING, inband); + return 0; +} From 8d96449785f60347cfe8e45171dd0344cd482080 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Mon, 29 Jul 2024 20:54:18 +0800 Subject: [PATCH 24/76] Bluetooth: HFP_HF: Add a function to reject the call Add a function `bt_hfp_hf_reject` to reject the incoming call. Add a flag BT_HFP_HF_FLAG_INCOMING to identify the incoming call. Set the flag if the call_setup value is 1. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 10 ++++++ subsys/bluetooth/host/classic/hfp_hf.c | 41 +++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index ba5ecd85b691d..1f1b1a6890673 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -353,6 +353,16 @@ int bt_hfp_hf_get_operator(struct bt_conn *conn); */ int bt_hfp_hf_accept(struct bt_conn *conn); +/** @brief Handsfree HF reject the incoming call + * + * Send the AT+CHUP command to reject the incoming call. + * + * @param conn Connection object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_reject(struct bt_conn *conn); + /** @brief Handsfree HF setup audio connection * * Setup audio conenction by sending AT+BCC. diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 4db5dc7344ebd..704ff2ea81e51 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -1410,6 +1410,47 @@ int bt_hfp_hf_accept(struct bt_conn *conn) return err; } +static int chup_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+CHUP (result %d) on %p", result, hf); + + return 0; +} + +int bt_hfp_hf_reject(struct bt_conn *conn) +{ + struct bt_hfp_hf *hf; + int err; + + LOG_DBG(""); + + if (!conn) { + LOG_ERR("Invalid connection"); + return -ENOTCONN; + } + + hf = bt_hfp_hf_lookup_bt_conn(conn); + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING)) { + LOG_ERR("No incoming call setup in progress"); + return -EINVAL; + } + + err = hfp_hf_send_cmd(hf, NULL, chup_finish, "AT+CHUP"); + if (err < 0) { + LOG_ERR("Fail to reject the incoming call on %p", hf); + } + + return err; +} + #if defined(CONFIG_BT_HFP_HF_CODEC_NEG) static int bcc_finish(struct at_client *hf_at, enum at_result result, enum at_cme cme_err) From 551d23ffb58b7eec57b9a549e61731962aec7409 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Mon, 29 Jul 2024 21:12:42 +0800 Subject: [PATCH 25/76] Bluetooth: HFP_HF: Add a function to terminate the call Add a function `bt_hfp_hf_terminate` to terminate the active call. Add a flag BT_HFP_HF_FLAG_ACTIVE to identify the call is active. Set the flag if the call value is 1. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 10 +++++++ subsys/bluetooth/host/classic/hfp_hf.c | 28 ++++++++++++++++++++ subsys/bluetooth/host/classic/hfp_internal.h | 1 + 3 files changed, 39 insertions(+) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index 1f1b1a6890673..01c5bd306b438 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -363,6 +363,16 @@ int bt_hfp_hf_accept(struct bt_conn *conn); */ int bt_hfp_hf_reject(struct bt_conn *conn); +/** @brief Handsfree HF terminate the incoming call + * + * Send the AT+CHUP command to terminate the incoming call. + * + * @param conn Connection object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_terminate(struct bt_conn *conn); + /** @brief Handsfree HF setup audio connection * * Setup audio conenction by sending AT+BCC. diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 704ff2ea81e51..97d0bc270faa7 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -417,6 +417,8 @@ void ag_indicator_handle_values(struct at_client *hf_at, uint32_t index, } break; case HF_CALL_IND: + atomic_set_bit_to(hf->flags, BT_HFP_HF_FLAG_ACTIVE, value); + if (bt_hf->call) { bt_hf->call(conn, value); } @@ -1451,6 +1453,32 @@ int bt_hfp_hf_reject(struct bt_conn *conn) return err; } +int bt_hfp_hf_terminate(struct bt_conn *conn) +{ + struct bt_hfp_hf *hf; + int err; + + LOG_DBG(""); + + if (!conn) { + LOG_ERR("Invalid connection"); + return -ENOTCONN; + } + + hf = bt_hfp_hf_lookup_bt_conn(conn); + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + err = hfp_hf_send_cmd(hf, NULL, chup_finish, "AT+CHUP"); + if (err < 0) { + LOG_ERR("Fail to terminate the active call on %p", hf); + } + + return err; +} + #if defined(CONFIG_BT_HFP_HF_CODEC_NEG) static int bcc_finish(struct at_client *hf_at, enum at_result result, enum at_cme cme_err) diff --git a/subsys/bluetooth/host/classic/hfp_internal.h b/subsys/bluetooth/host/classic/hfp_internal.h index 5d196b729b6db..0bab4a66db5af 100644 --- a/subsys/bluetooth/host/classic/hfp_internal.h +++ b/subsys/bluetooth/host/classic/hfp_internal.h @@ -118,6 +118,7 @@ enum { BT_HFP_HF_FLAG_CONNECTED, /* HFP HF SLC Established */ BT_HFP_HF_FLAG_TX_ONGOING, /* HFP HF TX is ongoing */ BT_HFP_HF_FLAG_INCOMING, /* HFP HF call incoming */ + BT_HFP_HF_FLAG_ACTIVE, /* HFP HF call active */ BT_HFP_HF_FLAG_CODEC_CONN, /* HFP HF codec connection setup */ /* Total number of flags - must be at the end of the enum */ BT_HFP_HF_NUM_FLAGS, From af4fd8db1226976bf91a565432ede993e1dd696b Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Wed, 31 Jul 2024 09:56:07 +0800 Subject: [PATCH 26/76] Bluetooth: HFP_AG: Add number dialing callback Add phone number dialing callback if the ATDNnnn AT command is received from HFP HF. If the callback is NULL, the ATDNnnn AT command cannot be supported by the HFP AG. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_ag.h | 16 ++++++++++++++++ subsys/bluetooth/host/classic/hfp_ag.c | 8 ++++++++ 2 files changed, 24 insertions(+) diff --git a/include/zephyr/bluetooth/classic/hfp_ag.h b/include/zephyr/bluetooth/classic/hfp_ag.h index c7ef457cd31a3..348429e25f152 100644 --- a/include/zephyr/bluetooth/classic/hfp_ag.h +++ b/include/zephyr/bluetooth/classic/hfp_ag.h @@ -95,6 +95,22 @@ struct bt_hfp_ag_cb { */ int (*memory_dial)(struct bt_hfp_ag *ag, const char *location, char **number); + /** HF phone number calling request Callback + * + * If this callback is provided it will be called whenever a + * new call is requested with specific phone number from HF. + * When the callback is triggered, the application needs to start + * dialing the number with the passed phone number. + * If the callback is invalid, the phone number dialing from HF + * cannot be supported. + * + * @param ag HFP AG object. + * @param number Dialing number + * + * @return 0 in case of success or negative value in case of error. + */ + int (*number_call)(struct bt_hfp_ag *ag, const char *number); + /** HF outgoing Callback * * If this callback is provided it will be called whenever a diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index 3ad41a20a48ba..8ac63d3d94316 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -1421,6 +1421,14 @@ static int bt_hfp_ag_atd_handler(struct bt_hfp_ag *ag, struct net_buf *buf) } } else { number = &buf->data[0]; + if (bt_ag && bt_ag->number_call) { + err = bt_ag->number_call(ag, &buf->data[0]); + if (err) { + return err; + } + } else { + return -ENOTSUP; + } } len = strlen(number); From 9916633e0b006c92f073944b009c82d79d4ce26a Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Mon, 2 Sep 2024 11:05:06 +0800 Subject: [PATCH 27/76] Bluetooth: HFP_HF: Support phone number calling Add a function `bt_hfp_hf_number_call` to initiate outgoing voice calls by providing the destination phone number to the AG. Add a callback `dialing` to notify the application the result from AG. Add a flag `BT_HFP_HF_FLAG_DIALING` to make sure there is only one dialing call at the same time. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 23 +++++ subsys/bluetooth/host/classic/hfp_hf.c | 94 ++++++++++++++++++++ subsys/bluetooth/host/classic/hfp_internal.h | 1 + 3 files changed, 118 insertions(+) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index 01c5bd306b438..0879044ba7abd 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -160,6 +160,14 @@ struct bt_hfp_hf_cb { */ void (*cmd_complete_cb)(struct bt_conn *conn, struct bt_hfp_hf_cmd_complete *cmd); + /** HF call dialing Callback + * + * This callback provides call dialing result to the application. + * + * @param conn Connection object. + * @param err Result of calling dialing. + */ + void (*dialing)(struct bt_conn *conn, int err); /** HF calling line identification notification callback to application * * If this callback is provided it will be called whenever there @@ -373,6 +381,21 @@ int bt_hfp_hf_reject(struct bt_conn *conn); */ int bt_hfp_hf_terminate(struct bt_conn *conn); +/** @brief Handsfree HF phone number call + * + * Initiate outgoing voice calls by providing the destination phone + * number to the AG. + * Send the ATDdd…dd command to start phone number call. + * The result of the command will be notified through the callback + * `dialing`. + * + * @param conn Connection object. + * @param number Phone number. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_number_call(struct bt_conn *conn, const char *number); + /** @brief Handsfree HF setup audio connection * * Setup audio conenction by sending AT+BCC. diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 97d0bc270faa7..d41e9ad5a717f 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -1479,6 +1479,100 @@ int bt_hfp_hf_terminate(struct bt_conn *conn) return err; } +static int bt_hfp_ag_get_cme_err(enum at_cme cme_err) +{ + int err; + + switch (cme_err) { + case CME_ERROR_OPERATION_NOT_SUPPORTED: + err = -EOPNOTSUPP; + break; + case CME_ERROR_AG_FAILURE: + err = -EFAULT; + break; + case CME_ERROR_MEMORY_FAILURE: + err = -ENOSR; + break; + case CME_ERROR_MEMORY_FULL: + err = -ENOMEM; + break; + case CME_ERROR_DIAL_STRING_TO_LONG: + err = -ENAMETOOLONG; + break; + case CME_ERROR_INVALID_INDEX: + err = -EINVAL; + break; + case CME_ERROR_OPERATION_NOT_ALLOWED: + err = -ENOTSUP; + break; + case CME_ERROR_NO_CONNECTION_TO_PHONE: + err = -ENOTCONN; + break; + default: + err = -ENOTSUP; + break; + } + + return err; +} + +static int atd_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + + LOG_DBG("ATD (result %d) on %p", result, hf); + + if (!atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_DIALING)) { + LOG_WRN("No dialing call"); + } + + if (result == AT_RESULT_CME_ERROR) { + err = bt_hfp_ag_get_cme_err(cme_err); + } else if (result == AT_RESULT_ERROR) { + err = -ENOTSUP; + } else { + err = 0; + } + + if (bt_hf && bt_hf->dialing) { + bt_hf->dialing(hf->acl, err); + } + return 0; +} + +int bt_hfp_hf_number_call(struct bt_conn *conn, const char *number) +{ + struct bt_hfp_hf *hf; + int err; + + LOG_DBG(""); + + if (!conn) { + LOG_ERR("Invalid connection"); + return -ENOTCONN; + } + + hf = bt_hfp_hf_lookup_bt_conn(conn); + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (atomic_test_and_set_bit(hf->flags, BT_HFP_HF_FLAG_DIALING)) { + LOG_ERR("Outgoing call is started"); + return -EBUSY; + } + + err = hfp_hf_send_cmd(hf, NULL, atd_finish, "ATD%s", number); + if (err < 0) { + LOG_ERR("Fail to start phone number call on %p", hf); + } + + return err; +} + #if defined(CONFIG_BT_HFP_HF_CODEC_NEG) static int bcc_finish(struct at_client *hf_at, enum at_result result, enum at_cme cme_err) diff --git a/subsys/bluetooth/host/classic/hfp_internal.h b/subsys/bluetooth/host/classic/hfp_internal.h index 0bab4a66db5af..6e1e6f7bf41d8 100644 --- a/subsys/bluetooth/host/classic/hfp_internal.h +++ b/subsys/bluetooth/host/classic/hfp_internal.h @@ -119,6 +119,7 @@ enum { BT_HFP_HF_FLAG_TX_ONGOING, /* HFP HF TX is ongoing */ BT_HFP_HF_FLAG_INCOMING, /* HFP HF call incoming */ BT_HFP_HF_FLAG_ACTIVE, /* HFP HF call active */ + BT_HFP_HF_FLAG_DIALING, /* HFP HF call dialing */ BT_HFP_HF_FLAG_CODEC_CONN, /* HFP HF codec connection setup */ /* Total number of flags - must be at the end of the enum */ BT_HFP_HF_NUM_FLAGS, From 1c042f40f9500e889845d96a659daab3a076d43a Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Wed, 31 Jul 2024 12:02:55 +0800 Subject: [PATCH 28/76] Bluetooth: HFP_HF: Support memory dialing Add a function `bt_hfp_hf_memory_dial` to initiate outgoing voice calls using the memory dialing feature of the AG. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 15 +++++++++++ subsys/bluetooth/host/classic/hfp_hf.c | 31 +++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index 0879044ba7abd..e826d80e54662 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -396,6 +396,21 @@ int bt_hfp_hf_terminate(struct bt_conn *conn); */ int bt_hfp_hf_number_call(struct bt_conn *conn, const char *number); +/** @brief Handsfree HF memory dialing call + * + * Initiate outgoing voice calls using the memory dialing feature + * of the AG. + * Send the ATD>Nan... command to start memory dialing. + * The result of the command will be notified through the callback + * `dialing`. + * + * @param conn Connection object. + * @param location Memory location. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_memory_dial(struct bt_conn *conn, const char *location); + /** @brief Handsfree HF setup audio connection * * Setup audio conenction by sending AT+BCC. diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index d41e9ad5a717f..429b7d84938dd 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -1573,6 +1573,37 @@ int bt_hfp_hf_number_call(struct bt_conn *conn, const char *number) return err; } +int bt_hfp_hf_memory_dial(struct bt_conn *conn, const char *location) +{ + struct bt_hfp_hf *hf; + int err; + + LOG_DBG(""); + + if (!conn) { + LOG_ERR("Invalid connection"); + return -ENOTCONN; + } + + hf = bt_hfp_hf_lookup_bt_conn(conn); + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (atomic_test_and_set_bit(hf->flags, BT_HFP_HF_FLAG_DIALING)) { + LOG_ERR("Outgoing call is started"); + return -EBUSY; + } + + err = hfp_hf_send_cmd(hf, NULL, atd_finish, "ATD>%s", location); + if (err < 0) { + LOG_ERR("Fail to start memory dialing on %p", hf); + } + + return err; +} + #if defined(CONFIG_BT_HFP_HF_CODEC_NEG) static int bcc_finish(struct at_client *hf_at, enum at_result result, enum at_cme cme_err) From 3006ada36ab66fa9e18ddcab23507c6ecd312af9 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Wed, 31 Jul 2024 12:14:04 +0800 Subject: [PATCH 29/76] Bluetooth: HFP_HF: Support last number recalling Add a function `bt_hfp_hf_redial` to initiate outgoing voice calls by recalling the last number dialed by the AG. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 14 ++++++ subsys/bluetooth/host/classic/hfp_hf.c | 57 +++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index e826d80e54662..efe9aa884bc59 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -411,6 +411,20 @@ int bt_hfp_hf_number_call(struct bt_conn *conn, const char *number); */ int bt_hfp_hf_memory_dial(struct bt_conn *conn, const char *location); +/** @brief Handsfree HF redial last number + * + * Initiate outgoing voice calls by recalling the last number + * dialed by the AG. + * Send the AT+BLDN command to recall the last number. + * The result of the command will be notified through the callback + * `dialing`. + * + * @param conn Connection object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_redial(struct bt_conn *conn); + /** @brief Handsfree HF setup audio connection * * Setup audio conenction by sending AT+BCC. diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 429b7d84938dd..1298930065e1c 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -1604,6 +1604,63 @@ int bt_hfp_hf_memory_dial(struct bt_conn *conn, const char *location) return err; } +static int bldn_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + + LOG_DBG("AT+BLDN (result %d) on %p", result, hf); + + if (!atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_DIALING)) { + LOG_WRN("No dialing call"); + } + + if (result == AT_RESULT_CME_ERROR) { + err = bt_hfp_ag_get_cme_err(cme_err); + } else if (result == AT_RESULT_ERROR) { + err = -ENOTSUP; + } else { + err = 0; + } + + if (bt_hf && bt_hf->dialing) { + bt_hf->dialing(hf->acl, err); + } + return 0; +} + +int bt_hfp_hf_redial(struct bt_conn *conn) +{ + struct bt_hfp_hf *hf; + int err; + + LOG_DBG(""); + + if (!conn) { + LOG_ERR("Invalid connection"); + return -ENOTCONN; + } + + hf = bt_hfp_hf_lookup_bt_conn(conn); + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (atomic_test_and_set_bit(hf->flags, BT_HFP_HF_FLAG_DIALING)) { + LOG_ERR("Outgoing call is started"); + return -EBUSY; + } + + err = hfp_hf_send_cmd(hf, NULL, bldn_finish, "AT+BLDN"); + if (err < 0) { + LOG_ERR("Fail to start memory dialing on %p", hf); + } + + return err; +} + #if defined(CONFIG_BT_HFP_HF_CODEC_NEG) static int bcc_finish(struct at_client *hf_at, enum at_result result, enum at_cme cme_err) From bca0faab665cf14b6487d7c7fc6c1709587cd6ea Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Wed, 31 Jul 2024 15:55:48 +0800 Subject: [PATCH 30/76] Bluetooth: HFP_AG: Handle command AT+NREC=0 Add a configuration to support "EC and/or NR function" of HFP AG. Add a callback `ecnr_turn_off` to notify the application that "EC and/ or NR function" has been turned off. Handle AT command AT+NREC=0. Only if the feature enabled, and the callback `ecnr_turn_off` provided, the result code `OK` will be responded. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_ag.h | 12 ++++++ subsys/bluetooth/host/classic/Kconfig | 5 +++ subsys/bluetooth/host/classic/hfp_ag.c | 38 ++++++++++++++++++- .../bluetooth/host/classic/hfp_ag_internal.h | 14 ++++++- 4 files changed, 66 insertions(+), 3 deletions(-) diff --git a/include/zephyr/bluetooth/classic/hfp_ag.h b/include/zephyr/bluetooth/classic/hfp_ag.h index 348429e25f152..51508c8e9d8e8 100644 --- a/include/zephyr/bluetooth/classic/hfp_ag.h +++ b/include/zephyr/bluetooth/classic/hfp_ag.h @@ -223,6 +223,18 @@ struct bt_hfp_ag_cb { * @param gain HF speaker gain value. */ void (*vgs)(struct bt_hfp_ag *ag, uint8_t gain); + + /** HF ECNR turns off callback + * + * If this callback is provided it will be called whenever the + * ECNR turning off request is received from HF. + * If the callback is NULL or @kconfig{CONFIG_BT_HFP_AG_ECNR} + * is not enabled, the response result code of AT command + * will be an AT ERROR. + * + * @param ag HFP AG object. + */ + void (*ecnr_turn_off)(struct bt_hfp_ag *ag); }; /** @brief Register HFP AG profile diff --git a/subsys/bluetooth/host/classic/Kconfig b/subsys/bluetooth/host/classic/Kconfig index 82ee97aa8c1b2..d5eee84087550 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -218,6 +218,11 @@ config BT_HFP_AG_CODEC_NEG help This option enables Codec Negotiation for HFP AG +config BT_HFP_AG_ECNR + bool "EC and/or NR function for HFP AG [EXPERIMENTAL]" + help + This option enables EC and/or NR function for HFP AG + config BT_HFP_AG_TX_BUF_COUNT int "Maximum number of TX buffers for HFP AG [EXPERIMENTAL]" default BT_RFCOMM_TX_MAX diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index 8ac63d3d94316..df9b70d247405 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -1565,6 +1565,42 @@ static int bt_hfp_ag_vgs_handler(struct bt_hfp_ag *ag, struct net_buf *buf) return err; } +static int bt_hfp_ag_nrec_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t disable; + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + err = get_number(buf, &disable); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + if (!(ag->ag_features & BT_HFP_AG_SDP_FEATURE_3WAY_CALL)) { + return -ENOTSUP; + } + + if (disable) { + LOG_ERR("Only support EC NR disable"); + return -ENOTSUP; + } + +#if defined(CONFIG_BT_HFP_AG_ECNR) + if (bt_ag && bt_ag->ecnr_turn_off) { + bt_ag->ecnr_turn_off(ag); + return 0; + } +#endif /* CONFIG_BT_HFP_AG_ECNR */ + return -ENOTSUP; +} + static struct bt_hfp_ag_at_cmd_handler cmd_handlers[] = { {"AT+BRSF", bt_hfp_ag_brsf_handler}, {"AT+BAC", bt_hfp_ag_bac_handler}, {"AT+CIND", bt_hfp_ag_cind_handler}, {"AT+CMER", bt_hfp_ag_cmer_handler}, @@ -1575,7 +1611,7 @@ static struct bt_hfp_ag_at_cmd_handler cmd_handlers[] = { {"AT+BCC", bt_hfp_ag_bcc_handler}, {"AT+BCS", bt_hfp_ag_bcs_handler}, {"ATD", bt_hfp_ag_atd_handler}, {"AT+BLDN", bt_hfp_ag_bldn_handler}, {"AT+CLIP", bt_hfp_ag_clip_handler}, {"AT+VGM", bt_hfp_ag_vgm_handler}, - {"AT+VGS", bt_hfp_ag_vgs_handler}, + {"AT+VGS", bt_hfp_ag_vgs_handler}, {"AT+NREC", bt_hfp_ag_nrec_handler}, }; static void hfp_ag_connected(struct bt_rfcomm_dlc *dlc) diff --git a/subsys/bluetooth/host/classic/hfp_ag_internal.h b/subsys/bluetooth/host/classic/hfp_ag_internal.h index 940ee6f0421e8..14d567e1ac97a 100644 --- a/subsys/bluetooth/host/classic/hfp_ag_internal.h +++ b/subsys/bluetooth/host/classic/hfp_ag_internal.h @@ -32,17 +32,27 @@ #define BT_HFP_AG_FEATURE_CODEC_NEG_ENABLE 0 #endif /* CONFIG_BT_HFP_AG_CODEC_NEG */ +#if defined(CONFIG_BT_HFP_AG_ECNR) +#define BT_HFP_AG_FEATURE_ECNR_ENABLE BT_HFP_AG_FEATURE_ECNR +#define BT_HFP_AG_SDP_FEATURE_ECNR_ENABLE BT_HFP_AG_SDP_FEATURE_ECNR +#else +#define BT_HFP_AG_FEATURE_ECNR_ENABLE 0 +#define BT_HFP_AG_SDP_FEATURE_ECNR_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_CODEC_NEG */ + /* HFP AG Supported features */ #define BT_HFP_AG_SUPPORTED_FEATURES (\ BT_HFP_AG_FEATURE_3WAY_CALL | \ BT_HFP_AG_FEATURE_INBAND_RINGTONE | \ BT_HFP_AG_FEATURE_EXT_ERR_ENABLE | \ - BT_HFP_AG_FEATURE_CODEC_NEG_ENABLE) + BT_HFP_AG_FEATURE_CODEC_NEG_ENABLE | \ + BT_HFP_AG_FEATURE_ECNR_ENABLE) /* HFP AG Supported features in SDP */ #define BT_HFP_AG_SDP_SUPPORTED_FEATURES (\ BT_HFP_AG_SDP_FEATURE_3WAY_CALL | \ - BT_HFP_AG_SDP_FEATURE_INBAND_RINGTONE) + BT_HFP_AG_SDP_FEATURE_INBAND_RINGTONE | \ + BT_HFP_AG_SDP_FEATURE_ECNR_ENABLE) /* bt_hfp_ag flags: the flags defined here represent HFP AG parameters */ enum { From d5c07a702492549cc2ec952c6ec9d0bfef9f0e7e Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Wed, 31 Jul 2024 17:00:09 +0800 Subject: [PATCH 31/76] Bluetooth: HFP_HF: Turn off AG's EC and NR Add a configuration `CONFIG_BT_HFP_HF_ECNR` to enable "EC and/or NR function" of HFP HF. Add a function `bt_hfp_hf_turn_off_ecnr` to turn off AG's EC and NR. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 25 +++++++++ subsys/bluetooth/host/classic/Kconfig | 5 ++ subsys/bluetooth/host/classic/hfp_hf.c | 58 ++++++++++++++++++++ subsys/bluetooth/host/classic/hfp_internal.h | 17 +++++- 4 files changed, 102 insertions(+), 3 deletions(-) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index efe9aa884bc59..030aafe4214ae 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -255,6 +255,17 @@ struct bt_hfp_hf_cb { * @param id Negotiated Codec ID. */ void (*codec_negotiate)(struct bt_conn *conn, uint8_t id); + /** HF ECNR turns off callback + * + * If this callback is provided it will be called whenever the + * response of ECNR turning off is received from AG. + * If @kconfig{CONFIG_BT_HFP_HF_ECNR} is not enabled, the + * callback will not be notified. + * + * @param conn Connection object. + * @param err The result of request. + */ + void (*ecnr_turn_off)(struct bt_conn *conn, int err); }; /** @brief Register HFP HF profile @@ -469,6 +480,20 @@ int bt_hfp_hf_select_codec(struct bt_conn *conn, uint8_t codec_id); */ int bt_hfp_hf_set_codecs(struct bt_conn *conn, uint8_t codec_ids); +/** @brief Handsfree HF turns off AG's EC and NR + * + * Turn off the AG's EC and NR by sending `AT+NREC=0`. + * The result of the command is notified through the callback + * `ecnr_turn_off`. + * If @kconfig{CONFIG_BT_HFP_HF_ECNR} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param conn Connection object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_turn_off_ecnr(struct bt_conn *conn); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/Kconfig b/subsys/bluetooth/host/classic/Kconfig index d5eee84087550..1c76807322bf8 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -192,6 +192,11 @@ config BT_HFP_HF_CODEC_NEG help This option enables Codec Negotiation for HFP HF +config BT_HFP_HF_ECNR + bool "EC and/or NR function for HFP HF [EXPERIMENTAL]" + help + This option enables EC and/or NR function for HFP HF + if BT_HFP_HF_CODEC_NEG config BT_HFP_HF_CODEC_MSBC bool "Support Codec mSBC for HFP HF [EXPERIMENTAL]" diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 1298930065e1c..81b481924a463 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -1800,6 +1800,64 @@ int bt_hfp_hf_set_codecs(struct bt_conn *conn, uint8_t codec_ids) #endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ } +#if defined(CONFIG_BT_HFP_HF_ECNR) +static int nrec_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + + LOG_DBG("AT+NREC=0 (result %d) on %p", result, hf); + + if (result == AT_RESULT_CME_ERROR) { + err = bt_hfp_ag_get_cme_err(cme_err); + } else if (result == AT_RESULT_ERROR) { + err = -ENOTSUP; + } else { + err = 0; + } + + if (bt_hf && bt_hf->ecnr_turn_off) { + bt_hf->ecnr_turn_off(hf->acl, err); + } + return 0; +} +#endif /* CONFIG_BT_HFP_HF_ECNR */ + +int bt_hfp_hf_turn_off_ecnr(struct bt_conn *conn) +{ +#if defined(CONFIG_BT_HFP_HF_ECNR) + struct bt_hfp_hf *hf; + + LOG_DBG(""); + + if (!conn) { + LOG_ERR("Invalid connection"); + return -ENOTCONN; + } + + hf = bt_hfp_hf_lookup_bt_conn(conn); + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_ECNR)) { + LOG_ERR("EC and/or NR functions is unsupported by AG"); + return -ENOTSUP; + } + + if (hf->chan.sco) { + LOG_ERR("Audio conenction has been connected"); + return -EBUSY; + } + + return hfp_hf_send_cmd(hf, NULL, nrec_finish, "AT+NREC=0"); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_ECNR */ +} + static void hfp_hf_connected(struct bt_rfcomm_dlc *dlc) { struct bt_hfp_hf *hf = CONTAINER_OF(dlc, struct bt_hfp_hf, rfcomm_dlc); diff --git a/subsys/bluetooth/host/classic/hfp_internal.h b/subsys/bluetooth/host/classic/hfp_internal.h index 6e1e6f7bf41d8..25062d4e31b87 100644 --- a/subsys/bluetooth/host/classic/hfp_internal.h +++ b/subsys/bluetooth/host/classic/hfp_internal.h @@ -74,15 +74,26 @@ #define BT_HFP_HF_CODEC_NEG_ENABLE 0 #endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ +#if defined(CONFIG_BT_HFP_HF_ECNR) +#define BT_HFP_HF_FEATURE_ECNR_ENABLE BT_HFP_HF_FEATURE_ECNR +#define BT_HFP_HF_SDP_FEATURE_ECNR_ENABLE BT_HFP_HF_SDP_FEATURE_ECNR +#else +#define BT_HFP_HF_FEATURE_ECNR_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_ECNR_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + /* HFP HF Supported features */ #define BT_HFP_HF_SUPPORTED_FEATURES (\ BT_HFP_HF_FEATURE_CLI_ENABLE | \ BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE |\ - BT_HFP_HF_CODEC_NEG_ENABLE) + BT_HFP_HF_CODEC_NEG_ENABLE | \ + BT_HFP_HF_FEATURE_ECNR_ENABLE) /* HFP HF Supported features in SDP */ -#define BT_HFP_HF_SDP_SUPPORTED_FEATURES \ -(BT_HFP_HF_SDP_FEATURE_CLI_ENABLE | BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE) +#define BT_HFP_HF_SDP_SUPPORTED_FEATURES (\ + BT_HFP_HF_SDP_FEATURE_CLI_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_ECNR_ENABLE) #define BT_HFP_HF_CODEC_CVSD_MASK BIT(BT_HFP_HF_CODEC_CVSD) From 4ac35fc9067546469540a3347494ff7436fa6c7f Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Wed, 31 Jul 2024 20:42:18 +0800 Subject: [PATCH 32/76] Bluetooth: HFP_HF: Hold incoming call Add a flag `BT_HFP_HF_FLAG_INCOMING_HELD` to identify the incoming call is held. Add a function `bt_hfp_hf_hold_incoming` to put the incoming call on hold. Handle the response `+BTRH` to get the incoming call status. Add a callback `incoming_held` to notify the application the incoming is held. Remove callback `call_setup`. Add callback `outgoing` for call_setup=1, `incoming` for call_setup=2, and `remote_ringing` for call_setup=3. Remove callback `call`. Add callback `accept`, `reject`, `terminate`, and `incoming_held`. `accept` is triggered if the call is active. `reject` is triggered if the incoming/ outgoing is not accepted but call is terminated. `terminate` is triggered if the accepted is terminated. `incoming_held` is triggered if the incoming call is held. Improve function `bt_hfp_hf_accept` to accept the held incoming call. Improve function `bt_hfp_hf_reject` to reject the held incoming call. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 84 +++++++- subsys/bluetooth/host/classic/hfp_hf.c | 208 +++++++++++++++++-- subsys/bluetooth/host/classic/hfp_internal.h | 19 +- 3 files changed, 278 insertions(+), 33 deletions(-) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index 030aafe4214ae..ea8ee656249ee 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -95,22 +95,62 @@ struct bt_hfp_hf_cb { * @param value service indicator value received from the AG. */ void (*service)(struct bt_conn *conn, uint32_t value); - /** HF indicator Callback + /** HF call outgoing Callback * - * This callback provides call indicator value to the application + * This callback provides the outgoing call status to + * the application. * * @param conn Connection object. - * @param value call indicator value received from the AG. */ - void (*call)(struct bt_conn *conn, uint32_t value); - /** HF indicator Callback + void (*outgoing)(struct bt_conn *conn); + /** HF call outgoing call is ringing Callback + * + * This callback provides the outgoing call is ringing + * status to the application. + * + * @param conn Connection object. + */ + void (*remote_ringing)(struct bt_conn *conn); + /** HF call incoming Callback + * + * This callback provides the incoming call status to + * the application. + * + * @param conn Connection object. + */ + void (*incoming)(struct bt_conn *conn); + /** HF incoming call on hold Callback + * + * This callback provides the incoming call on hold status to + * the application. + * + * @param conn Connection object. + */ + void (*incoming_held)(struct bt_conn *conn); + /** HF call accept Callback + * + * This callback provides the incoming/outgoing call active + * status to the application. + * + * @param conn Connection object. + */ + void (*accept)(struct bt_conn *conn); + /** HF call reject Callback * - * This callback provides call setup indicator value to the application + * This callback provides the incoming/outgoing call reject + * status to the application. * * @param conn Connection object. - * @param value call setup indicator value received from the AG. */ - void (*call_setup)(struct bt_conn *conn, uint32_t value); + void (*reject)(struct bt_conn *conn); + /** HF call terminate Callback + * + * This callback provides the incoming/outgoing call terminate + * status to the application. + * + * @param conn Connection object. + */ + void (*terminate)(struct bt_conn *conn); /** HF indicator Callback * * This callback provides call held indicator value to the application @@ -365,6 +405,8 @@ int bt_hfp_hf_get_operator(struct bt_conn *conn); /** @brief Handsfree HF accept the incoming call * * Send the ATA command to accept the incoming call. + * OR, send the AT+BTRH=1 command to accept a held incoming + * call. * * @param conn Connection object. * @@ -375,6 +417,8 @@ int bt_hfp_hf_accept(struct bt_conn *conn); /** @brief Handsfree HF reject the incoming call * * Send the AT+CHUP command to reject the incoming call. + * OR, send the AT+BTRH=2 command to reject a held incoming + * call. * * @param conn Connection object. * @@ -392,6 +436,30 @@ int bt_hfp_hf_reject(struct bt_conn *conn); */ int bt_hfp_hf_terminate(struct bt_conn *conn); +/** @brief Handsfree HF put the incoming call on hold + * + * Send the AT+BTRH=0 command to put the incoming call on hold. + * If the incoming call has been held, the callback `on_hold` will + * be triggered. + * + * @param conn Connection object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_hold_incoming(struct bt_conn *conn); + +/** @brief Handsfree HF query respond and hold status of AG + * + * Send the AT+BTRH? command to query respond and hold status of AG. + * The status respond and hold will be notified through callback + * `on_hold`. + * + * @param conn Connection object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_query_respond_hold_status(struct bt_conn *conn); + /** @brief Handsfree HF phone number call * * Initiate outgoing voice calls by providing the destination phone diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 81b481924a463..6bd8cd88c5029 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -391,6 +391,34 @@ int cind_resp(struct at_client *hf_at, struct net_buf *buf) return 0; } +static void ag_indicator_handle_call(struct bt_hfp_hf *hf, uint32_t value) +{ + struct bt_conn *conn = hf->acl; + + atomic_set_bit_to(hf->flags, BT_HFP_HF_FLAG_ACTIVE, value); + if (value) { + if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING_HELD)) { + if (bt_hf->incoming_held) { + bt_hf->incoming_held(conn); + } + } else { + if (bt_hf->accept) { + bt_hf->accept(conn); + } + } + } else { + if (atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING_HELD)) { + if (bt_hf->reject) { + bt_hf->reject(conn); + } + } else { + if (bt_hf->terminate) { + bt_hf->terminate(conn); + } + } + } +} + void ag_indicator_handle_values(struct at_client *hf_at, uint32_t index, uint32_t value) { @@ -417,18 +445,24 @@ void ag_indicator_handle_values(struct at_client *hf_at, uint32_t index, } break; case HF_CALL_IND: - atomic_set_bit_to(hf->flags, BT_HFP_HF_FLAG_ACTIVE, value); - - if (bt_hf->call) { - bt_hf->call(conn, value); - } + ag_indicator_handle_call(hf, value); break; case HF_CALL_SETUP_IND: atomic_set_bit_to(hf->flags, BT_HFP_HF_FLAG_INCOMING, value == BT_HFP_CALL_SETUP_INCOMING); - if (bt_hf->call_setup) { - bt_hf->call_setup(conn, value); + if (value == BT_HFP_CALL_SETUP_INCOMING) { + if (bt_hf->incoming) { + bt_hf->incoming(conn); + } + } else if (value == BT_HFP_CALL_SETUP_OUTGOING) { + if (bt_hf->outgoing) { + bt_hf->outgoing(conn); + } + } else if (value == BT_HFP_CALL_SETUP_REMOTE_ALERTING) { + if (bt_hf->remote_ringing) { + bt_hf->remote_ringing(conn); + } } break; case HF_CALL_HELD_IND: @@ -667,6 +701,40 @@ int bcs_handle(struct at_client *hf_at) } #endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ +static int btrh_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + uint32_t on_hold; + int err; + + err = at_get_number(hf_at, &on_hold); + if (err < 0) { + LOG_ERR("Error getting value"); + return err; + } + + if (on_hold == BT_HFP_BTRH_ON_HOLD) { + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_QUERY_HOLD); + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING_HELD); + } else if (on_hold == BT_HFP_BTRH_ACCEPTED) { + if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_ACTIVE) && + atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING_HELD)) { + if (bt_hf && bt_hf->accept) { + bt_hf->accept(hf->acl); + } + } + } else if (on_hold == BT_HFP_BTRH_REJECTED) { + if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_ACTIVE) && + atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING_HELD)) { + if (bt_hf && bt_hf->reject) { + bt_hf->reject(hf->acl); + } + } + } + + return 0; +} + static const struct unsolicited { const char *cmd; enum at_cmd_type type; @@ -685,6 +753,7 @@ static const struct unsolicited { #if defined(CONFIG_BT_HFP_HF_CODEC_NEG) { "BCS", AT_CMD_TYPE_UNSOLICITED, bcs_handle }, #endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + { "BTRH", AT_CMD_TYPE_UNSOLICITED, btrh_handle }, }; static const struct unsolicited *hfp_hf_unsol_lookup(struct at_client *hf_at) @@ -1381,6 +1450,16 @@ static int ata_finish(struct at_client *hf_at, enum at_result result, return 0; } +static int btrh_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BTRH (result %d) on %p", result, hf); + + return 0; +} + int bt_hfp_hf_accept(struct bt_conn *conn) { struct bt_hfp_hf *hf; @@ -1399,17 +1478,25 @@ int bt_hfp_hf_accept(struct bt_conn *conn) return -ENOTCONN; } - if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING)) { - LOG_ERR("No incoming call setup in progress"); - return -EINVAL; + if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING)) { + err = hfp_hf_send_cmd(hf, NULL, ata_finish, "ATA"); + if (err < 0) { + LOG_ERR("Fail to accept the incoming call on %p", hf); + } + return err; } - err = hfp_hf_send_cmd(hf, NULL, ata_finish, "ATA"); - if (err < 0) { - LOG_ERR("Fail to accept the incoming call on %p", hf); + if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING_HELD) && + atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_ACTIVE)) { + err = hfp_hf_send_cmd(hf, NULL, btrh_finish, "AT+BTRH=%d", + BT_HFP_BTRH_ACCEPTED); + if (err < 0) { + LOG_ERR("Fail to accept the held incoming call on %p", hf); + } + return err; } - return err; + return -EINVAL; } static int chup_finish(struct at_client *hf_at, enum at_result result, @@ -1440,20 +1527,101 @@ int bt_hfp_hf_reject(struct bt_conn *conn) return -ENOTCONN; } + if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING)) { + err = hfp_hf_send_cmd(hf, NULL, chup_finish, "AT+CHUP"); + if (err < 0) { + LOG_ERR("Fail to reject the incoming call on %p", hf); + } + return err; + } + + if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING_HELD) && + atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_ACTIVE)) { + err = hfp_hf_send_cmd(hf, NULL, btrh_finish, "AT+BTRH=%d", + BT_HFP_BTRH_REJECTED); + if (err < 0) { + LOG_ERR("Fail to reject the held incoming call on %p", hf); + } + return err; + } + + return -EINVAL; +} + +int bt_hfp_hf_terminate(struct bt_conn *conn) +{ + struct bt_hfp_hf *hf; + int err; + + LOG_DBG(""); + + if (!conn) { + LOG_ERR("Invalid connection"); + return -ENOTCONN; + } + + hf = bt_hfp_hf_lookup_bt_conn(conn); + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + err = hfp_hf_send_cmd(hf, NULL, chup_finish, "AT+CHUP"); + if (err < 0) { + LOG_ERR("Fail to terminate the active call on %p", hf); + } + + return err; +} + +int bt_hfp_hf_hold_incoming(struct bt_conn *conn) +{ + struct bt_hfp_hf *hf; + int err; + + LOG_DBG(""); + + if (!conn) { + LOG_ERR("Invalid connection"); + return -ENOTCONN; + } + + hf = bt_hfp_hf_lookup_bt_conn(conn); + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING)) { LOG_ERR("No incoming call setup in progress"); return -EINVAL; } - err = hfp_hf_send_cmd(hf, NULL, chup_finish, "AT+CHUP"); + err = hfp_hf_send_cmd(hf, NULL, btrh_finish, "AT+BTRH=%d", + BT_HFP_BTRH_ON_HOLD); if (err < 0) { - LOG_ERR("Fail to reject the incoming call on %p", hf); + LOG_ERR("Fail to hold the incoming call on %p", hf); } return err; } -int bt_hfp_hf_terminate(struct bt_conn *conn) +static int query_btrh_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BTRH? (result %d) on %p", result, hf); + + if (atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_QUERY_HOLD) && + bt_hf->incoming_held) { + bt_hf->incoming_held(hf->acl); + } + + return 0; +} + +int bt_hfp_hf_query_respond_hold_status(struct bt_conn *conn) { struct bt_hfp_hf *hf; int err; @@ -1471,9 +1639,11 @@ int bt_hfp_hf_terminate(struct bt_conn *conn) return -ENOTCONN; } - err = hfp_hf_send_cmd(hf, NULL, chup_finish, "AT+CHUP"); + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_QUERY_HOLD); + + err = hfp_hf_send_cmd(hf, NULL, query_btrh_finish, "AT+BTRH?"); if (err < 0) { - LOG_ERR("Fail to terminate the active call on %p", hf); + LOG_ERR("Fail to query respond and hold status of AG on %p", hf); } return err; diff --git a/subsys/bluetooth/host/classic/hfp_internal.h b/subsys/bluetooth/host/classic/hfp_internal.h index 25062d4e31b87..0bdce42e41b05 100644 --- a/subsys/bluetooth/host/classic/hfp_internal.h +++ b/subsys/bluetooth/host/classic/hfp_internal.h @@ -126,12 +126,14 @@ /* bt_hfp_hf flags: the flags defined here represent hfp hf parameters */ enum { - BT_HFP_HF_FLAG_CONNECTED, /* HFP HF SLC Established */ - BT_HFP_HF_FLAG_TX_ONGOING, /* HFP HF TX is ongoing */ - BT_HFP_HF_FLAG_INCOMING, /* HFP HF call incoming */ - BT_HFP_HF_FLAG_ACTIVE, /* HFP HF call active */ - BT_HFP_HF_FLAG_DIALING, /* HFP HF call dialing */ - BT_HFP_HF_FLAG_CODEC_CONN, /* HFP HF codec connection setup */ + BT_HFP_HF_FLAG_CONNECTED, /* HFP HF SLC Established */ + BT_HFP_HF_FLAG_TX_ONGOING, /* HFP HF TX is ongoing */ + BT_HFP_HF_FLAG_INCOMING, /* HFP HF call incoming */ + BT_HFP_HF_FLAG_INCOMING_HELD, /* HFP HF call incoming is held */ + BT_HFP_HF_FLAG_QUERY_HOLD, /* HFP HF query response and hold status */ + BT_HFP_HF_FLAG_ACTIVE, /* HFP HF call active */ + BT_HFP_HF_FLAG_DIALING, /* HFP HF call dialing */ + BT_HFP_HF_FLAG_CODEC_CONN, /* HFP HF codec connection setup */ /* Total number of flags - must be at the end of the enum */ BT_HFP_HF_NUM_FLAGS, }; @@ -175,3 +177,8 @@ enum hfp_hf_ag_indicators { #define BT_HFP_CALL_SETUP_INCOMING 1 #define BT_HFP_CALL_SETUP_OUTGOING 2 #define BT_HFP_CALL_SETUP_REMOTE_ALERTING 3 + +/* HFP incoming call status */ +#define BT_HFP_BTRH_ON_HOLD 0 +#define BT_HFP_BTRH_ACCEPTED 1 +#define BT_HFP_BTRH_REJECTED 2 From 18e1b5394b85611fa99479ad8bd1d1697f2955d2 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Wed, 31 Jul 2024 22:24:14 +0800 Subject: [PATCH 33/76] Bluetooth: HFP_AG: Hold incoming call Add a flag `BT_HFP_AG_INCOMING_HELD` to identify the incoming call is held. Add a function `bt_hfp_ag_hold_incoming` to put the incoming call on hold. Add a callback `incoming_held` to notify the application the incoming is held. Handle AT command `AT+BTRH=x` to hold/accept/reject the incoming call. Improve function `bt_hfp_hf_accept` to accept the held incoming call. Improve function `bt_hfp_hf_reject` to reject the held incoming call. Improve function `bt_hfp_hf_terminate` to terminate the held incoming call. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_ag.h | 19 ++ subsys/bluetooth/host/classic/hfp_ag.c | 249 ++++++++++++++++-- .../bluetooth/host/classic/hfp_ag_internal.h | 1 + 3 files changed, 247 insertions(+), 22 deletions(-) diff --git a/include/zephyr/bluetooth/classic/hfp_ag.h b/include/zephyr/bluetooth/classic/hfp_ag.h index 51508c8e9d8e8..3281635c80b7e 100644 --- a/include/zephyr/bluetooth/classic/hfp_ag.h +++ b/include/zephyr/bluetooth/classic/hfp_ag.h @@ -131,6 +131,15 @@ struct bt_hfp_ag_cb { */ void (*incoming)(struct bt_hfp_ag *ag, const char *number); + /** HF incoming call is held Callback + * + * If this callback is provided it will be called whenever the + * incoming call is held but not accepted. + * + * @param ag HFP AG object. + */ + void (*incoming_held)(struct bt_hfp_ag *ag); + /** HF ringing Callback * * If this callback is provided it will be called whenever the @@ -281,6 +290,16 @@ int bt_hfp_ag_disconnect(struct bt_hfp_ag *ag); */ int bt_hfp_ag_remote_incoming(struct bt_hfp_ag *ag, const char *number); +/** @brief Put the incoming call on hold + * + * Put the incoming call on hold. + * + * @param ag HFP AG object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_hold_incoming(struct bt_hfp_ag *ag); + /** @brief Reject the incoming call * * Reject the incoming call. diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index df9b70d247405..7e2ea8e58ac98 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -1601,6 +1601,134 @@ static int bt_hfp_ag_nrec_handler(struct bt_hfp_ag *ag, struct net_buf *buf) return -ENOTSUP; } +static void btrh_held_call_updated_cb(struct bt_hfp_ag *ag, void *user_data) +{ + bt_hfp_ag_set_call_state(ag, BT_HFP_CALL_ACTIVE); +} + +static void btrh_held_call_setup_updated_cb(struct bt_hfp_ag *ag, void *user_data) +{ + if (bt_ag && bt_ag->incoming_held) { + bt_ag->incoming_held(ag); + } +} + +static void btrh_held_cb(struct bt_hfp_ag *ag, void *user_data) +{ + int err; + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, btrh_held_call_updated_cb, NULL); + if (err != 0) { + LOG_ERR("Fail to send err :(%d)", err); + bt_hfp_ag_unit_call_terminate(ag, user_data); + } + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, + btrh_held_call_setup_updated_cb, NULL); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + bt_hfp_ag_unit_call_terminate(ag, user_data); + } +} + +static void btrh_accept_cb(struct bt_hfp_ag *ag, void *user_data) +{ + int err; + + if (bt_ag && bt_ag->accept) { + bt_ag->accept(ag); + } + + err = bt_hfp_ag_create_audio_connection(ag); + if (err) { + bt_hfp_ag_unit_call_terminate(ag, user_data); + } +} + +static void btrh_reject_cb(struct bt_hfp_ag *ag, void *user_data) +{ + int err; + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, bt_hfp_ag_reject_cb, NULL); + if (err) { + bt_hfp_ag_unit_call_terminate(ag, user_data); + } +} + +static int bt_hfp_ag_btrh_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t action; + bt_hfp_call_state_t call_state; + + hfp_ag_lock(ag); + call_state = ag->call_state; + hfp_ag_unlock(ag); + + if (is_char(buf, '?')) { + if (atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_HELD) && + (call_state == BT_HFP_CALL_ACTIVE)) { + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_ON_HOLD); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + return err; + } + return 0; + } + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + err = get_number(buf, &action); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + if (atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL)) { + if (action == BT_HFP_BTRH_ON_HOLD) { + if ((call_state == BT_HFP_CALL_ALERTING) || + (call_state == BT_HFP_CALL_INCOMING)) { + atomic_set_bit(ag->flags, BT_HFP_AG_INCOMING_HELD); + err = hfp_ag_send_data(ag, btrh_held_cb, NULL, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_ON_HOLD); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + return err; + } + } else if (action == BT_HFP_BTRH_ACCEPTED) { + if (atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_INCOMING_HELD) && + (call_state == BT_HFP_CALL_ACTIVE)) { + err = hfp_ag_send_data(ag, btrh_accept_cb, NULL, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_ACCEPTED); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + return err; + } + } else if (action == BT_HFP_BTRH_REJECTED) { + if (atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_INCOMING_HELD) && + (call_state == BT_HFP_CALL_ACTIVE)) { + err = hfp_ag_send_data(ag, btrh_reject_cb, NULL, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_REJECTED); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + return err; + } + } + } + + return -ENOTSUP; +} + static struct bt_hfp_ag_at_cmd_handler cmd_handlers[] = { {"AT+BRSF", bt_hfp_ag_brsf_handler}, {"AT+BAC", bt_hfp_ag_bac_handler}, {"AT+CIND", bt_hfp_ag_cind_handler}, {"AT+CMER", bt_hfp_ag_cmer_handler}, @@ -1612,6 +1740,7 @@ static struct bt_hfp_ag_at_cmd_handler cmd_handlers[] = { {"ATD", bt_hfp_ag_atd_handler}, {"AT+BLDN", bt_hfp_ag_bldn_handler}, {"AT+CLIP", bt_hfp_ag_clip_handler}, {"AT+VGM", bt_hfp_ag_vgm_handler}, {"AT+VGS", bt_hfp_ag_vgs_handler}, {"AT+NREC", bt_hfp_ag_nrec_handler}, + {"AT+BTRH", bt_hfp_ag_btrh_handler}, }; static void hfp_ag_connected(struct bt_rfcomm_dlc *dlc) @@ -2109,9 +2238,10 @@ int bt_hfp_ag_remote_incoming(struct bt_hfp_ag *ag, const char *number) return err; } -int bt_hfp_ag_reject(struct bt_hfp_ag *ag) +int bt_hfp_ag_hold_incoming(struct bt_hfp_ag *ag) { - int err; + int err = 0; + bt_hfp_call_state_t call_state; LOG_DBG(""); @@ -2125,25 +2255,76 @@ int bt_hfp_ag_reject(struct bt_hfp_ag *ag) return -ENOTCONN; } - if ((ag->call_state != BT_HFP_CALL_ALERTING) && (ag->call_state != BT_HFP_CALL_INCOMING)) { - hfp_ag_unlock(ag); + call_state = ag->call_state; + hfp_ag_unlock(ag); + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL)) { + return -EINVAL; + } + + if ((call_state == BT_HFP_CALL_ALERTING) || + (call_state == BT_HFP_CALL_INCOMING)) { + atomic_set_bit(ag->flags, BT_HFP_AG_INCOMING_HELD); + err = hfp_ag_send_data(ag, btrh_held_cb, NULL, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_ON_HOLD); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + return err; + } + + return -EINVAL; +} + +int bt_hfp_ag_reject(struct bt_hfp_ag *ag) +{ + int err; + bt_hfp_call_state_t call_state; + + LOG_DBG(""); + + if (ag == NULL) { return -EINVAL; } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + call_state = ag->call_state; hfp_ag_unlock(ag); if (!atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL)) { return -EINVAL; } - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, - bt_hfp_ag_reject_cb, NULL); + if ((call_state == BT_HFP_CALL_ALERTING) || + (call_state == BT_HFP_CALL_INCOMING)) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, + bt_hfp_ag_reject_cb, NULL); - return err; + return err; + } + + if ((call_state == BT_HFP_CALL_ACTIVE) && + atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_INCOMING_HELD)) { + err = hfp_ag_send_data(ag, btrh_reject_cb, NULL, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_REJECTED); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + return err; + } + + return -EINVAL; } int bt_hfp_ag_accept(struct bt_hfp_ag *ag) { int err; + bt_hfp_call_state_t call_state; LOG_DBG(""); @@ -2157,33 +2338,45 @@ int bt_hfp_ag_accept(struct bt_hfp_ag *ag) return -ENOTCONN; } - if (ag->call_state != BT_HFP_CALL_ALERTING) { - hfp_ag_unlock(ag); - return -EINVAL; - } + call_state = ag->call_state; hfp_ag_unlock(ag); if (!atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL)) { return -EINVAL; } - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, bt_hfp_ag_accept_cb, NULL); - if (err != 0) { - LOG_ERR("Fail to send err :(%d)", err); + if (call_state == BT_HFP_CALL_ALERTING) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, bt_hfp_ag_accept_cb, NULL); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, + bt_hfp_ag_audio_connection, NULL); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + + return err; } - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, - bt_hfp_ag_audio_connection, NULL); - if (err != 0) { - LOG_ERR("Fail to send err :(%d)", err); + if (atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_INCOMING_HELD) && + (call_state == BT_HFP_CALL_ACTIVE)) { + err = hfp_ag_send_data(ag, btrh_accept_cb, NULL, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_ACCEPTED); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + return err; } - return err; + return -EINVAL; } int bt_hfp_ag_terminate(struct bt_hfp_ag *ag) { int err; + bt_hfp_call_state_t call_state; LOG_DBG(""); @@ -2197,11 +2390,23 @@ int bt_hfp_ag_terminate(struct bt_hfp_ag *ag) return -ENOTCONN; } - if ((ag->call_state != BT_HFP_CALL_ACTIVE) && (ag->call_state != BT_HFP_CALL_HOLD)) { - hfp_ag_unlock(ag); + call_state = ag->call_state; + hfp_ag_unlock(ag); + + if (atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_INCOMING_HELD) && + atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL) && + (call_state == BT_HFP_CALL_ACTIVE)) { + err = hfp_ag_send_data(ag, btrh_reject_cb, NULL, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_REJECTED); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + return err; + } + + if ((call_state != BT_HFP_CALL_ACTIVE) && (call_state != BT_HFP_CALL_HOLD)) { return -EINVAL; } - hfp_ag_unlock(ag); err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, bt_hfp_ag_terminate_cb, NULL); diff --git a/subsys/bluetooth/host/classic/hfp_ag_internal.h b/subsys/bluetooth/host/classic/hfp_ag_internal.h index 14d567e1ac97a..9df05423607ff 100644 --- a/subsys/bluetooth/host/classic/hfp_ag_internal.h +++ b/subsys/bluetooth/host/classic/hfp_ag_internal.h @@ -62,6 +62,7 @@ enum { BT_HFP_AG_INBAND_RING, /* In-band ring */ BT_HFP_AG_COPS_SET, /* Query Operator selection */ BT_HFP_AG_INCOMING_CALL, /* Incoming call */ + BT_HFP_AG_INCOMING_HELD, /* Incoming call held */ BT_HFP_AG_AUDIO_CONN, /* Audio connection */ BT_HFP_AG_CODEC_CONN, /* Codec connection is ongoing */ BT_HFP_AG_CODEC_CHANGED, /* Codec Id Changed */ From 68bca324253a27b40f94211f04867b24bb1e7cfb Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Thu, 1 Aug 2024 11:53:19 +0800 Subject: [PATCH 34/76] Bluetooth: HFP_AG: Support Enhanced call status Add a configuration `CONFIG_BT_HFP_AG_ECS` to enable the supported feature `Enhanced call status`. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/Kconfig | 5 +++++ subsys/bluetooth/host/classic/hfp_ag_internal.h | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/subsys/bluetooth/host/classic/Kconfig b/subsys/bluetooth/host/classic/Kconfig index 1c76807322bf8..da86d5757ccb3 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -228,6 +228,11 @@ config BT_HFP_AG_ECNR help This option enables EC and/or NR function for HFP AG +config BT_HFP_AG_ECS + bool "Enhanced call status for HFP AG [EXPERIMENTAL]" + help + This option enables Enhanced call status for HFP AG + config BT_HFP_AG_TX_BUF_COUNT int "Maximum number of TX buffers for HFP AG [EXPERIMENTAL]" default BT_RFCOMM_TX_MAX diff --git a/subsys/bluetooth/host/classic/hfp_ag_internal.h b/subsys/bluetooth/host/classic/hfp_ag_internal.h index 9df05423607ff..dc9da59cea728 100644 --- a/subsys/bluetooth/host/classic/hfp_ag_internal.h +++ b/subsys/bluetooth/host/classic/hfp_ag_internal.h @@ -40,13 +40,20 @@ #define BT_HFP_AG_SDP_FEATURE_ECNR_ENABLE 0 #endif /* CONFIG_BT_HFP_AG_CODEC_NEG */ +#if defined(CONFIG_BT_HFP_AG_ECS) +#define BT_HFP_AG_FEATURE_ECS_ENABLE BT_HFP_AG_FEATURE_ECS +#else +#define BT_HFP_AG_FEATURE_ECS_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_ECS*/ + /* HFP AG Supported features */ #define BT_HFP_AG_SUPPORTED_FEATURES (\ BT_HFP_AG_FEATURE_3WAY_CALL | \ BT_HFP_AG_FEATURE_INBAND_RINGTONE | \ BT_HFP_AG_FEATURE_EXT_ERR_ENABLE | \ BT_HFP_AG_FEATURE_CODEC_NEG_ENABLE | \ - BT_HFP_AG_FEATURE_ECNR_ENABLE) + BT_HFP_AG_FEATURE_ECNR_ENABLE | \ + BT_HFP_AG_FEATURE_ECS_ENABLE) /* HFP AG Supported features in SDP */ #define BT_HFP_AG_SDP_SUPPORTED_FEATURES (\ From 86d073abc167b15dd344903c023cafd28a704b93 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Fri, 2 Aug 2024 10:05:54 +0800 Subject: [PATCH 35/76] Bluetooth: HFP_AG: Handle AT+CCWA command Add a flag `BT_HFP_AG_CCWA_ENABLE` to keep the status of `Call Waiting Notification`. Set/clear the flag according to the value of received command AT+CCWA. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/hfp_ag.c | 28 ++++++++++++++++++- .../bluetooth/host/classic/hfp_ag_internal.h | 1 + 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index 7e2ea8e58ac98..883fe4d900104 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -1729,6 +1729,32 @@ static int bt_hfp_ag_btrh_handler(struct bt_hfp_ag *ag, struct net_buf *buf) return -ENOTSUP; } +static int bt_hfp_ag_ccwa_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t value; + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + err = get_number(buf, &value); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + if (value > 1) { + return -ENOTSUP; + } + + atomic_set_bit_to(ag->flags, BT_HFP_AG_CCWA_ENABLE, value); + return 0; +} + static struct bt_hfp_ag_at_cmd_handler cmd_handlers[] = { {"AT+BRSF", bt_hfp_ag_brsf_handler}, {"AT+BAC", bt_hfp_ag_bac_handler}, {"AT+CIND", bt_hfp_ag_cind_handler}, {"AT+CMER", bt_hfp_ag_cmer_handler}, @@ -1740,7 +1766,7 @@ static struct bt_hfp_ag_at_cmd_handler cmd_handlers[] = { {"ATD", bt_hfp_ag_atd_handler}, {"AT+BLDN", bt_hfp_ag_bldn_handler}, {"AT+CLIP", bt_hfp_ag_clip_handler}, {"AT+VGM", bt_hfp_ag_vgm_handler}, {"AT+VGS", bt_hfp_ag_vgs_handler}, {"AT+NREC", bt_hfp_ag_nrec_handler}, - {"AT+BTRH", bt_hfp_ag_btrh_handler}, + {"AT+BTRH", bt_hfp_ag_btrh_handler}, {"AT+CCWA", bt_hfp_ag_ccwa_handler}, }; static void hfp_ag_connected(struct bt_rfcomm_dlc *dlc) diff --git a/subsys/bluetooth/host/classic/hfp_ag_internal.h b/subsys/bluetooth/host/classic/hfp_ag_internal.h index dc9da59cea728..7a13a33677c58 100644 --- a/subsys/bluetooth/host/classic/hfp_ag_internal.h +++ b/subsys/bluetooth/host/classic/hfp_ag_internal.h @@ -66,6 +66,7 @@ enum { BT_HFP_AG_CMEE_ENABLE, /* Extended Audio Gateway Error Result Code */ BT_HFP_AG_CMER_ENABLE, /* Indicator Events Reporting */ BT_HFP_AG_CLIP_ENABLE, /* Calling Line Identification notification */ + BT_HFP_AG_CCWA_ENABLE, /* Call Waiting notification */ BT_HFP_AG_INBAND_RING, /* In-band ring */ BT_HFP_AG_COPS_SET, /* Query Operator selection */ BT_HFP_AG_INCOMING_CALL, /* Incoming call */ From f83fd1c21b7febc19d24ab4aa57de7908ab9ce1f Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Mon, 2 Sep 2024 11:14:07 +0800 Subject: [PATCH 36/76] Bluetooth: HFP_HF: Code clean Remove useless function `bt_hfp_hf_send_cmd`. And related callback `cmd_complete_cb`. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 42 ------------- subsys/bluetooth/host/classic/hfp_hf.c | 74 ----------------------- 2 files changed, 116 deletions(-) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index ea8ee656249ee..97860f1c2bd0b 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -23,28 +23,6 @@ extern "C" { #endif -/* AT Commands */ -enum bt_hfp_hf_at_cmd { - BT_HFP_HF_ATA, - BT_HFP_HF_AT_CHUP, -}; - -/* - * Command complete types for the application - */ -#define HFP_HF_CMD_OK 0 -#define HFP_HF_CMD_ERROR 1 -#define HFP_HF_CMD_CME_ERROR 2 -#define HFP_HF_CMD_UNKNOWN_ERROR 4 - -/** @brief HFP HF Command completion field */ -struct bt_hfp_hf_cmd_complete { - /* Command complete status */ - uint8_t type; - /* CME error number to be added */ - uint8_t cme; -}; - /* HFP CODEC IDs */ #define BT_HFP_HF_CODEC_CVSD 0x01 #define BT_HFP_HF_CODEC_MSBC 0x02 @@ -191,15 +169,6 @@ struct bt_hfp_hf_cb { * @param conn Connection object. */ void (*ring_indication)(struct bt_conn *conn); - /** HF notify command completed callback to application - * - * The command sent from the application is notified about its status - * - * @param conn Connection object. - * @param cmd structure contains status of the command including cme. - */ - void (*cmd_complete_cb)(struct bt_conn *conn, - struct bt_hfp_hf_cmd_complete *cmd); /** HF call dialing Callback * * This callback provides call dialing result to the application. @@ -319,17 +288,6 @@ struct bt_hfp_hf_cb { */ int bt_hfp_hf_register(struct bt_hfp_hf_cb *cb); -/** @brief Handsfree client Send AT - * - * Send specific AT commands to handsfree client profile. - * - * @param conn Connection object. - * @param cmd AT command to be sent. - * - * @return 0 in case of success or negative value in case of error. - */ -int bt_hfp_hf_send_cmd(struct bt_conn *conn, enum bt_hfp_hf_at_cmd cmd); - /** @brief Handsfree HF enable/disable Calling Line Identification (CLI) Notification * * Enable/disable Calling Line Identification (CLI) Notification. diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 6bd8cd88c5029..0dd7661775871 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -788,39 +788,6 @@ int unsolicited_cb(struct at_client *hf_at, struct net_buf *buf) return -ENOMSG; } -int cmd_complete(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) -{ - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - struct bt_conn *conn = hf->acl; - struct bt_hfp_hf_cmd_complete cmd = { 0 }; - - LOG_DBG(""); - - switch (result) { - case AT_RESULT_OK: - cmd.type = HFP_HF_CMD_OK; - break; - case AT_RESULT_ERROR: - cmd.type = HFP_HF_CMD_ERROR; - break; - case AT_RESULT_CME_ERROR: - cmd.type = HFP_HF_CMD_CME_ERROR; - cmd.cme = cme_err; - break; - default: - LOG_ERR("Unknown error code"); - cmd.type = HFP_HF_CMD_UNKNOWN_ERROR; - break; - } - - if (bt_hf->cmd_complete_cb) { - bt_hf->cmd_complete_cb(conn, &cmd); - } - - return 0; -} - static int send_at_cmee(struct bt_hfp_hf *hf, at_finish_cb_t cb) { if (hf->ag_features & BT_HFP_AG_FEATURE_EXT_ERR) { @@ -1158,47 +1125,6 @@ static struct bt_hfp_hf *bt_hfp_hf_lookup_bt_conn(struct bt_conn *conn) return NULL; } -int bt_hfp_hf_send_cmd(struct bt_conn *conn, enum bt_hfp_hf_at_cmd cmd) -{ - struct bt_hfp_hf *hf; - int err; - - LOG_DBG(""); - - if (!conn) { - LOG_ERR("Invalid connection"); - return -ENOTCONN; - } - - hf = bt_hfp_hf_lookup_bt_conn(conn); - if (!hf) { - LOG_ERR("No HF connection found"); - return -ENOTCONN; - } - - switch (cmd) { - case BT_HFP_HF_ATA: - err = hfp_hf_send_cmd(hf, NULL, cmd_complete, "ATA"); - if (err < 0) { - LOG_ERR("Failed ATA"); - return err; - } - break; - case BT_HFP_HF_AT_CHUP: - err = hfp_hf_send_cmd(hf, NULL, cmd_complete, "AT+CHUP"); - if (err < 0) { - LOG_ERR("Failed AT+CHUP"); - return err; - } - break; - default: - LOG_ERR("Invalid AT Command"); - return -EINVAL; - } - - return 0; -} - #if defined(CONFIG_BT_HFP_HF_CLI) static int cli_finish(struct at_client *hf_at, enum at_result result, enum at_cme cme_err) From 30257defac8d6c2e21b47c5ec548c33b2cf18115 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Wed, 13 Nov 2024 16:23:12 +0800 Subject: [PATCH 37/76] Bluetooth: HFP_AG: Enable 3-way feature Add a configuration `BT_HFP_AG_3WAY_CALL` to control the feature. Add a configuration `BT_HFP_AG_MAX_CALLS` to define supported maximum calls. Add a structure `struct bt_hfp_ag_call` to manage the call. Add the call object to callback `outgoing`, and`incoming`. Use call object to replace AG object for callbacks `incoming_held`, `ringing`, `accept`, `reject`, and `terminate`. Add callback `held` to notify the call held status. Use call object to replace AG object for function `bt_hfp_ag_hold_incoming`, `bt_hfp_ag_reject`, `bt_hfp_ag_accept`, `bt_hfp_ag_terminate`, `bt_hfp_ag_remote_ringing`, `bt_hfp_ag_remote_reject`, `bt_hfp_ag_remote_accept`, and `bt_hfp_ag_remote_terminate`. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_ag.h | 125 +- subsys/bluetooth/host/classic/Kconfig | 20 + subsys/bluetooth/host/classic/hfp_ag.c | 2648 +++++++++++++---- .../bluetooth/host/classic/hfp_ag_internal.h | 86 +- subsys/bluetooth/host/classic/hfp_internal.h | 32 + 5 files changed, 2205 insertions(+), 706 deletions(-) diff --git a/include/zephyr/bluetooth/classic/hfp_ag.h b/include/zephyr/bluetooth/classic/hfp_ag.h index 3281635c80b7e..5e4f882919509 100644 --- a/include/zephyr/bluetooth/classic/hfp_ag.h +++ b/include/zephyr/bluetooth/classic/hfp_ag.h @@ -42,6 +42,7 @@ enum bt_hfp_ag_indicator { #define BT_HFP_AG_CODEC_LC3_SWB 0x03 struct bt_hfp_ag; +struct bt_hfp_ag_call; /** @brief HFP profile AG application callback */ struct bt_hfp_ag_cb { @@ -117,9 +118,10 @@ struct bt_hfp_ag_cb { * new call is outgoing. * * @param ag HFP AG object. + * @param call HFP AG call object. * @param number Dailing number */ - void (*outgoing)(struct bt_hfp_ag *ag, const char *number); + void (*outgoing)(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call, const char *number); /** HF incoming Callback * @@ -127,55 +129,74 @@ struct bt_hfp_ag_cb { * new call is incoming. * * @param ag HFP AG object. + * @param call HFP AG call object. * @param number Incoming number */ - void (*incoming)(struct bt_hfp_ag *ag, const char *number); + void (*incoming)(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call, const char *number); /** HF incoming call is held Callback * * If this callback is provided it will be called whenever the * incoming call is held but not accepted. * - * @param ag HFP AG object. + * @param call HFP AG call object. */ - void (*incoming_held)(struct bt_hfp_ag *ag); + void (*incoming_held)(struct bt_hfp_ag_call *call); /** HF ringing Callback * * If this callback is provided it will be called whenever the * call is in the ringing * - * @param ag HFP AG object. + * @param call HFP AG call object. * @param in_bond true - in-bond ringing, false - No in-bond ringing */ - void (*ringing)(struct bt_hfp_ag *ag, bool in_band); + void (*ringing)(struct bt_hfp_ag_call *call, bool in_band); /** HF call accept Callback * * If this callback is provided it will be called whenever the * call is accepted. * - * @param ag HFP AG object. + * @param call HFP AG call object. + */ + void (*accept)(struct bt_hfp_ag_call *call); + + /** HF call held Callback + * + * If this callback is provided it will be called whenever the + * call is held. + * + * @param call HFP AG call object. + */ + void (*held)(struct bt_hfp_ag_call *call); + + /** HF call retrieve Callback + * + * If this callback is provided it will be called whenever the + * call is retrieved. + * + * @param call HFP AG call object. */ - void (*accept)(struct bt_hfp_ag *ag); + void (*retrieve)(struct bt_hfp_ag_call *call); /** HF call reject Callback * * If this callback is provided it will be called whenever the * call is rejected. * - * @param ag HFP AG object. + * @param call HFP AG call object. */ - void (*reject)(struct bt_hfp_ag *ag); + void (*reject)(struct bt_hfp_ag_call *call); /** HF call terminate Callback * * If this callback is provided it will be called whenever the * call is terminated. * - * @param ag HFP AG object. + * @param call HFP AG call object. */ - void (*terminate)(struct bt_hfp_ag *ag); + void (*terminate)(struct bt_hfp_ag_call *call); /** Supported codec Ids callback * @@ -244,6 +265,23 @@ struct bt_hfp_ag_cb { * @param ag HFP AG object. */ void (*ecnr_turn_off)(struct bt_hfp_ag *ag); + + /** HF explicit call transfer callback + * + * If this callback is provided it will be called whenever the + * AT+CHLD=4 is sent from HF. + * When the callback is notified, the application should connect + * the two calls and disconnects the subscriber from both calls + * (Explicit Call Transfer). + * After the callback returned, the call objects will be invalid. + * If the callback is NULL, the response result code of AT command + * will be an AT ERROR. + * If @kconfig{CONFIG_BT_HFP_AG_3WAY_CALL} is not enabled, the + * callback will not be notified. + * + * @param ag HFP AG object. + */ + void (*explicit_call_transfer)(struct bt_hfp_ag *ag); }; /** @brief Register HFP AG profile @@ -294,41 +332,61 @@ int bt_hfp_ag_remote_incoming(struct bt_hfp_ag *ag, const char *number); * * Put the incoming call on hold. * - * @param ag HFP AG object. + * @param call HFP AG call object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_ag_hold_incoming(struct bt_hfp_ag *ag); +int bt_hfp_ag_hold_incoming(struct bt_hfp_ag_call *call); /** @brief Reject the incoming call * * Reject the incoming call. * - * @param ag HFP AG object. + * @param call HFP AG call object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_ag_reject(struct bt_hfp_ag *ag); +int bt_hfp_ag_reject(struct bt_hfp_ag_call *call); /** @brief Accept the incoming call * * Accept the incoming call. * - * @param ag HFP AG object. + * @param call HFP AG call object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_ag_accept(struct bt_hfp_ag *ag); +int bt_hfp_ag_accept(struct bt_hfp_ag_call *call); /** @brief Terminate the active/hold call * * Terminate the active/hold call. * - * @param ag HFP AG object. + * @param call HFP AG call object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_terminate(struct bt_hfp_ag_call *call); + +/** @brief Retrieve the held call + * + * Retrieve the held call. + * + * @param call HFP AG call object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_ag_terminate(struct bt_hfp_ag *ag); +int bt_hfp_ag_retrieve(struct bt_hfp_ag_call *call); + +/** @brief Hold the active call + * + * Hold the active call. + * + * @param call HFP AG call object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_hold(struct bt_hfp_ag_call *call); /** @brief Dial a call * @@ -345,41 +403,54 @@ int bt_hfp_ag_outgoing(struct bt_hfp_ag *ag, const char *number); * * Notify HFP Unit that the remote starts ringing. * - * @param ag HFP AG object. + * @param call HFP AG call object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_ag_remote_ringing(struct bt_hfp_ag *ag); +int bt_hfp_ag_remote_ringing(struct bt_hfp_ag_call *call); /** @brief Notify HFP Unit that the remote rejects the call * * Notify HFP Unit that the remote rejects the call. * - * @param ag HFP AG object. + * @param call HFP AG call object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_ag_remote_reject(struct bt_hfp_ag *ag); +int bt_hfp_ag_remote_reject(struct bt_hfp_ag_call *call); /** @brief Notify HFP Unit that the remote accepts the call * * Notify HFP Unit that the remote accepts the call. * - * @param ag HFP AG object. + * @param call HFP AG call object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_ag_remote_accept(struct bt_hfp_ag *ag); +int bt_hfp_ag_remote_accept(struct bt_hfp_ag_call *call); /** @brief Notify HFP Unit that the remote terminates the active/hold call * * Notify HFP Unit that the remote terminates the active/hold call. * + * @param call HFP AG call object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_remote_terminate(struct bt_hfp_ag_call *call); + +/** @brief explicit call transfer + * + * Connects the two calls and disconnects the subscriber from + * both calls (Explicit Call Transfer). + * If @kconfig{CONFIG_BT_HFP_AG_3WAY_CALL} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * * @param ag HFP AG object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_ag_remote_terminate(struct bt_hfp_ag *ag); +int bt_hfp_ag_explicit_call_transfer(struct bt_hfp_ag *ag); /** @brief Set the HF microphone gain * diff --git a/subsys/bluetooth/host/classic/Kconfig b/subsys/bluetooth/host/classic/Kconfig index da86d5757ccb3..28cf634a6e599 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -228,11 +228,31 @@ config BT_HFP_AG_ECNR help This option enables EC and/or NR function for HFP AG +config BT_HFP_AG_3WAY_CALL + bool "Three-way calling for HFP AG [EXPERIMENTAL]" + help + This option enables Three-way calling for HFP AG + +config BT_HFP_AG_MAX_CALLS + int "Maximum supported calls for HFP AG [EXPERIMENTAL]" + default 2 if BT_HFP_AG_3WAY_CALL + default 1 + range 1 2 + help + This option sets maximum supported calls for HFP AG + config BT_HFP_AG_ECS bool "Enhanced call status for HFP AG [EXPERIMENTAL]" + default y if BT_HFP_AG_3WAY_CALL help This option enables Enhanced call status for HFP AG +config BT_HFP_AG_ECC + bool "Enhanced call control for HFP AG [EXPERIMENTAL]" + default y if BT_HFP_AG_3WAY_CALL + help + This option enables Enhanced call control for HFP AG + config BT_HFP_AG_TX_BUF_COUNT int "Maximum number of TX buffers for HFP AG [EXPERIMENTAL]" default BT_RFCOMM_TX_MAX diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index 883fe4d900104..f63de6459fd4d 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -134,12 +134,19 @@ static void hfp_ag_unlock(struct bt_hfp_ag *ag) static void bt_hfp_ag_set_state(struct bt_hfp_ag *ag, bt_hfp_state_t state) { - LOG_DBG("update state %p, old %d -> new %d", ag, ag->state, state); + bt_hfp_state_t old_state; hfp_ag_lock(ag); + old_state = ag->state; ag->state = state; hfp_ag_unlock(ag); + LOG_DBG("update state %p, old %d -> new %d", ag, old_state, state); + + if (old_state == state) { + return; + } + switch (state) { case BT_HFP_DISCONNECTED: if (bt_ag && bt_ag->disconnected) { @@ -163,51 +170,72 @@ static void bt_hfp_ag_set_state(struct bt_hfp_ag *ag, bt_hfp_state_t state) } } -static void bt_hfp_ag_set_call_state(struct bt_hfp_ag *ag, bt_hfp_call_state_t call_state) +static struct bt_hfp_ag_call *get_call_from_number(struct bt_hfp_ag *ag, const char *number, + uint8_t type) { - bt_hfp_state_t state; + struct bt_hfp_ag_call *call; - LOG_DBG("update call state %p, old %d -> new %d", ag, ag->call_state, call_state); + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; - hfp_ag_lock(ag); - ag->call_state = call_state; - state = ag->state; - hfp_ag_unlock(ag); + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } - switch (call_state) { - case BT_HFP_CALL_TERMINATE: - atomic_clear_bit(ag->flags, BT_HFP_AG_CREATING_SCO); - atomic_clear_bit(ag->flags, BT_HFP_AG_INCOMING_CALL); - k_work_cancel_delayable(&ag->deferred_work); - break; - case BT_HFP_CALL_OUTGOING: - k_work_reschedule(&ag->deferred_work, K_SECONDS(CONFIG_BT_HFP_AG_OUTGOING_TIMEOUT)); - break; - case BT_HFP_CALL_INCOMING: - k_work_reschedule(&ag->deferred_work, K_SECONDS(CONFIG_BT_HFP_AG_INCOMING_TIMEOUT)); - break; - case BT_HFP_CALL_ALERTING: - k_work_reschedule(&ag->ringing_work, K_NO_WAIT); - k_work_reschedule(&ag->deferred_work, K_SECONDS(CONFIG_BT_HFP_AG_ALERTING_TIMEOUT)); - break; - case BT_HFP_CALL_ACTIVE: - k_work_cancel_delayable(&ag->ringing_work); - k_work_cancel_delayable(&ag->deferred_work); - break; - case BT_HFP_CALL_HOLD: - break; - default: - /* Invalid state */ - break; + if (call->type != type) { + continue; + } + + if (strcmp(call->number, number)) { + continue; + } + + return call; } - if (state == BT_HFP_DISCONNECTING) { - int err = bt_rfcomm_dlc_disconnect(&ag->rfcomm_dlc); + return NULL; +} - if (err) { - LOG_ERR("Fail to disconnect DLC %p", &ag->rfcomm_dlc); +static void bt_ag_deferred_work(struct k_work *work); +static void bt_ag_ringing_work(struct k_work *work); + +static struct bt_hfp_ag_call *get_new_call(struct bt_hfp_ag *ag, const char *number, uint8_t type) +{ + struct bt_hfp_ag_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_and_set_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + hfp_ag_lock(ag); + /* Copy number to ag->number including null-character */ + strcpy(call->number, number); + call->type = type; + call->ag = ag; + k_work_init_delayable(&call->deferred_work, bt_ag_deferred_work); + k_work_init_delayable(&call->ringing_work, bt_ag_ringing_work); + hfp_ag_unlock(ag); + return call; + } + } + + return NULL; +} + +static int get_none_released_calls(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + int count = 0; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + count++; } } + + return count; } static struct bt_ag_tx *bt_ag_tx_alloc(void) @@ -267,67 +295,11 @@ static int hfp_ag_next_step(struct bt_hfp_ag *ag, bt_hfp_ag_tx_cb_t cb, void *us return 0; } -static int hfp_ag_send(struct bt_hfp_ag *ag, struct bt_ag_tx *tx) -{ - int err = bt_rfcomm_dlc_send(&ag->rfcomm_dlc, tx->buf); - - if (err < 0) { - net_buf_unref(tx->buf); - } - - return err; -} - -static void bt_ag_tx_work(struct k_work *work) -{ - struct k_work_delayable *dwork = k_work_delayable_from_work(work); - struct bt_hfp_ag *ag = CONTAINER_OF(dwork, struct bt_hfp_ag, tx_work); - sys_snode_t *node; - struct bt_ag_tx *tx; - bt_hfp_state_t state; - - hfp_ag_lock(ag); - state = ag->state; - if ((state == BT_HFP_DISCONNECTED) || (state == BT_HFP_DISCONNECTING)) { - LOG_ERR("AG %p is not connected", ag); - goto unlock; - } - - node = sys_slist_peek_head(&ag->tx_pending); - if (!node) { - LOG_DBG("No pending tx"); - goto unlock; - } - - tx = CONTAINER_OF(node, struct bt_ag_tx, node); - - if (!atomic_test_and_set_bit(ag->flags, BT_HFP_AG_TX_ONGOING)) { - LOG_DBG("AG %p sending tx %p", ag, tx); - int err = hfp_ag_send(ag, tx); - - if (err < 0) { - LOG_ERR("Rfcomm send error :(%d)", err); - sys_slist_find_and_remove(&ag->tx_pending, &tx->node); - tx->err = err; - k_fifo_put(&ag_tx_notify, tx); - /* Clear the tx ongoing flag */ - if (!atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_TX_ONGOING)) { - LOG_WRN("tx ongoing flag is not set"); - } - /* Due to the work is failed, restart the tx work */ - k_work_reschedule(&ag->tx_work, K_NO_WAIT); - } - } - -unlock: - hfp_ag_unlock(ag); -} - static int hfp_ag_send_data(struct bt_hfp_ag *ag, bt_hfp_ag_tx_cb_t cb, void *user_data, const char *format, ...) { - struct net_buf *buf; - struct bt_ag_tx *tx; + struct net_buf *buf = NULL; + struct bt_ag_tx *tx = NULL; va_list vargs; int err; bt_hfp_state_t state; @@ -339,20 +311,22 @@ static int hfp_ag_send_data(struct bt_hfp_ag *ag, bt_hfp_ag_tx_cb_t cb, void *us hfp_ag_unlock(ag); if ((state == BT_HFP_DISCONNECTED) || (state == BT_HFP_DISCONNECTING)) { LOG_ERR("AG %p is not connected", ag); - return -ENOTCONN; + err = -ENOTCONN; + goto failed; } buf = bt_rfcomm_create_pdu(&ag_pool); if (!buf) { LOG_ERR("No Buffers!"); - return -ENOMEM; + err = -ENOMEM; + goto failed; } tx = bt_ag_tx_alloc(); if (tx == NULL) { LOG_ERR("No tx buffers!"); - net_buf_unref(buf); - return -ENOMEM; + err = -ENOMEM; + goto failed; } LOG_DBG("buf %p tx %p", buf, tx); @@ -368,9 +342,7 @@ static int hfp_ag_send_data(struct bt_hfp_ag *ag, bt_hfp_ag_tx_cb_t cb, void *us if (err < 0) { LOG_ERR("Unable to format variable arguments"); - net_buf_unref(buf); - bt_ag_tx_free(tx); - return err; + goto failed; } net_buf_add(buf, err); @@ -385,256 +357,1122 @@ static int hfp_ag_send_data(struct bt_hfp_ag *ag, bt_hfp_ag_tx_cb_t cb, void *us k_work_reschedule(&ag->tx_work, K_NO_WAIT); return 0; + +failed: + if (buf) { + net_buf_unref(buf); + } + + if (tx) { + bt_ag_tx_free(tx); + } + bt_hfp_ag_disconnect(ag); + return err; } -static void skip_space(struct net_buf *buf) +static int hfp_ag_update_indicator(struct bt_hfp_ag *ag, enum bt_hfp_ag_indicator index, + uint8_t value, bt_hfp_ag_tx_cb_t cb, void *user_data) { - while ((buf->len > 0) && (buf->data[0] == ' ')) { - (void)net_buf_pull(buf, 1); + int err; + uint8_t old_value; + + hfp_ag_lock(ag); + old_value = ag->indicator_value[index]; + if (value == old_value) { + LOG_ERR("Duplicate value setting, indicator %d, old %d -> new %d", index, old_value, + value); + hfp_ag_unlock(ag); + if (cb) { + cb(ag, user_data); + } + return -EINVAL; + } + + ag->indicator_value[index] = value; + hfp_ag_unlock(ag); + + LOG_DBG("indicator %d, old %d -> new %d", index, old_value, value); + + err = hfp_ag_send_data(ag, cb, user_data, "\r\n+CIEV:%d,%d\r\n", (uint8_t)index + 1, value); + if (err) { + hfp_ag_lock(ag); + ag->indicator_value[index] = old_value; + hfp_ag_unlock(ag); + LOG_ERR("Fail to update indicator %d, current %d", index, old_value); } + + return err; } -static int get_number(struct net_buf *buf, uint32_t *number) +static int hfp_ag_force_update_indicator(struct bt_hfp_ag *ag, enum bt_hfp_ag_indicator index, + uint8_t value, bt_hfp_ag_tx_cb_t cb, void *user_data) { - int err = -EINVAL; + int err; + uint8_t old_value; - *number = 0; + hfp_ag_lock(ag); + old_value = ag->indicator_value[index]; + ag->indicator_value[index] = value; + hfp_ag_unlock(ag); - skip_space(buf); - while (buf->len > 0) { - if ((buf->data[0] >= '0') && (buf->data[0] <= '9')) { - *number = *number * 10 + buf->data[0] - '0'; - (void)net_buf_pull(buf, 1); - err = 0; - } else { - break; - } + LOG_DBG("indicator %d, old %d -> new %d", index, old_value, value); + + err = hfp_ag_send_data(ag, cb, user_data, "\r\n+CIEV:%d,%d\r\n", (uint8_t)index + 1, value); + if (err) { + hfp_ag_lock(ag); + ag->indicator_value[index] = old_value; + hfp_ag_unlock(ag); + LOG_ERR("Fail to update indicator %d, current %d", index, old_value); } - skip_space(buf); return err; } -static bool is_char(struct net_buf *buf, uint8_t c) +static void clear_call_setup_ind_cb(struct bt_hfp_ag *ag, void *user_data) { - bool found = false; + uint8_t call_ind; + int err; - skip_space(buf); - if (buf->len > 0) { - if (buf->data[0] == c) { - (void)net_buf_pull(buf, 1); - found = true; + hfp_ag_lock(ag); + call_ind = ag->indicator_value[BT_HFP_AG_CALL_IND]; + hfp_ag_unlock(ag); + + if (call_ind) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, NULL, NULL); + if (err) { + LOG_ERR("Fail to clear call_setup ind"); } } - skip_space(buf); - - return found; } -static int bt_hfp_ag_brsf_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +static void clear_call_ind_cb(struct bt_hfp_ag *ag, void *user_data) { - uint32_t hf_features; + uint8_t call_setup_ind; int err; - if (!is_char(buf, '=')) { - return -ENOTSUP; - } + hfp_ag_lock(ag); + call_setup_ind = ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND]; + hfp_ag_unlock(ag); - err = get_number(buf, &hf_features); - if (err != 0) { - return -ENOTSUP; + if (call_setup_ind) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, + NULL, NULL); + if (err) { + LOG_ERR("Fail to clear call_setup ind"); + } } +} - if (!is_char(buf, '\r')) { - return -ENOTSUP; - } +static void clear_call_and_call_setup_ind(struct bt_hfp_ag *ag) +{ + uint8_t call_setup_ind; + uint8_t call_ind; + int err; hfp_ag_lock(ag); - ag->hf_features = hf_features; + call_setup_ind = ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND]; + call_ind = ag->indicator_value[BT_HFP_AG_CALL_IND]; hfp_ag_unlock(ag); - return hfp_ag_send_data(ag, NULL, NULL, "\r\n+BRSF:%d\r\n", ag->ag_features); + if (call_setup_ind) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, + clear_call_setup_ind_cb, NULL); + if (err) { + LOG_ERR("Fail to clear call_setup ind"); + } + } + + if (call_ind) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, clear_call_ind_cb, NULL); + if (err) { + LOG_ERR("Fail to clear call ind"); + } + } } -static int bt_hfp_ag_bac_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +static int get_active_calls(struct bt_hfp_ag *ag) { - uint32_t codec; - uint32_t codec_ids = 0U; - int err = 0; + struct bt_hfp_ag_call *call; + int count = 0; - if (!is_char(buf, '=')) { - return -ENOTSUP; + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + hfp_ag_lock(ag); + if ((call->call_state == BT_HFP_CALL_ACTIVE) && + (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD))) { + count++; + } + hfp_ag_unlock(ag); + } } - hfp_ag_lock(ag); - if (!(ag->hf_features & BT_HFP_HF_FEATURE_CODEC_NEG) || - !(ag->ag_features & BT_HFP_AG_FEATURE_CODEC_NEG)) { + return count; +} + +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) +static int get_active_held_calls(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + int count = 0; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + hfp_ag_lock(ag); + if ((call->call_state == BT_HFP_CALL_HOLD) || + ((call->call_state == BT_HFP_CALL_ACTIVE) && + (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)))) { + count++; + } + hfp_ag_unlock(ag); + } + } + + return count; +} +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + +static int get_held_calls(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + int count = 0; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + hfp_ag_lock(ag); + if ((call->call_state == BT_HFP_CALL_ACTIVE) && + (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD))) { + count++; + } else if (call->call_state == BT_HFP_CALL_HOLD) { + count++; + } + hfp_ag_unlock(ag); + } + } + + return count; +} + +static void ag_notify_call_held_ind(struct bt_hfp_ag *ag, void *user_data) +{ + int active_call_count; + int held_call_count; + int err; + uint8_t value; + + active_call_count = get_active_calls(ag); + held_call_count = get_held_calls(ag); + if (active_call_count && held_call_count) { + value = BT_HFP_CALL_HELD_ACTIVE_HELD; + } else if (held_call_count) { + value = BT_HFP_CALL_HELD_HELD; + } else { + value = BT_HFP_CALL_HELD_NONE; + } + + err = hfp_ag_force_update_indicator(ag, BT_HFP_AG_CALL_HELD_IND, value, NULL, NULL); + if (err) { + LOG_ERR("Fail to notify call_held ind :(%d)", err); + } +} + +static void free_call(struct bt_hfp_ag_call *call) +{ + int call_count; + struct bt_hfp_ag *ag; + int err; + + ag = call->ag; + + k_work_cancel_delayable(&call->ringing_work); + k_work_cancel_delayable(&call->deferred_work); + memset(call, 0, sizeof(*call)); + + call_count = get_none_released_calls(ag); + + if (!call_count) { + clear_call_and_call_setup_ind(ag); + } else { + err = hfp_ag_next_step(ag, ag_notify_call_held_ind, NULL); + if (err) { + LOG_ERR("Fail to notify call_held ind :(%d)", err); + } + } +} + +static void bt_hfp_ag_set_call_state(struct bt_hfp_ag_call *call, bt_hfp_call_state_t call_state) +{ + bt_hfp_state_t state; + struct bt_hfp_ag *ag = call->ag; + + LOG_DBG("update call state %p, old %d -> new %d", call, call->call_state, call_state); + + hfp_ag_lock(ag); + call->call_state = call_state; + state = ag->state; + hfp_ag_unlock(ag); + + switch (call_state) { + case BT_HFP_CALL_TERMINATE: + atomic_clear_bit(ag->flags, BT_HFP_AG_CREATING_SCO); + free_call(call); + break; + case BT_HFP_CALL_OUTGOING: + k_work_reschedule(&call->deferred_work, + K_SECONDS(CONFIG_BT_HFP_AG_OUTGOING_TIMEOUT)); + break; + case BT_HFP_CALL_INCOMING: + k_work_reschedule(&call->deferred_work, + K_SECONDS(CONFIG_BT_HFP_AG_INCOMING_TIMEOUT)); + break; + case BT_HFP_CALL_ALERTING: + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY)) { + k_work_reschedule(&call->ringing_work, K_NO_WAIT); + } else { + k_work_cancel_delayable(&call->ringing_work); + } + k_work_reschedule(&call->deferred_work, + K_SECONDS(CONFIG_BT_HFP_AG_ALERTING_TIMEOUT)); + break; + case BT_HFP_CALL_ACTIVE: + k_work_cancel_delayable(&call->ringing_work); + k_work_cancel_delayable(&call->deferred_work); + break; + case BT_HFP_CALL_HOLD: + break; + default: + /* Invalid state */ + break; + } + + if (state == BT_HFP_DISCONNECTING) { + int err = bt_rfcomm_dlc_disconnect(&ag->rfcomm_dlc); + + if (err) { + LOG_ERR("Fail to disconnect DLC %p", &ag->rfcomm_dlc); + } + } +} + +static void hfp_ag_close_sco(struct bt_hfp_ag *ag) +{ + struct bt_conn *sco; + int call_count; + + LOG_DBG(""); + + hfp_ag_lock(ag); + call_count = get_none_released_calls(ag); + if (call_count > 0) { + LOG_INF("Do not close sco because not all calls are released"); + hfp_ag_unlock(ag); + return; + } + + sco = ag->sco_chan.sco; + ag->sco_chan.sco = NULL; + hfp_ag_unlock(ag); + if (sco != NULL) { + LOG_DBG("Disconnect sco %p", sco); + bt_conn_disconnect(sco, BT_HCI_ERR_LOCALHOST_TERM_CONN); + } +} + +static void ag_reject_call(struct bt_hfp_ag_call *call) +{ + struct bt_hfp_ag *ag; + + ag = call->ag; + + if (bt_ag && bt_ag->reject) { + bt_ag->reject(call); + } + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_TERMINATE); + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_AUDIO_CONN)) { + LOG_DBG("It is not audio connection"); + hfp_ag_close_sco(ag); + } +} + +static void ag_terminate_call(struct bt_hfp_ag_call *call) +{ + struct bt_hfp_ag *ag; + + ag = call->ag; + + if (bt_ag && bt_ag->terminate) { + bt_ag->terminate(call); + } + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_TERMINATE); + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_AUDIO_CONN)) { + LOG_DBG("It is not audio connection"); + hfp_ag_close_sco(ag); + } +} + +static int hfp_ag_send(struct bt_hfp_ag *ag, struct bt_ag_tx *tx) +{ + int err = bt_rfcomm_dlc_send(&ag->rfcomm_dlc, tx->buf); + + if (err < 0) { + net_buf_unref(tx->buf); + } + + return err; +} + +static void bt_ag_tx_work(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_hfp_ag *ag = CONTAINER_OF(dwork, struct bt_hfp_ag, tx_work); + sys_snode_t *node; + struct bt_ag_tx *tx; + bt_hfp_state_t state; + + hfp_ag_lock(ag); + state = ag->state; + if ((state == BT_HFP_DISCONNECTED) || (state == BT_HFP_DISCONNECTING)) { + LOG_ERR("AG %p is not connected", ag); + goto unlock; + } + + node = sys_slist_peek_head(&ag->tx_pending); + if (!node) { + LOG_DBG("No pending tx"); + goto unlock; + } + + tx = CONTAINER_OF(node, struct bt_ag_tx, node); + + if (!atomic_test_and_set_bit(ag->flags, BT_HFP_AG_TX_ONGOING)) { + LOG_DBG("AG %p sending tx %p", ag, tx); + int err = hfp_ag_send(ag, tx); + + if (err < 0) { + LOG_ERR("Rfcomm send error :(%d)", err); + sys_slist_find_and_remove(&ag->tx_pending, &tx->node); + tx->err = err; + k_fifo_put(&ag_tx_notify, tx); + /* Clear the tx ongoing flag */ + if (!atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_TX_ONGOING)) { + LOG_WRN("tx ongoing flag is not set"); + } + /* Due to the work is failed, restart the tx work */ + k_work_reschedule(&ag->tx_work, K_NO_WAIT); + } + } + +unlock: + hfp_ag_unlock(ag); +} + +static void skip_space(struct net_buf *buf) +{ + size_t count = 0; + + while ((buf->len > count) && (buf->data[count] == ' ')) { + count++; + } + + if (count > 0) { + (void)net_buf_pull(buf, count); + } +} + +static int get_number(struct net_buf *buf, uint32_t *number) +{ + int err = -EINVAL; + + *number = 0; + + skip_space(buf); + while (buf->len > 0) { + if ((buf->data[0] >= '0') && (buf->data[0] <= '9')) { + *number = *number * 10 + buf->data[0] - '0'; + (void)net_buf_pull(buf, 1); + err = 0; + } else { + break; + } + } + skip_space(buf); + + return err; +} + +static bool is_char(struct net_buf *buf, uint8_t c) +{ + bool found = false; + + skip_space(buf); + if (buf->len > 0) { + if (buf->data[0] == c) { + (void)net_buf_pull(buf, 1); + found = true; + } + } + skip_space(buf); + + return found; +} + +static int bt_hfp_ag_brsf_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + uint32_t hf_features; + int err; + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + err = get_number(buf, &hf_features); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + hfp_ag_lock(ag); + ag->hf_features = hf_features; + hfp_ag_unlock(ag); + + return hfp_ag_send_data(ag, NULL, NULL, "\r\n+BRSF:%d\r\n", ag->ag_features); +} + +static int bt_hfp_ag_bac_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + uint32_t codec; + uint32_t codec_ids = 0U; + int err = 0; + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + hfp_ag_lock(ag); + if (!(ag->hf_features & BT_HFP_HF_FEATURE_CODEC_NEG) || + !(ag->ag_features & BT_HFP_AG_FEATURE_CODEC_NEG)) { + hfp_ag_unlock(ag); + return -EOPNOTSUPP; + } + hfp_ag_unlock(ag); + + while (buf->len > 0) { + err = get_number(buf, &codec); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, ',')) { + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + } + + if (codec < NUM_BITS(sizeof(codec_ids))) { + codec_ids |= BIT(codec); + } + } + + if (!(codec_ids & BIT(BT_HFP_AG_CODEC_CVSD))) { + return -EOPNOTSUPP; + } + + hfp_ag_lock(ag); + ag->hf_codec_ids = codec_ids; + ag->selected_codec_id = 0; + hfp_ag_unlock(ag); + + atomic_set_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED); + + if (atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_CODEC_CONN)) { + /* Codec connection is ended. It needs to be restarted. */ + LOG_DBG("Codec connection is ended. It needs to be restarted."); + if (bt_ag && bt_ag->codec_negotiate) { + bt_ag->codec_negotiate(ag, -EAGAIN); + } + } + + if (bt_ag && bt_ag->codec) { + bt_ag->codec(ag, ag->hf_codec_ids); + } + + return 0; +} + +static int bt_hfp_ag_cind_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + bool inquiry_status = true; + + if (is_char(buf, '=')) { + inquiry_status = false; + } + + if (!is_char(buf, '?')) { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + if (!inquiry_status) { + err = hfp_ag_send_data( + ag, NULL, NULL, + "\r\n+CIND:(\"%s\",(%d%s%d)),(\"%s\",(%d%s%d)),(\"%s\",(%d%s%d)),(\"%s\",(%" + "d%s%d)),(\"%s\",(%d%s%d)),(\"%s\",(%d%s%d)),(\"%s\",(%d%s%d))\r\n", + ag_ind[BT_HFP_AG_SERVICE_IND].name, ag_ind[BT_HFP_AG_SERVICE_IND].min, + ag_ind[BT_HFP_AG_SERVICE_IND].connector, ag_ind[BT_HFP_AG_SERVICE_IND].max, + ag_ind[BT_HFP_AG_CALL_IND].name, ag_ind[BT_HFP_AG_CALL_IND].min, + ag_ind[BT_HFP_AG_CALL_IND].connector, ag_ind[BT_HFP_AG_CALL_IND].max, + ag_ind[BT_HFP_AG_CALL_SETUP_IND].name, ag_ind[BT_HFP_AG_CALL_SETUP_IND].min, + ag_ind[BT_HFP_AG_CALL_SETUP_IND].connector, + ag_ind[BT_HFP_AG_CALL_SETUP_IND].max, ag_ind[BT_HFP_AG_CALL_HELD_IND].name, + ag_ind[BT_HFP_AG_CALL_HELD_IND].min, + ag_ind[BT_HFP_AG_CALL_HELD_IND].connector, + ag_ind[BT_HFP_AG_CALL_HELD_IND].max, ag_ind[BT_HFP_AG_SIGNAL_IND].name, + ag_ind[BT_HFP_AG_SIGNAL_IND].min, ag_ind[BT_HFP_AG_SIGNAL_IND].connector, + ag_ind[BT_HFP_AG_SIGNAL_IND].max, ag_ind[BT_HFP_AG_ROAM_IND].name, + ag_ind[BT_HFP_AG_ROAM_IND].min, ag_ind[BT_HFP_AG_ROAM_IND].connector, + ag_ind[BT_HFP_AG_ROAM_IND].max, ag_ind[BT_HFP_AG_BATTERY_IND].name, + ag_ind[BT_HFP_AG_BATTERY_IND].min, ag_ind[BT_HFP_AG_BATTERY_IND].connector, + ag_ind[BT_HFP_AG_BATTERY_IND].max); + } else { + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+CIND:%d,%d,%d,%d,%d,%d,%d\r\n", + ag->indicator_value[BT_HFP_AG_SERVICE_IND], + ag->indicator_value[BT_HFP_AG_CALL_IND], + ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND], + ag->indicator_value[BT_HFP_AG_CALL_HELD_IND], + ag->indicator_value[BT_HFP_AG_SIGNAL_IND], + ag->indicator_value[BT_HFP_AG_ROAM_IND], + ag->indicator_value[BT_HFP_AG_BATTERY_IND]); + } + + return err; +} + +static void bt_hfp_ag_set_in_band_ring(struct bt_hfp_ag *ag, void *user_data) +{ + bool is_inband_ringtone; + + hfp_ag_lock(ag); + is_inband_ringtone = (ag->ag_features & BT_HFP_AG_FEATURE_INBAND_RINGTONE) ? true : false; + hfp_ag_unlock(ag); + + if (is_inband_ringtone && !atomic_test_bit(ag->flags, BT_HFP_AG_INBAND_RING)) { + int err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BSIR:1\r\n"); + + atomic_set_bit_to(ag->flags, BT_HFP_AG_INBAND_RING, err == 0); + } +} + +static void bt_hfp_ag_slc_connected(struct bt_hfp_ag *ag, void *user_data) +{ + bt_hfp_ag_set_state(ag, BT_HFP_CONNECTED); + (void)hfp_ag_next_step(ag, bt_hfp_ag_set_in_band_ring, NULL); +} + +static int bt_hfp_ag_cmer_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + uint32_t number; + int err; + static const uint32_t command_line_prefix[] = {3, 0, 0}; + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + for (int i = 0; i < ARRAY_SIZE(command_line_prefix); i++) { + err = get_number(buf, &number); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, ',')) { + return -ENOTSUP; + } + + if (command_line_prefix[i] != number) { + return -ENOTSUP; + } + } + + err = get_number(buf, &number); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + if (number == 1) { + atomic_set_bit(ag->flags, BT_HFP_AG_CMER_ENABLE); + if ((ag->hf_features & BT_HFP_HF_FEATURE_3WAY_CALL) && + (ag->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) { + LOG_DBG("Waiting for AT+CHLD=?"); + return 0; + } + + err = hfp_ag_next_step(ag, bt_hfp_ag_slc_connected, NULL); + return err; + } else if (number == 0) { + atomic_clear_bit(ag->flags, BT_HFP_AG_CMER_ENABLE); + } else { + return -ENOTSUP; + } + + return 0; +} + +static void bt_hfp_ag_accept_cb(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); + + if (bt_ag && bt_ag->accept) { + bt_ag->accept(call); + } +} + +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) +static int chld_release_all(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + bt_hfp_call_state_t call_state; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + if ((call_state != BT_HFP_CALL_ACTIVE) || + ((call_state == BT_HFP_CALL_ACTIVE) && + atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD))) { + ag_terminate_call(call); + } + } + + ag_notify_call_held_ind(ag, NULL); + + return 0; +} + +static void bt_hfp_ag_accept_other_cb(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + bt_hfp_call_state_t call_state; + int err; + + if (!call) { + return; + } + + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); + + if (call_state == BT_HFP_CALL_HOLD) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_HELD_IND, + BT_HFP_CALL_HELD_ACTIVE_HELD, NULL, NULL); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + } + + if (call_state == BT_HFP_CALL_HOLD) { + if (bt_ag && bt_ag->retrieve) { + bt_ag->retrieve(call); + } + } else { + if (bt_ag && bt_ag->accept) { + bt_ag->accept(call); + } + } + + ag_notify_call_held_ind(ag, NULL); +} + +static void bt_hfp_ag_deactivate_accept_other_cb(struct bt_hfp_ag *ag, void *user_data) +{ + int err; + uint8_t call_setup; + + hfp_ag_lock(ag); + call_setup = ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND]; + hfp_ag_unlock(ag); + + if (call_setup) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, + bt_hfp_ag_accept_other_cb, user_data); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + } else { + bt_hfp_ag_accept_other_cb(ag, user_data); + } +} + +static struct bt_hfp_ag_call *get_none_accept_calls(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + bt_hfp_call_state_t call_state; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + if ((call_state == BT_HFP_CALL_OUTGOING) || (call_state == BT_HFP_CALL_INCOMING) || + (call_state == BT_HFP_CALL_ALERTING)) { + return call; + } + } + + return NULL; +} + +static struct bt_hfp_ag_call *get_none_active_calls(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + bt_hfp_call_state_t call_state; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + if (call_state != BT_HFP_CALL_ACTIVE) { + return call; + } else if ((call_state == BT_HFP_CALL_ACTIVE) && + atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) { + return call; + } + } + + return NULL; +} + +static int chld_deactivate_calls_and_accept_other_call(struct bt_hfp_ag *ag, bool release) +{ + struct bt_hfp_ag_call *call; + bt_hfp_call_state_t call_state; + struct bt_hfp_ag_call *none_active_call; + + none_active_call = get_none_accept_calls(ag); + + if (!none_active_call) { + none_active_call = get_none_active_calls(ag); + } else { + if (!(atomic_test_bit(none_active_call->flags, BT_HFP_AG_CALL_INCOMING_3WAY) || + atomic_test_bit(none_active_call->flags, BT_HFP_AG_CALL_INCOMING))) { + return -ENOTSUP; + } + } + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + if ((call_state == BT_HFP_CALL_ACTIVE) && + !atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) { + if (release) { + ag_terminate_call(call); + } else { + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_HOLD); + + if (bt_ag && bt_ag->held) { + bt_ag->held(call); + } + } + } + } + + return hfp_ag_next_step(ag, bt_hfp_ag_deactivate_accept_other_cb, none_active_call); +} + +static int chld_activate_held_call(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + bt_hfp_call_state_t call_state; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + if (call_state == BT_HFP_CALL_HOLD) { + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); + + if (bt_ag && bt_ag->retrieve) { + bt_ag->retrieve(call); + } + } + } + + ag_notify_call_held_ind(ag, NULL); + + return 0; +} + +static int chld_drop_conversation(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + bt_hfp_call_state_t call_state; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + if (call_state != BT_HFP_CALL_ACTIVE) { + return -ENOTSUP; + } + } + + if (!bt_ag || !bt_ag->explicit_call_transfer) { + return -EOPNOTSUPP; + } + + bt_ag->explicit_call_transfer(ag); + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_TERMINATE); + } + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_AUDIO_CONN)) { + LOG_DBG("Close SCO connection"); + hfp_ag_close_sco(ag); + } + + ag_notify_call_held_ind(ag, NULL); + + return 0; +} + +static int chld_release_call(struct bt_hfp_ag *ag, uint8_t call_index) +{ + struct bt_hfp_ag_call *call; + bt_hfp_call_state_t call_state; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + if (index != call_index) { + continue; + } + + hfp_ag_lock(ag); + call_state = call->call_state; hfp_ag_unlock(ag); - return -EOPNOTSUPP; - } - hfp_ag_unlock(ag); - while (buf->len > 0) { - err = get_number(buf, &codec); - if (err != 0) { - return -ENOTSUP; + if ((call_state == BT_HFP_CALL_HOLD) || + ((call_state == BT_HFP_CALL_ACTIVE) && + !atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD))) { + ag_terminate_call(call); + } else { + ag_reject_call(call); } + } - if (!is_char(buf, ',')) { - if (!is_char(buf, '\r')) { - return -ENOTSUP; - } + ag_notify_call_held_ind(ag, NULL); + + return 0; +} + +static int chld_held_other_calls(struct bt_hfp_ag *ag, uint8_t call_index) +{ + struct bt_hfp_ag_call *call; + bt_hfp_call_state_t call_state; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; } - if (codec < (sizeof(codec_ids) * 8)) { - codec_ids |= BIT(codec); + if (index == call_index) { + continue; } - } - if (!(codec_ids & BIT(BT_HFP_AG_CODEC_CVSD))) { - return -EOPNOTSUPP; + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + if ((call_state == BT_HFP_CALL_ACTIVE) && + !atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) { + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_HOLD); + + if (bt_ag && bt_ag->held) { + bt_ag->held(call); + } + } } + call = &ag->calls[call_index]; hfp_ag_lock(ag); - ag->hf_codec_ids = codec_ids; - ag->selected_codec_id = 0; + call_state = call->call_state; hfp_ag_unlock(ag); - atomic_set_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED); - - if (atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_CODEC_CONN)) { - /* Codec connection is ended. It needs to be restarted. */ - LOG_DBG("Codec connection is ended. It needs to be restarted."); - if (bt_ag && bt_ag->codec_negotiate) { - bt_ag->codec_negotiate(ag, -EAGAIN); + if (call_state == BT_HFP_CALL_HOLD) { + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); + if (bt_ag && bt_ag->retrieve) { + bt_ag->retrieve(call); } } - if (bt_ag && bt_ag->codec) { - bt_ag->codec(ag, ag->hf_codec_ids); - } + ag_notify_call_held_ind(ag, NULL); return 0; } -static int bt_hfp_ag_cind_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +static int chld_other(struct bt_hfp_ag *ag, uint32_t value) { - int err; - bool inquiry_status = true; + uint8_t index; + uint8_t command; - if (is_char(buf, '=')) { - inquiry_status = false; - } + index = value % 10; + command = value / 10; - if (!is_char(buf, '?')) { - return -ENOTSUP; + if (!index) { + return -EOPNOTSUPP; } - if (!is_char(buf, '\r')) { - return -ENOTSUP; + index = index - 1; + + if (index >= ARRAY_SIZE(ag->calls)) { + return -EOPNOTSUPP; } - if (!inquiry_status) { - err = hfp_ag_send_data( - ag, NULL, NULL, - "\r\n+CIND:(\"%s\",(%d%s%d)),(\"%s\",(%d%s%d)),(\"%s\",(%d%s%d)),(\"%s\",(%" - "d%s%d)),(\"%s\",(%d%s%d)),(\"%s\",(%d%s%d)),(\"%s\",(%d%s%d))\r\n", - ag_ind[BT_HFP_AG_SERVICE_IND].name, ag_ind[BT_HFP_AG_SERVICE_IND].min, - ag_ind[BT_HFP_AG_SERVICE_IND].connector, ag_ind[BT_HFP_AG_SERVICE_IND].max, - ag_ind[BT_HFP_AG_CALL_IND].name, ag_ind[BT_HFP_AG_CALL_IND].min, - ag_ind[BT_HFP_AG_CALL_IND].connector, ag_ind[BT_HFP_AG_CALL_IND].max, - ag_ind[BT_HFP_AG_CALL_SETUP_IND].name, ag_ind[BT_HFP_AG_CALL_SETUP_IND].min, - ag_ind[BT_HFP_AG_CALL_SETUP_IND].connector, - ag_ind[BT_HFP_AG_CALL_SETUP_IND].max, ag_ind[BT_HFP_AG_CALL_HELD_IND].name, - ag_ind[BT_HFP_AG_CALL_HELD_IND].min, - ag_ind[BT_HFP_AG_CALL_HELD_IND].connector, - ag_ind[BT_HFP_AG_CALL_HELD_IND].max, ag_ind[BT_HFP_AG_SIGNAL_IND].name, - ag_ind[BT_HFP_AG_SIGNAL_IND].min, ag_ind[BT_HFP_AG_SIGNAL_IND].connector, - ag_ind[BT_HFP_AG_SIGNAL_IND].max, ag_ind[BT_HFP_AG_ROAM_IND].name, - ag_ind[BT_HFP_AG_ROAM_IND].min, ag_ind[BT_HFP_AG_ROAM_IND].connector, - ag_ind[BT_HFP_AG_ROAM_IND].max, ag_ind[BT_HFP_AG_BATTERY_IND].name, - ag_ind[BT_HFP_AG_BATTERY_IND].min, ag_ind[BT_HFP_AG_BATTERY_IND].connector, - ag_ind[BT_HFP_AG_BATTERY_IND].max); - } else { - err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+CIND:%d,%d,%d,%d,%d,%d,%d\r\n", - ag->indicator_value[BT_HFP_AG_SERVICE_IND], - ag->indicator_value[BT_HFP_AG_CALL_IND], - ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND], - ag->indicator_value[BT_HFP_AG_CALL_HELD_IND], - ag->indicator_value[BT_HFP_AG_SIGNAL_IND], - ag->indicator_value[BT_HFP_AG_ROAM_IND], - ag->indicator_value[BT_HFP_AG_BATTERY_IND]); + switch (command) { + case BT_HFP_CHLD_RELEASE_ACTIVE_ACCEPT_OTHER: + return chld_release_call(ag, index); + case BT_HFP_CALL_HOLD_ACTIVE_ACCEPT_OTHER: + return chld_held_other_calls(ag, index); } - return err; + return -EOPNOTSUPP; } +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ -static void bt_hfp_ag_set_in_band_ring(struct bt_hfp_ag *ag, void *user_data) +static int bt_hfp_ag_chld_handler(struct bt_hfp_ag *ag, struct net_buf *buf) { - bool is_inband_ringtone; +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + uint32_t value; + int err; + char *response; hfp_ag_lock(ag); - is_inband_ringtone = (ag->ag_features & BT_HFP_AG_FEATURE_INBAND_RINGTONE) ? true : false; - hfp_ag_unlock(ag); - - if (is_inband_ringtone) { - int err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BSIR:1\r\n"); - - atomic_set_bit_to(ag->flags, BT_HFP_AG_INBAND_RING, err == 0); + if (!((ag->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL) && + (ag->hf_features & BT_HFP_HF_FEATURE_3WAY_CALL))) { + hfp_ag_unlock(ag); + return -EOPNOTSUPP; } -} - -static int bt_hfp_ag_cmer_handler(struct bt_hfp_ag *ag, struct net_buf *buf) -{ - uint32_t number; - int err; - static const uint32_t command_line_prefix[] = {3, 0, 0}; + hfp_ag_unlock(ag); if (!is_char(buf, '=')) { return -ENOTSUP; } - for (int i = 0; i < ARRAY_SIZE(command_line_prefix); i++) { - err = get_number(buf, &number); - if (err != 0) { - return -ENOTSUP; - } - - if (!is_char(buf, ',')) { + if (is_char(buf, '?')) { + if (!is_char(buf, '\r')) { return -ENOTSUP; } - if (command_line_prefix[i] != number) { - return -ENOTSUP; - } +#if defined(CONFIG_BT_HFP_AG_ECC) + response = "+CHLD:(0,1,1x,2,2x,3,4)"; +#else + response = "+CHLD:(0,1,2,3,4)"; +#endif /* CONFIG_BT_HFP_AG_ECC */ + err = hfp_ag_send_data(ag, bt_hfp_ag_slc_connected, NULL, "\r\n%s\r\n", response); + return err; } - err = get_number(buf, &number); + err = get_number(buf, &value); if (err != 0) { return -ENOTSUP; } - if (!is_char(buf, '\r')) { - return -ENOTSUP; - } - - if (number == 1) { - atomic_set_bit(ag->flags, BT_HFP_AG_CMER_ENABLE); - bt_hfp_ag_set_state(ag, BT_HFP_CONNECTED); - err = hfp_ag_next_step(ag, bt_hfp_ag_set_in_band_ring, NULL); - return err; - } else if (number == 0) { - atomic_clear_bit(ag->flags, BT_HFP_AG_CMER_ENABLE); - } else { - return -ENOTSUP; + switch (value) { + case BT_HFP_CHLD_RELEASE_ALL: + return chld_release_all(ag); + case BT_HFP_CHLD_RELEASE_ACTIVE_ACCEPT_OTHER: + return chld_deactivate_calls_and_accept_other_call(ag, true); + case BT_HFP_CALL_HOLD_ACTIVE_ACCEPT_OTHER: + return chld_deactivate_calls_and_accept_other_call(ag, false); + case BT_HFP_CALL_ACTIVE_HELD: + return chld_activate_held_call(ag); + case BT_HFP_CALL_QUITE: + return chld_drop_conversation(ag); } - return 0; -} - -static int bt_hfp_ag_chld_handler(struct bt_hfp_ag *ag, struct net_buf *buf) -{ + return chld_other(ag, value); +#else return -EOPNOTSUPP; +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ } static int bt_hfp_ag_bind_handler(struct bt_hfp_ag *ag, struct net_buf *buf) @@ -774,65 +1612,18 @@ static int bt_hfp_ag_cmee_handler(struct bt_hfp_ag *ag, struct net_buf *buf) return -ENOTSUP; } -static int hfp_ag_update_indicator(struct bt_hfp_ag *ag, enum bt_hfp_ag_indicator index, - uint8_t value, bt_hfp_ag_tx_cb_t cb, void *user_data) -{ - int err; - uint8_t old_value; - - hfp_ag_lock(ag); - old_value = ag->indicator_value[index]; - if (value == old_value) { - LOG_ERR("Duplicate value setting, indicator %d, old %d -> new %d", index, old_value, - value); - hfp_ag_unlock(ag); - return -EINVAL; - } - - ag->indicator_value[index] = value; - hfp_ag_unlock(ag); - - LOG_DBG("indicator %d, old %d -> new %d", index, old_value, value); - - err = hfp_ag_send_data(ag, cb, user_data, "\r\n+CIEV:%d,%d\r\n", (uint8_t)index + 1, value); - if (err) { - hfp_ag_lock(ag); - ag->indicator_value[index] = old_value; - hfp_ag_unlock(ag); - LOG_ERR("Fail to update indicator %d, current %d", index, old_value); - } - - return err; -} - -static void hfp_ag_close_sco(struct bt_hfp_ag *ag) +static void bt_hfp_ag_reject_cb(struct bt_hfp_ag *ag, void *user_data) { - struct bt_conn *sco; - - LOG_DBG(""); + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; - hfp_ag_lock(ag); - sco = ag->sco_chan.sco; - ag->sco_chan.sco = NULL; - hfp_ag_unlock(ag); - if (sco != NULL) { - LOG_DBG("Disconnect sco %p", sco); - bt_conn_disconnect(sco, BT_HCI_ERR_LOCALHOST_TERM_CONN); + if (call) { + ag_reject_call(call); } -} -static void bt_hfp_ag_reject_cb(struct bt_hfp_ag *ag, void *user_data) -{ if (!atomic_test_bit(ag->flags, BT_HFP_AG_AUDIO_CONN)) { LOG_DBG("It is not audio connection"); hfp_ag_close_sco(ag); } - - bt_hfp_ag_set_call_state(ag, BT_HFP_CALL_TERMINATE); - - if (bt_ag && bt_ag->reject) { - bt_ag->reject(ag); - } } static void bt_hfp_ag_call_reject(struct bt_hfp_ag *ag, void *user_data) @@ -846,16 +1637,16 @@ static void bt_hfp_ag_call_reject(struct bt_hfp_ag *ag, void *user_data) static void bt_hfp_ag_terminate_cb(struct bt_hfp_ag *ag, void *user_data) { + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + if (call) { + ag_terminate_call(call); + } + if (!atomic_test_bit(ag->flags, BT_HFP_AG_AUDIO_CONN)) { LOG_DBG("It is not audio connection"); hfp_ag_close_sco(ag); } - - bt_hfp_ag_set_call_state(ag, BT_HFP_CALL_TERMINATE); - - if (bt_ag && bt_ag->terminate) { - bt_ag->terminate(ag); - } } static void bt_hfp_ag_unit_call_terminate(struct bt_hfp_ag *ag, void *user_data) @@ -867,39 +1658,138 @@ static void bt_hfp_ag_unit_call_terminate(struct bt_hfp_ag *ag, void *user_data) } } +static void bt_hfp_ag_active_terminate_cb(struct bt_hfp_ag *ag, void *user_data) +{ + bt_hfp_ag_unit_call_terminate(ag, user_data); +} + +static void bt_hfp_ag_call_terminate(struct bt_hfp_ag *ag, void *user_data) +{ + int err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, + bt_hfp_ag_active_terminate_cb, user_data); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } +} + +struct bt_hfp_ag_call *get_call_with_flag(struct bt_hfp_ag *ag, int bit, bool clear) +{ + struct bt_hfp_ag_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + if (clear) { + if (atomic_test_and_set_bit(call->flags, bit)) { + return call; + } + } else { + if (atomic_test_bit(call->flags, bit)) { + return call; + } + } + } + + return NULL; +} + +struct bt_hfp_ag_call *get_call_with_flag_and_state(struct bt_hfp_ag *ag, int bit, + bt_hfp_call_state_t state) +{ + struct bt_hfp_ag_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + if ((call->call_state & state) && atomic_test_bit(call->flags, bit)) { + return call; + } + } + + return NULL; +} + static int bt_hfp_ag_chup_handler(struct bt_hfp_ag *ag, struct net_buf *buf) { int err; bt_hfp_call_state_t call_state; + struct bt_hfp_ag_call *call; + int call_count; if (!is_char(buf, '\r')) { return -ENOTSUP; } - hfp_ag_lock(ag); - call_state = ag->call_state; - hfp_ag_unlock(ag); + call_count = get_none_released_calls(ag); + + if (call_count == 1) { + bt_hfp_ag_tx_cb_t next_step = NULL; + + call = get_call_with_flag(ag, BT_HFP_AG_CALL_IN_USING, false); + if (!call) { + return 0; + } + + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + if (call_state == BT_HFP_CALL_ALERTING) { + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { + next_step = bt_hfp_ag_call_terminate; + } else { + next_step = bt_hfp_ag_call_reject; + } + } else if (call_state == BT_HFP_CALL_ACTIVE) { + next_step = bt_hfp_ag_unit_call_terminate; + } + if (next_step) { + err = hfp_ag_next_step(ag, next_step, call); + return err; + } + + return -ENOTSUP; + } + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); - if (call_state == BT_HFP_CALL_ALERTING) { - if (!atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL)) { - return -ENOTSUP; + if (call_state == BT_HFP_CALL_ACTIVE) { + err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_terminate, call); } - err = hfp_ag_next_step(ag, bt_hfp_ag_call_reject, NULL); - } else if ((call_state == BT_HFP_CALL_ACTIVE) || (call_state == BT_HFP_CALL_HOLD)) { - err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_terminate, NULL); - } else { - return -ENOTSUP; } - return err; + return 0; } -static uint8_t bt_hfp_get_call_state(struct bt_hfp_ag *ag) +static uint8_t bt_hfp_get_call_state(struct bt_hfp_ag_call *call) { uint8_t status = HFP_AG_CLCC_STATUS_INVALID; + struct bt_hfp_ag *ag = call->ag; hfp_ag_lock(ag); - switch (ag->call_state) { + switch (call->call_state) { case BT_HFP_CALL_TERMINATE: + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { + status = BT_HFP_CLCC_STATUS_INCOMING; + } else { + status = BT_HFP_CLCC_STATUS_DIALING; + } break; case BT_HFP_CALL_OUTGOING: status = HFP_AG_CLCC_STATUS_DIALING; @@ -908,14 +1798,18 @@ static uint8_t bt_hfp_get_call_state(struct bt_hfp_ag *ag) status = HFP_AG_CLCC_STATUS_INCOMING; break; case BT_HFP_CALL_ALERTING: - if (atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL)) { + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { status = HFP_AG_CLCC_STATUS_WAITING; } else { status = HFP_AG_CLCC_STATUS_ALERTING; } break; case BT_HFP_CALL_ACTIVE: - status = HFP_AG_CLCC_STATUS_ACTIVE; + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) { + status = BT_HFP_AG_CALL_INCOMING_HELD; + } else { + status = HFP_AG_CLCC_STATUS_ACTIVE; + } break; case BT_HFP_CALL_HOLD: status = HFP_AG_CLCC_STATUS_HELD; @@ -931,31 +1825,36 @@ static uint8_t bt_hfp_get_call_state(struct bt_hfp_ag *ag) static int bt_hfp_ag_clcc_handler(struct bt_hfp_ag *ag, struct net_buf *buf) { int err; + struct bt_hfp_ag_call *call; uint8_t dir; uint8_t status; uint8_t mode; uint8_t mpty; + int active_calls; if (!is_char(buf, '\r')) { return -ENOTSUP; } - hfp_ag_lock(ag); - if (ag->call_state == BT_HFP_CALL_TERMINATE) { - /* AG shall always send OK response to HF */ - hfp_ag_unlock(ag); - return 0; - } - hfp_ag_unlock(ag); + active_calls = get_active_calls(ag); - dir = atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL) ? 1 : 0; - status = bt_hfp_get_call_state(ag); - mode = 0; - mpty = 0; - err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+CLCC:%d,%d,%d,%d,%d\r\n", 1, dir, status, mode, - mpty); - if (err != 0) { - LOG_ERR("Fail to send err :(%d)", err); + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + dir = atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING) ? 1 : 0; + status = bt_hfp_get_call_state(call); + mode = 0; + mpty = (status == HFP_AG_CLCC_STATUS_ACTIVE) && (active_calls > 1) ? 1 : 0; + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+CLCC:%d,%d,%d,%d,%d,\"%s\",%d\r\n", + index + 1, dir, status, mode, mpty, call->number, + call->type); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } } /* AG shall always send OK response to HF */ @@ -1011,19 +1910,10 @@ static int bt_hfp_ag_bia_handler(struct bt_hfp_ag *ag, struct net_buf *buf) return 0; } -static void bt_hfp_ag_accept_cb(struct bt_hfp_ag *ag, void *user_data) -{ - bt_hfp_ag_set_call_state(ag, BT_HFP_CALL_ACTIVE); - - if (bt_ag && bt_ag->accept) { - bt_ag->accept(ag); - } -} - -static void bt_hfp_ag_call_ringing_cb(struct bt_hfp_ag *ag, bool in_bond) +static void bt_hfp_ag_call_ringing_cb(struct bt_hfp_ag_call *call, bool in_bond) { if (bt_ag && bt_ag->ringing) { - bt_ag->ringing(ag, in_bond); + bt_ag->ringing(call, in_bond); } } @@ -1031,13 +1921,18 @@ static void hfp_ag_sco_connected(struct bt_sco_chan *chan) { struct bt_hfp_ag *ag = CONTAINER_OF(chan, struct bt_hfp_ag, sco_chan); bt_hfp_call_state_t call_state; + struct bt_hfp_ag_call *call; - hfp_ag_lock(ag); - call_state = ag->call_state; - hfp_ag_unlock(ag); - if (call_state == BT_HFP_CALL_INCOMING) { - bt_hfp_ag_set_call_state(ag, BT_HFP_CALL_ALERTING); - bt_hfp_ag_call_ringing_cb(ag, true); + call = get_call_with_flag(ag, BT_HFP_AG_CALL_OPEN_SCO, true); + + if (call) { + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + if (call_state == BT_HFP_CALL_INCOMING) { + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING); + bt_hfp_ag_call_ringing_cb(call, true); + } } if ((bt_ag) && bt_ag->sco_connected) { @@ -1049,16 +1944,23 @@ static void hfp_ag_sco_disconnected(struct bt_sco_chan *chan, uint8_t reason) { struct bt_hfp_ag *ag = CONTAINER_OF(chan, struct bt_hfp_ag, sco_chan); bt_hfp_call_state_t call_state; + struct bt_hfp_ag_call *call; + + call = get_call_with_flag(ag, BT_HFP_AG_CALL_OPEN_SCO, true); if ((bt_ag) && bt_ag->sco_disconnected) { bt_ag->sco_disconnected(ag); } + if (!call) { + return; + } + hfp_ag_lock(ag); - call_state = ag->call_state; + call_state = call->call_state; hfp_ag_unlock(ag); if ((call_state == BT_HFP_CALL_INCOMING) || (call_state == BT_HFP_CALL_OUTGOING)) { - bt_hfp_ag_call_reject(ag, NULL); + bt_hfp_ag_call_reject(ag, call); } } @@ -1089,7 +1991,7 @@ static struct bt_conn *bt_hfp_ag_create_sco(struct bt_hfp_ag *ag) return sco_conn; } -static int hfp_ag_open_sco(struct bt_hfp_ag *ag) +static int hfp_ag_open_sco(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call) { bool create_sco; bt_hfp_call_state_t call_state; @@ -1100,7 +2002,6 @@ static int hfp_ag_open_sco(struct bt_hfp_ag *ag) } hfp_ag_lock(ag); - call_state = ag->call_state; create_sco = (ag->sco_chan.sco == NULL) ? true : false; if (create_sco) { atomic_set_bit(ag->flags, BT_HFP_AG_CREATING_SCO); @@ -1118,13 +2019,22 @@ static int hfp_ag_open_sco(struct bt_hfp_ag *ag) return -ENOTCONN; } - atomic_set_bit_to(ag->flags, BT_HFP_AG_AUDIO_CONN, call_state == BT_HFP_CALL_TERMINATE); + atomic_set_bit_to(ag->flags, BT_HFP_AG_AUDIO_CONN, call == NULL); + if (call) { + atomic_set_bit(call->flags, BT_HFP_AG_CALL_OPEN_SCO); + } LOG_DBG("SCO connection created (%p)", sco_conn); } else { - if (call_state == BT_HFP_CALL_INCOMING) { - bt_hfp_ag_set_call_state(ag, BT_HFP_CALL_ALERTING); - bt_hfp_ag_call_ringing_cb(ag, true); + if (call) { + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + if (call_state == BT_HFP_CALL_INCOMING) { + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING); + bt_hfp_ag_call_ringing_cb(call, true); + } } } @@ -1157,7 +2067,7 @@ static int bt_hfp_ag_codec_select(struct bt_hfp_ag *ag) return err; } -static int bt_hfp_ag_create_audio_connection(struct bt_hfp_ag *ag) +static int bt_hfp_ag_create_audio_connection(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call) { int err; uint32_t hf_codec_ids; @@ -1169,8 +2079,11 @@ static int bt_hfp_ag_create_audio_connection(struct bt_hfp_ag *ag) if ((hf_codec_ids != 0) && atomic_test_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED)) { err = bt_hfp_ag_codec_select(ag); atomic_set_bit_to(ag->flags, BT_HFP_AG_CODEC_CONN, err == 0); + if (call) { + atomic_set_bit_to(call->flags, BT_HFP_AG_CALL_OPEN_SCO, err == 0); + } } else { - err = hfp_ag_open_sco(ag); + err = hfp_ag_open_sco(ag, call); } return err; @@ -1179,25 +2092,44 @@ static int bt_hfp_ag_create_audio_connection(struct bt_hfp_ag *ag) static void bt_hfp_ag_audio_connection(struct bt_hfp_ag *ag, void *user_data) { int err; + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; - err = bt_hfp_ag_create_audio_connection(ag); + err = bt_hfp_ag_create_audio_connection(ag, call); if (err) { bt_hfp_ag_unit_call_terminate(ag, user_data); } } +static void bt_hfp_ag_call_setup_none_cb(struct bt_hfp_ag *ag, void *user_data) +{ + int err; + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_OUTGOING_3WAY)) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_HELD_IND, + BT_HFP_CALL_HELD_ACTIVE_HELD, bt_hfp_ag_accept_cb, + call); + if (err) { + bt_hfp_ag_unit_call_terminate(ag, user_data); + } + return; + } + + bt_hfp_ag_audio_connection(ag, user_data); +} + static void bt_hfp_ag_unit_call_accept(struct bt_hfp_ag *ag, void *user_data) { int err; err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, bt_hfp_ag_accept_cb, user_data); - if (err != 0) { + if (err) { LOG_ERR("Fail to send err :(%d)", err); } err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, - bt_hfp_ag_audio_connection, user_data); - if (err != 0) { + bt_hfp_ag_call_setup_none_cb, user_data); + if (err) { LOG_ERR("Fail to send err :(%d)", err); } } @@ -1205,23 +2137,34 @@ static void bt_hfp_ag_unit_call_accept(struct bt_hfp_ag *ag, void *user_data) static int bt_hfp_ag_ata_handler(struct bt_hfp_ag *ag, struct net_buf *buf) { int err; + int call_count; + struct bt_hfp_ag_call *call; if (!is_char(buf, '\r')) { return -ENOTSUP; } hfp_ag_lock(ag); - if (ag->call_state != BT_HFP_CALL_ALERTING) { + call_count = get_none_released_calls(ag); + if (call_count != 1) { + hfp_ag_unlock(ag); + return -ENOTSUP; + } + + call = get_call_with_flag(ag, BT_HFP_AG_CALL_IN_USING, false); + __ASSERT(call, "Invalid call object"); + + if (call->call_state != BT_HFP_CALL_ALERTING) { hfp_ag_unlock(ag); return -ENOTSUP; } hfp_ag_unlock(ag); - if (!atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL)) { + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { return -ENOTSUP; } - err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_accept, NULL); + err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_accept, call); return err; } @@ -1267,9 +2210,9 @@ static int bt_hfp_ag_cops_handler(struct bt_hfp_ag *ag, struct net_buf *buf) return -ENOTSUP; } - err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+COPS:%d,%d,\"%s\"\r\n", - ag->mode, 0, ag->operator); - if (err != 0) { + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+COPS:%d,%d,\"%s\"\r\n", ag->mode, 0, + ag->operator); + if (err) { LOG_ERR("Fail to send err :(%d)", err); } @@ -1312,7 +2255,7 @@ static int bt_hfp_ag_bcc_handler(struct bt_hfp_ag *ag, struct net_buf *buf) static void bt_hfp_ag_unit_codec_conn_setup(struct bt_hfp_ag *ag, void *user_data) { - int err = hfp_ag_open_sco(ag); + int err = hfp_ag_open_sco(ag, user_data); if (err) { bt_hfp_ag_call_reject(ag, user_data); @@ -1324,6 +2267,7 @@ static int bt_hfp_ag_bcs_handler(struct bt_hfp_ag *ag, struct net_buf *buf) int err; uint32_t number; bool codec_conn; + struct bt_hfp_ag_call *call; if (!is_char(buf, '=')) { return -ENOTSUP; @@ -1344,12 +2288,12 @@ static int bt_hfp_ag_bcs_handler(struct bt_hfp_ag *ag, struct net_buf *buf) hfp_ag_lock(ag); if (ag->selected_codec_id != number) { - LOG_ERR("Received codec id %d is not aligned with selected %d", - number, ag->selected_codec_id); + LOG_ERR("Received codec id %d is not aligned with selected %d", number, + ag->selected_codec_id); err = -ENOTSUP; } else if (!(ag->hf_codec_ids & BIT(ag->selected_codec_id))) { - LOG_ERR("Selected codec id %d is unsupported %d", - ag->selected_codec_id, ag->hf_codec_ids); + LOG_ERR("Selected codec id %d is unsupported %d", ag->selected_codec_id, + ag->hf_codec_ids); err = -ENOTSUP; } hfp_ag_unlock(ag); @@ -1357,11 +2301,13 @@ static int bt_hfp_ag_bcs_handler(struct bt_hfp_ag *ag, struct net_buf *buf) codec_conn = atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_CODEC_CONN); atomic_clear_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED); + call = get_call_with_flag(ag, BT_HFP_AG_CALL_OPEN_SCO, false); + if (err == 0) { if (codec_conn && bt_ag && bt_ag->codec_negotiate) { bt_ag->codec_negotiate(ag, err); } - err = hfp_ag_next_step(ag, bt_hfp_ag_unit_codec_conn_setup, NULL); + err = hfp_ag_next_step(ag, bt_hfp_ag_unit_codec_conn_setup, call); } else { bt_hfp_call_state_t call_state; @@ -1369,32 +2315,182 @@ static int bt_hfp_ag_bcs_handler(struct bt_hfp_ag *ag, struct net_buf *buf) bt_ag->codec_negotiate(ag, err); } - hfp_ag_lock(ag); - call_state = ag->call_state; - hfp_ag_unlock(ag); - if (call_state != BT_HFP_CALL_TERMINATE) { - (void)hfp_ag_next_step(ag, bt_hfp_ag_unit_call_terminate, NULL); + if (call) { + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + if (call_state != BT_HFP_CALL_TERMINATE) { + (void)hfp_ag_next_step(ag, bt_hfp_ag_unit_call_terminate, call); + } } } return err; } +static void bt_hfp_ag_outgoing_cb(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_OUTGOING); + + if (bt_ag && bt_ag->outgoing) { + bt_ag->outgoing(ag, call, call->number); + } + + if (atomic_test_bit(ag->flags, BT_HFP_AG_INBAND_RING) || + atomic_test_bit(call->flags, BT_HFP_AG_CALL_OUTGOING_3WAY)) { + int err; + + err = bt_hfp_ag_create_audio_connection(ag, call); + if (err) { + bt_hfp_ag_call_reject(ag, user_data); + } + } +} + static void bt_hfp_ag_unit_call_outgoing(struct bt_hfp_ag *ag, void *user_data) { - int err = bt_hfp_ag_outgoing(ag, ag->number); + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + int err; - if (err != 0) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_OUTGOING, + bt_hfp_ag_outgoing_cb, call); + + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } +} + +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) +static void bt_hfp_ag_call_held_cb(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + bt_hfp_call_state_t call_state; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + if ((call_state == BT_HFP_CALL_ACTIVE) && + !atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) { + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_HOLD); + if (bt_ag && bt_ag->held) { + bt_ag->held(call); + } + } + } + + bt_hfp_ag_unit_call_outgoing(ag, user_data); +} + +static void bt_hfp_ag_unit_call_outgoing_3way(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + int err; + uint8_t old_value; + + atomic_set_bit(call->flags, BT_HFP_AG_CALL_OUTGOING_3WAY); + + hfp_ag_lock(ag); + old_value = ag->indicator_value[BT_HFP_AG_CALL_HELD_IND]; + hfp_ag_unlock(ag); + if (old_value == BT_HFP_CALL_HELD_HELD) { + LOG_WRN("No active call"); + bt_hfp_ag_call_held_cb(ag, user_data); + return; + } + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_HELD_IND, BT_HFP_CALL_HELD_HELD, + bt_hfp_ag_call_held_cb, call); + if (err) { LOG_ERR("Fail to send err :(%d)", err); } } +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + +static int bt_hfp_ag_outgoing_call(struct bt_hfp_ag *ag, const char *number, uint8_t type) +{ + int err; + size_t len; + struct bt_hfp_ag_call *call; + int call_count; + + len = strlen(number); + if (len == 0) { + return -ENOTSUP; + } + + if (len > CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN) { + return -ENAMETOOLONG; + } + + hfp_ag_lock(ag); + (void)strcpy(ag->last_number, number); + ag->type = type; + hfp_ag_unlock(ag); + + call = get_call_from_number(ag, number, type); + if (call) { + return -EBUSY; + } + + call_count = get_none_released_calls(ag); + + if (call_count) { +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + if (!((ag->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL) && + (ag->hf_features & BT_HFP_HF_FEATURE_3WAY_CALL))) { + return -EOPNOTSUPP; + } + + if (!get_active_held_calls(ag)) { + LOG_WRN("The first call is not accepted"); + return -EBUSY; + } +#else + return -EBUSY; +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + } + + call = get_new_call(ag, number, type); + if (!call) { + LOG_WRN("Cannot allocate a new call object"); + return -ENOMEM; + } + + atomic_clear_bit(call->flags, BT_HFP_AG_CALL_INCOMING); + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_OUTGOING); + +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + if (call_count) { + err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_outgoing_3way, call); + if (err) { + free_call(call); + } + return err; + } +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + + err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_outgoing, call); + if (err) { + free_call(call); + } + return err; +} static int bt_hfp_ag_atd_handler(struct bt_hfp_ag *ag, struct net_buf *buf) { int err; char *number = NULL; bool is_memory_dial = false; - size_t len; if (buf->data[buf->len - 1] != '\r') { return -ENOTSUP; @@ -1431,53 +2527,16 @@ static int bt_hfp_ag_atd_handler(struct bt_hfp_ag *ag, struct net_buf *buf) } } - len = strlen(number); - if (len == 0) { - return -ENOTSUP; - } - - if (len > CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN) { - return -ENAMETOOLONG; - } - - hfp_ag_lock(ag); - if (ag->call_state != BT_HFP_CALL_TERMINATE) { - hfp_ag_unlock(ag); - return -EBUSY; - } - - /* Copy number to ag->number including null-character */ - memcpy(ag->number, number, len + 1); - hfp_ag_unlock(ag); - - err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_outgoing, NULL); - - return err; + return bt_hfp_ag_outgoing_call(ag, number, 0); } static int bt_hfp_ag_bldn_handler(struct bt_hfp_ag *ag, struct net_buf *buf) { - int err; - if (!is_char(buf, '\r')) { return -ENOTSUP; } - hfp_ag_lock(ag); - if (strlen(ag->number) == 0) { - hfp_ag_unlock(ag); - return -ENOSR; - } - - if (ag->call_state != BT_HFP_CALL_TERMINATE) { - hfp_ag_unlock(ag); - return -EBUSY; - } - hfp_ag_unlock(ag); - - err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_outgoing, NULL); - - return err; + return bt_hfp_ag_outgoing_call(ag, ag->last_number, ag->type); } static int bt_hfp_ag_clip_handler(struct bt_hfp_ag *ag, struct net_buf *buf) @@ -1603,13 +2662,17 @@ static int bt_hfp_ag_nrec_handler(struct bt_hfp_ag *ag, struct net_buf *buf) static void btrh_held_call_updated_cb(struct bt_hfp_ag *ag, void *user_data) { - bt_hfp_ag_set_call_state(ag, BT_HFP_CALL_ACTIVE); + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); } static void btrh_held_call_setup_updated_cb(struct bt_hfp_ag *ag, void *user_data) { + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + if (bt_ag && bt_ag->incoming_held) { - bt_ag->incoming_held(ag); + bt_ag->incoming_held(call); } } @@ -1617,14 +2680,15 @@ static void btrh_held_cb(struct bt_hfp_ag *ag, void *user_data) { int err; - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, btrh_held_call_updated_cb, NULL); - if (err != 0) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, btrh_held_call_updated_cb, + user_data); + if (err) { LOG_ERR("Fail to send err :(%d)", err); bt_hfp_ag_unit_call_terminate(ag, user_data); } err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, - btrh_held_call_setup_updated_cb, NULL); + btrh_held_call_setup_updated_cb, user_data); if (err) { LOG_ERR("Fail to send err :(%d)", err); bt_hfp_ag_unit_call_terminate(ag, user_data); @@ -1634,12 +2698,13 @@ static void btrh_held_cb(struct bt_hfp_ag *ag, void *user_data) static void btrh_accept_cb(struct bt_hfp_ag *ag, void *user_data) { int err; + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; if (bt_ag && bt_ag->accept) { - bt_ag->accept(ag); + bt_ag->accept(call); } - err = bt_hfp_ag_create_audio_connection(ag); + err = bt_hfp_ag_create_audio_connection(ag, call); if (err) { bt_hfp_ag_unit_call_terminate(ag, user_data); } @@ -1649,7 +2714,7 @@ static void btrh_reject_cb(struct bt_hfp_ag *ag, void *user_data) { int err; - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, bt_hfp_ag_reject_cb, NULL); + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, bt_hfp_ag_reject_cb, user_data); if (err) { bt_hfp_ag_unit_call_terminate(ag, user_data); } @@ -1660,16 +2725,16 @@ static int bt_hfp_ag_btrh_handler(struct bt_hfp_ag *ag, struct net_buf *buf) int err; uint32_t action; bt_hfp_call_state_t call_state; - - hfp_ag_lock(ag); - call_state = ag->call_state; - hfp_ag_unlock(ag); + struct bt_hfp_ag_call *call; if (is_char(buf, '?')) { - if (atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_HELD) && - (call_state == BT_HFP_CALL_ACTIVE)) { + call = get_call_with_flag(ag, BT_HFP_AG_CALL_INCOMING_HELD, false); + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + if (call && (call_state == BT_HFP_CALL_ACTIVE)) { err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BTRH:%d\r\n", - BT_HFP_BTRH_ON_HOLD); + BT_HFP_BTRH_ON_HOLD); if (err) { LOG_ERR("Fail to send err :(%d)", err); } @@ -1691,38 +2756,41 @@ static int bt_hfp_ag_btrh_handler(struct bt_hfp_ag *ag, struct net_buf *buf) return -ENOTSUP; } - if (atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL)) { - if (action == BT_HFP_BTRH_ON_HOLD) { - if ((call_state == BT_HFP_CALL_ALERTING) || - (call_state == BT_HFP_CALL_INCOMING)) { - atomic_set_bit(ag->flags, BT_HFP_AG_INCOMING_HELD); - err = hfp_ag_send_data(ag, btrh_held_cb, NULL, "\r\n+BTRH:%d\r\n", - BT_HFP_BTRH_ON_HOLD); - if (err) { - LOG_ERR("Fail to send err :(%d)", err); - } - return err; + if (action == BT_HFP_BTRH_ON_HOLD) { + call = get_call_with_flag_and_state(ag, BT_HFP_AG_CALL_INCOMING, + BT_HFP_CALL_ALERTING | BT_HFP_CALL_INCOMING); + if (call) { + atomic_set_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD); + err = hfp_ag_send_data(ag, btrh_held_cb, call, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_ON_HOLD); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); } - } else if (action == BT_HFP_BTRH_ACCEPTED) { - if (atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_INCOMING_HELD) && - (call_state == BT_HFP_CALL_ACTIVE)) { - err = hfp_ag_send_data(ag, btrh_accept_cb, NULL, "\r\n+BTRH:%d\r\n", - BT_HFP_BTRH_ACCEPTED); - if (err) { - LOG_ERR("Fail to send err :(%d)", err); - } - return err; + return err; + } + } else if (action == BT_HFP_BTRH_ACCEPTED) { + call = get_call_with_flag_and_state(ag, BT_HFP_AG_CALL_INCOMING_HELD, + BT_HFP_CALL_ACTIVE); + if (call) { + atomic_clear_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD); + err = hfp_ag_send_data(ag, btrh_accept_cb, call, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_ACCEPTED); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); } - } else if (action == BT_HFP_BTRH_REJECTED) { - if (atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_INCOMING_HELD) && - (call_state == BT_HFP_CALL_ACTIVE)) { - err = hfp_ag_send_data(ag, btrh_reject_cb, NULL, "\r\n+BTRH:%d\r\n", - BT_HFP_BTRH_REJECTED); - if (err) { - LOG_ERR("Fail to send err :(%d)", err); - } - return err; + return err; + } + } else if (action == BT_HFP_BTRH_REJECTED) { + call = get_call_with_flag_and_state(ag, BT_HFP_AG_CALL_INCOMING_HELD, + BT_HFP_CALL_ACTIVE); + if (call) { + atomic_clear_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD); + err = hfp_ag_send_data(ag, btrh_reject_cb, call, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_REJECTED); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); } + return err; } } @@ -1734,6 +2802,14 @@ static int bt_hfp_ag_ccwa_handler(struct bt_hfp_ag *ag, struct net_buf *buf) int err; uint32_t value; + hfp_ag_lock(ag); + if (!((ag->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL) && + (ag->hf_features & BT_HFP_HF_FEATURE_3WAY_CALL))) { + hfp_ag_unlock(ag); + return -EOPNOTSUPP; + } + hfp_ag_unlock(ag); + if (!is_char(buf, '=')) { return -ENOTSUP; } @@ -1784,6 +2860,7 @@ static void hfp_ag_disconnected(struct bt_rfcomm_dlc *dlc) bt_hfp_call_state_t call_state; sys_snode_t *node; struct bt_ag_tx *tx; + struct bt_hfp_ag_call *call; k_work_cancel_delayable(&ag->tx_work); @@ -1805,12 +2882,20 @@ static void hfp_ag_disconnected(struct bt_rfcomm_dlc *dlc) bt_hfp_ag_set_state(ag, BT_HFP_DISCONNECTED); - hfp_ag_lock(ag); - call_state = ag->call_state; - hfp_ag_unlock(ag); - if ((call_state == BT_HFP_CALL_ALERTING) || (call_state == BT_HFP_CALL_ACTIVE) || - (call_state == BT_HFP_CALL_HOLD)) { - bt_hfp_ag_terminate_cb(ag, NULL); + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + if ((call_state == BT_HFP_CALL_ALERTING) || (call_state == BT_HFP_CALL_ACTIVE) || + (call_state == BT_HFP_CALL_HOLD)) { + bt_hfp_ag_terminate_cb(ag, call); + } } LOG_DBG("AG %p", ag); @@ -1917,22 +3002,58 @@ static void hfp_ag_sent(struct bt_rfcomm_dlc *dlc, int err) tx = CONTAINER_OF(node, struct bt_ag_tx, node); LOG_DBG("Completed pending tx %p", tx); - /* Restart the tx work */ - k_work_reschedule(&ag->tx_work, K_NO_WAIT); + /* Restart the tx work */ + k_work_reschedule(&ag->tx_work, K_NO_WAIT); + + tx->err = err; + k_fifo_put(&ag_tx_notify, tx); +} + +static const char *bt_ag_get_call_state_string(bt_hfp_call_state_t call_state) +{ + const char *s; + + switch (call_state) { + case BT_HFP_CALL_TERMINATE: + s = "terminate"; + break; + case BT_HFP_CALL_ACTIVE: + s = "active"; + break; + case BT_HFP_CALL_HOLD: + s = "hold"; + break; + case BT_HFP_CALL_OUTGOING: + s = "outgoing"; + break; + case BT_HFP_CALL_INCOMING: + s = "incoming"; + break; + case BT_HFP_CALL_ALERTING: + s = "alerting"; + break; + default: + s = "unknown"; + break; + } - tx->err = err; - k_fifo_put(&ag_tx_notify, tx); + return s; } static void bt_ag_deferred_work_cb(struct bt_hfp_ag *ag, void *user_data) { int err; bt_hfp_call_state_t call_state; + uint8_t call_setup_ind; + uint8_t call_ind; + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; LOG_DBG(""); hfp_ag_lock(ag); - call_state = ag->call_state; + call_state = call->call_state; + call_setup_ind = ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND]; + call_ind = ag->indicator_value[BT_HFP_AG_CALL_IND]; hfp_ag_unlock(ag); switch (call_state) { @@ -1949,35 +3070,52 @@ static void bt_ag_deferred_work_cb(struct bt_hfp_ag *ag, void *user_data) case BT_HFP_CALL_ALERTING: __fallthrough; default: - if (ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND] && - ag->indicator_value[BT_HFP_AG_CALL_IND]) { + LOG_WRN("Call timeout, status %s", bt_ag_get_call_state_string(call_state)); + + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_OUTGOING_3WAY) || + atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY)) { + if (!call_setup_ind) { + return; + } + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_NONE, + bt_hfp_ag_reject_cb, call); + if (err) { + LOG_ERR("Fail to send indicator"); + bt_hfp_ag_terminate_cb(ag, call); + } + return; + } + + if (call_setup_ind && call_ind) { err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, - BT_HFP_CALL_SETUP_NONE, NULL, NULL); + BT_HFP_CALL_SETUP_NONE, NULL, NULL); if (err) { LOG_ERR("Fail to send indicator"); - bt_hfp_ag_terminate_cb(ag, NULL); + bt_hfp_ag_terminate_cb(ag, call); } else { err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, - bt_hfp_ag_terminate_cb, NULL); + bt_hfp_ag_terminate_cb, call); if (err) { LOG_ERR("Fail to send indicator"); - bt_hfp_ag_terminate_cb(ag, NULL); + bt_hfp_ag_terminate_cb(ag, call); } } - } else if (ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND]) { + } else if (call_setup_ind) { err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, - BT_HFP_CALL_SETUP_NONE, bt_hfp_ag_reject_cb, - NULL); + BT_HFP_CALL_SETUP_NONE, bt_hfp_ag_reject_cb, + call); if (err) { LOG_ERR("Fail to send indicator"); - bt_hfp_ag_terminate_cb(ag, NULL); + bt_hfp_ag_terminate_cb(ag, call); } - } else if (ag->indicator_value[BT_HFP_AG_CALL_IND]) { + } else if (call_ind) { err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, - bt_hfp_ag_terminate_cb, NULL); + bt_hfp_ag_terminate_cb, call); if (err) { LOG_ERR("Fail to send indicator"); - bt_hfp_ag_terminate_cb(ag, NULL); + bt_hfp_ag_terminate_cb(ag, call); } } break; @@ -1987,28 +3125,30 @@ static void bt_ag_deferred_work_cb(struct bt_hfp_ag *ag, void *user_data) static void bt_ag_deferred_work(struct k_work *work) { struct k_work_delayable *dwork = k_work_delayable_from_work(work); - struct bt_hfp_ag *ag = CONTAINER_OF(dwork, struct bt_hfp_ag, deferred_work); + struct bt_hfp_ag_call *call = CONTAINER_OF(dwork, struct bt_hfp_ag_call, deferred_work); + struct bt_hfp_ag *ag = call->ag; - (void)hfp_ag_next_step(ag, bt_ag_deferred_work_cb, NULL); + (void)hfp_ag_next_step(ag, bt_ag_deferred_work_cb, call); } static void bt_ag_ringing_work_cb(struct bt_hfp_ag *ag, void *user_data) { int err; bt_hfp_call_state_t call_state; + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; LOG_DBG(""); hfp_ag_lock(ag); - call_state = ag->call_state; + call_state = call->call_state; hfp_ag_unlock(ag); if (call_state == BT_HFP_CALL_ALERTING) { - if (!atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL)) { + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { return; } - k_work_reschedule(&ag->ringing_work, + k_work_reschedule(&call->ringing_work, K_SECONDS(CONFIG_BT_HFP_AG_RING_NOTIFY_INTERVAL)); err = hfp_ag_send_data(ag, NULL, NULL, "\r\nRING\r\n"); @@ -2017,7 +3157,7 @@ static void bt_ag_ringing_work_cb(struct bt_hfp_ag *ag, void *user_data) } else { if (atomic_test_bit(ag->flags, BT_HFP_AG_CLIP_ENABLE)) { err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+CLIP:\"%s\",%d\r\n", - ag->number, 0); + call->number, 0); if (err) { LOG_ERR("Fail to send CLIP %d", err); } @@ -2029,9 +3169,9 @@ static void bt_ag_ringing_work_cb(struct bt_hfp_ag *ag, void *user_data) static void bt_ag_ringing_work(struct k_work *work) { struct k_work_delayable *dwork = k_work_delayable_from_work(work); - struct bt_hfp_ag *ag = CONTAINER_OF(dwork, struct bt_hfp_ag, ringing_work); + struct bt_hfp_ag_call *call = CONTAINER_OF(dwork, struct bt_hfp_ag_call, ringing_work); - (void)hfp_ag_next_step(ag, bt_ag_ringing_work_cb, NULL); + (void)hfp_ag_next_step(call->ag, bt_ag_ringing_work_cb, call); } int bt_hfp_ag_connect(struct bt_conn *conn, struct bt_hfp_ag **ag, uint8_t channel) @@ -2124,10 +3264,6 @@ int bt_hfp_ag_connect(struct bt_conn *conn, struct bt_hfp_ag **ag, uint8_t chann _ag->selected_codec_id = BT_HFP_AG_CODEC_CVSD; /* Init delay work */ - k_work_init_delayable(&_ag->deferred_work, bt_ag_deferred_work); - - k_work_init_delayable(&_ag->ringing_work, bt_ag_ringing_work); - k_work_init_delayable(&_ag->tx_work, bt_ag_tx_work); *ag = _ag; @@ -2150,37 +3286,14 @@ int bt_hfp_ag_connect(struct bt_conn *conn, struct bt_hfp_ag **ag, uint8_t chann int bt_hfp_ag_disconnect(struct bt_hfp_ag *ag) { - int err; - bt_hfp_call_state_t call_state; - LOG_DBG(""); if (ag == NULL) { return -EINVAL; } - hfp_ag_lock(ag); - call_state = ag->call_state; - hfp_ag_unlock(ag); - bt_hfp_ag_set_state(ag, BT_HFP_DISCONNECTING); - if ((call_state == BT_HFP_CALL_ACTIVE) || (call_state == BT_HFP_CALL_HOLD)) { - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, bt_hfp_ag_terminate_cb, - NULL); - if (err != 0) { - LOG_ERR("HFP AG send response err :(%d)", err); - } - return err; - } else if (call_state != BT_HFP_CALL_TERMINATE) { - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, - bt_hfp_ag_reject_cb, NULL); - if (err != 0) { - LOG_ERR("HFP AG send response err :(%d)", err); - } - return err; - } - return bt_rfcomm_dlc_disconnect(&ag->rfcomm_dlc); } @@ -2201,29 +3314,63 @@ int bt_hfp_ag_register(struct bt_hfp_ag_cb *cb) static void bt_hfp_ag_incoming_cb(struct bt_hfp_ag *ag, void *user_data) { - bt_hfp_ag_set_call_state(ag, BT_HFP_CALL_INCOMING); + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + __ASSERT(call, "Invalid call object"); + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_INCOMING); if (bt_ag && bt_ag->incoming) { - bt_ag->incoming(ag, ag->number); + bt_ag->incoming(ag, call, call->number); } if (atomic_test_bit(ag->flags, BT_HFP_AG_INBAND_RING)) { int err; - err = bt_hfp_ag_create_audio_connection(ag); + err = bt_hfp_ag_create_audio_connection(ag, call); if (err) { - bt_hfp_ag_call_reject(ag, NULL); + bt_hfp_ag_call_reject(ag, user_data); } } else { - bt_hfp_ag_set_call_state(ag, BT_HFP_CALL_ALERTING); - bt_hfp_ag_call_ringing_cb(ag, false); + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING); + bt_hfp_ag_call_ringing_cb(call, false); + } +} + +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) +static void bt_hfp_ag_2nd_incoming_cb(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call; + + call = (struct bt_hfp_ag_call *)user_data; + + __ASSERT(call, "Invalid call object"); + + if (bt_ag && bt_ag->incoming) { + bt_ag->incoming(ag, call, call->number); + } + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING); +} + +static void bt_hfp_ag_ccwa_cb(struct bt_hfp_ag *ag, void *user_data) +{ + int err; + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_INCOMING, + bt_hfp_ag_2nd_incoming_cb, user_data); + if (err) { + LOG_ERR("Fail to send call setup"); } } +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ int bt_hfp_ag_remote_incoming(struct bt_hfp_ag *ag, const char *number) { int err = 0; size_t len; + struct bt_hfp_ag_call *call; + int call_count; LOG_DBG(""); @@ -2236,11 +3383,6 @@ int bt_hfp_ag_remote_incoming(struct bt_hfp_ag *ag, const char *number) hfp_ag_unlock(ag); return -ENOTCONN; } - - if (ag->call_state != BT_HFP_CALL_TERMINATE) { - hfp_ag_unlock(ag); - return -EBUSY; - } hfp_ag_unlock(ag); len = strlen(number); @@ -2248,29 +3390,80 @@ int bt_hfp_ag_remote_incoming(struct bt_hfp_ag *ag, const char *number) return -EINVAL; } - hfp_ag_lock(ag); - /* Copy number to ag->number including null-character */ - memcpy(ag->number, number, len + 1); - hfp_ag_unlock(ag); + call = get_call_from_number(ag, number, 0); + if (call) { + return -EBUSY; + } + + call_count = get_none_released_calls(ag); + if (call_count) { +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + if (!((ag->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL) && + (ag->hf_features & BT_HFP_HF_FEATURE_3WAY_CALL))) { + LOG_WRN("3 Way call feature is not supported on both sides"); + return -EOPNOTSUPP; + } + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_CCWA_ENABLE)) { + LOG_WRN("Call waiting notification is not enabled"); + return -ENOTSUP; + } + + if (!get_active_held_calls(ag)) { + LOG_WRN("The first call is not accepted"); + return -EBUSY; + } +#else + return -EBUSY; +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + } + + call = get_new_call(ag, number, 0); + if (!call) { + LOG_WRN("Cannot allocate a new call object"); + return -ENOMEM; + } + + atomic_set_bit(call->flags, BT_HFP_AG_CALL_INCOMING); + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_INCOMING); - atomic_set_bit(ag->flags, BT_HFP_AG_INCOMING_CALL); +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + if (call_count) { + atomic_set_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY); + err = hfp_ag_send_data(ag, bt_hfp_ag_ccwa_cb, call, "\r\n+CCWA:\"%s\",%d\r\n", + call->number, call->type); + if (err) { + LOG_WRN("Fail to send call waiting notification"); + free_call(call); + } + + return err; + } +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_INCOMING, - bt_hfp_ag_incoming_cb, NULL); - if (err != 0) { - atomic_clear_bit(ag->flags, BT_HFP_AG_INCOMING_CALL); + bt_hfp_ag_incoming_cb, call); + if (err) { + free_call(call); } return err; } -int bt_hfp_ag_hold_incoming(struct bt_hfp_ag *ag) +int bt_hfp_ag_hold_incoming(struct bt_hfp_ag_call *call) { int err = 0; + struct bt_hfp_ag *ag; bt_hfp_call_state_t call_state; LOG_DBG(""); + if (call == NULL) { + return -EINVAL; + } + + ag = call->ag; + if (ag == NULL) { return -EINVAL; } @@ -2281,18 +3474,17 @@ int bt_hfp_ag_hold_incoming(struct bt_hfp_ag *ag) return -ENOTCONN; } - call_state = ag->call_state; + call_state = call->call_state; hfp_ag_unlock(ag); - if (!atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL)) { + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { return -EINVAL; } - if ((call_state == BT_HFP_CALL_ALERTING) || - (call_state == BT_HFP_CALL_INCOMING)) { - atomic_set_bit(ag->flags, BT_HFP_AG_INCOMING_HELD); - err = hfp_ag_send_data(ag, btrh_held_cb, NULL, "\r\n+BTRH:%d\r\n", - BT_HFP_BTRH_ON_HOLD); + if ((call_state == BT_HFP_CALL_ALERTING) || (call_state == BT_HFP_CALL_INCOMING)) { + atomic_set_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD); + err = hfp_ag_send_data(ag, btrh_held_cb, call, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_ON_HOLD); if (err) { LOG_ERR("Fail to send err :(%d)", err); } @@ -2302,13 +3494,20 @@ int bt_hfp_ag_hold_incoming(struct bt_hfp_ag *ag) return -EINVAL; } -int bt_hfp_ag_reject(struct bt_hfp_ag *ag) +int bt_hfp_ag_reject(struct bt_hfp_ag_call *call) { - int err; + int err = 0; + struct bt_hfp_ag *ag; bt_hfp_call_state_t call_state; LOG_DBG(""); + if (call == NULL) { + return -EINVAL; + } + + ag = call->ag; + if (ag == NULL) { return -EINVAL; } @@ -2319,25 +3518,46 @@ int bt_hfp_ag_reject(struct bt_hfp_ag *ag) return -ENOTCONN; } - call_state = ag->call_state; + call_state = call->call_state; hfp_ag_unlock(ag); - if (!atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL)) { + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { return -EINVAL; } - if ((call_state == BT_HFP_CALL_ALERTING) || - (call_state == BT_HFP_CALL_INCOMING)) { + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY)) { + if ((call_state == BT_HFP_CALL_ALERTING) || (call_state == BT_HFP_CALL_INCOMING)) { + uint8_t call_setup; + + hfp_ag_lock(ag); + call_setup = ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND]; + hfp_ag_unlock(ag); + + if (call_setup) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_NONE, NULL, NULL); + if (err) { + return err; + } + } + + ag_reject_call(call); + + return 0; + } + } + + if ((call_state == BT_HFP_CALL_ALERTING) || (call_state == BT_HFP_CALL_INCOMING)) { err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, - bt_hfp_ag_reject_cb, NULL); + bt_hfp_ag_reject_cb, call); return err; } if ((call_state == BT_HFP_CALL_ACTIVE) && - atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_INCOMING_HELD)) { - err = hfp_ag_send_data(ag, btrh_reject_cb, NULL, "\r\n+BTRH:%d\r\n", - BT_HFP_BTRH_REJECTED); + atomic_test_and_clear_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) { + err = hfp_ag_send_data(ag, btrh_reject_cb, call, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_REJECTED); if (err) { LOG_ERR("Fail to send err :(%d)", err); } @@ -2347,13 +3567,20 @@ int bt_hfp_ag_reject(struct bt_hfp_ag *ag) return -EINVAL; } -int bt_hfp_ag_accept(struct bt_hfp_ag *ag) +int bt_hfp_ag_accept(struct bt_hfp_ag_call *call) { - int err; + int err = 0; + struct bt_hfp_ag *ag; bt_hfp_call_state_t call_state; LOG_DBG(""); + if (call == NULL) { + return -EINVAL; + } + + ag = call->ag; + if (ag == NULL) { return -EINVAL; } @@ -2364,21 +3591,29 @@ int bt_hfp_ag_accept(struct bt_hfp_ag *ag) return -ENOTCONN; } - call_state = ag->call_state; + call_state = call->call_state; hfp_ag_unlock(ag); - if (!atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL)) { + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { return -EINVAL; } +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY)) { + if ((call_state == BT_HFP_CALL_ALERTING) || (call_state == BT_HFP_CALL_INCOMING)) { + return chld_deactivate_calls_and_accept_other_call(ag, false); + } + } +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + if (call_state == BT_HFP_CALL_ALERTING) { - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, bt_hfp_ag_accept_cb, NULL); + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, bt_hfp_ag_accept_cb, call); if (err) { LOG_ERR("Fail to send err :(%d)", err); } err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, - bt_hfp_ag_audio_connection, NULL); + bt_hfp_ag_call_setup_none_cb, call); if (err) { LOG_ERR("Fail to send err :(%d)", err); } @@ -2386,10 +3621,10 @@ int bt_hfp_ag_accept(struct bt_hfp_ag *ag) return err; } - if (atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_INCOMING_HELD) && - (call_state == BT_HFP_CALL_ACTIVE)) { - err = hfp_ag_send_data(ag, btrh_accept_cb, NULL, "\r\n+BTRH:%d\r\n", - BT_HFP_BTRH_ACCEPTED); + if (atomic_test_and_clear_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD) && + (call_state == BT_HFP_CALL_ACTIVE)) { + err = hfp_ag_send_data(ag, btrh_accept_cb, call, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_ACCEPTED); if (err) { LOG_ERR("Fail to send err :(%d)", err); } @@ -2399,13 +3634,20 @@ int bt_hfp_ag_accept(struct bt_hfp_ag *ag) return -EINVAL; } -int bt_hfp_ag_terminate(struct bt_hfp_ag *ag) +int bt_hfp_ag_terminate(struct bt_hfp_ag_call *call) { - int err; + int err = 0; + struct bt_hfp_ag *ag; bt_hfp_call_state_t call_state; LOG_DBG(""); + if (call == NULL) { + return -EINVAL; + } + + ag = call->ag; + if (ag == NULL) { return -EINVAL; } @@ -2416,14 +3658,14 @@ int bt_hfp_ag_terminate(struct bt_hfp_ag *ag) return -ENOTCONN; } - call_state = ag->call_state; + call_state = call->call_state; hfp_ag_unlock(ag); - if (atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_INCOMING_HELD) && - atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL) && - (call_state == BT_HFP_CALL_ACTIVE)) { - err = hfp_ag_send_data(ag, btrh_reject_cb, NULL, "\r\n+BTRH:%d\r\n", - BT_HFP_BTRH_REJECTED); + if (atomic_test_and_clear_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD) && + atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING) && + (call_state == BT_HFP_CALL_ACTIVE)) { + err = hfp_ag_send_data(ag, btrh_reject_cb, call, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_REJECTED); if (err) { LOG_ERR("Fail to send err :(%d)", err); } @@ -2434,36 +3676,65 @@ int bt_hfp_ag_terminate(struct bt_hfp_ag *ag) return -EINVAL; } - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, bt_hfp_ag_terminate_cb, NULL); + ag_terminate_call(call); - return err; + return 0; } -static void bt_hfp_ag_outgoing_cb(struct bt_hfp_ag *ag, void *user_data) +int bt_hfp_ag_retrieve(struct bt_hfp_ag_call *call) { - bt_hfp_ag_set_call_state(ag, BT_HFP_CALL_OUTGOING); + struct bt_hfp_ag *ag; + bt_hfp_call_state_t call_state; - if (bt_ag && bt_ag->outgoing) { - bt_ag->outgoing(ag, ag->number); + LOG_DBG(""); + + if (call == NULL) { + return -EINVAL; } - if (atomic_test_bit(ag->flags, BT_HFP_AG_INBAND_RING)) { - int err; + ag = call->ag; - err = bt_hfp_ag_create_audio_connection(ag); - if (err) { - bt_hfp_ag_call_reject(ag, NULL); - } + if (ag == NULL) { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + call_state = call->call_state; + hfp_ag_unlock(ag); + + if (call_state != BT_HFP_CALL_HOLD) { + return -EINVAL; + } + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); + + if (bt_ag && bt_ag->retrieve) { + bt_ag->retrieve(call); } + + ag_notify_call_held_ind(ag, NULL); + + return 0; } -int bt_hfp_ag_outgoing(struct bt_hfp_ag *ag, const char *number) +int bt_hfp_ag_hold(struct bt_hfp_ag_call *call) { - int err = 0; - size_t len; + struct bt_hfp_ag *ag; + bt_hfp_call_state_t call_state; LOG_DBG(""); + if (call == NULL) { + return -EINVAL; + } + + ag = call->ag; + if (ag == NULL) { return -EINVAL; } @@ -2474,47 +3745,73 @@ int bt_hfp_ag_outgoing(struct bt_hfp_ag *ag, const char *number) return -ENOTCONN; } - if (ag->call_state != BT_HFP_CALL_TERMINATE) { - hfp_ag_unlock(ag); - return -EBUSY; - } + call_state = call->call_state; hfp_ag_unlock(ag); - len = strlen(number); - if ((len == 0) || (len > CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN)) { + if (call_state != BT_HFP_CALL_ACTIVE) { return -EINVAL; } - hfp_ag_lock(ag); - /* Copy number to ag->number including null-character */ - memcpy(ag->number, number, len + 1); - hfp_ag_unlock(ag); + if ((call_state == BT_HFP_CALL_ACTIVE) && + (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD))) { + return -EINVAL; + } - atomic_clear_bit(ag->flags, BT_HFP_AG_INCOMING_CALL); + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_HOLD); - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_OUTGOING, - bt_hfp_ag_outgoing_cb, NULL); + if (bt_ag && bt_ag->held) { + bt_ag->held(call); + } - return err; + ag_notify_call_held_ind(ag, NULL); + + return 0; +} + +int bt_hfp_ag_outgoing(struct bt_hfp_ag *ag, const char *number) +{ + LOG_DBG(""); + + if (ag == NULL) { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + hfp_ag_unlock(ag); + + return bt_hfp_ag_outgoing_call(ag, number, 0); } static void bt_hfp_ag_ringing_cb(struct bt_hfp_ag *ag, void *user_data) { - bt_hfp_ag_set_call_state(ag, BT_HFP_CALL_ALERTING); + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING); if (atomic_test_bit(ag->flags, BT_HFP_AG_INBAND_RING)) { - bt_hfp_ag_call_ringing_cb(ag, true); + bt_hfp_ag_call_ringing_cb(call, true); } else { - bt_hfp_ag_call_ringing_cb(ag, false); + bt_hfp_ag_call_ringing_cb(call, false); } } -int bt_hfp_ag_remote_ringing(struct bt_hfp_ag *ag) +int bt_hfp_ag_remote_ringing(struct bt_hfp_ag_call *call) { int err = 0; + struct bt_hfp_ag *ag; LOG_DBG(""); + if (call == NULL) { + return -EINVAL; + } + + ag = call->ag; + if (ag == NULL) { return -EINVAL; } @@ -2525,7 +3822,7 @@ int bt_hfp_ag_remote_ringing(struct bt_hfp_ag *ag) return -ENOTCONN; } - if (ag->call_state != BT_HFP_CALL_OUTGOING) { + if (call->call_state != BT_HFP_CALL_OUTGOING) { hfp_ag_unlock(ag); return -EBUSY; } @@ -2539,18 +3836,25 @@ int bt_hfp_ag_remote_ringing(struct bt_hfp_ag *ag) hfp_ag_unlock(ag); err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, - BT_HFP_CALL_SETUP_REMOTE_ALERTING, bt_hfp_ag_ringing_cb, - NULL); + BT_HFP_CALL_SETUP_REMOTE_ALERTING, bt_hfp_ag_ringing_cb, + call); return err; } -int bt_hfp_ag_remote_reject(struct bt_hfp_ag *ag) +int bt_hfp_ag_remote_reject(struct bt_hfp_ag_call *call) { int err; + struct bt_hfp_ag *ag; LOG_DBG(""); + if (call == NULL) { + return -EINVAL; + } + + ag = call->ag; + if (ag == NULL) { return -EINVAL; } @@ -2561,28 +3865,36 @@ int bt_hfp_ag_remote_reject(struct bt_hfp_ag *ag) return -ENOTCONN; } - if ((ag->call_state != BT_HFP_CALL_ALERTING) && (ag->call_state != BT_HFP_CALL_OUTGOING)) { + if ((call->call_state != BT_HFP_CALL_ALERTING) && + (call->call_state != BT_HFP_CALL_OUTGOING)) { hfp_ag_unlock(ag); return -EINVAL; } hfp_ag_unlock(ag); - if (atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL)) { + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { return -EINVAL; } err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, - bt_hfp_ag_reject_cb, NULL); + bt_hfp_ag_reject_cb, call); return err; } -int bt_hfp_ag_remote_accept(struct bt_hfp_ag *ag) +int bt_hfp_ag_remote_accept(struct bt_hfp_ag_call *call) { int err; + struct bt_hfp_ag *ag; LOG_DBG(""); + if (call == NULL) { + return -EINVAL; + } + + ag = call->ag; + if (ag == NULL) { return -EINVAL; } @@ -2593,36 +3905,44 @@ int bt_hfp_ag_remote_accept(struct bt_hfp_ag *ag) return -ENOTCONN; } - if (ag->call_state != BT_HFP_CALL_ALERTING) { + if (call->call_state != BT_HFP_CALL_ALERTING) { hfp_ag_unlock(ag); return -EINVAL; } hfp_ag_unlock(ag); - if (atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL)) { + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { return -EINVAL; } - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, bt_hfp_ag_accept_cb, NULL); - if (err != 0) { - LOG_ERR("Fail to send err :(%d)", err); + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_OUTGOING_3WAY)) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, bt_hfp_ag_accept_cb, call); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } } err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, - bt_hfp_ag_audio_connection, NULL); - if (err != 0) { + bt_hfp_ag_call_setup_none_cb, call); + if (err) { LOG_ERR("Fail to send err :(%d)", err); } return err; } -int bt_hfp_ag_remote_terminate(struct bt_hfp_ag *ag) +int bt_hfp_ag_remote_terminate(struct bt_hfp_ag_call *call) { - int err; + struct bt_hfp_ag *ag; LOG_DBG(""); + if (call == NULL) { + return -EINVAL; + } + + ag = call->ag; + if (ag == NULL) { return -EINVAL; } @@ -2633,15 +3953,35 @@ int bt_hfp_ag_remote_terminate(struct bt_hfp_ag *ag) return -ENOTCONN; } - if ((ag->call_state != BT_HFP_CALL_ACTIVE) && (ag->call_state != BT_HFP_CALL_HOLD)) { + if ((call->call_state != BT_HFP_CALL_ACTIVE) && (call->call_state != BT_HFP_CALL_HOLD)) { hfp_ag_unlock(ag); return -EINVAL; } hfp_ag_unlock(ag); - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, bt_hfp_ag_terminate_cb, NULL); + ag_terminate_call(call); - return err; + return 0; +} + +int bt_hfp_ag_explicit_call_transfer(struct bt_hfp_ag *ag) +{ +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + if (ag == NULL) { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + hfp_ag_unlock(ag); + + return chld_drop_conversation(ag); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ } int bt_hfp_ag_set_indicator(struct bt_hfp_ag *ag, enum bt_hfp_ag_indicator index, uint8_t value) @@ -2760,7 +4100,7 @@ int bt_hfp_ag_audio_connect(struct bt_hfp_ag *ag, uint8_t id) ag->selected_codec_id = id; hfp_ag_unlock(ag); - err = bt_hfp_ag_create_audio_connection(ag); + err = bt_hfp_ag_create_audio_connection(ag, NULL); return err; } diff --git a/subsys/bluetooth/host/classic/hfp_ag_internal.h b/subsys/bluetooth/host/classic/hfp_ag_internal.h index 7a13a33677c58..5235ab5ea25bc 100644 --- a/subsys/bluetooth/host/classic/hfp_ag_internal.h +++ b/subsys/bluetooth/host/classic/hfp_ag_internal.h @@ -46,18 +46,33 @@ #define BT_HFP_AG_FEATURE_ECS_ENABLE 0 #endif /* CONFIG_BT_HFP_AG_ECS*/ +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) +#define BT_HFP_AG_FEATURE_3WAY_CALL_ENABLE BT_HFP_AG_FEATURE_3WAY_CALL +#define BT_HFP_AG_SDP_FEATURE_3WAY_CALL_ENABLE BT_HFP_AG_SDP_FEATURE_3WAY_CALL +#else +#define BT_HFP_AG_FEATURE_3WAY_CALL_ENABLE 0 +#define BT_HFP_AG_SDP_FEATURE_3WAY_CALL_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + +#if defined(CONFIG_BT_HFP_AG_ECC) +#define BT_HFP_AG_FEATURE_ECC_ENABLE BT_HFP_AG_FEATURE_ECC +#else +#define BT_HFP_AG_FEATURE_ECC_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_ECC */ + /* HFP AG Supported features */ #define BT_HFP_AG_SUPPORTED_FEATURES (\ - BT_HFP_AG_FEATURE_3WAY_CALL | \ + BT_HFP_AG_FEATURE_3WAY_CALL_ENABLE | \ BT_HFP_AG_FEATURE_INBAND_RINGTONE | \ BT_HFP_AG_FEATURE_EXT_ERR_ENABLE | \ BT_HFP_AG_FEATURE_CODEC_NEG_ENABLE | \ BT_HFP_AG_FEATURE_ECNR_ENABLE | \ - BT_HFP_AG_FEATURE_ECS_ENABLE) + BT_HFP_AG_FEATURE_ECS_ENABLE | \ + BT_HFP_AG_FEATURE_ECC_ENABLE) /* HFP AG Supported features in SDP */ #define BT_HFP_AG_SDP_SUPPORTED_FEATURES (\ - BT_HFP_AG_SDP_FEATURE_3WAY_CALL | \ + BT_HFP_AG_SDP_FEATURE_3WAY_CALL_ENABLE | \ BT_HFP_AG_SDP_FEATURE_INBAND_RINGTONE | \ BT_HFP_AG_SDP_FEATURE_ECNR_ENABLE) @@ -69,8 +84,6 @@ enum { BT_HFP_AG_CCWA_ENABLE, /* Call Waiting notification */ BT_HFP_AG_INBAND_RING, /* In-band ring */ BT_HFP_AG_COPS_SET, /* Query Operator selection */ - BT_HFP_AG_INCOMING_CALL, /* Incoming call */ - BT_HFP_AG_INCOMING_HELD, /* Incoming call held */ BT_HFP_AG_AUDIO_CONN, /* Audio connection */ BT_HFP_AG_CODEC_CONN, /* Codec connection is ongoing */ BT_HFP_AG_CODEC_CHANGED, /* Codec Id Changed */ @@ -81,6 +94,19 @@ enum { BT_HFP_AG_NUM_FLAGS, }; +/* bt_hfp_ag_call flags: the flags defined here represent HFP AG parameters */ +enum { + BT_HFP_AG_CALL_IN_USING, /* Object is in using */ + BT_HFP_AG_CALL_INCOMING, /* Incoming call */ + BT_HFP_AG_CALL_INCOMING_HELD, /* Incoming call held */ + BT_HFP_AG_CALL_OPEN_SCO, /* Open SCO */ + BT_HFP_AG_CALL_OUTGOING_3WAY, /* Outgoing 3 way call */ + BT_HFP_AG_CALL_INCOMING_3WAY, /* Incoming 3 way call */ + + /* Total number of flags - must be at the end of the enum */ + BT_HFP_AG_CALL_NUM_FLAGS, +}; + /* HFP HF Indicators */ enum { HFP_HF_ENHANCED_SAFETY_IND = 1, /* Enhanced Safety */ @@ -103,21 +129,38 @@ typedef enum __packed { typedef enum __packed { /** Call terminate */ - BT_HFP_CALL_TERMINATE, + BT_HFP_CALL_TERMINATE = 1, /** Call outgoing */ - BT_HFP_CALL_OUTGOING, + BT_HFP_CALL_OUTGOING = 2, /** Call incoming */ - BT_HFP_CALL_INCOMING, + BT_HFP_CALL_INCOMING = 4, /** Call alerting */ - BT_HFP_CALL_ALERTING, + BT_HFP_CALL_ALERTING = 8, /** Call active */ - BT_HFP_CALL_ACTIVE, + BT_HFP_CALL_ACTIVE = 16, /** Call hold */ - BT_HFP_CALL_HOLD, + BT_HFP_CALL_HOLD = 32, } bt_hfp_call_state_t; #define AT_COPS_OPERATOR_MAX_LEN 16 +struct bt_hfp_ag_call { + struct bt_hfp_ag *ag; + + char number[CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN + 1]; + uint8_t type; + + ATOMIC_DEFINE(flags, BT_HFP_AG_CALL_NUM_FLAGS); + + /* HFP Call state */ + bt_hfp_call_state_t call_state; + + /* Calling Line Identification notification */ + struct k_work_delayable ringing_work; + + struct k_work_delayable deferred_work; +}; + struct bt_hfp_ag { struct bt_rfcomm_dlc rfcomm_dlc; char buffer[HF_MAX_BUF_LEN]; @@ -136,14 +179,6 @@ struct bt_hfp_ag { /* HFP Connection state */ bt_hfp_state_t state; - /* HFP Call state */ - bt_hfp_call_state_t call_state; - - /* Delayed work deferred tasks: - * - call status cleanup. - */ - struct k_work_delayable deferred_work; - /* AG Indicators */ uint8_t indicator_value[BT_HFP_AG_IND_MAX]; uint32_t indicator; @@ -157,17 +192,18 @@ struct bt_hfp_ag { uint8_t mode; char operator[AT_COPS_OPERATOR_MAX_LEN + 1]; + /* calls */ + struct bt_hfp_ag_call calls[CONFIG_BT_HFP_AG_MAX_CALLS]; + + /* last dialing number and type */ + char last_number[CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN + 1]; + uint8_t type; + /* SCO Connection Object */ struct bt_sco_chan sco_chan; /* HFP TX pending */ sys_slist_t tx_pending; - /* Dial number or incoming number */ - char number[CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN + 1]; - - /* Calling Line Identification notification */ - struct k_work_delayable ringing_work; - /* Critical locker */ struct k_sem lock; diff --git a/subsys/bluetooth/host/classic/hfp_internal.h b/subsys/bluetooth/host/classic/hfp_internal.h index 0bdce42e41b05..9b1363d1419eb 100644 --- a/subsys/bluetooth/host/classic/hfp_internal.h +++ b/subsys/bluetooth/host/classic/hfp_internal.h @@ -182,3 +182,35 @@ enum hfp_hf_ag_indicators { #define BT_HFP_BTRH_ON_HOLD 0 #define BT_HFP_BTRH_ACCEPTED 1 #define BT_HFP_BTRH_REJECTED 2 + +/* HFP Call held status */ +/* No calls held */ +#define BT_HFP_CALL_HELD_NONE 0 +/* Call is placed on hold or active/held calls swapped + * The AG has both an active AND a held call + */ +#define BT_HFP_CALL_HELD_ACTIVE_HELD 1 +/* Call on hold, no active call */ +#define BT_HFP_CALL_HELD_HELD 2 + +/* HFP AT+CHLD command value */ +/* Releases all held calls or sets User Determined User Busy + * (UDUB) for a waiting call + */ +#define BT_HFP_CHLD_RELEASE_ALL 0 +/* Releases all active calls (if any exist) and accepts the + * other (held or waiting) call. + */ +#define BT_HFP_CHLD_RELEASE_ACTIVE_ACCEPT_OTHER 1 +/* Places all active calls (if any exist) on hold and accepts + * the other (held or waiting) call + */ +#define BT_HFP_CALL_HOLD_ACTIVE_ACCEPT_OTHER 2 +/* Adds a held call to the conversation */ +#define BT_HFP_CALL_ACTIVE_HELD 3 +/* Connects the two calls and disconnects the subscriber from + * both calls (Explicit Call Transfer). + * Support for this value and its associated functionality is + * optional for the HF. + */ +#define BT_HFP_CALL_QUITE 4 From fe916fe7009f40f13827a237fe33932cc414a5f6 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Mon, 2 Sep 2024 12:13:11 +0800 Subject: [PATCH 38/76] Bluetooth: HFP_HF: Use HF object to replace conn object Use HF object to replace conn object for callbacks `disconnected`, `sco_connected`, `service`, `outgoing`, `remote_ringing`, `incoming`, `incoming_held`, `accept`, `reject`, `terminate`, `call_held`, `signal`, `roam`, `battery`, `ring_indication`, `dialing`, `clip`, `vgm`, `vgs`, `inband_ring`, `operator`, `codec_negotiate`, and `ecnr_turn_off`. Use HF object to replace conn object for functions `bt_hfp_hf_cli`, `bt_hfp_hf_vgm`, `bt_hfp_hf_vgs`, `bt_hfp_hf_get_operator`, `bt_hfp_hf_accept`, `bt_hfp_hf_reject`, `bt_hfp_hf_terminate`, `bt_hfp_hf_hold_incoming`, `bt_hfp_hf_query_respond_hold_status`, `bt_hfp_hf_number_call`, `bt_hfp_hf_memory_dial`, `bt_hfp_hf_redial`, `bt_hfp_hf_audio_connect`, `bt_hfp_hf_select_codec`, `bt_hfp_hf_set_codecs`, and `bt_hfp_hf_turn_off_ecnr`. Add a parameter `struct bt_hfp_hf *hf` to callback `connected`. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 165 +++++++------- subsys/bluetooth/host/classic/hfp_hf.c | 251 +++++----------------- 2 files changed, 142 insertions(+), 274 deletions(-) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index 97860f1c2bd0b..391f5374f3e86 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -28,6 +28,8 @@ extern "C" { #define BT_HFP_HF_CODEC_MSBC 0x02 #define BT_HFP_HF_CODEC_LC3_SWB 0x03 +struct bt_hfp_hf; + /** @brief HFP profile application callback */ struct bt_hfp_hf_cb { /** HF connected callback to application @@ -36,32 +38,35 @@ struct bt_hfp_hf_cb { * connection completes. * * @param conn Connection object. + * @param hf HFP HF object. */ - void (*connected)(struct bt_conn *conn); + void (*connected)(struct bt_conn *conn, struct bt_hfp_hf *hf); /** HF disconnected callback to application * * If this callback is provided it will be called whenever the * connection gets disconnected, including when a connection gets * rejected or cancelled or any error in SLC establishment. + * And the HFP HF object will be freed after the registered + * callback `disconnected` returned. * - * @param conn Connection object. + * @param hf HFP HF object. */ - void (*disconnected)(struct bt_conn *conn); + void (*disconnected)(struct bt_hfp_hf *hf); /** HF SCO/eSCO connected Callback * * If this callback is provided it will be called whenever the * SCO/eSCO connection completes. * - * @param conn Connection object. + * @param hf HFP HF object. * @param sco_conn SCO/eSCO Connection object. */ - void (*sco_connected)(struct bt_conn *conn, struct bt_conn *sco_conn); + void (*sco_connected)(struct bt_hfp_hf *hf, struct bt_conn *sco_conn); /** HF SCO/eSCO disconnected Callback * * If this callback is provided it will be called whenever the * SCO/eSCO connection gets disconnected. * - * @param conn Connection object. + * @param conn SCO/eSCO Connection object. * @param reason BT_HCI_ERR_* reason for the disconnection. */ void (*sco_disconnected)(struct bt_conn *sco_conn, uint8_t reason); @@ -69,114 +74,114 @@ struct bt_hfp_hf_cb { * * This callback provides service indicator value to the application * - * @param conn Connection object. + * @param hf HFP HF object. * @param value service indicator value received from the AG. */ - void (*service)(struct bt_conn *conn, uint32_t value); + void (*service)(struct bt_hfp_hf *hf, uint32_t value); /** HF call outgoing Callback * * This callback provides the outgoing call status to * the application. * - * @param conn Connection object. + * @param hf HFP HF object. */ - void (*outgoing)(struct bt_conn *conn); + void (*outgoing)(struct bt_hfp_hf *hf); /** HF call outgoing call is ringing Callback * * This callback provides the outgoing call is ringing * status to the application. * - * @param conn Connection object. + * @param hf HFP HF object. */ - void (*remote_ringing)(struct bt_conn *conn); + void (*remote_ringing)(struct bt_hfp_hf *hf); /** HF call incoming Callback * * This callback provides the incoming call status to * the application. * - * @param conn Connection object. + * @param hf HFP HF object. */ - void (*incoming)(struct bt_conn *conn); + void (*incoming)(struct bt_hfp_hf *hf); /** HF incoming call on hold Callback * * This callback provides the incoming call on hold status to * the application. * - * @param conn Connection object. + * @param hf HFP HF object. */ - void (*incoming_held)(struct bt_conn *conn); + void (*incoming_held)(struct bt_hfp_hf *hf); /** HF call accept Callback * * This callback provides the incoming/outgoing call active * status to the application. * - * @param conn Connection object. + * @param hf HFP HF object. */ - void (*accept)(struct bt_conn *conn); + void (*accept)(struct bt_hfp_hf *hf); /** HF call reject Callback * * This callback provides the incoming/outgoing call reject * status to the application. * - * @param conn Connection object. + * @param hf HFP HF object. */ - void (*reject)(struct bt_conn *conn); + void (*reject)(struct bt_hfp_hf *hf); /** HF call terminate Callback * * This callback provides the incoming/outgoing call terminate * status to the application. * - * @param conn Connection object. + * @param hf HFP HF object. */ - void (*terminate)(struct bt_conn *conn); + void (*terminate)(struct bt_hfp_hf *hf); /** HF indicator Callback * * This callback provides call held indicator value to the application * - * @param conn Connection object. + * @param hf HFP HF object. * @param value call held indicator value received from the AG. */ - void (*call_held)(struct bt_conn *conn, uint32_t value); + void (*call_held)(struct bt_hfp_hf *hf, uint32_t value); /** HF indicator Callback * * This callback provides signal indicator value to the application * - * @param conn Connection object. + * @param hf HFP HF object. * @param value signal indicator value received from the AG. */ - void (*signal)(struct bt_conn *conn, uint32_t value); + void (*signal)(struct bt_hfp_hf *hf, uint32_t value); /** HF indicator Callback * * This callback provides roaming indicator value to the application * - * @param conn Connection object. + * @param hf HFP HF object. * @param value roaming indicator value received from the AG. */ - void (*roam)(struct bt_conn *conn, uint32_t value); + void (*roam)(struct bt_hfp_hf *hf, uint32_t value); /** HF indicator Callback * * This callback battery service indicator value to the application * - * @param conn Connection object. + * @param hf HFP HF object. * @param value battery indicator value received from the AG. */ - void (*battery)(struct bt_conn *conn, uint32_t value); + void (*battery)(struct bt_hfp_hf *hf, uint32_t value); /** HF incoming call Ring indication callback to application * * If this callback is provided it will be called whenever there * is an incoming call. * - * @param conn Connection object. + * @param hf HFP HF object. */ - void (*ring_indication)(struct bt_conn *conn); + void (*ring_indication)(struct bt_hfp_hf *hf); /** HF call dialing Callback * * This callback provides call dialing result to the application. * - * @param conn Connection object. + * @param hf HFP HF object. * @param err Result of calling dialing. */ - void (*dialing)(struct bt_conn *conn, int err); + void (*dialing)(struct bt_hfp_hf *hf, int err); /** HF calling line identification notification callback to application * * If this callback is provided it will be called whenever there @@ -185,11 +190,11 @@ struct bt_hfp_hf_cb { * result code +CLIP will be ignored. And the callback will not be * notified. * - * @param conn Connection object. + * @param hf HFP HF object. * @param number Notified phone number. * @param type Specify the format of the phone number. */ - void (*clip)(struct bt_conn *conn, char *number, uint8_t type); + void (*clip)(struct bt_hfp_hf *hf, char *number, uint8_t type); /** HF microphone gain notification callback to application * * If this callback is provided it will be called whenever there @@ -198,10 +203,10 @@ struct bt_hfp_hf_cb { * result code +VGM will be ignored. And the callback will not be * notified. * - * @param conn Connection object. + * @param hf HFP HF object. * @param gain Microphone gain. */ - void (*vgm)(struct bt_conn *conn, uint8_t gain); + void (*vgm)(struct bt_hfp_hf *hf, uint8_t gain); /** HF speaker gain notification callback to application * * If this callback is provided it will be called whenever there @@ -210,10 +215,10 @@ struct bt_hfp_hf_cb { * result code +VGS will be ignored. And the callback will not be * notified. * - * @param conn Connection object. + * @param hf HFP HF object. * @param gain Speaker gain. */ - void (*vgs)(struct bt_conn *conn, uint8_t gain); + void (*vgs)(struct bt_hfp_hf *hf, uint8_t gain); /** HF in-band ring tone notification callback to application * * If this callback is provided it will be called whenever there @@ -221,10 +226,10 @@ struct bt_hfp_hf_cb { * indicate to the HF that the in-band ring tone setting * has been locally changed. * - * @param conn Connection object. + * @param hf HFP HF object. * @param inband In-band ring tone status from the AG. */ - void (*inband_ring)(struct bt_conn *conn, bool inband); + void (*inband_ring)(struct bt_hfp_hf *hf, bool inband); /** HF network operator notification callback to application * * If this callback is provided it will be called whenever there @@ -232,7 +237,7 @@ struct bt_hfp_hf_cb { * response the AT+COPS? command issued by the HF by calling * function `bt_hfp_hf_get_operator`. * - * @param conn Connection object. + * @param hf HFP HF object. * @param mode Current mode. * @param format Format of the `operator` parameter string. * It should be zero. @@ -240,7 +245,7 @@ struct bt_hfp_hf_cb { * representing the name of the network * operator. */ - void (*operator)(struct bt_conn *conn, uint8_t mode, uint8_t format, char *operator); + void (*operator)(struct bt_hfp_hf *hf, uint8_t mode, uint8_t format, char *operator); /** Codec negotiate callback * * If this callback is provided it will be called whenever the @@ -260,10 +265,10 @@ struct bt_hfp_hf_cb { * unsolicited result code +BCS will be ignored. And the callback * will not be notified. * - * @param conn Connection object. + * @param hf HFP HF object. * @param id Negotiated Codec ID. */ - void (*codec_negotiate)(struct bt_conn *conn, uint8_t id); + void (*codec_negotiate)(struct bt_hfp_hf *hf, uint8_t id); /** HF ECNR turns off callback * * If this callback is provided it will be called whenever the @@ -271,10 +276,10 @@ struct bt_hfp_hf_cb { * If @kconfig{CONFIG_BT_HFP_HF_ECNR} is not enabled, the * callback will not be notified. * - * @param conn Connection object. + * @param hf HFP HF object. * @param err The result of request. */ - void (*ecnr_turn_off)(struct bt_conn *conn, int err); + void (*ecnr_turn_off)(struct bt_hfp_hf *hf, int err); }; /** @brief Register HFP HF profile @@ -296,12 +301,12 @@ int bt_hfp_hf_register(struct bt_hfp_hf_cb *cb); * If @kconfig{CONFIG_BT_HFP_HF_CLI} is not enabled, the error `-ENOTSUP` will * be returned if the function called. * - * @param conn Connection object. + * @param hf HFP HF object. * @param enable Enable/disable CLI. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_hf_cli(struct bt_conn *conn, bool enable); +int bt_hfp_hf_cli(struct bt_hfp_hf *hf, bool enable); /** @brief Handsfree HF report Gain of Microphone (VGM) * @@ -319,12 +324,12 @@ int bt_hfp_hf_cli(struct bt_conn *conn, bool enable); * function. Then after the HF connection callback returned, VGM gain * will be sent to HFP AG. * - * @param conn Connection object. + * @param hf HFP HF object. * @param gain Gain of microphone. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_hf_vgm(struct bt_conn *conn, uint8_t gain); +int bt_hfp_hf_vgm(struct bt_hfp_hf *hf, uint8_t gain); /** @brief Handsfree HF report Gain of Speaker (VGS) * @@ -342,23 +347,23 @@ int bt_hfp_hf_vgm(struct bt_conn *conn, uint8_t gain); * function. Then after the HF connection callback returned, VGS gain * will be sent to HFP AG. * - * @param conn Connection object. + * @param hf HFP HF object. * @param gain Gain of speaker. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_hf_vgs(struct bt_conn *conn, uint8_t gain); +int bt_hfp_hf_vgs(struct bt_hfp_hf *hf, uint8_t gain); /** @brief Handsfree HF requests currently selected operator * * Send the AT+COPS? (Read) command to find the currently * selected operator. * - * @param conn Connection object. + * @param hf HFP HF object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_hf_get_operator(struct bt_conn *conn); +int bt_hfp_hf_get_operator(struct bt_hfp_hf *hf); /** @brief Handsfree HF accept the incoming call * @@ -366,11 +371,11 @@ int bt_hfp_hf_get_operator(struct bt_conn *conn); * OR, send the AT+BTRH=1 command to accept a held incoming * call. * - * @param conn Connection object. + * @param hf HFP HF object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_hf_accept(struct bt_conn *conn); +int bt_hfp_hf_accept(struct bt_hfp_hf *hf); /** @brief Handsfree HF reject the incoming call * @@ -378,21 +383,21 @@ int bt_hfp_hf_accept(struct bt_conn *conn); * OR, send the AT+BTRH=2 command to reject a held incoming * call. * - * @param conn Connection object. + * @param hf HFP HF object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_hf_reject(struct bt_conn *conn); +int bt_hfp_hf_reject(struct bt_hfp_hf *hf); /** @brief Handsfree HF terminate the incoming call * * Send the AT+CHUP command to terminate the incoming call. * - * @param conn Connection object. + * @param hf HFP HF object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_hf_terminate(struct bt_conn *conn); +int bt_hfp_hf_terminate(struct bt_hfp_hf *hf); /** @brief Handsfree HF put the incoming call on hold * @@ -400,11 +405,11 @@ int bt_hfp_hf_terminate(struct bt_conn *conn); * If the incoming call has been held, the callback `on_hold` will * be triggered. * - * @param conn Connection object. + * @param hf HFP HF object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_hf_hold_incoming(struct bt_conn *conn); +int bt_hfp_hf_hold_incoming(struct bt_hfp_hf *hf); /** @brief Handsfree HF query respond and hold status of AG * @@ -412,11 +417,11 @@ int bt_hfp_hf_hold_incoming(struct bt_conn *conn); * The status respond and hold will be notified through callback * `on_hold`. * - * @param conn Connection object. + * @param hf HFP HF object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_hf_query_respond_hold_status(struct bt_conn *conn); +int bt_hfp_hf_query_respond_hold_status(struct bt_hfp_hf *hf); /** @brief Handsfree HF phone number call * @@ -426,12 +431,12 @@ int bt_hfp_hf_query_respond_hold_status(struct bt_conn *conn); * The result of the command will be notified through the callback * `dialing`. * - * @param conn Connection object. + * @param hf HFP HF object. * @param number Phone number. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_hf_number_call(struct bt_conn *conn, const char *number); +int bt_hfp_hf_number_call(struct bt_hfp_hf *hf, const char *number); /** @brief Handsfree HF memory dialing call * @@ -441,12 +446,12 @@ int bt_hfp_hf_number_call(struct bt_conn *conn, const char *number); * The result of the command will be notified through the callback * `dialing`. * - * @param conn Connection object. + * @param hf HFP HF object. * @param location Memory location. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_hf_memory_dial(struct bt_conn *conn, const char *location); +int bt_hfp_hf_memory_dial(struct bt_hfp_hf *hf, const char *location); /** @brief Handsfree HF redial last number * @@ -456,11 +461,11 @@ int bt_hfp_hf_memory_dial(struct bt_conn *conn, const char *location); * The result of the command will be notified through the callback * `dialing`. * - * @param conn Connection object. + * @param hf HFP HF object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_hf_redial(struct bt_conn *conn); +int bt_hfp_hf_redial(struct bt_hfp_hf *hf); /** @brief Handsfree HF setup audio connection * @@ -468,11 +473,11 @@ int bt_hfp_hf_redial(struct bt_conn *conn); * If @kconfig{CONFIG_BT_HFP_HF_CODEC_NEG} is not enabled, the error * `-ENOTSUP` will be returned if the function called. * - * @param conn Connection object. + * @param hf HFP HF object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_hf_audio_connect(struct bt_conn *conn); +int bt_hfp_hf_audio_connect(struct bt_hfp_hf *hf); /** @brief Handsfree HF set selected codec id * @@ -485,12 +490,12 @@ int bt_hfp_hf_audio_connect(struct bt_conn *conn); * If @kconfig{CONFIG_BT_HFP_HF_CODEC_NEG} is not enabled, the error * `-ENOTSUP` will be returned if the function called. * - * @param conn Connection object. + * @param hf HFP HF object. * @param codec_id Selected codec id. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_hf_select_codec(struct bt_conn *conn, uint8_t codec_id); +int bt_hfp_hf_select_codec(struct bt_hfp_hf *hf, uint8_t codec_id); /** @brief Handsfree HF set supported codec ids * @@ -499,12 +504,12 @@ int bt_hfp_hf_select_codec(struct bt_conn *conn, uint8_t codec_id); * If @kconfig{CONFIG_BT_HFP_HF_CODEC_NEG} is not enabled, the error * `-ENOTSUP` will be returned if the function called. * - * @param conn Connection object. + * @param hf HFP HF object. * @param codec_ids Supported codec IDs. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_hf_set_codecs(struct bt_conn *conn, uint8_t codec_ids); +int bt_hfp_hf_set_codecs(struct bt_hfp_hf *hf, uint8_t codec_ids); /** @brief Handsfree HF turns off AG's EC and NR * @@ -514,11 +519,11 @@ int bt_hfp_hf_set_codecs(struct bt_conn *conn, uint8_t codec_ids); * If @kconfig{CONFIG_BT_HFP_HF_ECNR} is not enabled, the error * `-ENOTSUP` will be returned if the function called. * - * @param conn Connection object. + * @param hf HFP HF object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_hf_turn_off_ecnr(struct bt_conn *conn); +int bt_hfp_hf_turn_off_ecnr(struct bt_hfp_hf *hf); #ifdef __cplusplus } diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 0dd7661775871..631cdb7875730 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -393,27 +393,25 @@ int cind_resp(struct at_client *hf_at, struct net_buf *buf) static void ag_indicator_handle_call(struct bt_hfp_hf *hf, uint32_t value) { - struct bt_conn *conn = hf->acl; - atomic_set_bit_to(hf->flags, BT_HFP_HF_FLAG_ACTIVE, value); if (value) { if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING_HELD)) { if (bt_hf->incoming_held) { - bt_hf->incoming_held(conn); + bt_hf->incoming_held(hf); } } else { if (bt_hf->accept) { - bt_hf->accept(conn); + bt_hf->accept(hf); } } } else { if (atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING_HELD)) { if (bt_hf->reject) { - bt_hf->reject(conn); + bt_hf->reject(hf); } } else { if (bt_hf->terminate) { - bt_hf->terminate(conn); + bt_hf->terminate(hf); } } } @@ -423,7 +421,6 @@ void ag_indicator_handle_values(struct at_client *hf_at, uint32_t index, uint32_t value) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - struct bt_conn *conn = hf->acl; LOG_DBG("Index :%u, Value :%u", index, value); @@ -441,7 +438,7 @@ void ag_indicator_handle_values(struct at_client *hf_at, uint32_t index, switch (hf->ind_table[index]) { case HF_SERVICE_IND: if (bt_hf->service) { - bt_hf->service(conn, value); + bt_hf->service(hf, value); } break; case HF_CALL_IND: @@ -453,36 +450,36 @@ void ag_indicator_handle_values(struct at_client *hf_at, uint32_t index, if (value == BT_HFP_CALL_SETUP_INCOMING) { if (bt_hf->incoming) { - bt_hf->incoming(conn); + bt_hf->incoming(hf); } } else if (value == BT_HFP_CALL_SETUP_OUTGOING) { if (bt_hf->outgoing) { - bt_hf->outgoing(conn); + bt_hf->outgoing(hf); } } else if (value == BT_HFP_CALL_SETUP_REMOTE_ALERTING) { if (bt_hf->remote_ringing) { - bt_hf->remote_ringing(conn); + bt_hf->remote_ringing(hf); } } break; case HF_CALL_HELD_IND: if (bt_hf->call_held) { - bt_hf->call_held(conn, value); + bt_hf->call_held(hf, value); } break; case HF_SINGNAL_IND: if (bt_hf->signal) { - bt_hf->signal(conn, value); + bt_hf->signal(hf, value); } break; case HF_ROAM_IND: if (bt_hf->roam) { - bt_hf->roam(conn, value); + bt_hf->roam(hf, value); } break; case HF_BATTERY_IND: if (bt_hf->battery) { - bt_hf->battery(conn, value); + bt_hf->battery(hf, value); } break; default: @@ -557,10 +554,9 @@ int ciev_handle(struct at_client *hf_at) int ring_handle(struct at_client *hf_at) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - struct bt_conn *conn = hf->acl; if (bt_hf->ring_indication) { - bt_hf->ring_indication(conn); + bt_hf->ring_indication(hf); } return 0; @@ -570,7 +566,6 @@ int ring_handle(struct at_client *hf_at) int clip_handle(struct at_client *hf_at) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - struct bt_conn *conn = hf->acl; char *number; uint32_t type; int err; @@ -585,7 +580,7 @@ int clip_handle(struct at_client *hf_at) } if (bt_hf->clip) { - bt_hf->clip(conn, number, (uint8_t)type); + bt_hf->clip(hf, number, (uint8_t)type); } return 0; @@ -596,7 +591,6 @@ int clip_handle(struct at_client *hf_at) int vgm_handle(struct at_client *hf_at) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - struct bt_conn *conn = hf->acl; uint32_t gain; int err; @@ -612,7 +606,7 @@ int vgm_handle(struct at_client *hf_at) } if (bt_hf->vgm) { - bt_hf->vgm(conn, (uint8_t)gain); + bt_hf->vgm(hf, (uint8_t)gain); } return 0; @@ -621,7 +615,6 @@ int vgm_handle(struct at_client *hf_at) int vgs_handle(struct at_client *hf_at) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - struct bt_conn *conn = hf->acl; uint32_t gain; int err; @@ -637,7 +630,7 @@ int vgs_handle(struct at_client *hf_at) } if (bt_hf->vgs) { - bt_hf->vgs(conn, (uint8_t)gain); + bt_hf->vgs(hf, (uint8_t)gain); } return 0; @@ -647,7 +640,6 @@ int vgs_handle(struct at_client *hf_at) int bsir_handle(struct at_client *hf_at) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - struct bt_conn *conn = hf->acl; uint32_t inband; int err; @@ -663,7 +655,7 @@ int bsir_handle(struct at_client *hf_at) } if (bt_hf->inband_ring) { - bt_hf->inband_ring(conn, (bool)inband); + bt_hf->inband_ring(hf, (bool)inband); } return 0; @@ -673,7 +665,6 @@ int bsir_handle(struct at_client *hf_at) int bcs_handle(struct at_client *hf_at) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - struct bt_conn *conn = hf->acl; uint32_t codec_id; int err; @@ -685,18 +676,18 @@ int bcs_handle(struct at_client *hf_at) if (!(hf->hf_codec_ids & BIT(codec_id))) { LOG_ERR("Invalid codec id %d", codec_id); - err = bt_hfp_hf_set_codecs(conn, hf->hf_codec_ids); + err = bt_hfp_hf_set_codecs(hf, hf->hf_codec_ids); return err; } atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_CODEC_CONN); if (bt_hf->codec_negotiate) { - bt_hf->codec_negotiate(conn, codec_id); + bt_hf->codec_negotiate(hf, codec_id); return 0; } - err = bt_hfp_hf_select_codec(conn, codec_id); + err = bt_hfp_hf_select_codec(hf, codec_id); return err; } #endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ @@ -720,7 +711,7 @@ static int btrh_handle(struct at_client *hf_at) if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_ACTIVE) && atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING_HELD)) { if (bt_hf && bt_hf->accept) { - bt_hf->accept(hf->acl); + bt_hf->accept(hf); } } } else if (on_hold == BT_HFP_BTRH_REJECTED) { @@ -1003,7 +994,7 @@ static void slc_completed(struct at_client *hf_at) struct bt_conn *conn = hf->acl; if (bt_hf->connected) { - bt_hf->connected(conn); + bt_hf->connected(conn, hf); } atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED); @@ -1110,21 +1101,6 @@ int hf_slc_establish(struct bt_hfp_hf *hf) return 0; } -static struct bt_hfp_hf *bt_hfp_hf_lookup_bt_conn(struct bt_conn *conn) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(bt_hfp_hf_pool); i++) { - struct bt_hfp_hf *hf = &bt_hfp_hf_pool[i]; - - if (hf->acl == conn) { - return hf; - } - } - - return NULL; -} - #if defined(CONFIG_BT_HFP_HF_CLI) static int cli_finish(struct at_client *hf_at, enum at_result result, enum at_cme cme_err) @@ -1138,20 +1114,13 @@ static int cli_finish(struct at_client *hf_at, enum at_result result, } #endif /* CONFIG_BT_HFP_HF_CLI */ -int bt_hfp_hf_cli(struct bt_conn *conn, bool enable) +int bt_hfp_hf_cli(struct bt_hfp_hf *hf, bool enable) { #if defined(CONFIG_BT_HFP_HF_CLI) - struct bt_hfp_hf *hf; int err; LOG_DBG(""); - if (!conn) { - LOG_ERR("Invalid connection"); - return -ENOTCONN; - } - - hf = bt_hfp_hf_lookup_bt_conn(conn); if (!hf) { LOG_ERR("No HF connection found"); return -ENOTCONN; @@ -1186,30 +1155,23 @@ static int vgm_finish(struct at_client *hf_at, enum at_result result, } #endif /* CONFIG_BT_HFP_HF_VOLUME */ -int bt_hfp_hf_vgm(struct bt_conn *conn, uint8_t gain) +int bt_hfp_hf_vgm(struct bt_hfp_hf *hf, uint8_t gain) { #if defined(CONFIG_BT_HFP_HF_VOLUME) - struct bt_hfp_hf *hf; int err; LOG_DBG(""); - if (gain > BT_HFP_HF_VGM_GAIN_MAX) { - LOG_ERR("Invalid gain %d>%d", gain, BT_HFP_HF_VGM_GAIN_MAX); - return -EINVAL; - } - - if (!conn) { - LOG_ERR("Invalid connection"); - return -ENOTCONN; - } - - hf = bt_hfp_hf_lookup_bt_conn(conn); if (!hf) { LOG_ERR("No HF connection found"); return -ENOTCONN; } + if (gain > BT_HFP_HF_VGM_GAIN_MAX) { + LOG_ERR("Invalid gain %d>%d", gain, BT_HFP_HF_VGM_GAIN_MAX); + return -EINVAL; + } + hf->vgm = gain; if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { @@ -1240,30 +1202,23 @@ static int vgs_finish(struct at_client *hf_at, enum at_result result, } #endif /* CONFIG_BT_HFP_HF_VOLUME */ -int bt_hfp_hf_vgs(struct bt_conn *conn, uint8_t gain) +int bt_hfp_hf_vgs(struct bt_hfp_hf *hf, uint8_t gain) { #if defined(CONFIG_BT_HFP_HF_VOLUME) - struct bt_hfp_hf *hf; int err; LOG_DBG(""); - if (gain > BT_HFP_HF_VGM_GAIN_MAX) { - LOG_ERR("Invalid gain %d>%d", gain, BT_HFP_HF_VGM_GAIN_MAX); - return -EINVAL; - } - - if (!conn) { - LOG_ERR("Invalid connection"); - return -ENOTCONN; - } - - hf = bt_hfp_hf_lookup_bt_conn(conn); if (!hf) { LOG_ERR("No HF connection found"); return -ENOTCONN; } + if (gain > BT_HFP_HF_VGM_GAIN_MAX) { + LOG_ERR("Invalid gain %d>%d", gain, BT_HFP_HF_VGM_GAIN_MAX); + return -EINVAL; + } + hf->vgs = gain; if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { @@ -1304,7 +1259,7 @@ static int cops_handle(struct at_client *hf_at) operator = at_get_string(hf_at); if (bt_hf && bt_hf->operator) { - bt_hf->operator(hf->acl, (uint8_t)mode, (uint8_t)format, operator); + bt_hf->operator(hf, (uint8_t)mode, (uint8_t)format, operator); } return 0; @@ -1336,19 +1291,12 @@ static int cops_finish(struct at_client *hf_at, enum at_result result, return 0; } -int bt_hfp_hf_get_operator(struct bt_conn *conn) +int bt_hfp_hf_get_operator(struct bt_hfp_hf *hf) { - struct bt_hfp_hf *hf; int err; LOG_DBG(""); - if (!conn) { - LOG_ERR("Invalid connection"); - return -ENOTCONN; - } - - hf = bt_hfp_hf_lookup_bt_conn(conn); if (!hf) { LOG_ERR("No HF connection found"); return -ENOTCONN; @@ -1386,19 +1334,12 @@ static int btrh_finish(struct at_client *hf_at, enum at_result result, return 0; } -int bt_hfp_hf_accept(struct bt_conn *conn) +int bt_hfp_hf_accept(struct bt_hfp_hf *hf) { - struct bt_hfp_hf *hf; int err; LOG_DBG(""); - if (!conn) { - LOG_ERR("Invalid connection"); - return -ENOTCONN; - } - - hf = bt_hfp_hf_lookup_bt_conn(conn); if (!hf) { LOG_ERR("No HF connection found"); return -ENOTCONN; @@ -1435,19 +1376,12 @@ static int chup_finish(struct at_client *hf_at, enum at_result result, return 0; } -int bt_hfp_hf_reject(struct bt_conn *conn) +int bt_hfp_hf_reject(struct bt_hfp_hf *hf) { - struct bt_hfp_hf *hf; int err; LOG_DBG(""); - if (!conn) { - LOG_ERR("Invalid connection"); - return -ENOTCONN; - } - - hf = bt_hfp_hf_lookup_bt_conn(conn); if (!hf) { LOG_ERR("No HF connection found"); return -ENOTCONN; @@ -1474,19 +1408,12 @@ int bt_hfp_hf_reject(struct bt_conn *conn) return -EINVAL; } -int bt_hfp_hf_terminate(struct bt_conn *conn) +int bt_hfp_hf_terminate(struct bt_hfp_hf *hf) { - struct bt_hfp_hf *hf; int err; LOG_DBG(""); - if (!conn) { - LOG_ERR("Invalid connection"); - return -ENOTCONN; - } - - hf = bt_hfp_hf_lookup_bt_conn(conn); if (!hf) { LOG_ERR("No HF connection found"); return -ENOTCONN; @@ -1500,19 +1427,12 @@ int bt_hfp_hf_terminate(struct bt_conn *conn) return err; } -int bt_hfp_hf_hold_incoming(struct bt_conn *conn) +int bt_hfp_hf_hold_incoming(struct bt_hfp_hf *hf) { - struct bt_hfp_hf *hf; int err; LOG_DBG(""); - if (!conn) { - LOG_ERR("Invalid connection"); - return -ENOTCONN; - } - - hf = bt_hfp_hf_lookup_bt_conn(conn); if (!hf) { LOG_ERR("No HF connection found"); return -ENOTCONN; @@ -1541,25 +1461,18 @@ static int query_btrh_finish(struct at_client *hf_at, enum at_result result, if (atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_QUERY_HOLD) && bt_hf->incoming_held) { - bt_hf->incoming_held(hf->acl); + bt_hf->incoming_held(hf); } return 0; } -int bt_hfp_hf_query_respond_hold_status(struct bt_conn *conn) +int bt_hfp_hf_query_respond_hold_status(struct bt_hfp_hf *hf) { - struct bt_hfp_hf *hf; int err; LOG_DBG(""); - if (!conn) { - LOG_ERR("Invalid connection"); - return -ENOTCONN; - } - - hf = bt_hfp_hf_lookup_bt_conn(conn); if (!hf) { LOG_ERR("No HF connection found"); return -ENOTCONN; @@ -1633,24 +1546,17 @@ static int atd_finish(struct at_client *hf_at, enum at_result result, } if (bt_hf && bt_hf->dialing) { - bt_hf->dialing(hf->acl, err); + bt_hf->dialing(hf, err); } return 0; } -int bt_hfp_hf_number_call(struct bt_conn *conn, const char *number) +int bt_hfp_hf_number_call(struct bt_hfp_hf *hf, const char *number) { - struct bt_hfp_hf *hf; int err; LOG_DBG(""); - if (!conn) { - LOG_ERR("Invalid connection"); - return -ENOTCONN; - } - - hf = bt_hfp_hf_lookup_bt_conn(conn); if (!hf) { LOG_ERR("No HF connection found"); return -ENOTCONN; @@ -1669,19 +1575,12 @@ int bt_hfp_hf_number_call(struct bt_conn *conn, const char *number) return err; } -int bt_hfp_hf_memory_dial(struct bt_conn *conn, const char *location) +int bt_hfp_hf_memory_dial(struct bt_hfp_hf *hf, const char *location) { - struct bt_hfp_hf *hf; int err; LOG_DBG(""); - if (!conn) { - LOG_ERR("Invalid connection"); - return -ENOTCONN; - } - - hf = bt_hfp_hf_lookup_bt_conn(conn); if (!hf) { LOG_ERR("No HF connection found"); return -ENOTCONN; @@ -1721,24 +1620,17 @@ static int bldn_finish(struct at_client *hf_at, enum at_result result, } if (bt_hf && bt_hf->dialing) { - bt_hf->dialing(hf->acl, err); + bt_hf->dialing(hf, err); } return 0; } -int bt_hfp_hf_redial(struct bt_conn *conn) +int bt_hfp_hf_redial(struct bt_hfp_hf *hf) { - struct bt_hfp_hf *hf; int err; LOG_DBG(""); - if (!conn) { - LOG_ERR("Invalid connection"); - return -ENOTCONN; - } - - hf = bt_hfp_hf_lookup_bt_conn(conn); if (!hf) { LOG_ERR("No HF connection found"); return -ENOTCONN; @@ -1769,20 +1661,13 @@ static int bcc_finish(struct at_client *hf_at, enum at_result result, } #endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ -int bt_hfp_hf_audio_connect(struct bt_conn *conn) +int bt_hfp_hf_audio_connect(struct bt_hfp_hf *hf) { #if defined(CONFIG_BT_HFP_HF_CODEC_NEG) - struct bt_hfp_hf *hf; int err; LOG_DBG(""); - if (!conn) { - LOG_ERR("Invalid connection"); - return -ENOTCONN; - } - - hf = bt_hfp_hf_lookup_bt_conn(conn); if (!hf) { LOG_ERR("No HF connection found"); return -ENOTCONN; @@ -1816,19 +1701,11 @@ static int bcs_finish(struct at_client *hf_at, enum at_result result, } #endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ -int bt_hfp_hf_select_codec(struct bt_conn *conn, uint8_t codec_id) +int bt_hfp_hf_select_codec(struct bt_hfp_hf *hf, uint8_t codec_id) { #if defined(CONFIG_BT_HFP_HF_CODEC_NEG) - struct bt_hfp_hf *hf; - LOG_DBG(""); - if (!conn) { - LOG_ERR("Invalid connection"); - return -ENOTCONN; - } - - hf = bt_hfp_hf_lookup_bt_conn(conn); if (!hf) { LOG_ERR("No HF connection found"); return -ENOTCONN; @@ -1862,20 +1739,13 @@ static int bac_finish(struct at_client *hf_at, enum at_result result, } #endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ -int bt_hfp_hf_set_codecs(struct bt_conn *conn, uint8_t codec_ids) +int bt_hfp_hf_set_codecs(struct bt_hfp_hf *hf, uint8_t codec_ids) { #if defined(CONFIG_BT_HFP_HF_CODEC_NEG) - struct bt_hfp_hf *hf; char ids[sizeof(hf->hf_codec_ids)*2*8 + 1]; LOG_DBG(""); - if (!conn) { - LOG_ERR("Invalid connection"); - return -ENOTCONN; - } - - hf = bt_hfp_hf_lookup_bt_conn(conn); if (!hf) { LOG_ERR("No HF connection found"); return -ENOTCONN; @@ -1914,25 +1784,17 @@ static int nrec_finish(struct at_client *hf_at, enum at_result result, } if (bt_hf && bt_hf->ecnr_turn_off) { - bt_hf->ecnr_turn_off(hf->acl, err); + bt_hf->ecnr_turn_off(hf, err); } return 0; } #endif /* CONFIG_BT_HFP_HF_ECNR */ -int bt_hfp_hf_turn_off_ecnr(struct bt_conn *conn) +int bt_hfp_hf_turn_off_ecnr(struct bt_hfp_hf *hf) { #if defined(CONFIG_BT_HFP_HF_ECNR) - struct bt_hfp_hf *hf; - LOG_DBG(""); - if (!conn) { - LOG_ERR("Invalid connection"); - return -ENOTCONN; - } - - hf = bt_hfp_hf_lookup_bt_conn(conn); if (!hf) { LOG_ERR("No HF connection found"); return -ENOTCONN; @@ -1967,11 +1829,10 @@ static void hfp_hf_connected(struct bt_rfcomm_dlc *dlc) static void hfp_hf_disconnected(struct bt_rfcomm_dlc *dlc) { struct bt_hfp_hf *hf = CONTAINER_OF(dlc, struct bt_hfp_hf, rfcomm_dlc); - struct bt_conn *conn = hf->acl; LOG_DBG("hf disconnected!"); if (bt_hf->disconnected) { - bt_hf->disconnected(conn); + bt_hf->disconnected(hf); } } @@ -2043,8 +1904,10 @@ static int hfp_hf_accept(struct bt_conn *conn, struct bt_rfcomm_server *server, static void hfp_hf_sco_connected(struct bt_sco_chan *chan) { + struct bt_hfp_hf *hf = CONTAINER_OF(chan, struct bt_hfp_hf, chan); + if ((bt_hf != NULL) && (bt_hf->sco_connected)) { - bt_hf->sco_connected(chan->sco->sco.acl, chan->sco); + bt_hf->sco_connected(hf, chan->sco); } } From 7f148526cb7cdc3d64c17db13d94205c84de1f26 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Thu, 29 Aug 2024 16:58:59 +0800 Subject: [PATCH 39/76] Bluetooth: HFP_AG: Move CLCC status definitions Move CLCC status definitions from hfp_ag_internal.h to hfp_internal.h. And change name from HFP_AG_CLCC_STATUS_XXX to BT_HFP_CLCC_STATUS_XXX. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/hfp_ag.c | 18 +++++++++--------- .../bluetooth/host/classic/hfp_ag_internal.h | 17 ----------------- subsys/bluetooth/host/classic/hfp_internal.h | 15 +++++++++++++++ 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index f63de6459fd4d..29a767e49dc86 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -1779,7 +1779,7 @@ static int bt_hfp_ag_chup_handler(struct bt_hfp_ag *ag, struct net_buf *buf) static uint8_t bt_hfp_get_call_state(struct bt_hfp_ag_call *call) { - uint8_t status = HFP_AG_CLCC_STATUS_INVALID; + uint8_t status = 0; struct bt_hfp_ag *ag = call->ag; hfp_ag_lock(ag); @@ -1792,27 +1792,27 @@ static uint8_t bt_hfp_get_call_state(struct bt_hfp_ag_call *call) } break; case BT_HFP_CALL_OUTGOING: - status = HFP_AG_CLCC_STATUS_DIALING; + status = BT_HFP_CLCC_STATUS_DIALING; break; case BT_HFP_CALL_INCOMING: - status = HFP_AG_CLCC_STATUS_INCOMING; + status = BT_HFP_CLCC_STATUS_INCOMING; break; case BT_HFP_CALL_ALERTING: if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { - status = HFP_AG_CLCC_STATUS_WAITING; + status = BT_HFP_CLCC_STATUS_WAITING; } else { - status = HFP_AG_CLCC_STATUS_ALERTING; + status = BT_HFP_CLCC_STATUS_ALERTING; } break; case BT_HFP_CALL_ACTIVE: if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) { - status = BT_HFP_AG_CALL_INCOMING_HELD; + status = BT_HFP_CLCC_STATUS_CALL_HELD_HOLD; } else { - status = HFP_AG_CLCC_STATUS_ACTIVE; + status = BT_HFP_CLCC_STATUS_ACTIVE; } break; case BT_HFP_CALL_HOLD: - status = HFP_AG_CLCC_STATUS_HELD; + status = BT_HFP_CLCC_STATUS_HELD; break; default: break; @@ -1848,7 +1848,7 @@ static int bt_hfp_ag_clcc_handler(struct bt_hfp_ag *ag, struct net_buf *buf) dir = atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING) ? 1 : 0; status = bt_hfp_get_call_state(call); mode = 0; - mpty = (status == HFP_AG_CLCC_STATUS_ACTIVE) && (active_calls > 1) ? 1 : 0; + mpty = (status == BT_HFP_CLCC_STATUS_ACTIVE) && (active_calls > 1) ? 1 : 0; err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+CLCC:%d,%d,%d,%d,%d,\"%s\",%d\r\n", index + 1, dir, status, mode, mpty, call->number, call->type); diff --git a/subsys/bluetooth/host/classic/hfp_ag_internal.h b/subsys/bluetooth/host/classic/hfp_ag_internal.h index 5235ab5ea25bc..9d23ef4e437bf 100644 --- a/subsys/bluetooth/host/classic/hfp_ag_internal.h +++ b/subsys/bluetooth/host/classic/hfp_ag_internal.h @@ -210,20 +210,3 @@ struct bt_hfp_ag { /* TX work */ struct k_work_delayable tx_work; }; - -/* Active */ -#define HFP_AG_CLCC_STATUS_ACTIVE 0 -/* Held */ -#define HFP_AG_CLCC_STATUS_HELD 1 -/* Dialing (outgoing calls only) */ -#define HFP_AG_CLCC_STATUS_DIALING 2 -/* Alerting (outgoing calls only) */ -#define HFP_AG_CLCC_STATUS_ALERTING 3 -/* Incoming (incoming calls only) */ -#define HFP_AG_CLCC_STATUS_INCOMING 4 -/* Waiting (incoming calls only) */ -#define HFP_AG_CLCC_STATUS_WAITING 5 -/* Call held by Response and Hold */ -#define HFP_AG_CLCC_STATUS_CALL_HELD_HOLD 6 -/* Invalid status */ -#define HFP_AG_CLCC_STATUS_INVALID 0xFFU diff --git a/subsys/bluetooth/host/classic/hfp_internal.h b/subsys/bluetooth/host/classic/hfp_internal.h index 9b1363d1419eb..19a1e2c819206 100644 --- a/subsys/bluetooth/host/classic/hfp_internal.h +++ b/subsys/bluetooth/host/classic/hfp_internal.h @@ -214,3 +214,18 @@ enum hfp_hf_ag_indicators { * optional for the HF. */ #define BT_HFP_CALL_QUITE 4 + +/* Active */ +#define BT_HFP_CLCC_STATUS_ACTIVE 0 +/* Held */ +#define BT_HFP_CLCC_STATUS_HELD 1 +/* Dialing (outgoing calls only) */ +#define BT_HFP_CLCC_STATUS_DIALING 2 +/* Alerting (outgoing calls only) */ +#define BT_HFP_CLCC_STATUS_ALERTING 3 +/* Incoming (incoming calls only) */ +#define BT_HFP_CLCC_STATUS_INCOMING 4 +/* Waiting (incoming calls only) */ +#define BT_HFP_CLCC_STATUS_WAITING 5 +/* Call held by Response and Hold */ +#define BT_HFP_CLCC_STATUS_CALL_HELD_HOLD 6 From 0ae0bea53c09f0a83816e3bfd54cb321d4bdcab0 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Mon, 2 Sep 2024 11:45:24 +0800 Subject: [PATCH 40/76] Bluetooth: HFP_HF: Enable 3-way feature Add a configuration `BT_HFP_HF_3WAY_CALL` to control the feature. Add a configuration `BT_HFP_HF_MAX_CALLS` to define supported maximum calls. Add a configuration `BT_HFP_HF_ECS` to configure the Enhanced Call Status feature. Add a configuration `BT_HFP_HF_ECC` to configure the Enhanced Call Control feature. Add a structure `struct bt_hfp_hf_call` to manage the call. Add the call object to callback `outgoing`, and`incoming`. Use call object to replace AG object for callbacks `remote_ringing`, `on_hold`, `accept`, `reject`, `terminate`, `call_held`, `ring_indication`, and `clip`. Change callback `call_held` to `held`. Add callback `retrieve` for the retrieved held call. Add callback `call_waiting` to notify the application a new call is waiting. Use call object to replace AG object for function `bt_hfp_hf_accept`, `bt_hfp_hf_reject`, `bt_hfp_hf_terminate`, and `bt_hfp_hf_hold_incoming`. Add functions for 3-way feature, including `bt_hfp_hf_call_waiting_notify`, `bt_hfp_hf_release_all_held`, `bt_hfp_hf_set_udub`, `bt_hfp_hf_release_active_accept_other`, `bt_hfp_hf_hold_active_accept_other`, `bt_hfp_hf_join_conversation`, `bt_hfp_hf_explicit_call_transfer`, `bt_hfp_hf_release_specified_call`, and `bt_hfp_hf_private_consultation_mode`. Add a new function `at_get_raw_string` to get the raw string from AT response. Move definitions of AT+CLCC value from hfp_ag_internal.h to hfp_internal.h. Monitor unsolicited result code `+CCWA`. Enable `Call Waiting Notification` if the feature is supported by both side when the SLC connected. Get CHLD values supported by AG if the 3-way calling feature is supported by both side. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 209 +- subsys/bluetooth/host/classic/Kconfig | 25 + subsys/bluetooth/host/classic/at.c | 26 +- subsys/bluetooth/host/classic/at.h | 1 + subsys/bluetooth/host/classic/hfp_hf.c | 3060 +++++++++++++----- subsys/bluetooth/host/classic/hfp_internal.h | 85 +- 6 files changed, 2544 insertions(+), 862 deletions(-) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index 391f5374f3e86..a9008b9dc176e 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -30,6 +30,8 @@ extern "C" { struct bt_hfp_hf; +struct bt_hfp_hf_call; + /** @brief HFP profile application callback */ struct bt_hfp_hf_cb { /** HF connected callback to application @@ -84,64 +86,72 @@ struct bt_hfp_hf_cb { * the application. * * @param hf HFP HF object. + * @param call HFP HF call object. */ - void (*outgoing)(struct bt_hfp_hf *hf); + void (*outgoing)(struct bt_hfp_hf *hf, struct bt_hfp_hf_call *call); /** HF call outgoing call is ringing Callback * * This callback provides the outgoing call is ringing * status to the application. * - * @param hf HFP HF object. + * @param call HFP HF call object. */ - void (*remote_ringing)(struct bt_hfp_hf *hf); + void (*remote_ringing)(struct bt_hfp_hf_call *call); /** HF call incoming Callback * * This callback provides the incoming call status to * the application. * * @param hf HFP HF object. + * @param call HFP HF call object. */ - void (*incoming)(struct bt_hfp_hf *hf); + void (*incoming)(struct bt_hfp_hf *hf, struct bt_hfp_hf_call *call); /** HF incoming call on hold Callback * * This callback provides the incoming call on hold status to * the application. * - * @param hf HFP HF object. + * @param call HFP HF call object. */ - void (*incoming_held)(struct bt_hfp_hf *hf); + void (*incoming_held)(struct bt_hfp_hf_call *call); /** HF call accept Callback * * This callback provides the incoming/outgoing call active * status to the application. * - * @param hf HFP HF object. + * @param call HFP HF call object. */ - void (*accept)(struct bt_hfp_hf *hf); + void (*accept)(struct bt_hfp_hf_call *call); /** HF call reject Callback * * This callback provides the incoming/outgoing call reject * status to the application. * - * @param hf HFP HF object. + * @param call HFP HF call object. */ - void (*reject)(struct bt_hfp_hf *hf); + void (*reject)(struct bt_hfp_hf_call *call); /** HF call terminate Callback * * This callback provides the incoming/outgoing call terminate * status to the application. * - * @param hf HFP HF object. + * @param call HFP HF call object. */ - void (*terminate)(struct bt_hfp_hf *hf); - /** HF indicator Callback + void (*terminate)(struct bt_hfp_hf_call *call); + /** HF call held Callback * - * This callback provides call held indicator value to the application + * This callback provides call held to the application * - * @param hf HFP HF object. - * @param value call held indicator value received from the AG. + * @param call HFP HF call object. + */ + void (*held)(struct bt_hfp_hf_call *call); + /** HF call retrieve Callback + * + * This callback provides call retrieved to the application + * + * @param call HFP HF call object. */ - void (*call_held)(struct bt_hfp_hf *hf, uint32_t value); + void (*retrieve)(struct bt_hfp_hf_call *call); /** HF indicator Callback * * This callback provides signal indicator value to the application @@ -171,9 +181,9 @@ struct bt_hfp_hf_cb { * If this callback is provided it will be called whenever there * is an incoming call. * - * @param hf HFP HF object. + * @param call HFP HF call object. */ - void (*ring_indication)(struct bt_hfp_hf *hf); + void (*ring_indication)(struct bt_hfp_hf_call *call); /** HF call dialing Callback * * This callback provides call dialing result to the application. @@ -190,11 +200,11 @@ struct bt_hfp_hf_cb { * result code +CLIP will be ignored. And the callback will not be * notified. * - * @param hf HFP HF object. + * @param call HFP HF call object. * @param number Notified phone number. * @param type Specify the format of the phone number. */ - void (*clip)(struct bt_hfp_hf *hf, char *number, uint8_t type); + void (*clip)(struct bt_hfp_hf_call *call, char *number, uint8_t type); /** HF microphone gain notification callback to application * * If this callback is provided it will be called whenever there @@ -280,6 +290,21 @@ struct bt_hfp_hf_cb { * @param err The result of request. */ void (*ecnr_turn_off)(struct bt_hfp_hf *hf, int err); + /** HF call waiting notification callback to application + * + * If this callback is provided it will be called whenever there + * is a unsolicited result code +CCWA. + * This notification can be enabled/disabled by calling function + * `bt_hfp_hf_call_waiting_notify`. + * If @kconfig{CONFIG_BT_HFP_HF_3WAY_CALL} is not enabled, the + * unsolicited result code +CCWA will be ignored. And the callback + * will not be notified. + * + * @param call HFP HF call object. + * @param number Notified phone number. + * @param type Specify the format of the phone number. + */ + void (*call_waiting)(struct bt_hfp_hf_call *call, char *number, uint8_t type); }; /** @brief Register HFP HF profile @@ -371,11 +396,13 @@ int bt_hfp_hf_get_operator(struct bt_hfp_hf *hf); * OR, send the AT+BTRH=1 command to accept a held incoming * call. * - * @param hf HFP HF object. + * @note It cannot be used when multiple calls are ongoing. + * + * @param call HFP HF call object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_hf_accept(struct bt_hfp_hf *hf); +int bt_hfp_hf_accept(struct bt_hfp_hf_call *call); /** @brief Handsfree HF reject the incoming call * @@ -383,21 +410,25 @@ int bt_hfp_hf_accept(struct bt_hfp_hf *hf); * OR, send the AT+BTRH=2 command to reject a held incoming * call. * - * @param hf HFP HF object. + * @note It cannot be used when multiple calls are ongoing. + * + * @param call HFP HF call object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_hf_reject(struct bt_hfp_hf *hf); +int bt_hfp_hf_reject(struct bt_hfp_hf_call *call); /** @brief Handsfree HF terminate the incoming call * * Send the AT+CHUP command to terminate the incoming call. * - * @param hf HFP HF object. + * @note It cannot be used when multiple calls are ongoing. + * + * @param call HFP HF call object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_hf_terminate(struct bt_hfp_hf *hf); +int bt_hfp_hf_terminate(struct bt_hfp_hf_call *call); /** @brief Handsfree HF put the incoming call on hold * @@ -405,11 +436,13 @@ int bt_hfp_hf_terminate(struct bt_hfp_hf *hf); * If the incoming call has been held, the callback `on_hold` will * be triggered. * - * @param hf HFP HF object. + * @note It cannot be used when multiple calls are ongoing. + * + * @param call HFP HF call object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_hf_hold_incoming(struct bt_hfp_hf *hf); +int bt_hfp_hf_hold_incoming(struct bt_hfp_hf_call *call); /** @brief Handsfree HF query respond and hold status of AG * @@ -525,6 +558,124 @@ int bt_hfp_hf_set_codecs(struct bt_hfp_hf *hf, uint8_t codec_ids); */ int bt_hfp_hf_turn_off_ecnr(struct bt_hfp_hf *hf); +/** @brief Handsfree HF enable/disable call waiting notification + * + * Enable call waiting notification by sending `AT+CCWA=1`. + * Disable call waiting notification by sending `AT+CCWA=0`. + * If @kconfig{CONFIG_BT_HFP_HF_3WAY_CALL} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * @param enable Enable/disable. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_call_waiting_notify(struct bt_hfp_hf *hf, bool enable); + +/** @brief Handsfree HF release all held calls + * + * Release all held calls by sending `AT+CHLD=0`. + * If @kconfig{CONFIG_BT_HFP_HF_3WAY_CALL} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_release_all_held(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF set User Determined User Busy (UDUB) for a waiting call + * + * Set User Determined User Busy (UDUB) for a waiting call + * by sending `AT+CHLD=0`. + * If @kconfig{CONFIG_BT_HFP_HF_3WAY_CALL} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_set_udub(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF release all active calls and accept other call + * + * Release all active calls (if any exist) and accepts the other + * (held or waiting) call by sending `AT+CHLD=1`. + * If @kconfig{CONFIG_BT_HFP_HF_3WAY_CALL} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_release_active_accept_other(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF hold all active calls and accept other call + * + * Hold all active calls (if any exist) and accepts the other + * (held or waiting) call by sending `AT+CHLD=2`. + * If @kconfig{CONFIG_BT_HFP_HF_3WAY_CALL} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_hold_active_accept_other(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF add a held call to the conversation + * + * Add a held call to the conversation by sending `AT+CHLD=3`. + * If @kconfig{CONFIG_BT_HFP_HF_3WAY_CALL} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_join_conversation(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF explicit call transfer + * + * Connects the two calls and disconnects the subscriber from + * both calls (Explicit Call Transfer) by sending `AT+CHLD=4`. + * If @kconfig{CONFIG_BT_HFP_HF_3WAY_CALL} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_explicit_call_transfer(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF release call with specified index + * + * Release call with specified index by sending `AT+CHLD=1`. + * `` is index of specified call. + * If @kconfig{CONFIG_BT_HFP_HF_ECC} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param call HFP HF call object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_release_specified_call(struct bt_hfp_hf_call *call); + +/** @brief Handsfree HF request private consultation mode with specified call + * + * Request private consultation mode with specified call (Place all calls + * on hold EXCEPT the call indicated by ``.) by sending + * `AT+CHLD=2`. + * `` is index of specified call. + * If @kconfig{CONFIG_BT_HFP_HF_ECC} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param call HFP HF call object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_private_consultation_mode(struct bt_hfp_hf_call *call); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/Kconfig b/subsys/bluetooth/host/classic/Kconfig index 28cf634a6e599..6840ae6eac722 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -209,6 +209,31 @@ config BT_HFP_HF_CODEC_LC3_SWB This option enables Codec LC3 SWB for HFP HF endif # BT_HFP_HF_CODEC_NEG +config BT_HFP_HF_3WAY_CALL + bool "Three-way calling for HFP HF [EXPERIMENTAL]" + help + This option enables Three-way calling for HFP HF + +config BT_HFP_HF_ECS + bool "Enhanced Call Status for HFP HF [EXPERIMENTAL]" + default y if BT_HFP_HF_3WAY_CALL + help + This option enables Enhanced Call Status for HFP HF + +config BT_HFP_HF_ECC + bool "Enhanced Call Control for HFP HF [EXPERIMENTAL]" + default y if BT_HFP_HF_3WAY_CALL + help + This option enables Enhanced Call Control for HFP HF + +config BT_HFP_HF_MAX_CALLS + int "Maximum supported calls for HFP HF [EXPERIMENTAL]" + default 2 if BT_HFP_HF_3WAY_CALL + default 1 + range 1 2 + help + This option sets maximum supported calls for HFP HF + endif # BT_HFP_HF if BT_HFP_AG diff --git a/subsys/bluetooth/host/classic/at.c b/subsys/bluetooth/host/classic/at.c index a2196ade5df47..b56ad286f06fd 100644 --- a/subsys/bluetooth/host/classic/at.c +++ b/subsys/bluetooth/host/classic/at.c @@ -449,7 +449,7 @@ int at_parse_cmd_input(struct at_client *at, struct net_buf *buf, int at_has_next_list(struct at_client *at) { - return at->buf[at->pos] != '\0'; + return at->buf[at->pos] != '\0' && at->buf[at->pos] != ')'; } int at_open_list(struct at_client *at) @@ -589,3 +589,27 @@ char *at_get_string(struct at_client *at) return string; } + +char *at_get_raw_string(struct at_client *at, size_t *string_len) +{ + char *string; + + skip_space(at); + + string = &at->buf[at->pos]; + + while (at->buf[at->pos] != '\0' && + at->buf[at->pos] != ',' && + at->buf[at->pos] != ')') { + at->pos++; + } + + if (string_len) { + *string_len = &at->buf[at->pos] - string; + } + + skip_space(at); + next_list(at); + + return string; +} diff --git a/subsys/bluetooth/host/classic/at.h b/subsys/bluetooth/host/classic/at.h index 6b75725f83f19..fc6ab68ad2fb5 100644 --- a/subsys/bluetooth/host/classic/at.h +++ b/subsys/bluetooth/host/classic/at.h @@ -117,3 +117,4 @@ int at_close_list(struct at_client *at); int at_open_list(struct at_client *at); int at_has_next_list(struct at_client *at); char *at_get_string(struct at_client *at); +char *at_get_raw_string(struct at_client *at, size_t *string_len); diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 631cdb7875730..d9f69d13664ee 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -42,6 +42,8 @@ NET_BUF_POOL_FIXED_DEFINE(hf_pool, CONFIG_BT_MAX_CONN + 1, static struct bt_hfp_hf bt_hfp_hf_pool[CONFIG_BT_MAX_CONN]; +#define HF_ENHANCED_CALL_STATUS_TIMEOUT 50 /* ms */ + struct at_callback_set { void *resp; void *finish; @@ -391,732 +393,2055 @@ int cind_resp(struct at_client *hf_at, struct net_buf *buf) return 0; } -static void ag_indicator_handle_call(struct bt_hfp_hf *hf, uint32_t value) +static void free_call(struct bt_hfp_hf_call *call) { - atomic_set_bit_to(hf->flags, BT_HFP_HF_FLAG_ACTIVE, value); - if (value) { - if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING_HELD)) { - if (bt_hf->incoming_held) { - bt_hf->incoming_held(hf); - } - } else { - if (bt_hf->accept) { - bt_hf->accept(hf); - } - } - } else { - if (atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING_HELD)) { - if (bt_hf->reject) { - bt_hf->reject(hf); - } - } else { - if (bt_hf->terminate) { - bt_hf->terminate(hf); - } - } - } + memset(call, 0, sizeof(*call)); } -void ag_indicator_handle_values(struct at_client *hf_at, uint32_t index, - uint32_t value) +#if defined(CONFIG_BT_HFP_HF_ECS) +static struct bt_hfp_hf_call *get_call_with_index(struct bt_hfp_hf *hf, uint8_t index) { - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - - LOG_DBG("Index :%u, Value :%u", index, value); - - if (index >= ARRAY_SIZE(ag_ind)) { - LOG_ERR("Max only %zu indicators are supported", ARRAY_SIZE(ag_ind)); - return; - } + struct bt_hfp_hf_call *call; - if (value > ag_ind[hf->ind_table[index]].max || - value < ag_ind[hf->ind_table[index]].min) { - LOG_ERR("Indicators out of range - value: %u", value); - return; - } + for (size_t i = 0; i < ARRAY_SIZE(hf->calls); i++) { + call = &hf->calls[i]; - switch (hf->ind_table[index]) { - case HF_SERVICE_IND: - if (bt_hf->service) { - bt_hf->service(hf, value); + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + continue; } - break; - case HF_CALL_IND: - ag_indicator_handle_call(hf, value); - break; - case HF_CALL_SETUP_IND: - atomic_set_bit_to(hf->flags, BT_HFP_HF_FLAG_INCOMING, - value == BT_HFP_CALL_SETUP_INCOMING); - if (value == BT_HFP_CALL_SETUP_INCOMING) { - if (bt_hf->incoming) { - bt_hf->incoming(hf); - } - } else if (value == BT_HFP_CALL_SETUP_OUTGOING) { - if (bt_hf->outgoing) { - bt_hf->outgoing(hf); - } - } else if (value == BT_HFP_CALL_SETUP_REMOTE_ALERTING) { - if (bt_hf->remote_ringing) { - bt_hf->remote_ringing(hf); - } - } - break; - case HF_CALL_HELD_IND: - if (bt_hf->call_held) { - bt_hf->call_held(hf, value); - } - break; - case HF_SINGNAL_IND: - if (bt_hf->signal) { - bt_hf->signal(hf, value); - } - break; - case HF_ROAM_IND: - if (bt_hf->roam) { - bt_hf->roam(hf, value); + if (call->index == index) { + return call; } - break; - case HF_BATTERY_IND: - if (bt_hf->battery) { - bt_hf->battery(hf, value); - } - break; - default: - LOG_ERR("Unknown AG indicator"); - break; } + + return NULL; } -int cind_status_handle(struct at_client *hf_at) +static struct bt_hfp_hf_call *get_call_without_index(struct bt_hfp_hf *hf) { - uint32_t index = 0U; + struct bt_hfp_hf_call *call; - while (at_has_next_list(hf_at)) { - uint32_t value; - int ret; + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) { + call = &hf->calls[index]; - ret = at_get_number(hf_at, &value); - if (ret < 0) { - LOG_ERR("could not get the value"); - return ret; + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + continue; } - ag_indicator_handle_values(hf_at, index, value); - - index++; + if (!call->index) { + return call; + } } - return 0; + return NULL; } +#endif /* CONFIG_BT_HFP_HF_ECS */ -int cind_status_resp(struct at_client *hf_at, struct net_buf *buf) +static void hf_reject_call(struct bt_hfp_hf_call *call) { - int err; - - err = at_parse_cmd_input(hf_at, buf, "CIND", cind_status_handle, - AT_CMD_TYPE_NORMAL); - if (err < 0) { - LOG_ERR("Error parsing CMD input"); - hf_slc_error(hf_at); + if (bt_hf->reject) { + bt_hf->reject(call); } + free_call(call); +} - return 0; +static void hf_terminate_call(struct bt_hfp_hf_call *call) +{ + if (bt_hf->terminate) { + bt_hf->terminate(call); + } + free_call(call); } -int ciev_handle(struct at_client *hf_at) +static void clear_call_without_clcc(struct bt_hfp_hf *hf) { - uint32_t index, value; - int ret; + struct bt_hfp_hf_call *call; - ret = at_get_number(hf_at, &index); - if (ret < 0) { - LOG_ERR("could not get the Index"); - return ret; - } - /* The first element of the list shall have 1 */ - if (!index) { - LOG_ERR("Invalid index value '0'"); - return 0; - } + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) { + call = &hf->calls[index]; - ret = at_get_number(hf_at, &value); - if (ret < 0) { - LOG_ERR("could not get the value"); - return ret; - } + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + continue; + } - ag_indicator_handle_values(hf_at, (index - 1), value); + if (atomic_test_and_clear_bit(call->flags, BT_HFP_HF_CALL_CLCC)) { + continue; + } - return 0; + switch (atomic_get(call->state)) { + case BT_HFP_HF_CALL_STATE_TERMINATE: + break; + case BT_HFP_HF_CALL_STATE_OUTGOING: + case BT_HFP_HF_CALL_STATE_INCOMING: + case BT_HFP_HF_CALL_STATE_ALERTING: + case BT_HFP_HF_CALL_STATE_WAITING: + hf_reject_call(call); + break; + case BT_HFP_HF_CALL_STATE_ACTIVE: + case BT_HFP_HF_CALL_STATE_HELD: + hf_terminate_call(call); + break; + default: + free_call(call); + break; + } + } } -int ring_handle(struct at_client *hf_at) +static int clcc_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - if (bt_hf->ring_indication) { - bt_hf->ring_indication(hf); + LOG_DBG("AT+CLCC (result %d) on %p", result, hf); + + if (result == AT_RESULT_OK) { + clear_call_without_clcc(hf); } + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_CLCC_PENDING); + return 0; } -#if defined(CONFIG_BT_HFP_HF_CLI) -int clip_handle(struct at_client *hf_at) +static void clear_call_clcc_state(struct bt_hfp_hf *hf) { - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - char *number; - uint32_t type; - int err; + struct bt_hfp_hf_call *call; - number = at_get_string(hf_at); + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) { + call = &hf->calls[index]; - err = at_get_number(hf_at, &type); - if (err) { - LOG_WRN("could not get the type"); - } else { - type = 0; - } + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + continue; + } - if (bt_hf->clip) { - bt_hf->clip(hf, number, (uint8_t)type); + atomic_clear_bit(call->flags, BT_HFP_HF_CALL_CLCC); } - - return 0; } -#endif /* CONFIG_BT_HFP_HF_CLI */ -#if defined(CONFIG_BT_HFP_HF_VOLUME) -int vgm_handle(struct at_client *hf_at) +static void hf_query_current_calls(struct bt_hfp_hf *hf) { - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - uint32_t gain; int err; - err = at_get_number(hf_at, &gain); - if (err) { - LOG_ERR("could not get the microphone gain"); - return err; - } + LOG_DBG(""); - if (gain > BT_HFP_HF_VGM_GAIN_MAX) { - LOG_ERR("Invalid microphone gain (%d > %d)", gain, BT_HFP_HF_VGM_GAIN_MAX); - return -EINVAL; + if (!hf) { + LOG_ERR("No HF connection found"); + return; } - if (bt_hf->vgm) { - bt_hf->vgm(hf, (uint8_t)gain); + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + return; } - return 0; -} - -int vgs_handle(struct at_client *hf_at) -{ - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - uint32_t gain; - int err; - - err = at_get_number(hf_at, &gain); - if (err) { - LOG_ERR("could not get the speaker gain"); - return err; + if (!(hf->ag_features & BT_HFP_AG_FEATURE_ECS)) { + return; } - if (gain > BT_HFP_HF_VGS_GAIN_MAX) { - LOG_ERR("Invalid speaker gain (%d > %d)", gain, BT_HFP_HF_VGS_GAIN_MAX); - return -EINVAL; + if (!(hf->hf_features & BT_HFP_HF_FEATURE_ECS)) { + return; } - if (bt_hf->vgs) { - bt_hf->vgs(hf, (uint8_t)gain); + if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CLCC_PENDING)) { + k_work_reschedule(&hf->deferred_work, K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + return; } - return 0; + clear_call_clcc_state(hf); + + err = hfp_hf_send_cmd(hf, NULL, clcc_finish, "AT+CLCC"); + if (err < 0) { + LOG_ERR("Fail to query current calls on %p", hf); + } } -#endif /* CONFIG_BT_HFP_HF_VOLUME */ -int bsir_handle(struct at_client *hf_at) +static void hf_call_state_update(struct bt_hfp_hf_call *call, int state) { - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - uint32_t inband; - int err; + int old_state; - err = at_get_number(hf_at, &inband); - if (err) { - LOG_ERR("could not get bsir value"); - return err; - } + old_state = atomic_get(call->state); + atomic_set(call->state, state); - if (inband > 1) { - LOG_ERR("Invalid %d bsir value", inband); - return -EINVAL; - } + LOG_DBG("Call %p state update %d->%d", call, old_state, state); - if (bt_hf->inband_ring) { - bt_hf->inband_ring(hf, (bool)inband); + switch (state) { + case BT_HFP_HF_CALL_STATE_TERMINATE: + free_call(call); + break; + case BT_HFP_HF_CALL_STATE_OUTGOING: + break; + case BT_HFP_HF_CALL_STATE_INCOMING: + break; + case BT_HFP_HF_CALL_STATE_ALERTING: + k_work_reschedule(&call->hf->deferred_work, + K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + break; + case BT_HFP_HF_CALL_STATE_WAITING: + k_work_reschedule(&call->hf->deferred_work, + K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + break; + case BT_HFP_HF_CALL_STATE_ACTIVE: + break; + case BT_HFP_HF_CALL_STATE_HELD: + break; } +} - return 0; +#if defined(CONFIG_BT_HFP_HF_ECS) +static void call_state_update(struct bt_hfp_hf_call *call, uint32_t status) +{ + switch (status) { + case BT_HFP_CLCC_STATUS_ACTIVE: + if ((atomic_get(call->state) != BT_HFP_HF_CALL_STATE_ACTIVE) || + ((atomic_get(call->state) == BT_HFP_HF_CALL_STATE_ACTIVE) && + atomic_test_and_clear_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD))) { + atomic_val_t state; + + state = atomic_get(call->state); + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_ACTIVE); + if (state == BT_HFP_HF_CALL_STATE_HELD) { + if (bt_hf->retrieve) { + bt_hf->retrieve(call); + } + } else { + if (bt_hf->accept) { + bt_hf->accept(call); + } + } + } + break; + case BT_HFP_CLCC_STATUS_HELD: + if ((atomic_get(call->state) == BT_HFP_HF_CALL_STATE_ACTIVE) && + !atomic_test_and_clear_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD)) { + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_HELD); + if (bt_hf->held) { + bt_hf->held(call); + } + } + break; + case BT_HFP_CLCC_STATUS_DIALING: + break; + case BT_HFP_CLCC_STATUS_ALERTING: + break; + case BT_HFP_CLCC_STATUS_INCOMING: + break; + case BT_HFP_CLCC_STATUS_WAITING: + break; + case BT_HFP_CLCC_STATUS_CALL_HELD_HOLD: + if ((atomic_get(call->state) == BT_HFP_HF_CALL_STATE_INCOMING) || + (atomic_get(call->state) == BT_HFP_HF_CALL_STATE_WAITING)) { + atomic_set_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD); + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_ACTIVE); + if (bt_hf->incoming_held) { + bt_hf->incoming_held(call); + } + } + break; + } } -#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) -int bcs_handle(struct at_client *hf_at) +static int clcc_handle(struct at_client *hf_at) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - uint32_t codec_id; int err; + struct bt_hfp_hf_call *call; + uint32_t index; + uint32_t dir; + uint32_t status; + uint32_t mode; + uint32_t mpty; + char *number = NULL; + uint32_t type = 0; + bool incoming = false; - err = at_get_number(hf_at, &codec_id); - if (err) { - LOG_ERR("could not get bcs value"); + err = at_get_number(hf_at, &index); + if (err < 0) { + LOG_ERR("Error getting index"); return err; } - if (!(hf->hf_codec_ids & BIT(codec_id))) { - LOG_ERR("Invalid codec id %d", codec_id); - err = bt_hfp_hf_set_codecs(hf, hf->hf_codec_ids); + call = get_call_with_index(hf, (uint8_t)index); + if (!call) { + LOG_INF("Valid call with index %d not found", index); + call = get_call_without_index(hf); + if (!call) { + LOG_INF("Not available call"); + return 0; + } + call->index = (uint8_t)index; + } + + atomic_set_bit(call->flags, BT_HFP_HF_CALL_CLCC); + + err = at_get_number(hf_at, &dir); + if (err < 0) { + LOG_ERR("Error getting dir"); return err; } - atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_CODEC_CONN); + if (atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING) || + atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING_3WAY)) { + incoming = true; + } - if (bt_hf->codec_negotiate) { - bt_hf->codec_negotiate(hf, codec_id); + if (incoming != !!dir) { + LOG_ERR("Call dir of HF is not aligned with AG"); return 0; } - err = bt_hfp_hf_select_codec(hf, codec_id); - return err; -} -#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + err = at_get_number(hf_at, &status); + if (err < 0) { + LOG_ERR("Error getting status"); + return err; + } -static int btrh_handle(struct at_client *hf_at) -{ - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - uint32_t on_hold; - int err; + err = at_get_number(hf_at, &mode); + if (err < 0) { + LOG_ERR("Error getting mode"); + return err; + } - err = at_get_number(hf_at, &on_hold); + err = at_get_number(hf_at, &mpty); if (err < 0) { - LOG_ERR("Error getting value"); + LOG_ERR("Error getting mpty"); return err; } - if (on_hold == BT_HFP_BTRH_ON_HOLD) { - atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_QUERY_HOLD); - atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING_HELD); - } else if (on_hold == BT_HFP_BTRH_ACCEPTED) { - if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_ACTIVE) && - atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING_HELD)) { - if (bt_hf && bt_hf->accept) { - bt_hf->accept(hf); - } - } - } else if (on_hold == BT_HFP_BTRH_REJECTED) { - if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_ACTIVE) && - atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING_HELD)) { - if (bt_hf && bt_hf->reject) { - bt_hf->reject(hf->acl); - } - } + number = at_get_string(hf_at); + + if (number) { + (void)at_get_number(hf_at, &type); } + call_state_update(call, status); + + LOG_DBG("CLCC idx %d dir %d status %d mode %d mpty %d number %s type %d", + index, dir, status, mode, mpty, number, type); + return 0; } +#endif /* CONFIG_BT_HFP_HF_ECS */ -static const struct unsolicited { - const char *cmd; - enum at_cmd_type type; - int (*func)(struct at_client *hf_at); -} handlers[] = { - { "CIEV", AT_CMD_TYPE_UNSOLICITED, ciev_handle }, - { "RING", AT_CMD_TYPE_OTHER, ring_handle }, -#if defined(CONFIG_BT_HFP_HF_CLI) - { "CLIP", AT_CMD_TYPE_UNSOLICITED, clip_handle }, -#endif /* CONFIG_BT_HFP_HF_CLI */ -#if defined(CONFIG_BT_HFP_HF_VOLUME) - { "VGM", AT_CMD_TYPE_UNSOLICITED, vgm_handle }, - { "VGS", AT_CMD_TYPE_UNSOLICITED, vgs_handle }, -#endif /* CONFIG_BT_HFP_HF_VOLUME */ - { "BSIR", AT_CMD_TYPE_UNSOLICITED, bsir_handle }, -#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) - { "BCS", AT_CMD_TYPE_UNSOLICITED, bcs_handle }, -#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ - { "BTRH", AT_CMD_TYPE_UNSOLICITED, btrh_handle }, -}; - -static const struct unsolicited *hfp_hf_unsol_lookup(struct at_client *hf_at) +static struct bt_hfp_hf_call *get_dialing_call(struct bt_hfp_hf *hf) { - int i; + struct bt_hfp_hf_call *call; - for (i = 0; i < ARRAY_SIZE(handlers); i++) { - if (!strncmp(hf_at->buf, handlers[i].cmd, - strlen(handlers[i].cmd))) { - return &handlers[i]; + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) { + call = &hf->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + continue; + } + + switch (atomic_get(call->state)) { + case BT_HFP_HF_CALL_STATE_OUTGOING: + case BT_HFP_HF_CALL_STATE_INCOMING: + case BT_HFP_HF_CALL_STATE_ALERTING: + case BT_HFP_HF_CALL_STATE_WAITING: + return call; } } return NULL; } -int unsolicited_cb(struct at_client *hf_at, struct net_buf *buf) +static struct bt_hfp_hf_call *get_call_with_state(struct bt_hfp_hf *hf, int state) { - const struct unsolicited *handler; + struct bt_hfp_hf_call *call; - handler = hfp_hf_unsol_lookup(hf_at); - if (!handler) { - LOG_ERR("Unhandled unsolicited response"); - return -ENOMSG; - } + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) { + call = &hf->calls[index]; - if (!at_parse_cmd_input(hf_at, buf, handler->cmd, handler->func, - handler->type)) { - return 0; + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + continue; + } + + if (atomic_get(call->state) == state) { + return call; + } } - return -ENOMSG; + return NULL; } -static int send_at_cmee(struct bt_hfp_hf *hf, at_finish_cb_t cb) +static struct bt_hfp_hf_call *get_call_with_flag(struct bt_hfp_hf *hf, int flag) { - if (hf->ag_features & BT_HFP_AG_FEATURE_EXT_ERR) { - return hfp_hf_send_cmd(hf, NULL, cb, "AT+CMEE=1"); - } else { - return -ENOTSUP; + struct bt_hfp_hf_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) { + call = &hf->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + continue; + } + + if (atomic_test_bit(call->flags, flag)) { + return call; + } } + + return NULL; } -static int at_cmee_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) +static struct bt_hfp_hf_call *get_call_with_state_and_flag(struct bt_hfp_hf *hf, + int state, int flag) { - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + struct bt_hfp_hf_call *call; - LOG_DBG("CMEE set (result %d) on %p", result, hf); + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) { + call = &hf->calls[index]; - return 0; -} + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + continue; + } -static int send_at_cops(struct bt_hfp_hf *hf, at_finish_cb_t cb) -{ - return hfp_hf_send_cmd(hf, NULL, cb, "AT+COPS=3,0"); + if ((atomic_get(call->state) == state) && + atomic_test_bit(call->flags, flag)) { + return call; + } + } + + return NULL; } -static int at_cops_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) +static struct bt_hfp_hf_call *get_using_call(struct bt_hfp_hf *hf) { - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + struct bt_hfp_hf_call *call; - LOG_DBG("COPS set (result %d) on %p", result, hf); + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) { + call = &hf->calls[index]; - return 0; + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + continue; + } + + return call; + } + + return NULL; } -#if defined(CONFIG_BT_HFP_HF_CLI) -static int send_at_clip(struct bt_hfp_hf *hf, at_finish_cb_t cb) +static void bt_hf_deferred_work(struct k_work *work) { - return hfp_hf_send_cmd(hf, NULL, cb, "AT+CLIP=1"); + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_hfp_hf *hf = CONTAINER_OF(dwork, struct bt_hfp_hf, deferred_work); + + hf_query_current_calls(hf); } -static int at_clip_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) +static struct bt_hfp_hf_call *get_new_call(struct bt_hfp_hf *hf) { - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + struct bt_hfp_hf_call *call; - LOG_DBG("CLIP set (result %d) on %p", result, hf); + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) { + call = &hf->calls[index]; - return 0; -} -#endif /* CONFIG_BT_HFP_HF_CLI */ + if (atomic_test_and_set_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + continue; + } -#if defined(CONFIG_BT_HFP_HF_VOLUME) -static int send_at_vgm(struct bt_hfp_hf *hf, at_finish_cb_t cb) -{ - return hfp_hf_send_cmd(hf, NULL, cb, "AT+VGM=%d", hf->vgm); + call->hf = hf; + + return call; + } + + return NULL; } -static int at_vgm_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) +static int get_using_call_count(struct bt_hfp_hf *hf) { - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + struct bt_hfp_hf_call *call; + int count = 0; - LOG_DBG("VGM set (result %d) on %p", result, hf); + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) { + call = &hf->calls[index]; - return 0; -} + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + continue; + } -static int send_at_vgs(struct bt_hfp_hf *hf, at_finish_cb_t cb) -{ - return hfp_hf_send_cmd(hf, NULL, cb, "AT+VGS=%d", hf->vgs); + count++; + } + + return count; } -static int at_vgs_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) +static void set_all_calls_held_state(struct bt_hfp_hf *hf, bool held) { - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + struct bt_hfp_hf_call *call; - LOG_DBG("VGS set (result %d) on %p", result, hf); + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) { + call = &hf->calls[index]; - return 0; + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + continue; + } + + if (held && (atomic_get(call->state) == BT_HFP_HF_CALL_STATE_ACTIVE)) { + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_HELD); + if (bt_hf->held) { + bt_hf->held(call); + } + } + + if (!held && (atomic_get(call->state) == BT_HFP_HF_CALL_STATE_HELD)) { + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_ACTIVE); + if (bt_hf->retrieve) { + bt_hf->retrieve(call); + } + } + } } -#endif /* CONFIG_BT_HFP_HF_VOLUME */ -#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) -static void get_codec_ids(struct bt_hfp_hf *hf, char *buffer, size_t buffer_len) +static void ag_indicator_handle_call(struct bt_hfp_hf *hf, uint32_t value) { - size_t len = 0; - uint8_t ids = hf->hf_codec_ids; - int index = 0; + struct bt_hfp_hf_call *call; - while (ids && (len < (buffer_len-2))) { - if (ids & 0x01) { - buffer[len++] = index + '0'; - buffer[len++] = ','; + LOG_DBG("call %d", value); + + if (value) { + call = get_dialing_call(hf); + if (!call) { + return; } - index ++; - ids = ids >> 1; - } - if (len > 0) { - len --; - } + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_ACTIVE); - buffer[len] = '\0'; + if (atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD)) { + if (bt_hf->incoming_held) { + bt_hf->incoming_held(call); + } + } else { + if (bt_hf->accept) { + bt_hf->accept(call); + } + } + } else { + do { + call = get_using_call(hf); + if (!call) { + return; + } + + switch (atomic_get(call->state)) { + case BT_HFP_HF_CALL_STATE_OUTGOING: + case BT_HFP_HF_CALL_STATE_INCOMING: + case BT_HFP_HF_CALL_STATE_ALERTING: + case BT_HFP_HF_CALL_STATE_WAITING: + hf_reject_call(call); + break; + case BT_HFP_HF_CALL_STATE_ACTIVE: + if (atomic_test_and_clear_bit(call->flags, + BT_HFP_HF_CALL_INCOMING_HELD)) { + hf_reject_call(call); + break; + } + __fallthrough; + case BT_HFP_HF_CALL_STATE_HELD: + hf_terminate_call(call); + break; + default: + free_call(call); + break; + } + } while (call); + } } -static int send_at_bac(struct bt_hfp_hf *hf, at_finish_cb_t cb) +static void ag_indicator_handle_call_setup(struct bt_hfp_hf *hf, uint32_t value) { - if (hf->ag_features & BT_HFP_AG_FEATURE_CODEC_NEG) { - char ids[sizeof(hf->hf_codec_ids)*2*8 + 1]; - get_codec_ids(hf, &ids[0], ARRAY_SIZE(ids)); - return hfp_hf_send_cmd(hf, NULL, cb, "AT+BAC=%s", ids); - } + struct bt_hfp_hf_call *call; + int call_count; - return -ENOTSUP; + call_count = get_using_call_count(hf); + + LOG_DBG("call setup %d", value); + + switch (value) { + case BT_HFP_CALL_SETUP_NONE: + if (call_count == 1) { + call = get_using_call(hf); + if (!call) { + break; + } + + if ((atomic_get(call->state) == BT_HFP_HF_CALL_STATE_ACTIVE) || + (atomic_get(call->state) == BT_HFP_HF_CALL_STATE_HELD)) { + break; + } + + hf_reject_call(call); + } else { + call = get_dialing_call(hf); + if (!call) { + break; + } + + if (atomic_get(call->state) == BT_HFP_HF_CALL_STATE_OUTGOING) { + LOG_INF("The outgoing is not alerted"); + hf_reject_call(call); + } else { + LOG_INF("Waiting for +CIEV: (callheld = 1)"); + k_work_reschedule(&hf->deferred_work, + K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + } + } + break; + case BT_HFP_CALL_SETUP_INCOMING: + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_INCOMING); + if (!call) { + call = get_new_call(hf); + if (!call) { + break; + } + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_INCOMING); + } + + if (call_count == 0) { + atomic_set_bit(call->flags, BT_HFP_HF_CALL_INCOMING); + } else { + atomic_set_bit(call->flags, BT_HFP_HF_CALL_INCOMING_3WAY); + } + if (bt_hf->incoming) { + bt_hf->incoming(hf, call); + } + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_WAITING); + break; + case BT_HFP_CALL_SETUP_OUTGOING: + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_OUTGOING); + if (!call) { + call = get_new_call(hf); + if (!call) { + break; + } + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_OUTGOING); + } + + if (call_count) { + atomic_set_bit(call->flags, BT_HFP_HF_CALL_OUTGOING_3WAY); + } + if (bt_hf->outgoing) { + bt_hf->outgoing(hf, call); + } + break; + case BT_HFP_CALL_SETUP_REMOTE_ALERTING: + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_OUTGOING); + if (!call) { + break; + } + + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_ALERTING); + if (bt_hf->remote_ringing) { + bt_hf->remote_ringing(call); + } + break; + default: + break; + } } -static int at_bac_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) +static void ag_indicator_handle_call_held(struct bt_hfp_hf *hf, uint32_t value) { - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + struct bt_hfp_hf_call *call; - LOG_DBG("BAC set (result %d) on %p", result, hf); + k_work_reschedule(&hf->deferred_work, K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); - return 0; -} -#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + LOG_DBG("call setup %d", value); -typedef int (*at_send_t)(struct bt_hfp_hf *hf, at_finish_cb_t cb); + switch (value) { + case BT_HFP_CALL_HELD_NONE: + set_all_calls_held_state(hf, false); + break; + case BT_HFP_CALL_HELD_ACTIVE_HELD: + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_ALERTING); + if (!call) { + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_WAITING); + if (!call) { + break; + } + } -static struct at_cmd_init + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_ACTIVE); + if (bt_hf->accept) { + bt_hf->accept(call); + } + break; + case BT_HFP_CALL_HELD_HELD: + set_all_calls_held_state(hf, true); + break; + default: + break; + } +} + +void ag_indicator_handle_values(struct at_client *hf_at, uint32_t index, + uint32_t value) { - at_send_t send; - at_finish_cb_t finish; - bool disconnect; /* Disconnect if command failed. */ -} cmd_init_list[] = { -#if defined(CONFIG_BT_HFP_HF_VOLUME) - {send_at_vgm, at_vgm_finish, false}, - {send_at_vgs, at_vgs_finish, false}, -#endif /* CONFIG_BT_HFP_HF_VOLUME */ - {send_at_cmee, at_cmee_finish, false}, - {send_at_cops, at_cops_finish, false}, + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("Index :%u, Value :%u", index, value); + + if (index >= ARRAY_SIZE(ag_ind)) { + LOG_ERR("Max only %zu indicators are supported", ARRAY_SIZE(ag_ind)); + return; + } + + if (value > ag_ind[hf->ind_table[index]].max || + value < ag_ind[hf->ind_table[index]].min) { + LOG_ERR("Indicators out of range - value: %u", value); + return; + } + + switch (hf->ind_table[index]) { + case HF_SERVICE_IND: + if (bt_hf->service) { + bt_hf->service(hf, value); + } + break; + case HF_CALL_IND: + ag_indicator_handle_call(hf, value); + break; + case HF_CALL_SETUP_IND: + ag_indicator_handle_call_setup(hf, value); + break; + case HF_CALL_HELD_IND: + ag_indicator_handle_call_held(hf, value); + break; + case HF_SINGNAL_IND: + if (bt_hf->signal) { + bt_hf->signal(hf, value); + } + break; + case HF_ROAM_IND: + if (bt_hf->roam) { + bt_hf->roam(hf, value); + } + break; + case HF_BATTERY_IND: + if (bt_hf->battery) { + bt_hf->battery(hf, value); + } + break; + default: + LOG_ERR("Unknown AG indicator"); + break; + } +} + +int cind_status_handle(struct at_client *hf_at) +{ + uint32_t index = 0U; + + while (at_has_next_list(hf_at)) { + uint32_t value; + int ret; + + ret = at_get_number(hf_at, &value); + if (ret < 0) { + LOG_ERR("could not get the value"); + return ret; + } + + ag_indicator_handle_values(hf_at, index, value); + + index++; + } + + return 0; +} + +int cind_status_resp(struct at_client *hf_at, struct net_buf *buf) +{ + int err; + + err = at_parse_cmd_input(hf_at, buf, "CIND", cind_status_handle, + AT_CMD_TYPE_NORMAL); + if (err < 0) { + LOG_ERR("Error parsing CMD input"); + hf_slc_error(hf_at); + } + + return 0; +} + +int ciev_handle(struct at_client *hf_at) +{ + uint32_t index, value; + int ret; + + ret = at_get_number(hf_at, &index); + if (ret < 0) { + LOG_ERR("could not get the Index"); + return ret; + } + /* The first element of the list shall have 1 */ + if (!index) { + LOG_ERR("Invalid index value '0'"); + return 0; + } + + ret = at_get_number(hf_at, &value); + if (ret < 0) { + LOG_ERR("could not get the value"); + return ret; + } + + ag_indicator_handle_values(hf_at, (index - 1), value); + + return 0; +} + +int ring_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + struct bt_hfp_hf_call *call; + + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_WAITING); + if (!call) { + return 0; + } + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING)) { + LOG_WRN("Invalid call dir (outgoing)"); + } + + if (bt_hf->ring_indication) { + bt_hf->ring_indication(call); + } + + return 0; +} + #if defined(CONFIG_BT_HFP_HF_CLI) - {send_at_clip, at_clip_finish, false}, +int clip_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + char *number; + uint32_t type; + int err; + struct bt_hfp_hf_call *call; + + number = at_get_string(hf_at); + + err = at_get_number(hf_at, &type); + if (err) { + LOG_WRN("could not get the type"); + } else { + type = 0; + } + + + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_WAITING); + if (!call) { + return 0; + } + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING)) { + LOG_WRN("Invalid call dir (outgoing)"); + } + + if (bt_hf->clip) { + bt_hf->clip(call, number, (uint8_t)type); + } + + return 0; +} #endif /* CONFIG_BT_HFP_HF_CLI */ + +#if defined(CONFIG_BT_HFP_HF_VOLUME) +int vgm_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + uint32_t gain; + int err; + + err = at_get_number(hf_at, &gain); + if (err) { + LOG_ERR("could not get the microphone gain"); + return err; + } + + if (gain > BT_HFP_HF_VGM_GAIN_MAX) { + LOG_ERR("Invalid microphone gain (%d > %d)", gain, BT_HFP_HF_VGM_GAIN_MAX); + return -EINVAL; + } + + if (bt_hf->vgm) { + bt_hf->vgm(hf, (uint8_t)gain); + } + + return 0; +} + +int vgs_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + uint32_t gain; + int err; + + err = at_get_number(hf_at, &gain); + if (err) { + LOG_ERR("could not get the speaker gain"); + return err; + } + + if (gain > BT_HFP_HF_VGS_GAIN_MAX) { + LOG_ERR("Invalid speaker gain (%d > %d)", gain, BT_HFP_HF_VGS_GAIN_MAX); + return -EINVAL; + } + + if (bt_hf->vgs) { + bt_hf->vgs(hf, (uint8_t)gain); + } + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + +int bsir_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + uint32_t inband; + int err; + + err = at_get_number(hf_at, &inband); + if (err) { + LOG_ERR("could not get bsir value"); + return err; + } + + if (inband > 1) { + LOG_ERR("Invalid %d bsir value", inband); + return -EINVAL; + } + + if (bt_hf->inband_ring) { + bt_hf->inband_ring(hf, (bool)inband); + } + + return 0; +} + #if defined(CONFIG_BT_HFP_HF_CODEC_NEG) - {send_at_bac, at_bac_finish, false}, -#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ -}; +int bcs_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + uint32_t codec_id; + int err; + + err = at_get_number(hf_at, &codec_id); + if (err) { + LOG_ERR("could not get bcs value"); + return err; + } + + if (!(hf->hf_codec_ids & BIT(codec_id))) { + LOG_ERR("Invalid codec id %d", codec_id); + err = bt_hfp_hf_set_codecs(hf, hf->hf_codec_ids); + return err; + } + + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_CODEC_CONN); + + if (bt_hf->codec_negotiate) { + bt_hf->codec_negotiate(hf, codec_id); + return 0; + } + + err = bt_hfp_hf_select_codec(hf, codec_id); + return err; +} +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +static int btrh_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + uint32_t on_hold; + int err; + struct bt_hfp_hf_call *call; + + err = at_get_number(hf_at, &on_hold); + if (err < 0) { + LOG_ERR("Error getting value"); + return err; + } + + if (on_hold == BT_HFP_BTRH_ON_HOLD) { + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_WAITING); + if (!call) { + return 0; + } + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING)) { + LOG_WRN("Invalid call dir (outgoing)"); + } + atomic_set_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD); + } else { + call = get_call_with_state_and_flag(hf, + BT_HFP_HF_CALL_STATE_ACTIVE, BT_HFP_HF_CALL_INCOMING_HELD); + if (!call) { + return 0; + } + + if (on_hold == BT_HFP_BTRH_ACCEPTED) { + atomic_clear_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD); + if (bt_hf && bt_hf->accept) { + bt_hf->accept(call); + } + } else if (on_hold == BT_HFP_BTRH_REJECTED) { + hf_reject_call(call); + } else { + return -EINVAL; + } + } + + return 0; +} + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int ccwa_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + char *number; + uint32_t type; + int err; + struct bt_hfp_hf_call *call; + + number = at_get_string(hf_at); + + err = at_get_number(hf_at, &type); + if (err) { + LOG_WRN("could not get the type"); + } else { + type = 0; + } + + call = get_new_call(hf); + if (!call) { + LOG_ERR("Not available call object"); + return 0; + } + + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_INCOMING); + if (bt_hf->call_waiting) { + bt_hf->call_waiting(call, number, (uint8_t)type); + } + + return 0; +} + +static struct _chld_feature { + const char *name; + int value; +} chld_feature_map[] = { + { "1x", BT_HFP_CALL_RELEASE_SPECIFIED_ACTIVE }, + { "2x", BT_HFP_CALL_PRIVATE_CNLTN_MODE }, + { "0", BT_HFP_CHLD_RELEASE_ALL }, + { "1", BT_HFP_CHLD_RELEASE_ACTIVE_ACCEPT_OTHER }, + { "2", BT_HFP_CALL_HOLD_ACTIVE_ACCEPT_OTHER }, + { "3", BT_HFP_CALL_ACTIVE_HELD }, + { "4", BT_HFP_CALL_QUITE }, +}; + +static int get_chld_feature(const char *name) +{ + struct _chld_feature *chld; + + for (size_t index = 0; index < ARRAY_SIZE(chld_feature_map); index++) { + chld = &chld_feature_map[index]; + if (!strncmp(name, chld->name, strlen(chld->name))) { + return chld->value; + } + } + + return -EINVAL; +} + +int chld_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + char *value; + uint32_t chld_features = 0; + int err; + + /* Parsing Example: CHLD: (0,1,2,3,4) */ + if (at_open_list(hf_at) < 0) { + LOG_ERR("Could not get open list"); + goto error; + } + + while (at_has_next_list(hf_at)) { + value = at_get_raw_string(hf_at, NULL); + if (!value) { + LOG_ERR("Could not get value"); + goto error; + } + + err = get_chld_feature(value); + if (err < 0) { + LOG_ERR("Cannot parse the value %s", value); + goto error; + } + + if (NUM_BITS(sizeof(chld_features)) > err) { + chld_features |= BIT(err); + } + } + + if (at_close_list(hf_at) < 0) { + LOG_ERR("Could not get close list"); + goto error; + } + + if (!((chld_features & BIT(BT_HFP_CHLD_RELEASE_ACTIVE_ACCEPT_OTHER)) && + (chld_features & BIT(BT_HFP_CALL_HOLD_ACTIVE_ACCEPT_OTHER)))) { + LOG_ERR("AT+CHLD values 1 and 2 should be supported by AG"); + goto error; + } + + hf->chld_features = chld_features; + + return 0; + +error: + LOG_ERR("Error on AT+CHLD=? response"); + hf_slc_error(hf_at); + return -EINVAL; +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +static const struct unsolicited { + const char *cmd; + enum at_cmd_type type; + int (*func)(struct at_client *hf_at); +} handlers[] = { + { "CIEV", AT_CMD_TYPE_UNSOLICITED, ciev_handle }, + { "RING", AT_CMD_TYPE_OTHER, ring_handle }, +#if defined(CONFIG_BT_HFP_HF_CLI) + { "CLIP", AT_CMD_TYPE_UNSOLICITED, clip_handle }, +#endif /* CONFIG_BT_HFP_HF_CLI */ +#if defined(CONFIG_BT_HFP_HF_VOLUME) + { "VGM", AT_CMD_TYPE_UNSOLICITED, vgm_handle }, + { "VGS", AT_CMD_TYPE_UNSOLICITED, vgs_handle }, +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + { "BSIR", AT_CMD_TYPE_UNSOLICITED, bsir_handle }, +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) + { "BCS", AT_CMD_TYPE_UNSOLICITED, bcs_handle }, +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + { "BTRH", AT_CMD_TYPE_UNSOLICITED, btrh_handle }, +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + { "CCWA", AT_CMD_TYPE_UNSOLICITED, ccwa_handle }, + { "CHLD", AT_CMD_TYPE_UNSOLICITED, chld_handle }, +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +#if defined(CONFIG_BT_HFP_HF_ECS) + { "CLCC", AT_CMD_TYPE_UNSOLICITED, clcc_handle }, +#endif /* CONFIG_BT_HFP_HF_ECS */ +}; + +static const struct unsolicited *hfp_hf_unsol_lookup(struct at_client *hf_at) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(handlers); i++) { + if (!strncmp(hf_at->buf, handlers[i].cmd, + strlen(handlers[i].cmd))) { + return &handlers[i]; + } + } + + return NULL; +} + +int unsolicited_cb(struct at_client *hf_at, struct net_buf *buf) +{ + const struct unsolicited *handler; + + handler = hfp_hf_unsol_lookup(hf_at); + if (!handler) { + LOG_ERR("Unhandled unsolicited response"); + return -ENOMSG; + } + + if (!at_parse_cmd_input(hf_at, buf, handler->cmd, handler->func, + handler->type)) { + return 0; + } + + return -ENOMSG; +} + +static int send_at_cmee(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + if (hf->ag_features & BT_HFP_AG_FEATURE_EXT_ERR) { + return hfp_hf_send_cmd(hf, NULL, cb, "AT+CMEE=1"); + } else { + return -ENOTSUP; + } +} + +static int at_cmee_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("CMEE set (result %d) on %p", result, hf); + + return 0; +} + +static int send_at_cops(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, NULL, cb, "AT+COPS=3,0"); +} + +static int at_cops_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("COPS set (result %d) on %p", result, hf); + + return 0; +} + +#if defined(CONFIG_BT_HFP_HF_CLI) +static int send_at_clip(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, NULL, cb, "AT+CLIP=1"); +} + +static int at_clip_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("CLIP set (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_CLI */ + +#if defined(CONFIG_BT_HFP_HF_VOLUME) +static int send_at_vgm(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, NULL, cb, "AT+VGM=%d", hf->vgm); +} + +static int at_vgm_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("VGM set (result %d) on %p", result, hf); + + return 0; +} + +static int send_at_vgs(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, NULL, cb, "AT+VGS=%d", hf->vgs); +} + +static int at_vgs_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("VGS set (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +static void get_codec_ids(struct bt_hfp_hf *hf, char *buffer, size_t buffer_len) +{ + size_t len = 0; + uint8_t ids = hf->hf_codec_ids; + int index = 0; + + while (ids && (len < (buffer_len-2))) { + if (ids & 0x01) { + buffer[len++] = index + '0'; + buffer[len++] = ','; + } + index ++; + ids = ids >> 1; + } + + if (len > 0) { + len --; + } + + buffer[len] = '\0'; +} + +static int send_at_bac(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + if (hf->ag_features & BT_HFP_AG_FEATURE_CODEC_NEG) { + char ids[sizeof(hf->hf_codec_ids)*2*8 + 1]; + get_codec_ids(hf, &ids[0], ARRAY_SIZE(ids)); + return hfp_hf_send_cmd(hf, NULL, cb, "AT+BAC=%s", ids); + } + + return -ENOTSUP; +} + +static int at_bac_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("BAC set (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int send_at_ccwa(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) { + return -ENOTSUP; + } + + if (!(hf->hf_features & BT_HFP_HF_FEATURE_3WAY_CALL)) { + return -ENOTSUP; + } + + return hfp_hf_send_cmd(hf, NULL, cb, "AT+CCWA=1"); +} + +static int at_ccwa_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("CCWA set (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +typedef int (*at_send_t)(struct bt_hfp_hf *hf, at_finish_cb_t cb); + +static struct at_cmd_init +{ + at_send_t send; + at_finish_cb_t finish; + bool disconnect; /* Disconnect if command failed. */ +} cmd_init_list[] = { +#if defined(CONFIG_BT_HFP_HF_VOLUME) + {send_at_vgm, at_vgm_finish, false}, + {send_at_vgs, at_vgs_finish, false}, +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + {send_at_cmee, at_cmee_finish, false}, + {send_at_cops, at_cops_finish, false}, +#if defined(CONFIG_BT_HFP_HF_CLI) + {send_at_clip, at_clip_finish, false}, +#endif /* CONFIG_BT_HFP_HF_CLI */ +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) + {send_at_bac, at_bac_finish, false}, +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + {send_at_ccwa, at_ccwa_finish, false}, +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +}; + +static int at_cmd_init_start(struct bt_hfp_hf *hf); + +static int at_cmd_init_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + at_finish_cb_t finish; + + if (result != AT_RESULT_OK) { + LOG_WRN("It is ERROR response of AT command %d.", hf->cmd_init_seq); + } + + if (ARRAY_SIZE(cmd_init_list) > hf->cmd_init_seq) { + finish = cmd_init_list[hf->cmd_init_seq].finish; + if (finish) { + (void)finish(hf_at, result, cme_err); + } + } else { + LOG_ERR("Invalid indicator (%d>=%d)", hf->cmd_init_seq, + ARRAY_SIZE(cmd_init_list)); + } + + /* Goto next AT command */ + hf->cmd_init_seq++; + (void)at_cmd_init_start(hf); + return 0; +} + +static int at_cmd_init_start(struct bt_hfp_hf *hf) +{ + at_send_t send; + int err = -EINVAL; + + while (ARRAY_SIZE(cmd_init_list) > hf->cmd_init_seq) { + LOG_DBG("Fetch AT command (%d)", hf->cmd_init_seq); + send = cmd_init_list[hf->cmd_init_seq].send; + if (send) { + LOG_DBG("Send AT command"); + err = send(hf, at_cmd_init_finish); + } else { + LOG_WRN("Invalid send func of AT command"); + } + + if (!err) { + break; + } + + LOG_WRN("AT command sending failed"); + if (cmd_init_list[hf->cmd_init_seq].disconnect) { + hfp_hf_send_failed(hf); + break; + } + /* Goto next AT command */ + LOG_WRN("Send next AT command"); + hf->cmd_init_seq++; + } + return err; +} + +static void slc_completed(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + struct bt_conn *conn = hf->acl; + + if (bt_hf->connected) { + bt_hf->connected(conn, hf); + } + + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED); + + /* Start with first AT command */ + hf->cmd_init_seq = 0; + if (at_cmd_init_start(hf)) { + LOG_ERR("Fail to start AT command initialization"); + } +} + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +int chld_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + if (result != AT_RESULT_OK) { + LOG_ERR("SLC Connection ERROR in response"); + hf_slc_error(hf_at); + return -EINVAL; + } + + slc_completed(hf_at); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +int cmer_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + + if (result != AT_RESULT_OK) { + LOG_ERR("SLC Connection ERROR in response"); + hf_slc_error(hf_at); + return -EINVAL; + } + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + if ((hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL) && + (hf->hf_features & BT_HFP_HF_FEATURE_3WAY_CALL)) { + err = hfp_hf_send_cmd(hf, NULL, chld_finish, "AT+CHLD=?"); + if (err < 0) { + hf_slc_error(hf_at); + } + return err; + } +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + + slc_completed(hf_at); + + return 0; +} + +int cind_status_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + + if (result != AT_RESULT_OK) { + LOG_ERR("SLC Connection ERROR in response"); + hf_slc_error(hf_at); + return -EINVAL; + } + + at_register_unsolicited(hf_at, unsolicited_cb); + err = hfp_hf_send_cmd(hf, NULL, cmer_finish, "AT+CMER=3,0,0,1"); + if (err < 0) { + hf_slc_error(hf_at); + return err; + } + + return 0; +} + +int cind_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + + if (result != AT_RESULT_OK) { + LOG_ERR("SLC Connection ERROR in response"); + hf_slc_error(hf_at); + return -EINVAL; + } + + err = hfp_hf_send_cmd(hf, cind_status_resp, cind_status_finish, + "AT+CIND?"); + if (err < 0) { + hf_slc_error(hf_at); + return err; + } + + return 0; +} + +int brsf_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + + if (result != AT_RESULT_OK) { + LOG_ERR("SLC Connection ERROR in response"); + hf_slc_error(hf_at); + return -EINVAL; + } + + err = hfp_hf_send_cmd(hf, cind_resp, cind_finish, "AT+CIND=?"); + if (err < 0) { + hf_slc_error(hf_at); + return err; + } + + return 0; +} + +int hf_slc_establish(struct bt_hfp_hf *hf) +{ + int err; + + LOG_DBG(""); + + err = hfp_hf_send_cmd(hf, brsf_resp, brsf_finish, "AT+BRSF=%u", + hf->hf_features); + if (err < 0) { + hf_slc_error(&hf->at); + return err; + } + + return 0; +} + +#if defined(CONFIG_BT_HFP_HF_CLI) +static int cli_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("CLIP set (result %d) on %p", result, hf); + + /* AT+CLI is done. */ + return 0; +} +#endif /* CONFIG_BT_HFP_HF_CLI */ + +int bt_hfp_hf_cli(struct bt_hfp_hf *hf, bool enable) +{ +#if defined(CONFIG_BT_HFP_HF_CLI) + int err; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + LOG_ERR("SLC is not established on %p", hf); + return -EINVAL; + } + + err = hfp_hf_send_cmd(hf, NULL, cli_finish, "AT+CLIP=%d", enable ? 1 : 0); + if (err < 0) { + LOG_ERR("HFP HF CLI set failed on %p", hf); + } + + return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_CLI */ +} + +#if defined(CONFIG_BT_HFP_HF_VOLUME) +static int vgm_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("VGM set (result %d) on %p", result, hf); + + /* AT+VGM is done. */ + return 0; +} +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + +int bt_hfp_hf_vgm(struct bt_hfp_hf *hf, uint8_t gain) +{ +#if defined(CONFIG_BT_HFP_HF_VOLUME) + int err; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (gain > BT_HFP_HF_VGM_GAIN_MAX) { + LOG_ERR("Invalid gain %d>%d", gain, BT_HFP_HF_VGM_GAIN_MAX); + return -EINVAL; + } + + hf->vgm = gain; + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + return 0; + } + + err = hfp_hf_send_cmd(hf, NULL, vgm_finish, "AT+VGM=%d", gain); + if (err < 0) { + LOG_ERR("HFP HF VGM set failed on %p", hf); + } + + return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_VOLUME */ +} + +#if defined(CONFIG_BT_HFP_HF_VOLUME) +static int vgs_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("VGS set (result %d) on %p", result, hf); + + /* AT+VGS is done. */ + return 0; +} +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + +int bt_hfp_hf_vgs(struct bt_hfp_hf *hf, uint8_t gain) +{ +#if defined(CONFIG_BT_HFP_HF_VOLUME) + int err; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (gain > BT_HFP_HF_VGM_GAIN_MAX) { + LOG_ERR("Invalid gain %d>%d", gain, BT_HFP_HF_VGM_GAIN_MAX); + return -EINVAL; + } + + hf->vgs = gain; + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + return 0; + } + + err = hfp_hf_send_cmd(hf, NULL, vgs_finish, "AT+VGS=%d", gain); + if (err < 0) { + LOG_ERR("HFP HF VGS set failed on %p", hf); + } + + return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_VOLUME */ +} + +static int cops_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + uint32_t mode; + uint32_t format; + char *operator; + int err; + + err = at_get_number(hf_at, &mode); + if (err < 0) { + LOG_ERR("Error getting value"); + return err; + } + + err = at_get_number(hf_at, &format); + if (err < 0) { + LOG_ERR("Error getting value"); + return err; + } + + operator = at_get_string(hf_at); + + if (bt_hf && bt_hf->operator) { + bt_hf->operator(hf, (uint8_t)mode, (uint8_t)format, operator); + } + + return 0; +} + +static int cops_resp(struct at_client *hf_at, struct net_buf *buf) +{ + int err; + + LOG_DBG(""); + + err = at_parse_cmd_input(hf_at, buf, "COPS", cops_handle, + AT_CMD_TYPE_NORMAL); + if (err < 0) { + LOG_ERR("Cannot parse response of AT+COPS?"); + return err; + } + + return 0; +} + +static int cops_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+COPS? (result %d) on %p", result, hf); + + return 0; +} + +int bt_hfp_hf_get_operator(struct bt_hfp_hf *hf) +{ + int err; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + return 0; + } + + err = hfp_hf_send_cmd(hf, cops_resp, cops_finish, "AT+COPS?"); + if (err < 0) { + LOG_ERR("Fail to read the currently selected operator on %p", hf); + } + + return err; +} + +static int ata_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("ATA (result %d) on %p", result, hf); + + return 0; +} + +static int btrh_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BTRH (result %d) on %p", result, hf); + + return 0; +} + +int bt_hfp_hf_accept(struct bt_hfp_hf_call *call) +{ + int err; + struct bt_hfp_hf *hf; + int count; + + LOG_DBG(""); + + if (!call) { + LOG_ERR("Invalid call"); + return -EINVAL; + } + + hf = call->hf; + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + LOG_ERR("No valid call"); + return -EINVAL; + } + + count = get_using_call_count(call->hf); + if (count > 1) { + LOG_ERR("Unsupported 3Way call"); + return -EINVAL; + } + + if (atomic_get(call->state) == BT_HFP_HF_CALL_STATE_WAITING) { + err = hfp_hf_send_cmd(hf, NULL, ata_finish, "ATA"); + if (err < 0) { + LOG_ERR("Fail to accept the incoming call on %p", hf); + } + return err; + } -static int at_cmd_init_start(struct bt_hfp_hf *hf); + if (atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD) && + (atomic_get(call->state) == BT_HFP_HF_CALL_STATE_ACTIVE)) { + err = hfp_hf_send_cmd(hf, NULL, btrh_finish, "AT+BTRH=%d", + BT_HFP_BTRH_ACCEPTED); + if (err < 0) { + LOG_ERR("Fail to accept the held incoming call on %p", hf); + } + return err; + } -static int at_cmd_init_finish(struct at_client *hf_at, enum at_result result, + return -EINVAL; +} + +static int chup_finish(struct at_client *hf_at, enum at_result result, enum at_cme cme_err) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - at_finish_cb_t finish; - - if (result != AT_RESULT_OK) { - LOG_WRN("It is ERROR response of AT command %d.", hf->cmd_init_seq); - } - if (ARRAY_SIZE(cmd_init_list) > hf->cmd_init_seq) { - finish = cmd_init_list[hf->cmd_init_seq].finish; - if (finish) { - (void)finish(hf_at, result, cme_err); - } - } else { - LOG_ERR("Invalid indicator (%d>=%d)", hf->cmd_init_seq, - ARRAY_SIZE(cmd_init_list)); - } + LOG_DBG("AT+CHUP (result %d) on %p", result, hf); - /* Goto next AT command */ - hf->cmd_init_seq++; - (void)at_cmd_init_start(hf); return 0; } -static int at_cmd_init_start(struct bt_hfp_hf *hf) +int bt_hfp_hf_reject(struct bt_hfp_hf_call *call) { - at_send_t send; - int err = -EINVAL; + int err; + struct bt_hfp_hf *hf; + int count; - while (ARRAY_SIZE(cmd_init_list) > hf->cmd_init_seq) { - LOG_DBG("Fetch AT command (%d)", hf->cmd_init_seq); - send = cmd_init_list[hf->cmd_init_seq].send; - if (send) { - LOG_DBG("Send AT command"); - err = send(hf, at_cmd_init_finish); - } else { - LOG_WRN("Invalid send func of AT command"); - } + LOG_DBG(""); - if (!err) { - break; + if (!call) { + LOG_ERR("Invalid call"); + return -EINVAL; + } + + hf = call->hf; + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + LOG_ERR("No valid call"); + return -EINVAL; + } + + count = get_using_call_count(call->hf); + if (count > 1) { + LOG_ERR("Unsupported 3Way call"); + return -EINVAL; + } + + if (atomic_get(call->state) == BT_HFP_HF_CALL_STATE_WAITING) { + err = hfp_hf_send_cmd(hf, NULL, chup_finish, "AT+CHUP"); + if (err < 0) { + LOG_ERR("Fail to reject the incoming call on %p", hf); } + return err; + } - LOG_WRN("AT command sending failed"); - if (cmd_init_list[hf->cmd_init_seq].disconnect) { - hfp_hf_send_failed(hf); - break; + if (atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD) && + (atomic_get(call->state) == BT_HFP_HF_CALL_STATE_ACTIVE)) { + err = hfp_hf_send_cmd(hf, NULL, btrh_finish, "AT+BTRH=%d", + BT_HFP_BTRH_REJECTED); + if (err < 0) { + LOG_ERR("Fail to reject the held incoming call on %p", hf); } - /* Goto next AT command */ - LOG_WRN("Send next AT command"); - hf->cmd_init_seq++; + return err; } - return err; + + return -EINVAL; } -static void slc_completed(struct at_client *hf_at) +int bt_hfp_hf_terminate(struct bt_hfp_hf_call *call) { - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - struct bt_conn *conn = hf->acl; + int err; + struct bt_hfp_hf *hf; + int count; - if (bt_hf->connected) { - bt_hf->connected(conn, hf); + LOG_DBG(""); + + if (!call) { + LOG_ERR("Invalid call"); + return -EINVAL; } - atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED); + hf = call->hf; + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } - /* Start with first AT command */ - hf->cmd_init_seq = 0; - if (at_cmd_init_start(hf)) { - LOG_ERR("Fail to start AT command initialization"); + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + LOG_ERR("No valid call"); + return -EINVAL; } -} -int cmer_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) -{ - if (result != AT_RESULT_OK) { - LOG_ERR("SLC Connection ERROR in response"); - hf_slc_error(hf_at); + count = get_using_call_count(call->hf); + if (count > 1) { + LOG_ERR("Unsupported 3Way call"); return -EINVAL; } - slc_completed(hf_at); + if (atomic_get(call->state) == BT_HFP_HF_CALL_STATE_HELD) { + LOG_ERR("Held call cannot be terminated"); + return -EINVAL; + } - return 0; + err = hfp_hf_send_cmd(hf, NULL, chup_finish, "AT+CHUP"); + if (err < 0) { + LOG_ERR("Fail to terminate the none held call on %p", hf); + } + + return err; } -int cind_status_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) +int bt_hfp_hf_hold_incoming(struct bt_hfp_hf_call *call) { - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); int err; + struct bt_hfp_hf *hf; + int count; - if (result != AT_RESULT_OK) { - LOG_ERR("SLC Connection ERROR in response"); - hf_slc_error(hf_at); + LOG_DBG(""); + + if (!call) { + LOG_ERR("Invalid call"); return -EINVAL; } - at_register_unsolicited(hf_at, unsolicited_cb); - err = hfp_hf_send_cmd(hf, NULL, cmer_finish, "AT+CMER=3,0,0,1"); - if (err < 0) { - hf_slc_error(hf_at); - return err; + hf = call->hf; + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; } - return 0; -} + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + LOG_ERR("No valid call"); + return -EINVAL; + } -int cind_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) -{ - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - int err; + count = get_using_call_count(call->hf); + if (count > 1) { + LOG_ERR("Unsupported 3Way call"); + return -EINVAL; + } - if (result != AT_RESULT_OK) { - LOG_ERR("SLC Connection ERROR in response"); - hf_slc_error(hf_at); + if (!(atomic_get(call->state) == BT_HFP_HF_CALL_STATE_WAITING)) { + LOG_ERR("No incoming call setup in progress"); return -EINVAL; } - err = hfp_hf_send_cmd(hf, cind_status_resp, cind_status_finish, - "AT+CIND?"); + err = hfp_hf_send_cmd(hf, NULL, btrh_finish, "AT+BTRH=%d", + BT_HFP_BTRH_ON_HOLD); if (err < 0) { - hf_slc_error(hf_at); - return err; + LOG_ERR("Fail to hold the incoming call on %p", hf); } - return 0; + return err; } -int brsf_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) +static int query_btrh_handle(struct at_client *hf_at) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); int err; + struct bt_hfp_hf_call *call; + uint32_t value; - if (result != AT_RESULT_OK) { - LOG_ERR("SLC Connection ERROR in response"); - hf_slc_error(hf_at); + err = at_get_number(hf_at, &value); + if (err < 0) { + LOG_ERR("Cannot get value"); + return err; + } + + if (value) { + LOG_ERR("Only support value 0"); + return 0; + } + + call = get_call_with_flag(hf, BT_HFP_HF_CALL_INCOMING_HELD); + if (!call) { + LOG_ERR("Held incoming call is not found"); return -EINVAL; } - err = hfp_hf_send_cmd(hf, cind_resp, cind_finish, "AT+CIND=?"); - if (err < 0) { - hf_slc_error(hf_at); - return err; + if (bt_hf->incoming_held) { + bt_hf->incoming_held(call); } return 0; } -int hf_slc_establish(struct bt_hfp_hf *hf) +static int query_btrh_resp(struct at_client *hf_at, struct net_buf *buf) { int err; - LOG_DBG(""); - - err = hfp_hf_send_cmd(hf, brsf_resp, brsf_finish, "AT+BRSF=%u", - hf->hf_features); + err = at_parse_cmd_input(hf_at, buf, "BTRH", query_btrh_handle, + AT_CMD_TYPE_NORMAL); if (err < 0) { - hf_slc_error(&hf->at); - return err; + LOG_ERR("Error parsing CMD input"); + hf_slc_error(hf_at); } return 0; } -#if defined(CONFIG_BT_HFP_HF_CLI) -static int cli_finish(struct at_client *hf_at, enum at_result result, +static int query_btrh_finish(struct at_client *hf_at, enum at_result result, enum at_cme cme_err) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - LOG_DBG("CLIP set (result %d) on %p", result, hf); + LOG_DBG("AT+BTRH? (result %d) on %p", result, hf); - /* AT+CLI is done. */ return 0; } -#endif /* CONFIG_BT_HFP_HF_CLI */ -int bt_hfp_hf_cli(struct bt_hfp_hf *hf, bool enable) +int bt_hfp_hf_query_respond_hold_status(struct bt_hfp_hf *hf) { -#if defined(CONFIG_BT_HFP_HF_CLI) int err; LOG_DBG(""); @@ -1126,38 +2451,86 @@ int bt_hfp_hf_cli(struct bt_hfp_hf *hf, bool enable) return -ENOTCONN; } - if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { - LOG_ERR("SLC is not established on %p", hf); - return -EINVAL; + err = hfp_hf_send_cmd(hf, query_btrh_resp, query_btrh_finish, "AT+BTRH?"); + if (err < 0) { + LOG_ERR("Fail to query respond and hold status of AG on %p", hf); } - err = hfp_hf_send_cmd(hf, NULL, cli_finish, "AT+CLIP=%d", enable ? 1 : 0); - if (err < 0) { - LOG_ERR("HFP HF CLI set failed on %p", hf); + return err; +} + +static int bt_hfp_ag_get_cme_err(enum at_cme cme_err) +{ + int err; + + switch (cme_err) { + case CME_ERROR_OPERATION_NOT_SUPPORTED: + err = -EOPNOTSUPP; + break; + case CME_ERROR_AG_FAILURE: + err = -EFAULT; + break; + case CME_ERROR_MEMORY_FAILURE: + err = -ENOSR; + break; + case CME_ERROR_MEMORY_FULL: + err = -ENOMEM; + break; + case CME_ERROR_DIAL_STRING_TO_LONG: + err = -ENAMETOOLONG; + break; + case CME_ERROR_INVALID_INDEX: + err = -EINVAL; + break; + case CME_ERROR_OPERATION_NOT_ALLOWED: + err = -ENOTSUP; + break; + case CME_ERROR_NO_CONNECTION_TO_PHONE: + err = -ENOTCONN; + break; + default: + err = -ENOTSUP; + break; } return err; -#else - return -ENOTSUP; -#endif /* CONFIG_BT_HFP_HF_CLI */ } -#if defined(CONFIG_BT_HFP_HF_VOLUME) -static int vgm_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) -{ - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); +static int atd_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + struct bt_hfp_hf_call *call; + + LOG_DBG("ATD (result %d) on %p", result, hf); + + if (result == AT_RESULT_CME_ERROR) { + err = bt_hfp_ag_get_cme_err(cme_err); + } else if (result == AT_RESULT_ERROR) { + err = -ENOTSUP; + } else { + err = 0; + } + + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_OUTGOING); + if (!call) { + return -EINVAL; + } - LOG_DBG("VGM set (result %d) on %p", result, hf); + if (err) { + free_call(call); + } - /* AT+VGM is done. */ + if (bt_hf && bt_hf->dialing) { + bt_hf->dialing(hf, err); + } return 0; } -#endif /* CONFIG_BT_HFP_HF_VOLUME */ -int bt_hfp_hf_vgm(struct bt_hfp_hf *hf, uint8_t gain) +int bt_hfp_hf_number_call(struct bt_hfp_hf *hf, const char *number) { -#if defined(CONFIG_BT_HFP_HF_VOLUME) + struct bt_hfp_hf_call *call; int err; LOG_DBG(""); @@ -1167,44 +2540,31 @@ int bt_hfp_hf_vgm(struct bt_hfp_hf *hf, uint8_t gain) return -ENOTCONN; } - if (gain > BT_HFP_HF_VGM_GAIN_MAX) { - LOG_ERR("Invalid gain %d>%d", gain, BT_HFP_HF_VGM_GAIN_MAX); - return -EINVAL; + call = get_dialing_call(hf); + if (call) { + LOG_ERR("There is a call in alerting or waiting"); + return -EBUSY; } - hf->vgm = gain; - - if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { - return 0; + call = get_new_call(hf); + if (!call) { + LOG_ERR("Not available call object"); + return -ENOMEM; } - err = hfp_hf_send_cmd(hf, NULL, vgm_finish, "AT+VGM=%d", gain); + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_OUTGOING); + + err = hfp_hf_send_cmd(hf, NULL, atd_finish, "ATD%s", number); if (err < 0) { - LOG_ERR("HFP HF VGM set failed on %p", hf); + LOG_ERR("Fail to start phone number call on %p", hf); } return err; -#else - return -ENOTSUP; -#endif /* CONFIG_BT_HFP_HF_VOLUME */ -} - -#if defined(CONFIG_BT_HFP_HF_VOLUME) -static int vgs_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) -{ - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - - LOG_DBG("VGS set (result %d) on %p", result, hf); - - /* AT+VGS is done. */ - return 0; } -#endif /* CONFIG_BT_HFP_HF_VOLUME */ -int bt_hfp_hf_vgs(struct bt_hfp_hf *hf, uint8_t gain) +int bt_hfp_hf_memory_dial(struct bt_hfp_hf *hf, const char *location) { -#if defined(CONFIG_BT_HFP_HF_VOLUME) + struct bt_hfp_hf_call *call; int err; LOG_DBG(""); @@ -1214,85 +2574,109 @@ int bt_hfp_hf_vgs(struct bt_hfp_hf *hf, uint8_t gain) return -ENOTCONN; } - if (gain > BT_HFP_HF_VGM_GAIN_MAX) { - LOG_ERR("Invalid gain %d>%d", gain, BT_HFP_HF_VGM_GAIN_MAX); - return -EINVAL; + call = get_dialing_call(hf); + if (call) { + LOG_ERR("There is a call in alerting or waiting"); + return -EBUSY; } - hf->vgs = gain; - - if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { - return 0; + call = get_new_call(hf); + if (!call) { + LOG_ERR("Not available call object"); + return -ENOMEM; } - err = hfp_hf_send_cmd(hf, NULL, vgs_finish, "AT+VGS=%d", gain); + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_OUTGOING); + + err = hfp_hf_send_cmd(hf, NULL, atd_finish, "ATD>%s", location); if (err < 0) { - LOG_ERR("HFP HF VGS set failed on %p", hf); + LOG_ERR("Fail to last number re-Dial on %p", hf); } return err; -#else - return -ENOTSUP; -#endif /* CONFIG_BT_HFP_HF_VOLUME */ } -static int cops_handle(struct at_client *hf_at) +static int bldn_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - uint32_t mode; - uint32_t format; - char *operator; int err; + struct bt_hfp_hf_call *call; - err = at_get_number(hf_at, &mode); - if (err < 0) { - LOG_ERR("Error getting value"); - return err; - } + LOG_DBG("AT+BLDN (result %d) on %p", result, hf); - err = at_get_number(hf_at, &format); - if (err < 0) { - LOG_ERR("Error getting value"); - return err; + if (result == AT_RESULT_CME_ERROR) { + err = bt_hfp_ag_get_cme_err(cme_err); + } else if (result == AT_RESULT_ERROR) { + err = -ENOTSUP; + } else { + err = 0; } - operator = at_get_string(hf_at); + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_OUTGOING); + if (!call) { + return -EINVAL; + } - if (bt_hf && bt_hf->operator) { - bt_hf->operator(hf, (uint8_t)mode, (uint8_t)format, operator); + if (err) { + free_call(call); } + if (bt_hf && bt_hf->dialing) { + bt_hf->dialing(hf, err); + } return 0; } -static int cops_resp(struct at_client *hf_at, struct net_buf *buf) +int bt_hfp_hf_redial(struct bt_hfp_hf *hf) { + struct bt_hfp_hf_call *call; int err; LOG_DBG(""); - err = at_parse_cmd_input(hf_at, buf, "COPS", cops_handle, - AT_CMD_TYPE_NORMAL); + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + call = get_dialing_call(hf); + if (call) { + LOG_ERR("There is a call in alerting or waiting"); + return -EBUSY; + } + + call = get_new_call(hf); + if (!call) { + LOG_ERR("Not available call object"); + return -ENOMEM; + } + + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_OUTGOING); + + err = hfp_hf_send_cmd(hf, NULL, bldn_finish, "AT+BLDN"); if (err < 0) { - LOG_ERR("Cannot parse response of AT+COPS?"); - return err; + LOG_ERR("Fail to start memory dialing on %p", hf); } - return 0; + return err; } -static int cops_finish(struct at_client *hf_at, enum at_result result, +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +static int bcc_finish(struct at_client *hf_at, enum at_result result, enum at_cme cme_err) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - LOG_DBG("AT+COPS? (result %d) on %p", result, hf); + LOG_DBG("BCC (result %d) on %p", result, hf); return 0; } +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ -int bt_hfp_hf_get_operator(struct bt_hfp_hf *hf) +int bt_hfp_hf_audio_connect(struct bt_hfp_hf *hf) { +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) int err; LOG_DBG(""); @@ -1302,42 +2686,37 @@ int bt_hfp_hf_get_operator(struct bt_hfp_hf *hf) return -ENOTCONN; } - if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { - return 0; + if (hf->chan.sco) { + LOG_ERR("Audio conenction has been connected"); + return -ECONNREFUSED; } - err = hfp_hf_send_cmd(hf, cops_resp, cops_finish, "AT+COPS?"); + err = hfp_hf_send_cmd(hf, NULL, bcc_finish, "AT+BCC"); if (err < 0) { - LOG_ERR("Fail to read the currently selected operator on %p", hf); + LOG_ERR("Fail to setup audio connection on %p", hf); } return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ } -static int ata_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) -{ - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - - LOG_DBG("ATA (result %d) on %p", result, hf); - - return 0; -} - -static int btrh_finish(struct at_client *hf_at, enum at_result result, +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +static int bcs_finish(struct at_client *hf_at, enum at_result result, enum at_cme cme_err) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - LOG_DBG("AT+BTRH (result %d) on %p", result, hf); + LOG_DBG("BCS (result %d) on %p", result, hf); return 0; } +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ -int bt_hfp_hf_accept(struct bt_hfp_hf *hf) +int bt_hfp_hf_select_codec(struct bt_hfp_hf *hf, uint8_t codec_id) { - int err; - +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) LOG_DBG(""); if (!hf) { @@ -1345,40 +2724,38 @@ int bt_hfp_hf_accept(struct bt_hfp_hf *hf) return -ENOTCONN; } - if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING)) { - err = hfp_hf_send_cmd(hf, NULL, ata_finish, "ATA"); - if (err < 0) { - LOG_ERR("Fail to accept the incoming call on %p", hf); - } - return err; + if (!(hf->hf_codec_ids & BIT(codec_id))) { + LOG_ERR("Codec ID is unsupported"); + return -ENOTSUP; } - if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING_HELD) && - atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_ACTIVE)) { - err = hfp_hf_send_cmd(hf, NULL, btrh_finish, "AT+BTRH=%d", - BT_HFP_BTRH_ACCEPTED); - if (err < 0) { - LOG_ERR("Fail to accept the held incoming call on %p", hf); - } - return err; + if (!atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_CODEC_CONN)) { + LOG_ERR("Invalid context"); + return -ESRCH; } - return -EINVAL; + return hfp_hf_send_cmd(hf, NULL, bcs_finish, "AT+BCS=%d", codec_id); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ } -static int chup_finish(struct at_client *hf_at, enum at_result result, +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +static int bac_finish(struct at_client *hf_at, enum at_result result, enum at_cme cme_err) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - LOG_DBG("AT+CHUP (result %d) on %p", result, hf); + LOG_DBG("BAC (result %d) on %p", result, hf); return 0; } +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ -int bt_hfp_hf_reject(struct bt_hfp_hf *hf) +int bt_hfp_hf_set_codecs(struct bt_hfp_hf *hf, uint8_t codec_ids) { - int err; +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) + char ids[sizeof(hf->hf_codec_ids)*2*8 + 1]; LOG_DBG(""); @@ -1387,31 +2764,48 @@ int bt_hfp_hf_reject(struct bt_hfp_hf *hf) return -ENOTCONN; } - if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING)) { - err = hfp_hf_send_cmd(hf, NULL, chup_finish, "AT+CHUP"); - if (err < 0) { - LOG_ERR("Fail to reject the incoming call on %p", hf); - } - return err; + if (codec_ids & BT_HFP_HF_CODEC_CVSD) { + LOG_ERR("CVSD should be supported"); + return -EINVAL; } - if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING_HELD) && - atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_ACTIVE)) { - err = hfp_hf_send_cmd(hf, NULL, btrh_finish, "AT+BTRH=%d", - BT_HFP_BTRH_REJECTED); - if (err < 0) { - LOG_ERR("Fail to reject the held incoming call on %p", hf); - } - return err; - } + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_CODEC_CONN); + hf->hf_codec_ids = codec_ids; - return -EINVAL; + get_codec_ids(hf, &ids[0], ARRAY_SIZE(ids)); + return hfp_hf_send_cmd(hf, NULL, bac_finish, "AT+BAC=%s", ids); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ } -int bt_hfp_hf_terminate(struct bt_hfp_hf *hf) +#if defined(CONFIG_BT_HFP_HF_ECNR) +static int nrec_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) { + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); int err; + LOG_DBG("AT+NREC=0 (result %d) on %p", result, hf); + + if (result == AT_RESULT_CME_ERROR) { + err = bt_hfp_ag_get_cme_err(cme_err); + } else if (result == AT_RESULT_ERROR) { + err = -ENOTSUP; + } else { + err = 0; + } + + if (bt_hf && bt_hf->ecnr_turn_off) { + bt_hf->ecnr_turn_off(hf, err); + } + return 0; +} +#endif /* CONFIG_BT_HFP_HF_ECNR */ + +int bt_hfp_hf_turn_off_ecnr(struct bt_hfp_hf *hf) +{ +#if defined(CONFIG_BT_HFP_HF_ECNR) LOG_DBG(""); if (!hf) { @@ -1419,18 +2813,37 @@ int bt_hfp_hf_terminate(struct bt_hfp_hf *hf) return -ENOTCONN; } - err = hfp_hf_send_cmd(hf, NULL, chup_finish, "AT+CHUP"); - if (err < 0) { - LOG_ERR("Fail to terminate the active call on %p", hf); - } + if (!(hf->ag_features & BT_HFP_AG_FEATURE_ECNR)) { + LOG_ERR("EC and/or NR functions is unsupported by AG"); + return -ENOTSUP; + } + + if (hf->chan.sco) { + LOG_ERR("Audio conenction has been connected"); + return -EBUSY; + } + + return hfp_hf_send_cmd(hf, NULL, nrec_finish, "AT+NREC=0"); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_ECNR */ +} + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int ccwa_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - return err; + LOG_DBG("AT+CCWA (result %d) on %p", result, hf); + + return 0; } +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ -int bt_hfp_hf_hold_incoming(struct bt_hfp_hf *hf) +int bt_hfp_hf_call_waiting_notify(struct bt_hfp_hf *hf, bool enable) { - int err; - +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) LOG_DBG(""); if (!hf) { @@ -1438,39 +2851,34 @@ int bt_hfp_hf_hold_incoming(struct bt_hfp_hf *hf) return -ENOTCONN; } - if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING)) { - LOG_ERR("No incoming call setup in progress"); - return -EINVAL; - } - - err = hfp_hf_send_cmd(hf, NULL, btrh_finish, "AT+BTRH=%d", - BT_HFP_BTRH_ON_HOLD); - if (err < 0) { - LOG_ERR("Fail to hold the incoming call on %p", hf); + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; } - return err; + return hfp_hf_send_cmd(hf, NULL, ccwa_finish, "AT+CCWA=%d", enable ? 1 : 0); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ } -static int query_btrh_finish(struct at_client *hf_at, enum at_result result, +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int chld_release_all_held_finish(struct at_client *hf_at, enum at_result result, enum at_cme cme_err) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - LOG_DBG("AT+BTRH? (result %d) on %p", result, hf); + LOG_DBG("AT+CHLD=0 (result %d) on %p", result, hf); - if (atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_QUERY_HOLD) && - bt_hf->incoming_held) { - bt_hf->incoming_held(hf); - } + k_work_reschedule(&hf->deferred_work, K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); return 0; } +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ -int bt_hfp_hf_query_respond_hold_status(struct bt_hfp_hf *hf) +int bt_hfp_hf_release_all_held(struct bt_hfp_hf *hf) { - int err; - +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) LOG_DBG(""); if (!hf) { @@ -1478,83 +2886,39 @@ int bt_hfp_hf_query_respond_hold_status(struct bt_hfp_hf *hf) return -ENOTCONN; } - atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_QUERY_HOLD); - - err = hfp_hf_send_cmd(hf, NULL, query_btrh_finish, "AT+BTRH?"); - if (err < 0) { - LOG_ERR("Fail to query respond and hold status of AG on %p", hf); + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; } - return err; -} - -static int bt_hfp_ag_get_cme_err(enum at_cme cme_err) -{ - int err; - - switch (cme_err) { - case CME_ERROR_OPERATION_NOT_SUPPORTED: - err = -EOPNOTSUPP; - break; - case CME_ERROR_AG_FAILURE: - err = -EFAULT; - break; - case CME_ERROR_MEMORY_FAILURE: - err = -ENOSR; - break; - case CME_ERROR_MEMORY_FULL: - err = -ENOMEM; - break; - case CME_ERROR_DIAL_STRING_TO_LONG: - err = -ENAMETOOLONG; - break; - case CME_ERROR_INVALID_INDEX: - err = -EINVAL; - break; - case CME_ERROR_OPERATION_NOT_ALLOWED: - err = -ENOTSUP; - break; - case CME_ERROR_NO_CONNECTION_TO_PHONE: - err = -ENOTCONN; - break; - default: - err = -ENOTSUP; - break; + if (!(hf->chld_features & BIT(BT_HFP_CHLD_RELEASE_ALL))) { + LOG_ERR("Releasing all held calls is unsupported by AG"); + return -ENOTSUP; } - return err; + return hfp_hf_send_cmd(hf, NULL, chld_release_all_held_finish, "AT+CHLD=0"); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ } -static int atd_finish(struct at_client *hf_at, enum at_result result, +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int chld_set_udub_finish(struct at_client *hf_at, enum at_result result, enum at_cme cme_err) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - int err; - - LOG_DBG("ATD (result %d) on %p", result, hf); - if (!atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_DIALING)) { - LOG_WRN("No dialing call"); - } + LOG_DBG("AT+CHLD=0 (result %d) on %p", result, hf); - if (result == AT_RESULT_CME_ERROR) { - err = bt_hfp_ag_get_cme_err(cme_err); - } else if (result == AT_RESULT_ERROR) { - err = -ENOTSUP; - } else { - err = 0; - } + k_work_reschedule(&hf->deferred_work, K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); - if (bt_hf && bt_hf->dialing) { - bt_hf->dialing(hf, err); - } return 0; } +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ -int bt_hfp_hf_number_call(struct bt_hfp_hf *hf, const char *number) +int bt_hfp_hf_set_udub(struct bt_hfp_hf *hf) { - int err; - +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) LOG_DBG(""); if (!hf) { @@ -1562,23 +2926,39 @@ int bt_hfp_hf_number_call(struct bt_hfp_hf *hf, const char *number) return -ENOTCONN; } - if (atomic_test_and_set_bit(hf->flags, BT_HFP_HF_FLAG_DIALING)) { - LOG_ERR("Outgoing call is started"); - return -EBUSY; + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; } - err = hfp_hf_send_cmd(hf, NULL, atd_finish, "ATD%s", number); - if (err < 0) { - LOG_ERR("Fail to start phone number call on %p", hf); + if (!(hf->chld_features & BIT(BT_HFP_CHLD_RELEASE_ALL))) { + LOG_ERR("UDUB is unsupported by AG"); + return -ENOTSUP; } - return err; + return hfp_hf_send_cmd(hf, NULL, chld_set_udub_finish, "AT+CHLD=0"); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ } -int bt_hfp_hf_memory_dial(struct bt_hfp_hf *hf, const char *location) +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int chld_release_active_accept_other_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) { - int err; + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+CHLD=1 (result %d) on %p", result, hf); + k_work_reschedule(&hf->deferred_work, K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +int bt_hfp_hf_release_active_accept_other(struct bt_hfp_hf *hf) +{ +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) LOG_DBG(""); if (!hf) { @@ -1586,49 +2966,35 @@ int bt_hfp_hf_memory_dial(struct bt_hfp_hf *hf, const char *location) return -ENOTCONN; } - if (atomic_test_and_set_bit(hf->flags, BT_HFP_HF_FLAG_DIALING)) { - LOG_ERR("Outgoing call is started"); - return -EBUSY; - } - - err = hfp_hf_send_cmd(hf, NULL, atd_finish, "ATD>%s", location); - if (err < 0) { - LOG_ERR("Fail to start memory dialing on %p", hf); + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; } - return err; + return hfp_hf_send_cmd(hf, NULL, chld_release_active_accept_other_finish, + "AT+CHLD=1"); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ } -static int bldn_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int chld_hold_active_accept_other_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - int err; - - LOG_DBG("AT+BLDN (result %d) on %p", result, hf); - if (!atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_DIALING)) { - LOG_WRN("No dialing call"); - } + LOG_DBG("AT+CHLD=2 (result %d) on %p", result, hf); - if (result == AT_RESULT_CME_ERROR) { - err = bt_hfp_ag_get_cme_err(cme_err); - } else if (result == AT_RESULT_ERROR) { - err = -ENOTSUP; - } else { - err = 0; - } + k_work_reschedule(&hf->deferred_work, K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); - if (bt_hf && bt_hf->dialing) { - bt_hf->dialing(hf, err); - } return 0; } +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ -int bt_hfp_hf_redial(struct bt_hfp_hf *hf) +int bt_hfp_hf_hold_active_accept_other(struct bt_hfp_hf *hf) { - int err; - +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) LOG_DBG(""); if (!hf) { @@ -1636,36 +3002,35 @@ int bt_hfp_hf_redial(struct bt_hfp_hf *hf) return -ENOTCONN; } - if (atomic_test_and_set_bit(hf->flags, BT_HFP_HF_FLAG_DIALING)) { - LOG_ERR("Outgoing call is started"); - return -EBUSY; - } - - err = hfp_hf_send_cmd(hf, NULL, bldn_finish, "AT+BLDN"); - if (err < 0) { - LOG_ERR("Fail to start memory dialing on %p", hf); + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; } - return err; + return hfp_hf_send_cmd(hf, NULL, chld_hold_active_accept_other_finish, + "AT+CHLD=2"); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ } -#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) -static int bcc_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int chld_join_conversation_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - LOG_DBG("BCC (result %d) on %p", result, hf); + LOG_DBG("AT+CHLD=3 (result %d) on %p", result, hf); + + k_work_reschedule(&hf->deferred_work, K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); return 0; } -#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ -int bt_hfp_hf_audio_connect(struct bt_hfp_hf *hf) +int bt_hfp_hf_join_conversation(struct bt_hfp_hf *hf) { -#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) - int err; - +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) LOG_DBG(""); if (!hf) { @@ -1673,37 +3038,40 @@ int bt_hfp_hf_audio_connect(struct bt_hfp_hf *hf) return -ENOTCONN; } - if (hf->chan.sco) { - LOG_ERR("Audio conenction has been connected"); - return -ECONNREFUSED; + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; } - err = hfp_hf_send_cmd(hf, NULL, bcc_finish, "AT+BCC"); - if (err < 0) { - LOG_ERR("Fail to setup audio connection on %p", hf); + if (!(hf->chld_features & BIT(BT_HFP_CALL_ACTIVE_HELD))) { + LOG_ERR("Adding a held call to the conversation is unsupported by AG"); + return -ENOTSUP; } - return err; + return hfp_hf_send_cmd(hf, NULL, chld_join_conversation_finish, + "AT+CHLD=3"); #else return -ENOTSUP; -#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ } -#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) -static int bcs_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int chld_explicit_call_transfer_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - LOG_DBG("BCC (result %d) on %p", result, hf); + LOG_DBG("AT+CHLD=4 (result %d) on %p", result, hf); + + k_work_reschedule(&hf->deferred_work, K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); return 0; } -#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ -int bt_hfp_hf_select_codec(struct bt_hfp_hf *hf, uint8_t codec_id) +int bt_hfp_hf_explicit_call_transfer(struct bt_hfp_hf *hf) { -#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) LOG_DBG(""); if (!hf) { @@ -1711,109 +3079,149 @@ int bt_hfp_hf_select_codec(struct bt_hfp_hf *hf, uint8_t codec_id) return -ENOTCONN; } - if (!(hf->hf_codec_ids & BIT(codec_id))) { - LOG_ERR("Codec ID is unsupported"); + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) { + LOG_ERR("Three-way calling is unsupported by AG"); return -ENOTSUP; } - if (!atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_CODEC_CONN)) { - LOG_ERR("Invalid context"); - return -ESRCH; + if (!(hf->chld_features & BIT(BT_HFP_CALL_QUITE))) { + LOG_ERR("Expliciting Call Transfer is unsupported by AG"); + return -ENOTSUP; } - return hfp_hf_send_cmd(hf, NULL, bcs_finish, "AT+BCS=%d", codec_id); + return hfp_hf_send_cmd(hf, NULL, chld_explicit_call_transfer_finish, + "AT+CHLD=4"); #else return -ENOTSUP; -#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ } -#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) -static int bac_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) +#if defined(CONFIG_BT_HFP_HF_ECC) +static int chld_release_specified_call_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - LOG_DBG("BCC (result %d) on %p", result, hf); + LOG_DBG("AT+CHLD=1 (result %d) on %p", result, hf); + + k_work_reschedule(&hf->deferred_work, K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); return 0; } -#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ +#endif /* CONFIG_BT_HFP_HF_ECC */ -int bt_hfp_hf_set_codecs(struct bt_hfp_hf *hf, uint8_t codec_ids) +int bt_hfp_hf_release_specified_call(struct bt_hfp_hf_call *call) { -#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) - char ids[sizeof(hf->hf_codec_ids)*2*8 + 1]; +#if defined(CONFIG_BT_HFP_HF_ECC) + struct bt_hfp_hf *hf; LOG_DBG(""); + if (!call) { + LOG_ERR("Invalid call"); + return -ENOTCONN; + } + + hf = call->hf; if (!hf) { LOG_ERR("No HF connection found"); return -ENOTCONN; } - if (codec_ids & BT_HFP_HF_CODEC_CVSD) { - LOG_ERR("CVSD should be supported"); - return -EINVAL; + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; } - atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_CODEC_CONN); - hf->hf_codec_ids = codec_ids; + if (!(hf->ag_features & BT_HFP_AG_FEATURE_ECC)) { + LOG_ERR("Enhanced Call Control is unsupported by AG"); + return -ENOTSUP; + } - get_codec_ids(hf, &ids[0], ARRAY_SIZE(ids)); - return hfp_hf_send_cmd(hf, NULL, bac_finish, "AT+BAC=%s", ids); + if (!(hf->hf_features & BT_HFP_HF_FEATURE_ECC)) { + LOG_ERR("Enhanced Call Control is unsupported by HF"); + return -ENOTSUP; + } + + if (!(hf->chld_features & BIT(BT_HFP_CALL_RELEASE_SPECIFIED_ACTIVE))) { + LOG_ERR("Releasing a specific active call is unsupported by AG"); + return -ENOTSUP; + } + + if (!call->index) { + LOG_ERR("Invalid call index"); + return -EINVAL; + } + + return hfp_hf_send_cmd(hf, NULL, chld_release_specified_call_finish, + "AT+CHLD=1%d", call->index); #else return -ENOTSUP; -#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ +#endif /* CONFIG_BT_HFP_HF_ECC */ } -#if defined(CONFIG_BT_HFP_HF_ECNR) -static int nrec_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) +#if defined(CONFIG_BT_HFP_HF_ECC) +static int chld_private_consultation_mode_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - int err; - LOG_DBG("AT+NREC=0 (result %d) on %p", result, hf); + LOG_DBG("AT+CHLD=2 (result %d) on %p", result, hf); - if (result == AT_RESULT_CME_ERROR) { - err = bt_hfp_ag_get_cme_err(cme_err); - } else if (result == AT_RESULT_ERROR) { - err = -ENOTSUP; - } else { - err = 0; - } + k_work_reschedule(&hf->deferred_work, K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); - if (bt_hf && bt_hf->ecnr_turn_off) { - bt_hf->ecnr_turn_off(hf, err); - } return 0; } -#endif /* CONFIG_BT_HFP_HF_ECNR */ +#endif /* CONFIG_BT_HFP_HF_ECC */ -int bt_hfp_hf_turn_off_ecnr(struct bt_hfp_hf *hf) +int bt_hfp_hf_private_consultation_mode(struct bt_hfp_hf_call *call) { -#if defined(CONFIG_BT_HFP_HF_ECNR) +#if defined(CONFIG_BT_HFP_HF_ECC) + struct bt_hfp_hf *hf; + LOG_DBG(""); + if (!call) { + LOG_ERR("Invalid call"); + return -ENOTCONN; + } + + hf = call->hf; if (!hf) { LOG_ERR("No HF connection found"); return -ENOTCONN; } - if (!(hf->ag_features & BT_HFP_AG_FEATURE_ECNR)) { - LOG_ERR("EC and/or NR functions is unsupported by AG"); + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) { + LOG_ERR("Three-way calling is unsupported by AG"); return -ENOTSUP; } - if (hf->chan.sco) { - LOG_ERR("Audio conenction has been connected"); - return -EBUSY; + if (!(hf->ag_features & BT_HFP_AG_FEATURE_ECC)) { + LOG_ERR("Enhanced Call Control is unsupported by AG"); + return -ENOTSUP; } - return hfp_hf_send_cmd(hf, NULL, nrec_finish, "AT+NREC=0"); + if (!(hf->hf_features & BT_HFP_HF_FEATURE_ECC)) { + LOG_ERR("Enhanced Call Control is unsupported by HF"); + return -ENOTSUP; + } + + if (!(hf->chld_features & BIT(BT_HFP_CALL_PRIVATE_CNLTN_MODE))) { + LOG_ERR("Private Consultation Mode is unsupported by AG"); + return -ENOTSUP; + } + + if (!call->index) { + LOG_ERR("Invalid call index"); + return -EINVAL; + } + + return hfp_hf_send_cmd(hf, NULL, chld_private_consultation_mode_finish, + "AT+CHLD=2%d", call->index); #else return -ENOTSUP; -#endif /* CONFIG_BT_HFP_HF_ECNR */ +#endif /* CONFIG_BT_HFP_HF_ECC */ } static void hfp_hf_connected(struct bt_rfcomm_dlc *dlc) @@ -1890,6 +3298,8 @@ static int hfp_hf_accept(struct bt_conn *conn, struct bt_rfcomm_server *server, k_fifo_init(&hf->tx_pending); + k_work_init_delayable(&hf->deferred_work, bt_hf_deferred_work); + for (j = 0; j < HF_MAX_AG_INDICATORS; j++) { hf->ind_table[j] = -1; } diff --git a/subsys/bluetooth/host/classic/hfp_internal.h b/subsys/bluetooth/host/classic/hfp_internal.h index 19a1e2c819206..ee6094c40d644 100644 --- a/subsys/bluetooth/host/classic/hfp_internal.h +++ b/subsys/bluetooth/host/classic/hfp_internal.h @@ -82,18 +82,42 @@ #define BT_HFP_HF_SDP_FEATURE_ECNR_ENABLE 0 #endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +#define BT_HFP_HF_FEATURE_3WAY_CALL_ENABLE BT_HFP_HF_FEATURE_3WAY_CALL +#define BT_HFP_HF_SDP_FEATURE_3WAY_CALL_ENABLE BT_HFP_HF_SDP_FEATURE_3WAY_CALL +#else +#define BT_HFP_HF_FEATURE_3WAY_CALL_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_3WAY_CALL_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +#if defined(CONFIG_BT_HFP_HF_ECS) +#define BT_HFP_HF_FEATURE_ECS_ENABLE BT_HFP_HF_FEATURE_ECS +#else +#define BT_HFP_HF_FEATURE_ECS_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_ECS */ + +#if defined(CONFIG_BT_HFP_HF_ECC) +#define BT_HFP_HF_FEATURE_ECC_ENABLE BT_HFP_HF_FEATURE_ECC +#else +#define BT_HFP_HF_FEATURE_ECC_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_ECC */ + /* HFP HF Supported features */ #define BT_HFP_HF_SUPPORTED_FEATURES (\ BT_HFP_HF_FEATURE_CLI_ENABLE | \ BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE |\ BT_HFP_HF_CODEC_NEG_ENABLE | \ - BT_HFP_HF_FEATURE_ECNR_ENABLE) + BT_HFP_HF_FEATURE_ECNR_ENABLE | \ + BT_HFP_HF_FEATURE_3WAY_CALL_ENABLE | \ + BT_HFP_HF_FEATURE_ECS_ENABLE | \ + BT_HFP_HF_FEATURE_ECC_ENABLE) /* HFP HF Supported features in SDP */ #define BT_HFP_HF_SDP_SUPPORTED_FEATURES (\ BT_HFP_HF_SDP_FEATURE_CLI_ENABLE | \ BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE | \ - BT_HFP_HF_SDP_FEATURE_ECNR_ENABLE) + BT_HFP_HF_SDP_FEATURE_ECNR_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_3WAY_CALL_ENABLE) #define BT_HFP_HF_CODEC_CVSD_MASK BIT(BT_HFP_HF_CODEC_CVSD) @@ -128,16 +152,48 @@ enum { BT_HFP_HF_FLAG_CONNECTED, /* HFP HF SLC Established */ BT_HFP_HF_FLAG_TX_ONGOING, /* HFP HF TX is ongoing */ - BT_HFP_HF_FLAG_INCOMING, /* HFP HF call incoming */ - BT_HFP_HF_FLAG_INCOMING_HELD, /* HFP HF call incoming is held */ - BT_HFP_HF_FLAG_QUERY_HOLD, /* HFP HF query response and hold status */ - BT_HFP_HF_FLAG_ACTIVE, /* HFP HF call active */ - BT_HFP_HF_FLAG_DIALING, /* HFP HF call dialing */ BT_HFP_HF_FLAG_CODEC_CONN, /* HFP HF codec connection setup */ + BT_HFP_HF_FLAG_CLCC_PENDING, /* HFP HF CLCC is pending */ /* Total number of flags - must be at the end of the enum */ BT_HFP_HF_NUM_FLAGS, }; +/* bt_hfp_hf_call flags: the flags defined here represent hfp hf call parameters */ +enum { + BT_HFP_HF_CALL_IN_USING, /* Object is in using */ + BT_HFP_HF_CALL_CLCC, /* CLCC report received */ + BT_HFP_HF_CALL_INCOMING, /* Incoming call */ + BT_HFP_HF_CALL_INCOMING_HELD, /* Incoming call held */ + BT_HFP_HF_CALL_OUTGOING_3WAY, /* Outgoing 3 way call */ + BT_HFP_HF_CALL_INCOMING_3WAY, /* Incoming 3 way call */ + + /* Total number of flags - must be at the end of the enum */ + BT_HFP_HF_CALL_NUM_FLAGS, +}; + +/* bt_hfp_hf_call state: the flags defined here represent hfp hf call state parameters */ +enum { + /* Call state flags */ + BT_HFP_HF_CALL_STATE_TERMINATE, /* Call terminate */ + BT_HFP_HF_CALL_STATE_OUTGOING, /* Call outgoing */ + BT_HFP_HF_CALL_STATE_INCOMING, /* Call incoming */ + BT_HFP_HF_CALL_STATE_ALERTING, /* Call alerting */ + BT_HFP_HF_CALL_STATE_WAITING, /* Call waiting */ + BT_HFP_HF_CALL_STATE_ACTIVE, /* Call active */ + BT_HFP_HF_CALL_STATE_HELD, /* Call held */ + + /* Total number of flags - must be at the end of the enum */ + BT_HFP_HF_CALL_STATE_NUM_FLAGS, +}; + +struct bt_hfp_hf_call { + struct bt_hfp_hf *hf; + uint8_t index; + + ATOMIC_DEFINE(flags, BT_HFP_HF_CALL_NUM_FLAGS); + ATOMIC_DEFINE(state, BT_HFP_HF_CALL_STATE_NUM_FLAGS); +}; + struct bt_hfp_hf { struct bt_rfcomm_dlc rfcomm_dlc; /* ACL connection handle */ @@ -159,6 +215,14 @@ struct bt_hfp_hf { /* AT command initialization indicator */ uint8_t cmd_init_seq; + /* The features supported by AT+CHLD */ + uint32_t chld_features; + + struct k_work_delayable deferred_work; + + /* calls */ + struct bt_hfp_hf_call calls[CONFIG_BT_HFP_HF_MAX_CALLS]; + ATOMIC_DEFINE(flags, BT_HFP_HF_NUM_FLAGS); }; @@ -214,6 +278,13 @@ enum hfp_hf_ag_indicators { * optional for the HF. */ #define BT_HFP_CALL_QUITE 4 +/* Release a specific active call */ +#define BT_HFP_CALL_RELEASE_SPECIFIED_ACTIVE 10 +/* Private Consultation Mode + * place all parties of a multiparty call on hold with the + * exception of the specified call. + */ +#define BT_HFP_CALL_PRIVATE_CNLTN_MODE 20 /* Active */ #define BT_HFP_CLCC_STATUS_ACTIVE 0 From 2e09d894fe8b686bfede3801a0911f7cabb95904 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Tue, 20 Aug 2024 20:04:53 +0800 Subject: [PATCH 41/76] Bluetooth: HFP_HF: Avoid at status overwrote by sending There is an issue that the sending of AT+command will overwrite the at status when the at is handling receiving. Add a flag `BT_HFP_HF_FLAG_RX_ONGOING` to flag the receiving is working. When the flag is set, suspend the TX sending. Add a worker to restart the tx sending. After the flag `BT_HFP_HF_FLAG_RX_ONGOING` is cleared, submit the TX worker to restart the tx sending. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/hfp_hf.c | 16 ++++++++++++++++ subsys/bluetooth/host/classic/hfp_internal.h | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index d9f69d13664ee..43589a209e378 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -207,6 +207,10 @@ static void hfp_hf_send_data(struct bt_hfp_hf *hf) at_finish_cb_t finish; int err; + if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_RX_ONGOING)) { + return; + } + if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_TX_ONGOING)) { return; } @@ -3248,9 +3252,12 @@ static void hfp_hf_recv(struct bt_rfcomm_dlc *dlc, struct net_buf *buf) { struct bt_hfp_hf *hf = CONTAINER_OF(dlc, struct bt_hfp_hf, rfcomm_dlc); + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_RX_ONGOING); if (at_parse_input(&hf->at, buf) < 0) { LOG_ERR("Parsing failed"); } + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_RX_ONGOING); + k_work_submit(&hf->work); } static void hfp_hf_sent(struct bt_rfcomm_dlc *dlc, int err) @@ -3258,6 +3265,13 @@ static void hfp_hf_sent(struct bt_rfcomm_dlc *dlc, int err) LOG_DBG("DLC %p sent cb (err %d)", dlc, err); } +static void bt_hf_work(struct k_work *work) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(work, struct bt_hfp_hf, work); + + hfp_hf_send_data(hf); +} + static int hfp_hf_accept(struct bt_conn *conn, struct bt_rfcomm_server *server, struct bt_rfcomm_dlc **dlc) { @@ -3298,6 +3312,8 @@ static int hfp_hf_accept(struct bt_conn *conn, struct bt_rfcomm_server *server, k_fifo_init(&hf->tx_pending); + k_work_init(&hf->work, bt_hf_work); + k_work_init_delayable(&hf->deferred_work, bt_hf_deferred_work); for (j = 0; j < HF_MAX_AG_INDICATORS; j++) { diff --git a/subsys/bluetooth/host/classic/hfp_internal.h b/subsys/bluetooth/host/classic/hfp_internal.h index ee6094c40d644..12f4ca5b0db72 100644 --- a/subsys/bluetooth/host/classic/hfp_internal.h +++ b/subsys/bluetooth/host/classic/hfp_internal.h @@ -152,6 +152,7 @@ enum { BT_HFP_HF_FLAG_CONNECTED, /* HFP HF SLC Established */ BT_HFP_HF_FLAG_TX_ONGOING, /* HFP HF TX is ongoing */ + BT_HFP_HF_FLAG_RX_ONGOING, /* HFP HF RX is ongoing */ BT_HFP_HF_FLAG_CODEC_CONN, /* HFP HF codec connection setup */ BT_HFP_HF_FLAG_CLCC_PENDING, /* HFP HF CLCC is pending */ /* Total number of flags - must be at the end of the enum */ @@ -218,6 +219,9 @@ struct bt_hfp_hf { /* The features supported by AT+CHLD */ uint32_t chld_features; + /* Worker for pending TX */ + struct k_work work; + struct k_work_delayable deferred_work; /* calls */ From f6a18e7e99c2867bfe4e0e429fa55f4d54689f9f Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Thu, 15 Aug 2024 22:14:05 +0800 Subject: [PATCH 42/76] Bluetooth: HFP_HF: Fix TX queue broken issue The TX buf will be removed from TX queue after the data has been sent. It causes the following TX buffers to be lost. Fix it by removing TX buffer when sending it. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/hfp_hf.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 43589a209e378..5c0e44de46917 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -190,8 +190,7 @@ static int hfp_hf_common_finish(struct at_client *at, enum at_result result, } if (atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_TX_ONGOING)) { - LOG_DBG("Remove completed buf %p on %p", k_fifo_peek_head(&hf->tx_pending), hf); - (void)k_fifo_get(&hf->tx_pending, K_NO_WAIT); + LOG_DBG("TX is done on %p", hf); } else { LOG_WRN("Tx is not ongoing on %p", hf); } @@ -211,17 +210,16 @@ static void hfp_hf_send_data(struct bt_hfp_hf *hf) return; } - if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_TX_ONGOING)) { + if (atomic_test_and_set_bit(hf->flags, BT_HFP_HF_FLAG_TX_ONGOING)) { return; } - buf = k_fifo_peek_head(&hf->tx_pending); + buf = k_fifo_get(&hf->tx_pending, K_NO_WAIT); if (!buf) { + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_TX_ONGOING); return; } - atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_TX_ONGOING); - resp = (at_resp_cb_t)at_callback_set_resp(buf->user_data); finish = (at_finish_cb_t)at_callback_set_finish(buf->user_data); @@ -238,6 +236,8 @@ static void hfp_hf_send_data(struct bt_hfp_hf *hf) err = bt_rfcomm_dlc_send(&hf->rfcomm_dlc, buf); if (err < 0) { LOG_ERR("Rfcomm send error :(%d)", err); + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_TX_ONGOING); + net_buf_unref(buf); hfp_hf_send_failed(hf); } } From 41e29e0eb38c0a6db069aadb564af2eeb4a0d5fb Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Mon, 2 Sep 2024 11:55:10 +0800 Subject: [PATCH 43/76] Bluetooth: HFP_HF: Support Voice recognition activation Add configuration `CONFIG_BT_HFP_HF_VOICE_RECG` to enable Voice recognition activation feature. Add configuration `CONFIG_BT_HFP_HF_ENH_VOICE_RECG` to enable Enhanced Voice Recognition Status feature. Add configuration `CONFIG_BT_HFP_HF_VOICE_RECG_TEXT` to enable Voice Recognition Text feature. Add function `bt_hfp_hf_voice_recognition` to activate/deactivate the Voice recognition activation feature. Add function `bt_hfp_hf_ready_to_accept_audio` to indicate that the HF is ready to accept audio. Add a callback `voice_recognition` to notify the upper layer the status of voice recognition activation. Add a callback `vre_state` to notify the upper layer the state of voice recognition engine. Add a callback `textual_representation` to notify the upper layer the textual representation. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 95 +++++++++ subsys/bluetooth/host/classic/Kconfig | 17 ++ subsys/bluetooth/host/classic/hfp_hf.c | 200 +++++++++++++++++++ subsys/bluetooth/host/classic/hfp_internal.h | 48 ++++- 4 files changed, 358 insertions(+), 2 deletions(-) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index a9008b9dc176e..fa99af599166b 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -305,6 +305,74 @@ struct bt_hfp_hf_cb { * @param type Specify the format of the phone number. */ void (*call_waiting)(struct bt_hfp_hf_call *call, char *number, uint8_t type); + /** Voice recognition activation/deactivation callback + * + * If this callback is provided it will be called whenever the + * unsolicited result code +BVRA is notified the HF when the + * voice recognition function in the AG is activated/deactivated + * autonomously from the AG. + * If @kconfig{CONFIG_BT_HFP_HF_VOICE_RECG} is not enabled, the + * unsolicited result code +BVRA will be ignored. And the callback + * will not be notified. + * + * @param hf HFP HF object. + * @param activate Voice recognition activation/deactivation. + */ + void (*voice_recognition)(struct bt_hfp_hf *hf, bool activate); + /** Voice recognition engine state callback + * + * If this callback is provided it will be called whenever the + * unsolicited result code `+BVRA: 1,` is received from AG. + * ``: Bitmask that reflects the current state of the voice + * recognition engine on the AG. + * Bit 0 - If it is 1, the AG is ready to accept audio input + * Bit 1 - If it is 1, the AG is sending audio to the HF + * Bit 2 - If it is 1, the AG is processing the audio input + * If @kconfig{CONFIG_BT_HFP_HF_ENH_VOICE_RECG} is not enabled, the + * unsolicited result code +BVRA will be ignored. And the callback + * will not be notified. + * + * @param hf HFP HF object. + * @param state Value of ``. + */ + void (*vre_state)(struct bt_hfp_hf *hf, uint8_t state); + /** Textual representation callback + * + * If this callback is provided it will be called whenever the + * unsolicited result code `+BVRA: 1,, + * ` is received from AG. + * `: ,,, + * `. + * ``: Unique ID of the current text as a hexadecimal string + * (a maximum of 4 characters in length, but less than 4 characters + * in length is valid). + * ``: ID of the textType from the following list: + * 0 - Text recognized by the AG from the audio input provided by the HF + * 1 - Text of the audio output from the AG + * 2 - Text of the audio output from the AG that contains a question + * 3 - Text of the audio output from the AG that contains an error + * description + * ``: ID of the operation of the text + * 1 - NewText: Indicates that a new text started. Shall be used when the + * `` changes + * 2 - Replace: Replace any existing text with the same `` and + * same `` + * 3 - Append: Attach new text to existing text and keep the same + * `` and same `` + * ``: The `` parameter shall be a UTF-8 text string and + * shall always be contained within double quotes. + * If @kconfig{CONFIG_BT_HFP_HF_VOICE_RECG_TEXT} is not enabled, the + * unsolicited result code +BVRA will be ignored. And the callback + * will not be notified. + * + * @param hf HFP HF object. + * @param id Value of ``. + * @param type Value of ``. + * @param operation Value of ``. + * @param text Value of ``. + */ + void (*textual_representation)(struct bt_hfp_hf *hf, char *id, uint8_t type, + uint8_t operation, char *text); }; /** @brief Register HFP HF profile @@ -676,6 +744,33 @@ int bt_hfp_hf_release_specified_call(struct bt_hfp_hf_call *call); */ int bt_hfp_hf_private_consultation_mode(struct bt_hfp_hf_call *call); +/** @brief Handsfree HF enable/disable the voice recognition function + * + * Enables/disables the voice recognition function in the AG. + * If @kconfig{CONFIG_BT_HFP_HF_VOICE_RECG} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * @param activate Activate/deactivate the voice recognition function. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_voice_recognition(struct bt_hfp_hf *hf, bool activate); + +/** @brief Handsfree HF indicate that the HF is ready to accept audio + * + * This value indicates that the HF is ready to accept audio when + * the Audio Connection is first established. The HF shall only send + * this value if the eSCO link has been established. + * If @kconfig{CONFIG_BT_HFP_HF_ENH_VOICE_RECG} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_ready_to_accept_audio(struct bt_hfp_hf *hf); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/Kconfig b/subsys/bluetooth/host/classic/Kconfig index 6840ae6eac722..40b5c2daf1fb3 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -234,6 +234,23 @@ config BT_HFP_HF_MAX_CALLS help This option sets maximum supported calls for HFP HF +config BT_HFP_HF_VOICE_RECG + bool "Voice recognition activation for HFP HF [EXPERIMENTAL]" + help + This option enables Voice recognition activation for HFP HF + +config BT_HFP_HF_ENH_VOICE_RECG + bool "Enhanced Voice Recognition Status for HFP HF [EXPERIMENTAL]" + select BT_HFP_HF_VOICE_RECG + help + This option enables Enhanced Voice Recognition Status for HFP HF + +config BT_HFP_HF_VOICE_RECG_TEXT + bool "Voice Recognition Text for HFP HF [EXPERIMENTAL]" + select BT_HFP_HF_ENH_VOICE_RECG + help + This option enables Voice Recognition Text for HFP HF + endif # BT_HFP_HF if BT_HFP_AG diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 5c0e44de46917..4d0a06c96e4e1 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -727,6 +727,94 @@ static int clcc_handle(struct at_client *hf_at) } #endif /* CONFIG_BT_HFP_HF_ECS */ +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) +static int bvra_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + uint32_t activate; + uint32_t state; + char *id; + char text_id[BT_HFP_BVRA_TEXT_ID_MAX_LEN + 1]; + size_t id_len; + uint32_t type; + uint32_t operation; + char *text; + + err = at_get_number(hf_at, &activate); + if (err < 0) { + LOG_ERR("Error getting activate"); + return err; + } + + if (activate) { + if (!atomic_test_and_set_bit(hf->flags, BT_HFP_HF_FLAG_VRE_ACTIVATE)) { + if (bt_hf->voice_recognition) { + bt_hf->voice_recognition(hf, true); + } + } + } else { + if (atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_VRE_ACTIVATE)) { + if (bt_hf->voice_recognition) { + bt_hf->voice_recognition(hf, false); + } + } + } + +#if defined(CONFIG_BT_HFP_HF_ENH_VOICE_RECG) + err = at_get_number(hf_at, &state); + if (err < 0) { + LOG_INF("Error getting VRE state"); + return 0; + } + + if (bt_hf->vre_state) { + bt_hf->vre_state(hf, (uint8_t)state); + } +#endif /* CONFIG_BT_HFP_HF_ENH_VOICE_RECG */ + +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG_TEXT) + id = at_get_raw_string(hf_at, &id_len); + if (!id) { + LOG_INF("Error getting text ID"); + return 0; + } + + if (id_len > BT_HFP_BVRA_TEXT_ID_MAX_LEN) { + LOG_ERR("Invalid text ID length %d", id_len); + return -ENOTSUP; + } + + strncpy(text_id, id, MIN(id_len, BT_HFP_BVRA_TEXT_ID_MAX_LEN)); + text_id[MIN(id_len, BT_HFP_BVRA_TEXT_ID_MAX_LEN)] = '\0'; + + err = at_get_number(hf_at, &type); + if (err < 0) { + LOG_INF("Error getting text type"); + return 0; + } + + err = at_get_number(hf_at, &operation); + if (err < 0) { + LOG_INF("Error getting text operation"); + return 0; + } + + text = at_get_string(hf_at); + if (!text) { + LOG_INF("Error getting text string"); + return 0; + } + + if (bt_hf->textual_representation) { + bt_hf->textual_representation(hf, text_id, (uint8_t)type, + (uint8_t)operation, text); + } +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG_TEXT */ + return 0; +} +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ + static struct bt_hfp_hf_call *get_dialing_call(struct bt_hfp_hf *hf) { struct bt_hfp_hf_call *call; @@ -1541,6 +1629,9 @@ static const struct unsolicited { #if defined(CONFIG_BT_HFP_HF_ECS) { "CLCC", AT_CMD_TYPE_UNSOLICITED, clcc_handle }, #endif /* CONFIG_BT_HFP_HF_ECS */ +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) + { "BVRA", AT_CMD_TYPE_UNSOLICITED, bvra_handle }, +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ }; static const struct unsolicited *hfp_hf_unsol_lookup(struct at_client *hf_at) @@ -3228,6 +3319,115 @@ int bt_hfp_hf_private_consultation_mode(struct bt_hfp_hf_call *call) #endif /* CONFIG_BT_HFP_HF_ECC */ } +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) +static int bvra_1_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BVRA=1 (result %d) on %p", result, hf); + + if (result == AT_RESULT_OK) { + if (!atomic_test_and_set_bit(hf->flags, BT_HFP_HF_FLAG_VRE_ACTIVATE)) { + if (bt_hf->voice_recognition) { + bt_hf->voice_recognition(hf, true); + } + } + } + + return 0; +} + +static int bvra_0_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BVRA=0 (result %d) on %p", result, hf); + + if (atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_VRE_ACTIVATE)) { + if (bt_hf->voice_recognition) { + bt_hf->voice_recognition(hf, false); + } + } + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ + +int bt_hfp_hf_voice_recognition(struct bt_hfp_hf *hf, bool activate) +{ +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) + at_finish_cb_t finish; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_VOICE_RECG)) { + LOG_ERR("Voice recognition is unsupported by AG"); + return -ENOTSUP; + } + + if (activate) { + finish = bvra_1_finish; + } else { + finish = bvra_0_finish; + } + + return hfp_hf_send_cmd(hf, NULL, finish, "AT+BVRA=%d", + activate ? 1 : 0); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ +} + +#if defined(CONFIG_BT_HFP_HF_ENH_VOICE_RECG) +static int bvra_2_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BVRA=2 (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_ENH_VOICE_RECG */ + +int bt_hfp_hf_ready_to_accept_audio(struct bt_hfp_hf *hf) +{ +#if defined(CONFIG_BT_HFP_HF_ENH_VOICE_RECG) + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_VOICE_RECG)) { + LOG_ERR("Voice recognition is unsupported by AG"); + return -ENOTSUP; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_VRE_ACTIVATE)) { + LOG_ERR("Voice recognition is not activated"); + return -EINVAL; + } + + if (!hf->chan.sco) { + LOG_ERR("SCO channel is not ready"); + return -ENOTCONN; + } + + return hfp_hf_send_cmd(hf, NULL, bvra_2_finish, "AT+BVRA=2"); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_ENH_VOICE_RECG */ +} + static void hfp_hf_connected(struct bt_rfcomm_dlc *dlc) { struct bt_hfp_hf *hf = CONTAINER_OF(dlc, struct bt_hfp_hf, rfcomm_dlc); diff --git a/subsys/bluetooth/host/classic/hfp_internal.h b/subsys/bluetooth/host/classic/hfp_internal.h index 12f4ca5b0db72..31865931a2477 100644 --- a/subsys/bluetooth/host/classic/hfp_internal.h +++ b/subsys/bluetooth/host/classic/hfp_internal.h @@ -102,6 +102,30 @@ #define BT_HFP_HF_FEATURE_ECC_ENABLE 0 #endif /* CONFIG_BT_HFP_HF_ECC */ +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) +#define BT_HFP_HF_FEATURE_VOICE_RECG_ENABLE BT_HFP_HF_FEATURE_VOICE_RECG +#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_ENABLE BT_HFP_HF_SDP_FEATURE_VOICE_RECG +#else +#define BT_HFP_HF_FEATURE_VOICE_RECG_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ + +#if defined(CONFIG_BT_HFP_HF_ENH_VOICE_RECG) +#define BT_HFP_HF_FEATURE_ENH_VOICE_RECG_ENABLE BT_HFP_HF_FEATURE_ENH_VOICE_RECG +#define BT_HFP_HF_SDP_FEATURE_ENH_VOICE_RECG_ENABLE BT_HFP_HF_SDP_FEATURE_ENH_VOICE_RECG +#else +#define BT_HFP_HF_FEATURE_ENH_VOICE_RECG_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_ENH_VOICE_RECG_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_ENH_VOICE_RECG */ + +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG_TEXT) +#define BT_HFP_HF_FEATURE_VOICE_RECG_TEXT_ENABLE BT_HFP_HF_FEATURE_VOICE_RECG_TEXT +#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT +#else +#define BT_HFP_HF_FEATURE_VOICE_RECG_TEXT_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG_TEXT */ + /* HFP HF Supported features */ #define BT_HFP_HF_SUPPORTED_FEATURES (\ BT_HFP_HF_FEATURE_CLI_ENABLE | \ @@ -110,14 +134,20 @@ BT_HFP_HF_FEATURE_ECNR_ENABLE | \ BT_HFP_HF_FEATURE_3WAY_CALL_ENABLE | \ BT_HFP_HF_FEATURE_ECS_ENABLE | \ - BT_HFP_HF_FEATURE_ECC_ENABLE) + BT_HFP_HF_FEATURE_ECC_ENABLE | \ + BT_HFP_HF_FEATURE_VOICE_RECG_ENABLE | \ + BT_HFP_HF_FEATURE_ENH_VOICE_RECG_ENABLE | \ + BT_HFP_HF_FEATURE_VOICE_RECG_TEXT_ENABLE) /* HFP HF Supported features in SDP */ #define BT_HFP_HF_SDP_SUPPORTED_FEATURES (\ BT_HFP_HF_SDP_FEATURE_CLI_ENABLE | \ BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE | \ BT_HFP_HF_SDP_FEATURE_ECNR_ENABLE | \ - BT_HFP_HF_SDP_FEATURE_3WAY_CALL_ENABLE) + BT_HFP_HF_SDP_FEATURE_3WAY_CALL_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_VOICE_RECG_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_ENH_VOICE_RECG_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE) #define BT_HFP_HF_CODEC_CVSD_MASK BIT(BT_HFP_HF_CODEC_CVSD) @@ -155,6 +185,7 @@ enum { BT_HFP_HF_FLAG_RX_ONGOING, /* HFP HF RX is ongoing */ BT_HFP_HF_FLAG_CODEC_CONN, /* HFP HF codec connection setup */ BT_HFP_HF_FLAG_CLCC_PENDING, /* HFP HF CLCC is pending */ + BT_HFP_HF_FLAG_VRE_ACTIVATE, /* VRE is activated */ /* Total number of flags - must be at the end of the enum */ BT_HFP_HF_NUM_FLAGS, }; @@ -304,3 +335,16 @@ enum hfp_hf_ag_indicators { #define BT_HFP_CLCC_STATUS_WAITING 5 /* Call held by Response and Hold */ #define BT_HFP_CLCC_STATUS_CALL_HELD_HOLD 6 + +/* BVRA Value */ +/* BVRA Deactivation */ +#define BT_HFP_BVRA_DEACTIVATION 0 +/* BVRA Activation */ +#define BT_HFP_BVRA_ACTIVATION 1 +/* Ready to accept audio */ +#define BT_HFP_BVRA_READY_TO_ACCEPT 2 + +/* a maximum of 4 characters in length, but + * less than 4 characters in length is valid. + */ +#define BT_HFP_BVRA_TEXT_ID_MAX_LEN 4 From 198f5e4a5ae840ffd78dcd9b219f6ea4fc81fda1 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Mon, 2 Sep 2024 14:38:00 +0800 Subject: [PATCH 44/76] Bluetooth: HFP_AG: Support Voice recognition activation Add configuration `CONFIG_BT_HFP_AG_VOICE_RECG` to enable Voice recognition activation feature. Add configuration `CONFIG_BT_HFP_AG_ENH_VOICE_RECG` to enable Enhanced Voice Recognition Status feature. Add configuration `CONFIG_BT_HFP_AG_VOICE_RECG_TEXT` to enable Voice Recognition Text feature. Add function `bt_hfp_ag_voice_recognition` activate/deactivate the Voice recognition activation feature. Add function `bt_hfp_ag_vre_state` to notify the state of the voice recognition engine. Add function `bt_hfp_ag_vre_textual_representation` to notify the state and textual representation of voice recognition engine. Add callback `voice_recognition` to notify the upper layer the status of voice recognition activation. Add callback `ready_to_accept_audio` to notify the upper layer that the HF is ready to accept audio. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_ag.h | 111 +++++++ subsys/bluetooth/host/classic/Kconfig | 18 ++ subsys/bluetooth/host/classic/hfp_ag.c | 276 ++++++++++++++++++ .../bluetooth/host/classic/hfp_ag_internal.h | 36 ++- subsys/bluetooth/host/classic/hfp_internal.h | 8 + 5 files changed, 447 insertions(+), 2 deletions(-) diff --git a/include/zephyr/bluetooth/classic/hfp_ag.h b/include/zephyr/bluetooth/classic/hfp_ag.h index 5e4f882919509..bc486e2bfc58b 100644 --- a/include/zephyr/bluetooth/classic/hfp_ag.h +++ b/include/zephyr/bluetooth/classic/hfp_ag.h @@ -282,6 +282,42 @@ struct bt_hfp_ag_cb { * @param ag HFP AG object. */ void (*explicit_call_transfer)(struct bt_hfp_ag *ag); + + /** Voice recognition activation/deactivation callback + * + * If this callback is provided it will be called whenever the + * voice recognition activation is changed. + * If voice recognition is activated, the upper layer should + * call `bt_hfp_ag_audio_connect` with appropriate codec ID to + * setup audio connection. + * If the callback is not provided by upper layer, the function + * `bt_hfp_ag_audio_connect` will be called with default codec + * ID `BT_HFP_AG_CODEC_CVSD`. + * If @kconfig{CONFIG_BT_HFP_AG_VOICE_RECG} is not enabled, + * the callback will not be notified. + * + * @param ag HFP AG object. + * @param activate Voice recognition activation/deactivation. + */ + void (*voice_recognition)(struct bt_hfp_ag *ag, bool activate); + + /** Ready to accept audio callback + * + * If this callback is provided it will be called whenever the + * HF is ready to accept audio. + * If the feature `Enhanced Voice Recognition Status` is supported + * by HF, the callback will be notified if the AT command `AT+BVRA=2` + * is received. The HF may send this value during an ongoing VR + * (Voice Recognition) session to terminate audio output from the + * AG (if there is any) and prepare the AG for new audio input. + * Or, the callback will be notified after the voice recognition + * is activated. + * If @kconfig{CONFIG_BT_HFP_AG_ENH_VOICE_RECG} is not enabled, + * the callback will not be notified. + * + * @param ag HFP AG object. + */ + void (*ready_to_accept_audio)(struct bt_hfp_ag *ag); }; /** @brief Register HFP AG profile @@ -522,6 +558,81 @@ int bt_hfp_ag_audio_connect(struct bt_hfp_ag *ag, uint8_t id); */ int bt_hfp_ag_inband_ringtone(struct bt_hfp_ag *ag, bool inband); +/** @brief Enable/disable the voice recognition function + * + * Enables/disables the voice recognition function. + * If @kconfig{CONFIG_BT_HFP_AG_VOICE_RECG} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param ag HFP AG object. + * @param activate Activate/deactivate the voice recognition function. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_voice_recognition(struct bt_hfp_ag *ag, bool activate); + +/** @brief set voice recognition engine state + * + * It is used to set the voice recognition engine state. + * The unsolicited result code `+BVRA: 1,` will be sent. + * ``: Bitmask that reflects the current state of the voice + * recognition engine on the AG. + * Bit 0 - If it is 1, the AG is ready to accept audio input + * Bit 1 - If it is 1, the AG is sending audio to the HF + * Bit 2 - If it is 1, the AG is processing the audio input + * If @kconfig{CONFIG_BT_HFP_AG_ENH_VOICE_RECG} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param ag HFP AG object. + * @param state The value of ``. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_vre_state(struct bt_hfp_ag *ag, uint8_t state); + +/** @brief set voice recognition engine state and textual representation + * + * It is used to set the voice recognition engine state with + * textual representation. + * unsolicited result code `+BVRA: 1,, + * ` will be sent. + * `` is same as parameter `state` of function + * `bt_hfp_ag_vre_state`. + * `: ,,, + * `. + * ``: Unique ID of the current text as a hexadecimal string + * (a maximum of 4 characters in length, but less than 4 characters + * in length is valid). + * ``: ID of the textType from the following list: + * 0 - Text recognized by the AG from the audio input provided by the HF + * 1 - Text of the audio output from the AG + * 2 - Text of the audio output from the AG that contains a question + * 3 - Text of the audio output from the AG that contains an error + * description + * ``: ID of the operation of the text + * 1 - NewText: Indicates that a new text started. Shall be used when the + * `` changes + * 2 - Replace: Replace any existing text with the same `` and + * same `` + * 3 - Append: Attach new text to existing text and keep the same + * `` and same `` + * ``: The `` parameter shall be a UTF-8 text string and + * shall always be contained within double quotes. + * If @kconfig{CONFIG_BT_HFP_AG_VOICE_RECG_TEXT} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param ag HFP AG object. + * @param state The value of ``. + * @param id Value of ``. + * @param type Value of ``. + * @param operation Value of ``. + * @param text Value of ``. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_vre_textual_representation(struct bt_hfp_ag *ag, uint8_t state, const char *id, + uint8_t type, uint8_t operation, const char *text); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/Kconfig b/subsys/bluetooth/host/classic/Kconfig index 40b5c2daf1fb3..526d11823b31d 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -355,6 +355,24 @@ config BT_HFP_AG_RING_NOTIFY_INTERVAL help Ring notification interval if the call is in alert state. The unit is seconds. + +config BT_HFP_AG_VOICE_RECG + bool "Voice recognition activation for HFP AG [EXPERIMENTAL]" + help + This option enables Voice recognition activation for HFP AG + +config BT_HFP_AG_ENH_VOICE_RECG + bool "Enhanced Voice Recognition Status for HFP AG [EXPERIMENTAL]" + select BT_HFP_AG_VOICE_RECG + help + This option enables Enhanced Voice Recognition Status for HFP AG + +config BT_HFP_AG_VOICE_RECG_TEXT + bool "Voice Recognition Text for HFP AG [EXPERIMENTAL]" + select BT_HFP_AG_ENH_VOICE_RECG + help + This option enables Voice Recognition Text for HFP AG + endif # BT_HFP_AG config BT_AVDTP diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index 29a767e49dc86..e09eeaa0e25fc 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -2831,6 +2831,118 @@ static int bt_hfp_ag_ccwa_handler(struct bt_hfp_ag *ag, struct net_buf *buf) return 0; } +static void bt_hfp_ag_vr_activate(struct bt_hfp_ag *ag, void *user_data) +{ + bool feature; + + hfp_ag_lock(ag); + feature = (ag->ag_features & BT_HFP_AG_FEATURE_ENH_VOICE_RECG) && + (ag->hf_features & BT_HFP_HF_FEATURE_ENH_VOICE_RECG); + hfp_ag_unlock(ag); + +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG) + if (bt_ag && bt_ag->voice_recognition) { + bt_ag->voice_recognition(ag, true); + } else { + (void)bt_hfp_ag_audio_connect(ag, BT_HFP_AG_CODEC_CVSD); + } +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG */ + + atomic_set_bit_to(ag->flags, BT_HFP_AG_VRE_R2A, !feature); + +#if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) + if (atomic_test_bit(ag->flags, BT_HFP_AG_VRE_R2A)) { + if (bt_ag && bt_ag->ready_to_accept_audio) { + bt_ag->ready_to_accept_audio(ag); + } + } +#endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ +} + +static void bt_hfp_ag_vr_deactivate(struct bt_hfp_ag *ag, void *user_data) +{ +#if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) + if (bt_ag && bt_ag->voice_recognition) { + bt_ag->voice_recognition(ag, false); + } +#endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ +} + +static void bt_hfp_ag_vr_ready2accept(struct bt_hfp_ag *ag, void *user_data) +{ + atomic_set_bit(ag->flags, BT_HFP_AG_VRE_R2A); + +#if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) + if (bt_ag && bt_ag->ready_to_accept_audio) { + bt_ag->ready_to_accept_audio(ag); + } +#endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ +} + +static int bt_hfp_ag_bvra_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t value; + + hfp_ag_lock(ag); + if (!((ag->ag_features & BT_HFP_AG_FEATURE_VOICE_RECG) && + (ag->hf_features & BT_HFP_HF_FEATURE_VOICE_RECG))) { + hfp_ag_unlock(ag); + return -EOPNOTSUPP; + } + hfp_ag_unlock(ag); + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + err = get_number(buf, &value); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + switch (value) { + case BT_HFP_BVRA_DEACTIVATION: + if (!atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) { + LOG_WRN("VR is not activated"); + return -ENOTSUP; + } + err = hfp_ag_next_step(ag, bt_hfp_ag_vr_deactivate, NULL); + break; + case BT_HFP_BVRA_ACTIVATION: + if (atomic_test_and_set_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) { + LOG_WRN("VR has been activated"); + return -ENOTSUP; + } + atomic_clear_bit(ag->flags, BT_HFP_AG_VRE_R2A); + err = hfp_ag_next_step(ag, bt_hfp_ag_vr_activate, NULL); + break; + case BT_HFP_BVRA_READY_TO_ACCEPT: + hfp_ag_lock(ag); + if (!((ag->ag_features & BT_HFP_AG_FEATURE_ENH_VOICE_RECG) && + (ag->hf_features & BT_HFP_HF_FEATURE_ENH_VOICE_RECG))) { + hfp_ag_unlock(ag); + LOG_WRN("Enhance voice recognition is not supported"); + return -EOPNOTSUPP; + } + hfp_ag_unlock(ag); + if (!atomic_test_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) { + LOG_WRN("Voice recognition is not activated"); + return -ENOTSUP; + } + err = hfp_ag_next_step(ag, bt_hfp_ag_vr_ready2accept, NULL); + break; + default: + return -ENOTSUP; + } + + return err; +} + static struct bt_hfp_ag_at_cmd_handler cmd_handlers[] = { {"AT+BRSF", bt_hfp_ag_brsf_handler}, {"AT+BAC", bt_hfp_ag_bac_handler}, {"AT+CIND", bt_hfp_ag_cind_handler}, {"AT+CMER", bt_hfp_ag_cmer_handler}, @@ -2843,6 +2955,7 @@ static struct bt_hfp_ag_at_cmd_handler cmd_handlers[] = { {"AT+CLIP", bt_hfp_ag_clip_handler}, {"AT+VGM", bt_hfp_ag_vgm_handler}, {"AT+VGS", bt_hfp_ag_vgs_handler}, {"AT+NREC", bt_hfp_ag_nrec_handler}, {"AT+BTRH", bt_hfp_ag_btrh_handler}, {"AT+CCWA", bt_hfp_ag_ccwa_handler}, + {"AT+BVRA", bt_hfp_ag_bvra_handler}, }; static void hfp_ag_connected(struct bt_rfcomm_dlc *dlc) @@ -4203,3 +4316,166 @@ int bt_hfp_ag_inband_ringtone(struct bt_hfp_ag *ag, bool inband) atomic_set_bit_to(ag->flags, BT_HFP_AG_INBAND_RING, inband); return 0; } + +int bt_hfp_ag_voice_recognition(struct bt_hfp_ag *ag, bool activate) +{ +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG) + int err; + bool feature; + char *bvra; + + LOG_DBG(""); + + if (ag == NULL) { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + hfp_ag_unlock(ag); + + if (activate && atomic_test_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) { + LOG_WRN("VR has been activated"); + return -ENOTSUP; + } else if (!activate && !atomic_test_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) { + LOG_WRN("VR is not activated"); + return -ENOTSUP; + } + + hfp_ag_lock(ag); + feature = (ag->ag_features & BT_HFP_AG_FEATURE_ENH_VOICE_RECG) && + (ag->hf_features & BT_HFP_HF_FEATURE_ENH_VOICE_RECG); + hfp_ag_unlock(ag); + + if (!feature) { + bvra = ""; + } else { + bvra = ",0"; + } + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BVRA:%d%s\r\n", activate, bvra); + if (err) { + LOG_ERR("Fail to notify VR activation :(%d)", err); + return err; + } + + atomic_set_bit_to(ag->flags, BT_HFP_AG_VRE_ACTIVATE, activate); + atomic_clear_bit(ag->flags, BT_HFP_AG_VRE_R2A); + return 0; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG */ +} + +int bt_hfp_ag_vre_state(struct bt_hfp_ag *ag, uint8_t state) +{ +#if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) + int err; + bool feature; + + LOG_DBG(""); + + if (ag == NULL) { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + hfp_ag_unlock(ag); + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) { + LOG_WRN("Voice Recognition is not activated"); + return -EINVAL; + } + + hfp_ag_lock(ag); + feature = (ag->ag_features & BT_HFP_AG_FEATURE_ENH_VOICE_RECG) && + (ag->hf_features & BT_HFP_HF_FEATURE_ENH_VOICE_RECG); + hfp_ag_unlock(ag); + if (!feature) { + return -ENOTSUP; + } + + if ((state & BT_HFP_BVRA_STATE_SEND_AUDIO) && + !atomic_test_bit(ag->flags, BT_HFP_AG_VRE_R2A)) { + LOG_ERR("HFP HF is not ready to accept audio input"); + return -EINVAL; + } + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BVRA:1,%d\r\n", state); + if (err) { + LOG_ERR("Fail to send state of VRE :(%d)", err); + return err; + } + + return 0; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ +} + +int bt_hfp_ag_vre_textual_representation(struct bt_hfp_ag *ag, uint8_t state, const char *id, + uint8_t type, uint8_t operation, const char *text) +{ +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG_TEXT) + int err; + bool feature; + + LOG_DBG(""); + + if (ag == NULL) { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + hfp_ag_unlock(ag); + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) { + LOG_WRN("Voice Recognition is not activated"); + return -EINVAL; + } + + hfp_ag_lock(ag); + feature = (ag->ag_features & BT_HFP_AG_FEATURE_ENH_VOICE_RECG) && + (ag->hf_features & BT_HFP_HF_FEATURE_ENH_VOICE_RECG); + hfp_ag_unlock(ag); + if (!feature) { + return -ENOTSUP; + } + + hfp_ag_lock(ag); + feature = (ag->ag_features & BT_HFP_AG_FEATURE_VOICE_RECG_TEXT) && + (ag->hf_features & BT_HFP_HF_FEATURE_VOICE_RECG_TEXT); + hfp_ag_unlock(ag); + if (!feature) { + return -ENOTSUP; + } + + if ((state & BT_HFP_BVRA_STATE_SEND_AUDIO) && + !atomic_test_bit(ag->flags, BT_HFP_AG_VRE_R2A)) { + LOG_ERR("HFP HF is not ready to accept audio input"); + return -EINVAL; + } + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BVRA:1,%d,%s,%d,%d,\"%s\"\r\n", + state, id, type, operation, text); + if (err) { + LOG_ERR("Fail to send state of VRE :(%d)", err); + return err; + } + + return 0; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG_TEXT */ +} diff --git a/subsys/bluetooth/host/classic/hfp_ag_internal.h b/subsys/bluetooth/host/classic/hfp_ag_internal.h index 9d23ef4e437bf..82c774891428f 100644 --- a/subsys/bluetooth/host/classic/hfp_ag_internal.h +++ b/subsys/bluetooth/host/classic/hfp_ag_internal.h @@ -60,6 +60,30 @@ #define BT_HFP_AG_FEATURE_ECC_ENABLE 0 #endif /* CONFIG_BT_HFP_AG_ECC */ +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG) +#define BT_HFP_AG_FEATURE_VOICE_RECG_ENABLE BT_HFP_AG_FEATURE_VOICE_RECG +#define BT_HFP_AG_SDP_FEATURE_VOICE_RECG_ENABLE BT_HFP_AG_SDP_FEATURE_VOICE_RECG +#else +#define BT_HFP_AG_FEATURE_VOICE_RECG_ENABLE 0 +#define BT_HFP_AG_SDP_FEATURE_VOICE_RECG_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG */ + +#if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) +#define BT_HFP_AG_FEATURE_ENH_VOICE_RECG_ENABLE BT_HFP_AG_FEATURE_ENH_VOICE_RECG +#define BT_HFP_AG_SDP_FEATURE_ENH_VOICE_RECG_ENABLE BT_HFP_AG_SDP_FEATURE_ENH_VOICE_RECG +#else +#define BT_HFP_AG_FEATURE_ENH_VOICE_RECG_ENABLE 0 +#define BT_HFP_AG_SDP_FEATURE_ENH_VOICE_RECG_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ + +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG_TEXT) +#define BT_HFP_AG_FEATURE_VOICE_RECG_TEXT_ENABLE BT_HFP_AG_FEATURE_VOICE_RECG_TEXT +#define BT_HFP_AG_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE BT_HFP_AG_SDP_FEATURE_VOICE_RECG_TEXT +#else +#define BT_HFP_AG_FEATURE_VOICE_RECG_TEXT_ENABLE 0 +#define BT_HFP_AG_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG_TEXT */ + /* HFP AG Supported features */ #define BT_HFP_AG_SUPPORTED_FEATURES (\ BT_HFP_AG_FEATURE_3WAY_CALL_ENABLE | \ @@ -68,13 +92,19 @@ BT_HFP_AG_FEATURE_CODEC_NEG_ENABLE | \ BT_HFP_AG_FEATURE_ECNR_ENABLE | \ BT_HFP_AG_FEATURE_ECS_ENABLE | \ - BT_HFP_AG_FEATURE_ECC_ENABLE) + BT_HFP_AG_FEATURE_ECC_ENABLE | \ + BT_HFP_AG_FEATURE_VOICE_RECG_ENABLE | \ + BT_HFP_AG_FEATURE_ENH_VOICE_RECG_ENABLE | \ + BT_HFP_AG_FEATURE_VOICE_RECG_TEXT_ENABLE) /* HFP AG Supported features in SDP */ #define BT_HFP_AG_SDP_SUPPORTED_FEATURES (\ BT_HFP_AG_SDP_FEATURE_3WAY_CALL_ENABLE | \ BT_HFP_AG_SDP_FEATURE_INBAND_RINGTONE | \ - BT_HFP_AG_SDP_FEATURE_ECNR_ENABLE) + BT_HFP_AG_SDP_FEATURE_ECNR_ENABLE | \ + BT_HFP_AG_SDP_FEATURE_VOICE_RECG_ENABLE | \ + BT_HFP_AG_SDP_FEATURE_ENH_VOICE_RECG_ENABLE | \ + BT_HFP_AG_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE) /* bt_hfp_ag flags: the flags defined here represent HFP AG parameters */ enum { @@ -89,6 +119,8 @@ enum { BT_HFP_AG_CODEC_CHANGED, /* Codec Id Changed */ BT_HFP_AG_TX_ONGOING, /* TX is ongoing */ BT_HFP_AG_CREATING_SCO, /* SCO is creating */ + BT_HFP_AG_VRE_ACTIVATE, /* VRE is activated */ + BT_HFP_AG_VRE_R2A, /* HF is ready to accept audio */ /* Total number of flags - must be at the end of the enum */ BT_HFP_AG_NUM_FLAGS, diff --git a/subsys/bluetooth/host/classic/hfp_internal.h b/subsys/bluetooth/host/classic/hfp_internal.h index 31865931a2477..c11652d565be0 100644 --- a/subsys/bluetooth/host/classic/hfp_internal.h +++ b/subsys/bluetooth/host/classic/hfp_internal.h @@ -348,3 +348,11 @@ enum hfp_hf_ag_indicators { * less than 4 characters in length is valid. */ #define BT_HFP_BVRA_TEXT_ID_MAX_LEN 4 + +/* BVRA VRE state */ +/* BVRA VRE state: the AG is ready to accept audio input */ +#define BT_HFP_BVRA_STATE_ACCEPT_INPUT BIT(0) +/* BVRA VRE state: the AG is sending audio to the HF */ +#define BT_HFP_BVRA_STATE_SEND_AUDIO BIT(1) +/* BVRA VRE state: the AG is processing the audio input */ +#define BT_HFP_BVRA_STATE_PROCESS_AUDIO BIT(2) From 7b0935ecb0898a31e747b11dd9cff03af918dfd7 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Mon, 2 Sep 2024 18:44:54 +0800 Subject: [PATCH 45/76] Bluetooth: HFP_HF: Request phone number Add a function `bt_hfp_hf_request_phone_number` to request phone number to the AG. Add a callback `request_phone_number` to notify the result of the request. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 20 ++++++ subsys/bluetooth/host/classic/hfp_hf.c | 73 ++++++++++++++++++++ subsys/bluetooth/host/classic/hfp_internal.h | 1 + 3 files changed, 94 insertions(+) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index fa99af599166b..be3ad5fe63b0b 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -373,6 +373,16 @@ struct bt_hfp_hf_cb { */ void (*textual_representation)(struct bt_hfp_hf *hf, char *id, uint8_t type, uint8_t operation, char *text); + /** Request phone number callback + * + * If this callback is provided it will be called whenever the + * result code `+BINP: ` is received from AG. + * If the request is failed, the `number` will be NULL. + * + * @param hf HFP HF object. + * @param number Value of ``. + */ + void (*request_phone_number)(struct bt_hfp_hf *hf, const char *number); }; /** @brief Register HFP HF profile @@ -771,6 +781,16 @@ int bt_hfp_hf_voice_recognition(struct bt_hfp_hf *hf, bool activate); */ int bt_hfp_hf_ready_to_accept_audio(struct bt_hfp_hf *hf); +/** @brief Handsfree HF attach a phone number for a voice tag + * + * Send AT command "AT+BINP=1" to request phone number to the AG. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_request_phone_number(struct bt_hfp_hf *hf); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 4d0a06c96e4e1..1d7c9932c6843 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -2261,6 +2261,79 @@ int bt_hfp_hf_get_operator(struct bt_hfp_hf *hf) return err; } +static int binp_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + char *number; + + number = at_get_string(hf_at); + + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_BINP); + + if (bt_hf && bt_hf->request_phone_number) { + bt_hf->request_phone_number(hf, number); + } + + return 0; +} + +static int binp_resp(struct at_client *hf_at, struct net_buf *buf) +{ + int err; + + LOG_DBG(""); + + err = at_parse_cmd_input(hf_at, buf, "BINP", binp_handle, + AT_CMD_TYPE_NORMAL); + if (err < 0) { + LOG_ERR("Cannot parse response of AT+BINP=1"); + return err; + } + + return 0; +} + +static int binp_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BINP=1 (result %d) on %p", result, hf); + + if (!atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_BINP)) { + if (bt_hf && bt_hf->request_phone_number) { + bt_hf->request_phone_number(hf, NULL); + } + } + + return 0; +} + +int bt_hfp_hf_request_phone_number(struct bt_hfp_hf *hf) +{ + int err; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + return 0; + } + + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_BINP); + + err = hfp_hf_send_cmd(hf, binp_resp, binp_finish, "AT+BINP=1"); + if (err < 0) { + LOG_ERR("Fail to request phone number to the AG on %p", hf); + } + + return err; +} + static int ata_finish(struct at_client *hf_at, enum at_result result, enum at_cme cme_err) { diff --git a/subsys/bluetooth/host/classic/hfp_internal.h b/subsys/bluetooth/host/classic/hfp_internal.h index c11652d565be0..6e17ab6c3eef2 100644 --- a/subsys/bluetooth/host/classic/hfp_internal.h +++ b/subsys/bluetooth/host/classic/hfp_internal.h @@ -186,6 +186,7 @@ enum { BT_HFP_HF_FLAG_CODEC_CONN, /* HFP HF codec connection setup */ BT_HFP_HF_FLAG_CLCC_PENDING, /* HFP HF CLCC is pending */ BT_HFP_HF_FLAG_VRE_ACTIVATE, /* VRE is activated */ + BT_HFP_HF_FLAG_BINP, /* +BINP result code is received */ /* Total number of flags - must be at the end of the enum */ BT_HFP_HF_NUM_FLAGS, }; From 418a9ca68c15ba58053789e3b0676b4b1558b15d Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Mon, 2 Sep 2024 14:41:00 +0800 Subject: [PATCH 46/76] Bluetooth: HFP_AG: Attach a phone number for a voice tag Add configuration `CONFIG_BT_HFP_AG_VOICE_TAG` to enable the feature `attach a phone number for a voice tag`. Handle AT command `AT+BINP=1`. Add callback `request_phone_number` to notify the request and get the phone number from the upper layer. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_ag.h | 18 +++++++ subsys/bluetooth/host/classic/Kconfig | 6 +++ subsys/bluetooth/host/classic/hfp_ag.c | 53 ++++++++++++++++++- .../bluetooth/host/classic/hfp_ag_internal.h | 14 ++++- 4 files changed, 88 insertions(+), 3 deletions(-) diff --git a/include/zephyr/bluetooth/classic/hfp_ag.h b/include/zephyr/bluetooth/classic/hfp_ag.h index bc486e2bfc58b..6d6f76c7a1b1b 100644 --- a/include/zephyr/bluetooth/classic/hfp_ag.h +++ b/include/zephyr/bluetooth/classic/hfp_ag.h @@ -318,6 +318,24 @@ struct bt_hfp_ag_cb { * @param ag HFP AG object. */ void (*ready_to_accept_audio)(struct bt_hfp_ag *ag); + + /** Request phone number callback + * + * If this callback is provided it will be called whenever the + * AT command `AT+BINP=1` is received. + * If the upper layer accepts the request, it shall obtain a + * phone number. + * If the upper layer rejects the request, it shall return a + * an error. + * If @kconfig{CONFIG_BT_HFP_AG_VOICE_TAG} is not enabled, + * the callback will not be notified. + * + * @param ag HFP AG object. + * @param number Phone number of voice tag. + * + * @return 0 in case of success or negative value in case of error. + */ + int (*request_phone_number)(struct bt_hfp_ag *ag, char **number); }; /** @brief Register HFP AG profile diff --git a/subsys/bluetooth/host/classic/Kconfig b/subsys/bluetooth/host/classic/Kconfig index 526d11823b31d..c4846c0db0e66 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -373,6 +373,12 @@ config BT_HFP_AG_VOICE_RECG_TEXT help This option enables Voice Recognition Text for HFP AG +config BT_HFP_AG_VOICE_TAG + bool "Attach a phone number for a voice tag for HFP AG [EXPERIMENTAL]" + select BT_HFP_AG_VOICE_RECG + help + This option enables Attach a phone number for a voice tag for HFP AG + endif # BT_HFP_AG config BT_AVDTP diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index e09eeaa0e25fc..32d3c96752ab7 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -2943,6 +2943,57 @@ static int bt_hfp_ag_bvra_handler(struct bt_hfp_ag *ag, struct net_buf *buf) return err; } +static int bt_hfp_ag_binp_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t value; +#if defined(CONFIG_BT_HFP_AG_VOICE_TAG) + char *number = NULL; +#endif /* CONFIG_BT_HFP_AG_VOICE_TAG */ + + hfp_ag_lock(ag); + if (!(ag->ag_features & BT_HFP_AG_FEATURE_VOICE_TAG)) { + hfp_ag_unlock(ag); + return -EOPNOTSUPP; + } + hfp_ag_unlock(ag); + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + err = get_number(buf, &value); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + if (value != 1) { + return -ENOTSUP; + } + +#if defined(CONFIG_BT_HFP_AG_VOICE_TAG) + if (bt_ag && bt_ag->request_phone_number) { + err = bt_ag->request_phone_number(ag, &number); + if (err) { + LOG_DBG("Cannot request phone number :(%d)", err); + return err; + } + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BINP:\"%s\"\r\n", number); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + return err; + } +#endif /* CONFIG_BT_HFP_AG_VOICE_TAG */ + + return -ENOTSUP; +} + static struct bt_hfp_ag_at_cmd_handler cmd_handlers[] = { {"AT+BRSF", bt_hfp_ag_brsf_handler}, {"AT+BAC", bt_hfp_ag_bac_handler}, {"AT+CIND", bt_hfp_ag_cind_handler}, {"AT+CMER", bt_hfp_ag_cmer_handler}, @@ -2955,7 +3006,7 @@ static struct bt_hfp_ag_at_cmd_handler cmd_handlers[] = { {"AT+CLIP", bt_hfp_ag_clip_handler}, {"AT+VGM", bt_hfp_ag_vgm_handler}, {"AT+VGS", bt_hfp_ag_vgs_handler}, {"AT+NREC", bt_hfp_ag_nrec_handler}, {"AT+BTRH", bt_hfp_ag_btrh_handler}, {"AT+CCWA", bt_hfp_ag_ccwa_handler}, - {"AT+BVRA", bt_hfp_ag_bvra_handler}, + {"AT+BVRA", bt_hfp_ag_bvra_handler}, {"AT+BINP", bt_hfp_ag_binp_handler}, }; static void hfp_ag_connected(struct bt_rfcomm_dlc *dlc) diff --git a/subsys/bluetooth/host/classic/hfp_ag_internal.h b/subsys/bluetooth/host/classic/hfp_ag_internal.h index 82c774891428f..4f6d8abc2eadd 100644 --- a/subsys/bluetooth/host/classic/hfp_ag_internal.h +++ b/subsys/bluetooth/host/classic/hfp_ag_internal.h @@ -84,6 +84,14 @@ #define BT_HFP_AG_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE 0 #endif /* CONFIG_BT_HFP_AG_VOICE_RECG_TEXT */ +#if defined(CONFIG_BT_HFP_AG_VOICE_TAG) +#define BT_HFP_AG_FEATURE_VOICE_TAG_ENABLE BT_HFP_AG_FEATURE_VOICE_TAG +#define BT_HFP_AG_SDP_FEATURE_VOICE_TAG_ENABLE BT_HFP_AG_SDP_FEATURE_VOICE_TAG +#else +#define BT_HFP_AG_FEATURE_VOICE_TAG_ENABLE 0 +#define BT_HFP_AG_SDP_FEATURE_VOICE_TAG_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_VOICE_TAG */ + /* HFP AG Supported features */ #define BT_HFP_AG_SUPPORTED_FEATURES (\ BT_HFP_AG_FEATURE_3WAY_CALL_ENABLE | \ @@ -95,7 +103,8 @@ BT_HFP_AG_FEATURE_ECC_ENABLE | \ BT_HFP_AG_FEATURE_VOICE_RECG_ENABLE | \ BT_HFP_AG_FEATURE_ENH_VOICE_RECG_ENABLE | \ - BT_HFP_AG_FEATURE_VOICE_RECG_TEXT_ENABLE) + BT_HFP_AG_FEATURE_VOICE_RECG_TEXT_ENABLE | \ + BT_HFP_AG_FEATURE_VOICE_TAG_ENABLE) /* HFP AG Supported features in SDP */ #define BT_HFP_AG_SDP_SUPPORTED_FEATURES (\ @@ -104,7 +113,8 @@ BT_HFP_AG_SDP_FEATURE_ECNR_ENABLE | \ BT_HFP_AG_SDP_FEATURE_VOICE_RECG_ENABLE | \ BT_HFP_AG_SDP_FEATURE_ENH_VOICE_RECG_ENABLE | \ - BT_HFP_AG_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE) + BT_HFP_AG_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE | \ + BT_HFP_AG_SDP_FEATURE_VOICE_TAG_ENABLE) /* bt_hfp_ag flags: the flags defined here represent HFP AG parameters */ enum { From 3fccfbc18899973136c7a07a0416f9a728d22ecb Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Tue, 27 Aug 2024 14:27:36 +0800 Subject: [PATCH 47/76] Bluetooth: HFP_HF: Transmit DTMF Code Add a function `bt_hfp_hf_transmit_dtmf_code` to transmit DTMF Codes. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 14 ++++++ subsys/bluetooth/host/classic/hfp_hf.c | 53 ++++++++++++++++++++ subsys/bluetooth/host/classic/hfp_internal.h | 3 ++ 3 files changed, 70 insertions(+) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index be3ad5fe63b0b..365c3d036511e 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -791,6 +791,20 @@ int bt_hfp_hf_ready_to_accept_audio(struct bt_hfp_hf *hf); */ int bt_hfp_hf_request_phone_number(struct bt_hfp_hf *hf); +/** @brief Handsfree HF Transmit A specific DTMF Code + * + * During an ongoing call, the HF transmits the AT+VTS command to + * instruct the AG to transmit a specific DTMF code to its network + * connection. + * The set of the code is "0-9,#,*,A-D". + * + * @param call HFP HF call object. + * @param code A specific DTMF code. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_transmit_dtmf_code(struct bt_hfp_hf_call *call, char code); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 1d7c9932c6843..2e9e5713b7d61 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -2334,6 +2334,59 @@ int bt_hfp_hf_request_phone_number(struct bt_hfp_hf *hf) return err; } +static int vts_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+VTS (result %d) on %p", result, hf); + + return 0; +} + +int bt_hfp_hf_transmit_dtmf_code(struct bt_hfp_hf_call *call, char code) +{ + struct bt_hfp_hf *hf; + int err; + + LOG_DBG(""); + + if (!call) { + LOG_ERR("Invalid call"); + return -ENOTCONN; + } + + hf = call->hf; + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + LOG_ERR("SLC is not established on %p", hf); + return -ENOTCONN; + } + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING) || + (!(!atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD) && + (atomic_get(call->state) == BT_HFP_HF_CALL_STATE_ACTIVE)))) { + LOG_ERR("Invalid call status"); + return -EINVAL; + } + + if (!IS_VALID_DTMF(code)) { + LOG_ERR("Invalid code"); + return -EINVAL; + } + + err = hfp_hf_send_cmd(hf, NULL, vts_finish, "AT+VTS=%c", code); + if (err < 0) { + LOG_ERR("Fail to tramsit DTMF Codes on %p", hf); + } + + return err; +} + static int ata_finish(struct at_client *hf_at, enum at_result result, enum at_cme cme_err) { diff --git a/subsys/bluetooth/host/classic/hfp_internal.h b/subsys/bluetooth/host/classic/hfp_internal.h index 6e17ab6c3eef2..be776021eb856 100644 --- a/subsys/bluetooth/host/classic/hfp_internal.h +++ b/subsys/bluetooth/host/classic/hfp_internal.h @@ -357,3 +357,6 @@ enum hfp_hf_ag_indicators { #define BT_HFP_BVRA_STATE_SEND_AUDIO BIT(1) /* BVRA VRE state: the AG is processing the audio input */ #define BT_HFP_BVRA_STATE_PROCESS_AUDIO BIT(2) + +#define IS_VALID_DTMF(c) ((((c) >= '0') && ((c) <= '9')) || \ + (((c) >= 'A') && ((c) <= 'D')) || ((c) == '#') || ((c) == '*')) From 848d423ddf211f44b36c7a799e4390f19a5610b5 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Mon, 2 Sep 2024 14:42:01 +0800 Subject: [PATCH 48/76] Bluetooth: HFP_AG: Handle DTMF code Handle AT command `AT+VTS`. Add a callback `transmit_dtmf_code`. When a valid AT command `AT+VTS` is received, call the callback to notify the upper layer. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_ag.h | 13 ++++++ subsys/bluetooth/host/classic/hfp_ag.c | 53 +++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/include/zephyr/bluetooth/classic/hfp_ag.h b/include/zephyr/bluetooth/classic/hfp_ag.h index 6d6f76c7a1b1b..e766b698c34cb 100644 --- a/include/zephyr/bluetooth/classic/hfp_ag.h +++ b/include/zephyr/bluetooth/classic/hfp_ag.h @@ -336,6 +336,19 @@ struct bt_hfp_ag_cb { * @return 0 in case of success or negative value in case of error. */ int (*request_phone_number)(struct bt_hfp_ag *ag, char **number); + + /** Transmit a DTMF Code callback + * + * If this callback is provided it will be called whenever the + * AT command `AT+VTS=` is received. + * During an ongoing call, the HF transmits the AT+VTS command + * to instruct the AG to transmit a specific DTMF code to its + * network connection. + * + * @param ag HFP AG object. + * @param code A specific DTMF code. + */ + void (*transmit_dtmf_code)(struct bt_hfp_ag *ag, char code); }; /** @brief Register HFP AG profile diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index 32d3c96752ab7..7f986467b4f74 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -809,6 +809,21 @@ static int get_number(struct net_buf *buf, uint32_t *number) return err; } +static int get_char(struct net_buf *buf, char *c) +{ + int err = -EINVAL; + + skip_space(buf); + if (buf->len > 0) { + *c = (char)buf->data[0]; + (void)net_buf_pull(buf, 1); + err = 0; + } + skip_space(buf); + + return err; +} + static bool is_char(struct net_buf *buf, uint8_t c) { bool found = false; @@ -2994,6 +3009,43 @@ static int bt_hfp_ag_binp_handler(struct bt_hfp_ag *ag, struct net_buf *buf) return -ENOTSUP; } +static int bt_hfp_ag_vts_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + char code; + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + err = get_char(buf, &code); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + if (!IS_VALID_DTMF(code)) { + LOG_ERR("Invalid code"); + return -EINVAL; + } + + if (!get_active_calls(ag)) { + LOG_ERR("Not valid ongoing call"); + return -ENOTSUP; + } + + if (bt_ag && bt_ag->transmit_dtmf_code) { + bt_ag->transmit_dtmf_code(ag, code); + } else { + return -ENOTSUP; + } + + return 0; +} + static struct bt_hfp_ag_at_cmd_handler cmd_handlers[] = { {"AT+BRSF", bt_hfp_ag_brsf_handler}, {"AT+BAC", bt_hfp_ag_bac_handler}, {"AT+CIND", bt_hfp_ag_cind_handler}, {"AT+CMER", bt_hfp_ag_cmer_handler}, @@ -3007,6 +3059,7 @@ static struct bt_hfp_ag_at_cmd_handler cmd_handlers[] = { {"AT+VGS", bt_hfp_ag_vgs_handler}, {"AT+NREC", bt_hfp_ag_nrec_handler}, {"AT+BTRH", bt_hfp_ag_btrh_handler}, {"AT+CCWA", bt_hfp_ag_ccwa_handler}, {"AT+BVRA", bt_hfp_ag_bvra_handler}, {"AT+BINP", bt_hfp_ag_binp_handler}, + {"AT+VTS", bt_hfp_ag_vts_handler}, }; static void hfp_ag_connected(struct bt_rfcomm_dlc *dlc) From 0e22767ee802b4dc2e5356493b508506dc722b5b Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Tue, 27 Aug 2024 16:01:31 +0800 Subject: [PATCH 49/76] Bluetooth: HFP_HF: Query subscriber number Add function `bt_hfp_hf_query_subscriber` to query the AG subscriber number. Add callback `subscriber_number` to notify the result of the query of the subscriber number information. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 43 +++++++++++++ subsys/bluetooth/host/classic/hfp_hf.c | 74 +++++++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index 365c3d036511e..5a99cdec52b96 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -383,6 +383,39 @@ struct bt_hfp_hf_cb { * @param number Value of ``. */ void (*request_phone_number)(struct bt_hfp_hf *hf, const char *number); + + /** Query subscriber number callback + * + * If this callback is provided it will be called whenever the + * result code `+CUNM: [],, ,[ ,]` + * is received from AG. + * ``: This optional field is not supported, and shall be left + * blank. + * ``: Quoted string containing the phone number in the format + * specified by ``. + * `` field specifies the format of the phone number provided, + * and can be one of the following values: + * - values 128-143: The phone number format may be a national or + * international format, and may contain prefix and/or escape digits. + * No changes on the number presentation are required. + * - values 144-159: The phone number format is an international + * number, including the country code prefix. If the plus sign ("+") + * is not included as part of the number and shall be added by the AG + * as needed. + * - values 160-175: National number. No prefix nor escape digits + * included. + * ``: This optional field is not supported, and shall be left + * blank. + * ``: Indicates which service this phone number relates to. + * Shall be either 4 (voice) or 5 (fax). + * + * @param hf HFP HF object. + * @param number Value of `` without quotes. + * @param type Value of ``. + * @param service Value of ``. + */ + void (*subscriber_number)(struct bt_hfp_hf *hf, const char *number, uint8_t type, + uint8_t service); }; /** @brief Register HFP HF profile @@ -805,6 +838,16 @@ int bt_hfp_hf_request_phone_number(struct bt_hfp_hf *hf); */ int bt_hfp_hf_transmit_dtmf_code(struct bt_hfp_hf_call *call, char code); +/** @brief Handsfree HF Query Subscriber Number Information + * + * It allows HF to query the AG subscriber number by sending `AT+CNUM`. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_query_subscriber(struct bt_hfp_hf *hf); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 2e9e5713b7d61..778d9a9605f10 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -1603,6 +1603,45 @@ int chld_handle(struct at_client *hf_at) } #endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +static int cnum_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + char *alpha; + char *number; + uint32_t type; + char *speed; + uint32_t service = 4; + + alpha = at_get_raw_string(hf_at, NULL); + number = at_get_string(hf_at); + if (!number) { + LOG_INF("Cannot get number"); + return -EINVAL; + } + + err = at_get_number(hf_at, &type); + if (err) { + LOG_INF("Cannot get type"); + return -EINVAL; + } + + speed = at_get_raw_string(hf_at, NULL); + + err = at_get_number(hf_at, &service); + if (err) { + LOG_INF("Cannot get service"); + } + + if (bt_hf->subscriber_number) { + bt_hf->subscriber_number(hf, number, (uint8_t)type, (uint8_t)service); + } + + LOG_DBG("CNUM number %s type %d service %d", number, type, service); + + return 0; +} + static const struct unsolicited { const char *cmd; enum at_cmd_type type; @@ -1632,6 +1671,7 @@ static const struct unsolicited { #if defined(CONFIG_BT_HFP_HF_VOICE_RECG) { "BVRA", AT_CMD_TYPE_UNSOLICITED, bvra_handle }, #endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ + { "CNUM", AT_CMD_TYPE_UNSOLICITED, cnum_handle }, }; static const struct unsolicited *hfp_hf_unsol_lookup(struct at_client *hf_at) @@ -2387,6 +2427,40 @@ int bt_hfp_hf_transmit_dtmf_code(struct bt_hfp_hf_call *call, char code) return err; } +static int cnum_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+CNUM (result %d) on %p", result, hf); + + return 0; +} + +int bt_hfp_hf_query_subscriber(struct bt_hfp_hf *hf) +{ + int err; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + LOG_ERR("SLC is not established on %p", hf); + return -ENOTCONN; + } + + err = hfp_hf_send_cmd(hf, NULL, cnum_finish, "AT+CNUM"); + if (err < 0) { + LOG_ERR("Fail to query subscriber number information on %p", hf); + } + + return err; +} + static int ata_finish(struct at_client *hf_at, enum at_result result, enum at_cme cme_err) { From 40f94bd8cc6817bfdf6e04b96f6e037f55714995 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Tue, 27 Aug 2024 17:10:35 +0800 Subject: [PATCH 50/76] Bluetooth: HFP_AG: Send subscriber number info Handle AT command `AT+CNUM`. Add a callback function `subscriber_number` to notify the upper layer that the HF needs to get subscriber number information from the AG. If there is subscriber number information needs to be sent, the passed function `bt_hfp_ag_query_subscriber_func_t func` can be called for this purpose. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_ag.h | 39 +++++++++++++++++++++++ subsys/bluetooth/host/classic/hfp_ag.c | 31 +++++++++++++++++- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/include/zephyr/bluetooth/classic/hfp_ag.h b/include/zephyr/bluetooth/classic/hfp_ag.h index e766b698c34cb..7aac40d19550f 100644 --- a/include/zephyr/bluetooth/classic/hfp_ag.h +++ b/include/zephyr/bluetooth/classic/hfp_ag.h @@ -44,6 +44,33 @@ enum bt_hfp_ag_indicator { struct bt_hfp_ag; struct bt_hfp_ag_call; +/** @typedef bt_hfp_ag_query_subscriber_func_t + * @brief Query subscriber number callback function + * + * When AG wants to send subscriber number information, all information + * will be passed through the callback. And the subscriber number + * information will be sent out in this function. + * + * @param ag HFP AG object. + * @param number Subscriber number. + * @param type Type of subscriber number specifies the format of the phone number provided, + * and can be one of the following values: + * - values 128-143: The phone number format may be a national or international + * format, and may contain prefix and/or escape digits. No changes on the number + * presentation are required. + * - values 144-159: The phone number format is an international number, including + * the country code prefix. If the plus sign ("+") is not included as part of the + * number and shall be added by the AG as needed. + * - values 160-175: National number. No prefix nor escape digits included. + * @param service Service of subscriber number indicates which service this phone number relates + * to. Shall be either 4 (voice) or 5 (fax). + * + * @return 0 if should continue to the next subscriber number information. + * @return negative value to stop. + */ +typedef int (*bt_hfp_ag_query_subscriber_func_t)(struct bt_hfp_ag *ag, char *number, uint8_t type, + uint8_t service); + /** @brief HFP profile AG application callback */ struct bt_hfp_ag_cb { /** HF AG connected callback to application @@ -349,6 +376,18 @@ struct bt_hfp_ag_cb { * @param code A specific DTMF code. */ void (*transmit_dtmf_code)(struct bt_hfp_ag *ag, char code); + + /** Get subscriber number callback + * + * If this callback is provided it will be called whenever the + * AT command `AT+CNUM` is received. + * + * @param ag HFP AG object. + * @param func Query subscriber number callback. + * + * @return 0 in case of success or negative value in case of error. + */ + int (*subscriber_number)(struct bt_hfp_ag *ag, bt_hfp_ag_query_subscriber_func_t func); }; /** @brief Register HFP AG profile diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index 7f986467b4f74..4988c87ab2b2b 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -3046,6 +3046,35 @@ static int bt_hfp_ag_vts_handler(struct bt_hfp_ag *ag, struct net_buf *buf) return 0; } +static int send_subscriber_number(struct bt_hfp_ag *ag, char *number, + uint8_t type, uint8_t service) +{ + int err; + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+CNUM:,\"%s\",%d,,%d\r\n", + number, type, service); + if (err) { + LOG_ERR("Fail to send subscriber number :(%d)", err); + } + return err; +} + +static int bt_hfp_ag_cnum_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + if (bt_ag && bt_ag->subscriber_number) { + err = bt_ag->subscriber_number(ag, send_subscriber_number); + return err; + } + + return 0; +} + static struct bt_hfp_ag_at_cmd_handler cmd_handlers[] = { {"AT+BRSF", bt_hfp_ag_brsf_handler}, {"AT+BAC", bt_hfp_ag_bac_handler}, {"AT+CIND", bt_hfp_ag_cind_handler}, {"AT+CMER", bt_hfp_ag_cmer_handler}, @@ -3059,7 +3088,7 @@ static struct bt_hfp_ag_at_cmd_handler cmd_handlers[] = { {"AT+VGS", bt_hfp_ag_vgs_handler}, {"AT+NREC", bt_hfp_ag_nrec_handler}, {"AT+BTRH", bt_hfp_ag_btrh_handler}, {"AT+CCWA", bt_hfp_ag_ccwa_handler}, {"AT+BVRA", bt_hfp_ag_bvra_handler}, {"AT+BINP", bt_hfp_ag_binp_handler}, - {"AT+VTS", bt_hfp_ag_vts_handler}, + {"AT+VTS", bt_hfp_ag_vts_handler}, {"AT+CNUM", bt_hfp_ag_cnum_handler}, }; static void hfp_ag_connected(struct bt_rfcomm_dlc *dlc) From e2c55fedb46dade04fae348344f4212e7ceea570 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Mon, 2 Sep 2024 14:43:23 +0800 Subject: [PATCH 51/76] Bluetooth: HFP_AG: Set signal strength Add a function `bt_hfp_ag_signal_strength` to set the signal strength. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_ag.h | 11 ++++++++ subsys/bluetooth/host/classic/hfp_ag.c | 34 +++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/include/zephyr/bluetooth/classic/hfp_ag.h b/include/zephyr/bluetooth/classic/hfp_ag.h index 7aac40d19550f..835e2b6138c32 100644 --- a/include/zephyr/bluetooth/classic/hfp_ag.h +++ b/include/zephyr/bluetooth/classic/hfp_ag.h @@ -703,6 +703,17 @@ int bt_hfp_ag_vre_state(struct bt_hfp_ag *ag, uint8_t state); int bt_hfp_ag_vre_textual_representation(struct bt_hfp_ag *ag, uint8_t state, const char *id, uint8_t type, uint8_t operation, const char *text); +/** @brief Set signal strength + * + * Set signal strength. + * + * @param ag HFP AG object. + * @param strength Signal strength. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_signal_strength(struct bt_hfp_ag *ag, uint8_t strength); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index 4988c87ab2b2b..3d0ff2330846b 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -376,6 +376,14 @@ static int hfp_ag_update_indicator(struct bt_hfp_ag *ag, enum bt_hfp_ag_indicato int err; uint8_t old_value; + if (index >= BT_HFP_AG_IND_MAX) { + return -EINVAL; + } + + if ((ag_ind[index].max < value) || (ag_ind[index].min > value)) { + return -EINVAL; + } + hfp_ag_lock(ag); old_value = ag->indicator_value[index]; if (value == old_value) { @@ -4612,3 +4620,29 @@ int bt_hfp_ag_vre_textual_representation(struct bt_hfp_ag *ag, uint8_t state, co return -ENOTSUP; #endif /* CONFIG_BT_HFP_AG_VOICE_RECG_TEXT */ } + +int bt_hfp_ag_signal_strength(struct bt_hfp_ag *ag, uint8_t strength) +{ + int err; + + LOG_DBG(""); + + if (ag == NULL) { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + hfp_ag_unlock(ag); + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_SIGNAL_IND, strength, + NULL, NULL); + if (err) { + LOG_ERR("Fail to set signal strength err :(%d)", err); + } + + return err; +} From 7aa582765b3cb3388411f1a4ab7480984377de2b Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Tue, 27 Aug 2024 17:39:23 +0800 Subject: [PATCH 52/76] Bluetooth: HFP_AG: Set roaming status Add a function `bt_hfp_ag_roaming_status` to set the roaming status. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_ag.h | 11 ++++++++++ subsys/bluetooth/host/classic/hfp_ag.c | 26 +++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/include/zephyr/bluetooth/classic/hfp_ag.h b/include/zephyr/bluetooth/classic/hfp_ag.h index 835e2b6138c32..7ce10fc3288c4 100644 --- a/include/zephyr/bluetooth/classic/hfp_ag.h +++ b/include/zephyr/bluetooth/classic/hfp_ag.h @@ -714,6 +714,17 @@ int bt_hfp_ag_vre_textual_representation(struct bt_hfp_ag *ag, uint8_t state, co */ int bt_hfp_ag_signal_strength(struct bt_hfp_ag *ag, uint8_t strength); +/** @brief Set roaming status + * + * Set roaming status. + * + * @param ag HFP AG object. + * @param status Roaming status. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_roaming_status(struct bt_hfp_ag *ag, uint8_t status); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index 3d0ff2330846b..86824e1c20b7b 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -4646,3 +4646,29 @@ int bt_hfp_ag_signal_strength(struct bt_hfp_ag *ag, uint8_t strength) return err; } + +int bt_hfp_ag_roaming_status(struct bt_hfp_ag *ag, uint8_t status) +{ + int err; + + LOG_DBG(""); + + if (ag == NULL) { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + hfp_ag_unlock(ag); + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_ROAM_IND, status, + NULL, NULL); + if (err) { + LOG_ERR("Fail to set roaming status err :(%d)", err); + } + + return err; +} From 28ef3ed7c508b2d35861c27bd9ccfd576d776060 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Tue, 27 Aug 2024 17:43:14 +0800 Subject: [PATCH 53/76] Bluetooth: HFP_AG: Set battery level Add a function `bt_hfp_ag_battery_level` to set the battery level. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_ag.h | 11 ++++++++++ subsys/bluetooth/host/classic/hfp_ag.c | 26 +++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/include/zephyr/bluetooth/classic/hfp_ag.h b/include/zephyr/bluetooth/classic/hfp_ag.h index 7ce10fc3288c4..5b695b1736def 100644 --- a/include/zephyr/bluetooth/classic/hfp_ag.h +++ b/include/zephyr/bluetooth/classic/hfp_ag.h @@ -725,6 +725,17 @@ int bt_hfp_ag_signal_strength(struct bt_hfp_ag *ag, uint8_t strength); */ int bt_hfp_ag_roaming_status(struct bt_hfp_ag *ag, uint8_t status); +/** @brief Set battery level + * + * Set battery level. + * + * @param ag HFP AG object. + * @param level battery level. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_battery_level(struct bt_hfp_ag *ag, uint8_t level); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index 86824e1c20b7b..4d579da72f069 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -4672,3 +4672,29 @@ int bt_hfp_ag_roaming_status(struct bt_hfp_ag *ag, uint8_t status) return err; } + +int bt_hfp_ag_battery_level(struct bt_hfp_ag *ag, uint8_t level) +{ + int err; + + LOG_DBG(""); + + if (ag == NULL) { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + hfp_ag_unlock(ag); + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_BATTERY_IND, level, + NULL, NULL); + if (err) { + LOG_ERR("Fail to set battery level err :(%d)", err); + } + + return err; +} From 4e193cfe9ef5c748eb34402c2b0fa812e6bf2cd9 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Tue, 27 Aug 2024 17:57:20 +0800 Subject: [PATCH 54/76] Bluetooth: HFP_AG: Set service availability Add a function `bt_hfp_ag_service_availability` to set the service availability. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_ag.h | 11 ++++++++++ subsys/bluetooth/host/classic/hfp_ag.c | 26 +++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/include/zephyr/bluetooth/classic/hfp_ag.h b/include/zephyr/bluetooth/classic/hfp_ag.h index 5b695b1736def..ae6bf3b30b25a 100644 --- a/include/zephyr/bluetooth/classic/hfp_ag.h +++ b/include/zephyr/bluetooth/classic/hfp_ag.h @@ -736,6 +736,17 @@ int bt_hfp_ag_roaming_status(struct bt_hfp_ag *ag, uint8_t status); */ int bt_hfp_ag_battery_level(struct bt_hfp_ag *ag, uint8_t level); +/** @brief Set service availability + * + * Set service availability. + * + * @param ag HFP AG object. + * @param available service availability + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_service_availability(struct bt_hfp_ag *ag, bool available); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index 4d579da72f069..9c20493fe0699 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -4698,3 +4698,29 @@ int bt_hfp_ag_battery_level(struct bt_hfp_ag *ag, uint8_t level) return err; } + +int bt_hfp_ag_service_availability(struct bt_hfp_ag *ag, bool available) +{ + int err; + + LOG_DBG(""); + + if (ag == NULL) { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + hfp_ag_unlock(ag); + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_SERVICE_IND, + available ? 1 : 0, NULL, NULL); + if (err) { + LOG_ERR("Fail to set service availability err :(%d)", err); + } + + return err; +} From c629e88febc04a83ffe1b4f14b90fd6f2029370d Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Wed, 28 Aug 2024 16:04:45 +0800 Subject: [PATCH 55/76] Bluetooth: HFP_HF: Activate/deactivate AG indicators Add a function `bt_hfp_hf_indicator_status` to activate/deactivate AG indicators. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 29 ++++++++++ subsys/bluetooth/host/classic/hfp_hf.c | 61 ++++++++++++++++++++ subsys/bluetooth/host/classic/hfp_internal.h | 10 ---- 3 files changed, 90 insertions(+), 10 deletions(-) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index 5a99cdec52b96..31efb3fdf4e07 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -848,6 +848,35 @@ int bt_hfp_hf_transmit_dtmf_code(struct bt_hfp_hf_call *call, char code); */ int bt_hfp_hf_query_subscriber(struct bt_hfp_hf *hf); +/* HFP HF Indicators */ +enum hfp_hf_ag_indicators { + HF_SERVICE_IND = 0, /* AG service indicator */ + HF_CALL_IND, /* AG call indicator */ + HF_CALL_SETUP_IND, /* AG call setup indicator */ + HF_CALL_HELD_IND, /* AG call held indicator */ + HF_SINGNAL_IND, /* AG signal indicator */ + HF_ROAM_IND, /* AG roaming indicator */ + HF_BATTERY_IND /* AG battery indicator */ +}; + +/** @brief Handsfree HF set AG indicator activated/deactivated status + * + * It allows HF to issue the AT+BIA command if it needs to change the + * activated/deactivated status of indicators in the AG. + * The index of all indicators can be activated/deactivated are + * defined in `enum hfp_hf_ag_indicators`. + * The each bit of parameter `status` represents the indicator status + * corresponding to the index. Such as, value 0b111110 of `status` + * means the AG indicator `service` is required to be deactivated. + * Others are required to be activated. + * + * @param hf HFP HF object. + * @param status The activated/deactivated bitmap status of AG indicators. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_indicator_status(struct bt_hfp_hf *hf, uint8_t status); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 778d9a9605f10..e38e3966b5559 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -2461,6 +2461,67 @@ int bt_hfp_hf_query_subscriber(struct bt_hfp_hf *hf) return err; } +static int bia_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BIA (result %d) on %p", result, hf); + + return 0; +} + +int bt_hfp_hf_indicator_status(struct bt_hfp_hf *hf, uint8_t status) +{ + int err; + size_t index; + char buffer[HF_MAX_AG_INDICATORS * 2 + 1]; + char *bia_status; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + LOG_ERR("SLC is not established on %p", hf); + return -ENOTCONN; + } + + bia_status = &buffer[0]; + for (index = 0; index < ARRAY_SIZE(hf->ind_table); index++) { + if ((hf->ind_table[index] != -1) && (index < NUM_BITS(sizeof(status)))) { + if (status & BIT(hf->ind_table[index])) { + *bia_status = '1'; + } else { + *bia_status = '0'; + } + bia_status++; + *bia_status = ','; + bia_status++; + } else { + break; + } + } + + if (bia_status <= &buffer[0]) { + LOG_ERR("Not found valid AG indicator on %p", hf); + return -EINVAL; + } + + bia_status--; + *bia_status = '\0'; + + err = hfp_hf_send_cmd(hf, NULL, bia_finish, "AT+BIA=%s", buffer); + if (err < 0) { + LOG_ERR("Fail to activated/deactivated AG indicators on %p", hf); + } + + return err; +} + static int ata_finish(struct at_client *hf_at, enum at_result result, enum at_cme cme_err) { diff --git a/subsys/bluetooth/host/classic/hfp_internal.h b/subsys/bluetooth/host/classic/hfp_internal.h index be776021eb856..70bee514c53df 100644 --- a/subsys/bluetooth/host/classic/hfp_internal.h +++ b/subsys/bluetooth/host/classic/hfp_internal.h @@ -262,16 +262,6 @@ struct bt_hfp_hf { ATOMIC_DEFINE(flags, BT_HFP_HF_NUM_FLAGS); }; -enum hfp_hf_ag_indicators { - HF_SERVICE_IND, - HF_CALL_IND, - HF_CALL_SETUP_IND, - HF_CALL_HELD_IND, - HF_SINGNAL_IND, - HF_ROAM_IND, - HF_BATTERY_IND -}; - /* HFP call setup status */ #define BT_HFP_CALL_SETUP_NONE 0 #define BT_HFP_CALL_SETUP_INCOMING 1 From 7f10c0e39090b786d8f38648a289126062110694 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Wed, 28 Aug 2024 10:05:21 +0800 Subject: [PATCH 56/76] Bluetooth: HFP_AG: Indicators Activation and Deactivation Improve the process of AT command `AT+BIA` to mask the activated/ deactivated indicators. Only notify the activated indicator if the value of it is updated. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/hfp_ag.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index 9c20493fe0699..b270236e7f369 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -401,6 +401,12 @@ static int hfp_ag_update_indicator(struct bt_hfp_ag *ag, enum bt_hfp_ag_indicato LOG_DBG("indicator %d, old %d -> new %d", index, old_value, value); + if (!(ag->indicator & BIT(index))) { + LOG_INF("The indicator %d is deactivated", index); + /* If the indicator is deactivated, consider it a successful set. */ + return 0; + } + err = hfp_ag_send_data(ag, cb, user_data, "\r\n+CIEV:%d,%d\r\n", (uint8_t)index + 1, value); if (err) { hfp_ag_lock(ag); @@ -1923,7 +1929,7 @@ static int bt_hfp_ag_bia_handler(struct bt_hfp_ag *ag, struct net_buf *buf) } /* Force call, call setup and held call indicators are enabled. */ - indicator = BIT(BT_HFP_AG_CALL_IND) | BIT(BT_HFP_AG_CALL_SETUP_IND) | + indicator |= BIT(BT_HFP_AG_CALL_IND) | BIT(BT_HFP_AG_CALL_SETUP_IND) | BIT(BT_HFP_AG_CALL_HELD_IND); hfp_ag_lock(ag); From 272cdb840de1a5e04ce647f99009919260656af2 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Wed, 28 Aug 2024 11:44:23 +0800 Subject: [PATCH 57/76] Bluetooth: HFP_HF: Optimize SLC init procedure In current implementation, it is difficult to insert new AT commands in the SLC initialization sequence. Add a SLC initialization sequence. Send AT command one by one. It will be easy to insert new AT commands in the appropriate position. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/hfp_hf.c | 234 ++++++++++++------------- 1 file changed, 112 insertions(+), 122 deletions(-) diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index e38e3966b5559..3c6b53ec938c3 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -1789,51 +1789,6 @@ static int at_vgs_finish(struct at_client *hf_at, enum at_result result, } #endif /* CONFIG_BT_HFP_HF_VOLUME */ -#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) -static void get_codec_ids(struct bt_hfp_hf *hf, char *buffer, size_t buffer_len) -{ - size_t len = 0; - uint8_t ids = hf->hf_codec_ids; - int index = 0; - - while (ids && (len < (buffer_len-2))) { - if (ids & 0x01) { - buffer[len++] = index + '0'; - buffer[len++] = ','; - } - index ++; - ids = ids >> 1; - } - - if (len > 0) { - len --; - } - - buffer[len] = '\0'; -} - -static int send_at_bac(struct bt_hfp_hf *hf, at_finish_cb_t cb) -{ - if (hf->ag_features & BT_HFP_AG_FEATURE_CODEC_NEG) { - char ids[sizeof(hf->hf_codec_ids)*2*8 + 1]; - get_codec_ids(hf, &ids[0], ARRAY_SIZE(ids)); - return hfp_hf_send_cmd(hf, NULL, cb, "AT+BAC=%s", ids); - } - - return -ENOTSUP; -} - -static int at_bac_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) -{ - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - - LOG_DBG("BAC set (result %d) on %p", result, hf); - - return 0; -} -#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ - #if defined(CONFIG_BT_HFP_HF_3WAY_CALL) static int send_at_ccwa(struct bt_hfp_hf *hf, at_finish_cb_t cb) { @@ -1876,9 +1831,6 @@ static struct at_cmd_init #if defined(CONFIG_BT_HFP_HF_CLI) {send_at_clip, at_clip_finish, false}, #endif /* CONFIG_BT_HFP_HF_CLI */ -#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) - {send_at_bac, at_bac_finish, false}, -#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ #if defined(CONFIG_BT_HFP_HF_3WAY_CALL) {send_at_ccwa, at_ccwa_finish, false}, #endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ @@ -1962,114 +1914,152 @@ static void slc_completed(struct at_client *hf_at) } #if defined(CONFIG_BT_HFP_HF_3WAY_CALL) -int chld_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) +static int send_at_chld_supported(struct bt_hfp_hf *hf, at_finish_cb_t cb) { - if (result != AT_RESULT_OK) { - LOG_ERR("SLC Connection ERROR in response"); - hf_slc_error(hf_at); - return -EINVAL; - } + return hfp_hf_send_cmd(hf, NULL, cb, "AT+CHLD=?"); +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ - slc_completed(hf_at); +static int send_at_cmer(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + at_register_unsolicited(&hf->at, unsolicited_cb); + return hfp_hf_send_cmd(hf, NULL, cb, "AT+CMER=3,0,0,1"); +} - return 0; +static int send_at_cind_status(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, cind_status_resp, cb, "AT+CIND?"); } -#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ -int cmer_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) +static int send_at_cind_supported(struct bt_hfp_hf *hf, at_finish_cb_t cb) { -#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - int err; -#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + return hfp_hf_send_cmd(hf, cind_resp, cb, "AT+CIND=?"); +} - if (result != AT_RESULT_OK) { - LOG_ERR("SLC Connection ERROR in response"); - hf_slc_error(hf_at); - return -EINVAL; - } +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +static void get_codec_ids(struct bt_hfp_hf *hf, char *buffer, size_t buffer_len) +{ + size_t len = 0; + uint8_t ids = hf->hf_codec_ids; + int index = 0; -#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) - if ((hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL) && - (hf->hf_features & BT_HFP_HF_FEATURE_3WAY_CALL)) { - err = hfp_hf_send_cmd(hf, NULL, chld_finish, "AT+CHLD=?"); - if (err < 0) { - hf_slc_error(hf_at); + while (ids && (len < (buffer_len-2))) { + if (ids & 0x01) { + buffer[len++] = index + '0'; + buffer[len++] = ','; } - return err; + index++; + ids = ids >> 1; } -#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ - slc_completed(hf_at); + if (len > 0) { + len--; + } - return 0; + buffer[len] = '\0'; } -int cind_status_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) +static int send_at_bac(struct bt_hfp_hf *hf, at_finish_cb_t cb) { - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - int err; - - if (result != AT_RESULT_OK) { - LOG_ERR("SLC Connection ERROR in response"); - hf_slc_error(hf_at); - return -EINVAL; - } + char ids[sizeof(hf->hf_codec_ids)*2*8 + 1]; - at_register_unsolicited(hf_at, unsolicited_cb); - err = hfp_hf_send_cmd(hf, NULL, cmer_finish, "AT+CMER=3,0,0,1"); - if (err < 0) { - hf_slc_error(hf_at); - return err; - } + get_codec_ids(hf, &ids[0], ARRAY_SIZE(ids)); + return hfp_hf_send_cmd(hf, NULL, cb, "AT+BAC=%s", ids); +} +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ - return 0; +static int send_at_brsf(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, brsf_resp, cb, "AT+BRSF=%u", hf->hf_features); } -int cind_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) +static struct slc_init +{ + at_send_t send; + bool disconnect; /* Disconnect if command failed. */ + uint32_t ag_feature_mask; /* AG feature mask */ +} slc_init_list[] = { + {send_at_brsf, true, 0}, +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) + {send_at_bac, true, BT_HFP_AG_FEATURE_CODEC_NEG}, +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + {send_at_cind_supported, true, 0}, + {send_at_cind_status, true, 0}, + {send_at_cmer, true, 0}, +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + {send_at_chld_supported, true, BT_HFP_AG_FEATURE_3WAY_CALL}, +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +}; + +static int slc_init_start(struct bt_hfp_hf *hf); + +static int slc_init_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - int err; if (result != AT_RESULT_OK) { - LOG_ERR("SLC Connection ERROR in response"); - hf_slc_error(hf_at); - return -EINVAL; + LOG_WRN("It is ERROR response of AT command %d.", hf->cmd_init_seq); + if (slc_init_list[hf->cmd_init_seq].disconnect) { + hf_slc_error(&hf->at); + return 0; + } } - err = hfp_hf_send_cmd(hf, cind_status_resp, cind_status_finish, - "AT+CIND?"); - if (err < 0) { - hf_slc_error(hf_at); - return err; + if (ARRAY_SIZE(slc_init_list) <= hf->cmd_init_seq) { + LOG_ERR("Invalid indicator (%d>=%d)", hf->cmd_init_seq, + ARRAY_SIZE(slc_init_list)); } + /* Goto next AT command */ + hf->cmd_init_seq++; + (void)slc_init_start(hf); return 0; } -int brsf_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) +static int slc_init_start(struct bt_hfp_hf *hf) { - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - int err; + at_send_t send; + uint32_t feture_mask; + int err = -EINVAL; - if (result != AT_RESULT_OK) { - LOG_ERR("SLC Connection ERROR in response"); - hf_slc_error(hf_at); - return -EINVAL; + while (ARRAY_SIZE(slc_init_list) > hf->cmd_init_seq) { + LOG_DBG("Fetch AT command (%d)", hf->cmd_init_seq); + feture_mask = slc_init_list[hf->cmd_init_seq].ag_feature_mask; + if (feture_mask && (!(feture_mask & hf->ag_features))) { + /* The feature is not supported by AG. Skip the step. */ + LOG_INF("Skip SLC init step %d", hf->cmd_init_seq); + hf->cmd_init_seq++; + continue; + } + + send = slc_init_list[hf->cmd_init_seq].send; + if (send) { + LOG_DBG("Send AT command"); + err = send(hf, slc_init_finish); + } else { + LOG_WRN("Invalid send func of AT command"); + } + + if (!err) { + break; + } + + LOG_WRN("AT command sending failed"); + if (slc_init_list[hf->cmd_init_seq].disconnect) { + hfp_hf_send_failed(hf); + break; + } + /* Goto next AT command */ + LOG_WRN("Send next AT command"); + hf->cmd_init_seq++; } - err = hfp_hf_send_cmd(hf, cind_resp, cind_finish, "AT+CIND=?"); - if (err < 0) { - hf_slc_error(hf_at); - return err; + if (ARRAY_SIZE(slc_init_list) <= hf->cmd_init_seq) { + slc_completed(&hf->at); } - return 0; + return err; } int hf_slc_establish(struct bt_hfp_hf *hf) @@ -2078,8 +2068,8 @@ int hf_slc_establish(struct bt_hfp_hf *hf) LOG_DBG(""); - err = hfp_hf_send_cmd(hf, brsf_resp, brsf_finish, "AT+BRSF=%u", - hf->hf_features); + hf->cmd_init_seq = 0; + err = slc_init_start(hf); if (err < 0) { hf_slc_error(&hf->at); return err; From fe05f779a7764177358e4a73d454b6e46da93413 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Wed, 28 Aug 2024 13:10:13 +0800 Subject: [PATCH 58/76] Bluetooth: HFP_HF: Support HF Indicators Add configuration `CONFIG_BT_HFP_HF_HF_INDICATORS` to enable feature HF Indicators. Add configuration `CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY` to support HF indicator `Enhanced Safety`. Add configuration `CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY` to support HF indicator `Remaining level of Battery`. Add function `bt_hfp_hf_enhanced_safety` to transfer enhanced safety status. Add function `bt_hfp_hf_battery` to transfer Remaining level of Battery. Send AT command to notify AG the supported HF indicators of HF in SLC initialization sequence. Send AT command to get the supported HF indicators of AG in SLC initialization sequence. Send AT command to get the enabled/disabled state of generic status indicators from AG in SLC initialization sequence. Handle unsolicited result code `+BIND`. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 26 ++ subsys/bluetooth/host/classic/Kconfig | 17 ++ .../bluetooth/host/classic/hfp_ag_internal.h | 7 - subsys/bluetooth/host/classic/hfp_hf.c | 222 ++++++++++++++++++ subsys/bluetooth/host/classic/hfp_internal.h | 22 +- 5 files changed, 286 insertions(+), 8 deletions(-) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index 31efb3fdf4e07..4af640ad58755 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -877,6 +877,32 @@ enum hfp_hf_ag_indicators { */ int bt_hfp_hf_indicator_status(struct bt_hfp_hf *hf, uint8_t status); +/** @brief Handsfree HF enable/disable enhanced safety + * + * It allows HF to transfer of HF indicator enhanced safety value. + * If @kconfig{CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY} is not enabled, + * the error `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * @param enable The enhanced safety is enabled/disabled. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_enhanced_safety(struct bt_hfp_hf *hf, bool enable); + +/** @brief Handsfree HF remaining battery level + * + * It allows HF to transfer of HF indicator remaining battery level value. + * If @kconfig{CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY} is not enabled, + * the error `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * @param level The remaining battery level. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_battery(struct bt_hfp_hf *hf, uint8_t level); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/Kconfig b/subsys/bluetooth/host/classic/Kconfig index c4846c0db0e66..c81b5a2c1bb67 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -251,6 +251,23 @@ config BT_HFP_HF_VOICE_RECG_TEXT help This option enables Voice Recognition Text for HFP HF +config BT_HFP_HF_HF_INDICATORS + bool "HF Indicators for HFP HF [EXPERIMENTAL]" + help + This option enables HF Indicators for HFP HF + +config BT_HFP_HF_HF_INDICATOR_ENH_SAFETY + bool "HF Indicator Enhanced Safety for HFP HF [EXPERIMENTAL]" + select BT_HFP_HF_HF_INDICATORS + help + This option enables HF Indicator Enhanced Safety for HFP HF + +config BT_HFP_HF_HF_INDICATOR_BATTERY + bool "HF Indicator Battery level for HFP HF [EXPERIMENTAL]" + select BT_HFP_HF_HF_INDICATORS + help + This option enables HF Indicator Battery level for HFP HF + endif # BT_HFP_HF if BT_HFP_AG diff --git a/subsys/bluetooth/host/classic/hfp_ag_internal.h b/subsys/bluetooth/host/classic/hfp_ag_internal.h index 4f6d8abc2eadd..0a8a12a72f3dd 100644 --- a/subsys/bluetooth/host/classic/hfp_ag_internal.h +++ b/subsys/bluetooth/host/classic/hfp_ag_internal.h @@ -149,13 +149,6 @@ enum { BT_HFP_AG_CALL_NUM_FLAGS, }; -/* HFP HF Indicators */ -enum { - HFP_HF_ENHANCED_SAFETY_IND = 1, /* Enhanced Safety */ - HFP_HF_BATTERY_LEVEL_IND = 2, /* Remaining level of Battery */ - HFP_HF_IND_MAX -}; - typedef enum __packed { /** Session disconnected */ BT_HFP_DISCONNECTED, diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 3c6b53ec938c3..ecf008d0876ea 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -1642,6 +1642,66 @@ static int cnum_handle(struct at_client *hf_at) return 0; } +#if defined(CONFIG_BT_HFP_HF_HF_INDICATORS) +static int bind_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + uint32_t index; + uint32_t value; + uint32_t ind = 0; + uint32_t ind_enable = hf->ind_enable; + + err = at_open_list(hf_at); + if (!err) { + /* It is a list. */ + while (at_has_next_list(hf_at)) { + err = at_get_number(hf_at, &index); + if (err) { + LOG_INF("Cannot get indicator"); + goto failed; + } + + ind |= BIT(index); + } + + if (at_close_list(hf_at) < 0) { + LOG_ERR("Could not get close list"); + goto failed; + } + + hf->ag_ind = ind; + return 0; + } + + err = at_get_number(hf_at, &index); + if (err) { + LOG_INF("Cannot get indicator"); + goto failed; + } + + err = at_get_number(hf_at, &value); + if (err) { + LOG_INF("Cannot get status"); + goto failed; + } + + if (!value) { + ind_enable &= ~BIT(index); + } else { + ind_enable |= BIT(index); + } + + hf->ind_enable = ind_enable; + return 0; + +failed: + LOG_ERR("Error on AT+BIND response"); + hf_slc_error(hf_at); + return -EINVAL; +} +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ + static const struct unsolicited { const char *cmd; enum at_cmd_type type; @@ -1672,6 +1732,9 @@ static const struct unsolicited { { "BVRA", AT_CMD_TYPE_UNSOLICITED, bvra_handle }, #endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ { "CNUM", AT_CMD_TYPE_UNSOLICITED, cnum_handle }, +#if defined(CONFIG_BT_HFP_HF_HF_INDICATORS) + { "BIND", AT_CMD_TYPE_UNSOLICITED, bind_handle }, +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ }; static const struct unsolicited *hfp_hf_unsol_lookup(struct at_client *hf_at) @@ -1913,6 +1976,51 @@ static void slc_completed(struct at_client *hf_at) } } +#if defined(CONFIG_BT_HFP_HF_HF_INDICATORS) +static int send_at_bind_status(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, NULL, cb, "AT+BIND?"); +} + +static int send_at_bind_hf_supported(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + char buffer[4]; + char *bind; + + hf->hf_ind = 0; + + bind = &buffer[0]; + if (IS_ENABLED(CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY)) { + *bind = '0' + HFP_HF_ENHANCED_SAFETY_IND; + bind++; + *bind = ','; + bind++; + hf->hf_ind |= BIT(HFP_HF_ENHANCED_SAFETY_IND); + } + + if (IS_ENABLED(CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY)) { + *bind = '0' + HFP_HF_BATTERY_LEVEL_IND; + bind++; + *bind = ','; + bind++; + hf->hf_ind |= BIT(HFP_HF_BATTERY_LEVEL_IND); + } + + if (bind <= &buffer[0]) { + return -EINVAL; + } + + bind--; + *bind = '\0'; + return hfp_hf_send_cmd(hf, NULL, cb, "AT+BIND=%s", buffer); +} + +static int send_at_bind_supported(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, NULL, cb, "AT+BIND=?"); +} +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ + #if defined(CONFIG_BT_HFP_HF_3WAY_CALL) static int send_at_chld_supported(struct bt_hfp_hf *hf, at_finish_cb_t cb) { @@ -1989,6 +2097,11 @@ static struct slc_init #if defined(CONFIG_BT_HFP_HF_3WAY_CALL) {send_at_chld_supported, true, BT_HFP_AG_FEATURE_3WAY_CALL}, #endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +#if defined(CONFIG_BT_HFP_HF_HF_INDICATORS) + {send_at_bind_hf_supported, true, BT_HFP_AG_FEATURE_HF_IND}, + {send_at_bind_supported, true, BT_HFP_AG_FEATURE_HF_IND}, + {send_at_bind_status, true, BT_HFP_AG_FEATURE_HF_IND}, +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ }; static int slc_init_start(struct bt_hfp_hf *hf); @@ -2512,6 +2625,115 @@ int bt_hfp_hf_indicator_status(struct bt_hfp_hf *hf, uint8_t status) return err; } +#if defined(CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY) +static int biev_enh_safety_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BIEV (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY */ + +int bt_hfp_hf_enhanced_safety(struct bt_hfp_hf *hf, bool enable) +{ +#if defined(CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY) + int err; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + LOG_ERR("SLC is not established on %p", hf); + return -ENOTCONN; + } + + if (!((hf->hf_ind & BIT(HFP_HF_ENHANCED_SAFETY_IND)) && + (hf->ag_ind & BIT(HFP_HF_ENHANCED_SAFETY_IND)))) { + LOG_ERR("The indicator is unsupported"); + return -ENOTSUP; + } + + if (!(hf->ind_enable & BIT(HFP_HF_ENHANCED_SAFETY_IND))) { + LOG_ERR("The indicator is disabled"); + return -EINVAL; + } + + err = hfp_hf_send_cmd(hf, NULL, biev_enh_safety_finish, "AT+BIEV=%d,%d", + HFP_HF_ENHANCED_SAFETY_IND, enable ? 1 : 0); + if (err < 0) { + LOG_ERR("Fail to transfer enhanced safety value on %p", hf); + } + + return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY */ +} + +#if defined(CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY) +static int biev_battery_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BIEV (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY */ + +int bt_hfp_hf_battery(struct bt_hfp_hf *hf, uint8_t level) +{ +#if defined(CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY) + int err; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + LOG_ERR("SLC is not established on %p", hf); + return -ENOTCONN; + } + + if (!((hf->hf_ind & BIT(HFP_HF_BATTERY_LEVEL_IND)) && + (hf->ag_ind & BIT(HFP_HF_BATTERY_LEVEL_IND)))) { + LOG_ERR("The indicator is unsupported"); + return -ENOTSUP; + } + + if (!(hf->ind_enable & BIT(HFP_HF_BATTERY_LEVEL_IND))) { + LOG_ERR("The indicator is disabled"); + return -EINVAL; + } + + if (!IS_VALID_BATTERY_LEVEL(level)) { + LOG_ERR("Invalid battery level %d", level); + return -EINVAL; + } + + err = hfp_hf_send_cmd(hf, NULL, biev_battery_finish, "AT+BIEV=%d,%d", + HFP_HF_BATTERY_LEVEL_IND, level); + if (err < 0) { + LOG_ERR("Fail to transfer remaining battery level on %p", hf); + } + + return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY */ +} + static int ata_finish(struct at_client *hf_at, enum at_result result, enum at_cme cme_err) { diff --git a/subsys/bluetooth/host/classic/hfp_internal.h b/subsys/bluetooth/host/classic/hfp_internal.h index 70bee514c53df..12f89c09d1d6e 100644 --- a/subsys/bluetooth/host/classic/hfp_internal.h +++ b/subsys/bluetooth/host/classic/hfp_internal.h @@ -126,6 +126,12 @@ #define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE 0 #endif /* CONFIG_BT_HFP_HF_VOICE_RECG_TEXT */ +#if defined(CONFIG_BT_HFP_HF_HF_INDICATORS) +#define BT_HFP_HF_FEATURE_HF_IND_ENABLE BT_HFP_HF_FEATURE_HF_IND +#else +#define BT_HFP_HF_FEATURE_HF_IND_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ + /* HFP HF Supported features */ #define BT_HFP_HF_SUPPORTED_FEATURES (\ BT_HFP_HF_FEATURE_CLI_ENABLE | \ @@ -137,7 +143,8 @@ BT_HFP_HF_FEATURE_ECC_ENABLE | \ BT_HFP_HF_FEATURE_VOICE_RECG_ENABLE | \ BT_HFP_HF_FEATURE_ENH_VOICE_RECG_ENABLE | \ - BT_HFP_HF_FEATURE_VOICE_RECG_TEXT_ENABLE) + BT_HFP_HF_FEATURE_VOICE_RECG_TEXT_ENABLE | \ + BT_HFP_HF_FEATURE_HF_IND_ENABLE) /* HFP HF Supported features in SDP */ #define BT_HFP_HF_SDP_SUPPORTED_FEATURES (\ @@ -245,6 +252,10 @@ struct bt_hfp_hf { uint8_t vgs; int8_t ind_table[HF_MAX_AG_INDICATORS]; + uint32_t hf_ind; + uint32_t ag_ind; + uint32_t ind_enable; + /* AT command initialization indicator */ uint8_t cmd_init_seq; @@ -350,3 +361,12 @@ struct bt_hfp_hf { #define IS_VALID_DTMF(c) ((((c) >= '0') && ((c) <= '9')) || \ (((c) >= 'A') && ((c) <= 'D')) || ((c) == '#') || ((c) == '*')) + +/* HFP HF Indicators */ +enum { + HFP_HF_ENHANCED_SAFETY_IND = 1, /* Enhanced Safety */ + HFP_HF_BATTERY_LEVEL_IND = 2, /* Remaining level of Battery */ + HFP_HF_IND_MAX +}; + +#define IS_VALID_BATTERY_LEVEL(level) (((level) >= 0) && ((level) <= 100)) From ffba65ad2c6dadbd00b0aba4a8822a4c0111b81f Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Thu, 29 Aug 2024 14:36:27 +0800 Subject: [PATCH 59/76] Bluetooth: HFP_AG: Support HF Indicators Add configuration `CONFIG_BT_HFP_AG_HF_INDICATORS` to enable feature HF Indicators. Add configuration `CONFIG_BT_HFP_AG_HF_INDICATOR_ENH_SAFETY` to support HF indicator `Enhanced Safety`. Add configuration `CONFIG_BT_HFP_AG_HF_INDICATOR_BATTERY` to support HF indicator `Remaining level of Battery`. Add function `bt_hfp_ag_hf_indicator` to activate/deactivate HF indicator. Optimize the handle of AT command `AT+BIND`. Handle AT command `AT+BIEV`. Add callback `hf_indicator_value` to notify the value of HF indicator. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_ag.h | 43 ++++++ subsys/bluetooth/host/classic/Kconfig | 17 +++ subsys/bluetooth/host/classic/hfp_ag.c | 134 ++++++++++++++++-- .../bluetooth/host/classic/hfp_ag_internal.h | 11 +- 4 files changed, 188 insertions(+), 17 deletions(-) diff --git a/include/zephyr/bluetooth/classic/hfp_ag.h b/include/zephyr/bluetooth/classic/hfp_ag.h index ae6bf3b30b25a..7745c9ff3e7d6 100644 --- a/include/zephyr/bluetooth/classic/hfp_ag.h +++ b/include/zephyr/bluetooth/classic/hfp_ag.h @@ -71,6 +71,12 @@ struct bt_hfp_ag_call; typedef int (*bt_hfp_ag_query_subscriber_func_t)(struct bt_hfp_ag *ag, char *number, uint8_t type, uint8_t service); +/* HF indicators */ +enum hfp_ag_hf_indicators { + HFP_AG_ENHANCED_SAFETY_IND = 1, /* Enhanced Safety */ + HFP_AG_BATTERY_LEVEL_IND = 2, /* Remaining level of Battery */ +}; + /** @brief HFP profile AG application callback */ struct bt_hfp_ag_cb { /** HF AG connected callback to application @@ -388,6 +394,20 @@ struct bt_hfp_ag_cb { * @return 0 in case of success or negative value in case of error. */ int (*subscriber_number)(struct bt_hfp_ag *ag, bt_hfp_ag_query_subscriber_func_t func); + + /** HF indicator value callback + * + * If this callback is provided it will be called whenever the + * AT command `AT+BIEV` is received. + * If @kconfig{CONFIG_BT_HFP_AG_HF_INDICATORS} is not enabled, + * the callback will not be notified. + * + * @param ag HFP AG object. + * @param indicator HF indicator + * @param value The value of specific indicator + */ + void (*hf_indicator_value)(struct bt_hfp_ag *ag, enum hfp_ag_hf_indicators indicator, + uint32_t value); }; /** @brief Register HFP AG profile @@ -747,6 +767,29 @@ int bt_hfp_ag_battery_level(struct bt_hfp_ag *ag, uint8_t level); */ int bt_hfp_ag_service_availability(struct bt_hfp_ag *ag, bool available); +/** @brief Activate/deactivate HF indicator + * + * It allows HF to issue the +BIND unsolicited result code to + * activate/deactivate of the AG’s supported HF Indicators. + * The indicator of supported indicators can be activated/deactivated + * are defined in `enum hfp_ag_hf_indicators`. + * `BT_HFP_AG_HF_INDICATOR_ENH_SAFETY` is used to support + * `Enhanced Safety`. Only the configuration has been enabled, the + * `indicator` can be HFP_AG_ENHANCED_SAFETY_IND. + * `BT_HFP_AG_HF_INDICATOR_BATTERY` is used to support + * `Remaining level of Battery`. Only the configuration has been + * enabled, the `indicator` can be HFP_AG_BATTERY_LEVEL_IND. + * If @kconfig{CONFIG_BT_HFP_HF_HF_INDICATORS} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param ag HFP AG object. + * @param indicator The indicator of the AG’s supported HF Indicators. + * @param enable enable/disable specific HF Indicator. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_hf_indicator(struct bt_hfp_ag *ag, enum hfp_ag_hf_indicators indicator, bool enable); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/Kconfig b/subsys/bluetooth/host/classic/Kconfig index c81b5a2c1bb67..6277e6aa90193 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -396,6 +396,23 @@ config BT_HFP_AG_VOICE_TAG help This option enables Attach a phone number for a voice tag for HFP AG +config BT_HFP_AG_HF_INDICATORS + bool "HF Indicators for HFP AG [EXPERIMENTAL]" + help + This option enables HF Indicators for HFP AG + +config BT_HFP_AG_HF_INDICATOR_ENH_SAFETY + bool "HF Indicator Enhanced Safety for HFP AG [EXPERIMENTAL]" + select BT_HFP_AG_HF_INDICATORS + help + This option enables HF Indicator Enhanced Safety for HFP AG + +config BT_HFP_AG_HF_INDICATOR_BATTERY + bool "HF Indicator Battery level for HFP AG [EXPERIMENTAL]" + select BT_HFP_AG_HF_INDICATORS + help + This option enables HF Indicator Battery level for HFP AG + endif # BT_HFP_AG config BT_AVDTP diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index b270236e7f369..c9bcf1c4f49b7 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -1508,6 +1508,7 @@ static int bt_hfp_ag_bind_handler(struct bt_hfp_ag *ag, struct net_buf *buf) { uint32_t indicator; uint32_t hf_indicators = 0U; + uint32_t supported_indicators = 0U; int err; char *data; uint32_t len; @@ -1526,20 +1527,27 @@ static int bt_hfp_ag_bind_handler(struct bt_hfp_ag *ag, struct net_buf *buf) } hfp_ag_lock(ag); - hf_indicators = ag->hf_indicators_of_hf & ag->hf_indicators_of_ag; + hf_indicators = ag->hf_indicators; + supported_indicators = ag->hf_indicators_of_ag & ag->hf_indicators_of_hf; hfp_ag_unlock(ag); - len = (sizeof(hf_indicators) * 8) > HFP_HF_IND_MAX ? HFP_HF_IND_MAX - : (sizeof(hf_indicators) * 8); + len = MIN(NUM_BITS(sizeof(supported_indicators)), HFP_HF_IND_MAX); for (int i = 1; i < len; i++) { - if (BIT(i) & hf_indicators) { - err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BIND:%d,%d\r\n", i, 1); - } else { - err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BIND:%d,%d\r\n", i, 0); + bool enabled; + + if (!(BIT(i) & supported_indicators)) { + continue; } - if (err < 0) { + + enabled = BIT(i) & hf_indicators; + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BIND:%d,%d\r\n", i, + enabled ? 1 : 0); + if (err) { return err; } - if (hf_indicators == 0) { + + supported_indicators &= ~BIT(i); + + if (!supported_indicators) { break; } } @@ -1561,12 +1569,11 @@ static int bt_hfp_ag_bind_handler(struct bt_hfp_ag *ag, struct net_buf *buf) hfp_ag_lock(ag); hf_indicators = ag->hf_indicators_of_ag; hfp_ag_unlock(ag); - len = (sizeof(hf_indicators) * 8) > HFP_HF_IND_MAX ? HFP_HF_IND_MAX - : (sizeof(hf_indicators) * 8); + len = MIN(NUM_BITS(sizeof(hf_indicators)), HFP_HF_IND_MAX); for (int i = 1; (i < len) && (hf_indicators != 0); i++) { if (BIT(i) & hf_indicators) { int length = snprintk( - data, (char *)&ag->buffer[HF_MAX_BUF_LEN - 1] - data - 3, + data, (char *)&ag->buffer[HF_MAX_BUF_LEN - 1] - data - 2, "%d", i); data += length; hf_indicators &= ~BIT(i); @@ -1578,8 +1585,6 @@ static int bt_hfp_ag_bind_handler(struct bt_hfp_ag *ag, struct net_buf *buf) } *data = ')'; data++; - *data = '\r'; - data++; *data = '\0'; data++; @@ -1599,7 +1604,7 @@ static int bt_hfp_ag_bind_handler(struct bt_hfp_ag *ag, struct net_buf *buf) } } - if (indicator < (sizeof(hf_indicators) * 8)) { + if (indicator < NUM_BITS(sizeof(hf_indicators))) { hf_indicators |= BIT(indicator); } } @@ -3089,6 +3094,47 @@ static int bt_hfp_ag_cnum_handler(struct bt_hfp_ag *ag, struct net_buf *buf) return 0; } +static int bt_hfp_ag_biev_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + uint32_t indicator; + uint32_t value; + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + if (get_number(buf, &indicator)) { + return -ENOTSUP; + } + + if (!is_char(buf, ',')) { + return -ENOTSUP; + } + + if (get_number(buf, &value)) { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + hfp_ag_lock(ag); + if (!(ag->hf_indicators_of_ag & BIT(indicator))) { + hfp_ag_unlock(ag); + return -ENOTSUP; + } + hfp_ag_unlock(ag); + +#if defined(CONFIG_BT_HFP_AG_HF_INDICATORS) + if (bt_ag && bt_ag->hf_indicator_value) { + bt_ag->hf_indicator_value(ag, indicator, value); + } +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ + + return 0; +} + static struct bt_hfp_ag_at_cmd_handler cmd_handlers[] = { {"AT+BRSF", bt_hfp_ag_brsf_handler}, {"AT+BAC", bt_hfp_ag_bac_handler}, {"AT+CIND", bt_hfp_ag_cind_handler}, {"AT+CMER", bt_hfp_ag_cmer_handler}, @@ -3103,6 +3149,7 @@ static struct bt_hfp_ag_at_cmd_handler cmd_handlers[] = { {"AT+BTRH", bt_hfp_ag_btrh_handler}, {"AT+CCWA", bt_hfp_ag_ccwa_handler}, {"AT+BVRA", bt_hfp_ag_bvra_handler}, {"AT+BINP", bt_hfp_ag_binp_handler}, {"AT+VTS", bt_hfp_ag_vts_handler}, {"AT+CNUM", bt_hfp_ag_cnum_handler}, + {"AT+BIEV", bt_hfp_ag_biev_handler}, }; static void hfp_ag_connected(struct bt_rfcomm_dlc *dlc) @@ -3492,6 +3539,17 @@ int bt_hfp_ag_connect(struct bt_conn *conn, struct bt_hfp_ag **ag, uint8_t chann /* Set the supported features*/ _ag->ag_features = BT_HFP_AG_SUPPORTED_FEATURES; + /* Support HF indicators */ + if (IS_ENABLED(CONFIG_BT_HFP_AG_HF_INDICATOR_ENH_SAFETY)) { + _ag->hf_indicators_of_ag |= BIT(HFP_HF_ENHANCED_SAFETY_IND); + } + + if (IS_ENABLED(CONFIG_BT_HFP_AG_HF_INDICATOR_BATTERY)) { + _ag->hf_indicators_of_ag |= BIT(HFP_HF_BATTERY_LEVEL_IND); + } + + _ag->hf_indicators = _ag->hf_indicators_of_ag; + /* If supported codec ids cannot be notified, disable codec negotiation. */ if (!(bt_ag && bt_ag->codec)) { _ag->ag_features &= ~BT_HFP_AG_FEATURE_CODEC_NEG; @@ -4730,3 +4788,49 @@ int bt_hfp_ag_service_availability(struct bt_hfp_ag *ag, bool available) return err; } + +int bt_hfp_ag_hf_indicator(struct bt_hfp_ag *ag, enum hfp_ag_hf_indicators indicator, bool enable) +{ +#if defined(CONFIG_BT_HFP_AG_HF_INDICATORS) + int err; + uint32_t supported_indicators; + + LOG_DBG(""); + + if (ag == NULL) { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + supported_indicators = ag->hf_indicators_of_ag & ag->hf_indicators_of_hf; + hfp_ag_unlock(ag); + + if (!(supported_indicators & BIT(indicator))) { + LOG_ERR("Unsupported indicator %d", indicator); + return -ENOTSUP; + } + + hfp_ag_lock(ag); + if (enable) { + ag->hf_indicators |= BIT(indicator); + } else { + ag->hf_indicators &= ~BIT(indicator); + } + hfp_ag_unlock(ag); + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BIND:%d,%d\r\n", + indicator, enable ? 1 : 0); + if (err) { + LOG_ERR("Fail to update registration status of indicator:(%d)", err); + } + + return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ +} diff --git a/subsys/bluetooth/host/classic/hfp_ag_internal.h b/subsys/bluetooth/host/classic/hfp_ag_internal.h index 0a8a12a72f3dd..733ef20a6855c 100644 --- a/subsys/bluetooth/host/classic/hfp_ag_internal.h +++ b/subsys/bluetooth/host/classic/hfp_ag_internal.h @@ -92,6 +92,12 @@ #define BT_HFP_AG_SDP_FEATURE_VOICE_TAG_ENABLE 0 #endif /* CONFIG_BT_HFP_AG_VOICE_TAG */ +#if defined(CONFIG_BT_HFP_AG_HF_INDICATORS) +#define BT_HFP_AG_FEATURE_HF_IND_ENABLE BT_HFP_AG_FEATURE_HF_IND +#else +#define BT_HFP_AG_FEATURE_HF_IND_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ + /* HFP AG Supported features */ #define BT_HFP_AG_SUPPORTED_FEATURES (\ BT_HFP_AG_FEATURE_3WAY_CALL_ENABLE | \ @@ -104,7 +110,8 @@ BT_HFP_AG_FEATURE_VOICE_RECG_ENABLE | \ BT_HFP_AG_FEATURE_ENH_VOICE_RECG_ENABLE | \ BT_HFP_AG_FEATURE_VOICE_RECG_TEXT_ENABLE | \ - BT_HFP_AG_FEATURE_VOICE_TAG_ENABLE) + BT_HFP_AG_FEATURE_VOICE_TAG_ENABLE | \ + BT_HFP_AG_FEATURE_HF_IND_ENABLE) /* HFP AG Supported features in SDP */ #define BT_HFP_AG_SDP_SUPPORTED_FEATURES (\ @@ -221,7 +228,7 @@ struct bt_hfp_ag { /* HF Indicators */ uint32_t hf_indicators_of_ag; uint32_t hf_indicators_of_hf; - uint8_t hf_indicator_value[HFP_HF_IND_MAX]; + uint32_t hf_indicators; /* operator */ uint8_t mode; From 3e264cec097defc79fa10b033fcd1e2fcbaad8a2 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Thu, 29 Aug 2024 12:53:31 +0800 Subject: [PATCH 60/76] Bluetooth: HFP_HF: fix typo Change `HF_SINGNAL_IND` to `HF_SIGNAL_IND`. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 2 +- subsys/bluetooth/host/classic/hfp_hf.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index 4af640ad58755..fcb51f97cbce7 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -854,7 +854,7 @@ enum hfp_hf_ag_indicators { HF_CALL_IND, /* AG call indicator */ HF_CALL_SETUP_IND, /* AG call setup indicator */ HF_CALL_HELD_IND, /* AG call held indicator */ - HF_SINGNAL_IND, /* AG signal indicator */ + HF_SIGNAL_IND, /* AG signal indicator */ HF_ROAM_IND, /* AG roaming indicator */ HF_BATTERY_IND /* AG battery indicator */ }; diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index ecf008d0876ea..9bfb4c97ce388 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -1200,7 +1200,7 @@ void ag_indicator_handle_values(struct at_client *hf_at, uint32_t index, case HF_CALL_HELD_IND: ag_indicator_handle_call_held(hf, value); break; - case HF_SINGNAL_IND: + case HF_SIGNAL_IND: if (bt_hf->signal) { bt_hf->signal(hf, value); } From ab1b9efef70c8f6a205e9ef8952dc79361643fc1 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Fri, 21 Feb 2025 09:49:21 +0800 Subject: [PATCH 61/76] Bluetooth: HFP_AG: fix typo Change `Dailing` to `Dialing`. Change `HFP unit` to `HF`. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_ag.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/zephyr/bluetooth/classic/hfp_ag.h b/include/zephyr/bluetooth/classic/hfp_ag.h index 7745c9ff3e7d6..5dc89110621c6 100644 --- a/include/zephyr/bluetooth/classic/hfp_ag.h +++ b/include/zephyr/bluetooth/classic/hfp_ag.h @@ -118,12 +118,12 @@ struct bt_hfp_ag_cb { /** HF memory dialing request Callback * * If this callback is provided it will be called whenever a - * new call is requested with memory dialing from HFP unit. + * new call is requested with memory dialing from HF. * Get the phone number according to the given AG memory location. * * @param ag HFP AG object. * @param location AG memory location - * @param number Dailing number + * @param number Dialing number * * @return 0 in case of success or negative value in case of error. */ @@ -152,7 +152,7 @@ struct bt_hfp_ag_cb { * * @param ag HFP AG object. * @param call HFP AG call object. - * @param number Dailing number + * @param number Dialing number */ void (*outgoing)(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call, const char *number); @@ -448,7 +448,7 @@ int bt_hfp_ag_disconnect(struct bt_hfp_ag *ag); * Notify HFP Unit of an incoming call. * * @param ag HFP AG object. - * @param number Dailing number. + * @param number Dialing number. * * @return 0 in case of success or negative value in case of error. */ @@ -519,7 +519,7 @@ int bt_hfp_ag_hold(struct bt_hfp_ag_call *call); * Dial a call. * * @param ag HFP AG object. - * @param number Dailing number. + * @param number Dialing number. * * @return 0 in case of success or negative value in case of error. */ From b877c114ebc73c053307f6862c6e508573f564bc Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Mon, 2 Sep 2024 12:03:38 +0800 Subject: [PATCH 62/76] Bluetooth: HFP_HF: Initiate SLC establishment Add function `bt_hfp_hf_connect` to initialize the Service Level Connection establishment procedure. Add function `bt_hfp_hf_disconnect` to release the Service Level Connection. Clear HF object if the RFCOMM is disconnected. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_hf.h | 49 ++++++++++ subsys/bluetooth/host/classic/hfp_hf.c | 110 ++++++++++++++++------ 2 files changed, 130 insertions(+), 29 deletions(-) diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index fcb51f97cbce7..3a6dd4fc8f59f 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -429,6 +429,55 @@ struct bt_hfp_hf_cb { */ int bt_hfp_hf_register(struct bt_hfp_hf_cb *cb); +/** @brief Initiate the service level connection establishment procedure + * + * Initiate the service level connection establishment procedure on the + * ACL connection specified by the parameter `conn` using the specific + * RFCOMM channel discovered by the function `bt_br_discovery_start`. + * + * The parameter `hf` is a output parameter. When the service level + * connection establishment procedure is initiated without any error, + * the HFP HF object is allocated and it will be returned via the parameter + * `hf` if the parameter `hf` is not a NULL pointer. + * + * When service level conenction is established, the registered callback + * `connected` will be triggered to notify the application that the service + * level connection establishment procedure is done. And the HFP HF object + * is valid at this time. It means after the function is called without + * any error, all interfaces provided by HFP HF can only be called after + * the registered callback `connected` is triggered. + * + * @param conn ACL connection object. + * @param hf Created HFP HF object. + * @param channel Peer RFCOMM channel to be connected. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_connect(struct bt_conn *conn, struct bt_hfp_hf **hf, uint8_t channel); + +/** @brief Release the service level connection + * + * Release the service level connection from the peer device. + * + * The function can only be called after the registered callback `connected` + * is triggered. + * + * If the function is called without any error, the HFP HF object is + * invalid at this time. All interfaces provided by HFP HF should not + * be called anymore. + * + * If the service level connection is released, the registered callback + * `disconnected` will be triggered to notify the application that the + * service level connection release procedure is done. And the HFP HF + * object will be freed after the registered callback `disconnected` + * returned. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_disconnect(struct bt_hfp_hf *hf); + /** @brief Handsfree HF enable/disable Calling Line Identification (CLI) Notification * * Enable/disable Calling Line Identification (CLI) Notification. diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 9bfb4c97ce388..cd3f78f79edbd 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -3919,6 +3919,10 @@ static void hfp_hf_disconnected(struct bt_rfcomm_dlc *dlc) if (bt_hf->disconnected) { bt_hf->disconnected(hf); } + + k_work_cancel(&hf->work); + k_work_cancel_delayable(&hf->deferred_work); + hf->acl = NULL; } static void hfp_hf_recv(struct bt_rfcomm_dlc *dlc, struct net_buf *buf) @@ -3945,60 +3949,68 @@ static void bt_hf_work(struct k_work *work) hfp_hf_send_data(hf); } -static int hfp_hf_accept(struct bt_conn *conn, struct bt_rfcomm_server *server, - struct bt_rfcomm_dlc **dlc) +static struct bt_hfp_hf *hfp_hf_create(struct bt_conn *conn) { - int i; + size_t index; static struct bt_rfcomm_dlc_ops ops = { .connected = hfp_hf_connected, .disconnected = hfp_hf_disconnected, .recv = hfp_hf_recv, .sent = hfp_hf_sent, }; + struct bt_hfp_hf *hf; LOG_DBG("conn %p", conn); - for (i = 0; i < ARRAY_SIZE(bt_hfp_hf_pool); i++) { - struct bt_hfp_hf *hf = &bt_hfp_hf_pool[i]; - int j; + index = (size_t)bt_conn_index(conn); + hf = &bt_hfp_hf_pool[index]; + if (hf->acl) { + LOG_ERR("HF connection (%p) is established", conn); + return NULL; + } - if (hf->rfcomm_dlc.session) { - continue; - } + memset(hf, 0, sizeof(*hf)); + + hf->acl = conn; + hf->at.buf = hf->hf_buffer; + hf->at.buf_max_len = HF_MAX_BUF_LEN; - memset(hf, 0, sizeof(*hf)); + hf->rfcomm_dlc.ops = &ops; + hf->rfcomm_dlc.mtu = BT_HFP_MAX_MTU; - hf->acl = conn; - hf->at.buf = hf->hf_buffer; - hf->at.buf_max_len = HF_MAX_BUF_LEN; + /* Set the supported features*/ + hf->hf_features = BT_HFP_HF_SUPPORTED_FEATURES; - hf->rfcomm_dlc.ops = &ops; - hf->rfcomm_dlc.mtu = BT_HFP_MAX_MTU; + /* Set supported codec ids */ + hf->hf_codec_ids = BT_HFP_HF_SUPPORTED_CODEC_IDS; - *dlc = &hf->rfcomm_dlc; + k_fifo_init(&hf->tx_pending); - /* Set the supported features*/ - hf->hf_features = BT_HFP_HF_SUPPORTED_FEATURES; + k_work_init(&hf->work, bt_hf_work); - /* Set supported codec ids */ - hf->hf_codec_ids = BT_HFP_HF_SUPPORTED_CODEC_IDS; + k_work_init_delayable(&hf->deferred_work, bt_hf_deferred_work); - k_fifo_init(&hf->tx_pending); + for (index = 0; index < ARRAY_SIZE(hf->ind_table); index++) { + hf->ind_table[index] = -1; + } - k_work_init(&hf->work, bt_hf_work); + return hf; +} - k_work_init_delayable(&hf->deferred_work, bt_hf_deferred_work); +static int hfp_hf_accept(struct bt_conn *conn, struct bt_rfcomm_server *server, + struct bt_rfcomm_dlc **dlc) +{ + struct bt_hfp_hf *hf; - for (j = 0; j < HF_MAX_AG_INDICATORS; j++) { - hf->ind_table[j] = -1; - } + hf = hfp_hf_create(conn); - return 0; + if (!hf) { + return -ECONNREFUSED; } - LOG_ERR("Unable to establish HF connection (%p)", conn); + *dlc = &hf->rfcomm_dlc; - return -ENOMEM; + return 0; } static void hfp_hf_sco_connected(struct bt_sco_chan *chan) @@ -4086,3 +4098,43 @@ int bt_hfp_hf_register(struct bt_hfp_hf_cb *cb) return 0; } + +int bt_hfp_hf_connect(struct bt_conn *conn, struct bt_hfp_hf **hf, uint8_t channel) +{ + struct bt_hfp_hf *new_hf; + int err; + + if (!conn || !hf || !channel) { + return -EINVAL; + } + + if (!bt_hf) { + return -EFAULT; + } + + new_hf = hfp_hf_create(conn); + if (!new_hf) { + return -ECONNREFUSED; + } + + err = bt_rfcomm_dlc_connect(conn, &new_hf->rfcomm_dlc, channel); + if (err != 0) { + (void)memset(new_hf, 0, sizeof(*new_hf)); + *hf = NULL; + } else { + *hf = new_hf; + } + + return err; +} + +int bt_hfp_hf_disconnect(struct bt_hfp_hf *hf) +{ + LOG_DBG(""); + + if (!hf) { + return -EINVAL; + } + + return bt_rfcomm_dlc_disconnect(&hf->rfcomm_dlc); +} From 6df2fa3d868243a030d343d5d83c2a38ce34ea03 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Wed, 28 Aug 2024 17:07:15 +0800 Subject: [PATCH 63/76] Bluetooth: HFP_AG: Ability to reject call Add a configuration `CONFIG_BT_HFP_AG_REJECT_CALL` for feature `Ability to reject a call`. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/Kconfig | 6 ++++++ subsys/bluetooth/host/classic/hfp_ag.c | 7 +++++++ subsys/bluetooth/host/classic/hfp_ag_internal.h | 9 ++++++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/subsys/bluetooth/host/classic/Kconfig b/subsys/bluetooth/host/classic/Kconfig index 6277e6aa90193..1fbf45f9099c6 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -413,6 +413,12 @@ config BT_HFP_AG_HF_INDICATOR_BATTERY help This option enables HF Indicator Battery level for HFP AG +config BT_HFP_AG_REJECT_CALL + bool "Ability to reject a call for HFP AG [EXPERIMENTAL]" + default y + help + This option enables ability to reject a call for HFP AG + endif # BT_HFP_AG config BT_AVDTP diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index c9bcf1c4f49b7..6259d165bbb81 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -3817,6 +3817,7 @@ int bt_hfp_ag_reject(struct bt_hfp_ag_call *call) int err = 0; struct bt_hfp_ag *ag; bt_hfp_call_state_t call_state; + uint32_t ag_features; LOG_DBG(""); @@ -3837,12 +3838,18 @@ int bt_hfp_ag_reject(struct bt_hfp_ag_call *call) } call_state = call->call_state; + ag_features = ag->ag_features; hfp_ag_unlock(ag); if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { return -EINVAL; } + if (!(ag_features & BT_HFP_AG_FEATURE_REJECT_CALL)) { + LOG_ERR("AG has not ability to reject call"); + return -ENOTSUP; + } + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY)) { if ((call_state == BT_HFP_CALL_ALERTING) || (call_state == BT_HFP_CALL_INCOMING)) { uint8_t call_setup; diff --git a/subsys/bluetooth/host/classic/hfp_ag_internal.h b/subsys/bluetooth/host/classic/hfp_ag_internal.h index 733ef20a6855c..9aadfbae2e112 100644 --- a/subsys/bluetooth/host/classic/hfp_ag_internal.h +++ b/subsys/bluetooth/host/classic/hfp_ag_internal.h @@ -98,6 +98,12 @@ #define BT_HFP_AG_FEATURE_HF_IND_ENABLE 0 #endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ +#if defined(CONFIG_BT_HFP_AG_REJECT_CALL) +#define BT_HFP_AG_FEATURE_REJECT_CALL_ENABLE BT_HFP_AG_FEATURE_REJECT_CALL +#else +#define BT_HFP_AG_FEATURE_REJECT_CALL_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_REJECT_CALL */ + /* HFP AG Supported features */ #define BT_HFP_AG_SUPPORTED_FEATURES (\ BT_HFP_AG_FEATURE_3WAY_CALL_ENABLE | \ @@ -111,7 +117,8 @@ BT_HFP_AG_FEATURE_ENH_VOICE_RECG_ENABLE | \ BT_HFP_AG_FEATURE_VOICE_RECG_TEXT_ENABLE | \ BT_HFP_AG_FEATURE_VOICE_TAG_ENABLE | \ - BT_HFP_AG_FEATURE_HF_IND_ENABLE) + BT_HFP_AG_FEATURE_HF_IND_ENABLE | \ + BT_HFP_AG_FEATURE_REJECT_CALL_ENABLE) /* HFP AG Supported features in SDP */ #define BT_HFP_AG_SDP_SUPPORTED_FEATURES (\ From ea107eb8b8aedee0feaffa7f7deaae57e14893c9 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Fri, 30 Aug 2024 14:59:12 +0800 Subject: [PATCH 64/76] Bluetooth: HFP_HF: Verify ability to reject a call Check the ability of AG to reject a call. If the AG cannot reject the call, return error `-ENOTSUP`. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/hfp_hf.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index cd3f78f79edbd..2ebc6d99e68e8 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -2845,6 +2845,11 @@ int bt_hfp_hf_reject(struct bt_hfp_hf_call *call) return -EINVAL; } + if (!(hf->ag_features & BT_HFP_AG_FEATURE_REJECT_CALL)) { + LOG_ERR("AG has not ability to reject call"); + return -ENOTSUP; + } + if (atomic_get(call->state) == BT_HFP_HF_CALL_STATE_WAITING) { err = hfp_hf_send_cmd(hf, NULL, chup_finish, "AT+CHUP"); if (err < 0) { From c4ec15dd17172f7c4f478baf74cf0de3cd15fba0 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Wed, 28 Aug 2024 18:30:21 +0800 Subject: [PATCH 65/76] Bluetooth: HFP_AG: Support RFCOMM responder Define SDP records for HFP AG. Register HFP AG SDP service records in AG initialization. Register HFP AG RFCOMM server in AG initialization. Register HFP AG SCO server in AG initialization. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/hfp_ag.c | 291 +++++++++++++++++++------ 1 file changed, 221 insertions(+), 70 deletions(-) diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index 6259d165bbb81..d326912b9aaf7 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -17,6 +17,7 @@ #include #include +#include #include "host/hci_core.h" #include "host/conn_internal.h" @@ -79,9 +80,78 @@ static struct bt_ag_tx ag_tx[CONFIG_BT_HFP_AG_TX_BUF_COUNT * 2]; static K_FIFO_DEFINE(ag_tx_free); static K_FIFO_DEFINE(ag_tx_notify); -struct k_thread ag_thread; -static K_KERNEL_STACK_MEMBER(ag_thread_stack, CONFIG_BT_HFP_AG_THREAD_STACK_SIZE); -static k_tid_t ag_thread_id; +/* HFP Gateway SDP record */ +static struct bt_sdp_attribute hfp_ag_attrs[] = { + BT_SDP_NEW_SERVICE, + BT_SDP_LIST( + BT_SDP_ATTR_SVCLASS_ID_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_HANDSFREE_AGW_SVCLASS) + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_GENERIC_AUDIO_SVCLASS) + } + ) + ), + BT_SDP_LIST( + BT_SDP_ATTR_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 12), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) + }, + ) + }, + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 5), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_RFCOMM) + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT8), + BT_SDP_ARRAY_8(BT_RFCOMM_CHAN_HFP_AG) + }, + ) + }, + ) + ), + BT_SDP_LIST( + BT_SDP_ATTR_PROFILE_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_HANDSFREE_SVCLASS) + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(0x0109) + }, + ) + ), + + BT_SDP_LIST( + BT_SDP_ATTR_NETWORK, + BT_SDP_TYPE_SIZE(BT_SDP_UINT8), + BT_SDP_ARRAY_8(IS_ENABLED(CONFIG_BT_HFP_AG_REJECT_CALL)) + ), + /* The values of the “SupportedFeatures” bitmap shall be the same as the + * values of the Bits 0 to 4 of the AT-command AT+BRSF (see Section 5.3). + */ + BT_SDP_SUPPORTED_FEATURES(BT_HFP_AG_SDP_SUPPORTED_FEATURES), +}; + +static struct bt_sdp_record hfp_ag_rec = BT_SDP_RECORD(hfp_ag_attrs); static enum at_cme bt_hfp_ag_get_cme_err(int err) { @@ -3205,6 +3275,8 @@ static void hfp_ag_disconnected(struct bt_rfcomm_dlc *dlc) } } + ag->acl_conn = NULL; + LOG_DBG("AG %p", ag); } @@ -3481,121 +3553,133 @@ static void bt_ag_ringing_work(struct k_work *work) (void)hfp_ag_next_step(call->ag, bt_ag_ringing_work_cb, call); } -int bt_hfp_ag_connect(struct bt_conn *conn, struct bt_hfp_ag **ag, uint8_t channel) -{ - int i; - int err; +static K_KERNEL_STACK_MEMBER(ag_thread_stack, CONFIG_BT_HFP_AG_THREAD_STACK_SIZE); +static struct bt_hfp_ag *hfp_ag_create(struct bt_conn *conn) +{ static struct bt_rfcomm_dlc_ops ops = { .connected = hfp_ag_connected, .disconnected = hfp_ag_disconnected, .recv = hfp_ag_recv, .sent = hfp_ag_sent, }; + static k_tid_t ag_thread_id; + static struct k_thread ag_thread; + size_t index; + struct bt_hfp_ag *ag; - LOG_DBG(""); - - if (ag == NULL) { - return -EINVAL; - } - - *ag = NULL; + LOG_DBG("conn %p", conn); if (ag_thread_id == NULL) { k_fifo_init(&ag_tx_free); k_fifo_init(&ag_tx_notify); - for (i = 0; i < ARRAY_SIZE(ag_tx); i++) { - k_fifo_put(&ag_tx_free, &ag_tx[i]); + for (index = 0; index < ARRAY_SIZE(ag_tx); index++) { + k_fifo_put(&ag_tx_free, &ag_tx[index]); } ag_thread_id = k_thread_create( &ag_thread, ag_thread_stack, K_KERNEL_STACK_SIZEOF(ag_thread_stack), bt_hfp_ag_thread, NULL, NULL, NULL, K_PRIO_COOP(CONFIG_BT_HFP_AG_THREAD_PRIO), 0, K_NO_WAIT); - if (ag_thread_id == NULL) { - return -ENOMEM; - } + __ASSERT(ag_thread_id, "Cannot create thread for AG"); k_thread_name_set(ag_thread_id, "HFP AG"); } - for (i = 0; i < ARRAY_SIZE(bt_hfp_ag_pool); i++) { - struct bt_hfp_ag *_ag = &bt_hfp_ag_pool[i]; + index = (size_t)bt_conn_index(conn); + ag = &bt_hfp_ag_pool[index]; + if (ag->acl_conn) { + LOG_ERR("AG connection (%p) is established", conn); + return NULL; + } - if (_ag->rfcomm_dlc.session) { - continue; - } + (void)memset(ag, 0, sizeof(struct bt_hfp_ag)); - (void)memset(_ag, 0, sizeof(struct bt_hfp_ag)); + sys_slist_init(&ag->tx_pending); - sys_slist_init(&_ag->tx_pending); + k_sem_init(&ag->lock, 1, 1); - k_sem_init(&_ag->lock, 1, 1); + ag->rfcomm_dlc.ops = &ops; + ag->rfcomm_dlc.mtu = BT_HFP_MAX_MTU; - _ag->rfcomm_dlc.ops = &ops; - _ag->rfcomm_dlc.mtu = BT_HFP_MAX_MTU; + /* Set the supported features*/ + ag->ag_features = BT_HFP_AG_SUPPORTED_FEATURES; - /* Set the supported features*/ - _ag->ag_features = BT_HFP_AG_SUPPORTED_FEATURES; + /* Support HF indicators */ + if (IS_ENABLED(CONFIG_BT_HFP_AG_HF_INDICATOR_ENH_SAFETY)) { + ag->hf_indicators_of_ag |= BIT(HFP_HF_ENHANCED_SAFETY_IND); + } - /* Support HF indicators */ - if (IS_ENABLED(CONFIG_BT_HFP_AG_HF_INDICATOR_ENH_SAFETY)) { - _ag->hf_indicators_of_ag |= BIT(HFP_HF_ENHANCED_SAFETY_IND); - } + if (IS_ENABLED(CONFIG_BT_HFP_AG_HF_INDICATOR_BATTERY)) { + ag->hf_indicators_of_ag |= BIT(HFP_HF_BATTERY_LEVEL_IND); + } - if (IS_ENABLED(CONFIG_BT_HFP_AG_HF_INDICATOR_BATTERY)) { - _ag->hf_indicators_of_ag |= BIT(HFP_HF_BATTERY_LEVEL_IND); - } + ag->hf_indicators = ag->hf_indicators_of_ag; - _ag->hf_indicators = _ag->hf_indicators_of_ag; + /* If supported codec ids cannot be notified, disable codec negotiation. */ + if (!(bt_ag && bt_ag->codec)) { + ag->ag_features &= ~BT_HFP_AG_FEATURE_CODEC_NEG; + } - /* If supported codec ids cannot be notified, disable codec negotiation. */ - if (!(bt_ag && bt_ag->codec)) { - _ag->ag_features &= ~BT_HFP_AG_FEATURE_CODEC_NEG; - } + ag->hf_features = 0; + ag->hf_codec_ids = 0; + + ag->acl_conn = conn; + + /* Set AG indicator value */ + ag->indicator_value[BT_HFP_AG_SERVICE_IND] = 0; + ag->indicator_value[BT_HFP_AG_CALL_IND] = 0; + ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND] = 0; + ag->indicator_value[BT_HFP_AG_CALL_HELD_IND] = 0; + ag->indicator_value[BT_HFP_AG_SIGNAL_IND] = 0; + ag->indicator_value[BT_HFP_AG_ROAM_IND] = 0; + ag->indicator_value[BT_HFP_AG_BATTERY_IND] = 0; + + /* Set AG indicator status */ + ag->indicator = BIT(BT_HFP_AG_SERVICE_IND) | BIT(BT_HFP_AG_CALL_IND) | + BIT(BT_HFP_AG_CALL_SETUP_IND) | BIT(BT_HFP_AG_CALL_HELD_IND) | + BIT(BT_HFP_AG_SIGNAL_IND) | BIT(BT_HFP_AG_ROAM_IND) | + BIT(BT_HFP_AG_BATTERY_IND); - _ag->hf_features = 0; - _ag->hf_codec_ids = 0; + /* Set AG operator */ + memcpy(ag->operator, "UNKNOWN", sizeof("UNKNOWN")); - _ag->acl_conn = conn; + /* Set Codec ID*/ + ag->selected_codec_id = BT_HFP_AG_CODEC_CVSD; - /* Set AG indicator value */ - _ag->indicator_value[BT_HFP_AG_SERVICE_IND] = 0; - _ag->indicator_value[BT_HFP_AG_CALL_IND] = 0; - _ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND] = 0; - _ag->indicator_value[BT_HFP_AG_CALL_HELD_IND] = 0; - _ag->indicator_value[BT_HFP_AG_SIGNAL_IND] = 0; - _ag->indicator_value[BT_HFP_AG_ROAM_IND] = 0; - _ag->indicator_value[BT_HFP_AG_BATTERY_IND] = 0; + /* Init delay work */ + k_work_init_delayable(&ag->tx_work, bt_ag_tx_work); - /* Set AG indicator status */ - _ag->indicator = BIT(BT_HFP_AG_SERVICE_IND) | BIT(BT_HFP_AG_CALL_IND) | - BIT(BT_HFP_AG_CALL_SETUP_IND) | BIT(BT_HFP_AG_CALL_HELD_IND) | - BIT(BT_HFP_AG_SIGNAL_IND) | BIT(BT_HFP_AG_ROAM_IND) | - BIT(BT_HFP_AG_BATTERY_IND); + return ag; +} - /* Set AG operator */ - memcpy(_ag->operator, "UNKNOWN", sizeof("UNKNOWN")); +int bt_hfp_ag_connect(struct bt_conn *conn, struct bt_hfp_ag **ag, uint8_t channel) +{ + struct bt_hfp_ag *new_ag; + int err; - /* Set Codec ID*/ - _ag->selected_codec_id = BT_HFP_AG_CODEC_CVSD; + LOG_DBG(""); - /* Init delay work */ - k_work_init_delayable(&_ag->tx_work, bt_ag_tx_work); + if (!conn || !ag || !channel) { + return -EINVAL; + } - *ag = _ag; + if (!bt_ag) { + return -EFAULT; } - if (*ag == NULL) { - return -ENOMEM; + new_ag = hfp_ag_create(conn); + if (!new_ag) { + return -ECONNREFUSED; } - err = bt_rfcomm_dlc_connect(conn, &(*ag)->rfcomm_dlc, channel); + err = bt_rfcomm_dlc_connect(conn, &new_ag->rfcomm_dlc, channel); if (err != 0) { - (void)memset(*ag, 0, sizeof(struct bt_hfp_ag)); + (void)memset(new_ag, 0, sizeof(*new_ag)); *ag = NULL; } else { + *ag = new_ag; bt_hfp_ag_set_state(*ag, BT_HFP_CONNECTING); } @@ -3615,6 +3699,71 @@ int bt_hfp_ag_disconnect(struct bt_hfp_ag *ag) return bt_rfcomm_dlc_disconnect(&ag->rfcomm_dlc); } +static int hfp_ag_accept(struct bt_conn *conn, struct bt_rfcomm_server *server, + struct bt_rfcomm_dlc **dlc) +{ + struct bt_hfp_ag *ag; + + ag = hfp_ag_create(conn); + + if (!ag) { + return -ECONNREFUSED; + } + + *dlc = &ag->rfcomm_dlc; + + return 0; +} + +static int bt_hfp_ag_sco_accept(const struct bt_sco_accept_info *info, + struct bt_sco_chan **chan) +{ + static struct bt_sco_chan_ops ops = { + .connected = hfp_ag_sco_connected, + .disconnected = hfp_ag_sco_disconnected, + }; + size_t index; + struct bt_hfp_ag *ag; + + LOG_DBG("conn %p", info->acl); + + index = (size_t)bt_conn_index(info->acl); + ag = &bt_hfp_ag_pool[index]; + if (ag->acl_conn != info->acl) { + LOG_ERR("ACL %p of AG is unaligned with SCO's %p", ag->acl_conn, info->acl); + return -EINVAL; + } + + if (ag->sco_chan.sco) { + return -ECONNREFUSED; + } + + ag->sco_chan.ops = &ops; + + *chan = &ag->sco_chan; + + return 0; +} + +static void hfp_ag_init(void) +{ + static struct bt_rfcomm_server chan = { + .channel = BT_RFCOMM_CHAN_HFP_AG, + .accept = hfp_ag_accept, + }; + + bt_rfcomm_server_register(&chan); + + static struct bt_sco_server sco_server = { + .sec_level = BT_SECURITY_L0, + .accept = bt_hfp_ag_sco_accept, + }; + + bt_sco_server_register(&sco_server); + + bt_sdp_register_service(&hfp_ag_rec); +} + int bt_hfp_ag_register(struct bt_hfp_ag_cb *cb) { if (!cb) { @@ -3627,6 +3776,8 @@ int bt_hfp_ag_register(struct bt_hfp_ag_cb *cb) bt_ag = cb; + hfp_ag_init(); + return 0; } From 3a877117bce73075d3304f80e565f302413bb967 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Wed, 28 Aug 2024 18:33:55 +0800 Subject: [PATCH 66/76] Bluetooth: HFP_HF: Improve SCO accept Find HF object according to conn object index directly. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/hfp_hf.c | 30 ++++++++++---------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 2ebc6d99e68e8..904232ee3918e 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -4037,35 +4037,27 @@ static void hfp_hf_sco_disconnected(struct bt_sco_chan *chan, uint8_t reason) static int bt_hfp_hf_sco_accept(const struct bt_sco_accept_info *info, struct bt_sco_chan **chan) { - int i; static struct bt_sco_chan_ops ops = { .connected = hfp_hf_sco_connected, .disconnected = hfp_hf_sco_disconnected, }; + size_t index; + struct bt_hfp_hf *hf; LOG_DBG("conn %p", info->acl); - for (i = 0; i < ARRAY_SIZE(bt_hfp_hf_pool); i++) { - struct bt_hfp_hf *hf = &bt_hfp_hf_pool[i]; - - if (NULL == hf->rfcomm_dlc.session) { - continue; - } - - if (info->acl != hf->rfcomm_dlc.session->br_chan.chan.conn) { - continue; - } - - hf->chan.ops = &ops; - - *chan = &hf->chan; - - return 0; + index = (size_t)bt_conn_index(info->acl); + hf = &bt_hfp_hf_pool[index]; + if (hf->acl != info->acl) { + LOG_ERR("ACL %p of HF is unaligned with SCO's %p", hf->acl, info->acl); + return -EINVAL; } - LOG_ERR("Unable to establish HF connection (%p)", info->acl); + hf->chan.ops = &ops; - return -ENOMEM; + *chan = &hf->chan; + + return 0; } static void hfp_hf_init(void) From d7452f0ac771afedbb4314dc96d772fc9897703a Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Wed, 28 Aug 2024 19:17:46 +0800 Subject: [PATCH 67/76] Bluetooth: shell: Support HFP HF and AG Support HFP HF and AG in shell. Signed-off-by: Lyle Zhu --- .../host/classic/shell/CMakeLists.txt | 5 + subsys/bluetooth/host/classic/shell/hfp.c | 1901 +++++++++++++++++ .../mimxrt1170_evk_mimxrt1176_cm7_B.conf | 30 + .../mimxrt1170_evk_mimxrt1176_cm7_B.overlay | 12 + tests/bluetooth/shell/prj_br.conf | 3 + 5 files changed, 1951 insertions(+) create mode 100644 subsys/bluetooth/host/classic/shell/hfp.c create mode 100644 tests/bluetooth/shell/boards/mimxrt1170_evk_mimxrt1176_cm7_B.conf create mode 100644 tests/bluetooth/shell/boards/mimxrt1170_evk_mimxrt1176_cm7_B.overlay diff --git a/subsys/bluetooth/host/classic/shell/CMakeLists.txt b/subsys/bluetooth/host/classic/shell/CMakeLists.txt index dbf42a44cb1ac..2732cb2eb6d94 100644 --- a/subsys/bluetooth/host/classic/shell/CMakeLists.txt +++ b/subsys/bluetooth/host/classic/shell/CMakeLists.txt @@ -5,3 +5,8 @@ zephyr_library_sources(bredr.c) zephyr_library_sources_ifdef(CONFIG_BT_RFCOMM rfcomm.c) zephyr_library_sources_ifdef(CONFIG_BT_A2DP a2dp.c) zephyr_library_sources_ifdef(CONFIG_BT_AVRCP avrcp.c) +if(CONFIG_BT_HFP_HF OR CONFIG_BT_HFP_AG) + zephyr_library_sources( + hfp.c + ) +endif() diff --git a/subsys/bluetooth/host/classic/shell/hfp.c b/subsys/bluetooth/host/classic/shell/hfp.c new file mode 100644 index 0000000000000..1f5f82914cdff --- /dev/null +++ b/subsys/bluetooth/host/classic/shell/hfp.c @@ -0,0 +1,1901 @@ +/* + * Copyright 2024-2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "host/shell/bt.h" +#include "common/bt_shell_private.h" + +#define HELP_NONE "[none]" + +extern struct bt_conn *default_conn; + +#if defined(CONFIG_BT_HFP_HF) + +struct bt_conn *hf_conn; +struct bt_hfp_hf *hfp_hf; +struct bt_conn *hf_sco_conn; +static struct bt_hfp_hf_call *hfp_hf_call[CONFIG_BT_HFP_HF_MAX_CALLS]; + +static void hf_add_a_call(struct bt_hfp_hf_call *call) +{ + for (size_t index = 0; index < ARRAY_SIZE(hfp_hf_call); index++) { + if (!hfp_hf_call[index]) { + hfp_hf_call[index] = call; + return; + } + } +} + +static void hf_remove_a_call(struct bt_hfp_hf_call *call) +{ + for (size_t index = 0; index < ARRAY_SIZE(hfp_hf_call); index++) { + if (call == hfp_hf_call[index]) { + hfp_hf_call[index] = NULL; + return; + } + } +} + +static void hf_connected(struct bt_conn *conn, struct bt_hfp_hf *hf) +{ + hf_conn = conn; + hfp_hf = hf; + bt_shell_print("HF connected"); +} + +static void hf_disconnected(struct bt_hfp_hf *hf) +{ + hf_conn = NULL; + hfp_hf = NULL; + bt_shell_print("HF disconnected"); +} + +static void hf_sco_connected(struct bt_hfp_hf *hf, struct bt_conn *sco_conn) +{ + bt_shell_print("HF SCO connected"); + hf_sco_conn = sco_conn; +} + +static void hf_sco_disconnected(struct bt_conn *sco_conn, uint8_t reason) +{ + bt_shell_print("HF SCO disconnected"); + hf_sco_conn = NULL; +} + +void hf_service(struct bt_hfp_hf *hf, uint32_t value) +{ + bt_shell_print("HF service %d", value); +} + +void hf_outgoing(struct bt_hfp_hf *hf, struct bt_hfp_hf_call *call) +{ + hf_add_a_call(call); + bt_shell_print("HF call %p outgoing", call); +} + +void hf_remote_ringing(struct bt_hfp_hf_call *call) +{ + bt_shell_print("HF remote call %p start ringing", call); +} + +void hf_incoming(struct bt_hfp_hf *hf, struct bt_hfp_hf_call *call) +{ + hf_add_a_call(call); + bt_shell_print("HF call %p incoming", call); +} + +void hf_incoming_held(struct bt_hfp_hf_call *call) +{ + bt_shell_print("HF call %p is held", call); +} + +void hf_accept(struct bt_hfp_hf_call *call) +{ + bt_shell_print("HF call %p accepted", call); +} + +void hf_reject(struct bt_hfp_hf_call *call) +{ + hf_remove_a_call(call); + bt_shell_print("HF call %p rejected", call); +} + +void hf_terminate(struct bt_hfp_hf_call *call) +{ + hf_remove_a_call(call); + bt_shell_print("HF call %p terminated", call); +} + +void hf_held(struct bt_hfp_hf_call *call) +{ + bt_shell_print("hf call %p held", call); +} + +void hf_retrieve(struct bt_hfp_hf_call *call) +{ + bt_shell_print("hf call %p retrieve", call); +} + +void hf_signal(struct bt_hfp_hf *hf, uint32_t value) +{ + bt_shell_print("HF signal %d", value); +} + +void hf_roam(struct bt_hfp_hf *hf, uint32_t value) +{ + bt_shell_print("HF roam %d", value); +} + +void hf_battery(struct bt_hfp_hf *hf, uint32_t value) +{ + bt_shell_print("HF battery %d", value); +} + +void hf_ring_indication(struct bt_hfp_hf_call *call) +{ + bt_shell_print("HF call %p ring", call); +} + +void hf_dialing(struct bt_hfp_hf *hf, int err) +{ + bt_shell_print("HF start dialing call: err %d", err); +} + +#if defined(CONFIG_BT_HFP_HF_CLI) +void hf_clip(struct bt_hfp_hf_call *call, char *number, uint8_t type) +{ + bt_shell_print("HF call %p CLIP %s %d", call, number, type); +} +#endif /* CONFIG_BT_HFP_HF_CLI */ + +#if defined(CONFIG_BT_HFP_HF_VOLUME) +static void hf_vgm(struct bt_hfp_hf *hf, uint8_t gain) +{ + bt_shell_print("HF VGM %d", gain); +} + +static void hf_vgs(struct bt_hfp_hf *hf, uint8_t gain) +{ + bt_shell_print("HF VGS %d", gain); +} +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + +static void hf_inband_ring(struct bt_hfp_hf *hf, bool inband) +{ + bt_shell_print("HF ring: %s", inband ? "in-band" : "no in-hand"); +} + +static void hf_operator(struct bt_hfp_hf *hf, uint8_t mode, uint8_t format, char *operator) +{ + bt_shell_print("HF mode %d, format %d, operator %s", mode, format, operator); +} + +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +static void hf_codec_negotiate(struct bt_hfp_hf *hf, uint8_t id) +{ + bt_shell_print("codec negotiation: %d", id); +} +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +#if defined(CONFIG_BT_HFP_HF_ECNR) +static void hf_ecnr_turn_off(struct bt_hfp_hf *hf, int err) +{ + bt_shell_print("Turn off ECNR: %d", err); +} +#endif /* CONFIG_BT_HFP_HF_ECNR */ + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static void hf_call_waiting(struct bt_hfp_hf_call *call, char *number, uint8_t type) +{ + bt_shell_print("3way call %p waiting. number %s type %d", call, number, type); +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) +void hf_voice_recognition(struct bt_hfp_hf *hf, bool activate) +{ + bt_shell_print("Voice recognition %s", activate ? "activate" : "deactivate"); +} + +#if defined(CONFIG_BT_HFP_HF_ENH_VOICE_RECG) +void hf_vre_state(struct bt_hfp_hf *hf, uint8_t state) +{ + bt_shell_print("Voice recognition engine state %d", state); +} +#endif /* CONFIG_BT_HFP_HF_ENH_VOICE_RECG */ + +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG_TEXT) +void hf_textual_representation(struct bt_hfp_hf *hf, char *id, uint8_t type, uint8_t operation, + char *text) +{ + bt_shell_print("Text id %s, type %d, operation %d, string %s", id, type, operation, text); +} +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG_TEXT */ +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ + +void hf_request_phone_number(struct bt_hfp_hf *hf, const char *number) +{ + if (number) { + bt_shell_print("Requested phone number %s", number); + } else { + bt_shell_print("Failed to request phone number"); + } +} + +void hf_subscriber_number(struct bt_hfp_hf *hf, const char *number, uint8_t type, uint8_t service) +{ + bt_shell_print("Subscriber number %s, type %d, service %d", number, type, service); +} + +static struct bt_hfp_hf_cb hf_cb = { + .connected = hf_connected, + .disconnected = hf_disconnected, + .sco_connected = hf_sco_connected, + .sco_disconnected = hf_sco_disconnected, + .service = hf_service, + .outgoing = hf_outgoing, + .remote_ringing = hf_remote_ringing, + .incoming = hf_incoming, + .incoming_held = hf_incoming_held, + .accept = hf_accept, + .reject = hf_reject, + .terminate = hf_terminate, + .held = hf_held, + .retrieve = hf_retrieve, + .signal = hf_signal, + .roam = hf_roam, + .battery = hf_battery, + .ring_indication = hf_ring_indication, + .dialing = hf_dialing, +#if defined(CONFIG_BT_HFP_HF_CLI) + .clip = hf_clip, +#endif /* CONFIG_BT_HFP_HF_CLI */ +#if defined(CONFIG_BT_HFP_HF_VOLUME) + .vgm = hf_vgm, + .vgs = hf_vgs, +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + .inband_ring = hf_inband_ring, + .operator = hf_operator, +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) + .codec_negotiate = hf_codec_negotiate, +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ +#if defined(CONFIG_BT_HFP_HF_ECNR) + .ecnr_turn_off = hf_ecnr_turn_off, +#endif /* CONFIG_BT_HFP_HF_ECNR */ +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + .call_waiting = hf_call_waiting, +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) + .voice_recognition = hf_voice_recognition, +#if defined(CONFIG_BT_HFP_HF_ENH_VOICE_RECG) + .vre_state = hf_vre_state, +#endif /* CONFIG_BT_HFP_HF_ENH_VOICE_RECG */ +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG_TEXT) + .textual_representation = hf_textual_representation, +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG_TEXT */ +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ + .request_phone_number = hf_request_phone_number, + .subscriber_number = hf_subscriber_number, +}; + +static int cmd_reg_enable(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_register(&hf_cb); + if (err) { + shell_error(sh, "Callback register failed: %d", err); + } + + return err; +} + +static int cmd_connect(const struct shell *sh, size_t argc, char **argv) +{ + int err; + struct bt_hfp_hf *hf; + uint8_t channel; + + channel = atoi(argv[1]); + + err = bt_hfp_hf_connect(default_conn, &hf, channel); + if (err) { + shell_error(sh, "Connect failed: %d", err); + } + + return err; +} + +static int cmd_disconnect(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_disconnect(hfp_hf); + if (err) { + shell_error(sh, "Disconnect failed: %d", err); + } + + return err; +} + +static int cmd_sco_disconnect(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_conn_disconnect(hf_sco_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + if (err) { + shell_error(sh, "Disconnect failed: %d", err); + } + + return err; +} + +#if defined(CONFIG_BT_HFP_HF_CLI) +static int cmd_cli_enable(const struct shell *sh, size_t argc, char **argv) +{ + const char *action = argv[1]; + bool enable; + int err; + + if (strcmp(action, "enable") == 0) { + enable = true; + } else if (strcmp(action, "disable") == 0) { + enable = false; + } else { + shell_error(sh, "Invalid option."); + return -ENOEXEC; + } + + err = bt_hfp_hf_cli(hfp_hf, enable); + if (err) { + shell_error(sh, "Fail to send AT+CLIP=%d (err %d)", enable, err); + return -ENOEXEC; + } + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_CLI */ + +#if defined(CONFIG_BT_HFP_HF_VOLUME) +static int cmd_vgm_enable(const struct shell *sh, size_t argc, char **argv) +{ + uint32_t gain; + int err; + + gain = atoi(argv[1]); + + err = bt_hfp_hf_vgm(hfp_hf, gain); + if (err) { + shell_error(sh, "Fail to send AT+VGM=%d (err %d)", gain, err); + } + + return err; +} + +static int cmd_vgs_enable(const struct shell *sh, size_t argc, char **argv) +{ + uint32_t gain; + int err; + + gain = atoi(argv[1]); + + err = bt_hfp_hf_vgs(hfp_hf, gain); + if (err) { + shell_error(sh, "Fail to send AT+VGS=%d (err %d)", gain, err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + +static int cmd_operator(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_get_operator(hfp_hf); + if (err) { + shell_error(sh, "Failed to read network operator: %d", err); + } + + return err; +} + +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +static int cmd_audio_connect(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_audio_connect(hfp_hf); + if (err) { + shell_error(sh, "Failed to start audio connection procedure: %d", err); + } + + return err; +} + +static int cmd_select_codec(const struct shell *sh, size_t argc, char **argv) +{ + int err; + uint8_t codec_id; + + codec_id = atoi(argv[1]); + + err = bt_hfp_hf_select_codec(hfp_hf, codec_id); + if (err) { + shell_error(sh, "Failed to select codec id: %d", err); + } + + return err; +} + +static int cmd_set_codecs(const struct shell *sh, size_t argc, char **argv) +{ + int err; + uint8_t codec_ids; + + codec_ids = atoi(argv[1]); + + err = bt_hfp_hf_set_codecs(hfp_hf, codec_ids); + if (err) { + shell_error(sh, "Failed to set codecs: %d", err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +static int cmd_accept(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_hf_call))) || !hfp_hf_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_hf_accept(hfp_hf_call[index]); + if (err) { + shell_error(sh, "Failed to accept call: %d", err); + } + + return err; +} + +static int cmd_reject(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_hf_call))) || !hfp_hf_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_hf_reject(hfp_hf_call[index]); + if (err) { + shell_error(sh, "Failed to reject call: %d", err); + } + + return err; +} + +static int cmd_terminate(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_hf_call))) || !hfp_hf_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_hf_terminate(hfp_hf_call[index]); + if (err) { + shell_error(sh, "Failed to terminate call: %d", err); + } + + return err; +} + +static int cmd_hold_incoming(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_hf_call))) || !hfp_hf_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_hf_hold_incoming(hfp_hf_call[index]); + if (err) { + shell_error(sh, "Failed to put incoming call on hold: %d", err); + } + + return err; +} + +static int cmd_query_respond_hold_status(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_query_respond_hold_status(hfp_hf); + if (err) { + shell_error(sh, "Failed to query respond and hold status: %d", err); + } + + return err; +} + +static int cmd_number_call(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_number_call(hfp_hf, argv[1]); + if (err) { + shell_error(sh, "Failed to start phone number call: %d", err); + } + + return err; +} + +static int cmd_memory_dial(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_memory_dial(hfp_hf, argv[1]); + if (err) { + shell_error(sh, "Failed to memory dial call: %d", err); + } + + return err; +} + +static int cmd_redial(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_redial(hfp_hf); + if (err) { + shell_error(sh, "Failed to redial call: %d", err); + } + + return err; +} + +#if defined(CONFIG_BT_HFP_HF_ECNR) +static int cmd_turn_off_ecnr(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_turn_off_ecnr(hfp_hf); + if (err) { + shell_error(sh, "Failed to turn off ecnr: %d", err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_HF_ECNR */ + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int cmd_call_waiting_notify(const struct shell *sh, size_t argc, char **argv) +{ + const char *action = argv[1]; + bool enable; + int err; + + action = argv[1]; + + if (strcmp(action, "enable") == 0) { + enable = true; + } else if (strcmp(action, "disable") == 0) { + enable = false; + } else { + shell_error(sh, "Invalid option."); + return -ENOEXEC; + } + + err = bt_hfp_hf_call_waiting_notify(hfp_hf, enable); + if (err) { + shell_error(sh, "Failed to set call waiting notify: %d", err); + } + + return err; +} + +static int cmd_release_all_held(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_release_all_held(hfp_hf); + if (err) { + shell_error(sh, "Failed to release all held: %d", err); + } + + return err; +} + +static int cmd_set_udub(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_set_udub(hfp_hf); + if (err) { + shell_error(sh, "Failed to reject waiting call: %d", err); + } + + return err; +} + +static int cmd_release_active_accept_other(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_release_active_accept_other(hfp_hf); + if (err) { + shell_error(sh, "Failed to release active calls and accept other call: %d", err); + } + + return err; +} + +static int cmd_hold_active_accept_other(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_hold_active_accept_other(hfp_hf); + if (err) { + shell_error(sh, "Failed to hold all active calls and accept other call: %d", err); + } + + return err; +} + +static int cmd_join_conversation(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_join_conversation(hfp_hf); + if (err) { + shell_error(sh, "Failed to join the conversation: %d", err); + } + + return err; +} + +static int cmd_explicit_call_transfer(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_explicit_call_transfer(hfp_hf); + if (err) { + shell_error(sh, "Failed to explicit call transfer: %d", err); + } + + return err; +} + +static int cmd_release_specified_call(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_hf_call))) || !hfp_hf_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_hf_release_specified_call(hfp_hf_call[index]); + if (err) { + shell_error(sh, "Failed to release specified call: %d", err); + } + + return err; +} + +static int cmd_private_consultation_mode(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_hf_call))) || !hfp_hf_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_hf_private_consultation_mode(hfp_hf_call[index]); + if (err) { + shell_error(sh, "Failed to set private consultation mode: %d", err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) +static int cmd_voice_recognition(const struct shell *sh, size_t argc, char **argv) +{ + const char *action; + bool activate; + int err; + + action = argv[1]; + + if (strcmp(action, "activate") == 0) { + activate = true; + } else if (strcmp(action, "deactivate") == 0) { + activate = false; + } else { + shell_error(sh, "Invalid option."); + return -ENOEXEC; + } + + err = bt_hfp_hf_voice_recognition(hfp_hf, activate); + if (err) { + shell_error(sh, "Failed to set voice recognition: %d", err); + } + + return err; +} + +#if defined(CONFIG_BT_HFP_HF_ENH_VOICE_RECG) +static int cmd_ready_to_accept_audio(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_ready_to_accept_audio(hfp_hf); + if (err) { + shell_error(sh, "Failed to send ready to accept audio notify: %d", err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_HF_ENH_VOICE_RECG */ +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ + +static int cmd_request_phone_number(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_request_phone_number(hfp_hf); + if (err) { + shell_error(sh, "Failed to request phone number: %d", err); + } + + return err; +} + +static int cmd_transmit_dtmf_code(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_hf_call))) || !hfp_hf_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_hf_transmit_dtmf_code(hfp_hf_call[index], argv[2][0]); + if (err) { + shell_error(sh, "Failed to transmit DTMF Code: %d", err); + } + + return err; +} + +static int cmd_query_subscriber(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_query_subscriber(hfp_hf); + if (err) { + shell_error(sh, "Failed to query subscriber: %d", err); + } + + return err; +} + +static int cmd_indicator_status(const struct shell *sh, size_t argc, char **argv) +{ + int err; + uint8_t status[4]; + size_t len; + + len = hex2bin(argv[1], strlen(argv[1]), status, sizeof(status)); + if (len == 0) { + shell_error(sh, "Failed to parse status %s", argv[1]); + return -EINVAL; + } + + err = bt_hfp_hf_indicator_status(hfp_hf, (uint32_t)status[0]); + if (err) { + shell_error(sh, "Failed to set AG indicator activated/deactivated status: %d", err); + } + + return err; +} + +#if defined(CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY) +static int cmd_enhanced_safety(const struct shell *sh, size_t argc, char **argv) +{ + const char *action; + bool enable; + int err; + + action = argv[1]; + + if (strcmp(action, "enable") == 0) { + enable = true; + } else if (strcmp(action, "disable") == 0) { + enable = false; + } else { + shell_error(sh, "Invalid option."); + return -ENOEXEC; + } + + err = bt_hfp_hf_enhanced_safety(hfp_hf, enable); + if (err) { + shell_error(sh, "Failed to transfer enhanced safety status: %d", err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY */ + +#if defined(CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY) +static int cmd_battery(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int level; + + level = atoi(argv[1]); + + err = bt_hfp_hf_battery(hfp_hf, level); + if (err) { + shell_error(sh, "Failed to transfer battery level: %d", err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY */ + +SHELL_STATIC_SUBCMD_SET_CREATE(hf_cmds, + SHELL_CMD_ARG(reg, NULL, HELP_NONE, cmd_reg_enable, 1, 0), + SHELL_CMD_ARG(connect, NULL, "", cmd_connect, 2, 0), + SHELL_CMD_ARG(disconnect, NULL, HELP_NONE, cmd_disconnect, 1, 0), + SHELL_CMD_ARG(sco_disconnect, NULL, HELP_NONE, cmd_sco_disconnect, 1, 0), +#if defined(CONFIG_BT_HFP_HF_CLI) + SHELL_CMD_ARG(cli, NULL, "", cmd_cli_enable, 2, 0), +#endif /* CONFIG_BT_HFP_HF_CLI */ +#if defined(CONFIG_BT_HFP_HF_VOLUME) + SHELL_CMD_ARG(vgm, NULL, "", cmd_vgm_enable, 2, 0), + SHELL_CMD_ARG(vgs, NULL, "", cmd_vgs_enable, 2, 0), +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + SHELL_CMD_ARG(operator, NULL, HELP_NONE, cmd_operator, 1, 0), +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) + SHELL_CMD_ARG(audio_connect, NULL, HELP_NONE, cmd_audio_connect, 1, 0), + SHELL_CMD_ARG(select_codec, NULL, "Codec ID", cmd_select_codec, 2, 0), + SHELL_CMD_ARG(set_codecs, NULL, "Codec ID Map", cmd_set_codecs, 2, 0), +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + SHELL_CMD_ARG(accept, NULL, "", cmd_accept, 2, 0), + SHELL_CMD_ARG(reject, NULL, "", cmd_reject, 2, 0), + SHELL_CMD_ARG(terminate, NULL, "", cmd_terminate, 2, 0), + SHELL_CMD_ARG(hold_incoming, NULL, "", cmd_hold_incoming, 2, 0), + SHELL_CMD_ARG(query_respond_hold_status, NULL, HELP_NONE, cmd_query_respond_hold_status, 1, + 0), + SHELL_CMD_ARG(number_call, NULL, "", cmd_number_call, 2, 0), + SHELL_CMD_ARG(memory_dial, NULL, "", cmd_memory_dial, 2, 0), + SHELL_CMD_ARG(redial, NULL, HELP_NONE, cmd_redial, 1, 0), +#if defined(CONFIG_BT_HFP_HF_ECNR) + SHELL_CMD_ARG(turn_off_ecnr, NULL, HELP_NONE, cmd_turn_off_ecnr, 1, 0), +#endif /* CONFIG_BT_HFP_HF_ECNR */ +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + SHELL_CMD_ARG(call_waiting_notify, NULL, "", cmd_call_waiting_notify, 2, 0), + SHELL_CMD_ARG(release_all_held, NULL, HELP_NONE, cmd_release_all_held, 1, 0), + SHELL_CMD_ARG(set_udub, NULL, HELP_NONE, cmd_set_udub, 1, 0), + SHELL_CMD_ARG(release_active_accept_other, NULL, HELP_NONE, cmd_release_active_accept_other, + 1, 0), + SHELL_CMD_ARG(hold_active_accept_other, NULL, HELP_NONE, cmd_hold_active_accept_other, 1, + 0), + SHELL_CMD_ARG(join_conversation, NULL, HELP_NONE, cmd_join_conversation, 1, 0), + SHELL_CMD_ARG(explicit_call_transfer, NULL, HELP_NONE, cmd_explicit_call_transfer, 1, 0), + SHELL_CMD_ARG(release_specified_call, NULL, "", cmd_release_specified_call, 2, + 0), + SHELL_CMD_ARG(private_consultation_mode, NULL, "", + cmd_private_consultation_mode, 2, 0), +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) + SHELL_CMD_ARG(voice_recognition, NULL, "", cmd_voice_recognition, 2, + 0), +#if defined(CONFIG_BT_HFP_HF_ENH_VOICE_RECG) + SHELL_CMD_ARG(ready_to_accept_audio, NULL, HELP_NONE, cmd_ready_to_accept_audio, 1, 0), +#endif /* CONFIG_BT_HFP_HF_ENH_VOICE_RECG */ +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ + SHELL_CMD_ARG(request_phone_number, NULL, HELP_NONE, cmd_request_phone_number, 1, 0), + SHELL_CMD_ARG(transmit_dtmf_code, NULL, " ", + cmd_transmit_dtmf_code, 3, 0), + SHELL_CMD_ARG(query_subscriber, NULL, HELP_NONE, cmd_query_subscriber, 1, 0), + SHELL_CMD_ARG(indicator_status, NULL, "", + cmd_indicator_status, 2, 0), +#if defined(CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY) + SHELL_CMD_ARG(enhanced_safety, NULL, "", cmd_enhanced_safety, 2, 0), +#endif /* CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY */ +#if defined(CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY) + SHELL_CMD_ARG(battery, NULL, "", cmd_battery, 2, 0), +#endif /* CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY */ + SHELL_SUBCMD_SET_END +); +#endif /* CONFIG_BT_HFP_HF */ + +#if defined(CONFIG_BT_HFP_AG) + +struct bt_hfp_ag *hfp_ag; +struct bt_conn *hfp_ag_sco_conn; +static struct bt_hfp_ag_call *hfp_ag_call[CONFIG_BT_HFP_AG_MAX_CALLS]; + +static void ag_add_a_call(struct bt_hfp_ag_call *call) +{ + for (size_t index = 0; index < ARRAY_SIZE(hfp_ag_call); index++) { + if (!hfp_ag_call[index]) { + hfp_ag_call[index] = call; + return; + } + } +} + +static void ag_remove_a_call(struct bt_hfp_ag_call *call) +{ + for (size_t index = 0; index < ARRAY_SIZE(hfp_ag_call); index++) { + if (call == hfp_ag_call[index]) { + hfp_ag_call[index] = NULL; + return; + } + } +} + +static void ag_connected(struct bt_hfp_ag *ag) +{ + hfp_ag = ag; + bt_shell_print("ag connected"); +} + +static void ag_disconnected(struct bt_hfp_ag *ag) +{ + bt_shell_print("ag disconnected"); +} + +static void ag_sco_connected(struct bt_hfp_ag *ag, struct bt_conn *sco_conn) +{ + bt_shell_print("ag sco connected"); + hfp_ag_sco_conn = sco_conn; +} + +static void ag_sco_disconnected(struct bt_hfp_ag *ag) +{ + bt_shell_print("ag sco disconnected"); + hfp_ag_sco_conn = NULL; +} + +static int ag_memory_dial(struct bt_hfp_ag *ag, const char *location, char **number) +{ + static char *phone = "123456789"; + + if (strcmp(location, "0")) { + return -ENOTSUP; + } + + bt_shell_print("ag memory dial"); + + *number = phone; + + return 0; +} + +static int ag_number_call(struct bt_hfp_ag *ag, const char *number) +{ + static char *phone = "123456789"; + + bt_shell_print("ag number call"); + + if (strcmp(number, phone)) { + return -ENOTSUP; + } + + return 0; +} + +static void ag_outgoing(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call, const char *number) +{ + bt_shell_print("ag outgoing call %p, number %s", call, number); + ag_add_a_call(call); +} + +static void ag_incoming(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call, const char *number) +{ + bt_shell_print("ag incoming call %p, number %s", call, number); + ag_add_a_call(call); +} + +static void ag_incoming_held(struct bt_hfp_ag_call *call) +{ + bt_shell_print("ag incoming call %p is held", call); +} + +static void ag_ringing(struct bt_hfp_ag_call *call, bool in_band) +{ + bt_shell_print("ag call %p start ringing mode %d", call, in_band); +} + +static void ag_accept(struct bt_hfp_ag_call *call) +{ + bt_shell_print("ag call %p accept", call); +} + +static void ag_held(struct bt_hfp_ag_call *call) +{ + bt_shell_print("ag call %p held", call); +} + +static void ag_retrieve(struct bt_hfp_ag_call *call) +{ + bt_shell_print("ag call %p retrieved", call); +} + +static void ag_reject(struct bt_hfp_ag_call *call) +{ + bt_shell_print("ag call %p reject", call); + ag_remove_a_call(call); +} + +static void ag_terminate(struct bt_hfp_ag_call *call) +{ + bt_shell_print("ag call %p terminate", call); + ag_remove_a_call(call); +} + +static void ag_codec(struct bt_hfp_ag *ag, uint32_t ids) +{ + bt_shell_print("ag received codec id bit map %x", ids); +} + +void ag_vgm(struct bt_hfp_ag *ag, uint8_t gain) +{ + bt_shell_print("ag received vgm %d", gain); +} + +void ag_vgs(struct bt_hfp_ag *ag, uint8_t gain) +{ + bt_shell_print("ag received vgs %d", gain); +} + +void ag_codec_negotiate(struct bt_hfp_ag *ag, int err) +{ + bt_shell_print("ag codec negotiation result %d", err); +} + +void ag_audio_connect_req(struct bt_hfp_ag *ag) +{ + bt_shell_print("Receive audio connect request. " + "Input `hfp ag audio_connect` to start audio connect"); +} + +void ag_ecnr_turn_off(struct bt_hfp_ag *ag) +{ + bt_shell_print("encr is disabled"); +} + +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) +void ag_explicit_call_transfer(struct bt_hfp_ag *ag) +{ + bt_shell_print("explicit call transfer"); +} +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG) +void ag_voice_recognition(struct bt_hfp_ag *ag, bool activate) +{ + bt_shell_print("AG Voice recognition %s", activate ? "activate" : "deactivate"); +} + +#if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) +void ag_ready_to_accept_audio(struct bt_hfp_ag *ag) +{ + bt_shell_print("hf is ready to accept audio"); +} +#endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG */ + +#if defined(CONFIG_BT_HFP_AG_VOICE_TAG) +int ag_request_phone_number(struct bt_hfp_ag *ag, char **number) +{ + static bool valid_number; + + if (valid_number && number) { + valid_number = false; + *number = "123456789"; + return 0; + } + + valid_number = true; + return -EINVAL; +} +#endif /* CONFIG_BT_HFP_AG_VOICE_TAG */ + +void ag_transmit_dtmf_code(struct bt_hfp_ag *ag, char code) +{ + bt_shell_print("DTMF code is %c", code); +} + +struct { + char *number; + uint8_t type; + uint8_t service; +} ag_subscriber_number_info[] = { + { + .number = "12345678", + .type = 128, + .service = 4, + }, + { + .number = "87654321", + .type = 128, + .service = 4, + }, +}; + +static bool subscriber; + +int ag_subscriber_number(struct bt_hfp_ag *ag, bt_hfp_ag_query_subscriber_func_t func) +{ + int err; + + if (subscriber && func) { + for (size_t index = 0; index < ARRAY_SIZE(ag_subscriber_number_info); index++) { + err = func(ag, ag_subscriber_number_info[index].number, + ag_subscriber_number_info[index].type, + ag_subscriber_number_info[index].service); + if (err < 0) { + break; + } + } + } + return 0; +} + +void ag_hf_indicator_value(struct bt_hfp_ag *ag, enum hfp_ag_hf_indicators indicator, + uint32_t value) +{ + bt_shell_print("indicator %d value %d", indicator, value); +} + +static struct bt_hfp_ag_cb ag_cb = { + .connected = ag_connected, + .disconnected = ag_disconnected, + .sco_connected = ag_sco_connected, + .sco_disconnected = ag_sco_disconnected, + .memory_dial = ag_memory_dial, + .number_call = ag_number_call, + .outgoing = ag_outgoing, + .incoming = ag_incoming, + .incoming_held = ag_incoming_held, + .ringing = ag_ringing, + .accept = ag_accept, + .held = ag_held, + .retrieve = ag_retrieve, + .reject = ag_reject, + .terminate = ag_terminate, + .codec = ag_codec, + .codec_negotiate = ag_codec_negotiate, + .audio_connect_req = ag_audio_connect_req, + .vgm = ag_vgm, + .vgs = ag_vgs, +#if defined(CONFIG_BT_HFP_AG_ECNR) + .ecnr_turn_off = ag_ecnr_turn_off, +#endif /* CONFIG_BT_HFP_AG_ECNR */ +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + .explicit_call_transfer = ag_explicit_call_transfer, +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG) + .voice_recognition = ag_voice_recognition, +#if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) + .ready_to_accept_audio = ag_ready_to_accept_audio, +#endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG */ +#if defined(CONFIG_BT_HFP_AG_VOICE_TAG) + .request_phone_number = ag_request_phone_number, +#endif /* CONFIG_BT_HFP_AG_VOICE_TAG */ + .transmit_dtmf_code = ag_transmit_dtmf_code, + .subscriber_number = ag_subscriber_number, + .hf_indicator_value = ag_hf_indicator_value, +}; + +static int cmd_ag_reg_enable(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_ag_register(&ag_cb); + if (err) { + shell_error(sh, "Callback register failed: %d", err); + } + + return err; +} + +static int cmd_ag_connect(const struct shell *sh, size_t argc, char **argv) +{ + int err; + struct bt_hfp_ag *ag; + uint8_t channel; + + channel = atoi(argv[1]); + + err = bt_hfp_ag_connect(default_conn, &ag, channel); + if (err) { + shell_error(sh, "Connect failed: %d", err); + } + + return err; +} + +static int cmd_ag_disconnect(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_ag_disconnect(hfp_ag); + if (err) { + shell_error(sh, "Disconnect failed: %d", err); + } + + return err; +} + +static int cmd_ag_sco_disconnect(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_conn_disconnect(hfp_ag_sco_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + if (err) { + shell_error(sh, "Disconnect failed: %d", err); + } + + return err; +} + +static int cmd_ag_remote_incoming(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_ag_remote_incoming(hfp_ag, argv[1]); + if (err) { + shell_error(sh, "Set remote incoming failed: %d", err); + } + + return err; +} + +static int cmd_ag_hold_incoming(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_ag_call))) || !hfp_ag_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_ag_hold_incoming(hfp_ag_call[index]); + if (err) { + shell_error(sh, "Set remote incoming failed: %d", err); + } + + return err; +} + +static int cmd_ag_remote_reject(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_ag_call))) || !hfp_ag_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_ag_remote_reject(hfp_ag_call[index]); + if (err) { + shell_error(sh, "Set remote reject failed: %d", err); + } + + return err; +} + +static int cmd_ag_remote_accept(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_ag_call))) || !hfp_ag_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_ag_remote_accept(hfp_ag_call[index]); + if (err) { + shell_error(sh, "Set remote accept failed: %d", err); + } + + return err; +} + +static int cmd_ag_remote_terminate(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_ag_call))) || !hfp_ag_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_ag_remote_terminate(hfp_ag_call[index]); + if (err) { + shell_error(sh, "Set remote terminate failed: %d", err); + } + + return err; +} + +static int cmd_ag_remote_ringing(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_ag_call))) || !hfp_ag_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_ag_remote_ringing(hfp_ag_call[index]); + if (err) { + shell_error(sh, "Set remote ringing failed: %d", err); + } + + return err; +} + +static int cmd_ag_outgoing(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_ag_outgoing(hfp_ag, argv[1]); + if (err) { + shell_error(sh, "Set outgoing failed: %d", err); + } + + return err; +} + +static int cmd_ag_reject(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_ag_call))) || !hfp_ag_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_ag_reject(hfp_ag_call[index]); + if (err) { + shell_error(sh, "Set reject failed: %d", err); + } + + return err; +} + +static int cmd_ag_accept(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_ag_call))) || !hfp_ag_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_ag_accept(hfp_ag_call[index]); + if (err) { + shell_error(sh, "Set accept failed: %d", err); + } + + return err; +} + +static int cmd_ag_hold(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_ag_call))) || !hfp_ag_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_ag_hold(hfp_ag_call[index]); + if (err) { + shell_error(sh, "Set hold failed: %d", err); + } + + return err; +} + +static int cmd_ag_retrieve(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_ag_call))) || !hfp_ag_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_ag_retrieve(hfp_ag_call[index]); + if (err) { + shell_error(sh, "Set retrieve failed: %d", err); + } + + return err; +} + +static int cmd_ag_terminate(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_ag_call))) || !hfp_ag_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_ag_terminate(hfp_ag_call[index]); + if (err) { + shell_error(sh, "Set terminate failed: %d", err); + } + + return err; +} + +static int cmd_ag_vgm(const struct shell *sh, size_t argc, char **argv) +{ + int err; + uint8_t vgm; + + vgm = atoi(argv[1]); + + err = bt_hfp_ag_vgm(hfp_ag, vgm); + if (err) { + shell_error(sh, "Set microphone gain failed: %d", err); + } + + return err; +} + +static int cmd_ag_vgs(const struct shell *sh, size_t argc, char **argv) +{ + int err; + uint8_t vgs; + + vgs = atoi(argv[1]); + + err = bt_hfp_ag_vgs(hfp_ag, vgs); + if (err) { + shell_error(sh, "Set speaker gain failed: %d", err); + } + + return err; +} + +static int cmd_ag_operator(const struct shell *sh, size_t argc, char **argv) +{ + int err; + uint8_t mode; + + mode = atoi(argv[1]); + + err = bt_hfp_ag_set_operator(hfp_ag, mode, argv[2]); + if (err) { + shell_error(sh, "Set network operator failed: %d", err); + } + + return err; +} + +#if defined(CONFIG_BT_HFP_AG_CODEC_NEG) +static int cmd_ag_audio_connect(const struct shell *sh, size_t argc, char **argv) +{ + int err; + uint8_t id; + + id = atoi(argv[1]); + + err = bt_hfp_ag_audio_connect(hfp_ag, id); + if (err) { + shell_error(sh, "Start audio connection procedure failed: %d", err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_AG_CODEC_NEG */ + +static int cmd_ag_inband_ringtone(const struct shell *sh, size_t argc, char **argv) +{ + const char *action; + bool enable; + int err; + + action = argv[1]; + + if (strcmp(action, "enable") == 0) { + enable = true; + } else if (strcmp(action, "disable") == 0) { + enable = false; + } else { + shell_error(sh, "Invalid option."); + return -ENOEXEC; + } + + err = bt_hfp_ag_inband_ringtone(hfp_ag, (bool)enable); + if (err) { + shell_error(sh, "Set inband ringtone failed: %d", err); + } + + return err; +} + +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) +static int cmd_ag_explicit_call_transfer(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_ag_explicit_call_transfer(hfp_ag); + if (err) { + shell_error(sh, "Explicit call transfer failed: %d", err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG) +static int cmd_ag_voice_recognition(const struct shell *sh, size_t argc, char **argv) +{ + const char *action; + bool enable; + int err; + + action = argv[1]; + + if (strcmp(action, "activate") == 0) { + enable = true; + } else if (strcmp(action, "deactivate") == 0) { + enable = false; + } else { + shell_error(sh, "Invalid option."); + return -ENOEXEC; + } + + err = bt_hfp_ag_voice_recognition(hfp_ag, enable); + if (err) { + shell_error(sh, "Set voice recognition failed: %d", err); + } + + return err; +} + +#if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) +static int cmd_ag_vre_state(const struct shell *sh, size_t argc, char **argv) +{ + const char *action; + uint8_t state = 0; + int err; + + action = argv[1]; + + for (size_t index = 0; index < strlen(action); index++) { + switch (action[index]) { + case 'R': + state |= BIT(0); + break; + case 'S': + state |= BIT(1); + break; + case 'P': + state |= BIT(2); + break; + } + } + + err = bt_hfp_ag_vre_state(hfp_ag, state); + if (err) { + shell_error(sh, "Set voice recognition engine state failed: %d", err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG_TEXT) +static int cmd_ag_vre_text(const struct shell *sh, size_t argc, char **argv) +{ + const char *action; + uint8_t state = 0; + const char *id; + uint8_t type; + uint8_t operation; + const char *text; + int err; + + action = argv[1]; + id = argv[2]; + type = (uint8_t)atoi(argv[3]); + operation = (uint8_t)atoi(argv[4]); + text = argv[5]; + + for (size_t index = 0; index < strlen(action); index++) { + switch (action[index]) { + case 'R': + state |= BIT(0); + break; + case 'S': + state |= BIT(1); + break; + case 'P': + state |= BIT(2); + break; + } + } + + err = bt_hfp_ag_vre_textual_representation(hfp_ag, state, id, type, operation, text); + if (err) { + shell_error(sh, "Set voice recognition engine textual representation failed: %d", + err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG_TEXT */ +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG */ + +static int cmd_ag_subscriber(const struct shell *sh, size_t argc, char **argv) +{ + const char *action; + + action = argv[1]; + + if (strcmp(action, "empty") == 0) { + subscriber = false; + } else if (strcmp(action, "notempty") == 0) { + subscriber = true; + } else { + shell_error(sh, "Invalid option."); + return -ENOEXEC; + } + + return 0; +} + +static int cmd_ag_signal_strength(const struct shell *sh, size_t argc, char **argv) +{ + uint8_t strength; + int err; + + strength = (uint8_t)atoi(argv[1]); + + err = bt_hfp_ag_signal_strength(hfp_ag, strength); + if (err) { + shell_error(sh, "Set signal strength failed: %d", err); + } + + return err; +} + +static int cmd_ag_roaming_status(const struct shell *sh, size_t argc, char **argv) +{ + uint8_t status; + int err; + + status = (uint8_t)atoi(argv[1]); + + err = bt_hfp_ag_roaming_status(hfp_ag, status); + if (err) { + shell_error(sh, "Set roaming status failed: %d", err); + } + + return err; +} + +static int cmd_ag_battery_level(const struct shell *sh, size_t argc, char **argv) +{ + uint8_t level; + int err; + + level = (uint8_t)atoi(argv[1]); + + err = bt_hfp_ag_battery_level(hfp_ag, level); + if (err) { + shell_error(sh, "Set battery level failed: %d", err); + } + + return err; +} + +static int cmd_ag_service_availability(const struct shell *sh, size_t argc, char **argv) +{ + bool available; + const char *action; + int err; + + action = argv[1]; + + if (strcmp(action, "yes") == 0) { + available = true; + } else if (strcmp(action, "no") == 0) { + available = false; + } else { + shell_error(sh, "Invalid option."); + return -ENOEXEC; + } + + err = bt_hfp_ag_service_availability(hfp_ag, available); + if (err) { + shell_error(sh, "Set service availability failed: %d", err); + } + + return err; +} + +#if defined(CONFIG_BT_HFP_AG_HF_INDICATORS) +static int cmd_ag_hf_indicator(const struct shell *sh, size_t argc, char **argv) +{ + bool enable; + const char *action; + int err; + size_t indicator; + + indicator = atoi(argv[1]); + action = argv[2]; + + if (strcmp(action, "enable") == 0) { + enable = true; + } else if (strcmp(action, "disable") == 0) { + enable = false; + } else { + shell_error(sh, "Invalid option."); + return -ENOEXEC; + } + + err = bt_hfp_ag_hf_indicator(hfp_ag, indicator, enable); + if (err) { + shell_error(sh, "Activate/deactivate HF indicator failed: %d", err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ + +#define HELP_AG_TEXTUAL_REPRESENTATION \ + "<[R-ready][S-send][P-processing]> " \ + " " + +SHELL_STATIC_SUBCMD_SET_CREATE(ag_cmds, + SHELL_CMD_ARG(reg, NULL, HELP_NONE, cmd_ag_reg_enable, 1, 0), + SHELL_CMD_ARG(connect, NULL, "", cmd_ag_connect, 2, 0), + SHELL_CMD_ARG(disconnect, NULL, HELP_NONE, cmd_ag_disconnect, 1, 0), + SHELL_CMD_ARG(sco_disconnect, NULL, HELP_NONE, cmd_ag_sco_disconnect, 1, 0), + SHELL_CMD_ARG(remote_incoming, NULL, "", cmd_ag_remote_incoming, 2, 0), + SHELL_CMD_ARG(hold_incoming, NULL, "", cmd_ag_hold_incoming, 2, 0), + SHELL_CMD_ARG(remote_reject, NULL, "", cmd_ag_remote_reject, 2, 0), + SHELL_CMD_ARG(remote_accept, NULL, "", cmd_ag_remote_accept, 2, 0), + SHELL_CMD_ARG(remote_terminate, NULL, "", cmd_ag_remote_terminate, 2, 0), + SHELL_CMD_ARG(remote_ringing, NULL, "", cmd_ag_remote_ringing, 2, 0), + SHELL_CMD_ARG(outgoing, NULL, "", cmd_ag_outgoing, 2, 0), + SHELL_CMD_ARG(reject, NULL, "", cmd_ag_reject, 2, 0), + SHELL_CMD_ARG(accept, NULL, "", cmd_ag_accept, 2, 0), + SHELL_CMD_ARG(hold, NULL, "", cmd_ag_hold, 2, 0), + SHELL_CMD_ARG(retrieve, NULL, "", cmd_ag_retrieve, 2, 0), + SHELL_CMD_ARG(terminate, NULL, "", cmd_ag_terminate, 2, 0), + SHELL_CMD_ARG(vgm, NULL, "", cmd_ag_vgm, 2, 0), + SHELL_CMD_ARG(vgs, NULL, "", cmd_ag_vgs, 2, 0), + SHELL_CMD_ARG(operator, NULL, " ", cmd_ag_operator, 3, 0), +#if defined(CONFIG_BT_HFP_AG_CODEC_NEG) + SHELL_CMD_ARG(audio_connect, NULL, "", cmd_ag_audio_connect, 2, 0), +#endif /* CONFIG_BT_HFP_AG_CODEC_NEG */ + SHELL_CMD_ARG(inband_ringtone, NULL, "", cmd_ag_inband_ringtone, 2, 0), +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + SHELL_CMD_ARG(explicit_call_transfer, NULL, HELP_NONE, cmd_ag_explicit_call_transfer, 1, 0), +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG) + SHELL_CMD_ARG(voice_recognition, NULL, "", cmd_ag_voice_recognition, 2, + 0), +#if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) + SHELL_CMD_ARG(vre_state, NULL, "<[R-ready][S-send][P-processing]>", cmd_ag_vre_state, 2, 0), +#endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG_TEXT) + SHELL_CMD_ARG(vre_text, NULL, HELP_AG_TEXTUAL_REPRESENTATION, cmd_ag_vre_text, 6, 0), +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG_TEXT */ +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG */ + SHELL_CMD_ARG(subscriber, NULL, "", cmd_ag_subscriber, 2, 0), + SHELL_CMD_ARG(signal_strength, NULL, "", cmd_ag_signal_strength, 2, 0), + SHELL_CMD_ARG(roaming_status, NULL, "", cmd_ag_roaming_status, 2, 0), + SHELL_CMD_ARG(battery_level, NULL, "", cmd_ag_battery_level, 2, 0), + SHELL_CMD_ARG(service_availability, NULL, "", cmd_ag_service_availability, 2, 0), +#if defined(CONFIG_BT_HFP_AG_HF_INDICATORS) + SHELL_CMD_ARG(hf_indicator, NULL, " ", cmd_ag_hf_indicator, 3, + 0), +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ + SHELL_SUBCMD_SET_END +); +#endif /* CONFIG_BT_HFP_AG */ + +static int cmd_default(const struct shell *sh, size_t argc, char **argv) +{ + if (argc == 1) { + shell_help(sh); + /* sh returns 1 when help is printed */ + return SHELL_CMD_HELP_PRINTED; + } + + shell_error(sh, "%s unknown parameter: %s", argv[0], argv[1]); + + return -ENOEXEC; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(hfp_cmds, +#if defined(CONFIG_BT_HFP_HF) + SHELL_CMD(hf, &hf_cmds, "HFP HF shell commands", cmd_default), +#endif /* CONFIG_BT_HFP_HF */ +#if defined(CONFIG_BT_HFP_AG) + SHELL_CMD(ag, &ag_cmds, "HFP AG shell commands", cmd_default), +#endif /* CONFIG_BT_HFP_AG */ + SHELL_SUBCMD_SET_END +); + +SHELL_CMD_ARG_REGISTER(hfp, &hfp_cmds, "Bluetooth HFP shell commands", cmd_default, 1, 1); diff --git a/tests/bluetooth/shell/boards/mimxrt1170_evk_mimxrt1176_cm7_B.conf b/tests/bluetooth/shell/boards/mimxrt1170_evk_mimxrt1176_cm7_B.conf new file mode 100644 index 0000000000000..c214409ea689b --- /dev/null +++ b/tests/bluetooth/shell/boards/mimxrt1170_evk_mimxrt1176_cm7_B.conf @@ -0,0 +1,30 @@ +#select NXP NW612 Chipset +CONFIG_BT_NXP_NW612=y +#Enable entropy generator +CONFIG_ENTROPY_GENERATOR=y + +CONFIG_BT_HFP_HF_CODEC_NEG=y +CONFIG_BT_HFP_AG_CODEC_NEG=y + +CONFIG_BT_HFP_HF_ECNR=y +CONFIG_BT_HFP_AG_ECNR=y + +CONFIG_BT_HFP_HF_3WAY_CALL=y +CONFIG_BT_HFP_AG_3WAY_CALL=y + +CONFIG_BT_HFP_HF_VOICE_RECG_TEXT=y +CONFIG_BT_HFP_AG_VOICE_RECG_TEXT=y + +CONFIG_BT_HFP_AG_VOICE_TAG=y + +CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY=y +CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY=y + +CONFIG_BT_HFP_AG_HF_INDICATOR_ENH_SAFETY=y +CONFIG_BT_HFP_AG_HF_INDICATOR_BATTERY=y + +CONFIG_BT_SETTINGS=n +CONFIG_FLASH=n +CONFIG_FLASH_MAP=n +CONFIG_NVS=n +CONFIG_SETTINGS=n diff --git a/tests/bluetooth/shell/boards/mimxrt1170_evk_mimxrt1176_cm7_B.overlay b/tests/bluetooth/shell/boards/mimxrt1170_evk_mimxrt1176_cm7_B.overlay new file mode 100644 index 0000000000000..0198087c363d5 --- /dev/null +++ b/tests/bluetooth/shell/boards/mimxrt1170_evk_mimxrt1176_cm7_B.overlay @@ -0,0 +1,12 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + chosen { + zephyr,sram = &dtcm; + zephyr,shell-uart = &lpuart1; + }; +}; diff --git a/tests/bluetooth/shell/prj_br.conf b/tests/bluetooth/shell/prj_br.conf index 12f51033f45aa..427fc3ec158d7 100644 --- a/tests/bluetooth/shell/prj_br.conf +++ b/tests/bluetooth/shell/prj_br.conf @@ -24,3 +24,6 @@ CONFIG_BT_L2CAP_STREAM=y CONFIG_BT_L2CAP_FCS=y CONFIG_BT_L2CAP_EXT_WIN_SIZE=y CONFIG_BT_L2CAP_MAX_WINDOW_SIZE=5 + +CONFIG_BT_HFP_HF=y +CONFIG_BT_HFP_AG=y From e0f784d1cb5ce17620aed90329abd74525e322fd Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Tue, 13 Aug 2024 10:03:47 +0800 Subject: [PATCH 68/76] Bluetooth: Shell: BR: Find HFP_HF records on HFP_AG side Add a option `HFPHF` to find HFP HF SDP records from HFP AG side. Add a SDP records discovery parameter `discov_hfphf` for this option. Add a function `sdp_hfp_hf_user` to handle the discovery result. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/shell/bredr.c | 63 ++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/subsys/bluetooth/host/classic/shell/bredr.c b/subsys/bluetooth/host/classic/shell/bredr.c index 0c78355c2cb10..4bd635f2bb0b4 100644 --- a/subsys/bluetooth/host/classic/shell/bredr.c +++ b/subsys/bluetooth/host/classic/shell/bredr.c @@ -753,6 +753,58 @@ static uint8_t sdp_hfp_ag_user(struct bt_conn *conn, struct bt_sdp_client_result return BT_SDP_DISCOVER_UUID_CONTINUE; } +static uint8_t sdp_hfp_hf_user(struct bt_conn *conn, + struct bt_sdp_client_result *result, + const struct bt_sdp_discover_params *params) +{ + char addr[BT_ADDR_STR_LEN]; + uint16_t param, version; + uint16_t features; + int err; + + conn_addr_str(conn, addr, sizeof(addr)); + + if (result && result->resp_buf) { + bt_shell_print("SDP HFPHF data@%p (len %u) hint %u from remote %s", + result->resp_buf, result->resp_buf->len, result->next_record_hint, + addr); + + /* + * Focus to get BT_SDP_ATTR_PROTO_DESC_LIST attribute item to + * get HFPHF Server Channel Number operating on RFCOMM protocol. + */ + err = bt_sdp_get_proto_param(result->resp_buf, BT_SDP_PROTO_RFCOMM, ¶m); + if (err < 0) { + bt_shell_error("Error getting Server CN, err %d", err); + goto done; + } + bt_shell_print("HFPHF Server CN param 0x%04x", param); + + err = bt_sdp_get_profile_version(result->resp_buf, BT_SDP_HANDSFREE_SVCLASS, + &version); + if (err < 0) { + bt_shell_error("Error getting profile version, err %d", err); + goto done; + } + bt_shell_print("HFP version param 0x%04x", version); + + /* + * Focus to get BT_SDP_ATTR_SUPPORTED_FEATURES attribute item to + * get profile Supported Features mask. + */ + err = bt_sdp_get_features(result->resp_buf, &features); + if (err < 0) { + bt_shell_error("Error getting HFPHF Features, err %d", err); + goto done; + } + bt_shell_print("HFPHF Supported Features param 0x%04x", features); + } else { + bt_shell_print("No SDP HFPHF data from remote %s", addr); + } +done: + return BT_SDP_DISCOVER_UUID_CONTINUE; +} + static uint8_t sdp_a2src_user(struct bt_conn *conn, struct bt_sdp_client_result *result, const struct bt_sdp_discover_params *params) { @@ -816,6 +868,13 @@ static struct bt_sdp_discover_params discov_hfpag = { .pool = &sdp_client_pool, }; +static struct bt_sdp_discover_params discov_hfphf = { + .type = BT_SDP_DISCOVER_SERVICE_SEARCH_ATTR, + .uuid = BT_UUID_DECLARE_16(BT_SDP_HANDSFREE_SVCLASS), + .func = sdp_hfp_hf_user, + .pool = &sdp_client_pool, +}; + static struct bt_sdp_discover_params discov_a2src = { .type = BT_SDP_DISCOVER_SERVICE_SEARCH_ATTR, .uuid = BT_UUID_DECLARE_16(BT_SDP_AUDIO_SOURCE_SVCLASS), @@ -839,6 +898,8 @@ static int cmd_sdp_find_record(const struct shell *sh, size_t argc, char *argv[] if (!strcmp(action, "HFPAG")) { discov = discov_hfpag; + } else if (!strcmp(action, "HFPHF")) { + discov = discov_hfphf; } else if (!strcmp(action, "A2SRC")) { discov = discov_a2src; } else { @@ -908,7 +969,7 @@ SHELL_STATIC_SUBCMD_SET_CREATE(br_cmds, SHELL_CMD(l2cap, &l2cap_cmds, HELP_NONE, cmd_default_handler), SHELL_CMD_ARG(oob, NULL, NULL, cmd_oob, 1, 0), SHELL_CMD_ARG(pscan, NULL, "", cmd_connectable, 2, 0), - SHELL_CMD_ARG(sdp-find, NULL, "", cmd_sdp_find_record, 2, 0), + SHELL_CMD_ARG(sdp-find, NULL, "", cmd_sdp_find_record, 2, 0), SHELL_SUBCMD_SET_END ); From 6516da0542f408a24a6672a054fd4ff69735968c Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Fri, 30 Aug 2024 11:11:12 +0800 Subject: [PATCH 69/76] Sample: Bluetooth: HFP_AG: Update the sample Due to the interface of AG is updated, update AG sample accordingly. Signed-off-by: Lyle Zhu --- samples/bluetooth/handsfree_ag/src/main.c | 24 +++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/samples/bluetooth/handsfree_ag/src/main.c b/samples/bluetooth/handsfree_ag/src/main.c index a87608c6eaafb..3bf6c2d15ece8 100644 --- a/samples/bluetooth/handsfree_ag/src/main.c +++ b/samples/bluetooth/handsfree_ag/src/main.c @@ -25,8 +25,7 @@ static struct bt_conn *default_conn; struct bt_hfp_ag *hfp_ag; - -static struct bt_br_discovery_param br_discover; +struct bt_hfp_ag_call *hfp_ag_call; static struct bt_br_discovery_param br_discover; static struct bt_br_discovery_result scan_result[CONFIG_BT_HFP_AG_DISCOVER_RESULT_COUNT]; @@ -43,6 +42,9 @@ NET_BUF_POOL_DEFINE(sdp_discover_pool, 10, BT_L2CAP_BUF_SIZE(CONFIG_BT_L2CAP_TX_ static void ag_connected(struct bt_hfp_ag *ag) { + if (!hfp_ag) { + hfp_ag = ag; + } printk("HFP AG connected!\n"); k_work_schedule(&call_connect_work, K_MSEC(CONFIG_BT_HFP_AG_START_CALL_DELAY_TIME)); } @@ -62,38 +64,40 @@ static void ag_sco_disconnected(struct bt_hfp_ag *ag) printk("HFP AG SCO disconnected!\n"); } -static void ag_ringing(struct bt_hfp_ag *ag, bool in_band) +static void ag_ringing(struct bt_hfp_ag_call *call, bool in_band) { printk("Ringing (in bond? %s)\n", in_band ? "Yes" : "No"); } -static void ag_accept(struct bt_hfp_ag *ag) +static void ag_accept(struct bt_hfp_ag_call *call) { printk("Call Accepted\n"); k_work_schedule(&call_disconnect_work, K_SECONDS(10)); } -static void ag_reject(struct bt_hfp_ag *ag) +static void ag_reject(struct bt_hfp_ag_call *call) { printk("Call Rejected\n"); k_work_schedule(&call_disconnect_work, K_SECONDS(1)); } -static void ag_terminate(struct bt_hfp_ag *ag) +static void ag_terminate(struct bt_hfp_ag_call *call) { printk("Call terminated\n"); k_work_schedule(&call_disconnect_work, K_SECONDS(1)); } -static void ag_outgoing(struct bt_hfp_ag *ag, const char *number) +static void ag_outgoing(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call, const char *number) { + hfp_ag_call = call; printk("Call outgoing, remote number %s\n", number); k_work_cancel_delayable(&call_connect_work); k_work_schedule(&call_remote_ringing_work, K_SECONDS(1)); } -static void ag_incoming(struct bt_hfp_ag *ag, const char *number) +static void ag_incoming(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call, const char *number) { + hfp_ag_call = call; printk("Incoming call, remote number %s\n", number); k_work_cancel_delayable(&call_connect_work); } @@ -329,7 +333,7 @@ static void call_remote_ringing_work_handler(struct k_work *work) printk("Remote starts ringing\n"); - err = bt_hfp_ag_remote_ringing(hfp_ag); + err = bt_hfp_ag_remote_ringing(hfp_ag_call); if (err != 0) { printk("Fail to notify hfp unit that the remote starts ringing (err %d)\n", err); @@ -344,7 +348,7 @@ static void call_remote_accept_work_handler(struct k_work *work) printk("Remote accepts the call\n"); - err = bt_hfp_ag_remote_accept(hfp_ag); + err = bt_hfp_ag_remote_accept(hfp_ag_call); if (err != 0) { printk("Fail to notify hfp unit that the remote accepts call (err %d)\n", err); From a6c7d70c9c07a90eabdf447f7b58113aced93ce8 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Fri, 30 Aug 2024 11:27:03 +0800 Subject: [PATCH 70/76] Bluetooth: HFP: Divide hfp_internal.h Divide hfp_internal.h into two parts, HFP HF part hfp_hf_internal.h and HFP common part hfp_internal.h. Includes "hfp_internal.h" in file "hfp_ag_internal.h". Includes "hfp_ag_internal.h" in file "hfp_ag.c". Includes "hfp_internal.h" in file "hfp_hf_internal.h". Includes "hfp_hf_internal.h" in file "hfp_hf.c". Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/hfp_ag.c | 2 - .../bluetooth/host/classic/hfp_ag_internal.h | 11 +- subsys/bluetooth/host/classic/hfp_hf.c | 2 +- .../bluetooth/host/classic/hfp_hf_internal.h | 223 ++++++++++++++++++ subsys/bluetooth/host/classic/hfp_internal.h | 222 +---------------- 5 files changed, 235 insertions(+), 225 deletions(-) create mode 100644 subsys/bluetooth/host/classic/hfp_hf_internal.h diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index d326912b9aaf7..c1db4126950ef 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -25,8 +25,6 @@ #include "rfcomm_internal.h" #include "at.h" #include "sco_internal.h" - -#include "hfp_internal.h" #include "hfp_ag_internal.h" #define LOG_LEVEL CONFIG_BT_HFP_AG_LOG_LEVEL diff --git a/subsys/bluetooth/host/classic/hfp_ag_internal.h b/subsys/bluetooth/host/classic/hfp_ag_internal.h index 9aadfbae2e112..fb17595422b7a 100644 --- a/subsys/bluetooth/host/classic/hfp_ag_internal.h +++ b/subsys/bluetooth/host/classic/hfp_ag_internal.h @@ -9,16 +9,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -/* HFP AG Features in SDP */ -#define BT_HFP_AG_SDP_FEATURE_3WAY_CALL BIT(0) /* Three-way calling */ -#define BT_HFP_AG_SDP_FEATURE_ECNR BIT(1) /* EC and/or NR function */ -#define BT_HFP_AG_SDP_FEATURE_VOICE_RECG BIT(2) /* Voice recognition */ -#define BT_HFP_AG_SDP_FEATURE_INBAND_RINGTONE BIT(3) /* In-band ring tone capability */ -#define BT_HFP_AG_SDP_FEATURE_VOICE_TAG BIT(4) /* Attach no. to voice tag */ -#define BT_HFP_AG_SDP_FEATURE_WBS BIT(5) /* Wide Band Speech */ -#define BT_HFP_AG_SDP_FEATURE_ENH_VOICE_RECG BIT(6) /* Enhanced Voice Recognition Status */ -#define BT_HFP_AG_SDP_FEATURE_VOICE_RECG_TEXT BIT(7) /* Voice Recognition Text */ -#define BT_HFP_AG_SDP_FEATURE_SUPER_WBS BIT(8) /* Super Wide Band Speech */ +#include "hfp_internal.h" #if defined(CONFIG_BT_HFP_AG_EXT_ERR) #define BT_HFP_AG_FEATURE_EXT_ERR_ENABLE BT_HFP_AG_FEATURE_EXT_ERR diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 904232ee3918e..ceeabf0ead97a 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -26,7 +26,7 @@ #include "rfcomm_internal.h" #include "at.h" #include "sco_internal.h" -#include "hfp_internal.h" +#include "hfp_hf_internal.h" #define LOG_LEVEL CONFIG_BT_HFP_HF_LOG_LEVEL #include diff --git a/subsys/bluetooth/host/classic/hfp_hf_internal.h b/subsys/bluetooth/host/classic/hfp_hf_internal.h new file mode 100644 index 0000000000000..caecc60dac9fa --- /dev/null +++ b/subsys/bluetooth/host/classic/hfp_hf_internal.h @@ -0,0 +1,223 @@ +/** @file + * @brief Internal APIs for Bluetooth Handsfree profile handling. + */ + +/* + * Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "hfp_internal.h" + +#if defined(CONFIG_BT_HFP_HF_CLI) +#define BT_HFP_HF_FEATURE_CLI_ENABLE BT_HFP_HF_FEATURE_CLI +#define BT_HFP_HF_SDP_FEATURE_CLI_ENABLE BT_HFP_HF_SDP_FEATURE_CLI +#else +#define BT_HFP_HF_FEATURE_CLI_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_CLI_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_CLI */ + +#if defined(CONFIG_BT_HFP_HF_VOLUME) +#define BT_HFP_HF_FEATURE_VOLUME_ENABLE BT_HFP_HF_FEATURE_VOLUME +#define BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE BT_HFP_HF_SDP_FEATURE_VOLUME +#else +#define BT_HFP_HF_FEATURE_VOLUME_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +#define BT_HFP_HF_CODEC_NEG_ENABLE BT_HFP_HF_FEATURE_CODEC_NEG +#else +#define BT_HFP_HF_CODEC_NEG_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +#if defined(CONFIG_BT_HFP_HF_ECNR) +#define BT_HFP_HF_FEATURE_ECNR_ENABLE BT_HFP_HF_FEATURE_ECNR +#define BT_HFP_HF_SDP_FEATURE_ECNR_ENABLE BT_HFP_HF_SDP_FEATURE_ECNR +#else +#define BT_HFP_HF_FEATURE_ECNR_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_ECNR_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +#define BT_HFP_HF_FEATURE_3WAY_CALL_ENABLE BT_HFP_HF_FEATURE_3WAY_CALL +#define BT_HFP_HF_SDP_FEATURE_3WAY_CALL_ENABLE BT_HFP_HF_SDP_FEATURE_3WAY_CALL +#else +#define BT_HFP_HF_FEATURE_3WAY_CALL_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_3WAY_CALL_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +#if defined(CONFIG_BT_HFP_HF_ECS) +#define BT_HFP_HF_FEATURE_ECS_ENABLE BT_HFP_HF_FEATURE_ECS +#else +#define BT_HFP_HF_FEATURE_ECS_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_ECS */ + +#if defined(CONFIG_BT_HFP_HF_ECC) +#define BT_HFP_HF_FEATURE_ECC_ENABLE BT_HFP_HF_FEATURE_ECC +#else +#define BT_HFP_HF_FEATURE_ECC_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_ECC */ + +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) +#define BT_HFP_HF_FEATURE_VOICE_RECG_ENABLE BT_HFP_HF_FEATURE_VOICE_RECG +#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_ENABLE BT_HFP_HF_SDP_FEATURE_VOICE_RECG +#else +#define BT_HFP_HF_FEATURE_VOICE_RECG_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ + +#if defined(CONFIG_BT_HFP_HF_ENH_VOICE_RECG) +#define BT_HFP_HF_FEATURE_ENH_VOICE_RECG_ENABLE BT_HFP_HF_FEATURE_ENH_VOICE_RECG +#define BT_HFP_HF_SDP_FEATURE_ENH_VOICE_RECG_ENABLE BT_HFP_HF_SDP_FEATURE_ENH_VOICE_RECG +#else +#define BT_HFP_HF_FEATURE_ENH_VOICE_RECG_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_ENH_VOICE_RECG_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_ENH_VOICE_RECG */ + +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG_TEXT) +#define BT_HFP_HF_FEATURE_VOICE_RECG_TEXT_ENABLE BT_HFP_HF_FEATURE_VOICE_RECG_TEXT +#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT +#else +#define BT_HFP_HF_FEATURE_VOICE_RECG_TEXT_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG_TEXT */ + +#if defined(CONFIG_BT_HFP_HF_HF_INDICATORS) +#define BT_HFP_HF_FEATURE_HF_IND_ENABLE BT_HFP_HF_FEATURE_HF_IND +#else +#define BT_HFP_HF_FEATURE_HF_IND_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ + +/* HFP HF Supported features */ +#define BT_HFP_HF_SUPPORTED_FEATURES (\ + BT_HFP_HF_FEATURE_CLI_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE |\ + BT_HFP_HF_CODEC_NEG_ENABLE | \ + BT_HFP_HF_FEATURE_ECNR_ENABLE | \ + BT_HFP_HF_FEATURE_3WAY_CALL_ENABLE | \ + BT_HFP_HF_FEATURE_ECS_ENABLE | \ + BT_HFP_HF_FEATURE_ECC_ENABLE | \ + BT_HFP_HF_FEATURE_VOICE_RECG_ENABLE | \ + BT_HFP_HF_FEATURE_ENH_VOICE_RECG_ENABLE | \ + BT_HFP_HF_FEATURE_VOICE_RECG_TEXT_ENABLE | \ + BT_HFP_HF_FEATURE_HF_IND_ENABLE) + +/* HFP HF Supported features in SDP */ +#define BT_HFP_HF_SDP_SUPPORTED_FEATURES (\ + BT_HFP_HF_SDP_FEATURE_CLI_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_ECNR_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_3WAY_CALL_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_VOICE_RECG_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_ENH_VOICE_RECG_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE) + +#define BT_HFP_HF_CODEC_CVSD_MASK BIT(BT_HFP_HF_CODEC_CVSD) + +#if defined(CONFIG_BT_HFP_HF_CODEC_MSBC) +#define BT_HFP_HF_CODEC_MSBC_ENABLE BIT(BT_HFP_HF_CODEC_MSBC) +#else +#define BT_HFP_HF_CODEC_MSBC_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_CODEC_MSBC */ + +#if defined(CONFIG_BT_HFP_HF_CODEC_LC3_SWB) +#define BT_HFP_HF_CODEC_LC3_SWB_ENABLE BIT(BT_HFP_HF_CODEC_LC3_SWB) +#else +#define BT_HFP_HF_CODEC_LC3_SWB_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_CODEC_LC3_SWB */ + +/* HFP HF Supported Codec IDs*/ +#define BT_HFP_HF_SUPPORTED_CODEC_IDS \ + BT_HFP_HF_CODEC_CVSD_MASK | \ + BT_HFP_HF_CODEC_MSBC_ENABLE | \ + BT_HFP_HF_CODEC_LC3_SWB_ENABLE + +/* bt_hfp_hf flags: the flags defined here represent hfp hf parameters */ +enum { + BT_HFP_HF_FLAG_CONNECTED, /* HFP HF SLC Established */ + BT_HFP_HF_FLAG_TX_ONGOING, /* HFP HF TX is ongoing */ + BT_HFP_HF_FLAG_RX_ONGOING, /* HFP HF RX is ongoing */ + BT_HFP_HF_FLAG_CODEC_CONN, /* HFP HF codec connection setup */ + BT_HFP_HF_FLAG_CLCC_PENDING, /* HFP HF CLCC is pending */ + BT_HFP_HF_FLAG_VRE_ACTIVATE, /* VRE is activated */ + BT_HFP_HF_FLAG_BINP, /* +BINP result code is received */ + /* Total number of flags - must be at the end of the enum */ + BT_HFP_HF_NUM_FLAGS, +}; + +/* bt_hfp_hf_call flags: the flags defined here represent hfp hf call parameters */ +enum { + BT_HFP_HF_CALL_IN_USING, /* Object is in using */ + BT_HFP_HF_CALL_CLCC, /* CLCC report received */ + BT_HFP_HF_CALL_INCOMING, /* Incoming call */ + BT_HFP_HF_CALL_INCOMING_HELD, /* Incoming call held */ + BT_HFP_HF_CALL_OUTGOING_3WAY, /* Outgoing 3 way call */ + BT_HFP_HF_CALL_INCOMING_3WAY, /* Incoming 3 way call */ + + /* Total number of flags - must be at the end of the enum */ + BT_HFP_HF_CALL_NUM_FLAGS, +}; + +/* bt_hfp_hf_call state: the flags defined here represent hfp hf call state parameters */ +enum { + /* Call state flags */ + BT_HFP_HF_CALL_STATE_TERMINATE, /* Call terminate */ + BT_HFP_HF_CALL_STATE_OUTGOING, /* Call outgoing */ + BT_HFP_HF_CALL_STATE_INCOMING, /* Call incoming */ + BT_HFP_HF_CALL_STATE_ALERTING, /* Call alerting */ + BT_HFP_HF_CALL_STATE_WAITING, /* Call waiting */ + BT_HFP_HF_CALL_STATE_ACTIVE, /* Call active */ + BT_HFP_HF_CALL_STATE_HELD, /* Call held */ + + /* Total number of flags - must be at the end of the enum */ + BT_HFP_HF_CALL_STATE_NUM_FLAGS, +}; + +struct bt_hfp_hf_call { + struct bt_hfp_hf *hf; + uint8_t index; + + ATOMIC_DEFINE(flags, BT_HFP_HF_CALL_NUM_FLAGS); + ATOMIC_DEFINE(state, BT_HFP_HF_CALL_STATE_NUM_FLAGS); +}; + +struct bt_hfp_hf { + struct bt_rfcomm_dlc rfcomm_dlc; + /* ACL connection handle */ + struct bt_conn *acl; + /* AT command sending queue */ + at_finish_cb_t backup_finish; + struct k_fifo tx_pending; + /* SCO Channel */ + struct bt_sco_chan chan; + char hf_buffer[HF_MAX_BUF_LEN]; + struct at_client at; + uint32_t hf_features; + uint32_t ag_features; + uint8_t hf_codec_ids; + uint8_t vgm; + uint8_t vgs; + int8_t ind_table[HF_MAX_AG_INDICATORS]; + + uint32_t hf_ind; + uint32_t ag_ind; + uint32_t ind_enable; + + /* AT command initialization indicator */ + uint8_t cmd_init_seq; + + /* The features supported by AT+CHLD */ + uint32_t chld_features; + + /* Worker for pending TX */ + struct k_work work; + + struct k_work_delayable deferred_work; + + /* calls */ + struct bt_hfp_hf_call calls[CONFIG_BT_HFP_HF_MAX_CALLS]; + + ATOMIC_DEFINE(flags, BT_HFP_HF_NUM_FLAGS); +}; diff --git a/subsys/bluetooth/host/classic/hfp_internal.h b/subsys/bluetooth/host/classic/hfp_internal.h index 12f89c09d1d6e..03a85e1a1230f 100644 --- a/subsys/bluetooth/host/classic/hfp_internal.h +++ b/subsys/bluetooth/host/classic/hfp_internal.h @@ -52,130 +52,16 @@ #define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT BIT(7) /* Voice Recognition Text */ #define BT_HFP_HF_SDP_FEATURE_SUPER_WBS BIT(7) /* Super Wide Band Speech */ -#if defined(CONFIG_BT_HFP_HF_CLI) -#define BT_HFP_HF_FEATURE_CLI_ENABLE BT_HFP_HF_FEATURE_CLI -#define BT_HFP_HF_SDP_FEATURE_CLI_ENABLE BT_HFP_HF_SDP_FEATURE_CLI -#else -#define BT_HFP_HF_FEATURE_CLI_ENABLE 0 -#define BT_HFP_HF_SDP_FEATURE_CLI_ENABLE 0 -#endif /* CONFIG_BT_HFP_HF_CLI */ - -#if defined(CONFIG_BT_HFP_HF_VOLUME) -#define BT_HFP_HF_FEATURE_VOLUME_ENABLE BT_HFP_HF_FEATURE_VOLUME -#define BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE BT_HFP_HF_SDP_FEATURE_VOLUME -#else -#define BT_HFP_HF_FEATURE_VOLUME_ENABLE 0 -#define BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE 0 -#endif /* CONFIG_BT_HFP_HF_VOLUME */ - -#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) -#define BT_HFP_HF_CODEC_NEG_ENABLE BT_HFP_HF_FEATURE_CODEC_NEG -#else -#define BT_HFP_HF_CODEC_NEG_ENABLE 0 -#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ - -#if defined(CONFIG_BT_HFP_HF_ECNR) -#define BT_HFP_HF_FEATURE_ECNR_ENABLE BT_HFP_HF_FEATURE_ECNR -#define BT_HFP_HF_SDP_FEATURE_ECNR_ENABLE BT_HFP_HF_SDP_FEATURE_ECNR -#else -#define BT_HFP_HF_FEATURE_ECNR_ENABLE 0 -#define BT_HFP_HF_SDP_FEATURE_ECNR_ENABLE 0 -#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ - -#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) -#define BT_HFP_HF_FEATURE_3WAY_CALL_ENABLE BT_HFP_HF_FEATURE_3WAY_CALL -#define BT_HFP_HF_SDP_FEATURE_3WAY_CALL_ENABLE BT_HFP_HF_SDP_FEATURE_3WAY_CALL -#else -#define BT_HFP_HF_FEATURE_3WAY_CALL_ENABLE 0 -#define BT_HFP_HF_SDP_FEATURE_3WAY_CALL_ENABLE 0 -#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ - -#if defined(CONFIG_BT_HFP_HF_ECS) -#define BT_HFP_HF_FEATURE_ECS_ENABLE BT_HFP_HF_FEATURE_ECS -#else -#define BT_HFP_HF_FEATURE_ECS_ENABLE 0 -#endif /* CONFIG_BT_HFP_HF_ECS */ - -#if defined(CONFIG_BT_HFP_HF_ECC) -#define BT_HFP_HF_FEATURE_ECC_ENABLE BT_HFP_HF_FEATURE_ECC -#else -#define BT_HFP_HF_FEATURE_ECC_ENABLE 0 -#endif /* CONFIG_BT_HFP_HF_ECC */ - -#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) -#define BT_HFP_HF_FEATURE_VOICE_RECG_ENABLE BT_HFP_HF_FEATURE_VOICE_RECG -#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_ENABLE BT_HFP_HF_SDP_FEATURE_VOICE_RECG -#else -#define BT_HFP_HF_FEATURE_VOICE_RECG_ENABLE 0 -#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_ENABLE 0 -#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ - -#if defined(CONFIG_BT_HFP_HF_ENH_VOICE_RECG) -#define BT_HFP_HF_FEATURE_ENH_VOICE_RECG_ENABLE BT_HFP_HF_FEATURE_ENH_VOICE_RECG -#define BT_HFP_HF_SDP_FEATURE_ENH_VOICE_RECG_ENABLE BT_HFP_HF_SDP_FEATURE_ENH_VOICE_RECG -#else -#define BT_HFP_HF_FEATURE_ENH_VOICE_RECG_ENABLE 0 -#define BT_HFP_HF_SDP_FEATURE_ENH_VOICE_RECG_ENABLE 0 -#endif /* CONFIG_BT_HFP_HF_ENH_VOICE_RECG */ - -#if defined(CONFIG_BT_HFP_HF_VOICE_RECG_TEXT) -#define BT_HFP_HF_FEATURE_VOICE_RECG_TEXT_ENABLE BT_HFP_HF_FEATURE_VOICE_RECG_TEXT -#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT -#else -#define BT_HFP_HF_FEATURE_VOICE_RECG_TEXT_ENABLE 0 -#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE 0 -#endif /* CONFIG_BT_HFP_HF_VOICE_RECG_TEXT */ - -#if defined(CONFIG_BT_HFP_HF_HF_INDICATORS) -#define BT_HFP_HF_FEATURE_HF_IND_ENABLE BT_HFP_HF_FEATURE_HF_IND -#else -#define BT_HFP_HF_FEATURE_HF_IND_ENABLE 0 -#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ - -/* HFP HF Supported features */ -#define BT_HFP_HF_SUPPORTED_FEATURES (\ - BT_HFP_HF_FEATURE_CLI_ENABLE | \ - BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE |\ - BT_HFP_HF_CODEC_NEG_ENABLE | \ - BT_HFP_HF_FEATURE_ECNR_ENABLE | \ - BT_HFP_HF_FEATURE_3WAY_CALL_ENABLE | \ - BT_HFP_HF_FEATURE_ECS_ENABLE | \ - BT_HFP_HF_FEATURE_ECC_ENABLE | \ - BT_HFP_HF_FEATURE_VOICE_RECG_ENABLE | \ - BT_HFP_HF_FEATURE_ENH_VOICE_RECG_ENABLE | \ - BT_HFP_HF_FEATURE_VOICE_RECG_TEXT_ENABLE | \ - BT_HFP_HF_FEATURE_HF_IND_ENABLE) - -/* HFP HF Supported features in SDP */ -#define BT_HFP_HF_SDP_SUPPORTED_FEATURES (\ - BT_HFP_HF_SDP_FEATURE_CLI_ENABLE | \ - BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE | \ - BT_HFP_HF_SDP_FEATURE_ECNR_ENABLE | \ - BT_HFP_HF_SDP_FEATURE_3WAY_CALL_ENABLE | \ - BT_HFP_HF_SDP_FEATURE_VOICE_RECG_ENABLE | \ - BT_HFP_HF_SDP_FEATURE_ENH_VOICE_RECG_ENABLE | \ - BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE) - -#define BT_HFP_HF_CODEC_CVSD_MASK BIT(BT_HFP_HF_CODEC_CVSD) - -#if defined(CONFIG_BT_HFP_HF_CODEC_MSBC) -#define BT_HFP_HF_CODEC_MSBC_ENABLE BIT(BT_HFP_HF_CODEC_MSBC) -#else -#define BT_HFP_HF_CODEC_MSBC_ENABLE 0 -#endif /* CONFIG_BT_HFP_HF_CODEC_MSBC */ - -#if defined(CONFIG_BT_HFP_HF_CODEC_LC3_SWB) -#define BT_HFP_HF_CODEC_LC3_SWB_ENABLE BIT(BT_HFP_HF_CODEC_LC3_SWB) -#else -#define BT_HFP_HF_CODEC_LC3_SWB_ENABLE 0 -#endif /* CONFIG_BT_HFP_HF_CODEC_LC3_SWB */ - -/* HFP HF Supported Codec IDs*/ -#define BT_HFP_HF_SUPPORTED_CODEC_IDS \ - BT_HFP_HF_CODEC_CVSD_MASK | \ - BT_HFP_HF_CODEC_MSBC_ENABLE | \ - BT_HFP_HF_CODEC_LC3_SWB_ENABLE - +/* HFP AG Features in SDP */ +#define BT_HFP_AG_SDP_FEATURE_3WAY_CALL BIT(0) /* Three-way calling */ +#define BT_HFP_AG_SDP_FEATURE_ECNR BIT(1) /* EC and/or NR function */ +#define BT_HFP_AG_SDP_FEATURE_VOICE_RECG BIT(2) /* Voice recognition */ +#define BT_HFP_AG_SDP_FEATURE_INBAND_RINGTONE BIT(3) /* In-band ring tone capability */ +#define BT_HFP_AG_SDP_FEATURE_VOICE_TAG BIT(4) /* Attach no. to voice tag */ +#define BT_HFP_AG_SDP_FEATURE_WBS BIT(5) /* Wide Band Speech */ +#define BT_HFP_AG_SDP_FEATURE_ENH_VOICE_RECG BIT(6) /* Enhanced Voice Recognition Status */ +#define BT_HFP_AG_SDP_FEATURE_VOICE_RECG_TEXT BIT(7) /* Voice Recognition Text */ +#define BT_HFP_AG_SDP_FEATURE_SUPER_WBS BIT(8) /* Super Wide Band Speech */ #define HF_MAX_BUF_LEN BT_HF_CLIENT_MAX_PDU #define HF_MAX_AG_INDICATORS 20 @@ -185,94 +71,6 @@ #define BT_HFP_HF_VGS_GAIN_MAX 15 #define BT_HFP_HF_VGS_GAIN_MIN 0 -/* bt_hfp_hf flags: the flags defined here represent hfp hf parameters */ -enum { - BT_HFP_HF_FLAG_CONNECTED, /* HFP HF SLC Established */ - BT_HFP_HF_FLAG_TX_ONGOING, /* HFP HF TX is ongoing */ - BT_HFP_HF_FLAG_RX_ONGOING, /* HFP HF RX is ongoing */ - BT_HFP_HF_FLAG_CODEC_CONN, /* HFP HF codec connection setup */ - BT_HFP_HF_FLAG_CLCC_PENDING, /* HFP HF CLCC is pending */ - BT_HFP_HF_FLAG_VRE_ACTIVATE, /* VRE is activated */ - BT_HFP_HF_FLAG_BINP, /* +BINP result code is received */ - /* Total number of flags - must be at the end of the enum */ - BT_HFP_HF_NUM_FLAGS, -}; - -/* bt_hfp_hf_call flags: the flags defined here represent hfp hf call parameters */ -enum { - BT_HFP_HF_CALL_IN_USING, /* Object is in using */ - BT_HFP_HF_CALL_CLCC, /* CLCC report received */ - BT_HFP_HF_CALL_INCOMING, /* Incoming call */ - BT_HFP_HF_CALL_INCOMING_HELD, /* Incoming call held */ - BT_HFP_HF_CALL_OUTGOING_3WAY, /* Outgoing 3 way call */ - BT_HFP_HF_CALL_INCOMING_3WAY, /* Incoming 3 way call */ - - /* Total number of flags - must be at the end of the enum */ - BT_HFP_HF_CALL_NUM_FLAGS, -}; - -/* bt_hfp_hf_call state: the flags defined here represent hfp hf call state parameters */ -enum { - /* Call state flags */ - BT_HFP_HF_CALL_STATE_TERMINATE, /* Call terminate */ - BT_HFP_HF_CALL_STATE_OUTGOING, /* Call outgoing */ - BT_HFP_HF_CALL_STATE_INCOMING, /* Call incoming */ - BT_HFP_HF_CALL_STATE_ALERTING, /* Call alerting */ - BT_HFP_HF_CALL_STATE_WAITING, /* Call waiting */ - BT_HFP_HF_CALL_STATE_ACTIVE, /* Call active */ - BT_HFP_HF_CALL_STATE_HELD, /* Call held */ - - /* Total number of flags - must be at the end of the enum */ - BT_HFP_HF_CALL_STATE_NUM_FLAGS, -}; - -struct bt_hfp_hf_call { - struct bt_hfp_hf *hf; - uint8_t index; - - ATOMIC_DEFINE(flags, BT_HFP_HF_CALL_NUM_FLAGS); - ATOMIC_DEFINE(state, BT_HFP_HF_CALL_STATE_NUM_FLAGS); -}; - -struct bt_hfp_hf { - struct bt_rfcomm_dlc rfcomm_dlc; - /* ACL connection handle */ - struct bt_conn *acl; - /* AT command sending queue */ - at_finish_cb_t backup_finish; - struct k_fifo tx_pending; - /* SCO Channel */ - struct bt_sco_chan chan; - char hf_buffer[HF_MAX_BUF_LEN]; - struct at_client at; - uint32_t hf_features; - uint32_t ag_features; - uint8_t hf_codec_ids; - uint8_t vgm; - uint8_t vgs; - int8_t ind_table[HF_MAX_AG_INDICATORS]; - - uint32_t hf_ind; - uint32_t ag_ind; - uint32_t ind_enable; - - /* AT command initialization indicator */ - uint8_t cmd_init_seq; - - /* The features supported by AT+CHLD */ - uint32_t chld_features; - - /* Worker for pending TX */ - struct k_work work; - - struct k_work_delayable deferred_work; - - /* calls */ - struct bt_hfp_hf_call calls[CONFIG_BT_HFP_HF_MAX_CALLS]; - - ATOMIC_DEFINE(flags, BT_HFP_HF_NUM_FLAGS); -}; - /* HFP call setup status */ #define BT_HFP_CALL_SETUP_NONE 0 #define BT_HFP_CALL_SETUP_INCOMING 1 From ff1c8d81f8f030e2080e9cb4fb0000aec150b67b Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Fri, 30 Aug 2024 13:32:54 +0800 Subject: [PATCH 71/76] Sample: Bluetooth: HFP_HF: Update the sample Due to the interface of HF is updated, update HF sample accordingly. Signed-off-by: Lyle Zhu --- samples/bluetooth/handsfree/src/main.c | 84 ++++++++++++++++++-------- 1 file changed, 60 insertions(+), 24 deletions(-) diff --git a/samples/bluetooth/handsfree/src/main.c b/samples/bluetooth/handsfree/src/main.c index cbad46596a69b..140eed4a350da 100644 --- a/samples/bluetooth/handsfree/src/main.c +++ b/samples/bluetooth/handsfree/src/main.c @@ -19,67 +19,103 @@ #include #include -static void connected(struct bt_conn *conn) +static void hf_connected(struct bt_conn *conn, struct bt_hfp_hf *hf) { printk("HFP HF Connected!\n"); } -static void disconnected(struct bt_conn *conn) +static void hf_disconnected(struct bt_hfp_hf *hf) { printk("HFP HF Disconnected!\n"); } -static void service(struct bt_conn *conn, uint32_t value) +static void hf_sco_connected(struct bt_hfp_hf *hf, struct bt_conn *sco_conn) +{ + printk("HF SCO connected\n"); +} + +static void hf_sco_disconnected(struct bt_conn *sco_conn, uint8_t reason) +{ + printk("HF SCO disconnected\n"); +} + +static void hf_service(struct bt_hfp_hf *hf, uint32_t value) { printk("Service indicator value: %u\n", value); } -static void call(struct bt_conn *conn, uint32_t value) +static void hf_outgoing(struct bt_hfp_hf *hf, struct bt_hfp_hf_call *call) +{ + printk("HF call %p outgoing\n", call); +} + +static void hf_remote_ringing(struct bt_hfp_hf_call *call) +{ + printk("HF remote call %p start ringing\n", call); +} + +static void hf_incoming(struct bt_hfp_hf *hf, struct bt_hfp_hf_call *call) +{ + printk("HF call %p incoming\n", call); +} + +static void hf_incoming_held(struct bt_hfp_hf_call *call) +{ + printk("HF call %p is held\n", call); +} + +static void hf_accept(struct bt_hfp_hf_call *call) { - printk("Call indicator value: %u\n", value); + printk("HF call %p accepted\n", call); } -static void call_setup(struct bt_conn *conn, uint32_t value) +static void hf_reject(struct bt_hfp_hf_call *call) { - printk("Call Setup indicator value: %u\n", value); + printk("HF call %p rejected\n", call); } -static void call_held(struct bt_conn *conn, uint32_t value) +static void hf_terminate(struct bt_hfp_hf_call *call) { - printk("Call Held indicator value: %u\n", value); + printk("HF call %p terminated\n", call); } -static void signal(struct bt_conn *conn, uint32_t value) +static void hf_signal(struct bt_hfp_hf *hf, uint32_t value) { printk("Signal indicator value: %u\n", value); } -static void roam(struct bt_conn *conn, uint32_t value) +static void hf_roam(struct bt_hfp_hf *hf, uint32_t value) { printk("Roaming indicator value: %u\n", value); } -static void battery(struct bt_conn *conn, uint32_t value) +static void hf_battery(struct bt_hfp_hf *hf, uint32_t value) { printk("Battery indicator value: %u\n", value); } -static void ring_cb(struct bt_conn *conn) +static void hf_ring_indication(struct bt_hfp_hf_call *call) { - printk("Incoming Call...\n"); + printk("HF call %p ring\n", call); } static struct bt_hfp_hf_cb hf_cb = { - .connected = connected, - .disconnected = disconnected, - .service = service, - .call = call, - .call_setup = call_setup, - .call_held = call_held, - .signal = signal, - .roam = roam, - .battery = battery, - .ring_indication = ring_cb, + .connected = hf_connected, + .disconnected = hf_disconnected, + .sco_connected = hf_sco_connected, + .sco_disconnected = hf_sco_disconnected, + .service = hf_service, + .outgoing = hf_outgoing, + .remote_ringing = hf_remote_ringing, + .incoming = hf_incoming, + .incoming_held = hf_incoming_held, + .accept = hf_accept, + .reject = hf_reject, + .terminate = hf_terminate, + .signal = hf_signal, + .roam = hf_roam, + .battery = hf_battery, + .ring_indication = hf_ring_indication, }; static void bt_ready(int err) From 60d9db1dc4f166f718cdf65cec0aed6db991b6e9 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Tue, 12 Nov 2024 16:07:38 +0800 Subject: [PATCH 72/76] Bluetooth: HFP_AG: Change "-EOPNOTSUPP" to "-ENOEXEC" There is an error reported in platform `native_sim` that the value of `EOPNOTSUPP` is same as `ENOTSUP`. It cause the building error `duplicate case value '-95'` reported. Use `ENOEXEC` to replace `EOPNOTSUPP` to fix the building issue. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/hfp_ag.c | 34 +++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index c1db4126950ef..c139536d2b6d8 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -156,7 +156,7 @@ static enum at_cme bt_hfp_ag_get_cme_err(int err) enum at_cme cme_err; switch (err) { - case -EOPNOTSUPP: + case -ENOEXEC: cme_err = CME_ERROR_OPERATION_NOT_SUPPORTED; break; case -EFAULT: @@ -961,7 +961,7 @@ static int bt_hfp_ag_bac_handler(struct bt_hfp_ag *ag, struct net_buf *buf) if (!(ag->hf_features & BT_HFP_HF_FEATURE_CODEC_NEG) || !(ag->ag_features & BT_HFP_AG_FEATURE_CODEC_NEG)) { hfp_ag_unlock(ag); - return -EOPNOTSUPP; + return -ENOEXEC; } hfp_ag_unlock(ag); @@ -983,7 +983,7 @@ static int bt_hfp_ag_bac_handler(struct bt_hfp_ag *ag, struct net_buf *buf) } if (!(codec_ids & BIT(BT_HFP_AG_CODEC_CVSD))) { - return -EOPNOTSUPP; + return -ENOEXEC; } hfp_ag_lock(ag); @@ -1380,7 +1380,7 @@ static int chld_drop_conversation(struct bt_hfp_ag *ag) } if (!bt_ag || !bt_ag->explicit_call_transfer) { - return -EOPNOTSUPP; + return -ENOEXEC; } bt_ag->explicit_call_transfer(ag); @@ -1495,13 +1495,13 @@ static int chld_other(struct bt_hfp_ag *ag, uint32_t value) command = value / 10; if (!index) { - return -EOPNOTSUPP; + return -ENOEXEC; } index = index - 1; if (index >= ARRAY_SIZE(ag->calls)) { - return -EOPNOTSUPP; + return -ENOEXEC; } switch (command) { @@ -1511,7 +1511,7 @@ static int chld_other(struct bt_hfp_ag *ag, uint32_t value) return chld_held_other_calls(ag, index); } - return -EOPNOTSUPP; + return -ENOEXEC; } #endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ @@ -1526,7 +1526,7 @@ static int bt_hfp_ag_chld_handler(struct bt_hfp_ag *ag, struct net_buf *buf) if (!((ag->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL) && (ag->hf_features & BT_HFP_HF_FEATURE_3WAY_CALL))) { hfp_ag_unlock(ag); - return -EOPNOTSUPP; + return -ENOEXEC; } hfp_ag_unlock(ag); @@ -1568,7 +1568,7 @@ static int bt_hfp_ag_chld_handler(struct bt_hfp_ag *ag, struct net_buf *buf) return chld_other(ag, value); #else - return -EOPNOTSUPP; + return -ENOEXEC; #endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ } @@ -1585,7 +1585,7 @@ static int bt_hfp_ag_bind_handler(struct bt_hfp_ag *ag, struct net_buf *buf) if (!((ag->ag_features & BT_HFP_AG_FEATURE_HF_IND) && (ag->hf_features & BT_HFP_HF_FEATURE_HF_IND))) { hfp_ag_unlock(ag); - return -EOPNOTSUPP; + return -ENOEXEC; } hfp_ag_unlock(ag); @@ -2550,7 +2550,7 @@ static int bt_hfp_ag_outgoing_call(struct bt_hfp_ag *ag, const char *number, uin #if defined(CONFIG_BT_HFP_AG_3WAY_CALL) if (!((ag->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL) && (ag->hf_features & BT_HFP_HF_FEATURE_3WAY_CALL))) { - return -EOPNOTSUPP; + return -ENOEXEC; } if (!get_active_held_calls(ag)) { @@ -2908,7 +2908,7 @@ static int bt_hfp_ag_ccwa_handler(struct bt_hfp_ag *ag, struct net_buf *buf) if (!((ag->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL) && (ag->hf_features & BT_HFP_HF_FEATURE_3WAY_CALL))) { hfp_ag_unlock(ag); - return -EOPNOTSUPP; + return -ENOEXEC; } hfp_ag_unlock(ag); @@ -2990,7 +2990,7 @@ static int bt_hfp_ag_bvra_handler(struct bt_hfp_ag *ag, struct net_buf *buf) if (!((ag->ag_features & BT_HFP_AG_FEATURE_VOICE_RECG) && (ag->hf_features & BT_HFP_HF_FEATURE_VOICE_RECG))) { hfp_ag_unlock(ag); - return -EOPNOTSUPP; + return -ENOEXEC; } hfp_ag_unlock(ag); @@ -3029,7 +3029,7 @@ static int bt_hfp_ag_bvra_handler(struct bt_hfp_ag *ag, struct net_buf *buf) (ag->hf_features & BT_HFP_HF_FEATURE_ENH_VOICE_RECG))) { hfp_ag_unlock(ag); LOG_WRN("Enhance voice recognition is not supported"); - return -EOPNOTSUPP; + return -ENOEXEC; } hfp_ag_unlock(ag); if (!atomic_test_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) { @@ -3056,7 +3056,7 @@ static int bt_hfp_ag_binp_handler(struct bt_hfp_ag *ag, struct net_buf *buf) hfp_ag_lock(ag); if (!(ag->ag_features & BT_HFP_AG_FEATURE_VOICE_TAG)) { hfp_ag_unlock(ag); - return -EOPNOTSUPP; + return -ENOEXEC; } hfp_ag_unlock(ag); @@ -3284,7 +3284,7 @@ static void hfp_ag_recv(struct bt_rfcomm_dlc *dlc, struct net_buf *buf) uint8_t *data = buf->data; uint16_t len = buf->len; enum at_cme cme_err; - int err = -EOPNOTSUPP; + int err = -ENOEXEC; LOG_HEXDUMP_DBG(data, len, "Received:"); @@ -3868,7 +3868,7 @@ int bt_hfp_ag_remote_incoming(struct bt_hfp_ag *ag, const char *number) if (!((ag->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL) && (ag->hf_features & BT_HFP_HF_FEATURE_3WAY_CALL))) { LOG_WRN("3 Way call feature is not supported on both sides"); - return -EOPNOTSUPP; + return -ENOEXEC; } if (!atomic_test_bit(ag->flags, BT_HFP_AG_CCWA_ENABLE)) { From 59eb98195422bf7d94993ab70a65ea7f62d37adb Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Wed, 13 Nov 2024 18:55:35 +0800 Subject: [PATCH 73/76] Bluetooth: HFP_AG: Remove unnecessary lock for feature read access The lock/unlock for AG/HF feature read access is unnecessary. For AG feature, it only be changed when creating new AG object. For HF feature, it only be changed when exchanging feature in SLC establishment. Remove the hfp_ag_lock/hfp_ag_unlock for AG/HF feature read access. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/hfp_ag.c | 54 ++------------------------ 1 file changed, 3 insertions(+), 51 deletions(-) diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index c139536d2b6d8..6d09143f2b31e 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -275,14 +275,12 @@ static struct bt_hfp_ag_call *get_new_call(struct bt_hfp_ag *ag, const char *num call = &ag->calls[index]; if (!atomic_test_and_set_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { - hfp_ag_lock(ag); /* Copy number to ag->number including null-character */ strcpy(call->number, number); call->type = type; call->ag = ag; k_work_init_delayable(&call->deferred_work, bt_ag_deferred_work); k_work_init_delayable(&call->ringing_work, bt_ag_ringing_work); - hfp_ag_unlock(ag); return call; } } @@ -940,9 +938,7 @@ static int bt_hfp_ag_brsf_handler(struct bt_hfp_ag *ag, struct net_buf *buf) return -ENOTSUP; } - hfp_ag_lock(ag); ag->hf_features = hf_features; - hfp_ag_unlock(ag); return hfp_ag_send_data(ag, NULL, NULL, "\r\n+BRSF:%d\r\n", ag->ag_features); } @@ -957,13 +953,10 @@ static int bt_hfp_ag_bac_handler(struct bt_hfp_ag *ag, struct net_buf *buf) return -ENOTSUP; } - hfp_ag_lock(ag); if (!(ag->hf_features & BT_HFP_HF_FEATURE_CODEC_NEG) || !(ag->ag_features & BT_HFP_AG_FEATURE_CODEC_NEG)) { - hfp_ag_unlock(ag); return -ENOEXEC; } - hfp_ag_unlock(ag); while (buf->len > 0) { err = get_number(buf, &codec); @@ -1064,9 +1057,7 @@ static void bt_hfp_ag_set_in_band_ring(struct bt_hfp_ag *ag, void *user_data) { bool is_inband_ringtone; - hfp_ag_lock(ag); is_inband_ringtone = (ag->ag_features & BT_HFP_AG_FEATURE_INBAND_RINGTONE) ? true : false; - hfp_ag_unlock(ag); if (is_inband_ringtone && !atomic_test_bit(ag->flags, BT_HFP_AG_INBAND_RING)) { int err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BSIR:1\r\n"); @@ -1522,13 +1513,10 @@ static int bt_hfp_ag_chld_handler(struct bt_hfp_ag *ag, struct net_buf *buf) int err; char *response; - hfp_ag_lock(ag); if (!((ag->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL) && (ag->hf_features & BT_HFP_HF_FEATURE_3WAY_CALL))) { - hfp_ag_unlock(ag); return -ENOEXEC; } - hfp_ag_unlock(ag); if (!is_char(buf, '=')) { return -ENOTSUP; @@ -1581,23 +1569,18 @@ static int bt_hfp_ag_bind_handler(struct bt_hfp_ag *ag, struct net_buf *buf) char *data; uint32_t len; - hfp_ag_lock(ag); if (!((ag->ag_features & BT_HFP_AG_FEATURE_HF_IND) && (ag->hf_features & BT_HFP_HF_FEATURE_HF_IND))) { - hfp_ag_unlock(ag); return -ENOEXEC; } - hfp_ag_unlock(ag); if (is_char(buf, '?')) { if (!is_char(buf, '\r')) { return -ENOTSUP; } - hfp_ag_lock(ag); hf_indicators = ag->hf_indicators; supported_indicators = ag->hf_indicators_of_ag & ag->hf_indicators_of_hf; - hfp_ag_unlock(ag); len = MIN(NUM_BITS(sizeof(supported_indicators)), HFP_HF_IND_MAX); for (int i = 1; i < len; i++) { bool enabled; @@ -1634,9 +1617,7 @@ static int bt_hfp_ag_bind_handler(struct bt_hfp_ag *ag, struct net_buf *buf) data = &ag->buffer[0]; *data = '('; data++; - hfp_ag_lock(ag); hf_indicators = ag->hf_indicators_of_ag; - hfp_ag_unlock(ag); len = MIN(NUM_BITS(sizeof(hf_indicators)), HFP_HF_IND_MAX); for (int i = 1; (i < len) && (hf_indicators != 0); i++) { if (BIT(i) & hf_indicators) { @@ -1677,9 +1658,7 @@ static int bt_hfp_ag_bind_handler(struct bt_hfp_ag *ag, struct net_buf *buf) } } - hfp_ag_lock(ag); ag->hf_indicators_of_hf = hf_indicators; - hfp_ag_unlock(ag); return 0; } @@ -2904,13 +2883,10 @@ static int bt_hfp_ag_ccwa_handler(struct bt_hfp_ag *ag, struct net_buf *buf) int err; uint32_t value; - hfp_ag_lock(ag); if (!((ag->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL) && (ag->hf_features & BT_HFP_HF_FEATURE_3WAY_CALL))) { - hfp_ag_unlock(ag); return -ENOEXEC; } - hfp_ag_unlock(ag); if (!is_char(buf, '=')) { return -ENOTSUP; @@ -2937,10 +2913,8 @@ static void bt_hfp_ag_vr_activate(struct bt_hfp_ag *ag, void *user_data) { bool feature; - hfp_ag_lock(ag); feature = (ag->ag_features & BT_HFP_AG_FEATURE_ENH_VOICE_RECG) && (ag->hf_features & BT_HFP_HF_FEATURE_ENH_VOICE_RECG); - hfp_ag_unlock(ag); #if defined(CONFIG_BT_HFP_AG_VOICE_RECG) if (bt_ag && bt_ag->voice_recognition) { @@ -2986,13 +2960,10 @@ static int bt_hfp_ag_bvra_handler(struct bt_hfp_ag *ag, struct net_buf *buf) int err; uint32_t value; - hfp_ag_lock(ag); if (!((ag->ag_features & BT_HFP_AG_FEATURE_VOICE_RECG) && (ag->hf_features & BT_HFP_HF_FEATURE_VOICE_RECG))) { - hfp_ag_unlock(ag); return -ENOEXEC; } - hfp_ag_unlock(ag); if (!is_char(buf, '=')) { return -ENOTSUP; @@ -3024,14 +2995,11 @@ static int bt_hfp_ag_bvra_handler(struct bt_hfp_ag *ag, struct net_buf *buf) err = hfp_ag_next_step(ag, bt_hfp_ag_vr_activate, NULL); break; case BT_HFP_BVRA_READY_TO_ACCEPT: - hfp_ag_lock(ag); if (!((ag->ag_features & BT_HFP_AG_FEATURE_ENH_VOICE_RECG) && (ag->hf_features & BT_HFP_HF_FEATURE_ENH_VOICE_RECG))) { - hfp_ag_unlock(ag); LOG_WRN("Enhance voice recognition is not supported"); return -ENOEXEC; } - hfp_ag_unlock(ag); if (!atomic_test_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) { LOG_WRN("Voice recognition is not activated"); return -ENOTSUP; @@ -3053,12 +3021,9 @@ static int bt_hfp_ag_binp_handler(struct bt_hfp_ag *ag, struct net_buf *buf) char *number = NULL; #endif /* CONFIG_BT_HFP_AG_VOICE_TAG */ - hfp_ag_lock(ag); if (!(ag->ag_features & BT_HFP_AG_FEATURE_VOICE_TAG)) { - hfp_ag_unlock(ag); return -ENOEXEC; } - hfp_ag_unlock(ag); if (!is_char(buf, '=')) { return -ENOTSUP; @@ -3966,7 +3931,6 @@ int bt_hfp_ag_reject(struct bt_hfp_ag_call *call) int err = 0; struct bt_hfp_ag *ag; bt_hfp_call_state_t call_state; - uint32_t ag_features; LOG_DBG(""); @@ -3987,14 +3951,13 @@ int bt_hfp_ag_reject(struct bt_hfp_ag_call *call) } call_state = call->call_state; - ag_features = ag->ag_features; hfp_ag_unlock(ag); if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { return -EINVAL; } - if (!(ag_features & BT_HFP_AG_FEATURE_REJECT_CALL)) { + if (!(ag->ag_features & BT_HFP_AG_FEATURE_REJECT_CALL)) { LOG_ERR("AG has not ability to reject call"); return -ENOTSUP; } @@ -4599,13 +4562,12 @@ int bt_hfp_ag_vgm(struct bt_hfp_ag *ag, uint8_t vgm) hfp_ag_unlock(ag); return -ENOTCONN; } + hfp_ag_unlock(ag); if (!(ag->hf_features & BT_HFP_HF_FEATURE_VOLUME)) { - hfp_ag_unlock(ag); LOG_ERR("Remote Audio Volume Control is unsupported"); return -ENOTSUP; } - hfp_ag_unlock(ag); err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+VGM=%d\r\n", vgm); if (err) { @@ -4635,13 +4597,12 @@ int bt_hfp_ag_vgs(struct bt_hfp_ag *ag, uint8_t vgs) hfp_ag_unlock(ag); return -ENOTCONN; } + hfp_ag_unlock(ag); if (!(ag->hf_features & BT_HFP_HF_FEATURE_VOLUME)) { - hfp_ag_unlock(ag); LOG_ERR("Remote Audio Volume Control is unsupported"); return -ENOTSUP; } - hfp_ag_unlock(ag); err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+VGS=%d\r\n", vgs); if (err) { @@ -4706,11 +4667,8 @@ int bt_hfp_ag_voice_recognition(struct bt_hfp_ag *ag, bool activate) return -ENOTSUP; } - hfp_ag_lock(ag); feature = (ag->ag_features & BT_HFP_AG_FEATURE_ENH_VOICE_RECG) && (ag->hf_features & BT_HFP_HF_FEATURE_ENH_VOICE_RECG); - hfp_ag_unlock(ag); - if (!feature) { bvra = ""; } else { @@ -4755,10 +4713,8 @@ int bt_hfp_ag_vre_state(struct bt_hfp_ag *ag, uint8_t state) return -EINVAL; } - hfp_ag_lock(ag); feature = (ag->ag_features & BT_HFP_AG_FEATURE_ENH_VOICE_RECG) && (ag->hf_features & BT_HFP_HF_FEATURE_ENH_VOICE_RECG); - hfp_ag_unlock(ag); if (!feature) { return -ENOTSUP; } @@ -4806,18 +4762,14 @@ int bt_hfp_ag_vre_textual_representation(struct bt_hfp_ag *ag, uint8_t state, co return -EINVAL; } - hfp_ag_lock(ag); feature = (ag->ag_features & BT_HFP_AG_FEATURE_ENH_VOICE_RECG) && (ag->hf_features & BT_HFP_HF_FEATURE_ENH_VOICE_RECG); - hfp_ag_unlock(ag); if (!feature) { return -ENOTSUP; } - hfp_ag_lock(ag); feature = (ag->ag_features & BT_HFP_AG_FEATURE_VOICE_RECG_TEXT) && (ag->hf_features & BT_HFP_HF_FEATURE_VOICE_RECG_TEXT); - hfp_ag_unlock(ag); if (!feature) { return -ENOTSUP; } From 4020bb2ae2fdb71bd5aa19dd8323790cee7684a4 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Wed, 13 Nov 2024 20:13:00 +0800 Subject: [PATCH 74/76] Bluetooth: HFP_AG: Optimize feature access Add `BOTH_SUPT_FEAT(ag, _hf_feature, _ag_feature)` to check if the feature is supported by both side. Add `HF_SUPT_FEAT(ag, _feature)` to check if the feature is supported by HF. Add `AG_SUPT_FEAT(ag, _feature)` to check if the feature is supported by AG. Use `BOTH_SUPT_FEAT/HF_SUPT_FEAT/AG_SUPT_FEAT` to optimize code that checks whether a feature is supported. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/hfp_ag.c | 67 +++++++++++++------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index 6d09143f2b31e..bd11e67550ac1 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -73,6 +73,11 @@ static struct bt_hfp_ag bt_hfp_ag_pool[CONFIG_BT_MAX_CONN]; static struct bt_hfp_ag_cb *bt_ag; +#define AG_SUPT_FEAT(ag, _feature) ((ag->ag_features & (_feature)) != 0) +#define HF_SUPT_FEAT(ag, _feature) ((ag->hf_features & (_feature)) != 0) +#define BOTH_SUPT_FEAT(ag, _hf_feature, _ag_feature) \ + (HF_SUPT_FEAT(ag, _hf_feature) && AG_SUPT_FEAT(ag, _ag_feature)) + /* Sent but not acknowledged TX packets with a callback */ static struct bt_ag_tx ag_tx[CONFIG_BT_HFP_AG_TX_BUF_COUNT * 2]; static K_FIFO_DEFINE(ag_tx_free); @@ -953,8 +958,7 @@ static int bt_hfp_ag_bac_handler(struct bt_hfp_ag *ag, struct net_buf *buf) return -ENOTSUP; } - if (!(ag->hf_features & BT_HFP_HF_FEATURE_CODEC_NEG) || - !(ag->ag_features & BT_HFP_AG_FEATURE_CODEC_NEG)) { + if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_CODEC_NEG, BT_HFP_AG_FEATURE_CODEC_NEG)) { return -ENOEXEC; } @@ -1057,7 +1061,7 @@ static void bt_hfp_ag_set_in_band_ring(struct bt_hfp_ag *ag, void *user_data) { bool is_inband_ringtone; - is_inband_ringtone = (ag->ag_features & BT_HFP_AG_FEATURE_INBAND_RINGTONE) ? true : false; + is_inband_ringtone = AG_SUPT_FEAT(ag, BT_HFP_AG_FEATURE_INBAND_RINGTONE) ? true : false; if (is_inband_ringtone && !atomic_test_bit(ag->flags, BT_HFP_AG_INBAND_RING)) { int err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BSIR:1\r\n"); @@ -1108,8 +1112,7 @@ static int bt_hfp_ag_cmer_handler(struct bt_hfp_ag *ag, struct net_buf *buf) if (number == 1) { atomic_set_bit(ag->flags, BT_HFP_AG_CMER_ENABLE); - if ((ag->hf_features & BT_HFP_HF_FEATURE_3WAY_CALL) && - (ag->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) { + if (BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_3WAY_CALL, BT_HFP_AG_FEATURE_3WAY_CALL)) { LOG_DBG("Waiting for AT+CHLD=?"); return 0; } @@ -1513,8 +1516,7 @@ static int bt_hfp_ag_chld_handler(struct bt_hfp_ag *ag, struct net_buf *buf) int err; char *response; - if (!((ag->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL) && - (ag->hf_features & BT_HFP_HF_FEATURE_3WAY_CALL))) { + if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_3WAY_CALL, BT_HFP_AG_FEATURE_3WAY_CALL)) { return -ENOEXEC; } @@ -1569,8 +1571,7 @@ static int bt_hfp_ag_bind_handler(struct bt_hfp_ag *ag, struct net_buf *buf) char *data; uint32_t len; - if (!((ag->ag_features & BT_HFP_AG_FEATURE_HF_IND) && - (ag->hf_features & BT_HFP_HF_FEATURE_HF_IND))) { + if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_HF_IND, BT_HFP_AG_FEATURE_HF_IND)) { return -ENOEXEC; } @@ -2527,8 +2528,8 @@ static int bt_hfp_ag_outgoing_call(struct bt_hfp_ag *ag, const char *number, uin if (call_count) { #if defined(CONFIG_BT_HFP_AG_3WAY_CALL) - if (!((ag->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL) && - (ag->hf_features & BT_HFP_HF_FEATURE_3WAY_CALL))) { + if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_3WAY_CALL, + BT_HFP_AG_FEATURE_3WAY_CALL)) { return -ENOEXEC; } @@ -2723,7 +2724,7 @@ static int bt_hfp_ag_nrec_handler(struct bt_hfp_ag *ag, struct net_buf *buf) return -ENOTSUP; } - if (!(ag->ag_features & BT_HFP_AG_SDP_FEATURE_3WAY_CALL)) { + if (!AG_SUPT_FEAT(ag, BT_HFP_AG_FEATURE_ECNR)) { return -ENOTSUP; } @@ -2883,8 +2884,7 @@ static int bt_hfp_ag_ccwa_handler(struct bt_hfp_ag *ag, struct net_buf *buf) int err; uint32_t value; - if (!((ag->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL) && - (ag->hf_features & BT_HFP_HF_FEATURE_3WAY_CALL))) { + if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_3WAY_CALL, BT_HFP_AG_FEATURE_3WAY_CALL)) { return -ENOEXEC; } @@ -2913,8 +2913,8 @@ static void bt_hfp_ag_vr_activate(struct bt_hfp_ag *ag, void *user_data) { bool feature; - feature = (ag->ag_features & BT_HFP_AG_FEATURE_ENH_VOICE_RECG) && - (ag->hf_features & BT_HFP_HF_FEATURE_ENH_VOICE_RECG); + feature = BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_ENH_VOICE_RECG, + BT_HFP_AG_FEATURE_ENH_VOICE_RECG); #if defined(CONFIG_BT_HFP_AG_VOICE_RECG) if (bt_ag && bt_ag->voice_recognition) { @@ -2960,8 +2960,7 @@ static int bt_hfp_ag_bvra_handler(struct bt_hfp_ag *ag, struct net_buf *buf) int err; uint32_t value; - if (!((ag->ag_features & BT_HFP_AG_FEATURE_VOICE_RECG) && - (ag->hf_features & BT_HFP_HF_FEATURE_VOICE_RECG))) { + if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_VOICE_RECG, BT_HFP_AG_FEATURE_VOICE_RECG)) { return -ENOEXEC; } @@ -2995,8 +2994,8 @@ static int bt_hfp_ag_bvra_handler(struct bt_hfp_ag *ag, struct net_buf *buf) err = hfp_ag_next_step(ag, bt_hfp_ag_vr_activate, NULL); break; case BT_HFP_BVRA_READY_TO_ACCEPT: - if (!((ag->ag_features & BT_HFP_AG_FEATURE_ENH_VOICE_RECG) && - (ag->hf_features & BT_HFP_HF_FEATURE_ENH_VOICE_RECG))) { + if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_ENH_VOICE_RECG, + BT_HFP_AG_FEATURE_ENH_VOICE_RECG)) { LOG_WRN("Enhance voice recognition is not supported"); return -ENOEXEC; } @@ -3021,7 +3020,7 @@ static int bt_hfp_ag_binp_handler(struct bt_hfp_ag *ag, struct net_buf *buf) char *number = NULL; #endif /* CONFIG_BT_HFP_AG_VOICE_TAG */ - if (!(ag->ag_features & BT_HFP_AG_FEATURE_VOICE_TAG)) { + if (!AG_SUPT_FEAT(ag, BT_HFP_AG_FEATURE_VOICE_TAG)) { return -ENOEXEC; } @@ -3830,8 +3829,8 @@ int bt_hfp_ag_remote_incoming(struct bt_hfp_ag *ag, const char *number) call_count = get_none_released_calls(ag); if (call_count) { #if defined(CONFIG_BT_HFP_AG_3WAY_CALL) - if (!((ag->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL) && - (ag->hf_features & BT_HFP_HF_FEATURE_3WAY_CALL))) { + if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_3WAY_CALL, + BT_HFP_AG_FEATURE_3WAY_CALL)) { LOG_WRN("3 Way call feature is not supported on both sides"); return -ENOEXEC; } @@ -3957,7 +3956,7 @@ int bt_hfp_ag_reject(struct bt_hfp_ag_call *call) return -EINVAL; } - if (!(ag->ag_features & BT_HFP_AG_FEATURE_REJECT_CALL)) { + if (!AG_SUPT_FEAT(ag, BT_HFP_AG_FEATURE_REJECT_CALL)) { LOG_ERR("AG has not ability to reject call"); return -ENOTSUP; } @@ -4564,7 +4563,7 @@ int bt_hfp_ag_vgm(struct bt_hfp_ag *ag, uint8_t vgm) } hfp_ag_unlock(ag); - if (!(ag->hf_features & BT_HFP_HF_FEATURE_VOLUME)) { + if (!HF_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_VOLUME)) { LOG_ERR("Remote Audio Volume Control is unsupported"); return -ENOTSUP; } @@ -4599,7 +4598,7 @@ int bt_hfp_ag_vgs(struct bt_hfp_ag *ag, uint8_t vgs) } hfp_ag_unlock(ag); - if (!(ag->hf_features & BT_HFP_HF_FEATURE_VOLUME)) { + if (!HF_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_VOLUME)) { LOG_ERR("Remote Audio Volume Control is unsupported"); return -ENOTSUP; } @@ -4667,8 +4666,8 @@ int bt_hfp_ag_voice_recognition(struct bt_hfp_ag *ag, bool activate) return -ENOTSUP; } - feature = (ag->ag_features & BT_HFP_AG_FEATURE_ENH_VOICE_RECG) && - (ag->hf_features & BT_HFP_HF_FEATURE_ENH_VOICE_RECG); + feature = BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_ENH_VOICE_RECG, + BT_HFP_AG_FEATURE_ENH_VOICE_RECG); if (!feature) { bvra = ""; } else { @@ -4713,8 +4712,8 @@ int bt_hfp_ag_vre_state(struct bt_hfp_ag *ag, uint8_t state) return -EINVAL; } - feature = (ag->ag_features & BT_HFP_AG_FEATURE_ENH_VOICE_RECG) && - (ag->hf_features & BT_HFP_HF_FEATURE_ENH_VOICE_RECG); + feature = BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_ENH_VOICE_RECG, + BT_HFP_AG_FEATURE_ENH_VOICE_RECG); if (!feature) { return -ENOTSUP; } @@ -4762,14 +4761,14 @@ int bt_hfp_ag_vre_textual_representation(struct bt_hfp_ag *ag, uint8_t state, co return -EINVAL; } - feature = (ag->ag_features & BT_HFP_AG_FEATURE_ENH_VOICE_RECG) && - (ag->hf_features & BT_HFP_HF_FEATURE_ENH_VOICE_RECG); + feature = BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_ENH_VOICE_RECG, + BT_HFP_AG_FEATURE_ENH_VOICE_RECG); if (!feature) { return -ENOTSUP; } - feature = (ag->ag_features & BT_HFP_AG_FEATURE_VOICE_RECG_TEXT) && - (ag->hf_features & BT_HFP_HF_FEATURE_VOICE_RECG_TEXT); + feature = BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_VOICE_RECG_TEXT, + BT_HFP_AG_FEATURE_VOICE_RECG_TEXT); if (!feature) { return -ENOTSUP; } From 0a79a94e6b64aa6ff0c31e321c0b68e783bd5c8d Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Wed, 13 Nov 2024 21:25:51 +0800 Subject: [PATCH 75/76] Bluetooth: HFP_AG: Remove unnecessary code line `__fallthrough` Remove all unnecessary code lines `__fallthrough` from switch-case. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/hfp_ag.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index bd11e67550ac1..a3f417c11ad6a 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -171,7 +171,6 @@ static enum at_cme bt_hfp_ag_get_cme_err(int err) cme_err = CME_ERROR_MEMORY_FAILURE; break; case -ENOMEM: - __fallthrough; case -ENOBUFS: cme_err = CME_ERROR_MEMORY_FULL; break; @@ -3405,11 +3404,8 @@ static void bt_ag_deferred_work_cb(struct bt_hfp_ag *ag, void *user_data) case BT_HFP_CALL_HOLD: break; case BT_HFP_CALL_OUTGOING: - __fallthrough; case BT_HFP_CALL_INCOMING: - __fallthrough; case BT_HFP_CALL_ALERTING: - __fallthrough; default: LOG_WRN("Call timeout, status %s", bt_ag_get_call_state_string(call_state)); @@ -4439,22 +4435,16 @@ int bt_hfp_ag_set_indicator(struct bt_hfp_ag *ag, enum bt_hfp_ag_indicator index switch (index) { case BT_HFP_AG_SERVICE_IND: - __fallthrough; case BT_HFP_AG_SIGNAL_IND: - __fallthrough; case BT_HFP_AG_ROAM_IND: - __fallthrough; case BT_HFP_AG_BATTERY_IND: if ((ag_ind[(uint8_t)index].min > value) || (ag_ind[(uint8_t)index].max < value)) { return -EINVAL; } break; case BT_HFP_AG_CALL_IND: - __fallthrough; case BT_HFP_AG_CALL_SETUP_IND: - __fallthrough; case BT_HFP_AG_CALL_HELD_IND: - __fallthrough; default: return -EINVAL; } From 1ecaf008010ebe2296778d0d7862855508ab87d8 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Thu, 27 Feb 2025 20:16:43 +0800 Subject: [PATCH 76/76] Bluetooth: HFP_AG: Add ACL conn to the `connected` callback If the AG works as Data Channel Acceptor, the ACL conn cannot be known by the application of AG. Similar with HF, add ACL conn as the first parameter to the `connected` callback of AG. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/hfp_ag.h | 3 ++- samples/bluetooth/handsfree_ag/src/main.c | 6 +++++- subsys/bluetooth/host/classic/hfp_ag.c | 2 +- subsys/bluetooth/host/classic/shell/hfp.c | 5 ++++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/include/zephyr/bluetooth/classic/hfp_ag.h b/include/zephyr/bluetooth/classic/hfp_ag.h index 5dc89110621c6..599a1ba1aaf44 100644 --- a/include/zephyr/bluetooth/classic/hfp_ag.h +++ b/include/zephyr/bluetooth/classic/hfp_ag.h @@ -84,9 +84,10 @@ struct bt_hfp_ag_cb { * If this callback is provided it will be called whenever the * AG connection completes. * + * @param conn Connection object. * @param ag HFP AG object. */ - void (*connected)(struct bt_hfp_ag *ag); + void (*connected)(struct bt_conn *conn, struct bt_hfp_ag *ag); /** HF disconnected callback to application * * If this callback is provided it will be called whenever the diff --git a/samples/bluetooth/handsfree_ag/src/main.c b/samples/bluetooth/handsfree_ag/src/main.c index 3bf6c2d15ece8..721b8f6e26057 100644 --- a/samples/bluetooth/handsfree_ag/src/main.c +++ b/samples/bluetooth/handsfree_ag/src/main.c @@ -40,8 +40,12 @@ struct k_work_delayable call_remote_accept_work; NET_BUF_POOL_DEFINE(sdp_discover_pool, 10, BT_L2CAP_BUF_SIZE(CONFIG_BT_L2CAP_TX_MTU), CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); -static void ag_connected(struct bt_hfp_ag *ag) +static void ag_connected(struct bt_conn *conn, struct bt_hfp_ag *ag) { + if (conn != default_conn) { + printk("The conn %p is not aligned with ACL conn %p", conn, default_conn); + } + if (!hfp_ag) { hfp_ag = ag; } diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index a3f417c11ad6a..8c12dde09a067 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -231,7 +231,7 @@ static void bt_hfp_ag_set_state(struct bt_hfp_ag *ag, bt_hfp_state_t state) break; case BT_HFP_CONNECTED: if (bt_ag && bt_ag->connected) { - bt_ag->connected(ag); + bt_ag->connected(ag->acl_conn, ag); } break; case BT_HFP_DISCONNECTING: diff --git a/subsys/bluetooth/host/classic/shell/hfp.c b/subsys/bluetooth/host/classic/shell/hfp.c index 1f5f82914cdff..7d713d81c34f7 100644 --- a/subsys/bluetooth/host/classic/shell/hfp.c +++ b/subsys/bluetooth/host/classic/shell/hfp.c @@ -982,8 +982,11 @@ static void ag_remove_a_call(struct bt_hfp_ag_call *call) } } -static void ag_connected(struct bt_hfp_ag *ag) +static void ag_connected(struct bt_conn *conn, struct bt_hfp_ag *ag) { + if (conn != default_conn) { + bt_shell_warn("The conn %p is not aligned with ACL conn %p", conn, default_conn); + } hfp_ag = ag; bt_shell_print("ag connected"); }