diff --git a/include/zephyr/bluetooth/audio/has.h b/include/zephyr/bluetooth/audio/has.h index 3dc54a749e53ef..46be03e72ef2e9 100644 --- a/include/zephyr/bluetooth/audio/has.h +++ b/include/zephyr/bluetooth/audio/has.h @@ -68,8 +68,8 @@ enum bt_has_capabilities { BT_HAS_PRESET_SUPPORT = BIT(0), }; -/** @brief Structure for registering a Hearing Access Service instance. */ -struct bt_has_register_param { +/** @brief Structure for registering features of a Hearing Access Service instance. */ +struct bt_has_features_param { /** Hearing Aid Type value */ enum bt_has_hearing_aid_type type; @@ -341,11 +341,11 @@ struct bt_has_preset_register_param { /** * @brief Register the Hearing Access Service instance. * - * @param param Hearing Access Service register parameters. + * @param features Hearing Access Service register parameters. * * @return 0 if success, errno on failure. */ -int bt_has_register(const struct bt_has_register_param *param); +int bt_has_register(const struct bt_has_features_param *features); /** * @brief Register preset. @@ -470,6 +470,17 @@ static inline int bt_has_preset_active_clear(void) */ int bt_has_preset_name_change(uint8_t index, const char *name); +/** + * @brief Change the Hearing Aid Features. + * + * Change the hearing aid features. + * + * @param features The features to be set. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_has_features_set(const struct bt_has_features_param *features); + #ifdef __cplusplus } #endif diff --git a/samples/bluetooth/hap_ha/src/has_server.c b/samples/bluetooth/hap_ha/src/has_server.c index 3f694c98c2dd40..1a41a6342ab34c 100644 --- a/samples/bluetooth/hap_ha/src/has_server.c +++ b/samples/bluetooth/hap_ha/src/has_server.c @@ -75,7 +75,7 @@ int has_server_preset_init(void) return 0; } -static struct bt_has_register_param param = { +static struct bt_has_features_param features = { .type = BT_HAS_HEARING_AID_TYPE_MONAURAL, .preset_sync_support = false, .independent_presets = false @@ -86,12 +86,12 @@ int has_server_init(void) int err; if (IS_ENABLED(CONFIG_HAP_HA_HEARING_AID_BINAURAL)) { - param.type = BT_HAS_HEARING_AID_TYPE_BINAURAL; + features.type = BT_HAS_HEARING_AID_TYPE_BINAURAL; } else if (IS_ENABLED(CONFIG_HAP_HA_HEARING_AID_BANDED)) { - param.type = BT_HAS_HEARING_AID_TYPE_BANDED; + features.type = BT_HAS_HEARING_AID_TYPE_BANDED; } - err = bt_has_register(¶m); + err = bt_has_register(&features); if (err) { return err; } diff --git a/subsys/bluetooth/audio/Kconfig.has b/subsys/bluetooth/audio/Kconfig.has index 965a51e8fa553f..d26826e3612bdf 100644 --- a/subsys/bluetooth/audio/Kconfig.has +++ b/subsys/bluetooth/audio/Kconfig.has @@ -15,6 +15,12 @@ menuconfig BT_HAS if BT_HAS +config BT_HAS_FEATURES_NOTIFIABLE + bool "Hearing Aid Features Notifiable Support" + help + This option enables support for clients to subscribe for notifications + on the Hearing Aid Features characteristic. + config BT_HAS_PRESET_COUNT int "Preset record list size" default 2 @@ -33,6 +39,18 @@ config BT_HAS_PRESET_NAME_DYNAMIC help Enabling this option allows for runtime configuration of preset name. +config BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE + bool "Preset Control Point Notifiable support" + depends on BT_HAS_PRESET_SUPPORT && BT_EATT + help + This option enables support for clients to subscribe for notifications + on the Hearing Aid Preset Control Point characteristic. + +config BT_HAS_ACTIVE_PRESET_INDEX + def_bool BT_HAS_PRESET_SUPPORT + help + This option enables the Hearing Aid Active Preset Index characteristic. + endif # BT_HAS_PRESET_SUPPORT endif # BT_HAS diff --git a/subsys/bluetooth/audio/has.c b/subsys/bluetooth/audio/has.c index ea57eb24f9ab2f..c230a42ad27631 100644 --- a/subsys/bluetooth/audio/has.c +++ b/subsys/bluetooth/audio/has.c @@ -32,8 +32,30 @@ LOG_MODULE_REGISTER(bt_has, CONFIG_BT_HAS_LOG_LEVEL); */ #define BT_HAS_MAX_CONN 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)) + static struct bt_has has; +#if defined(CONFIG_BT_HAS_PRESET_SUPPORT) +static void preset_cp_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) +{ + LOG_DBG("attr %p value 0x%04x", attr, value); +} +#endif /* CONFIG_BT_HAS_PRESET_SUPPORT */ + +#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) 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); @@ -50,12 +72,14 @@ static ssize_t read_active_preset_index(struct bt_conn *conn, const struct bt_ga return bt_gatt_attr_read(conn, attr, buf, len, offset, &has.active_index, sizeof(has.active_index)); } +#endif /* CONFIG_BT_HAS_PRESET_SUPPORT */ -static void ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) +#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_PRESET_SUPPORT */ +#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) @@ -70,36 +94,87 @@ static ssize_t read_features(struct bt_conn *conn, const struct bt_gatt_attr *at sizeof(has.features)); } -/* Hearing Access Service GATT Attributes */ -static struct bt_gatt_attr has_attrs[] = { - BT_GATT_PRIMARY_SERVICE(BT_UUID_HAS), - BT_AUDIO_CHRC(BT_UUID_HAS_HEARING_AID_FEATURES, - BT_GATT_CHRC_READ, - BT_GATT_PERM_READ_ENCRYPT, +#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) - BT_AUDIO_CHRC(BT_UUID_HAS_PRESET_CONTROL_POINT, -#if defined(CONFIG_BT_EATT) - BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE | BT_GATT_CHRC_NOTIFY, +#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 - BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE, -#endif /* CONFIG_BT_EATT */ - BT_GATT_PERM_WRITE_ENCRYPT, - NULL, write_control_point, NULL), - BT_AUDIO_CCC(ccc_cfg_changed), - 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(ccc_cfg_changed), +#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; +#if defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE) +#define FEATURES_ATTR &has_attrs[2] +#if defined(CONFIG_BT_HAS_PRESET_SUPPORT) +#define PRESET_CONTROL_POINT_ATTR &has_attrs[5] +#define ACTIVE_PRESET_INDEX_ATTR &has_attrs[8] +#endif /* CONFIG_BT_HAS_PRESET_SUPPORT */ +#else #if defined(CONFIG_BT_HAS_PRESET_SUPPORT) #define PRESET_CONTROL_POINT_ATTR &has_attrs[4] #define ACTIVE_PRESET_INDEX_ATTR &has_attrs[7] +#endif /* CONFIG_BT_HAS_PRESET_SUPPORT */ +#endif /* CONFIG_BT_HAS_FEATURES_NOTIFIABLE */ + +#if defined(CONFIG_BT_HAS_PRESET_SUPPORT) + +enum { + FLAG_ACTIVE_INDEX_CHANGED, + FLAG_CONTROL_POINT_NOTIFY, +#if defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE) + FLAG_FEATURES_CHANGED, +#endif /* CONFIG_BT_HAS_FEATURES_NOTIFIABLE */ + FLAG_NUM, +}; static struct has_client { struct bt_conn *conn; @@ -110,11 +185,8 @@ static struct has_client { #endif /* CONFIG_BT_EATT */ } params; - struct { - bool active_index; - bool control_point; - uint8_t preset_changed_index_next; - } pending_ntf; + ATOMIC_DEFINE(flags, FLAG_NUM); + uint8_t preset_changed_index_next; struct bt_has_cp_read_presets_req read_presets_req; struct k_work control_point_work; } has_client_list[BT_HAS_MAX_CONN]; @@ -134,6 +206,12 @@ static struct has_preset { /* Number of registered presets */ static uint8_t has_preset_num; +#if defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE) +/* Features notification work */ +static void features_work_process(struct k_work *work); +static K_WORK_DEFINE(features_work, features_work_process); +#endif /* CONFIG_BT_HAS_FEATURES_NOTIFIABLE */ + /* Active preset notification work */ static void active_preset_work_process(struct k_work *work); static K_WORK_DEFINE(active_preset_work, active_preset_work_process); @@ -180,8 +258,11 @@ static void client_free(struct has_client *client) read_presets_req_free(client); - client->pending_ntf.control_point = false; - client->pending_ntf.active_index = false; + atomic_clear_bit(client->flags, FLAG_CONTROL_POINT_NOTIFY); + atomic_clear_bit(client->flags, FLAG_ACTIVE_INDEX_CHANGED); +#if defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE) + atomic_clear_bit(client->flags, FLAG_FEATURES_CHANGED); +#endif /* CONFIG_BT_HAS_FEATURES_NOTIFIABLE */ bt_conn_unref(client->conn); @@ -220,15 +301,22 @@ static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_ } /* Notify after reconnection */ - if (client->pending_ntf.active_index) { + if (atomic_test_and_clear_bit(client->flags, FLAG_ACTIVE_INDEX_CHANGED)) { /* Emit active preset notification */ k_work_submit(&active_preset_work); } - if (client->pending_ntf.control_point) { + if (atomic_test_and_clear_bit(client->flags, FLAG_CONTROL_POINT_NOTIFY)) { /* Emit preset changed notifications */ k_work_submit(&client->control_point_work); } + +#if defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE) + if (atomic_test_and_clear_bit(client->flags, FLAG_FEATURES_CHANGED)) { + /* Emit preset changed notifications */ + k_work_submit(&features_work); + } +#endif /* CONFIG_BT_HAS_FEATURES_NOTIFIABLE */ } static void connected(struct bt_conn *conn, uint8_t err) @@ -359,7 +447,7 @@ static void control_point_ntf_complete(struct bt_conn *conn, void *user_data) /* Resubmit if needed */ if (client != NULL && (read_presets_req_pending_cp(client) || - client->pending_ntf.control_point)) { + atomic_test_and_clear_bit(client->flags, FLAG_CONTROL_POINT_NOTIFY))) { k_work_submit(&client->control_point_work); } } @@ -378,7 +466,7 @@ static void control_point_ind_complete(struct bt_conn *conn, static int control_point_send(struct has_client *client, struct net_buf_simple *buf) { -#if defined(CONFIG_BT_EATT) +#if defined(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)) { client->params.ntf.attr = PRESET_CONTROL_POINT_ATTR; @@ -413,12 +501,12 @@ static int control_point_send_all(struct net_buf_simple *buf) if (!client->conn) { /* Mark preset changed operation as pending */ - client->pending_ntf.control_point = true; + atomic_set_bit(client->flags, FLAG_CONTROL_POINT_NOTIFY); /* For simplicity we simply start with the first index, * rather than keeping detailed logs of which clients * have knowledge of which presets */ - client->pending_ntf.preset_changed_index_next = BT_HAS_PRESET_INDEX_FIRST; + client->preset_changed_index_next = BT_HAS_PRESET_INDEX_FIRST; continue; } @@ -554,12 +642,12 @@ static void process_control_point_work(struct k_work *work) client->read_presets_req.start_index = preset->index + 1; client->read_presets_req.num_presets--; } - } else if (client->pending_ntf.control_point) { + } else if (atomic_test_and_clear_bit(client->flags, FLAG_CONTROL_POINT_NOTIFY)) { const struct has_preset *preset = NULL; const struct has_preset *next = NULL; bool is_last = true; - preset_foreach(client->pending_ntf.preset_changed_index_next, + preset_foreach(client->preset_changed_index_next, BT_HAS_PRESET_INDEX_LAST, preset_found, &preset); if (preset == NULL) { @@ -577,9 +665,9 @@ static void process_control_point_work(struct k_work *work) } if (err || is_last) { - client->pending_ntf.control_point = false; + atomic_clear_bit(client->flags, FLAG_CONTROL_POINT_NOTIFY); } else { - client->pending_ntf.preset_changed_index_next = preset->index + 1; + client->preset_changed_index_next = preset->index + 1; } } } @@ -721,15 +809,15 @@ static void active_preset_work_process(struct k_work *work) if (client->conn == NULL) { /* mark to notify on reconnect */ - client->pending_ntf.active_index = true; + atomic_set_bit(client->flags, FLAG_ACTIVE_INDEX_CHANGED); continue; - } - - err = bt_gatt_notify(client->conn, ACTIVE_PRESET_INDEX_ATTR, - &active_index, sizeof(active_index)); - if (err != 0) { - LOG_DBG("failed to notify for %p: %d", - client->conn, err); + } else if (atomic_test_and_clear_bit(client->flags, FLAG_ACTIVE_INDEX_CHANGED)) { + err = bt_gatt_notify(client->conn, ACTIVE_PRESET_INDEX_ATTR, &active_index, + sizeof(active_index)); + if (err != 0) { + LOG_DBG("failed to notify active_index for %p: %d", client->conn, + err); + } } } } @@ -739,6 +827,12 @@ static void preset_active_set(uint8_t index) if (index != has.active_index) { has.active_index = index; + for (size_t i = 0U; i < ARRAY_SIZE(has_client_list); i++) { + struct has_client *client = &has_client_list[i]; + /* mark to notify */ + atomic_set_bit(client->flags, FLAG_ACTIVE_INDEX_CHANGED); + } + /* Emit active preset notification */ k_work_submit(&active_preset_work); } @@ -917,6 +1011,7 @@ static uint8_t handle_control_point_op(struct bt_conn *conn, struct net_buf_simp return BT_HAS_ERR_INVALID_OPCODE; } +#if defined(CONFIG_BT_HAS_PRESET_SUPPORT) 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) { @@ -944,6 +1039,29 @@ static ssize_t write_control_point(struct bt_conn *conn, const struct bt_gatt_at return len; } +#endif /* CONFIG_BT_HAS_PRESET_SUPPORT */ + +#if defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE) +static void features_work_process(struct k_work *work) +{ + for (size_t i = 0U; i < ARRAY_SIZE(has_client_list); i++) { + struct has_client *client = &has_client_list[i]; + int err; + + if (client->conn == NULL) { + /* mark to notify on reconnect */ + atomic_set_bit(client->flags, FLAG_FEATURES_CHANGED); + continue; + } else if (atomic_test_and_clear_bit(client->flags, FLAG_CONTROL_POINT_NOTIFY)) { + err = bt_gatt_notify(client->conn, FEATURES_ATTR, &has.features, + sizeof(has.features)); + if (err != 0) { + LOG_DBG("failed to notify features for %p: %d", client->conn, err); + } + } + } +} +#endif /* CONFIG_BT_HAS_FEATURES_NOTIFIABLE */ int bt_has_preset_register(const struct bt_has_preset_register_param *param) { @@ -1153,30 +1271,16 @@ int bt_has_preset_name_change(uint8_t index, const char *name) } #endif /* CONFIG_BT_HAS_PRESET_SUPPORT */ -int bt_has_register(const struct bt_has_register_param *param) +static int has_features_register(const struct bt_has_features_param *features) { - static bool registered; - int err; - - LOG_DBG("param %p", param); - - CHECKIF(!param) { - LOG_DBG("NULL params pointer"); - return -EINVAL; - } - - if (registered) { - return -EALREADY; - } - /* Initialize the supported features characteristic value */ - has.features = param->type; + has.features = features->type; if (IS_ENABLED(CONFIG_BT_HAS_PRESET_SUPPORT)) { has.features |= BT_HAS_FEAT_DYNAMIC_PRESETS; - if (param->preset_sync_support) { - if (param->type != BT_HAS_HEARING_AID_TYPE_BINAURAL) { + 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; @@ -1185,8 +1289,8 @@ int bt_has_register(const struct bt_has_register_param *param) has.features |= BT_HAS_FEAT_PRESET_SYNC_SUPP; } - if (param->independent_presets) { - if (param->type != BT_HAS_HEARING_AID_TYPE_BINAURAL) { + 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; @@ -1200,6 +1304,83 @@ int bt_has_register(const struct bt_has_register_param *param) 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) +{ + uint8_t tmp_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; + } + + tmp_features = has.features; + + err = has_features_register(features); + if (err != 0) { + LOG_DBG("Failed to register features"); + return err; + } + + bool tmp_pending_ntf_features[ARRAY_SIZE(has_client_list)]; + + for (size_t i = 0U; i < ARRAY_SIZE(has_client_list); i++) { + struct has_client *client = &has_client_list[i]; + /* save old state */ + tmp_pending_ntf_features[i] = atomic_test_bit(client->flags, FLAG_FEATURES_CHANGED); + /* mark to notify */ + atomic_set_bit(client->flags, FLAG_FEATURES_CHANGED); + } + + err = k_work_submit(&features_work); + if (err < 0) { + /* restore old values */ + for (size_t i = 0U; i < ARRAY_SIZE(has_client_list); i++) { + struct has_client *client = &has_client_list[i]; + + atomic_set_bit_to(client->flags, FLAG_FEATURES_CHANGED, + tmp_pending_ntf_features[i]); + } + has.features = tmp_features; + + return err; + } + + 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) { @@ -1207,7 +1388,7 @@ int bt_has_register(const struct bt_has_register_param *param) return err; } - registered = true; + has.registered = true; return 0; } diff --git a/subsys/bluetooth/audio/has_internal.h b/subsys/bluetooth/audio/has_internal.h index 09d98ce250fe59..96fafda9473300 100644 --- a/subsys/bluetooth/audio/has_internal.h +++ b/subsys/bluetooth/audio/has_internal.h @@ -52,6 +52,9 @@ struct bt_has { /** Active preset index value */ uint8_t active_index; + + /* Whether the service has been registered or not */ + bool registered; }; struct bt_has_cp_hdr { diff --git a/subsys/bluetooth/audio/shell/has.c b/subsys/bluetooth/audio/shell/has.c index 480059ef0f04fc..c6a7e7ccc06817 100644 --- a/subsys/bluetooth/audio/shell/has.c +++ b/subsys/bluetooth/audio/shell/has.c @@ -75,10 +75,47 @@ static int cmd_preset_unreg(const struct shell *sh, size_t argc, char **argv) return 0; } +static int cmd_features_set(const struct shell *sh, size_t argc, char **argv) +{ + int err; + struct bt_has_features_param param = { + .type = BT_HAS_HEARING_AID_TYPE_MONAURAL, + .preset_sync_support = false, + .independent_presets = false + }; + + for (size_t argn = 1; argn < argc; argn++) { + const char *arg = argv[argn]; + + if (strcmp(arg, "binaural") == 0) { + param.type = BT_HAS_HEARING_AID_TYPE_BINAURAL; + } else if (strcmp(arg, "monaural") == 0) { + param.type = BT_HAS_HEARING_AID_TYPE_MONAURAL; + } else if (strcmp(arg, "banded") == 0) { + param.type = BT_HAS_HEARING_AID_TYPE_BANDED; + } else if (strcmp(arg, "sync") == 0) { + param.preset_sync_support = true; + } else if (strcmp(arg, "independent") == 0) { + param.independent_presets = true; + } else { + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + } + + err = bt_has_features_set(¶m); + if (err != 0) { + shell_error(sh, "Could not set features: %d", err); + return err; + } + + return 0; +} + static int cmd_has_register(const struct shell *sh, size_t argc, char **argv) { int err; - struct bt_has_register_param param = { + struct bt_has_features_param param = { .type = BT_HAS_HEARING_AID_TYPE_MONAURAL, .preset_sync_support = false, .independent_presets = false @@ -271,6 +308,9 @@ SHELL_STATIC_SUBCMD_SET_CREATE(has_cmds, SHELL_CMD_ARG(preset-active-clear, NULL, "Clear selected preset", cmd_preset_active_clear, 1, 0), SHELL_CMD_ARG(set-name, NULL, "Set preset name ", cmd_preset_name_set, 3, 0), + SHELL_CMD_ARG(features-set, NULL, "Set hearing aid features " + "[binaural | monaural(default) | banded] [sync] [independent]", + cmd_features_set, 1, 3), SHELL_SUBCMD_SET_END ); diff --git a/tests/bluetooth/shell/audio.conf b/tests/bluetooth/shell/audio.conf index db9643ba3709dc..4da57268063291 100644 --- a/tests/bluetooth/shell/audio.conf +++ b/tests/bluetooth/shell/audio.conf @@ -135,6 +135,8 @@ CONFIG_BT_HAS=y CONFIG_BT_HAS_PRESET_NAME_DYNAMIC=y CONFIG_BT_HAS_PRESET_COUNT=4 CONFIG_BT_HAS_CLIENT=y +CONFIG_BT_HAS_FEATURES_NOTIFIABLE=y +CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE=y # Common Audio Profile CONFIG_BT_CAP_ACCEPTOR=y diff --git a/tests/bsim/bluetooth/audio/prj.conf b/tests/bsim/bluetooth/audio/prj.conf index 15afe2770fb375..54b69aef8add92 100644 --- a/tests/bsim/bluetooth/audio/prj.conf +++ b/tests/bsim/bluetooth/audio/prj.conf @@ -108,6 +108,7 @@ CONFIG_BT_BAP_BROADCAST_ASSISTANT=y # Hearing Access CONFIG_BT_HAS=y CONFIG_BT_HAS_CLIENT=y +CONFIG_BT_HAS_FEATURES_NOTIFIABLE=y # Common Audio Profile CONFIG_BT_CAP_ACCEPTOR=y diff --git a/tests/bsim/bluetooth/audio/src/has_test.c b/tests/bsim/bluetooth/audio/src/has_test.c index 177d07b988a3fd..f8178b61d2622c 100644 --- a/tests/bsim/bluetooth/audio/src/has_test.c +++ b/tests/bsim/bluetooth/audio/src/has_test.c @@ -29,7 +29,7 @@ static const struct bt_has_preset_ops preset_ops = { static void test_main(void) { - struct bt_has_register_param has_param = {0}; + struct bt_has_features_param has_param = {0}; struct bt_has_preset_register_param preset_param; int err; @@ -50,7 +50,8 @@ static void test_main(void) printk("Advertising successfully started\n"); - has_param.type = BT_HAS_HEARING_AID_TYPE_MONAURAL; + has_param.type = BT_HAS_HEARING_AID_TYPE_BINAURAL; + has_param.preset_sync_support = true; err = bt_has_register(&has_param); if (err) { @@ -58,6 +59,15 @@ static void test_main(void) return; } + has_param.type = BT_HAS_HEARING_AID_TYPE_MONAURAL; + has_param.preset_sync_support = false; + + err = bt_has_features_set(&has_param); + if (err) { + FAIL("HAS register failed (err %d)\n", err); + return; + } + preset_param.index = test_preset_index_5; preset_param.properties = test_preset_properties; preset_param.name = test_preset_name_5;