Bluetooth: has: Add initial Hearing Access Service client

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>
This commit is contained in:
Mariusz Skamra 2022-03-24 14:48:24 +01:00 committed by Carles Cufí
commit db59647c0a
5 changed files with 786 additions and 0 deletions

View file

@ -0,0 +1,118 @@
/*
* Copyright (c) 2022 Codecoup
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_HAS_H_
#define ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_HAS_H_
/**
* @brief Hearing Access Service (HAS)
*
* @defgroup bt_has Hearing Access Service (HAS)
*
* @ingroup bluetooth
* @{
*
* The Hearing Access Service is used to identify a hearing aid and optionally
* to control hearing aid presets.
*
* [Experimental] Users should note that the APIs can change as a part of
* ongoing development.
*/
#include <bluetooth/conn.h>
#ifdef __cplusplus
extern "C" {
#endif
/** @brief Opaque Hearing Access Service object. */
struct bt_has;
/** Hearing Aid device type */
enum bt_has_hearing_aid_type {
BT_HAS_HEARING_AID_TYPE_BINAURAL,
BT_HAS_HEARING_AID_TYPE_MONAURAL,
BT_HAS_HEARING_AID_TYPE_BANDED,
};
/** Hearing Aid device capablilities */
enum bt_has_capabilities {
BT_HAS_PRESET_SUPPORT = BIT(0),
};
/** @brief Hearing Access Service Client callback structure. */
struct bt_has_client_cb {
/**
* @brief Callback function for bt_has_discover.
*
* This callback is called when discovery procedure is complete.
*
* @param conn Bluetooth connection object.
* @param err 0 on success, ATT error or negative errno otherwise.
* @param has Pointer to the Hearing Access Service object or NULL on errors.
* @param type Hearing Aid type.
* @param caps Hearing Aid capabilities.
*/
void (*discover)(struct bt_conn *conn, int err, struct bt_has *has,
enum bt_has_hearing_aid_type type, enum bt_has_capabilities caps);
/**
* @brief Callback function for Hearing Access Service active preset changes.
*
* Optional callback called when the value is changed by the remote server.
* The callback must be set to receive active preset changes and enable support
* for switching presets. If the callback is not set, the Active Index and
* Control Point characteristics will not be discovered by @ref bt_has_client_discover.
*
* @param has Pointer to the Hearing Access Service object.
* @param index Active preset index.
*/
void (*preset_switch)(struct bt_has *has, uint8_t index);
};
/** @brief Registers the callbacks used by the Hearing Access Service client.
*
* @param cb The callback structure.
*
* @return 0 in case of success or negative value in case of error.
*/
int bt_has_client_cb_register(const struct bt_has_client_cb *cb);
/**
* @brief Discover Hearing Access Service on a remote device.
*
* Client method to find a Hearing Access Service on a server identified by @p conn.
* The @ref bt_has_client_cb.discover callback will be called when the discovery procedure
* is complete to provide user a @ref bt_has object.
*
* @param conn Bluetooth connection object.
*
* @return 0 if success, errno on failure.
*/
int bt_has_client_discover(struct bt_conn *conn);
/**
* @brief Get the Bluetooth connection object of the service object.
*
* The caller gets a new reference to the connection object which must be
* released with bt_conn_unref() once done using the object.
*
* @param[in] has Pointer to the Hearing Access Service object.
* @param[out] conn Connection object.
*
* @return 0 in case of success or negative value in case of error.
*/
int bt_has_client_conn_get(const struct bt_has *has, struct bt_conn **conn);
#ifdef __cplusplus
}
#endif
/**
* @}
*/
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_HAS_H_ */

View file

@ -55,3 +55,4 @@ zephyr_library_sources_ifdef(CONFIG_BT_AUDIO_BROADCAST_SINK broadcast_sink.c)
zephyr_library_sources_ifdef(CONFIG_BT_BASS bass.c) zephyr_library_sources_ifdef(CONFIG_BT_BASS bass.c)
zephyr_library_sources_ifdef(CONFIG_BT_BASS_CLIENT bass_client.c) zephyr_library_sources_ifdef(CONFIG_BT_BASS_CLIENT bass_client.c)
zephyr_library_sources_ifdef(CONFIG_BT_HAS has.c) zephyr_library_sources_ifdef(CONFIG_BT_HAS has.c)
zephyr_library_sources_ifdef(CONFIG_BT_HAS_CLIENT has_client.c)

View file

@ -96,3 +96,18 @@ config BT_DEBUG_HAS
This option enables enables Hearing Access Service debug logs. This option enables enables Hearing Access Service debug logs.
endif # BT_HAS endif # BT_HAS
config BT_HAS_CLIENT
bool "Hearing Access Service Client support [EXPERIMENTAL]"
select EXPERIMENTAL
select BT_GATT_CLIENT
select BT_GATT_AUTO_DISCOVER_CCC
select BT_GATT_AUTO_UPDATE_MTU
help
This option enables support for Hearing Access Service Client.
config BT_DEBUG_HAS_CLIENT
bool "Hearing Access Service Client debug"
depends on BT_HAS_CLIENT
help
This option enables enables Hearing Access Service Client debug logs.

View file

@ -0,0 +1,651 @@
/*
* 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,
};

View file

@ -33,6 +33,7 @@ config BT_L2CAP_TX_MTU
default 69 if BT_MESH_GATT default 69 if BT_MESH_GATT
default 65 if BT_SMP default 65 if BT_SMP
default 64 if BT_AUDIO_UNICAST_SERVER || BT_AUDIO_UNICAST_CLIENT || BT_AUDIO_BROADCAST_SINK || BT_BASS || BT_BASS_CLIENT default 64 if BT_AUDIO_UNICAST_SERVER || BT_AUDIO_UNICAST_CLIENT || BT_AUDIO_BROADCAST_SINK || BT_BASS || BT_BASS_CLIENT
default 49 if BT_HAS || BT_HAS_CLIENT
default 23 default 23
range 65 2000 if BT_SMP range 65 2000 if BT_SMP
range 23 2000 range 23 2000