Move the network buffer header file from zephyr/net/buf.h to zephyr/net_buf.h as the implementation now lives outside of the networking subsystem. Add (deprecated) zephyr/net/buf.h header to maintain compatibility with old file path. Signed-off-by: Henrik Brix Andersen <henrik@brixandersen.dk>
1821 lines
48 KiB
C
1821 lines
48 KiB
C
/*
|
|
* Copyright (c) 2022 Codecoup
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <zephyr/autoconf.h>
|
|
#include <zephyr/bluetooth/audio/audio.h>
|
|
#include <zephyr/bluetooth/audio/pacs.h>
|
|
#include <zephyr/bluetooth/audio/has.h>
|
|
#include <zephyr/bluetooth/addr.h>
|
|
#include <zephyr/bluetooth/att.h>
|
|
#include <zephyr/bluetooth/bluetooth.h>
|
|
#include <zephyr/bluetooth/conn.h>
|
|
#include <zephyr/bluetooth/gatt.h>
|
|
#include <zephyr/bluetooth/uuid.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/net_buf.h>
|
|
#include <zephyr/settings/settings.h>
|
|
#include <zephyr/sys/__assert.h>
|
|
#include <zephyr/sys/atomic.h>
|
|
#include <zephyr/sys/check.h>
|
|
#include <zephyr/sys/slist.h>
|
|
#include <zephyr/sys/util.h>
|
|
#include <zephyr/sys/util_macro.h>
|
|
#include <zephyr/sys_clock.h>
|
|
#include <zephyr/toolchain.h>
|
|
|
|
#include "../bluetooth/host/hci_core.h"
|
|
#include "../bluetooth/host/settings.h"
|
|
|
|
#include "audio_internal.h"
|
|
#include "common/bt_str.h"
|
|
#include "has_internal.h"
|
|
|
|
LOG_MODULE_REGISTER(bt_has, CONFIG_BT_HAS_LOG_LEVEL);
|
|
|
|
/* The service allows operations with paired devices only.
|
|
* The number of clients is set to maximum number of simultaneous connections to paired devices.
|
|
*/
|
|
#define MAX_INSTS MIN(CONFIG_BT_MAX_CONN, CONFIG_BT_MAX_PAIRED)
|
|
|
|
#define BITS_CHANGED(_new_value, _old_value) ((_new_value) ^ (_old_value))
|
|
#define FEATURE_DEVICE_TYPE_UNCHANGED(_new_value) \
|
|
!BITS_CHANGED(_new_value, (has.features & BT_HAS_FEAT_HEARING_AID_TYPE_MASK))
|
|
#define FEATURE_SYNC_SUPPORT_UNCHANGED(_new_value) \
|
|
!BITS_CHANGED(_new_value, ((has.features & BT_HAS_FEAT_PRESET_SYNC_SUPP) != 0 ? 1 : 0))
|
|
#define FEATURE_IND_PRESETS_UNCHANGED(_new_value) \
|
|
!BITS_CHANGED(_new_value, ((has.features & BT_HAS_FEAT_INDEPENDENT_PRESETS) != 0 ? 1 : 0))
|
|
#define BONDED_CLIENT_INIT_FLAGS \
|
|
(BIT(FLAG_ACTIVE_INDEX_CHANGED) | BIT(FLAG_NOTIFY_PRESET_LIST) | BIT(FLAG_FEATURES_CHANGED))
|
|
|
|
static struct bt_has has;
|
|
|
|
#if defined(CONFIG_BT_HAS_ACTIVE_PRESET_INDEX)
|
|
static void active_preset_index_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
|
|
{
|
|
LOG_DBG("attr %p value 0x%04x", attr, value);
|
|
}
|
|
#endif /* CONFIG_BT_HAS_ACTIVE_PRESET_INDEX */
|
|
|
|
#if defined(CONFIG_BT_HAS_PRESET_SUPPORT)
|
|
struct has_client;
|
|
|
|
static int read_preset_response(struct has_client *client);
|
|
static int preset_list_changed(struct has_client *client);
|
|
static int preset_list_changed_generic_update_tail(struct has_client *client);
|
|
static int preset_list_changed_record_deleted_last(struct has_client *client);
|
|
static ssize_t write_control_point(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
|
const void *data, uint16_t len, uint16_t offset, uint8_t flags);
|
|
|
|
static void preset_cp_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
|
|
{
|
|
LOG_DBG("attr %p value 0x%04x", attr, value);
|
|
}
|
|
|
|
static ssize_t read_active_preset_index(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
|
void *buf, uint16_t len, uint16_t offset)
|
|
{
|
|
uint8_t active_index;
|
|
|
|
LOG_DBG("conn %p attr %p offset %d", (void *)conn, attr, offset);
|
|
|
|
if (offset > sizeof(active_index)) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
|
|
}
|
|
|
|
active_index = bt_has_preset_active_get();
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset, &active_index, sizeof(active_index));
|
|
}
|
|
#endif /* CONFIG_BT_HAS_PRESET_SUPPORT */
|
|
|
|
#if defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE)
|
|
static void features_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
|
|
{
|
|
LOG_DBG("attr %p value 0x%04x", attr, value);
|
|
}
|
|
#endif /* CONFIG_BT_HAS_FEATURES_NOTIFIABLE */
|
|
|
|
static ssize_t read_features(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
|
|
uint16_t len, uint16_t offset)
|
|
{
|
|
LOG_DBG("conn %p attr %p offset %d", (void *)conn, attr, offset);
|
|
|
|
if (offset > sizeof(has.features)) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
|
|
}
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset, &has.features,
|
|
sizeof(has.features));
|
|
}
|
|
|
|
#if defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE)
|
|
#define BT_HAS_CHR_FEATURES \
|
|
BT_AUDIO_CHRC(BT_UUID_HAS_HEARING_AID_FEATURES, \
|
|
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \
|
|
BT_GATT_PERM_READ_ENCRYPT, \
|
|
read_features, NULL, NULL), \
|
|
BT_AUDIO_CCC(features_cfg_changed),
|
|
#else
|
|
#define BT_HAS_CHR_FEATURES \
|
|
BT_AUDIO_CHRC(BT_UUID_HAS_HEARING_AID_FEATURES, \
|
|
BT_GATT_CHRC_READ, \
|
|
BT_GATT_PERM_READ_ENCRYPT, \
|
|
read_features, NULL, NULL),
|
|
#endif /* CONFIG_BT_HAS_FEATURES_NOTIFIABLE */
|
|
|
|
#if defined(CONFIG_BT_HAS_PRESET_SUPPORT)
|
|
#if defined(CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE)
|
|
#define BT_HAS_CHR_PRESET_CONTROL_POINT \
|
|
BT_AUDIO_CHRC(BT_UUID_HAS_PRESET_CONTROL_POINT, \
|
|
BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE | BT_GATT_CHRC_NOTIFY, \
|
|
BT_GATT_PERM_WRITE_ENCRYPT, \
|
|
NULL, write_control_point, NULL), \
|
|
BT_AUDIO_CCC(preset_cp_cfg_changed),
|
|
#else
|
|
#define BT_HAS_CHR_PRESET_CONTROL_POINT \
|
|
BT_AUDIO_CHRC(BT_UUID_HAS_PRESET_CONTROL_POINT, \
|
|
BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE, \
|
|
BT_GATT_PERM_WRITE_ENCRYPT, \
|
|
NULL, write_control_point, NULL), \
|
|
BT_AUDIO_CCC(preset_cp_cfg_changed),
|
|
#endif /* CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE */
|
|
#else
|
|
#define BT_HAS_CHR_PRESET_CONTROL_POINT
|
|
#endif /* CONFIG_BT_HAS_PRESET_SUPPORT */
|
|
|
|
#if defined(CONFIG_BT_HAS_ACTIVE_PRESET_INDEX)
|
|
#define BT_HAS_CHR_ACTIVE_PRESET_INDEX \
|
|
BT_AUDIO_CHRC(BT_UUID_HAS_ACTIVE_PRESET_INDEX, \
|
|
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \
|
|
BT_GATT_PERM_READ_ENCRYPT, \
|
|
read_active_preset_index, NULL, NULL), \
|
|
BT_AUDIO_CCC(active_preset_index_cfg_changed)
|
|
#else
|
|
#define BT_HAS_CHR_ACTIVE_PRESET_INDEX
|
|
#endif /* CONFIG_BT_HAS_ACTIVE_PRESET_INDEX */
|
|
|
|
/* Hearing Access Service GATT Attributes */
|
|
static struct bt_gatt_attr has_attrs[] = {
|
|
BT_GATT_PRIMARY_SERVICE(BT_UUID_HAS),
|
|
BT_HAS_CHR_FEATURES
|
|
BT_HAS_CHR_PRESET_CONTROL_POINT
|
|
BT_HAS_CHR_ACTIVE_PRESET_INDEX
|
|
};
|
|
|
|
static struct bt_gatt_service has_svc;
|
|
static struct bt_gatt_attr *hearing_aid_features_attr;
|
|
static struct bt_gatt_attr *preset_control_point_attr;
|
|
static struct bt_gatt_attr *active_preset_index_attr;
|
|
|
|
#if defined(CONFIG_BT_HAS_PRESET_SUPPORT) || defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE)
|
|
static void notify_work_handler(struct k_work *work);
|
|
|
|
enum flag_internal {
|
|
FLAG_ACTIVE_INDEX_CHANGED,
|
|
FLAG_PENDING_READ_PRESET_RESPONSE,
|
|
FLAG_NOTIFY_PRESET_LIST,
|
|
FLAG_NOTIFY_PRESET_LIST_GENERIC_UPDATE_TAIL,
|
|
FLAG_NOTIFY_PRESET_LIST_RECORD_DELETED_LAST,
|
|
FLAG_FEATURES_CHANGED,
|
|
FLAG_NUM,
|
|
};
|
|
|
|
/* Stored client context */
|
|
static struct client_context {
|
|
bt_addr_le_t addr;
|
|
|
|
/* Pending notification flags */
|
|
ATOMIC_DEFINE(flags, FLAG_NUM);
|
|
|
|
/* Last notified preset index */
|
|
uint8_t last_preset_index_known;
|
|
} contexts[CONFIG_BT_MAX_PAIRED];
|
|
|
|
/* Connected client instance */
|
|
static struct has_client {
|
|
struct bt_conn *conn;
|
|
#if defined(CONFIG_BT_HAS_PRESET_SUPPORT)
|
|
union {
|
|
struct bt_gatt_indicate_params ind;
|
|
#if defined(CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE)
|
|
struct bt_gatt_notify_params ntf;
|
|
#endif /* CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE */
|
|
} params;
|
|
|
|
uint8_t preset_changed_index_next;
|
|
struct bt_has_cp_read_presets_req read_presets_req;
|
|
#endif /* CONFIG_BT_HAS_PRESET_SUPPORT */
|
|
struct k_work_delayable notify_work;
|
|
struct client_context *context;
|
|
} has_client_list[MAX_INSTS];
|
|
|
|
|
|
static struct client_context *context_find(const bt_addr_le_t *addr)
|
|
{
|
|
__ASSERT_NO_MSG(addr != NULL);
|
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(contexts); i++) {
|
|
if (bt_addr_le_eq(&contexts[i].addr, addr)) {
|
|
return &contexts[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct client_context *context_alloc(const bt_addr_le_t *addr)
|
|
{
|
|
struct client_context *context;
|
|
|
|
__ASSERT_NO_MSG(addr != NULL);
|
|
|
|
/* Free contexts has BT_ADDR_LE_ANY as the address */
|
|
context = context_find(BT_ADDR_LE_ANY);
|
|
if (context == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
memset(context, 0, sizeof(*context));
|
|
|
|
bt_addr_le_copy(&context->addr, addr);
|
|
|
|
return context;
|
|
}
|
|
|
|
static void context_free(struct client_context *context)
|
|
{
|
|
bt_addr_le_copy(&context->addr, BT_ADDR_LE_ANY);
|
|
}
|
|
|
|
static void client_free(struct has_client *client)
|
|
{
|
|
struct bt_conn_info info = { 0 };
|
|
int err;
|
|
|
|
#if defined(CONFIG_BT_HAS_PRESET_SUPPORT) || defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE)
|
|
(void)k_work_cancel_delayable(&client->notify_work);
|
|
#endif /* CONFIG_BT_HAS_PRESET_SUPPORT || CONFIG_BT_HAS_FEATURES_NOTIFIABLE */
|
|
|
|
err = bt_conn_get_info(client->conn, &info);
|
|
__ASSERT_NO_MSG(err == 0);
|
|
|
|
if (client->context != NULL && !bt_addr_le_is_bonded(info.id, info.le.dst)) {
|
|
/* Free stored context of non-bonded client */
|
|
context_free(client->context);
|
|
client->context = NULL;
|
|
}
|
|
|
|
bt_conn_unref(client->conn);
|
|
client->conn = NULL;
|
|
}
|
|
|
|
static struct has_client *client_alloc(struct bt_conn *conn)
|
|
{
|
|
struct bt_conn_info info = { 0 };
|
|
struct has_client *client = NULL;
|
|
int err;
|
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(has_client_list); i++) {
|
|
if (conn == has_client_list[i].conn) {
|
|
return &has_client_list[i];
|
|
}
|
|
|
|
/* first free slot */
|
|
if (!client && has_client_list[i].conn == NULL) {
|
|
client = &has_client_list[i];
|
|
}
|
|
}
|
|
|
|
__ASSERT(client, "failed to get client for conn %p", (void *)conn);
|
|
|
|
memset(client, 0, sizeof(*client));
|
|
|
|
client->conn = bt_conn_ref(conn);
|
|
|
|
#if defined(CONFIG_BT_HAS_PRESET_SUPPORT) || defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE)
|
|
k_work_init_delayable(&client->notify_work, notify_work_handler);
|
|
#endif /* CONFIG_BT_HAS_PRESET_SUPPORT || CONFIG_BT_HAS_FEATURES_NOTIFIABLE */
|
|
|
|
err = bt_conn_get_info(conn, &info);
|
|
if (err != 0) {
|
|
LOG_DBG("Could not get conn info: %d", err);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
client->context = context_find(info.le.dst);
|
|
if (client->context == NULL) {
|
|
client->context = context_alloc(info.le.dst);
|
|
if (client->context == NULL) {
|
|
LOG_ERR("Failed to allocate client_context for %s",
|
|
bt_addr_le_str(info.le.dst));
|
|
|
|
client_free(client);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
LOG_DBG("New client_context for %s", bt_addr_le_str(info.le.dst));
|
|
}
|
|
|
|
return client;
|
|
}
|
|
|
|
static struct has_client *client_find_by_conn(struct bt_conn *conn)
|
|
{
|
|
for (size_t i = 0; i < ARRAY_SIZE(has_client_list); i++) {
|
|
if (conn == has_client_list[i].conn) {
|
|
return &has_client_list[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void notify_work_reschedule(struct has_client *client, k_timeout_t delay)
|
|
{
|
|
int err;
|
|
|
|
__ASSERT(client->conn, "Not connected");
|
|
|
|
if (k_work_delayable_remaining_get(&client->notify_work) > 0) {
|
|
return;
|
|
}
|
|
|
|
err = k_work_reschedule(&client->notify_work, delay);
|
|
if (err < 0) {
|
|
LOG_ERR("Failed to reschedule notification work err %d", err);
|
|
}
|
|
}
|
|
|
|
static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err err)
|
|
{
|
|
struct has_client *client;
|
|
struct bt_conn_info info;
|
|
int ret;
|
|
|
|
LOG_DBG("conn %p level %d err %d", (void *)conn, level, err);
|
|
|
|
if (err != BT_SECURITY_ERR_SUCCESS) {
|
|
return;
|
|
}
|
|
|
|
client = client_alloc(conn);
|
|
if (unlikely(!client)) {
|
|
LOG_ERR("Failed to allocate client");
|
|
return;
|
|
}
|
|
|
|
ret = bt_conn_get_info(client->conn, &info);
|
|
if (ret < 0) {
|
|
LOG_ERR("bt_conn_get_info err %d", ret);
|
|
return;
|
|
}
|
|
|
|
if (!bt_addr_le_is_bonded(info.id, info.le.dst)) {
|
|
return;
|
|
}
|
|
|
|
if (atomic_get(client->context->flags) != 0) {
|
|
notify_work_reschedule(client, K_NO_WAIT);
|
|
}
|
|
}
|
|
|
|
static void disconnected(struct bt_conn *conn, uint8_t reason)
|
|
{
|
|
struct has_client *client;
|
|
|
|
LOG_DBG("conn %p reason %d", (void *)conn, reason);
|
|
|
|
client = client_find_by_conn(conn);
|
|
if (client) {
|
|
client_free(client);
|
|
}
|
|
}
|
|
|
|
static void identity_resolved(struct bt_conn *conn, const bt_addr_le_t *rpa,
|
|
const bt_addr_le_t *identity)
|
|
{
|
|
struct has_client *client;
|
|
|
|
LOG_DBG("conn %p %s -> %s", (void *)conn, bt_addr_le_str(rpa), bt_addr_le_str(identity));
|
|
|
|
client = client_find_by_conn(conn);
|
|
if (client == NULL) {
|
|
return;
|
|
}
|
|
|
|
bt_addr_le_copy(&client->context->addr, identity);
|
|
}
|
|
|
|
BT_CONN_CB_DEFINE(conn_cb) = {
|
|
.disconnected = disconnected,
|
|
.security_changed = security_changed,
|
|
.identity_resolved = identity_resolved,
|
|
};
|
|
|
|
static void notify_work_handler(struct k_work *work)
|
|
{
|
|
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
|
|
struct has_client *client = CONTAINER_OF(dwork, struct has_client, notify_work);
|
|
int err;
|
|
|
|
if (IS_ENABLED(CONFIG_BT_HAS_FEATURES_NOTIFIABLE) &&
|
|
atomic_test_and_clear_bit(client->context->flags, FLAG_FEATURES_CHANGED) &&
|
|
bt_gatt_is_subscribed(client->conn, hearing_aid_features_attr, BT_GATT_CCC_NOTIFY)) {
|
|
err = bt_gatt_notify(client->conn, hearing_aid_features_attr, &has.features,
|
|
sizeof(has.features));
|
|
if (err == -ENOMEM) {
|
|
atomic_set_bit(client->context->flags, FLAG_FEATURES_CHANGED);
|
|
notify_work_reschedule(client, K_USEC(BT_AUDIO_NOTIFY_RETRY_DELAY_US));
|
|
} else if (err < 0) {
|
|
LOG_ERR("Notify features err %d", err);
|
|
}
|
|
}
|
|
|
|
#if defined(CONFIG_BT_HAS_PRESET_SUPPORT)
|
|
if (atomic_test_and_clear_bit(client->context->flags, FLAG_PENDING_READ_PRESET_RESPONSE)) {
|
|
err = read_preset_response(client);
|
|
if (err == -ENOMEM) {
|
|
atomic_set_bit(client->context->flags, FLAG_PENDING_READ_PRESET_RESPONSE);
|
|
notify_work_reschedule(client, K_USEC(BT_AUDIO_NOTIFY_RETRY_DELAY_US));
|
|
} else if (err < 0) {
|
|
LOG_ERR("Notify read preset response err %d", err);
|
|
}
|
|
} else if (atomic_test_and_clear_bit(client->context->flags, FLAG_NOTIFY_PRESET_LIST)) {
|
|
err = preset_list_changed(client);
|
|
if (err == -ENOMEM) {
|
|
atomic_set_bit(client->context->flags, FLAG_NOTIFY_PRESET_LIST);
|
|
notify_work_reschedule(client, K_USEC(BT_AUDIO_NOTIFY_RETRY_DELAY_US));
|
|
} else if (err < 0) {
|
|
LOG_ERR("Notify preset list changed err %d", err);
|
|
}
|
|
} else if (atomic_test_and_clear_bit(client->context->flags,
|
|
FLAG_NOTIFY_PRESET_LIST_GENERIC_UPDATE_TAIL)) {
|
|
err = preset_list_changed_generic_update_tail(client);
|
|
if (err == -ENOMEM) {
|
|
atomic_set_bit(client->context->flags,
|
|
FLAG_NOTIFY_PRESET_LIST_GENERIC_UPDATE_TAIL);
|
|
notify_work_reschedule(client, K_USEC(BT_AUDIO_NOTIFY_RETRY_DELAY_US));
|
|
} else if (err < 0) {
|
|
LOG_ERR("Notify preset list changed generic update tail err %d", err);
|
|
}
|
|
} else if (atomic_test_and_clear_bit(client->context->flags,
|
|
FLAG_NOTIFY_PRESET_LIST_RECORD_DELETED_LAST)) {
|
|
err = preset_list_changed_record_deleted_last(client);
|
|
if (err == -ENOMEM) {
|
|
atomic_set_bit(client->context->flags,
|
|
FLAG_NOTIFY_PRESET_LIST_RECORD_DELETED_LAST);
|
|
notify_work_reschedule(client, K_USEC(BT_AUDIO_NOTIFY_RETRY_DELAY_US));
|
|
} else if (err < 0) {
|
|
LOG_ERR("Notify preset list changed recoed deleted last err %d", err);
|
|
}
|
|
}
|
|
|
|
#endif /* CONFIG_BT_HAS_PRESET_SUPPORT */
|
|
|
|
if (IS_ENABLED(CONFIG_BT_HAS_PRESET_SUPPORT) &&
|
|
atomic_test_and_clear_bit(client->context->flags, FLAG_ACTIVE_INDEX_CHANGED) &&
|
|
bt_gatt_is_subscribed(client->conn, active_preset_index_attr, BT_GATT_CCC_NOTIFY)) {
|
|
uint8_t active_index;
|
|
|
|
active_index = bt_has_preset_active_get();
|
|
|
|
err = bt_gatt_notify(client->conn, active_preset_index_attr,
|
|
&active_index, sizeof(active_index));
|
|
if (err == -ENOMEM) {
|
|
atomic_set_bit(client->context->flags, FLAG_ACTIVE_INDEX_CHANGED);
|
|
notify_work_reschedule(client, K_USEC(BT_AUDIO_NOTIFY_RETRY_DELAY_US));
|
|
} else if (err < 0) {
|
|
LOG_ERR("Notify active index err %d", err);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void notify(struct has_client *client, enum flag_internal flag)
|
|
{
|
|
if (client != NULL) {
|
|
atomic_set_bit(client->context->flags, flag);
|
|
notify_work_reschedule(client, K_NO_WAIT);
|
|
return;
|
|
}
|
|
|
|
/* Mark notification to be sent to all clients */
|
|
for (size_t i = 0U; i < ARRAY_SIZE(contexts); i++) {
|
|
atomic_set_bit(contexts[i].flags, flag);
|
|
}
|
|
|
|
for (size_t i = 0U; i < ARRAY_SIZE(has_client_list); i++) {
|
|
client = &has_client_list[i];
|
|
|
|
if (client->conn == NULL) {
|
|
continue;
|
|
}
|
|
|
|
notify_work_reschedule(client, K_NO_WAIT);
|
|
}
|
|
}
|
|
|
|
static void bond_deleted_cb(uint8_t id, const bt_addr_le_t *addr)
|
|
{
|
|
struct client_context *context;
|
|
|
|
context = context_find(addr);
|
|
if (context != NULL) {
|
|
context_free(context);
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
|
|
bt_settings_delete("has", 0, addr);
|
|
}
|
|
}
|
|
|
|
static struct bt_conn_auth_info_cb auth_info_cb = {
|
|
.bond_deleted = bond_deleted_cb,
|
|
};
|
|
#endif /* CONFIG_BT_HAS_PRESET_SUPPORT || CONFIG_BT_HAS_FEATURES_NOTIFIABLE */
|
|
|
|
#if defined(CONFIG_BT_HAS_PRESET_SUPPORT)
|
|
static struct has_preset *active_preset;
|
|
|
|
/* HAS internal preset representation */
|
|
static struct has_preset {
|
|
uint8_t index;
|
|
enum bt_has_properties properties;
|
|
#if defined(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC)
|
|
char name[BT_HAS_PRESET_NAME_MAX + 1]; /* +1 byte for NULL-terminator */
|
|
#else
|
|
const char *name;
|
|
#endif /* CONFIG_BT_HAS_PRESET_NAME_DYNAMIC */
|
|
const struct bt_has_preset_ops *ops;
|
|
sys_snode_t node;
|
|
} preset_pool[CONFIG_BT_HAS_PRESET_COUNT];
|
|
|
|
static sys_slist_t preset_list = SYS_SLIST_STATIC_INIT(&preset_list);
|
|
static sys_slist_t preset_free_list = SYS_SLIST_STATIC_INIT(&preset_free_list);
|
|
|
|
typedef uint8_t (*preset_func_t)(const struct has_preset *preset, void *user_data);
|
|
|
|
static void preset_foreach(uint8_t start_index, uint8_t end_index, preset_func_t func,
|
|
void *user_data)
|
|
{
|
|
struct has_preset *preset, *tmp;
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&preset_list, preset, tmp, node) {
|
|
if (preset->index < start_index) {
|
|
continue;
|
|
}
|
|
|
|
if (preset->index > end_index) {
|
|
return;
|
|
}
|
|
|
|
if (func(preset, user_data) == BT_HAS_PRESET_ITER_STOP) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static uint8_t preset_found(const struct has_preset *preset, void *user_data)
|
|
{
|
|
const struct has_preset **found = user_data;
|
|
|
|
*found = preset;
|
|
|
|
return BT_HAS_PRESET_ITER_STOP;
|
|
}
|
|
|
|
static void preset_insert(struct has_preset *preset)
|
|
{
|
|
struct has_preset *tmp, *prev = NULL;
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER(&preset_list, tmp, node) {
|
|
if (tmp->index > preset->index) {
|
|
if (prev) {
|
|
sys_slist_insert(&preset_list, &prev->node, &preset->node);
|
|
} else {
|
|
sys_slist_prepend(&preset_list, &preset->node);
|
|
}
|
|
return;
|
|
}
|
|
|
|
prev = tmp;
|
|
}
|
|
|
|
sys_slist_append(&preset_list, &preset->node);
|
|
}
|
|
|
|
static struct has_preset *preset_alloc(uint8_t index, enum bt_has_properties properties,
|
|
const char *name, const struct bt_has_preset_ops *ops)
|
|
{
|
|
struct has_preset *preset;
|
|
sys_snode_t *node;
|
|
|
|
node = sys_slist_get(&preset_free_list);
|
|
if (node == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
preset = CONTAINER_OF(node, struct has_preset, node);
|
|
preset->index = index;
|
|
preset->properties = properties;
|
|
#if defined(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC)
|
|
utf8_lcpy(preset->name, name, ARRAY_SIZE(preset->name));
|
|
#else
|
|
preset->name = name;
|
|
#endif /* CONFIG_BT_HAS_PRESET_NAME_DYNAMIC */
|
|
preset->ops = ops;
|
|
|
|
preset_insert(preset);
|
|
|
|
return preset;
|
|
}
|
|
|
|
static void preset_free(struct has_preset *preset)
|
|
{
|
|
bool removed;
|
|
|
|
removed = sys_slist_find_and_remove(&preset_list, &preset->node);
|
|
if (removed) {
|
|
sys_slist_append(&preset_free_list, &preset->node);
|
|
}
|
|
}
|
|
|
|
static struct has_preset *preset_get_head(void)
|
|
{
|
|
struct has_preset *next;
|
|
|
|
return SYS_SLIST_PEEK_HEAD_CONTAINER(&preset_list, next, node);
|
|
}
|
|
|
|
static struct has_preset *preset_get_tail(void)
|
|
{
|
|
struct has_preset *prev;
|
|
|
|
return SYS_SLIST_PEEK_TAIL_CONTAINER(&preset_list, prev, node);
|
|
}
|
|
|
|
static struct has_preset *preset_get_prev(const struct has_preset *preset)
|
|
{
|
|
struct has_preset *prev;
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER(&preset_list, prev, node) {
|
|
if (SYS_SLIST_PEEK_NEXT_CONTAINER(prev, node) == preset) {
|
|
return prev;
|
|
}
|
|
}
|
|
|
|
prev = preset_get_tail();
|
|
if (prev == preset) {
|
|
return NULL;
|
|
}
|
|
|
|
return prev;
|
|
}
|
|
|
|
static struct has_preset *preset_lookup_index(uint8_t index)
|
|
{
|
|
struct has_preset *preset;
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER(&preset_list, preset, node) {
|
|
if (preset->index == index) {
|
|
return preset;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct has_preset *preset_get_next(struct has_preset *preset)
|
|
{
|
|
struct has_preset *next;
|
|
|
|
next = SYS_SLIST_PEEK_NEXT_CONTAINER(preset, node);
|
|
if (next == NULL) {
|
|
next = preset_get_head();
|
|
if (next == preset) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return next;
|
|
}
|
|
|
|
static uint8_t preset_get_prev_index(const struct has_preset *preset)
|
|
{
|
|
const struct has_preset *prev;
|
|
|
|
prev = preset_get_prev(preset);
|
|
if (prev == NULL || prev->index >= preset->index) {
|
|
return BT_HAS_PRESET_INDEX_NONE;
|
|
}
|
|
|
|
return prev->index;
|
|
}
|
|
|
|
static void control_point_ntf_complete(struct bt_conn *conn, void *user_data)
|
|
{
|
|
struct has_client *client = client_find_by_conn(conn);
|
|
|
|
LOG_DBG("conn %p", (void *)conn);
|
|
|
|
/* Resubmit if needed */
|
|
if (client != NULL && atomic_get(client->context->flags) != 0) {
|
|
notify_work_reschedule(client, K_NO_WAIT);
|
|
}
|
|
}
|
|
|
|
static void control_point_ind_complete(struct bt_conn *conn,
|
|
struct bt_gatt_indicate_params *params,
|
|
uint8_t err)
|
|
{
|
|
if (err) {
|
|
/* TODO: Handle error somehow */
|
|
LOG_ERR("conn %p err 0x%02x", (void *)conn, err);
|
|
}
|
|
|
|
control_point_ntf_complete(conn, NULL);
|
|
}
|
|
|
|
static int control_point_send(struct has_client *client, struct net_buf_simple *buf)
|
|
{
|
|
const uint16_t mtu_size = bt_gatt_get_mtu(client->conn);
|
|
/* PDU structure is [Opcode (1)] [Handle (2)] [...] */
|
|
const uint16_t pdu_size = 3 + buf->len;
|
|
|
|
if (mtu_size < pdu_size) {
|
|
LOG_WRN("Sending truncated control point PDU %d < %d", mtu_size, pdu_size);
|
|
buf->len -= (pdu_size - mtu_size);
|
|
}
|
|
|
|
#if defined(CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE)
|
|
if (bt_eatt_count(client->conn) > 0 &&
|
|
bt_gatt_is_subscribed(client->conn, preset_control_point_attr, BT_GATT_CCC_NOTIFY)) {
|
|
memset(&client->params.ntf, 0, sizeof(client->params.ntf));
|
|
client->params.ntf.attr = preset_control_point_attr;
|
|
client->params.ntf.func = control_point_ntf_complete;
|
|
client->params.ntf.data = buf->data;
|
|
client->params.ntf.len = buf->len;
|
|
|
|
return bt_gatt_notify_cb(client->conn, &client->params.ntf);
|
|
}
|
|
#endif /* CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE */
|
|
|
|
if (bt_gatt_is_subscribed(client->conn, preset_control_point_attr, BT_GATT_CCC_INDICATE)) {
|
|
memset(&client->params.ind, 0, sizeof(client->params.ind));
|
|
client->params.ind.attr = preset_control_point_attr;
|
|
client->params.ind.func = control_point_ind_complete;
|
|
client->params.ind.destroy = NULL;
|
|
client->params.ind.data = buf->data;
|
|
client->params.ind.len = buf->len;
|
|
|
|
return bt_gatt_indicate(client->conn, &client->params.ind);
|
|
}
|
|
|
|
return -ECANCELED;
|
|
}
|
|
|
|
static int control_point_send_all(struct net_buf_simple *buf)
|
|
{
|
|
int result = 0;
|
|
|
|
for (size_t i = 0U; i < ARRAY_SIZE(contexts); i++) {
|
|
struct client_context *context = &contexts[i];
|
|
struct has_client *client = NULL;
|
|
int err;
|
|
|
|
for (size_t j = 0U; j < ARRAY_SIZE(has_client_list); j++) {
|
|
if (has_client_list[j].context == context) {
|
|
client = &has_client_list[j];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (client == NULL || client->conn == NULL) {
|
|
/* Mark preset changed operation as pending */
|
|
atomic_set_bit(context->flags, FLAG_NOTIFY_PRESET_LIST);
|
|
continue;
|
|
}
|
|
|
|
if (!bt_gatt_is_subscribed(client->conn, preset_control_point_attr,
|
|
BT_GATT_CCC_NOTIFY | BT_GATT_CCC_INDICATE)) {
|
|
continue;
|
|
}
|
|
|
|
err = control_point_send(client, buf);
|
|
if (err) {
|
|
result = err;
|
|
/* continue anyway */
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static int bt_has_cp_read_preset_rsp(struct has_client *client, const struct has_preset *preset,
|
|
bool is_last)
|
|
{
|
|
struct bt_has_cp_hdr *hdr;
|
|
struct bt_has_cp_read_preset_rsp *rsp;
|
|
|
|
NET_BUF_SIMPLE_DEFINE(buf, sizeof(*hdr) + sizeof(*rsp) + BT_HAS_PRESET_NAME_MAX);
|
|
|
|
LOG_DBG("conn %p index 0x%02x prop 0x%02x %s is_last 0x%02x", (void *)client->conn,
|
|
preset->index, preset->properties, preset->name, is_last);
|
|
|
|
hdr = net_buf_simple_add(&buf, sizeof(*hdr));
|
|
hdr->opcode = BT_HAS_OP_READ_PRESET_RSP;
|
|
rsp = net_buf_simple_add(&buf, sizeof(*rsp));
|
|
rsp->is_last = is_last ? 0x01 : 0x00;
|
|
rsp->index = preset->index;
|
|
rsp->properties = preset->properties;
|
|
net_buf_simple_add_mem(&buf, preset->name, strlen(preset->name));
|
|
|
|
return control_point_send(client, &buf);
|
|
}
|
|
|
|
static void preset_changed_prepare(struct net_buf_simple *buf, uint8_t change_id, uint8_t is_last)
|
|
{
|
|
struct bt_has_cp_hdr *hdr;
|
|
struct bt_has_cp_preset_changed *preset_changed;
|
|
|
|
hdr = net_buf_simple_add(buf, sizeof(*hdr));
|
|
hdr->opcode = BT_HAS_OP_PRESET_CHANGED;
|
|
preset_changed = net_buf_simple_add(buf, sizeof(*preset_changed));
|
|
preset_changed->change_id = change_id;
|
|
preset_changed->is_last = is_last;
|
|
}
|
|
|
|
static int bt_has_cp_generic_update(struct has_client *client, uint8_t prev_index, uint8_t index,
|
|
uint8_t properties, const char *name, uint8_t is_last)
|
|
{
|
|
struct bt_has_cp_generic_update *generic_update;
|
|
|
|
NET_BUF_SIMPLE_DEFINE(buf, sizeof(struct bt_has_cp_hdr) +
|
|
sizeof(struct bt_has_cp_preset_changed) +
|
|
sizeof(struct bt_has_cp_generic_update) + BT_HAS_PRESET_NAME_MAX);
|
|
|
|
LOG_DBG("client %p prev_index 0x%02x index 0x%02x prop 0x%02x %s is_last %d",
|
|
client, prev_index, index, properties, name, is_last);
|
|
|
|
preset_changed_prepare(&buf, BT_HAS_CHANGE_ID_GENERIC_UPDATE, is_last);
|
|
|
|
generic_update = net_buf_simple_add(&buf, sizeof(*generic_update));
|
|
generic_update->prev_index = prev_index;
|
|
generic_update->index = index;
|
|
generic_update->properties = properties;
|
|
net_buf_simple_add_mem(&buf, name, strlen(name));
|
|
|
|
if (client) {
|
|
return control_point_send(client, &buf);
|
|
} else {
|
|
return control_point_send_all(&buf);
|
|
}
|
|
}
|
|
|
|
#if defined(CONFIG_BT_SETTINGS)
|
|
struct client_context_store {
|
|
/* Last notified preset index */
|
|
uint8_t last_preset_index_known;
|
|
} __packed;
|
|
|
|
static int settings_set_cb(const char *name, size_t len_rd, settings_read_cb read_cb, void *cb_arg)
|
|
{
|
|
struct client_context_store store;
|
|
struct client_context *context;
|
|
bt_addr_le_t addr;
|
|
ssize_t len;
|
|
int err;
|
|
|
|
if (!name) {
|
|
LOG_ERR("Insufficient number of arguments");
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = bt_settings_decode_key(name, &addr);
|
|
if (err) {
|
|
LOG_ERR("Unable to decode address %s", name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
context = context_find(&addr);
|
|
if (context == NULL) {
|
|
/* Find and initialize a free entry */
|
|
context = context_alloc(&addr);
|
|
if (context == NULL) {
|
|
LOG_ERR("Failed to allocate client_context for %s", bt_addr_le_str(&addr));
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
if (len_rd) {
|
|
len = read_cb(cb_arg, &store, sizeof(store));
|
|
if (len < 0) {
|
|
LOG_ERR("Failed to decode value (err %zd)", len);
|
|
return len;
|
|
}
|
|
|
|
context->last_preset_index_known = store.last_preset_index_known;
|
|
} else {
|
|
context->last_preset_index_known = 0x00;
|
|
}
|
|
|
|
/* Notify all the characteristics values after reboot */
|
|
atomic_set(context->flags, BONDED_CLIENT_INIT_FLAGS);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static BT_SETTINGS_DEFINE(has, "has", settings_set_cb, NULL);
|
|
|
|
static void store_client_context(struct client_context *context)
|
|
{
|
|
struct client_context_store store = {
|
|
.last_preset_index_known = context->last_preset_index_known,
|
|
};
|
|
int err;
|
|
|
|
LOG_DBG("%s last_preset_index_known 0x%02x",
|
|
bt_addr_le_str(&context->addr), store.last_preset_index_known);
|
|
|
|
err = bt_settings_store("has", 0, &context->addr, &store, sizeof(store));
|
|
if (err != 0) {
|
|
LOG_ERR("Failed to store err %d", err);
|
|
}
|
|
}
|
|
#else
|
|
#define store_client_context(...)
|
|
#endif /* CONFIG_BT_SETTINGS */
|
|
|
|
static void update_last_preset_index_known(struct has_client *client, uint8_t index)
|
|
{
|
|
if (client != NULL && client->context != NULL &&
|
|
client->context->last_preset_index_known != index) {
|
|
client->context->last_preset_index_known = index;
|
|
store_client_context(client->context);
|
|
return;
|
|
}
|
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(has_client_list); i++) {
|
|
client = &has_client_list[i];
|
|
|
|
/* For each connected client */
|
|
if (client->conn != NULL && client->context != NULL &&
|
|
client->context->last_preset_index_known != index) {
|
|
client->context->last_preset_index_known = index;
|
|
store_client_context(client->context);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int read_preset_response(struct has_client *client)
|
|
{
|
|
const struct has_preset *preset = NULL;
|
|
bool is_last = true;
|
|
int err;
|
|
|
|
__ASSERT_NO_MSG(client != NULL);
|
|
|
|
preset_foreach(client->read_presets_req.start_index, BT_HAS_PRESET_INDEX_LAST,
|
|
preset_found, &preset);
|
|
|
|
if (unlikely(preset == NULL)) {
|
|
return bt_has_cp_read_preset_rsp(client, NULL, BT_HAS_IS_LAST);
|
|
}
|
|
|
|
if (client->read_presets_req.num_presets > 1) {
|
|
const struct has_preset *next = NULL;
|
|
|
|
preset_foreach(preset->index + 1, BT_HAS_PRESET_INDEX_LAST, preset_found, &next);
|
|
|
|
is_last = next == NULL;
|
|
}
|
|
|
|
err = bt_has_cp_read_preset_rsp(client, preset, is_last);
|
|
if (err != 0) {
|
|
return err;
|
|
}
|
|
|
|
if (preset->index > client->context->last_preset_index_known) {
|
|
update_last_preset_index_known(client, preset->index);
|
|
}
|
|
|
|
if (!is_last) {
|
|
client->read_presets_req.start_index = preset->index + 1;
|
|
client->read_presets_req.num_presets--;
|
|
|
|
atomic_set_bit(client->context->flags, FLAG_PENDING_READ_PRESET_RESPONSE);
|
|
notify_work_reschedule(client, K_USEC(BT_AUDIO_NOTIFY_RETRY_DELAY_US));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bt_has_cp_preset_record_deleted(struct has_client *client, uint8_t index)
|
|
{
|
|
NET_BUF_SIMPLE_DEFINE(buf, sizeof(struct bt_has_cp_hdr) +
|
|
sizeof(struct bt_has_cp_preset_changed) + sizeof(uint8_t));
|
|
|
|
LOG_DBG("client %p index 0x%02x", client, index);
|
|
|
|
preset_changed_prepare(&buf, BT_HAS_CHANGE_ID_PRESET_DELETED, BT_HAS_IS_LAST);
|
|
net_buf_simple_add_u8(&buf, index);
|
|
|
|
if (client != NULL) {
|
|
return control_point_send(client, &buf);
|
|
} else {
|
|
return control_point_send_all(&buf);
|
|
}
|
|
}
|
|
|
|
/* Generic Update the last (already deleted) preset */
|
|
static int preset_list_changed_generic_update_tail(struct has_client *client)
|
|
{
|
|
const struct has_preset *prev;
|
|
struct has_preset last = {
|
|
/* The index value of the last preset the client knew about. */
|
|
.index = client->context->last_preset_index_known,
|
|
|
|
/* As the properties of deleted preset is not available anymore, we set this value
|
|
* to 0x00 meaning the preset is unavailable and non-writable which is actually true
|
|
*/
|
|
.properties = BT_HAS_PROP_NONE,
|
|
|
|
/* As the name of deleted preset are not available anymore, we set this value
|
|
* to the value what is compliant with specification.
|
|
* As per HAS_v1.0 the Name is 1-40 octet value.
|
|
*/
|
|
.name = "N/A",
|
|
};
|
|
int err;
|
|
|
|
prev = preset_get_tail();
|
|
|
|
err = bt_has_cp_generic_update(client, prev ? prev->index : BT_HAS_PRESET_INDEX_NONE,
|
|
last.index, last.properties, last.name, false);
|
|
if (err != 0) {
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int preset_list_changed_record_deleted_last(struct has_client *client)
|
|
{
|
|
const struct has_preset *last;
|
|
int err;
|
|
|
|
err = bt_has_cp_preset_record_deleted(client, client->context->last_preset_index_known);
|
|
if (err != 0) {
|
|
return err;
|
|
}
|
|
|
|
last = preset_get_tail();
|
|
|
|
update_last_preset_index_known(client, last ? last->index : BT_HAS_PRESET_INDEX_NONE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int preset_list_changed(struct has_client *client)
|
|
{
|
|
const struct has_preset *preset = NULL;
|
|
const struct has_preset *next = NULL;
|
|
bool is_last = true;
|
|
int err;
|
|
|
|
if (sys_slist_is_empty(&preset_list)) {
|
|
/* The preset list is empty. We need to indicate deletion of all presets */
|
|
atomic_set_bit(client->context->flags,
|
|
FLAG_NOTIFY_PRESET_LIST_GENERIC_UPDATE_TAIL);
|
|
atomic_set_bit(client->context->flags,
|
|
FLAG_NOTIFY_PRESET_LIST_RECORD_DELETED_LAST);
|
|
notify_work_reschedule(client, K_USEC(BT_AUDIO_NOTIFY_RETRY_DELAY_US));
|
|
|
|
return 0;
|
|
}
|
|
|
|
preset_foreach(client->preset_changed_index_next, BT_HAS_PRESET_INDEX_LAST,
|
|
preset_found, &preset);
|
|
|
|
if (preset == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
preset_foreach(preset->index + 1, BT_HAS_PRESET_INDEX_LAST, preset_found, &next);
|
|
|
|
/* It is last Preset Changed notification if there are no presets left to notify and the
|
|
* currently notified preset have the highest index known to the client.
|
|
*/
|
|
is_last = next == NULL && preset->index >= client->context->last_preset_index_known;
|
|
|
|
err = bt_has_cp_generic_update(client, preset_get_prev_index(preset), preset->index,
|
|
preset->properties, preset->name, is_last);
|
|
if (err != 0) {
|
|
return err;
|
|
}
|
|
|
|
if (is_last) {
|
|
client->preset_changed_index_next = 0;
|
|
|
|
/* It's the last preset notified, so update the highest index known to the client */
|
|
update_last_preset_index_known(client, preset->index);
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (next == NULL) {
|
|
/* If we end up here, the last preset known to the client has been removed.
|
|
* As we do not hold the information about the deleted presets, we need to use
|
|
* Generic Update procedure to:
|
|
* 1. Notify the presets that have been removed in range
|
|
* (PrevIndex = current_preset_last, Index=previous_preset_last)
|
|
* 2. Notify deletion of preset Index=previous_preset_last.
|
|
*/
|
|
atomic_set_bit(client->context->flags,
|
|
FLAG_NOTIFY_PRESET_LIST_GENERIC_UPDATE_TAIL);
|
|
atomic_set_bit(client->context->flags,
|
|
FLAG_NOTIFY_PRESET_LIST_RECORD_DELETED_LAST);
|
|
} else {
|
|
client->preset_changed_index_next = preset->index + 1;
|
|
|
|
atomic_set_bit(client->context->flags, FLAG_NOTIFY_PRESET_LIST);
|
|
}
|
|
|
|
notify_work_reschedule(client, K_USEC(BT_AUDIO_NOTIFY_RETRY_DELAY_US));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint8_t handle_read_preset_req(struct bt_conn *conn, struct net_buf_simple *buf)
|
|
{
|
|
const struct bt_has_cp_read_presets_req *req;
|
|
const struct has_preset *preset = NULL;
|
|
struct has_client *client;
|
|
|
|
if (buf->len < sizeof(*req)) {
|
|
return BT_HAS_ERR_INVALID_PARAM_LEN;
|
|
}
|
|
|
|
/* As per HAS_d1.0r00 Client Characteristic Configuration Descriptor Improperly Configured
|
|
* shall be returned if client writes Read Presets Request but is not registered for
|
|
* indications.
|
|
*/
|
|
if (!bt_gatt_is_subscribed(conn, preset_control_point_attr, BT_GATT_CCC_INDICATE)) {
|
|
return BT_ATT_ERR_CCC_IMPROPER_CONF;
|
|
}
|
|
|
|
client = client_find_by_conn(conn);
|
|
if (client == NULL) {
|
|
return BT_ATT_ERR_UNLIKELY;
|
|
}
|
|
|
|
req = net_buf_simple_pull_mem(buf, sizeof(*req));
|
|
|
|
LOG_DBG("start_index %d num_presets %d", req->start_index, req->num_presets);
|
|
|
|
/* Abort if there is no preset in requested index range */
|
|
preset_foreach(req->start_index, BT_HAS_PRESET_INDEX_LAST, preset_found, &preset);
|
|
|
|
if (preset == NULL) {
|
|
return BT_ATT_ERR_OUT_OF_RANGE;
|
|
}
|
|
|
|
/* Reject if already in progress */
|
|
if (atomic_test_bit(client->context->flags, FLAG_PENDING_READ_PRESET_RESPONSE)) {
|
|
return BT_HAS_ERR_OPERATION_NOT_POSSIBLE;
|
|
}
|
|
|
|
/* Store the request */
|
|
client->read_presets_req.start_index = req->start_index;
|
|
client->read_presets_req.num_presets = req->num_presets;
|
|
|
|
notify(client, FLAG_PENDING_READ_PRESET_RESPONSE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int set_preset_name(uint8_t index, const char *name, size_t len)
|
|
{
|
|
struct has_preset *preset = NULL;
|
|
|
|
LOG_DBG("index %d name_len %zu", index, len);
|
|
|
|
if (len < BT_HAS_PRESET_NAME_MIN || len > BT_HAS_PRESET_NAME_MAX) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Abort if there is no preset in requested index range */
|
|
preset_foreach(index, BT_HAS_PRESET_INDEX_LAST, preset_found, &preset);
|
|
|
|
if (preset == NULL) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
if (!(preset->properties & BT_HAS_PROP_WRITABLE)) {
|
|
return -EPERM;
|
|
}
|
|
|
|
IF_ENABLED(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC, (
|
|
__ASSERT(len < ARRAY_SIZE(preset->name), "No space for name");
|
|
|
|
(void)memcpy(preset->name, name, len);
|
|
|
|
/* NULL-terminate string */
|
|
preset->name[len] = '\0';
|
|
|
|
/* Properly truncate a NULL-terminated UTF-8 string */
|
|
utf8_trunc(preset->name);
|
|
));
|
|
|
|
if (preset->ops->name_changed) {
|
|
preset->ops->name_changed(index, preset->name);
|
|
}
|
|
|
|
return bt_has_cp_generic_update(NULL, preset_get_prev_index(preset), preset->index,
|
|
preset->properties, preset->name, BT_HAS_IS_LAST);
|
|
}
|
|
|
|
static uint8_t handle_write_preset_name(struct bt_conn *conn, struct net_buf_simple *buf)
|
|
{
|
|
const struct bt_has_cp_write_preset_name *req;
|
|
struct has_client *client;
|
|
int err;
|
|
|
|
if (buf->len < sizeof(*req)) {
|
|
return BT_HAS_ERR_INVALID_PARAM_LEN;
|
|
}
|
|
|
|
/* As per HAS_v1.0 Client Characteristic Configuration Descriptor Improperly Configured
|
|
* shall be returned if client writes Write Preset Name opcode but is not registered for
|
|
* indications.
|
|
*/
|
|
if (!bt_gatt_is_subscribed(conn, preset_control_point_attr, BT_GATT_CCC_INDICATE)) {
|
|
return BT_ATT_ERR_CCC_IMPROPER_CONF;
|
|
}
|
|
|
|
client = client_find_by_conn(conn);
|
|
if (!client) {
|
|
return BT_ATT_ERR_UNLIKELY;
|
|
}
|
|
|
|
req = net_buf_simple_pull_mem(buf, sizeof(*req));
|
|
|
|
err = set_preset_name(req->index, req->name, buf->len);
|
|
if (err == -EINVAL) {
|
|
return BT_HAS_ERR_INVALID_PARAM_LEN;
|
|
} else if (err == -ENOENT) {
|
|
return BT_ATT_ERR_OUT_OF_RANGE;
|
|
} else if (err == -EPERM) {
|
|
return BT_HAS_ERR_WRITE_NAME_NOT_ALLOWED;
|
|
} else if (err) {
|
|
return BT_ATT_ERR_UNLIKELY;
|
|
}
|
|
|
|
return BT_ATT_ERR_SUCCESS;
|
|
}
|
|
|
|
static void preset_set_active(struct has_preset *preset)
|
|
{
|
|
if (active_preset != preset) {
|
|
active_preset = preset;
|
|
|
|
notify(NULL, FLAG_ACTIVE_INDEX_CHANGED);
|
|
}
|
|
}
|
|
|
|
static uint8_t preset_select(struct has_preset *preset, bool sync)
|
|
{
|
|
const int err = preset->ops->select(preset->index, sync);
|
|
|
|
if (err == -EINPROGRESS) {
|
|
/* User has to confirm once the requested preset becomes active by
|
|
* calling bt_has_preset_active_set.
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
if (err == -EBUSY) {
|
|
return BT_HAS_ERR_OPERATION_NOT_POSSIBLE;
|
|
}
|
|
|
|
if (err) {
|
|
return BT_ATT_ERR_UNLIKELY;
|
|
}
|
|
|
|
preset_set_active(preset);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool is_preset_available(const struct has_preset *preset)
|
|
{
|
|
return (preset->properties & BT_HAS_PROP_AVAILABLE) != 0;
|
|
}
|
|
|
|
static uint8_t handle_set_active_preset(struct net_buf_simple *buf, bool sync)
|
|
{
|
|
const struct bt_has_cp_set_active_preset *pdu;
|
|
struct has_preset *preset;
|
|
|
|
if (buf->len < sizeof(*pdu)) {
|
|
return BT_HAS_ERR_INVALID_PARAM_LEN;
|
|
}
|
|
|
|
pdu = net_buf_simple_pull_mem(buf, sizeof(*pdu));
|
|
|
|
preset = preset_lookup_index(pdu->index);
|
|
if (preset == NULL) {
|
|
return BT_ATT_ERR_OUT_OF_RANGE;
|
|
}
|
|
|
|
if (!is_preset_available(preset)) {
|
|
return BT_HAS_ERR_OPERATION_NOT_POSSIBLE;
|
|
}
|
|
|
|
return preset_select(preset, sync);
|
|
}
|
|
|
|
static uint8_t handle_set_next_preset(bool sync)
|
|
{
|
|
struct has_preset *next, *tmp;
|
|
|
|
if (active_preset == NULL) {
|
|
next = preset_get_head();
|
|
} else {
|
|
next = preset_get_next(active_preset);
|
|
}
|
|
|
|
tmp = next;
|
|
do {
|
|
if (next == NULL) {
|
|
break;
|
|
}
|
|
|
|
if (is_preset_available(next)) {
|
|
return preset_select(next, sync);
|
|
}
|
|
|
|
next = preset_get_next(next);
|
|
} while (tmp != next);
|
|
|
|
return BT_HAS_ERR_OPERATION_NOT_POSSIBLE;
|
|
}
|
|
|
|
static uint8_t handle_set_prev_preset(bool sync)
|
|
{
|
|
struct has_preset *prev, *tmp;
|
|
|
|
if (active_preset == NULL) {
|
|
prev = preset_get_tail();
|
|
} else {
|
|
prev = preset_get_prev(active_preset);
|
|
}
|
|
|
|
tmp = prev;
|
|
do {
|
|
if (prev == NULL) {
|
|
break;
|
|
}
|
|
|
|
if (is_preset_available(prev)) {
|
|
return preset_select(prev, sync);
|
|
}
|
|
|
|
prev = preset_get_prev(prev);
|
|
} while (tmp != prev);
|
|
|
|
return BT_HAS_ERR_OPERATION_NOT_POSSIBLE;
|
|
}
|
|
|
|
static uint8_t handle_control_point_op(struct bt_conn *conn, struct net_buf_simple *buf)
|
|
{
|
|
const struct bt_has_cp_hdr *hdr;
|
|
|
|
hdr = net_buf_simple_pull_mem(buf, sizeof(*hdr));
|
|
|
|
LOG_DBG("conn %p opcode %s (0x%02x)", (void *)conn, bt_has_op_str(hdr->opcode),
|
|
hdr->opcode);
|
|
|
|
switch (hdr->opcode) {
|
|
case BT_HAS_OP_READ_PRESET_REQ:
|
|
return handle_read_preset_req(conn, buf);
|
|
case BT_HAS_OP_WRITE_PRESET_NAME:
|
|
if (IS_ENABLED(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC)) {
|
|
return handle_write_preset_name(conn, buf);
|
|
} else {
|
|
return BT_HAS_ERR_WRITE_NAME_NOT_ALLOWED;
|
|
}
|
|
break;
|
|
case BT_HAS_OP_SET_ACTIVE_PRESET:
|
|
return handle_set_active_preset(buf, false);
|
|
case BT_HAS_OP_SET_NEXT_PRESET:
|
|
return handle_set_next_preset(false);
|
|
case BT_HAS_OP_SET_PREV_PRESET:
|
|
return handle_set_prev_preset(false);
|
|
case BT_HAS_OP_SET_ACTIVE_PRESET_SYNC:
|
|
if ((has.features & BT_HAS_FEAT_PRESET_SYNC_SUPP) != 0) {
|
|
return handle_set_active_preset(buf, true);
|
|
} else {
|
|
return BT_HAS_ERR_PRESET_SYNC_NOT_SUPP;
|
|
}
|
|
case BT_HAS_OP_SET_NEXT_PRESET_SYNC:
|
|
if ((has.features & BT_HAS_FEAT_PRESET_SYNC_SUPP) != 0) {
|
|
return handle_set_next_preset(true);
|
|
} else {
|
|
return BT_HAS_ERR_PRESET_SYNC_NOT_SUPP;
|
|
}
|
|
case BT_HAS_OP_SET_PREV_PRESET_SYNC:
|
|
if ((has.features & BT_HAS_FEAT_PRESET_SYNC_SUPP) != 0) {
|
|
return handle_set_prev_preset(true);
|
|
} else {
|
|
return BT_HAS_ERR_PRESET_SYNC_NOT_SUPP;
|
|
}
|
|
};
|
|
|
|
return BT_HAS_ERR_INVALID_OPCODE;
|
|
}
|
|
|
|
static ssize_t write_control_point(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
|
const void *data, uint16_t len, uint16_t offset, uint8_t flags)
|
|
{
|
|
struct net_buf_simple buf;
|
|
uint8_t err;
|
|
|
|
LOG_DBG("conn %p attr %p data %p len %d offset %d flags 0x%02x", (void *)conn, attr, data,
|
|
len, offset, flags);
|
|
|
|
if (offset > 0) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
|
|
}
|
|
|
|
if (len == 0) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
}
|
|
|
|
net_buf_simple_init_with_data(&buf, (void *)data, len);
|
|
|
|
err = handle_control_point_op(conn, &buf);
|
|
if (err) {
|
|
LOG_WRN("handle_control_point_op err 0x%02x", err);
|
|
return BT_GATT_ERR(err);
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
int bt_has_preset_register(const struct bt_has_preset_register_param *param)
|
|
{
|
|
struct has_preset *preset;
|
|
size_t name_len;
|
|
|
|
CHECKIF(param == NULL) {
|
|
LOG_ERR("param is NULL");
|
|
return -EINVAL;
|
|
}
|
|
|
|
CHECKIF(param->index == BT_HAS_PRESET_INDEX_NONE) {
|
|
LOG_ERR("param->index is invalid");
|
|
return -EINVAL;
|
|
}
|
|
|
|
CHECKIF(param->name == NULL) {
|
|
LOG_ERR("param->name is NULL");
|
|
return -EINVAL;
|
|
}
|
|
|
|
name_len = strlen(param->name);
|
|
|
|
CHECKIF(name_len < BT_HAS_PRESET_NAME_MIN) {
|
|
LOG_ERR("param->name is too short (%zu < %u)", name_len, BT_HAS_PRESET_NAME_MIN);
|
|
return -EINVAL;
|
|
}
|
|
|
|
CHECKIF(name_len > BT_HAS_PRESET_NAME_MAX) {
|
|
LOG_WRN("param->name is too long (%zu > %u)", name_len, BT_HAS_PRESET_NAME_MAX);
|
|
}
|
|
|
|
CHECKIF(param->ops == NULL) {
|
|
LOG_ERR("param->ops is NULL");
|
|
return -EINVAL;
|
|
}
|
|
|
|
CHECKIF(param->ops->select == NULL) {
|
|
LOG_ERR("param->ops->select is NULL");
|
|
return -EINVAL;
|
|
}
|
|
|
|
preset = preset_lookup_index(param->index);
|
|
if (preset != NULL) {
|
|
return -EALREADY;
|
|
}
|
|
|
|
CHECKIF(!IS_ENABLED(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC) &&
|
|
(param->properties & BT_HAS_PROP_WRITABLE) > 0) {
|
|
LOG_ERR("Writable presets are not supported");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
preset = preset_alloc(param->index, param->properties, param->name, param->ops);
|
|
if (preset == NULL) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (preset == preset_get_tail()) {
|
|
update_last_preset_index_known(NULL, preset->index);
|
|
}
|
|
|
|
return bt_has_cp_generic_update(NULL, preset_get_prev_index(preset), preset->index,
|
|
preset->properties, preset->name, BT_HAS_IS_LAST);
|
|
}
|
|
|
|
int bt_has_preset_unregister(uint8_t index)
|
|
{
|
|
struct has_preset *preset;
|
|
int err;
|
|
|
|
CHECKIF(index == BT_HAS_PRESET_INDEX_NONE) {
|
|
LOG_ERR("index is invalid");
|
|
return -EINVAL;
|
|
}
|
|
|
|
preset = preset_lookup_index(index);
|
|
if (preset == NULL) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
if (preset == active_preset) {
|
|
return -EADDRINUSE;
|
|
}
|
|
|
|
err = bt_has_cp_preset_record_deleted(NULL, preset->index);
|
|
if (err != 0) {
|
|
return err;
|
|
}
|
|
|
|
if (preset == preset_get_tail()) {
|
|
update_last_preset_index_known(NULL, preset_get_prev_index(preset));
|
|
}
|
|
|
|
preset_free(preset);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int set_preset_availability(uint8_t index, bool available)
|
|
{
|
|
NET_BUF_SIMPLE_DEFINE(buf, sizeof(struct bt_has_cp_hdr) +
|
|
sizeof(struct bt_has_cp_preset_changed) + sizeof(uint8_t));
|
|
struct has_preset *preset;
|
|
uint8_t change_id;
|
|
|
|
CHECKIF(index == BT_HAS_PRESET_INDEX_NONE) {
|
|
LOG_ERR("index is invalid");
|
|
return -EINVAL;
|
|
}
|
|
|
|
preset = preset_lookup_index(index);
|
|
if (preset == NULL) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
if (is_preset_available(preset) == available) {
|
|
/* availability not changed */
|
|
return 0;
|
|
}
|
|
|
|
preset->properties ^= BT_HAS_PROP_AVAILABLE;
|
|
|
|
if (is_preset_available(preset)) {
|
|
change_id = BT_HAS_CHANGE_ID_PRESET_AVAILABLE;
|
|
} else {
|
|
change_id = BT_HAS_CHANGE_ID_PRESET_UNAVAILABLE;
|
|
}
|
|
|
|
preset_changed_prepare(&buf, change_id, BT_HAS_IS_LAST);
|
|
net_buf_simple_add_u8(&buf, preset->index);
|
|
|
|
return control_point_send_all(&buf);
|
|
}
|
|
|
|
int bt_has_preset_available(uint8_t index)
|
|
{
|
|
return set_preset_availability(index, true);
|
|
}
|
|
|
|
int bt_has_preset_unavailable(uint8_t index)
|
|
{
|
|
return set_preset_availability(index, false);
|
|
}
|
|
|
|
struct bt_has_preset_foreach_data {
|
|
bt_has_preset_func_t func;
|
|
void *user_data;
|
|
};
|
|
|
|
static uint8_t bt_has_preset_foreach_func(const struct has_preset *preset, void *user_data)
|
|
{
|
|
const struct bt_has_preset_foreach_data *data = user_data;
|
|
|
|
return data->func(preset->index, preset->properties, preset->name, data->user_data);
|
|
}
|
|
|
|
void bt_has_preset_foreach(uint8_t index, bt_has_preset_func_t func, void *user_data)
|
|
{
|
|
uint8_t start_index, end_index;
|
|
struct bt_has_preset_foreach_data data = {
|
|
.func = func,
|
|
.user_data = user_data,
|
|
};
|
|
|
|
if (index == BT_HAS_PRESET_INDEX_NONE) {
|
|
start_index = BT_HAS_PRESET_INDEX_FIRST;
|
|
end_index = BT_HAS_PRESET_INDEX_LAST;
|
|
} else {
|
|
start_index = end_index = index;
|
|
}
|
|
|
|
preset_foreach(start_index, end_index, bt_has_preset_foreach_func, &data);
|
|
}
|
|
|
|
int bt_has_preset_active_set(uint8_t index)
|
|
{
|
|
struct has_preset *preset;
|
|
|
|
if (index == BT_HAS_PRESET_INDEX_NONE) {
|
|
preset_set_active(NULL);
|
|
return 0;
|
|
}
|
|
|
|
preset = preset_lookup_index(index);
|
|
if (preset == NULL) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
if (!is_preset_available(preset)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
preset_set_active(preset);
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint8_t bt_has_preset_active_get(void)
|
|
{
|
|
if (active_preset == NULL) {
|
|
return BT_HAS_PRESET_INDEX_NONE;
|
|
}
|
|
|
|
return active_preset->index;
|
|
}
|
|
|
|
int bt_has_preset_name_change(uint8_t index, const char *name)
|
|
{
|
|
CHECKIF(name == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC)) {
|
|
return set_preset_name(index, name, strlen(name));
|
|
} else {
|
|
return -EOPNOTSUPP;
|
|
}
|
|
}
|
|
#endif /* CONFIG_BT_HAS_PRESET_SUPPORT */
|
|
|
|
static int has_features_register(const struct bt_has_features_param *features)
|
|
{
|
|
/* Initialize the supported features characteristic value */
|
|
has.features = features->type;
|
|
|
|
if (IS_ENABLED(CONFIG_BT_HAS_PRESET_SUPPORT)) {
|
|
has.features |= BT_HAS_FEAT_DYNAMIC_PRESETS;
|
|
|
|
if (features->preset_sync_support) {
|
|
if (features->type != BT_HAS_HEARING_AID_TYPE_BINAURAL) {
|
|
LOG_DBG("Preset sync support only available "
|
|
"for binaural hearing aid type");
|
|
return -EINVAL;
|
|
}
|
|
|
|
has.features |= BT_HAS_FEAT_PRESET_SYNC_SUPP;
|
|
}
|
|
|
|
if (features->independent_presets) {
|
|
if (features->type != BT_HAS_HEARING_AID_TYPE_BINAURAL) {
|
|
LOG_DBG("Independent presets only available "
|
|
"for binaural hearing aid type");
|
|
return -EINVAL;
|
|
}
|
|
|
|
has.features |= BT_HAS_FEAT_INDEPENDENT_PRESETS;
|
|
}
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC)) {
|
|
has.features |= BT_HAS_FEAT_WRITABLE_PRESETS_SUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE)
|
|
int bt_has_features_set(const struct bt_has_features_param *features)
|
|
{
|
|
int err;
|
|
|
|
if (!has.registered) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* Check whether any features will change, otherwise we don't want to notify clients */
|
|
if (FEATURE_DEVICE_TYPE_UNCHANGED(features->type) &&
|
|
FEATURE_SYNC_SUPPORT_UNCHANGED(features->preset_sync_support) &&
|
|
FEATURE_IND_PRESETS_UNCHANGED(features->independent_presets)) {
|
|
return 0;
|
|
}
|
|
|
|
err = has_features_register(features);
|
|
if (err != 0) {
|
|
LOG_DBG("Failed to register features");
|
|
return err;
|
|
}
|
|
|
|
notify(NULL, FLAG_FEATURES_CHANGED);
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_BT_HAS_FEATURES_NOTIFIABLE */
|
|
|
|
int bt_has_register(const struct bt_has_features_param *features)
|
|
{
|
|
int err;
|
|
|
|
LOG_DBG("features %p", features);
|
|
|
|
CHECKIF(!features) {
|
|
LOG_DBG("NULL params pointer");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (has.registered) {
|
|
return -EALREADY;
|
|
}
|
|
|
|
err = has_features_register(features);
|
|
if (err != 0) {
|
|
LOG_DBG("HAS service failed to register features: %d", err);
|
|
return err;
|
|
}
|
|
|
|
has_svc = (struct bt_gatt_service)BT_GATT_SERVICE(has_attrs);
|
|
err = bt_gatt_service_register(&has_svc);
|
|
if (err != 0) {
|
|
LOG_DBG("HAS service register failed: %d", err);
|
|
return err;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_HAS_FEATURES_NOTIFIABLE)) {
|
|
hearing_aid_features_attr = bt_gatt_find_by_uuid(has_svc.attrs, has_svc.attr_count,
|
|
BT_UUID_HAS_HEARING_AID_FEATURES);
|
|
__ASSERT_NO_MSG(hearing_aid_features_attr != NULL);
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_HAS_PRESET_SUPPORT)) {
|
|
preset_control_point_attr = bt_gatt_find_by_uuid(has_svc.attrs, has_svc.attr_count,
|
|
BT_UUID_HAS_PRESET_CONTROL_POINT);
|
|
__ASSERT_NO_MSG(preset_control_point_attr != NULL);
|
|
|
|
active_preset_index_attr = bt_gatt_find_by_uuid(has_svc.attrs, has_svc.attr_count,
|
|
BT_UUID_HAS_ACTIVE_PRESET_INDEX);
|
|
__ASSERT_NO_MSG(active_preset_index_attr != NULL);
|
|
}
|
|
|
|
#if defined(CONFIG_BT_HAS_PRESET_SUPPORT)
|
|
for (size_t i = 0; i < ARRAY_SIZE(preset_pool); i++) {
|
|
struct has_preset *preset = &preset_pool[i];
|
|
|
|
sys_slist_append(&preset_free_list, &preset->node);
|
|
}
|
|
#endif /* CONFIG_BT_HAS_PRESET_SUPPORT */
|
|
|
|
#if defined(CONFIG_BT_HAS_PRESET_SUPPORT) || defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE)
|
|
bt_conn_auth_info_cb_register(&auth_info_cb);
|
|
#endif /* CONFIG_BT_HAS_PRESET_SUPPORT || CONFIG_BT_HAS_FEATURES_NOTIFIABLE */
|
|
|
|
has.registered = true;
|
|
|
|
return 0;
|
|
}
|