Rename the bt_codec struct to bt_audio_codec_conf or to the new struct bt_audio_codec_cap. Rename the bt_codec_data to bt_audio_codec_data. The purpose of this is to split the codec specific configuration and codec capabilities into seperate structs, as they do not reflect the same values, or used for the same purpose. This commit depends on the preset macros workings on either type of struct (for now), but will be modified in future updates. Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
1046 lines
26 KiB
C
1046 lines
26 KiB
C
/**
|
|
* @file
|
|
* @brief Shell APIs for Bluetooth BASS client
|
|
*
|
|
* Copyright (c) 2020-2023 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/types.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 <zephyr/bluetooth/hci.h>
|
|
#include <zephyr/bluetooth/audio/audio.h>
|
|
#include <zephyr/bluetooth/audio/bap.h>
|
|
#include <zephyr/sys/byteorder.h>
|
|
|
|
#include "shell/bt.h"
|
|
#include "../../host/hci_core.h"
|
|
#include "audio.h"
|
|
|
|
#define INVALID_BROADCAST_ID 0xFFFFFFFFU
|
|
|
|
static struct bt_bap_base received_base;
|
|
|
|
static struct bt_auto_scan {
|
|
uint32_t broadcast_id;
|
|
bool pa_sync;
|
|
struct bt_bap_scan_delegator_subgroup subgroup;
|
|
} auto_scan = {
|
|
.broadcast_id = INVALID_BROADCAST_ID,
|
|
};
|
|
|
|
static bool pa_decode_base(struct bt_data *data, void *user_data)
|
|
{
|
|
struct bt_bap_base base = { 0 };
|
|
int err;
|
|
|
|
if (data->type != BT_DATA_SVC_DATA16) {
|
|
return true;
|
|
}
|
|
|
|
if (data->data_len < BT_BAP_BASE_MIN_SIZE) {
|
|
return true;
|
|
}
|
|
|
|
err = bt_bap_decode_base(data, &base);
|
|
if (err != 0 && err != -ENOMSG) {
|
|
shell_error(ctx_shell, "Failed to decode BASE: %d", err);
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Compare BASE and print if different */
|
|
if (memcmp(&base, &received_base, sizeof(base)) != 0) {
|
|
(void)memcpy(&received_base, &base, sizeof(base));
|
|
|
|
#if BROADCAST_SNK_SUBGROUP_CNT > 0
|
|
print_base(ctx_shell, &received_base);
|
|
#endif /* BROADCAST_SNK_SUBGROUP_CNT > 0 */
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void pa_recv(struct bt_le_per_adv_sync *sync,
|
|
const struct bt_le_per_adv_sync_recv_info *info,
|
|
struct net_buf_simple *buf)
|
|
{
|
|
bt_data_parse(buf, pa_decode_base, NULL);
|
|
}
|
|
|
|
static void bap_broadcast_assistant_discover_cb(struct bt_conn *conn, int err,
|
|
uint8_t recv_state_count)
|
|
{
|
|
if (err != 0) {
|
|
shell_error(ctx_shell, "BASS discover failed (%d)", err);
|
|
} else {
|
|
shell_print(ctx_shell, "BASS discover done with %u recv states",
|
|
recv_state_count);
|
|
}
|
|
|
|
}
|
|
|
|
static void bap_broadcast_assistant_scan_cb(const struct bt_le_scan_recv_info *info,
|
|
uint32_t broadcast_id)
|
|
{
|
|
char le_addr[BT_ADDR_LE_STR_LEN];
|
|
|
|
bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr));
|
|
shell_print(ctx_shell,
|
|
"[DEVICE]: %s, broadcast_id 0x%06X, interval (ms) %u), SID 0x%x, RSSI %i",
|
|
le_addr, broadcast_id, BT_GAP_PER_ADV_INTERVAL_TO_MS(info->interval), info->sid,
|
|
info->rssi);
|
|
}
|
|
|
|
static bool metadata_entry(struct bt_data *data, void *user_data)
|
|
{
|
|
char metadata[512];
|
|
|
|
bin2hex(data->data, data->data_len, metadata, sizeof(metadata));
|
|
|
|
shell_print(ctx_shell, "\t\tMetadata length %u, type %u, data: %s",
|
|
data->data_len, data->type, metadata);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void bap_broadcast_assistant_recv_state_cb(
|
|
struct bt_conn *conn, int err,
|
|
const struct bt_bap_scan_delegator_recv_state *state)
|
|
{
|
|
char le_addr[BT_ADDR_LE_STR_LEN];
|
|
char bad_code[33];
|
|
bool is_bad_code;
|
|
|
|
if (err != 0) {
|
|
shell_error(ctx_shell, "BASS recv state read failed (%d)", err);
|
|
return;
|
|
}
|
|
|
|
bt_addr_le_to_str(&state->addr, le_addr, sizeof(le_addr));
|
|
bin2hex(state->bad_code, BT_AUDIO_BROADCAST_CODE_SIZE,
|
|
bad_code, sizeof(bad_code));
|
|
|
|
is_bad_code = state->encrypt_state == BT_BAP_BIG_ENC_STATE_BAD_CODE;
|
|
shell_print(
|
|
ctx_shell,
|
|
"BASS recv state: src_id %u, addr %s, sid %u, sync_state %u, encrypt_state %u%s%s",
|
|
state->src_id, le_addr, state->adv_sid, state->pa_sync_state, state->encrypt_state,
|
|
is_bad_code ? ", bad code" : "", is_bad_code ? bad_code : "");
|
|
|
|
for (int i = 0; i < state->num_subgroups; i++) {
|
|
const struct bt_bap_scan_delegator_subgroup *subgroup = &state->subgroups[i];
|
|
struct net_buf_simple buf;
|
|
|
|
shell_print(ctx_shell, "\t[%d]: BIS sync 0x%04X, metadata_len %u",
|
|
i, subgroup->bis_sync, subgroup->metadata_len);
|
|
|
|
net_buf_simple_init_with_data(&buf, (void *)subgroup->metadata,
|
|
subgroup->metadata_len);
|
|
bt_data_parse(&buf, metadata_entry, NULL);
|
|
}
|
|
|
|
|
|
if (state->pa_sync_state == BT_BAP_PA_STATE_INFO_REQ) {
|
|
struct bt_le_per_adv_sync *per_adv_sync = NULL;
|
|
struct bt_le_ext_adv *ext_adv = NULL;
|
|
|
|
/* Lookup matching PA sync */
|
|
for (int i = 0; i < ARRAY_SIZE(per_adv_syncs); i++) {
|
|
if (per_adv_syncs[i] &&
|
|
bt_addr_le_eq(&per_adv_syncs[i]->addr, &state->addr)) {
|
|
per_adv_sync = per_adv_syncs[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (per_adv_sync) {
|
|
shell_print(ctx_shell, "Sending PAST");
|
|
|
|
err = bt_le_per_adv_sync_transfer(per_adv_sync,
|
|
conn,
|
|
BT_UUID_BASS_VAL);
|
|
|
|
if (err != 0) {
|
|
shell_error(ctx_shell, "Could not transfer periodic adv sync: %d",
|
|
err);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* If no PA sync was found, check for local advertisers */
|
|
for (int i = 0; i < ARRAY_SIZE(adv_sets); i++) {
|
|
struct bt_le_oob oob_local;
|
|
|
|
if (adv_sets[i] == NULL) {
|
|
continue;
|
|
}
|
|
|
|
err = bt_le_ext_adv_oob_get_local(adv_sets[i],
|
|
&oob_local);
|
|
|
|
if (err != 0) {
|
|
shell_error(ctx_shell,
|
|
"Could not get local OOB %d",
|
|
err);
|
|
return;
|
|
}
|
|
|
|
if (bt_addr_le_eq(&oob_local.addr, &state->addr)) {
|
|
ext_adv = adv_sets[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ext_adv != NULL && IS_ENABLED(CONFIG_BT_PER_ADV)) {
|
|
shell_print(ctx_shell, "Sending local PAST");
|
|
|
|
err = bt_le_per_adv_set_info_transfer(ext_adv, conn,
|
|
BT_UUID_BASS_VAL);
|
|
|
|
if (err != 0) {
|
|
shell_error(ctx_shell,
|
|
"Could not transfer per adv set info: %d",
|
|
err);
|
|
}
|
|
} else {
|
|
shell_error(ctx_shell,
|
|
"Could not send PA to Scan Delegator");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void bap_broadcast_assistant_recv_state_removed_cb(struct bt_conn *conn,
|
|
int err,
|
|
uint8_t src_id)
|
|
{
|
|
if (err != 0) {
|
|
shell_error(ctx_shell, "BASS recv state removed failed (%d)",
|
|
err);
|
|
} else {
|
|
shell_print(ctx_shell, "BASS recv state %u removed", src_id);
|
|
}
|
|
}
|
|
|
|
static void bap_broadcast_assistant_scan_start_cb(struct bt_conn *conn, int err)
|
|
{
|
|
if (err != 0) {
|
|
shell_error(ctx_shell, "BASS scan start failed (%d)", err);
|
|
} else {
|
|
shell_print(ctx_shell, "BASS scan start successful");
|
|
}
|
|
}
|
|
|
|
static void bap_broadcast_assistant_scan_stop_cb(struct bt_conn *conn, int err)
|
|
{
|
|
if (err != 0) {
|
|
shell_error(ctx_shell, "BASS scan stop failed (%d)", err);
|
|
} else {
|
|
shell_print(ctx_shell, "BASS scan stop successful");
|
|
}
|
|
}
|
|
|
|
static void bap_broadcast_assistant_add_src_cb(struct bt_conn *conn, int err)
|
|
{
|
|
if (err != 0) {
|
|
shell_error(ctx_shell, "BASS add source failed (%d)", err);
|
|
} else {
|
|
shell_print(ctx_shell, "BASS add source successful");
|
|
}
|
|
}
|
|
|
|
static void bap_broadcast_assistant_mod_src_cb(struct bt_conn *conn, int err)
|
|
{
|
|
if (err != 0) {
|
|
shell_error(ctx_shell, "BASS modify source failed (%d)", err);
|
|
} else {
|
|
shell_print(ctx_shell, "BASS modify source successful");
|
|
}
|
|
}
|
|
|
|
static void bap_broadcast_assistant_broadcast_code_cb(struct bt_conn *conn,
|
|
int err)
|
|
{
|
|
if (err != 0) {
|
|
shell_error(ctx_shell, "BASS broadcast code failed (%d)", err);
|
|
} else {
|
|
shell_print(ctx_shell, "BASS broadcast code successful");
|
|
}
|
|
}
|
|
|
|
static void bap_broadcast_assistant_rem_src_cb(struct bt_conn *conn, int err)
|
|
{
|
|
if (err != 0) {
|
|
shell_error(ctx_shell, "BASS remove source failed (%d)", err);
|
|
} else {
|
|
shell_print(ctx_shell, "BASS remove source successful");
|
|
}
|
|
}
|
|
|
|
static struct bt_bap_broadcast_assistant_cb cbs = {
|
|
.discover = bap_broadcast_assistant_discover_cb,
|
|
.scan = bap_broadcast_assistant_scan_cb,
|
|
.recv_state = bap_broadcast_assistant_recv_state_cb,
|
|
.recv_state_removed = bap_broadcast_assistant_recv_state_removed_cb,
|
|
.scan_start = bap_broadcast_assistant_scan_start_cb,
|
|
.scan_stop = bap_broadcast_assistant_scan_stop_cb,
|
|
.add_src = bap_broadcast_assistant_add_src_cb,
|
|
.mod_src = bap_broadcast_assistant_mod_src_cb,
|
|
.broadcast_code = bap_broadcast_assistant_broadcast_code_cb,
|
|
.rem_src = bap_broadcast_assistant_rem_src_cb,
|
|
};
|
|
|
|
static int cmd_bap_broadcast_assistant_discover(const struct shell *sh,
|
|
size_t argc, char **argv)
|
|
{
|
|
static bool registered;
|
|
int result;
|
|
|
|
if (!registered) {
|
|
static struct bt_le_per_adv_sync_cb cb = {
|
|
.recv = pa_recv,
|
|
};
|
|
|
|
bt_le_per_adv_sync_cb_register(&cb);
|
|
|
|
bt_bap_broadcast_assistant_register_cb(&cbs);
|
|
|
|
registered = true;
|
|
}
|
|
|
|
result = bt_bap_broadcast_assistant_discover(default_conn);
|
|
if (result) {
|
|
shell_print(sh, "Fail: %d", result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static int cmd_bap_broadcast_assistant_scan_start(const struct shell *sh,
|
|
size_t argc, char **argv)
|
|
{
|
|
int result;
|
|
int start_scan = false;
|
|
|
|
if (argc > 1) {
|
|
result = 0;
|
|
|
|
start_scan = shell_strtobool(argv[1], 0, &result);
|
|
if (result != 0) {
|
|
shell_error(sh, "Could not parse start_scan: %d",
|
|
result);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
}
|
|
|
|
result = bt_bap_broadcast_assistant_scan_start(default_conn,
|
|
(bool)start_scan);
|
|
if (result) {
|
|
shell_print(sh, "Fail: %d", result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static int cmd_bap_broadcast_assistant_scan_stop(const struct shell *sh,
|
|
size_t argc, char **argv)
|
|
{
|
|
int result;
|
|
|
|
result = bt_bap_broadcast_assistant_scan_stop(default_conn);
|
|
if (result) {
|
|
shell_print(sh, "Fail: %d", result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static int cmd_bap_broadcast_assistant_add_src(const struct shell *sh,
|
|
size_t argc, char **argv)
|
|
{
|
|
struct bt_bap_broadcast_assistant_add_src_param param = { 0 };
|
|
struct bt_bap_scan_delegator_subgroup subgroup = { 0 };
|
|
unsigned long broadcast_id;
|
|
unsigned long adv_sid;
|
|
int result;
|
|
|
|
result = bt_addr_le_from_str(argv[1], argv[2], ¶m.addr);
|
|
if (result) {
|
|
shell_error(sh, "Invalid peer address (err %d)", result);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
adv_sid = shell_strtoul(argv[3], 0, &result);
|
|
if (result != 0) {
|
|
shell_error(sh, "Could not parse adv_sid: %d", result);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (adv_sid > BT_GAP_SID_MAX) {
|
|
shell_error(sh, "Invalid adv_sid: %lu", adv_sid);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
param.adv_sid = adv_sid;
|
|
|
|
param.pa_sync = shell_strtobool(argv[4], 0, &result);
|
|
if (result != 0) {
|
|
shell_error(sh, "Could not parse adv_sid: %d", result);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
broadcast_id = shell_strtoul(argv[5], 0, &result);
|
|
if (result != 0) {
|
|
shell_error(sh, "Could not parse broadcast_id: %d", result);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (broadcast_id > BT_AUDIO_BROADCAST_ID_MAX) {
|
|
shell_error(sh, "Invalid broadcast_id: %lu", broadcast_id);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
param.broadcast_id = broadcast_id;
|
|
|
|
if (argc > 6) {
|
|
unsigned long pa_interval;
|
|
|
|
pa_interval = shell_strtoul(argv[6], 0, &result);
|
|
if (result) {
|
|
shell_error(sh, "Could not parse pa_interval: %d",
|
|
result);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (!IN_RANGE(pa_interval,
|
|
BT_GAP_PER_ADV_MIN_INTERVAL,
|
|
BT_GAP_PER_ADV_MAX_INTERVAL)) {
|
|
shell_error(sh, "Invalid pa_interval: %lu",
|
|
pa_interval);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
param.pa_interval = pa_interval;
|
|
} else {
|
|
param.pa_interval = BT_BAP_PA_INTERVAL_UNKNOWN;
|
|
}
|
|
|
|
/* TODO: Support multiple subgroups */
|
|
if (argc > 7) {
|
|
unsigned long bis_sync;
|
|
|
|
bis_sync = shell_strtoul(argv[7], 0, &result);
|
|
if (result) {
|
|
shell_error(sh, "Could not parse bis_sync: %d", result);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (bis_sync > UINT32_MAX) {
|
|
shell_error(sh, "Invalid bis_sync: %lu", bis_sync);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
subgroup.bis_sync = bis_sync;
|
|
}
|
|
|
|
if (argc > 8) {
|
|
size_t metadata_len;
|
|
|
|
metadata_len = hex2bin(argv[8], strlen(argv[8]),
|
|
subgroup.metadata,
|
|
sizeof(subgroup.metadata));
|
|
|
|
if (metadata_len == 0U) {
|
|
shell_error(sh, "Could not parse metadata");
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
/* sizeof(subgroup.metadata) can always fit in uint8_t */
|
|
|
|
subgroup.metadata_len = metadata_len;
|
|
}
|
|
|
|
param.num_subgroups = 1;
|
|
param.subgroups = &subgroup;
|
|
|
|
result = bt_bap_broadcast_assistant_add_src(default_conn, ¶m);
|
|
if (result) {
|
|
shell_print(sh, "Fail: %d", result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static bool broadcast_source_found(struct bt_data *data, void *user_data)
|
|
{
|
|
struct bt_bap_broadcast_assistant_add_src_param param = { 0 };
|
|
const struct bt_le_scan_recv_info *info = user_data;
|
|
char addr_str[BT_ADDR_LE_STR_LEN];
|
|
struct bt_uuid_16 adv_uuid;
|
|
uint32_t broadcast_id;
|
|
int err;
|
|
|
|
/* Verify that it is a BAP broadcaster*/
|
|
|
|
if (data->type != BT_DATA_SVC_DATA16) {
|
|
return true;
|
|
}
|
|
|
|
if (data->data_len < BT_UUID_SIZE_16 + BT_AUDIO_BROADCAST_ID_SIZE) {
|
|
return true;
|
|
}
|
|
|
|
if (!bt_uuid_create(&adv_uuid.uuid, data->data, BT_UUID_SIZE_16)) {
|
|
return true;
|
|
}
|
|
|
|
if (bt_uuid_cmp(&adv_uuid.uuid, BT_UUID_BROADCAST_AUDIO) != 0) {
|
|
return true;
|
|
}
|
|
|
|
broadcast_id = sys_get_le24(data->data + BT_UUID_SIZE_16);
|
|
|
|
if (broadcast_id != auto_scan.broadcast_id) {
|
|
/* Not the one we want */
|
|
return false;
|
|
}
|
|
|
|
bt_addr_le_to_str(info->addr, addr_str, sizeof(addr_str));
|
|
shell_print(ctx_shell, "Found BAP broadcast source with address %s and ID 0x%06X\n",
|
|
addr_str, broadcast_id);
|
|
|
|
err = bt_le_scan_stop();
|
|
if (err) {
|
|
shell_error(ctx_shell, "Failed to stop scan: %d", err);
|
|
}
|
|
|
|
bt_addr_le_copy(¶m.addr, info->addr);
|
|
param.adv_sid = info->sid;
|
|
param.pa_interval = info->interval;
|
|
param.broadcast_id = broadcast_id;
|
|
param.pa_sync = auto_scan.pa_sync;
|
|
param.num_subgroups = 1;
|
|
param.subgroups = &auto_scan.subgroup;
|
|
|
|
err = bt_bap_broadcast_assistant_add_src(default_conn, ¶m);
|
|
if (err) {
|
|
shell_print(ctx_shell, "Failed to add source: %d", err);
|
|
}
|
|
|
|
memset(&auto_scan, 0, sizeof(auto_scan));
|
|
auto_scan.broadcast_id = INVALID_BROADCAST_ID;
|
|
|
|
return false;
|
|
}
|
|
|
|
static void scan_recv_cb(const struct bt_le_scan_recv_info *info,
|
|
struct net_buf_simple *ad)
|
|
{
|
|
if (auto_scan.broadcast_id == INVALID_BROADCAST_ID) {
|
|
/* no op */
|
|
return;
|
|
}
|
|
|
|
/* We are only interested in non-connectable periodic advertisers */
|
|
if ((info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE) != 0 ||
|
|
info->interval == 0) {
|
|
return;
|
|
}
|
|
|
|
if (!passes_scan_filter(info, ad)) {
|
|
return;
|
|
}
|
|
|
|
bt_data_parse(ad, broadcast_source_found, (void *)info);
|
|
}
|
|
|
|
static void scan_timeout_cb(void)
|
|
{
|
|
shell_print(ctx_shell, "Scan timeout");
|
|
|
|
if (auto_scan.broadcast_id != INVALID_BROADCAST_ID) {
|
|
memset(&auto_scan, 0, sizeof(auto_scan));
|
|
auto_scan.broadcast_id = INVALID_BROADCAST_ID;
|
|
}
|
|
}
|
|
|
|
static struct bt_le_scan_cb scan_callbacks = {
|
|
.recv = scan_recv_cb,
|
|
.timeout = scan_timeout_cb,
|
|
};
|
|
|
|
static int cmd_bap_broadcast_assistant_add_broadcast_id(const struct shell *sh,
|
|
size_t argc,
|
|
char **argv)
|
|
{
|
|
struct bt_bap_scan_delegator_subgroup subgroup = { 0 };
|
|
static bool scan_cbs_registered;
|
|
unsigned long broadcast_id;
|
|
unsigned long pa_sync;
|
|
int err = 0;
|
|
|
|
if (!scan_cbs_registered) {
|
|
bt_le_scan_cb_register(&scan_callbacks);
|
|
scan_cbs_registered = true;
|
|
}
|
|
|
|
if (auto_scan.broadcast_id != INVALID_BROADCAST_ID) {
|
|
shell_info(sh, "Already scanning, wait for sync or timeout");
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
broadcast_id = shell_strtoul(argv[1], 0, &err);
|
|
if (err != 0) {
|
|
shell_error(sh, "failed to parse broadcast_id: %d", err);
|
|
|
|
return -ENOEXEC;
|
|
} else if (broadcast_id > 0xFFFFFF /* 24 bits */) {
|
|
shell_error(sh, "Broadcast ID maximum 24 bits (was %lu)", broadcast_id);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
pa_sync = shell_strtoul(argv[2], 0, &err);
|
|
if (err != 0) {
|
|
shell_error(sh, "failed to parse pa_sync: %d", err);
|
|
|
|
return -ENOEXEC;
|
|
} else if (pa_sync != 0U && pa_sync != 1U) {
|
|
shell_error(sh, "pa_sync shall be boolean: %lu", pa_sync);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
/* TODO: Support multiple subgroups */
|
|
if (argc > 3) {
|
|
const unsigned long bis_sync = shell_strtoul(argv[3], 0, &err);
|
|
|
|
if (err != 0) {
|
|
shell_error(sh, "failed to parse bis_sync: %d", err);
|
|
|
|
return -ENOEXEC;
|
|
} else if (bis_sync > UINT32_MAX) {
|
|
shell_error(sh, "Invalid bis_sync: %lu", bis_sync);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
subgroup.bis_sync = bis_sync;
|
|
}
|
|
|
|
if (argc > 4) {
|
|
subgroup.metadata_len = hex2bin(argv[4], strlen(argv[4]), subgroup.metadata,
|
|
sizeof(subgroup.metadata));
|
|
|
|
if (subgroup.metadata_len == 0U) {
|
|
shell_error(sh, "Could not parse metadata");
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
}
|
|
|
|
err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, NULL);
|
|
if (err) {
|
|
shell_print(sh, "Fail to start scanning: %d", err);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
/* Store results in the `auto_scan` struct */
|
|
auto_scan.broadcast_id = broadcast_id;
|
|
auto_scan.pa_sync = pa_sync;
|
|
memcpy(&auto_scan.subgroup, &subgroup, sizeof(subgroup));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_bap_broadcast_assistant_mod_src(const struct shell *sh,
|
|
size_t argc, char **argv)
|
|
{
|
|
struct bt_bap_broadcast_assistant_mod_src_param param = { 0 };
|
|
struct bt_bap_scan_delegator_subgroup subgroup = { 0 };
|
|
unsigned long src_id;
|
|
int result = 0;
|
|
|
|
src_id = shell_strtoul(argv[1], 0, &result);
|
|
if (result != 0) {
|
|
shell_error(sh, "Could not parse src_id: %d", result);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (src_id > UINT8_MAX) {
|
|
shell_error(sh, "Invalid src_id: %lu", src_id);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
param.src_id = src_id;
|
|
|
|
param.pa_sync = shell_strtobool(argv[2], 0, &result);
|
|
if (result != 0) {
|
|
shell_error(sh, "Could not parse adv_sid: %d", result);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (argc > 3) {
|
|
unsigned long pa_interval;
|
|
|
|
pa_interval = shell_strtoul(argv[3], 0, &result);
|
|
if (result) {
|
|
shell_error(sh, "Could not parse pa_interval: %d", result);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (!IN_RANGE(pa_interval,
|
|
BT_GAP_PER_ADV_MIN_INTERVAL,
|
|
BT_GAP_PER_ADV_MAX_INTERVAL)) {
|
|
shell_error(sh, "Invalid pa_interval: %lu", pa_interval);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
param.pa_interval = pa_interval;
|
|
} else {
|
|
param.pa_interval = BT_BAP_PA_INTERVAL_UNKNOWN;
|
|
}
|
|
|
|
/* TODO: Support multiple subgroups */
|
|
if (argc > 4) {
|
|
unsigned long bis_sync;
|
|
|
|
bis_sync = shell_strtoul(argv[4], 0, &result);
|
|
if (result) {
|
|
shell_error(sh, "Could not parse bis_sync: %d", result);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (bis_sync > UINT32_MAX) {
|
|
shell_error(sh, "Invalid bis_sync: %lu", bis_sync);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
subgroup.bis_sync = bis_sync;
|
|
}
|
|
|
|
if (argc > 5) {
|
|
size_t metadata_len;
|
|
|
|
metadata_len = hex2bin(argv[5], strlen(argv[5]),
|
|
subgroup.metadata,
|
|
sizeof(subgroup.metadata));
|
|
|
|
if (metadata_len == 0U) {
|
|
shell_error(sh, "Could not parse metadata");
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
/* sizeof(subgroup.metadata) can always fit in uint8_t */
|
|
|
|
subgroup.metadata_len = metadata_len;
|
|
}
|
|
|
|
param.num_subgroups = 1;
|
|
param.subgroups = &subgroup;
|
|
|
|
result = bt_bap_broadcast_assistant_mod_src(default_conn, ¶m);
|
|
if (result) {
|
|
shell_print(sh, "Fail: %d", result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static int cmd_bap_broadcast_assistant_add_pa_sync(const struct shell *sh,
|
|
size_t argc, char **argv)
|
|
{
|
|
struct bt_bap_scan_delegator_subgroup subgroup_params[BT_ISO_MAX_GROUP_ISO_COUNT] = { 0 };
|
|
struct bt_bap_broadcast_assistant_add_src_param param = { 0 };
|
|
/* TODO: Add support to select which PA sync to BIG sync to */
|
|
struct bt_le_per_adv_sync *pa_sync = per_adv_syncs[0];
|
|
struct bt_le_per_adv_sync_info pa_info;
|
|
unsigned long broadcast_id;
|
|
unsigned long pa_sync_req;
|
|
uint32_t bis_bitfield_req;
|
|
int err;
|
|
|
|
if (pa_sync == NULL) {
|
|
shell_error(sh, "PA not synced");
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
err = bt_le_per_adv_sync_get_info(pa_sync, &pa_info);
|
|
if (err != 0) {
|
|
shell_error(sh, "Could not get PA sync info: %d", err);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
bt_addr_le_copy(¶m.addr, &pa_info.addr);
|
|
param.adv_sid = pa_info.sid;
|
|
param.pa_interval = pa_info.interval;
|
|
|
|
pa_sync_req = shell_strtoul(argv[1], 0, &err);
|
|
if (err != 0) {
|
|
shell_error(sh, "failed to parse pa_sync: %d", err);
|
|
|
|
return -ENOEXEC;
|
|
} else if (pa_sync_req != 0U && pa_sync_req != 1U) {
|
|
shell_error(sh, "pa_sync_req shall be boolean: %lu", pa_sync_req);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
param.pa_sync = (bool)pa_sync_req;
|
|
|
|
broadcast_id = shell_strtoul(argv[2], 0, &err);
|
|
if (err != 0) {
|
|
shell_error(sh, "failed to parse broadcast_id: %d", err);
|
|
|
|
return -ENOEXEC;
|
|
} else if (broadcast_id > BT_AUDIO_BROADCAST_ID_MAX /* 24 bits */) {
|
|
shell_error(sh, "Invalid Broadcast ID: %x",
|
|
param.broadcast_id);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
param.broadcast_id = broadcast_id;
|
|
|
|
bis_bitfield_req = 0U;
|
|
for (size_t i = 3U; i < argc; i++) {
|
|
const unsigned long index = shell_strtoul(argv[i], 16, &err);
|
|
|
|
if (err != 0) {
|
|
shell_error(sh, "failed to parse index: %d", err);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (index < BT_ISO_BIS_INDEX_MIN ||
|
|
index > BT_ISO_BIS_INDEX_MAX) {
|
|
shell_error(sh, "Invalid index: %ld", index);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
bis_bitfield_req |= BIT(index);
|
|
}
|
|
|
|
/* The MIN is used to handle `array-bounds` error on some compilers */
|
|
param.num_subgroups = MIN(received_base.subgroup_count, BROADCAST_SNK_SUBGROUP_CNT);
|
|
param.subgroups = subgroup_params;
|
|
for (size_t i = 0; i < param.num_subgroups; i++) {
|
|
struct bt_bap_scan_delegator_subgroup *subgroup_param = &subgroup_params[i];
|
|
const struct bt_bap_base_subgroup *subgroup = &received_base.subgroups[i];
|
|
uint32_t subgroup_bis_indexes = 0U;
|
|
ssize_t metadata_len;
|
|
|
|
for (size_t j = 0U; j < MIN(subgroup->bis_count, ARRAY_SIZE(subgroup->bis_data));
|
|
j++) {
|
|
const struct bt_bap_base_bis_data *bis_data = &subgroup->bis_data[j];
|
|
|
|
subgroup_bis_indexes |= BIT(bis_data->index);
|
|
}
|
|
|
|
subgroup_param->bis_sync = subgroup_bis_indexes & bis_bitfield_req;
|
|
|
|
#if CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_COUNT > 0
|
|
metadata_len = bt_audio_codec_data_to_buf(subgroup->codec_cfg.meta,
|
|
subgroup->codec_cfg.meta_count,
|
|
subgroup_param->metadata,
|
|
sizeof(subgroup_param->metadata));
|
|
if (metadata_len < 0) {
|
|
return -ENOMEM;
|
|
}
|
|
#else
|
|
metadata_len = 0U;
|
|
#endif /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_COUNT > 0 */
|
|
subgroup_param->metadata_len = metadata_len;
|
|
}
|
|
|
|
err = bt_bap_broadcast_assistant_add_src(default_conn, ¶m);
|
|
if (err != 0) {
|
|
shell_print(sh, "Fail: %d", err);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_bap_broadcast_assistant_broadcast_code(const struct shell *sh,
|
|
size_t argc, char **argv)
|
|
{
|
|
uint8_t broadcast_code[BT_AUDIO_BROADCAST_CODE_SIZE] = { 0 };
|
|
size_t broadcast_code_len;
|
|
unsigned long src_id;
|
|
int result = 0;
|
|
|
|
src_id = shell_strtoul(argv[1], 0, &result);
|
|
if (result != 0) {
|
|
shell_error(sh, "Could not parse src_id: %d", result);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (src_id > UINT8_MAX) {
|
|
shell_error(sh, "Invalid src_id: %lu", src_id);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
broadcast_code_len = strlen(argv[2]);
|
|
if (!IN_RANGE(broadcast_code_len, 1, BT_AUDIO_BROADCAST_CODE_SIZE)) {
|
|
shell_error(sh, "Invalid broadcast code length: %zu", broadcast_code_len);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
memcpy(broadcast_code, argv[2], broadcast_code_len);
|
|
|
|
shell_info(sh, "Sending broadcast code:");
|
|
shell_hexdump(sh, broadcast_code, sizeof(broadcast_code));
|
|
|
|
result = bt_bap_broadcast_assistant_set_broadcast_code(default_conn,
|
|
src_id,
|
|
broadcast_code);
|
|
if (result) {
|
|
shell_print(sh, "Fail: %d", result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static int cmd_bap_broadcast_assistant_rem_src(const struct shell *sh,
|
|
size_t argc, char **argv)
|
|
{
|
|
unsigned long src_id;
|
|
int result = 0;
|
|
|
|
src_id = shell_strtoul(argv[1], 0, &result);
|
|
if (result != 0) {
|
|
shell_error(sh, "Could not parse src_id: %d", result);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (src_id > UINT8_MAX) {
|
|
shell_error(sh, "Invalid src_id: %lu", src_id);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
result = bt_bap_broadcast_assistant_rem_src(default_conn, src_id);
|
|
if (result) {
|
|
shell_print(sh, "Fail: %d", result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static int cmd_bap_broadcast_assistant_read_recv_state(const struct shell *sh,
|
|
size_t argc, char **argv)
|
|
{
|
|
unsigned long idx;
|
|
int result = 0;
|
|
|
|
idx = shell_strtoul(argv[1], 0, &result);
|
|
if (result != 0) {
|
|
shell_error(sh, "Could not parse idx: %d", result);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (idx > UINT8_MAX) {
|
|
shell_error(sh, "Invalid idx: %lu", idx);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
result = bt_bap_broadcast_assistant_read_recv_state(default_conn, idx);
|
|
if (result) {
|
|
shell_print(sh, "Fail: %d", result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static int cmd_bap_broadcast_assistant(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(
|
|
bap_broadcast_assistant_cmds,
|
|
SHELL_CMD_ARG(discover, NULL, "Discover BASS on the server",
|
|
cmd_bap_broadcast_assistant_discover, 1, 0),
|
|
SHELL_CMD_ARG(scan_start, NULL, "Start scanning for broadcasters",
|
|
cmd_bap_broadcast_assistant_scan_start, 1, 1),
|
|
SHELL_CMD_ARG(scan_stop, NULL, "Stop scanning for BISs",
|
|
cmd_bap_broadcast_assistant_scan_stop, 1, 0),
|
|
SHELL_CMD_ARG(add_src, NULL,
|
|
"Add a source <address: XX:XX:XX:XX:XX:XX> "
|
|
"<type: public/random> <adv_sid> <sync_pa> "
|
|
"<broadcast_id> [<pa_interval>] [<sync_bis>] "
|
|
"[<metadata>]",
|
|
cmd_bap_broadcast_assistant_add_src, 6, 3),
|
|
SHELL_CMD_ARG(add_broadcast_id, NULL,
|
|
"Add a source by broadcast ID <broadcast_id> <sync_pa> "
|
|
"[<sync_bis>] [<metadata>]",
|
|
cmd_bap_broadcast_assistant_add_broadcast_id, 3, 2),
|
|
SHELL_CMD_ARG(add_pa_sync, NULL,
|
|
"Add a PA sync as a source <sync_pa> <broadcast_id> "
|
|
"[bis_index [bis_index [bix_index [...]]]]>",
|
|
cmd_bap_broadcast_assistant_add_pa_sync, 3, BT_ISO_MAX_GROUP_ISO_COUNT),
|
|
SHELL_CMD_ARG(mod_src, NULL,
|
|
"Set sync <src_id> <sync_pa> [<pa_interval>] "
|
|
"[<sync_bis>] [<metadata>]",
|
|
cmd_bap_broadcast_assistant_mod_src, 3, 2),
|
|
SHELL_CMD_ARG(broadcast_code, NULL,
|
|
"Send a space separated broadcast code of up to 16 bytes "
|
|
"<src_id> <broadcast code>",
|
|
cmd_bap_broadcast_assistant_broadcast_code, 3, 0),
|
|
SHELL_CMD_ARG(rem_src, NULL, "Remove a source <src_id>",
|
|
cmd_bap_broadcast_assistant_rem_src, 2, 0),
|
|
SHELL_CMD_ARG(read_state, NULL, "Remove a source <index>",
|
|
cmd_bap_broadcast_assistant_read_recv_state, 2, 0),
|
|
SHELL_SUBCMD_SET_END);
|
|
|
|
SHELL_CMD_ARG_REGISTER(bap_broadcast_assistant, &bap_broadcast_assistant_cmds,
|
|
"Bluetooth BAP broadcast assistant client shell commands",
|
|
cmd_bap_broadcast_assistant, 1, 1);
|