Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions include/zephyr/bluetooth/classic/hfp_hf.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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: <idx>,<dir>,<status>,<mode>,<mprty>[,<number>,<type>]`
* 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
Expand Down Expand Up @@ -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
Expand Down
132 changes: 96 additions & 36 deletions subsys/bluetooth/host/classic/hfp_hf.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -529,32 +530,36 @@ 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;

LOG_DBG("");

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);
Expand All @@ -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)
Expand Down Expand Up @@ -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");
Expand All @@ -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, &current_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 */
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}
4 changes: 4 additions & 0 deletions subsys/bluetooth/host/classic/hfp_hf_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down
33 changes: 33 additions & 0 deletions subsys/bluetooth/host/classic/shell/hfp.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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, "<channel>", cmd_connect, 2, 0),
Expand Down Expand Up @@ -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, "<level>", 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 */
Expand Down