Skip to content

Commit

Permalink
Bluetooth: audio: Add HAS client support for preset switching
Browse files Browse the repository at this point in the history
This adds client support for switching the active preset along with bsim
tests implementation.

Signed-off-by: Mariusz Skamra <[email protected]>
  • Loading branch information
MariuszSkamra authored and carlescufi committed Aug 18, 2022
1 parent 2ddd221 commit 541d946
Show file tree
Hide file tree
Showing 4 changed files with 394 additions and 10 deletions.
52 changes: 47 additions & 5 deletions include/zephyr/bluetooth/audio/has.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,17 @@ struct bt_has_client_cb {
/**
* @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.
* Optional callback called when the active preset is changed by the remote server when the
* preset switch procedure is complete. 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 err 0 on success, ATT error or negative errno otherwise.
* @param index Active preset index.
*/
void (*preset_switch)(struct bt_has *has, uint8_t index);
void (*preset_switch)(struct bt_has *has, int err, uint8_t index);

/**
* @brief Callback function for presets read operation.
Expand Down Expand Up @@ -213,6 +215,46 @@ int bt_has_client_conn_get(const struct bt_has *has, struct bt_conn **conn);
*/
int bt_has_client_presets_read(struct bt_has *has, uint8_t index, uint8_t max_count);

/**
* @brief Set Active Preset.
*
* Client procedure to set preset identified by @p index as active.
* The status is returned in the @ref bt_has_client_cb.preset_switch callback.
*
* @param has Pointer to the Hearing Access Service object.
* @param index Preset index to activate.
* @param sync Request active preset synchronization in set.
*
* @return 0 in case of success or negative value in case of error.
*/
int bt_has_client_preset_set(struct bt_has *has, uint8_t index, bool sync);

/**
* @brief Activate Next Preset.
*
* Client procedure to set next available preset as active.
* The status is returned in the @ref bt_has_client_cb.preset_switch callback.
*
* @param has Pointer to the Hearing Access Service object.
* @param sync Request active preset synchronization in set.
*
* @return 0 in case of success or negative value in case of error.
*/
int bt_has_client_preset_next(struct bt_has *has, bool sync);

/**
* @brief Activate Previous Preset.
*
* Client procedure to set previous available preset as active.
* The status is returned in the @ref bt_has_client_cb.preset_switch callback.
*
* @param has Pointer to the Hearing Access Service object.
* @param sync Request active preset synchronization in set.
*
* @return 0 in case of success or negative value in case of error.
*/
int bt_has_client_preset_prev(struct bt_has *has, bool sync);

/** @brief Preset operations structure. */
struct bt_has_preset_ops {
/**
Expand Down
130 changes: 128 additions & 2 deletions subsys/bluetooth/audio/has_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ static void discover_complete(struct has_inst *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);
client_cb->preset_switch(&inst->has, 0, inst->has.active_index);
}
}

Expand Down Expand Up @@ -341,6 +341,53 @@ static int read_presets_req(struct has_inst *inst, uint8_t start_index, uint8_t
return cp_write(inst, &buf, read_presets_req_cb);
}

static void set_active_preset_cb(struct bt_conn *conn, uint8_t 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 err 0x%02x param %p", (void *)conn, err, params);

atomic_clear_bit(inst->flags, HAS_CP_OPERATION_IN_PROGRESS);

if (err) {
client_cb->preset_switch(&inst->has, err, inst->has.active_index);
}
}

static int preset_set(struct has_inst *inst, uint8_t opcode, uint8_t index)
{
struct bt_has_cp_hdr *hdr;
struct bt_has_cp_set_active_preset *req;

NET_BUF_SIMPLE_DEFINE(buf, sizeof(*hdr) + sizeof(*req));

BT_DBG("conn %p opcode 0x%02x index 0x%02x", (void *)inst->conn, opcode, index);

hdr = net_buf_simple_add(&buf, sizeof(*hdr));
hdr->opcode = opcode;
req = net_buf_simple_add(&buf, sizeof(*req));
req->index = index;

return cp_write(inst, &buf, set_active_preset_cb);
}

static int preset_set_next_or_prev(struct has_inst *inst, uint8_t opcode)
{
struct bt_has_cp_hdr *hdr;

NET_BUF_SIMPLE_DEFINE(buf, sizeof(*hdr));

BT_DBG("conn %p opcode 0x%02x", (void *)inst->conn, opcode);

hdr = net_buf_simple_add(&buf, sizeof(*hdr));
hdr->opcode = opcode;

return cp_write(inst, &buf, set_active_preset_cb);
}

static uint8_t active_index_update(struct has_inst *inst, const void *data, uint16_t len)
{
struct net_buf_simple buf;
Expand Down Expand Up @@ -397,7 +444,7 @@ static uint8_t active_preset_notify_cb(struct bt_conn *conn,
}

if (client_cb && client_cb->preset_switch && inst->has.active_index != prev) {
client_cb->preset_switch(&inst->has, inst->has.active_index);
client_cb->preset_switch(&inst->has, 0, inst->has.active_index);
}

return BT_GATT_ITER_CONTINUE;
Expand Down Expand Up @@ -890,6 +937,85 @@ int bt_has_client_presets_read(struct bt_has *has, uint8_t start_index, uint8_t
return err;
}

int bt_has_client_preset_set(struct bt_has *has, uint8_t index, bool sync)
{
struct has_inst *inst = HAS_INST(has);
uint8_t opcode;

BT_DBG("conn %p index 0x%02x", (void *)inst->conn, index);

if (!inst->conn) {
return -ENOTCONN;
}

CHECKIF(index == BT_HAS_PRESET_INDEX_NONE) {
return -EINVAL;
}

if (sync && (inst->has.features & BT_HAS_FEAT_PRESET_SYNC_SUPP) == 0) {
return -EOPNOTSUPP;
}

if (atomic_test_bit(inst->flags, HAS_DISCOVER_IN_PROGRESS) ||
atomic_test_and_set_bit(inst->flags, HAS_CP_OPERATION_IN_PROGRESS)) {
return -EBUSY;
}

opcode = sync ? BT_HAS_OP_SET_ACTIVE_PRESET_SYNC : BT_HAS_OP_SET_ACTIVE_PRESET;

return preset_set(inst, opcode, index);
}

int bt_has_client_preset_next(struct bt_has *has, bool sync)
{
struct has_inst *inst = HAS_INST(has);
uint8_t opcode;

BT_DBG("conn %p sync %d", (void *)inst->conn, sync);

if (!inst->conn) {
return -ENOTCONN;
}

if (sync && (inst->has.features & BT_HAS_FEAT_PRESET_SYNC_SUPP) == 0) {
return -EOPNOTSUPP;
}

if (atomic_test_bit(inst->flags, HAS_DISCOVER_IN_PROGRESS) ||
atomic_test_and_set_bit(inst->flags, HAS_CP_OPERATION_IN_PROGRESS)) {
return -EBUSY;
}

opcode = sync ? BT_HAS_OP_SET_NEXT_PRESET_SYNC : BT_HAS_OP_SET_NEXT_PRESET;

return preset_set_next_or_prev(inst, opcode);
}

int bt_has_client_preset_prev(struct bt_has *has, bool sync)
{
struct has_inst *inst = HAS_INST(has);
uint8_t opcode;

BT_DBG("conn %p sync %d", (void *)inst->conn, sync);

if (!inst->conn) {
return -ENOTCONN;
}

if (sync && (inst->has.features & BT_HAS_FEAT_PRESET_SYNC_SUPP) == 0) {
return -EOPNOTSUPP;
}

if (atomic_test_bit(inst->flags, HAS_DISCOVER_IN_PROGRESS) ||
atomic_test_and_set_bit(inst->flags, HAS_CP_OPERATION_IN_PROGRESS)) {
return -EBUSY;
}

opcode = sync ? BT_HAS_OP_SET_PREV_PRESET_SYNC : BT_HAS_OP_SET_PREV_PRESET;

return preset_set_next_or_prev(inst, opcode);
}

static void disconnected(struct bt_conn *conn, uint8_t reason)
{
struct has_inst *inst = inst_by_conn(conn);
Expand Down
123 changes: 121 additions & 2 deletions subsys/bluetooth/shell/has_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,13 @@ static void has_client_discover_cb(struct bt_conn *conn, int err, struct bt_has
inst = has;
}

static void has_client_preset_switch_cb(struct bt_has *has, uint8_t index)
static void has_client_preset_switch_cb(struct bt_has *has, int err, uint8_t index)
{
shell_print(ctx_shell, "HAS %p preset switch index 0x%02x", has, index);
if (err != 0) {
shell_error(ctx_shell, "HAS %p preset switch error (err %d)", has, err);
} else {
shell_print(ctx_shell, "HAS %p preset switch index 0x%02x", has, index);
}
}

static void has_client_preset_read_rsp_cb(struct bt_has *has, int err,
Expand Down Expand Up @@ -127,6 +131,118 @@ static int cmd_has_client_read_presets(const struct shell *sh, size_t argc, char
return err;
}

static int cmd_has_client_preset_set(const struct shell *sh, size_t argc, char **argv)
{
bool sync = false;
uint8_t index;
int err = 0;

index = shell_strtoul(argv[1], 16, &err);
if (err < 0) {
shell_error(sh, "Invalid command parameter (err %d)", err);
return -ENOEXEC;
}

for (size_t argn = 2; argn < argc; argn++) {
const char *arg = argv[argn];

if (!strcmp(arg, "sync")) {
sync = true;
} else {
shell_error(sh, "Invalid argument");
return -ENOEXEC;
}
}

if (default_conn == NULL) {
shell_error(sh, "Not connected");
return -ENOEXEC;
}

if (!inst) {
shell_error(sh, "No instance discovered");
return -ENOEXEC;
}

err = bt_has_client_preset_set(inst, index, sync);
if (err != 0) {
shell_error(sh, "bt_has_client_preset_switch (err %d)", err);
return -ENOEXEC;
}

return 0;
}

static int cmd_has_client_preset_next(const struct shell *sh, size_t argc, char **argv)
{
bool sync = false;
int err;

for (size_t argn = 1; argn < argc; argn++) {
const char *arg = argv[argn];

if (!strcmp(arg, "sync")) {
sync = true;
} else {
shell_error(sh, "Invalid argument");
return -ENOEXEC;
}
}

if (default_conn == NULL) {
shell_error(sh, "Not connected");
return -ENOEXEC;
}

if (!inst) {
shell_error(sh, "No instance discovered");
return -ENOEXEC;
}

err = bt_has_client_preset_next(inst, sync);
if (err != 0) {
shell_error(sh, "bt_has_client_preset_next (err %d)", err);
return -ENOEXEC;
}

return err;
}

static int cmd_has_client_preset_prev(const struct shell *sh, size_t argc, char **argv)
{
bool sync = false;
int err;

for (size_t argn = 1; argn < argc; argn++) {
const char *arg = argv[argn];

if (!strcmp(arg, "sync")) {
sync = true;
} else {
shell_error(sh, "Invalid argument");
return -ENOEXEC;
}
}

if (default_conn == NULL) {
shell_error(sh, "Not connected");
return -ENOEXEC;
}

if (!inst) {
shell_error(sh, "No instance discovered");
return -ENOEXEC;
}

err = bt_has_client_preset_prev(inst, sync);
if (err != 0) {
shell_error(sh, "bt_has_client_preset_prev (err %d)", err);
return -ENOEXEC;
}

return err;
}

static int cmd_has_client(const struct shell *sh, size_t argc, char **argv)
{
if (argc > 1) {
Expand All @@ -145,6 +261,9 @@ SHELL_STATIC_SUBCMD_SET_CREATE(has_client_cmds,
SHELL_CMD_ARG(discover, NULL, HELP_NONE, cmd_has_client_discover, 1, 0),
SHELL_CMD_ARG(presets-read, NULL, "<start_index_hex> <max_count_dec>",
cmd_has_client_read_presets, 3, 0),
SHELL_CMD_ARG(preset-set, NULL, "<index_hex> [sync]", cmd_has_client_preset_set, 2, 1),
SHELL_CMD_ARG(preset-next, NULL, "[sync]", cmd_has_client_preset_next, 1, 1),
SHELL_CMD_ARG(preset-prev, NULL, "[sync]", cmd_has_client_preset_prev, 1, 1),
SHELL_SUBCMD_SET_END
);

Expand Down
Loading

0 comments on commit 541d946

Please sign in to comment.