This adds initial support for Hearing Access Service client. The client performs GATT discovery to find and read HAS related characteristics and subscribe for characteristic value notifications/indications. Signed-off-by: Mariusz Skamra <mariusz.skamra@codecoup.pl>
652 lines
16 KiB
C
652 lines
16 KiB
C
/*
|
|
* Copyright (c) 2022 Codecoup
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr.h>
|
|
|
|
#include <bluetooth/bluetooth.h>
|
|
#include <bluetooth/gatt.h>
|
|
#include <bluetooth/audio/has.h>
|
|
#include <sys/check.h>
|
|
|
|
#include "has_internal.h"
|
|
|
|
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HAS_CLIENT)
|
|
#define LOG_MODULE_NAME bt_has_client
|
|
#include "../common/log.h"
|
|
|
|
#define HAS_INST(_has) CONTAINER_OF(_has, struct has_inst, has)
|
|
#define HANDLE_IS_VALID(handle) ((handle) != 0x0000)
|
|
|
|
enum {
|
|
HAS_DISCOVER_IN_PROGRESS,
|
|
|
|
HAS_NUM_FLAGS, /* keep as last */
|
|
};
|
|
|
|
static struct has_inst {
|
|
/** Common profile reference object */
|
|
struct bt_has has;
|
|
|
|
/** Profile connection reference */
|
|
struct bt_conn *conn;
|
|
|
|
/** Internal flags */
|
|
ATOMIC_DEFINE(flags, HAS_NUM_FLAGS);
|
|
|
|
/* GATT procedure parameters */
|
|
struct {
|
|
struct bt_uuid_16 uuid;
|
|
union {
|
|
struct bt_gatt_read_params read;
|
|
struct bt_gatt_discover_params discover;
|
|
};
|
|
} params;
|
|
|
|
struct bt_gatt_subscribe_params features_subscription;
|
|
struct bt_gatt_subscribe_params control_point_subscription;
|
|
struct bt_gatt_subscribe_params active_index_subscription;
|
|
} has_insts[CONFIG_BT_MAX_CONN];
|
|
|
|
static const struct bt_has_client_cb *client_cb;
|
|
|
|
static struct has_inst *inst_by_conn(struct bt_conn *conn)
|
|
{
|
|
struct has_inst *inst = &has_insts[bt_conn_index(conn)];
|
|
|
|
if (inst->conn == conn) {
|
|
return inst;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void inst_cleanup(struct has_inst *inst)
|
|
{
|
|
bt_conn_unref(inst->conn);
|
|
|
|
(void)memset(inst, 0, sizeof(*inst));
|
|
}
|
|
|
|
static enum bt_has_capabilities get_capabilities(const struct has_inst *inst)
|
|
{
|
|
enum bt_has_capabilities caps = 0;
|
|
|
|
/* The Control Point support is optional, as the server might have no presets support */
|
|
if (HANDLE_IS_VALID(inst->control_point_subscription.value_handle)) {
|
|
caps |= BT_HAS_PRESET_SUPPORT;
|
|
}
|
|
|
|
return caps;
|
|
}
|
|
|
|
static uint8_t control_point_notify_cb(struct bt_conn *conn,
|
|
struct bt_gatt_subscribe_params *params, const void *data,
|
|
uint16_t len)
|
|
{
|
|
/* TODO: Handle Control Point PDU */
|
|
|
|
return BT_GATT_ITER_CONTINUE;
|
|
}
|
|
|
|
static void discover_complete(struct has_inst *inst)
|
|
{
|
|
BT_DBG("conn %p", (void *)inst->conn);
|
|
|
|
atomic_clear_bit(inst->flags, HAS_DISCOVER_IN_PROGRESS);
|
|
|
|
client_cb->discover(inst->conn, 0, &inst->has,
|
|
inst->has.features & BT_HAS_FEAT_HEARING_AID_TYPE_MASK,
|
|
get_capabilities(inst));
|
|
|
|
/* If Active Preset Index supported, notify it's value */
|
|
if (client_cb->preset_switch &&
|
|
HANDLE_IS_VALID(inst->active_index_subscription.value_handle)) {
|
|
client_cb->preset_switch(&inst->has, inst->has.active_index);
|
|
}
|
|
}
|
|
|
|
static void discover_failed(struct bt_conn *conn, int err)
|
|
{
|
|
BT_DBG("conn %p", (void *)conn);
|
|
|
|
client_cb->discover(conn, err, NULL, 0, 0);
|
|
}
|
|
|
|
static uint8_t active_index_update(struct has_inst *inst, const void *data, uint16_t len)
|
|
{
|
|
struct net_buf_simple buf;
|
|
const uint8_t prev = inst->has.active_index;
|
|
|
|
net_buf_simple_init_with_data(&buf, (void *)data, len);
|
|
|
|
inst->has.active_index = net_buf_simple_pull_u8(&buf);
|
|
|
|
BT_DBG("conn %p index 0x%02x", (void *)inst->conn, inst->has.active_index);
|
|
|
|
return prev;
|
|
}
|
|
|
|
static uint8_t active_preset_notify_cb(struct bt_conn *conn,
|
|
struct bt_gatt_subscribe_params *params, const void *data,
|
|
uint16_t len)
|
|
{
|
|
struct has_inst *inst;
|
|
uint8_t prev;
|
|
|
|
BT_DBG("conn %p params %p data %p len %u", (void *)conn, params, data, len);
|
|
|
|
if (!conn) {
|
|
/* Unpaired, stop receiving notifications from device */
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
if (!data) {
|
|
/* Unsubscribed */
|
|
params->value_handle = 0u;
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
inst = inst_by_conn(conn);
|
|
if (!inst) {
|
|
/* Ignore notification from unknown instance */
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
if (len == 0) {
|
|
/* Ignore empty notification */
|
|
return BT_GATT_ITER_CONTINUE;
|
|
}
|
|
|
|
prev = active_index_update(inst, data, len);
|
|
|
|
if (atomic_test_bit(inst->flags, HAS_DISCOVER_IN_PROGRESS)) {
|
|
/* Got notification during discovery process, postpone the active_index callback
|
|
* until discovery is complete.
|
|
*/
|
|
return BT_GATT_ITER_CONTINUE;
|
|
}
|
|
|
|
if (client_cb && client_cb->preset_switch && inst->has.active_index != prev) {
|
|
client_cb->preset_switch(&inst->has, inst->has.active_index);
|
|
}
|
|
|
|
return BT_GATT_ITER_CONTINUE;
|
|
}
|
|
|
|
static void active_index_subscribe_cb(struct bt_conn *conn, uint8_t att_err,
|
|
struct bt_gatt_write_params *params)
|
|
{
|
|
struct has_inst *inst = inst_by_conn(conn);
|
|
|
|
__ASSERT(inst, "no instance for conn %p", (void *)conn);
|
|
|
|
BT_DBG("conn %p att_err 0x%02x params %p", (void *)inst->conn, att_err, params);
|
|
|
|
if (att_err != BT_ATT_ERR_SUCCESS) {
|
|
/* Cleanup instance so that it can be reused */
|
|
inst_cleanup(inst);
|
|
|
|
discover_failed(conn, att_err);
|
|
} else {
|
|
discover_complete(inst);
|
|
}
|
|
}
|
|
|
|
static int active_index_subscribe(struct has_inst *inst, uint16_t value_handle)
|
|
{
|
|
BT_DBG("conn %p handle 0x%04x", (void *)inst->conn, value_handle);
|
|
|
|
inst->active_index_subscription.notify = active_preset_notify_cb;
|
|
inst->active_index_subscription.write = active_index_subscribe_cb;
|
|
inst->active_index_subscription.value_handle = value_handle;
|
|
inst->active_index_subscription.ccc_handle = 0x0000;
|
|
inst->active_index_subscription.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
|
|
inst->active_index_subscription.disc_params = &inst->params.discover;
|
|
inst->active_index_subscription.value = BT_GATT_CCC_NOTIFY;
|
|
atomic_set_bit(inst->active_index_subscription.flags, BT_GATT_SUBSCRIBE_FLAG_VOLATILE);
|
|
|
|
return bt_gatt_subscribe(inst->conn, &inst->active_index_subscription);
|
|
}
|
|
|
|
static uint8_t active_index_read_cb(struct bt_conn *conn, uint8_t att_err,
|
|
struct bt_gatt_read_params *params, const void *data,
|
|
uint16_t len)
|
|
{
|
|
struct has_inst *inst = inst_by_conn(conn);
|
|
int err = att_err;
|
|
|
|
__ASSERT(inst, "no instance for conn %p", (void *)conn);
|
|
|
|
BT_DBG("conn %p att_err 0x%02x params %p data %p len %u", (void *)conn, att_err, params,
|
|
data, len);
|
|
|
|
if (att_err != BT_ATT_ERR_SUCCESS || len == 0) {
|
|
goto fail;
|
|
}
|
|
|
|
active_index_update(inst, data, len);
|
|
|
|
err = active_index_subscribe(inst, params->by_uuid.start_handle);
|
|
if (err) {
|
|
BT_ERR("Subscribe failed (err %d)", err);
|
|
goto fail;
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
|
|
fail:
|
|
/* Cleanup instance so that it can be reused */
|
|
inst_cleanup(inst);
|
|
|
|
discover_failed(conn, err);
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
static int active_index_read(struct has_inst *inst)
|
|
{
|
|
BT_DBG("conn %p", (void *)inst->conn);
|
|
|
|
(void)memset(&inst->params.read, 0, sizeof(inst->params.read));
|
|
|
|
(void)memcpy(&inst->params.uuid, BT_UUID_HAS_ACTIVE_PRESET_INDEX,
|
|
sizeof(inst->params.uuid));
|
|
inst->params.read.func = active_index_read_cb;
|
|
inst->params.read.handle_count = 0u;
|
|
inst->params.read.by_uuid.uuid = &inst->params.uuid.uuid;
|
|
inst->params.read.by_uuid.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
|
|
inst->params.read.by_uuid.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
|
|
|
|
return bt_gatt_read(inst->conn, &inst->params.read);
|
|
}
|
|
|
|
static void control_point_subscribe_cb(struct bt_conn *conn, uint8_t att_err,
|
|
struct bt_gatt_write_params *write)
|
|
{
|
|
struct has_inst *inst = inst_by_conn(conn);
|
|
int err = att_err;
|
|
|
|
__ASSERT(inst, "no instance for conn %p", (void *)conn);
|
|
|
|
BT_DBG("conn %p att_err 0x%02x", (void *)inst->conn, att_err);
|
|
|
|
if (att_err != BT_ATT_ERR_SUCCESS) {
|
|
goto fail;
|
|
}
|
|
|
|
err = active_index_read(inst);
|
|
if (err) {
|
|
BT_ERR("Active Preset Index read failed (err %d)", err);
|
|
goto fail;
|
|
}
|
|
|
|
return;
|
|
|
|
fail:
|
|
/* Cleanup instance so that it can be reused */
|
|
inst_cleanup(inst);
|
|
|
|
discover_failed(conn, err);
|
|
}
|
|
|
|
static int control_point_subscribe(struct has_inst *inst, uint16_t value_handle,
|
|
uint8_t properties)
|
|
{
|
|
BT_DBG("conn %p handle 0x%04x", (void *)inst->conn, value_handle);
|
|
|
|
inst->control_point_subscription.notify = control_point_notify_cb;
|
|
inst->control_point_subscription.write = control_point_subscribe_cb;
|
|
inst->control_point_subscription.value_handle = value_handle;
|
|
inst->control_point_subscription.ccc_handle = 0x0000;
|
|
inst->control_point_subscription.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
|
|
inst->control_point_subscription.disc_params = &inst->params.discover;
|
|
atomic_set_bit(inst->control_point_subscription.flags, BT_GATT_SUBSCRIBE_FLAG_VOLATILE);
|
|
|
|
if (IS_ENABLED(CONFIG_BT_EATT) && properties & BT_GATT_CHRC_NOTIFY) {
|
|
inst->control_point_subscription.value = BT_GATT_CCC_INDICATE | BT_GATT_CCC_NOTIFY;
|
|
} else {
|
|
inst->control_point_subscription.value = BT_GATT_CCC_INDICATE;
|
|
}
|
|
|
|
return bt_gatt_subscribe(inst->conn, &inst->control_point_subscription);
|
|
}
|
|
|
|
static uint8_t control_point_discover_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
|
struct bt_gatt_discover_params *params)
|
|
{
|
|
struct has_inst *inst = inst_by_conn(conn);
|
|
const struct bt_gatt_chrc *chrc;
|
|
int err;
|
|
|
|
__ASSERT(inst, "no instance for conn %p", (void *)conn);
|
|
|
|
BT_DBG("conn %p attr %p params %p", (void *)inst->conn, attr, params);
|
|
|
|
if (!attr) {
|
|
BT_INFO("Control Point not found");
|
|
discover_complete(inst);
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
chrc = attr->user_data;
|
|
|
|
err = control_point_subscribe(inst, chrc->value_handle, chrc->properties);
|
|
if (err) {
|
|
BT_ERR("Subscribe failed (err %d)", err);
|
|
|
|
/* Cleanup instance so that it can be reused */
|
|
inst_cleanup(inst);
|
|
|
|
discover_failed(conn, err);
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
static int control_point_discover(struct has_inst *inst)
|
|
{
|
|
BT_DBG("conn %p", (void *)inst->conn);
|
|
|
|
(void)memset(&inst->params.discover, 0, sizeof(inst->params.discover));
|
|
|
|
(void)memcpy(&inst->params.uuid, BT_UUID_HAS_PRESET_CONTROL_POINT,
|
|
sizeof(inst->params.uuid));
|
|
inst->params.discover.uuid = &inst->params.uuid.uuid;
|
|
inst->params.discover.func = control_point_discover_cb;
|
|
inst->params.discover.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
|
|
inst->params.discover.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
|
|
inst->params.discover.type = BT_GATT_DISCOVER_CHARACTERISTIC;
|
|
|
|
return bt_gatt_discover(inst->conn, &inst->params.discover);
|
|
}
|
|
|
|
static void features_update(struct has_inst *inst, const void *data, uint16_t len)
|
|
{
|
|
struct net_buf_simple buf;
|
|
|
|
net_buf_simple_init_with_data(&buf, (void *)data, len);
|
|
|
|
inst->has.features = net_buf_simple_pull_u8(&buf);
|
|
|
|
BT_DBG("conn %p features 0x%02x", (void *)inst->conn, inst->has.features);
|
|
}
|
|
|
|
static uint8_t features_read_cb(struct bt_conn *conn, uint8_t att_err,
|
|
struct bt_gatt_read_params *params, const void *data, uint16_t len)
|
|
{
|
|
struct has_inst *inst = inst_by_conn(conn);
|
|
int err = att_err;
|
|
|
|
__ASSERT(inst, "no instance for conn %p", (void *)conn);
|
|
|
|
BT_DBG("conn %p att_err 0x%02x params %p data %p len %u", (void *)conn, att_err, params,
|
|
data, len);
|
|
|
|
if (att_err != BT_ATT_ERR_SUCCESS || len == 0) {
|
|
goto fail;
|
|
}
|
|
|
|
features_update(inst, data, len);
|
|
|
|
if (!client_cb->preset_switch) {
|
|
/* Complete the discovery if client is not interested in active preset changes */
|
|
discover_complete(inst);
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
err = control_point_discover(inst);
|
|
if (err) {
|
|
BT_ERR("Control Point discover failed (err %d)", err);
|
|
goto fail;
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
|
|
fail:
|
|
/* Cleanup instance so that it can be reused */
|
|
inst_cleanup(inst);
|
|
|
|
discover_failed(conn, err);
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
static int features_read(struct has_inst *inst, uint16_t value_handle)
|
|
{
|
|
BT_DBG("conn %p handle 0x%04x", (void *)inst->conn, value_handle);
|
|
|
|
inst->params.read.func = features_read_cb;
|
|
inst->params.read.handle_count = 1u;
|
|
inst->params.read.single.handle = value_handle;
|
|
inst->params.read.single.offset = 0u;
|
|
|
|
return bt_gatt_read(inst->conn, &inst->params.read);
|
|
}
|
|
|
|
static void features_subscribe_cb(struct bt_conn *conn, uint8_t att_err,
|
|
struct bt_gatt_write_params *params)
|
|
{
|
|
struct has_inst *inst = inst_by_conn(conn);
|
|
int err = att_err;
|
|
|
|
__ASSERT(inst, "no instance for conn %p", (void *)conn);
|
|
|
|
BT_DBG("conn %p att_err 0x%02x params %p", (void *)inst->conn, att_err, params);
|
|
|
|
if (att_err != BT_ATT_ERR_SUCCESS) {
|
|
goto fail;
|
|
}
|
|
|
|
err = features_read(inst, inst->features_subscription.value_handle);
|
|
if (err) {
|
|
BT_ERR("Read failed (err %d)", err);
|
|
goto fail;
|
|
}
|
|
|
|
return;
|
|
|
|
fail:
|
|
/* Cleanup instance so that it can be reused */
|
|
inst_cleanup(inst);
|
|
|
|
discover_failed(conn, err);
|
|
}
|
|
|
|
static uint8_t features_notify_cb(struct bt_conn *conn, struct bt_gatt_subscribe_params *params,
|
|
const void *data, uint16_t len)
|
|
{
|
|
struct has_inst *inst;
|
|
|
|
BT_DBG("conn %p params %p data %p len %u", (void *)conn, params, data, len);
|
|
|
|
if (!conn) {
|
|
/* Unpaired, stop receiving notifications from device */
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
if (!data) {
|
|
/* Unsubscribed */
|
|
params->value_handle = 0u;
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
inst = inst_by_conn(conn);
|
|
if (!inst) {
|
|
/* Ignore notification from unknown instance */
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
if (len == 0) {
|
|
/* Ignore empty notification */
|
|
return BT_GATT_ITER_CONTINUE;
|
|
}
|
|
|
|
features_update(inst, data, len);
|
|
|
|
return BT_GATT_ITER_CONTINUE;
|
|
}
|
|
|
|
static int features_subscribe(struct has_inst *inst, uint16_t value_handle)
|
|
{
|
|
BT_DBG("conn %p handle 0x%04x", (void *)inst->conn, value_handle);
|
|
|
|
inst->features_subscription.notify = features_notify_cb;
|
|
inst->features_subscription.write = features_subscribe_cb;
|
|
inst->features_subscription.value_handle = value_handle;
|
|
inst->features_subscription.ccc_handle = 0x0000;
|
|
inst->features_subscription.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
|
|
inst->features_subscription.disc_params = &inst->params.discover;
|
|
inst->features_subscription.value = BT_GATT_CCC_NOTIFY;
|
|
atomic_set_bit(inst->features_subscription.flags, BT_GATT_SUBSCRIBE_FLAG_VOLATILE);
|
|
|
|
return bt_gatt_subscribe(inst->conn, &inst->features_subscription);
|
|
}
|
|
|
|
static uint8_t features_discover_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
|
struct bt_gatt_discover_params *params)
|
|
{
|
|
struct has_inst *inst = inst_by_conn(conn);
|
|
const struct bt_gatt_chrc *chrc;
|
|
int err;
|
|
|
|
__ASSERT(inst, "no instance for conn %p", (void *)conn);
|
|
|
|
BT_DBG("conn %p attr %p params %p", (void *)conn, attr, params);
|
|
|
|
if (!attr) {
|
|
err = -ENOENT;
|
|
goto fail;
|
|
}
|
|
|
|
chrc = attr->user_data;
|
|
|
|
/* Subscribe first if notifications are supported, otherwise read the features */
|
|
if (chrc->properties & BT_GATT_CHRC_NOTIFY) {
|
|
err = features_subscribe(inst, chrc->value_handle);
|
|
if (err) {
|
|
BT_ERR("Subscribe failed (err %d)", err);
|
|
goto fail;
|
|
}
|
|
} else {
|
|
err = features_read(inst, chrc->value_handle);
|
|
if (err) {
|
|
BT_ERR("Read failed (err %d)", err);
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
|
|
fail:
|
|
/* Cleanup instance so that it can be reused */
|
|
inst_cleanup(inst);
|
|
|
|
discover_failed(conn, err);
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
static int features_discover(struct has_inst *inst)
|
|
{
|
|
BT_DBG("conn %p", (void *)inst->conn);
|
|
|
|
(void)memset(&inst->params.discover, 0, sizeof(inst->params.discover));
|
|
|
|
(void)memcpy(&inst->params.uuid, BT_UUID_HAS_HEARING_AID_FEATURES,
|
|
sizeof(inst->params.uuid));
|
|
inst->params.discover.uuid = &inst->params.uuid.uuid;
|
|
inst->params.discover.func = features_discover_cb;
|
|
inst->params.discover.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
|
|
inst->params.discover.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
|
|
inst->params.discover.type = BT_GATT_DISCOVER_CHARACTERISTIC;
|
|
|
|
return bt_gatt_discover(inst->conn, &inst->params.discover);
|
|
}
|
|
|
|
int bt_has_client_cb_register(const struct bt_has_client_cb *cb)
|
|
{
|
|
CHECKIF(!cb) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
CHECKIF(client_cb) {
|
|
return -EALREADY;
|
|
}
|
|
|
|
client_cb = cb;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Hearing Access Service discovery
|
|
*
|
|
* This will initiate a discover procedure. The procedure will do the following sequence:
|
|
* 1) HAS related characteristic discovery
|
|
* 2) CCC subscription
|
|
* 3) Hearing Aid Features and Active Preset Index characteristic read
|
|
* 5) When everything above have been completed, the callback is called
|
|
*/
|
|
int bt_has_client_discover(struct bt_conn *conn)
|
|
{
|
|
struct has_inst *inst;
|
|
int err;
|
|
|
|
BT_DBG("conn %p", (void *)conn);
|
|
|
|
CHECKIF(!conn || !client_cb || !client_cb->discover) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
inst = &has_insts[bt_conn_index(conn)];
|
|
|
|
if (atomic_test_and_set_bit(inst->flags, HAS_DISCOVER_IN_PROGRESS)) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (inst->conn) {
|
|
return -EALREADY;
|
|
}
|
|
|
|
inst->conn = bt_conn_ref(conn);
|
|
|
|
err = features_discover(inst);
|
|
if (err) {
|
|
atomic_clear_bit(inst->flags, HAS_DISCOVER_IN_PROGRESS);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int bt_has_client_conn_get(const struct bt_has *has, struct bt_conn **conn)
|
|
{
|
|
struct has_inst *inst = HAS_INST(has);
|
|
|
|
*conn = bt_conn_ref(inst->conn);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void disconnected(struct bt_conn *conn, uint8_t reason)
|
|
{
|
|
struct has_inst *inst = inst_by_conn(conn);
|
|
|
|
if (!inst) {
|
|
return;
|
|
}
|
|
|
|
if (atomic_test_bit(inst->flags, HAS_DISCOVER_IN_PROGRESS)) {
|
|
discover_failed(conn, -ECONNABORTED);
|
|
}
|
|
|
|
inst_cleanup(inst);
|
|
}
|
|
|
|
BT_CONN_CB_DEFINE(conn_cb) = {
|
|
.disconnected = disconnected,
|
|
};
|