diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index 3a6dd4fc8f59f..7139fa38fcc41 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -32,6 +32,41 @@ struct bt_hfp_hf; struct bt_hfp_hf_call; +/* The status of the call */ +enum __packed bt_hfp_hf_call_status { + BT_HFP_HF_CALL_STATUS_ACTIVE = 0, /* Call is active */ + BT_HFP_HF_CALL_STATUS_HELD = 1, /* Call is on hold */ + BT_HFP_HF_CALL_STATUS_DIALING = 2, /* Outgoing call is being dialed */ + BT_HFP_HF_CALL_STATUS_ALERTING = 3, /* Outgoing call is being alerted */ + BT_HFP_HF_CALL_STATUS_INCOMING = 4, /* Incoming call is came */ + BT_HFP_HF_CALL_STATUS_WAITING = 5, /* Incoming call is waiting */ + BT_HFP_HF_CALL_STATUS_INCOMING_HELD = 6 /* Call held by Response and Hold */ +}; + +/* The direction of the call */ +enum __packed bt_hfp_hf_call_dir { + BT_HFP_HF_CALL_DIR_OUTGOING = 0, /* It is a outgoing call */ + BT_HFP_HF_CALL_DIR_INCOMING = 1, /* It is a incoming call */ +}; + +/* The mode of the call */ +enum __packed bt_hfp_hf_call_mode { + BT_HFP_HF_CALL_MODE_VOICE = 0, /* Voice */ + BT_HFP_HF_CALL_MODE_DATA = 1, /* Data */ + BT_HFP_HF_CALL_MODE_FAX = 2, /* FAX */ +}; + +/* The information of current call */ +struct bt_hfp_hf_current_call { + uint8_t index; /* index */ + enum bt_hfp_hf_call_dir dir; /* direction */ + enum bt_hfp_hf_call_status status; /* status */ + enum bt_hfp_hf_call_mode mode; /* mode */ + bool multiparty; /* multiparty */ + const char *number; /* phone number */ + uint8_t type; /* phone number type */ +}; + /** @brief HFP profile application callback */ struct bt_hfp_hf_cb { /** HF connected callback to application @@ -416,6 +451,20 @@ struct bt_hfp_hf_cb { */ void (*subscriber_number)(struct bt_hfp_hf *hf, const char *number, uint8_t type, uint8_t service); + + /** Query list of current calls callback + * + * If this callback is provided it will be called whenever the + * result code `+CLCC: ,,,,[,,]` + * is received from AG. + * If the request is failed or no active calls, the callback will not be called. + * If the @ref bt_hfp_hf_current_call::number is NULL, the + * @ref bt_hfp_hf_current_call::type shall be ignored. + * + * @param hf HFP HF object. + * @param call Current call information. + */ + void (*query_call)(struct bt_hfp_hf *hf, struct bt_hfp_hf_current_call *call); }; /** @brief Register HFP HF profile @@ -952,6 +1001,18 @@ int bt_hfp_hf_enhanced_safety(struct bt_hfp_hf *hf, bool enable); */ int bt_hfp_hf_battery(struct bt_hfp_hf *hf, uint8_t level); +/** @brief Handsfree HF query list of current calls + * + * It allows HF to query list of current calls by sending `AT+CLCC` command. + * If @kconfig{CONFIG_BT_HFP_HF_ECS} 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_query_list_of_current_calls(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 40f6523f44b84..a7742c6facb43 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -509,6 +509,7 @@ static int clcc_finish(struct at_client *hf_at, enum at_result result, clear_call_without_clcc(hf); } + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_USR_CLCC_CMD); atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_CLCC_PENDING); return 0; @@ -529,7 +530,7 @@ static void clear_call_clcc_state(struct bt_hfp_hf *hf) } } -static void hf_query_current_calls(struct bt_hfp_hf *hf) +static int hf_query_current_calls(struct bt_hfp_hf *hf) { int err; @@ -537,24 +538,28 @@ static void hf_query_current_calls(struct bt_hfp_hf *hf) if (!hf) { LOG_ERR("No HF connection found"); - return; + return -EINVAL; } if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { - return; + return -ENOTCONN; } if (!(hf->ag_features & BT_HFP_AG_FEATURE_ECS)) { - return; + return -ENOTSUP; } if (!(hf->hf_features & BT_HFP_HF_FEATURE_ECS)) { - return; + return -ENOTSUP; } - if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CLCC_PENDING)) { + if (atomic_test_and_set_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; + } + + if (atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_USR_CLCC_PND)) { + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_USR_CLCC_CMD); } clear_call_clcc_state(hf); @@ -563,6 +568,8 @@ static void hf_query_current_calls(struct bt_hfp_hf *hf) if (err < 0) { LOG_ERR("Fail to query current calls on %p", hf); } + + return err; } static void hf_call_state_update(struct bt_hfp_hf_call *call, int state) @@ -812,20 +819,6 @@ static int clcc_handle(struct at_client *hf_at) return err; } - if (new_call) { - set_call_incoming_flag(call, dir == BT_HFP_CLCC_DIR_INCOMING); - } - - 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 (incoming != (dir == BT_HFP_CLCC_DIR_INCOMING)) { - LOG_ERR("Call dir of HF is not aligned with AG"); - return 0; - } - err = at_get_number(hf_at, &status); if (err < 0) { LOG_ERR("Error getting status"); @@ -846,19 +839,48 @@ static int clcc_handle(struct at_client *hf_at) number = at_get_string(hf_at); - if (number) { + if (number != NULL) { (void)at_get_number(hf_at, &type); } + if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_USR_CLCC_CMD) && + (bt_hf->query_call != NULL)) { + struct bt_hfp_hf_current_call current_call; + + current_call.index = (uint8_t)index; + current_call.dir = (enum bt_hfp_hf_call_dir)dir; + current_call.status = (enum bt_hfp_hf_call_status)status; + current_call.mode = (enum bt_hfp_hf_call_mode)mode; + current_call.multiparty = mpty > 0 ? true : false; + current_call.number = number; + current_call.type = (uint8_t)type; + + bt_hf->query_call(hf, ¤t_call); + } + + LOG_DBG("CLCC idx %d dir %d status %d mode %d mpty %d number %s type %d", + index, dir, status, mode, mpty, number, type); + + if (new_call) { + set_call_incoming_flag(call, dir == BT_HFP_CLCC_DIR_INCOMING); + } + + 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 (incoming != (dir == BT_HFP_CLCC_DIR_INCOMING)) { + LOG_ERR("Call dir of HF is not aligned with AG"); + return 0; + } + if (new_call) { new_call_state_update(call, incoming, status); } else { 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 */ @@ -1054,8 +1076,12 @@ static void bt_hf_deferred_work(struct k_work *work) { 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); + int err; - hf_query_current_calls(hf); + err = hf_query_current_calls(hf); + if (err != 0) { + LOG_ERR("Failed to query current calls: %d", err); + } } static void set_all_calls_held_state(struct bt_hfp_hf *hf, bool held) @@ -1091,8 +1117,8 @@ static void ag_indicator_handle_call(struct bt_hfp_hf *hf, uint32_t value) LOG_DBG("call %d", value); - if (value != 0) { - atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_CLCC_PENDING); + if ((value != 0) && atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_INITIATING)) { + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_QUERY_CALLS); } if (value) { @@ -1153,8 +1179,9 @@ static void ag_indicator_handle_call_setup(struct bt_hfp_hf *hf, uint32_t value) LOG_DBG("call setup %d", value); - if (value != BT_HFP_CALL_SETUP_NONE) { - atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_CLCC_PENDING); + if ((value != BT_HFP_CALL_SETUP_NONE) && + atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_INITIATING)) { + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_QUERY_CALLS); } switch (value) { @@ -1254,12 +1281,15 @@ static void ag_indicator_handle_call_held(struct bt_hfp_hf *hf, uint32_t value) { struct bt_hfp_hf_call *call; - k_work_reschedule(&hf->deferred_work, K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + k_work_reschedule(&hf->deferred_work, K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + } LOG_DBG("call setup %d", value); - if (value != BT_HFP_CALL_HELD_NONE) { - atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_CLCC_PENDING); + if ((value != BT_HFP_CALL_HELD_NONE) && + atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_INITIATING)) { + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_QUERY_CALLS); } switch (value) { @@ -2077,9 +2107,12 @@ static int at_cmd_init_start(struct bt_hfp_hf *hf) hf->cmd_init_seq++; } - if ((ARRAY_SIZE(cmd_init_list) <= hf->cmd_init_seq) && - atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_CLCC_PENDING)) { - k_work_reschedule(&hf->deferred_work, K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + if (ARRAY_SIZE(cmd_init_list) <= hf->cmd_init_seq) { + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_INITIATING); + if (atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_QUERY_CALLS)) { + k_work_reschedule(&hf->deferred_work, + K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + } } return err; @@ -2308,6 +2341,8 @@ int hf_slc_establish(struct bt_hfp_hf *hf) LOG_DBG(""); + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_INITIATING); + hf->cmd_init_seq = 0; err = slc_init_start(hf); if (err < 0) { @@ -4266,3 +4301,28 @@ int bt_hfp_hf_disconnect(struct bt_hfp_hf *hf) return bt_rfcomm_dlc_disconnect(&hf->rfcomm_dlc); } + +int bt_hfp_hf_query_list_of_current_calls(struct bt_hfp_hf *hf) +{ + int err; + + if ((hf == NULL) || (bt_hf == NULL) || (bt_hf->query_call == NULL)) { + return -EINVAL; + } + + if (!IS_ENABLED(CONFIG_BT_HFP_HF_ECS)) { + return -ENOTSUP; + } + + if (atomic_test_and_set_bit(hf->flags, BT_HFP_HF_FLAG_USR_CLCC_PND)) { + return -EBUSY; + } + + err = hf_query_current_calls(hf); + if (err != 0) { + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_USR_CLCC_PND); + LOG_ERR("Failed to query current calls, err %d", err); + } + + return err; +} diff --git a/subsys/bluetooth/host/classic/hfp_hf_internal.h b/subsys/bluetooth/host/classic/hfp_hf_internal.h index caecc60dac9fa..2bbaf671a2f85 100644 --- a/subsys/bluetooth/host/classic/hfp_hf_internal.h +++ b/subsys/bluetooth/host/classic/hfp_hf_internal.h @@ -143,6 +143,10 @@ enum { 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 */ + BT_HFP_HF_FLAG_INITIATING, /* HF is in initiating state */ + BT_HFP_HF_FLAG_QUERY_CALLS, /* Require to query list of current calls */ + BT_HFP_HF_FLAG_USR_CLCC_CMD, /* User-initiated AT+CLCC command */ + BT_HFP_HF_FLAG_USR_CLCC_PND, /* User-initiated AT+CLCC command is pending */ /* Total number of flags - must be at the end of the enum */ BT_HFP_HF_NUM_FLAGS, }; diff --git a/subsys/bluetooth/host/classic/shell/hfp.c b/subsys/bluetooth/host/classic/shell/hfp.c index 7b19ad0ea46b2..f3a3f776b03cc 100644 --- a/subsys/bluetooth/host/classic/shell/hfp.c +++ b/subsys/bluetooth/host/classic/shell/hfp.c @@ -299,6 +299,19 @@ void hf_subscriber_number(struct bt_hfp_hf *hf, const char *number, uint8_t type bt_shell_print("Subscriber number %s, type %d, service %d", number, type, service); } +#if defined(CONFIG_BT_HFP_HF_ECS) +void hf_query_call(struct bt_hfp_hf *hf, struct bt_hfp_hf_current_call *call) +{ + if (call == NULL) { + return; + } + + bt_shell_print("CLCC idx %d dir %d status %d mode %d mpty %d number %s type %d", + call->index, call->dir, call->status, call->mode, call->multiparty, + call->number, call->type); +} +#endif /* CONFIG_BT_HFP_HF_ECS */ + static struct bt_hfp_hf_cb hf_cb = { .connected = hf_connected, .disconnected = hf_disconnected, @@ -348,6 +361,9 @@ static struct bt_hfp_hf_cb hf_cb = { #endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ .request_phone_number = hf_request_phone_number, .subscriber_number = hf_subscriber_number, +#if defined(CONFIG_BT_HFP_HF_ECS) + .query_call = hf_query_call, +#endif /* CONFIG_BT_HFP_HF_ECS */ }; static int cmd_reg_enable(const struct shell *sh, size_t argc, char **argv) @@ -952,6 +968,20 @@ static int cmd_battery(const struct shell *sh, size_t argc, char **argv) } #endif /* CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY */ +#if defined(CONFIG_BT_HFP_HF_ECS) +static int cmd_query_calls(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_query_list_of_current_calls(hfp_hf); + if (err != 0) { + shell_error(sh, "Failed to query list of current calls: %d", err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_HF_ECS */ + 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), @@ -1017,6 +1047,9 @@ SHELL_STATIC_SUBCMD_SET_CREATE(hf_cmds, #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 */ +#if defined(CONFIG_BT_HFP_HF_ECS) + SHELL_CMD_ARG(query_calls, NULL, HELP_NONE, cmd_query_calls, 1, 0), +#endif /* */ SHELL_SUBCMD_SET_END ); #endif /* CONFIG_BT_HFP_HF */