blob: 654d67408879cd9d32c5d630932dc668e8fc972f [file] [log] [blame]
/**
* @file
* @brief Shell APIs for Bluetooth CSIP set coordinator
*
* Copyright (c) 2020 Bose Corporation
* Copyright (c) 2021-2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/types.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/kernel.h>
#include <zephyr/types.h>
#include <zephyr/shell/shell.h>
#include <stdlib.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/bluetooth.h>
#include "bt.h"
#include <zephyr/bluetooth/audio/csip.h>
static uint8_t members_found;
static struct k_work_delayable discover_members_timer;
static struct bt_conn *conns[CONFIG_BT_MAX_CONN];
static const struct bt_csip_set_coordinator_set_member *set_members[CONFIG_BT_MAX_CONN];
struct bt_csip_set_coordinator_csis_inst *cur_inst;
static bt_addr_le_t addr_found[CONFIG_BT_MAX_CONN];
static const struct bt_csip_set_coordinator_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_eq(addr, &addr_found[i])) {
return true;
}
}
return false;
}
static void connected_cb(struct bt_conn *conn, uint8_t err)
{
char addr[BT_ADDR_LE_STR_LEN];
uint8_t conn_index;
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;
}
conn_index = bt_conn_index(conn);
shell_print(ctx_shell, "[%u]: Connected to %s", conn_index, addr);
/* TODO: Handle RPAs */
conns[conn_index] = bt_conn_ref(conn);
shell_print(ctx_shell, "Member[%u] connected", conn_index);
}
static void disconnected_cb(struct bt_conn *conn, uint8_t reason)
{
uint8_t conn_index = bt_conn_index(conn);
bt_conn_unref(conns[conn_index]);
conns[conn_index] = NULL;
}
static struct bt_conn_cb conn_callbacks = {
.connected = connected_cb,
.disconnected = disconnected_cb
};
static void csip_discover_cb(struct bt_conn *conn,
const struct bt_csip_set_coordinator_set_member *member,
int err, size_t set_count)
{
uint8_t conn_index;
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;
}
conn_index = bt_conn_index(conn);
shell_print(ctx_shell, "Found %zu sets on member[%u]",
set_count, conn_index);
for (size_t i = 0U; i < set_count; i++) {
shell_print(ctx_shell, "CSIS[%zu]: %p", i, &member->insts[i]);
}
set_members[conn_index] = member;
}
static void csip_set_coordinator_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 csip_set_coordinator_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 csip_set_coordinator_ordered_access_cb(
const struct bt_csip_set_coordinator_set_info *set_info, int err,
bool locked, struct bt_csip_set_coordinator_set_member *member)
{
if (err) {
printk("Ordered access failed with err %d\n", err);
} else if (locked) {
printk("Cannot do ordered access as member %p is locked\n",
member);
} else {
printk("Ordered access procedure finished\n");
}
}
static struct bt_csip_set_coordinator_cb cbs = {
.lock_set = csip_set_coordinator_lock_set_cb,
.release_set = csip_set_coordinator_release_set_cb,
.discover = csip_discover_cb,
.ordered_access = csip_set_coordinator_ordered_access_cb
};
static bool csip_set_coordinator_oap_cb(const struct bt_csip_set_coordinator_set_info *set_info,
struct bt_csip_set_coordinator_set_member *members[],
size_t count)
{
for (size_t i = 0; i < count; i++) {
printk("Ordered access for members[%zu]: %p\n", i, members[i]);
}
return true;
}
static bool csip_found(struct bt_data *data, void *user_data)
{
if (bt_csip_set_coordinator_is_set_member(cur_inst->info.set_sirk, 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 CSIP 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(&addr_found[members_found++], addr);
shell_print(ctx_shell, "Found member (%u / %u)",
members_found, cur_inst->info.set_size);
if (members_found == cur_inst->info.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 csip_set_coordinator_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_inst != NULL) {
bt_data_parse(ad, csip_found, (void *)info->addr);
}
}
}
static struct bt_le_scan_cb csip_set_coordinator_scan_callbacks = {
.recv = csip_set_coordinator_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_inst->info.set_size);
err = bt_le_scan_stop();
if (err != 0) {
shell_error(ctx_shell, "Failed to stop scan: %d", err);
}
}
static int cmd_csip_set_coordinator_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(&csip_set_coordinator_scan_callbacks);
bt_csip_set_coordinator_register_cb(&cbs);
bt_conn_cb_register(&conn_callbacks);
initialized = true;
return 0;
}
static int cmd_csip_set_coordinator_discover(const struct shell *sh,
size_t argc, char *argv[])
{
char addr[BT_ADDR_LE_STR_LEN];
long member_index = 0;
struct bt_conn *conn;
int err;
if (argc > 1) {
member_index = strtol(argv[1], NULL, 0);
if (member_index < 0 || member_index > ARRAY_SIZE(conns)) {
shell_error(sh, "Invalid member_index %ld",
member_index);
return -ENOEXEC;
}
}
if (ctx_shell == NULL) {
ctx_shell = sh;
}
conn = conns[member_index];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
shell_print(sh, "Discovering for member[%u] (%s)",
(uint8_t)member_index, addr);
err = bt_csip_set_coordinator_discover(conn);
if (err != 0) {
shell_error(sh, "Fail: %d", err);
}
return err;
}
static int cmd_csip_set_coordinator_discover_members(const struct shell *sh,
size_t argc, char *argv[])
{
int err;
cur_inst = (struct bt_csip_set_coordinator_csis_inst *)strtol(argv[1], NULL, 0);
if (cur_inst == NULL) {
shell_error(sh, "NULL set");
return -EINVAL;
}
if (cur_inst->info.set_size > ARRAY_SIZE(set_members)) {
/*
* 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_inst->info.set_size, ARRAY_SIZE(set_members));
return -EINVAL;
}
if (members_found > 1) {
members_found = 1;
}
err = k_work_reschedule(&discover_members_timer,
BT_CSIP_SET_COORDINATOR_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_csip_set_coordinator_lock_set(const struct shell *sh,
size_t argc, char *argv[])
{
int err;
int conn_count = 0;
if (cur_inst == NULL) {
shell_error(sh, "No set selected");
return -ENOEXEC;
}
for (size_t i = 0; i < ARRAY_SIZE(locked_members); i++) {
if (set_members[i] != NULL) {
locked_members[conn_count++] = set_members[i];
}
}
err = bt_csip_set_coordinator_lock(locked_members, conn_count,
&cur_inst->info);
if (err != 0) {
shell_error(sh, "Fail: %d", err);
}
return err;
}
static int cmd_csip_set_coordinator_release_set(const struct shell *sh,
size_t argc, char *argv[])
{
int err;
int conn_count = 0;
if (cur_inst == NULL) {
shell_error(sh, "No set selected");
return -ENOEXEC;
}
for (size_t i = 0; i < ARRAY_SIZE(locked_members); i++) {
if (set_members[i] != NULL) {
locked_members[conn_count++] = set_members[i];
}
}
err = bt_csip_set_coordinator_release(locked_members, conn_count,
&cur_inst->info);
if (err != 0) {
shell_error(sh, "Fail: %d", err);
}
return err;
}
static int cmd_csip_set_coordinator_ordered_access(const struct shell *sh,
size_t argc, char *argv[])
{
int err;
long member_count = (long)ARRAY_SIZE(set_members);
const struct bt_csip_set_coordinator_set_member *members[ARRAY_SIZE(set_members)];
if (argc > 1) {
member_count = strtol(argv[1], NULL, 0);
if (member_count < 0 || member_count > ARRAY_SIZE(members)) {
shell_error(sh, "Invalid member count %ld",
member_count);
return -ENOEXEC;
}
}
for (size_t i = 0; i < (size_t)member_count; i++) {
members[i] = set_members[i];
}
err = bt_csip_set_coordinator_ordered_access(members,
ARRAY_SIZE(members),
&cur_inst->info,
csip_set_coordinator_oap_cb);
if (err != 0) {
shell_error(sh, "Fail: %d", err);
}
return err;
}
static int cmd_csip_set_coordinator_lock(const struct shell *sh, size_t argc,
char *argv[])
{
int err;
long member_index = 0;
const struct bt_csip_set_coordinator_set_member *lock_member[1];
if (cur_inst == 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 > ARRAY_SIZE(set_members)) {
shell_error(sh, "Invalid member_index %ld",
member_index);
return -ENOEXEC;
}
}
lock_member[0] = set_members[member_index];
err = bt_csip_set_coordinator_lock(lock_member, 1, &cur_inst->info);
if (err != 0) {
shell_error(sh, "Fail: %d", err);
}
return err;
}
static int cmd_csip_set_coordinator_release(const struct shell *sh, size_t argc,
char *argv[])
{
int err;
long member_index = 0;
const struct bt_csip_set_coordinator_set_member *lock_member[1];
if (cur_inst == 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 > ARRAY_SIZE(set_members)) {
shell_error(sh, "Invalid member_index %ld",
member_index);
return -ENOEXEC;
}
}
lock_member[0] = set_members[member_index];
err = bt_csip_set_coordinator_release(lock_member, 1, &cur_inst->info);
if (err != 0) {
shell_error(sh, "Fail: %d", err);
}
return err;
}
static int cmd_csip_set_coordinator(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(csip_set_coordinator_cmds,
SHELL_CMD_ARG(init, NULL,
"Initialize csip_set_coordinator",
cmd_csip_set_coordinator_init, 1, 1),
SHELL_CMD_ARG(discover, NULL,
"Run discover for CSIS on peer device [member_index]",
cmd_csip_set_coordinator_discover, 1, 1),
SHELL_CMD_ARG(discover_members, NULL,
"Scan for set members <set_pointer>",
cmd_csip_set_coordinator_discover_members, 2, 0),
SHELL_CMD_ARG(lock_set, NULL,
"Lock set",
cmd_csip_set_coordinator_lock_set, 1, 0),
SHELL_CMD_ARG(release_set, NULL,
"Release set",
cmd_csip_set_coordinator_release_set, 1, 0),
SHELL_CMD_ARG(lock, NULL,
"Lock specific member [member_index]",
cmd_csip_set_coordinator_lock, 1, 1),
SHELL_CMD_ARG(release, NULL,
"Release specific member [member_index]",
cmd_csip_set_coordinator_release, 1, 1),
SHELL_CMD_ARG(ordered_access, NULL,
"Perform dummy ordered access procedure [member_count]",
cmd_csip_set_coordinator_ordered_access, 1, 1),
SHELL_SUBCMD_SET_END
);
SHELL_CMD_ARG_REGISTER(csip_set_coordinator, &csip_set_coordinator_cmds,
"Bluetooth csip_set_coordinator shell commands",
cmd_csip_set_coordinator, 1, 1);