diff --git a/subsys/bluetooth/shell/CMakeLists.txt b/subsys/bluetooth/shell/CMakeLists.txt index 94b28c8a5a1..38442110a7e 100644 --- a/subsys/bluetooth/shell/CMakeLists.txt +++ b/subsys/bluetooth/shell/CMakeLists.txt @@ -45,6 +45,10 @@ zephyr_library_sources_ifdef( CONFIG_BT_CSIS csis.c ) +zephyr_library_sources_ifdef( + CONFIG_BT_CSIS_CLIENT + csis_client.c + ) if(CONFIG_BT_CTLR AND CONFIG_BT_LL_SW_SPLIT) zephyr_library_sources( diff --git a/subsys/bluetooth/shell/csis_client.c b/subsys/bluetooth/shell/csis_client.c new file mode 100644 index 00000000000..d1666c25f32 --- /dev/null +++ b/subsys/bluetooth/shell/csis_client.c @@ -0,0 +1,574 @@ +/** + * @file + * @brief Shell APIs for Bluetooth CSIS client + * + * Copyright (c) 2020 Bose Corporation + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "bt.h" + +#include "../audio/csis_client.h" + +static uint8_t members_found; +static struct k_work_delayable discover_members_timer; +static struct bt_csis_client_set_member set_members[CONFIG_BT_MAX_CONN]; +struct bt_csis_client_set *cur_set; +const struct bt_csis_client_set_member *locked_members[CONFIG_BT_MAX_CONN]; + +static bool is_discovered(const bt_addr_le_t *addr) +{ + for (int i = 0; i < members_found; i++) { + if (bt_addr_le_cmp(addr, &set_members[i].addr) == 0) { + return true; + } + } + return false; +} + +static void connected_cb(struct bt_conn *conn, uint8_t err) +{ + char addr[BT_ADDR_LE_STR_LEN]; + + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + + if (err != 0) { + shell_error(ctx_shell, "Failed to connect to %s (%u)", + addr, err); + return; + } + + shell_print(ctx_shell, "[%u]: Connected to %s", + bt_conn_index(conn), addr); + + /* TODO: Handle RPAs */ + + if (members_found == 0) { + shell_print(ctx_shell, "Assuming member[0] connected"); + set_members[0].conn = conn; + bt_addr_le_copy(&set_members[0].addr, bt_conn_get_dst(conn)); + members_found = 1; + return; + } + + for (uint8_t i = 0; i < members_found; i++) { + if (bt_addr_le_cmp(bt_conn_get_dst(conn), + &set_members[i].addr) == 0) { + set_members[i].conn = conn; + shell_print(ctx_shell, "Member[%u] connected", i); + return; + } + } + shell_warn(ctx_shell, "[%u] connected but was not member of set", + bt_conn_index(conn)); +} + +static struct bt_conn_cb conn_callbacks = { + .connected = connected_cb, +}; + +static void csis_discover_cb(struct bt_conn *conn, int err, uint8_t set_count) +{ + if (err != 0) { + shell_error(ctx_shell, "discover failed (%d)", err); + return; + } + + if (set_count == 0) { + shell_warn(ctx_shell, "Device has no sets"); + return; + } + + for (size_t i = 0; i < ARRAY_SIZE(set_members); i++) { + if (set_members[i].conn == conn) { + shell_print(ctx_shell, "Found %u sets on member[%u]", + set_count, i); + } + } +} + +static void csis_client_discover_sets_cb(struct bt_conn *conn, int err, + uint8_t set_count, + struct bt_csis_client_set *sets) +{ + if (err != 0) { + shell_error(ctx_shell, "Discover sets failed (%d)", err); + return; + } + + for (uint8_t i = 0; i < set_count; i++) { + shell_print(ctx_shell, "Set size %d (pointer: %p)", + sets[i].set_size, &sets[i]); + } + + for (size_t i = 0; i < ARRAY_SIZE(set_members); i++) { + struct bt_csis_client_set_member *member; + + member = &set_members[i]; + + if (member->conn == conn) { + for (uint8_t j = 0; j < set_count; j++) { + (void)memcpy(&member->sets[j], &sets[j], + sizeof(sets[j])); + } + } + } +} + +static void csis_client_lock_set_cb(int err) +{ + if (err != 0) { + shell_error(ctx_shell, "Lock sets failed (%d)", err); + return; + } + + shell_print(ctx_shell, "Set locked"); +} + +static void csis_client_release_set_cb(int err) +{ + if (err != 0) { + shell_error(ctx_shell, "Lock sets failed (%d)", err); + return; + } + + shell_print(ctx_shell, "Set released"); +} + +static void csis_client_lock_cb(struct bt_conn *conn, int err, uint8_t inst_idx) +{ + if (err != 0) { + shell_error(ctx_shell, "Device (index 0x%02x) lock failed (%d)", + inst_idx, err); + } + + shell_print(ctx_shell, "Device (index 0x%02x) locked", inst_idx); +} + +static void csis_client_release_cb(struct bt_conn *conn, int err, + uint8_t inst_idx) +{ + if (err != 0) { + shell_error(ctx_shell, "Device (index 0x%02x) release " + "failed (%d)", inst_idx, err); + } + + shell_print(ctx_shell, "Device (index 0x%02x) released", inst_idx); +} + +static void csis_client_lock_get_cb(struct bt_conn *conn, int err, + uint8_t inst_idx, bool locked) +{ + if (err != 0) { + shell_error(ctx_shell, "Device (index 0x%02x) lock get " + "failed (%d)", inst_idx, err); + } + + shell_print(ctx_shell, "Device (index 0x%02x) lock value %d", + inst_idx, locked); +} + +static struct bt_csis_client_cb cbs = { + .lock_set = csis_client_lock_set_cb, + .release_set = csis_client_release_set_cb, + .sets = csis_client_discover_sets_cb, + .discover = csis_discover_cb, + .lock = csis_client_lock_cb, + .release = csis_client_release_cb, + .lock_read = csis_client_lock_get_cb +}; + +static bool csis_found(struct bt_data *data, void *user_data) +{ + if (bt_csis_client_is_set_member(cur_set->set_sirk.value, data)) { + bt_addr_le_t *addr = user_data; + char addr_str[BT_ADDR_LE_STR_LEN]; + + bt_addr_le_to_str(addr, addr_str, sizeof(addr_str)); + shell_print(ctx_shell, "Found CSIS advertiser with address %s", + addr_str); + + if (is_discovered(addr)) { + shell_print(ctx_shell, "Set member already found"); + /* Stop parsing */ + return false; + } + + bt_addr_le_copy(&set_members[members_found++].addr, addr); + + shell_print(ctx_shell, "Found member (%u / %u)", + members_found, cur_set->set_size); + + if (members_found == cur_set->set_size) { + int err; + + (void)k_work_cancel_delayable(&discover_members_timer); + + err = bt_le_scan_stop(); + if (err != 0) { + shell_error(ctx_shell, + "Failed to stop scan: %d", + err); + } + } + + /* Stop parsing */ + return false; + } + /* Continue parsing */ + return true; +} + +static void csis_client_scan_recv(const struct bt_le_scan_recv_info *info, + struct net_buf_simple *ad) +{ + /* We're only interested in connectable events */ + if (info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE) { + if (cur_set != NULL) { + bt_data_parse(ad, csis_found, (void *)info->addr); + } + } +} + +static struct bt_le_scan_cb csis_client_scan_callbacks = { + .recv = csis_client_scan_recv +}; + +static void discover_members_timer_handler(struct k_work *work) +{ + int err; + + shell_error(ctx_shell, "Could not find all members (%u / %u)", + members_found, cur_set->set_size); + + err = bt_le_scan_stop(); + if (err != 0) { + shell_error(ctx_shell, "Failed to stop scan: %d", err); + } +} + +static int cmd_csis_client_init(const struct shell *sh, size_t argc, + char *argv[]) +{ + static bool initialized; + + if (initialized) { + return -EALREADY; + } + + k_work_init_delayable(&discover_members_timer, + discover_members_timer_handler); + bt_le_scan_cb_register(&csis_client_scan_callbacks); + bt_csis_client_register_cb(&cbs); + bt_conn_cb_register(&conn_callbacks); + + initialized = true; + + return 0; +} + +static int cmd_csis_client_discover(const struct shell *sh, size_t argc, + char *argv[]) +{ + int err; + long member_index = 0; + + if (argc > 1) { + member_index = strtol(argv[1], NULL, 0); + + if (member_index < 0 || member_index > CONFIG_BT_MAX_CONN) { + shell_error(sh, "Invalid member_index %ld", + member_index); + return -ENOEXEC; + } + } + + if (ctx_shell == NULL) { + ctx_shell = sh; + } + + shell_print(sh, "Discovering for member[%u]", (uint8_t)member_index); + err = bt_csis_client_discover(&set_members[member_index]); + if (err != 0) { + shell_error(sh, "Fail: %d", err); + } + + return err; +} + +static int cmd_csis_client_discover_sets(const struct shell *sh, size_t argc, + char *argv[]) +{ + int err; + long member_index = 0; + + if (argc > 1) { + member_index = strtol(argv[1], NULL, 0); + + if (member_index < 0 || member_index > CONFIG_BT_MAX_CONN) { + shell_error(sh, "Invalid member_index %ld", + member_index); + return -ENOEXEC; + } + } + + err = bt_csis_client_discover_sets(set_members[member_index].conn); + if (err != 0) { + shell_error(sh, "Fail: %d", err); + } + + return err; +} + +static int cmd_csis_client_discover_members(const struct shell *sh, size_t argc, + char *argv[]) +{ + int err; + + cur_set = (struct bt_csis_client_set *)strtol(argv[1], NULL, 0); + + if (cur_set == NULL) { + shell_error(sh, "NULL set"); + return -EINVAL; + } + + if (cur_set->set_size > CONFIG_BT_MAX_CONN) { + /* + * TODO Handle case where set size is larger than + * number of possible connections + */ + shell_error(sh, + "Set size (%u) larger than max connections (%u)", + cur_set->set_size, CONFIG_BT_MAX_CONN); + return -EINVAL; + } + + if (members_found > 1) { + members_found = 1; + } + + err = k_work_reschedule(&discover_members_timer, + CSIS_CLIENT_DISCOVER_TIMER_VALUE); + if (err < 0) { /* Can return 0, 1 and 2 for success */ + shell_error(sh, + "Could not schedule discover_members_timer %d", + err); + return err; + } + + err = bt_le_scan_start(BT_LE_SCAN_ACTIVE, NULL); + if (err != 0) { + shell_error(sh, "Could not start scan: %d", err); + } + + return err; +} + +static int cmd_csis_client_lock_set(const struct shell *sh, size_t argc, + char *argv[]) +{ + int err; + int conn_count = 0; + + if (cur_set == NULL) { + shell_error(sh, "No set selected"); + return -ENOEXEC; + } + + for (size_t i = 0; i < ARRAY_SIZE(locked_members); i++) { + if (set_members[i].conn != NULL) { + locked_members[conn_count++] = &set_members[i]; + } + } + + err = bt_csis_client_lock(locked_members, conn_count, cur_set); + if (err != 0) { + shell_error(sh, "Fail: %d", err); + } + + return err; +} + +static int cmd_csis_client_release_set(const struct shell *sh, size_t argc, + char *argv[]) +{ + int err; + int conn_count = 0; + + if (cur_set == NULL) { + shell_error(sh, "No set selected"); + return -ENOEXEC; + } + + for (size_t i = 0; i < ARRAY_SIZE(locked_members); i++) { + if (set_members[i].conn != NULL) { + locked_members[conn_count++] = &set_members[i]; + } + } + + err = bt_csis_client_release(locked_members, conn_count, cur_set); + if (err != 0) { + shell_error(sh, "Fail: %d", err); + } + + return err; +} + +static int cmd_csis_client_lock_get(const struct shell *sh, size_t argc, + char *argv[]) +{ + int err; + long idx = 0; + long member_index = 0; + + if (argc > 1) { + member_index = strtol(argv[1], NULL, 0); + + if (member_index < 0 || member_index > CONFIG_BT_MAX_CONN) { + shell_error(sh, "Invalid member_index %ld", + member_index); + return -ENOEXEC; + } + } + + if (argc > 2) { + idx = strtol(argv[2], NULL, 0); + + if (idx < 0 || idx > CONFIG_BT_CSIS_CLIENT_MAX_CSIS_INSTANCES) { + shell_error(sh, "Invalid index %ld", idx); + return -ENOEXEC; + } + } + + err = bt_csis_client_lock_get(set_members[member_index].conn, + (uint8_t)idx); + if (err != 0) { + shell_error(sh, "Fail: %d", err); + } + + return err; +} + +static int cmd_csis_client_lock(const struct shell *sh, size_t argc, + char *argv[]) +{ + int err; + long member_index = 0; + const struct bt_csis_client_set_member *lock_member[1]; + + if (cur_set == NULL) { + shell_error(sh, "No set selected"); + return -ENOEXEC; + } + + if (argc > 1) { + member_index = strtol(argv[1], NULL, 0); + + if (member_index < 0 || member_index > CONFIG_BT_MAX_CONN) { + shell_error(sh, "Invalid member_index %ld", + member_index); + return -ENOEXEC; + } + } + + lock_member[0] = &set_members[member_index]; + + err = bt_csis_client_lock(lock_member, 1, cur_set); + if (err != 0) { + shell_error(sh, "Fail: %d", err); + } + + return err; +} + +static int cmd_csis_client_release(const struct shell *sh, size_t argc, + char *argv[]) +{ + int err; + long member_index = 0; + const struct bt_csis_client_set_member *lock_member[1]; + + if (cur_set == NULL) { + shell_error(sh, "No set selected"); + return -ENOEXEC; + } + + if (argc > 1) { + member_index = strtol(argv[1], NULL, 0); + + if (member_index < 0 || member_index > CONFIG_BT_MAX_CONN) { + shell_error(sh, "Invalid member_index %ld", + member_index); + return -ENOEXEC; + } + } + + lock_member[0] = &set_members[member_index]; + + err = bt_csis_client_release(lock_member, 1, cur_set); + if (err != 0) { + shell_error(sh, "Fail: %d", err); + } + + return err; +} + +static int cmd_csis_client(const struct shell *sh, size_t argc, char **argv) +{ + if (argc > 1) { + shell_error(sh, "%s unknown parameter: %s", + argv[0], argv[1]); + } else { + shell_error(sh, "%s Missing subcommand", argv[0]); + } + + return -ENOEXEC; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(csis_client_cmds, + SHELL_CMD_ARG(init, NULL, + "Initialize CSIS_CLIENT", + cmd_csis_client_init, 1, 1), + SHELL_CMD_ARG(discover, NULL, + "Run discover for CSIS on peer device [member_index]", + cmd_csis_client_discover, 1, 1), + SHELL_CMD_ARG(discover_sets, NULL, + "Read all set values on connected device [member_index]", + cmd_csis_client_discover_sets, 1, 1), + SHELL_CMD_ARG(discover_members, NULL, + "Scan for set members ", + cmd_csis_client_discover_members, 2, 0), + SHELL_CMD_ARG(lock_set, NULL, + "Lock set", + cmd_csis_client_lock_set, 1, 0), + SHELL_CMD_ARG(release_set, NULL, + "Release set", + cmd_csis_client_release_set, 1, 0), + SHELL_CMD_ARG(lock, NULL, + "Lock specific member [member_index]", + cmd_csis_client_lock, 1, 1), + SHELL_CMD_ARG(release, NULL, + "Release specific member [member_index]", + cmd_csis_client_release, 1, 1), + SHELL_CMD_ARG(lock_get, NULL, + "Get the lock value of the specific member and instance " + "[member_index [inst_idx]]", + cmd_csis_client_lock_get, 1, 2), + SHELL_SUBCMD_SET_END +); + +SHELL_CMD_ARG_REGISTER(csis_client, &csis_client_cmds, + "Bluetooth CSIS_CLIENT shell commands", + cmd_csis_client, 1, 1);