diff --git a/include/zephyr/bluetooth/audio/has.h b/include/zephyr/bluetooth/audio/has.h index a6a47ee8adb..86d4f879e1d 100644 --- a/include/zephyr/bluetooth/audio/has.h +++ b/include/zephyr/bluetooth/audio/has.h @@ -231,6 +231,16 @@ struct bt_has_preset_ops { * becomes active by calling @ref bt_has_preset_active_set. */ int (*select)(uint8_t index, bool sync); + + /** + * @brief Preset name changed callback + * + * This callback is called when the name of the preset identified by @p index has changed. + * + * @param index Preset index that name has been changed. + * @param name Preset current name. + */ + void (*name_changed)(uint8_t index, const char *name); }; /** @brief Register structure for preset. */ @@ -374,6 +384,18 @@ static inline int bt_has_preset_active_clear(void) return bt_has_preset_active_set(BT_HAS_PRESET_INDEX_NONE); } +/** + * @brief Change the Preset Name. + * + * Change the name of the preset identified by @p index. + * + * @param index The index of the preset to change the name of. + * @param name Name to write. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_has_preset_name_change(uint8_t index, const char *name); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/audio/has.c b/subsys/bluetooth/audio/has.c index f1baf24dc77..325ed96ab3d 100644 --- a/subsys/bluetooth/audio/has.c +++ b/subsys/bluetooth/audio/has.c @@ -539,6 +539,85 @@ static uint8_t handle_read_preset_req(struct bt_conn *conn, struct net_buf_simpl return 0; } +static int set_preset_name(uint8_t index, const char *name, size_t len) +{ + struct has_preset *preset = NULL; + + BT_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(preset, 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_get(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 active_preset_work_process(struct k_work *work) { const uint8_t active_index = bt_has_preset_active_get(); @@ -695,6 +774,11 @@ static uint8_t handle_control_point_op(struct bt_conn *conn, struct net_buf_simp 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); + } + break; case BT_HAS_OP_SET_ACTIVE_PRESET: return handle_set_active_preset(buf, false); case BT_HAS_OP_SET_NEXT_PRESET: @@ -945,6 +1029,19 @@ uint8_t bt_has_preset_active_get(void) { return has.active_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_init(const struct device *dev) diff --git a/subsys/bluetooth/shell/has.c b/subsys/bluetooth/shell/has.c index e31ab7f6d86..76da338ef2a 100644 --- a/subsys/bluetooth/shell/has.c +++ b/subsys/bluetooth/shell/has.c @@ -22,8 +22,14 @@ static int preset_select(uint8_t index, bool sync) return 0; } +static void preset_name_changed(uint8_t index, const char *name) +{ + shell_print(ctx_shell, "Preset name changed index %u name %s", index, name); +} + static const struct bt_has_preset_ops preset_ops = { .select = preset_select, + .name_changed = preset_name_changed, }; static int cmd_preset_reg(const struct shell *sh, size_t argc, char **argv) @@ -179,6 +185,25 @@ static int cmd_preset_active_clear(const struct shell *sh, size_t argc, char **a return 0; } +static int cmd_preset_name_set(const struct shell *sh, size_t argc, char **argv) +{ + int err = 0; + const uint8_t index = shell_strtoul(argv[1], 16, &err); + + if (err < 0) { + shell_print(sh, "Invalid command parameter (err %d)", err); + return err; + } + + err = bt_has_preset_name_change(index, argv[2]); + if (err < 0) { + shell_print(sh, "Preset name change failed (err %d)", err); + return err; + } + + return 0; +} + static int cmd_has(const struct shell *sh, size_t argc, char **argv) { if (argc > 1) { @@ -204,6 +229,7 @@ SHELL_STATIC_SUBCMD_SET_CREATE(has_cmds, SHELL_CMD_ARG(preset-active-get, NULL, "Get active preset", cmd_preset_active_get, 1, 0), 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_SUBCMD_SET_END );