blob: 40f6523f44b8478f942d1e4226442e217cdccde0 [file] [log] [blame]
/* hfp_hf.c - Hands free Profile - Handsfree side handling */
/*
* Copyright (c) 2015-2016 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <errno.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/printk.h>
#include <zephyr/bluetooth/conn.h>
#include "common/assert.h"
#include <zephyr/bluetooth/classic/rfcomm.h>
#include <zephyr/bluetooth/classic/hfp_hf.h>
#include <zephyr/bluetooth/classic/sdp.h>
#include "host/hci_core.h"
#include "host/conn_internal.h"
#include "l2cap_br_internal.h"
#include "rfcomm_internal.h"
#include "at.h"
#include "sco_internal.h"
#include "hfp_hf_internal.h"
#define LOG_LEVEL CONFIG_BT_HFP_HF_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_hfp_hf);
#define MAX_IND_STR_LEN 17
struct bt_hfp_hf_cb *bt_hf;
NET_BUF_POOL_FIXED_DEFINE(hf_pool, CONFIG_BT_MAX_CONN + 1,
BT_RFCOMM_BUF_SIZE(BT_HF_CLIENT_MAX_PDU),
CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL);
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;
} __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;
uint32_t min;
uint32_t max;
} ag_ind[] = {
{"service", 0, 1}, /* HF_SERVICE_IND */
{"call", 0, 1}, /* HF_CALL_IND */
{"callsetup", 0, 3}, /* HF_CALL_SETUP_IND */
{"callheld", 0, 2}, /* HF_CALL_HELD_IND */
{"signal", 0, 5}, /* HF_SIGNAL_IND */
{"roam", 0, 1}, /* HF_ROAM_IND */
{"battchg", 0, 5} /* HF_BATTERY_IND */
};
/* HFP Hands-Free SDP record */
static struct bt_sdp_attribute hfp_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_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_HF)
},
)
},
)
),
BT_SDP_LIST(
BT_SDP_ATTR_PROFILE_DESC_LIST,
BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8),
BT_SDP_DATA_ELEM_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)
},
)
},
)
),
/* 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_SDP_SUPPORTED_FEATURES),
};
static struct bt_sdp_record hfp_rec = BT_SDP_RECORD(hfp_attrs);
void hf_slc_error(struct at_client *hf_at)
{
struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at);
int err;
LOG_ERR("SLC error: disconnecting");
err = bt_rfcomm_dlc_disconnect(&hf->rfcomm_dlc);
if (err) {
LOG_ERR("Rfcomm: Unable to disconnect :%d", -err);
}
}
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("TX is done on %p", hf);
} 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_RX_ONGOING)) {
return;
}
if (atomic_test_and_set_bit(hf->flags, BT_HFP_HF_FLAG_TX_ONGOING)) {
return;
}
buf = k_fifo_get(&hf->tx_pending, K_NO_WAIT);
if (!buf) {
atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_TX_ONGOING);
return;
}
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);
atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_TX_ONGOING);
net_buf_unref(buf);
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, ...)
{
struct net_buf *buf;
va_list vargs;
int ret;
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) {
LOG_ERR("Unable to format variable arguments");
return ret;
}
va_end(vargs);
net_buf_add(buf, ret);
net_buf_add_u8(buf, '\r');
LOG_DBG("HF %p, DLC %p sending buf %p", hf, &hf->rfcomm_dlc, buf);
k_fifo_put(&hf->tx_pending, buf);
hfp_hf_send_data(hf);
return 0;
}
int brsf_handle(struct at_client *hf_at)
{
struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at);
uint32_t val;
int ret;
ret = at_get_number(hf_at, &val);
if (ret < 0) {
LOG_ERR("Error getting value");
return ret;
}
hf->ag_features = val;
return 0;
}
int brsf_resp(struct at_client *hf_at, struct net_buf *buf)
{
int err;
LOG_DBG("");
err = at_parse_cmd_input(hf_at, buf, "BRSF", brsf_handle,
AT_CMD_TYPE_NORMAL);
if (err < 0) {
/* Returning negative value is avoided before SLC connection
* established.
*/
LOG_ERR("Error parsing CMD input");
hf_slc_error(hf_at);
}
return 0;
}
static void cind_handle_values(struct at_client *hf_at, uint32_t index,
char *name, uint32_t min, uint32_t max)
{
struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at);
int i;
LOG_DBG("index: %u, name: %s, min: %u, max:%u", index, name, min, max);
for (i = 0; i < ARRAY_SIZE(ag_ind); i++) {
if (strcmp(name, ag_ind[i].name) != 0) {
continue;
}
if (min != ag_ind[i].min || max != ag_ind[i].max) {
LOG_ERR("%s indicator min/max value not matching", name);
}
hf->ind_table[index] = i;
break;
}
}
int cind_handle(struct at_client *hf_at)
{
uint32_t index = 0U;
/* Parsing Example: CIND: ("call",(0,1)) etc.. */
while (at_has_next_list(hf_at)) {
char name[MAX_IND_STR_LEN];
uint32_t min, max;
if (at_open_list(hf_at) < 0) {
LOG_ERR("Could not get open list");
goto error;
}
if (at_list_get_string(hf_at, name, sizeof(name)) < 0) {
LOG_ERR("Could not get string");
goto error;
}
if (at_open_list(hf_at) < 0) {
LOG_ERR("Could not get open list");
goto error;
}
if (at_list_get_range(hf_at, &min, &max) < 0) {
LOG_ERR("Could not get range");
goto error;
}
if (at_close_list(hf_at) < 0) {
LOG_ERR("Could not get close list");
goto error;
}
if (at_close_list(hf_at) < 0) {
LOG_ERR("Could not get close list");
goto error;
}
cind_handle_values(hf_at, index, name, min, max);
index++;
}
return 0;
error:
LOG_ERR("Error on CIND response");
hf_slc_error(hf_at);
return -EINVAL;
}
int cind_resp(struct at_client *hf_at, struct net_buf *buf)
{
int err;
err = at_parse_cmd_input(hf_at, buf, "CIND", cind_handle,
AT_CMD_TYPE_NORMAL);
if (err < 0) {
LOG_ERR("Error parsing CMD input");
hf_slc_error(hf_at);
}
return 0;
}
static void free_call(struct bt_hfp_hf_call *call)
{
memset(call, 0, sizeof(*call));
}
#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_call *call;
for (size_t i = 0; i < ARRAY_SIZE(hf->calls); i++) {
call = &hf->calls[i];
if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) {
continue;
}
if (call->index == index) {
return call;
}
}
return NULL;
}
static struct bt_hfp_hf_call *get_call_without_index(struct bt_hfp_hf *hf)
{
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 (!call->index) {
return call;
}
}
return NULL;
}
#endif /* CONFIG_BT_HFP_HF_ECS */
static void hf_reject_call(struct bt_hfp_hf_call *call)
{
if (bt_hf->reject) {
bt_hf->reject(call);
}
free_call(call);
}
static void hf_terminate_call(struct bt_hfp_hf_call *call)
{
if (bt_hf->terminate) {
bt_hf->terminate(call);
}
free_call(call);
}
static void clear_call_without_clcc(struct bt_hfp_hf *hf)
{
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_and_clear_bit(call->flags, BT_HFP_HF_CALL_CLCC)) {
continue;
}
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;
}
}
}
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);
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;
}
static void clear_call_clcc_state(struct bt_hfp_hf *hf)
{
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;
}
atomic_clear_bit(call->flags, BT_HFP_HF_CALL_CLCC);
}
}
static void hf_query_current_calls(struct bt_hfp_hf *hf)
{
int err;
LOG_DBG("");
if (!hf) {
LOG_ERR("No HF connection found");
return;
}
if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) {
return;
}
if (!(hf->ag_features & BT_HFP_AG_FEATURE_ECS)) {
return;
}
if (!(hf->hf_features & BT_HFP_HF_FEATURE_ECS)) {
return;
}
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;
}
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);
}
}
static void hf_call_state_update(struct bt_hfp_hf_call *call, int state)
{
int old_state;
old_state = atomic_get(call->state);
atomic_set(call->state, state);
LOG_DBG("Call %p state update %d->%d", call, old_state, state);
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;
}
}
static int get_using_call_count(struct bt_hfp_hf *hf)
{
struct bt_hfp_hf_call *call;
int count = 0;
ARRAY_FOR_EACH(hf->calls, i) {
call = &hf->calls[i];
if (atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) {
count++;
}
}
return count;
}
static struct bt_hfp_hf_call *get_new_call(struct bt_hfp_hf *hf)
{
struct bt_hfp_hf_call *call;
ARRAY_FOR_EACH(hf->calls, i) {
call = &hf->calls[i];
if (atomic_test_and_set_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) {
continue;
}
call->hf = hf;
return call;
}
return NULL;
}
#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;
}
}
static void new_call_state_update(struct bt_hfp_hf_call *call, bool incoming, uint32_t status)
{
switch (status) {
case BT_HFP_CLCC_STATUS_ACTIVE:
case BT_HFP_CLCC_STATUS_HELD:
case BT_HFP_CLCC_STATUS_DIALING:
case BT_HFP_CLCC_STATUS_ALERTING:
case BT_HFP_CLCC_STATUS_INCOMING:
case BT_HFP_CLCC_STATUS_WAITING:
case BT_HFP_CLCC_STATUS_CALL_HELD_HOLD:
if (incoming) {
if (bt_hf->incoming) {
bt_hf->incoming(call->hf, call);
}
} else {
if (bt_hf->outgoing) {
bt_hf->outgoing(call->hf, call);
}
}
break;
default:
LOG_WRN("Invalid call status %u", status);
free_call(call);
return;
}
switch (status) {
case BT_HFP_CLCC_STATUS_ACTIVE:
hf_call_state_update(call, BT_HFP_HF_CALL_STATE_ACTIVE);
if (bt_hf->accept) {
bt_hf->accept(call);
}
break;
case BT_HFP_CLCC_STATUS_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:
hf_call_state_update(call, BT_HFP_HF_CALL_STATE_OUTGOING);
if (bt_hf->dialing) {
bt_hf->dialing(call->hf, 0);
}
break;
case BT_HFP_CLCC_STATUS_ALERTING:
hf_call_state_update(call, BT_HFP_HF_CALL_STATE_ALERTING);
if (bt_hf->remote_ringing) {
bt_hf->remote_ringing(call);
}
break;
case BT_HFP_CLCC_STATUS_INCOMING:
case BT_HFP_CLCC_STATUS_WAITING:
hf_call_state_update(call, BT_HFP_HF_CALL_STATE_WAITING);
break;
case BT_HFP_CLCC_STATUS_CALL_HELD_HOLD:
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;
default:
break;
}
}
static void set_call_incoming_flag(struct bt_hfp_hf_call *call, bool incoming)
{
int call_count;
call_count = get_using_call_count(call->hf);
if (call_count > 1) {
if (incoming) {
atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING_3WAY);
} else {
atomic_test_bit(call->flags, BT_HFP_HF_CALL_OUTGOING_3WAY);
}
} else {
atomic_set_bit_to(call->flags, BT_HFP_HF_CALL_INCOMING, incoming);
}
}
static int clcc_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 index;
uint32_t dir;
uint32_t status;
uint32_t mode;
uint32_t mpty;
char *number = NULL;
uint32_t type = 0;
bool incoming = false;
bool new_call = false;
err = at_get_number(hf_at, &index);
if (err < 0) {
LOG_ERR("Error getting index");
return err;
}
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) {
call = get_new_call(hf);
if (!call) {
LOG_INF("Not available call");
return 0;
}
new_call = true;
}
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;
}
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");
return err;
}
err = at_get_number(hf_at, &mode);
if (err < 0) {
LOG_ERR("Error getting mode");
return err;
}
err = at_get_number(hf_at, &mpty);
if (err < 0) {
LOG_ERR("Error getting mpty");
return err;
}
number = at_get_string(hf_at);
if (number) {
(void)at_get_number(hf_at, &type);
}
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 */
#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;
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;
}
static struct bt_hfp_hf_call *get_call_with_state(struct bt_hfp_hf *hf, int state)
{
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_get(call->state) == state) {
return call;
}
}
return NULL;
}
static struct bt_hfp_hf_call *get_call_with_flag(struct bt_hfp_hf *hf, int flag)
{
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 struct bt_hfp_hf_call *get_call_with_state_and_flag(struct bt_hfp_hf *hf,
int state, int flag)
{
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_get(call->state) == state) &&
atomic_test_bit(call->flags, flag)) {
return call;
}
}
return NULL;
}
static struct bt_hfp_hf_call *get_using_call(struct bt_hfp_hf *hf)
{
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;
}
return call;
}
return NULL;
}
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);
hf_query_current_calls(hf);
}
static void set_all_calls_held_state(struct bt_hfp_hf *hf, bool held)
{
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 (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);
}
}
}
}
static void ag_indicator_handle_call(struct bt_hfp_hf *hf, uint32_t value)
{
struct bt_hfp_hf_call *call;
LOG_DBG("call %d", value);
if (value != 0) {
atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_CLCC_PENDING);
}
if (value) {
call = get_dialing_call(hf);
if (!call) {
return;
}
hf_call_state_update(call, BT_HFP_HF_CALL_STATE_ACTIVE);
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 void ag_indicator_handle_call_setup(struct bt_hfp_hf *hf, uint32_t value)
{
struct bt_hfp_hf_call *call;
int call_count;
call_count = get_using_call_count(hf);
LOG_DBG("call setup %d", value);
if (value != BT_HFP_CALL_SETUP_NONE) {
atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_CLCC_PENDING);
}
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) {
if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) {
LOG_INF("SLC is not connected. Will get call status via AT+CLCC");
break;
}
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) {
if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) {
LOG_INF("SLC is not connected. Will get call status via AT+CLCC");
break;
}
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 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));
LOG_DBG("call setup %d", value);
if (value != BT_HFP_CALL_HELD_NONE) {
atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_CLCC_PENDING);
}
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;
}
}
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)
{
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_SIGNAL_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)
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)
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 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;
}
#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;
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 */
#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 },
#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)
{
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_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_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++;
}
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));
}
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_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)
{
return hfp_hf_send_cmd(hf, NULL, cb, "AT+CHLD=?");
}
#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */
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");
}
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?");
}
static int send_at_cind_supported(struct bt_hfp_hf *hf, at_finish_cb_t cb)
{
return hfp_hf_send_cmd(hf, cind_resp, cb, "AT+CIND=?");
}
#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)
{
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);
}
#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */
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);
}
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 */
#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);
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);
if (result != AT_RESULT_OK) {
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;
}
}
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;
}
static int slc_init_start(struct bt_hfp_hf *hf)
{
at_send_t send;
uint32_t feture_mask;
int err = -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++;
}
if (ARRAY_SIZE(slc_init_list) <= hf->cmd_init_seq) {
slc_completed(&hf->at);
}
return err;
}
int hf_slc_establish(struct bt_hfp_hf *hf)
{
int err;
LOG_DBG("");
hf->cmd_init_seq = 0;
err = slc_init_start(hf);
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 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 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 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 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;
}
#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)
{
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;
}
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;
}
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);
LOG_DBG("AT+CHUP (result %d) on %p", result, hf);
return 0;
}
int bt_hfp_hf_reject(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 (!(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) {
LOG_ERR("Fail to reject the incoming call on %p", hf);
}
return err;
}
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);
}
return err;
}
return -EINVAL;
}
int bt_hfp_hf_terminate(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_HELD) {
LOG_ERR("Held call cannot be terminated");
return -EINVAL;
}
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 bt_hfp_hf_hold_incoming(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)) {
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);
}
return 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;
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;
}
if (bt_hf->incoming_held) {
bt_hf->incoming_held(call);
}
return 0;
}
static int query_btrh_resp(struct at_client *hf_at, struct net_buf *buf)
{
int err;
err = at_parse_cmd_input(hf_at, buf, "BTRH", query_btrh_handle,
AT_CMD_TYPE_NORMAL);
if (err < 0) {
LOG_ERR("Error parsing CMD input");
hf_slc_error(hf_at);
}
return 0;
}
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);
return 0;
}
int bt_hfp_hf_query_respond_hold_status(struct bt_hfp_hf *hf)
{
int err;
LOG_DBG("");
if (!hf) {
LOG_ERR("No HF connection found");
return -ENOTCONN;
}
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);
}
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;
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;
}
if (err) {
free_call(call);
}
if (bt_hf && bt_hf->dialing) {
bt_hf->dialing(hf, err);
}
return 0;
}
int bt_hfp_hf_number_call(struct bt_hfp_hf *hf, const char *number)
{
struct bt_hfp_hf_call *call;
int err;
LOG_DBG("");
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, atd_finish, "ATD%s", number);
if (err < 0) {
LOG_ERR("Fail to start phone number call on %p", hf);
}
return err;
}
int bt_hfp_hf_memory_dial(struct bt_hfp_hf *hf, const char *location)
{
struct bt_hfp_hf_call *call;
int err;
LOG_DBG("");
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, atd_finish, "ATD>%s", location);
if (err < 0) {
LOG_ERR("Fail to last number re-Dial on %p", hf);
}
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;
struct bt_hfp_hf_call *call;
LOG_DBG("AT+BLDN (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;
}
if (err) {
free_call(call);
}
if (bt_hf && bt_hf->dialing) {
bt_hf->dialing(hf, err);
}
return 0;
}
int bt_hfp_hf_redial(struct bt_hfp_hf *hf)
{
struct bt_hfp_hf_call *call;
int err;
LOG_DBG("");
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("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)
{
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_hfp_hf *hf)
{
#if defined(CONFIG_BT_HFP_HF_CODEC_NEG)
int err;
LOG_DBG("");
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("BCS (result %d) on %p", result, hf);
return 0;
}
#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */
int bt_hfp_hf_select_codec(struct bt_hfp_hf *hf, uint8_t codec_id)
{
#if defined(CONFIG_BT_HFP_HF_CODEC_NEG)
LOG_DBG("");
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("BAC (result %d) on %p", result, hf);
return 0;
}
#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */
int bt_hfp_hf_set_codecs(struct bt_hfp_hf *hf, uint8_t codec_ids)
{
#if defined(CONFIG_BT_HFP_HF_CODEC_NEG)
char ids[sizeof(hf->hf_codec_ids)*2*8 + 1];
LOG_DBG("");
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 */
}
#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) {
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 */
}
#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);
LOG_DBG("AT+CCWA (result %d) on %p", result, hf);
return 0;
}
#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */
int bt_hfp_hf_call_waiting_notify(struct bt_hfp_hf *hf, bool enable)
{
#if defined(CONFIG_BT_HFP_HF_3WAY_CALL)
LOG_DBG("");
if (!hf) {
LOG_ERR("No HF connection found");
return -ENOTCONN;
}
if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) {
LOG_ERR("Three-way calling is unsupported by AG");
return -ENOTSUP;
}
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 */
}
#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+CHLD=0 (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_all_held(struct bt_hfp_hf *hf)
{
#if defined(CONFIG_BT_HFP_HF_3WAY_CALL)
LOG_DBG("");
if (!hf) {
LOG_ERR("No HF connection found");
return -ENOTCONN;
}
if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) {
LOG_ERR("Three-way calling is unsupported by AG");
return -ENOTSUP;
}
if (!(hf->chld_features & BIT(BT_HFP_CHLD_RELEASE_ALL))) {
LOG_ERR("Releasing all held calls is unsupported by AG");
return -ENOTSUP;
}
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 */
}
#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);
LOG_DBG("AT+CHLD=0 (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_set_udub(struct bt_hfp_hf *hf)
{
#if defined(CONFIG_BT_HFP_HF_3WAY_CALL)
LOG_DBG("");
if (!hf) {
LOG_ERR("No HF connection found");
return -ENOTCONN;
}
if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) {
LOG_ERR("Three-way calling is unsupported by AG");
return -ENOTSUP;
}
if (!(hf->chld_features & BIT(BT_HFP_CHLD_RELEASE_ALL))) {
LOG_ERR("UDUB is unsupported by AG");
return -ENOTSUP;
}
return hfp_hf_send_cmd(hf, NULL, chld_set_udub_finish, "AT+CHLD=0");
#else
return -ENOTSUP;
#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */
}
#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)
{
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) {
LOG_ERR("No HF connection found");
return -ENOTCONN;
}
if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) {
LOG_ERR("Three-way calling is unsupported by AG");
return -ENOTSUP;
}
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 */
}
#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);
LOG_DBG("AT+CHLD=2 (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_hold_active_accept_other(struct bt_hfp_hf *hf)
{
#if defined(CONFIG_BT_HFP_HF_3WAY_CALL)
LOG_DBG("");
if (!hf) {
LOG_ERR("No HF connection found");
return -ENOTCONN;
}
if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) {
LOG_ERR("Three-way calling is unsupported by AG");
return -ENOTSUP;
}
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_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("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_3WAY_CALL */
int bt_hfp_hf_join_conversation(struct bt_hfp_hf *hf)
{
#if defined(CONFIG_BT_HFP_HF_3WAY_CALL)
LOG_DBG("");
if (!hf) {
LOG_ERR("No HF connection found");
return -ENOTCONN;
}
if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) {
LOG_ERR("Three-way calling is unsupported by AG");
return -ENOTSUP;
}
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 hfp_hf_send_cmd(hf, NULL, chld_join_conversation_finish,
"AT+CHLD=3");
#else
return -ENOTSUP;
#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */
}
#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("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_3WAY_CALL */
int bt_hfp_hf_explicit_call_transfer(struct bt_hfp_hf *hf)
{
#if defined(CONFIG_BT_HFP_HF_3WAY_CALL)
LOG_DBG("");
if (!hf) {
LOG_ERR("No HF connection found");
return -ENOTCONN;
}
if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) {
LOG_ERR("Three-way calling is unsupported by AG");
return -ENOTSUP;
}
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, chld_explicit_call_transfer_finish,
"AT+CHLD=4");
#else
return -ENOTSUP;
#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */
}
#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("AT+CHLD=1<idx> (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_ECC */
int bt_hfp_hf_release_specified_call(struct bt_hfp_hf_call *call)
{
#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_3WAY_CALL)) {
LOG_ERR("Three-way calling is unsupported by AG");
return -ENOTSUP;
}
if (!(hf->ag_features & BT_HFP_AG_FEATURE_ECC)) {
LOG_ERR("Enhanced Call Control is unsupported by AG");
return -ENOTSUP;
}
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_ECC */
}
#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);
LOG_DBG("AT+CHLD=2<idx> (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_ECC */
int bt_hfp_hf_private_consultation_mode(struct bt_hfp_hf_call *call)
{
#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_3WAY_CALL)) {
LOG_ERR("Three-way calling is unsupported by AG");
return -ENOTSUP;
}
if (!(hf->ag_features & BT_HFP_AG_FEATURE_ECC)) {
LOG_ERR("Enhanced Call Control is unsupported by AG");
return -ENOTSUP;
}
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_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);
LOG_DBG("hf connected");
BT_ASSERT(hf);
hf_slc_establish(hf);
}
static void hfp_hf_disconnected(struct bt_rfcomm_dlc *dlc)
{
struct bt_hfp_hf *hf = CONTAINER_OF(dlc, struct bt_hfp_hf, rfcomm_dlc);
LOG_DBG("hf disconnected!");
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)
{
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)
{
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 struct bt_hfp_hf *hfp_hf_create(struct bt_conn *conn)
{
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);
index = (size_t)bt_conn_index(conn);
__ASSERT(index < ARRAY_SIZE(bt_hfp_hf_pool), "Index is out of bounds");
hf = &bt_hfp_hf_pool[index];
if (hf->acl) {
LOG_ERR("HF connection (%p) is established", conn);
return NULL;
}
memset(hf, 0, sizeof(*hf));
hf->acl = conn;
hf->at.buf = hf->hf_buffer;
hf->at.buf_max_len = HF_MAX_BUF_LEN;
hf->rfcomm_dlc.ops = &ops;
hf->rfcomm_dlc.mtu = BT_HFP_MAX_MTU;
/* 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);
k_work_init(&hf->work, bt_hf_work);
k_work_init_delayable(&hf->deferred_work, bt_hf_deferred_work);
for (index = 0; index < ARRAY_SIZE(hf->ind_table); index++) {
hf->ind_table[index] = -1;
}
return hf;
}
static int hfp_hf_accept(struct bt_conn *conn, struct bt_rfcomm_server *server,
struct bt_rfcomm_dlc **dlc)
{
struct bt_hfp_hf *hf;
hf = hfp_hf_create(conn);
if (!hf) {
return -ECONNREFUSED;
}
*dlc = &hf->rfcomm_dlc;
return 0;
}
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(hf, chan->sco);
}
}
static void hfp_hf_sco_disconnected(struct bt_sco_chan *chan, uint8_t reason)
{
if ((bt_hf != NULL) && (bt_hf->sco_disconnected)) {
bt_hf->sco_disconnected(chan->sco, reason);
}
}
static int bt_hfp_hf_sco_accept(const struct bt_sco_accept_info *info,
struct bt_sco_chan **chan)
{
static const 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);
index = (size_t)bt_conn_index(info->acl);
__ASSERT(index < ARRAY_SIZE(bt_hfp_hf_pool), "Index is out of bounds");
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;
}
hf->chan.ops = &ops;
*chan = &hf->chan;
return 0;
}
static void hfp_hf_init(void)
{
static struct bt_rfcomm_server chan = {
.channel = BT_RFCOMM_CHAN_HFP_HF,
.accept = hfp_hf_accept,
};
bt_rfcomm_server_register(&chan);
static struct bt_sco_server sco_server = {
.sec_level = BT_SECURITY_L0,
.accept = bt_hfp_hf_sco_accept,
};
bt_sco_server_register(&sco_server);
bt_sdp_register_service(&hfp_rec);
}
int bt_hfp_hf_register(struct bt_hfp_hf_cb *cb)
{
if (!cb) {
return -EINVAL;
}
if (bt_hf) {
return -EALREADY;
}
bt_hf = cb;
hfp_hf_init();
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);
}