Bluetooth: Audio: add add_by_broadcast_name for the assistant shell

Add add_by_broadcast_name command that scan for broadcast sources
has BT_DATA_BROADCAST_NAME that matches with whatever name is given
to the shell command.

Fixes #70836

Signed-off-by: Babak Arisian <bbaa@demant.com>
This commit is contained in:
Babak Arisian 2024-06-05 16:35:44 +02:00 committed by Anas Nashif
commit 7b9a0e7e95
4 changed files with 206 additions and 100 deletions

View file

@ -48,6 +48,11 @@ extern "C" {
#define BT_AUDIO_BROADCAST_CODE_SIZE 16
/** The minimum size of a Broadcast Name as defined by Bluetooth Assigned Numbers */
#define BT_AUDIO_BROADCAST_NAME_LEN_MIN 4
/** The maximum size of a Broadcast Name as defined by Bluetooth Assigned Numbers */
#define BT_AUDIO_BROADCAST_NAME_LEN_MAX 128
/** Size of the stream language value, e.g. "eng" */
#define BT_AUDIO_LANG_SIZE 3

View file

@ -43,12 +43,18 @@ static size_t received_base_size;
static struct bt_auto_scan {
uint32_t broadcast_id;
char broadcast_name[BT_AUDIO_BROADCAST_NAME_LEN_MAX + 1];
bool pa_sync;
struct bt_bap_bass_subgroup subgroup;
} auto_scan = {
.broadcast_id = INVALID_BROADCAST_ID,
};
struct bt_scan_recv_info {
uint32_t broadcast_id;
char broadcast_name[BT_AUDIO_BROADCAST_NAME_LEN_MAX + 1];
};
static bool pa_decode_base(struct bt_data *data, void *user_data)
{
const struct bt_bap_base *base = bt_bap_base_get_base_from_ad(data);
@ -304,32 +310,6 @@ static struct bt_bap_broadcast_assistant_cb cbs = {
.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)
{
@ -499,70 +479,49 @@ static int cmd_bap_broadcast_assistant_add_src(const struct shell *sh,
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_scan_recv_info *sr_info = (struct bt_scan_recv_info *)user_data;
struct bt_uuid_16 adv_uuid;
uint32_t broadcast_id;
int err;
/* Verify that it is a BAP broadcaster*/
switch (data->type) {
case BT_DATA_SVC_DATA16:
if (data->data_len < BT_UUID_SIZE_16 + BT_AUDIO_BROADCAST_ID_SIZE) {
return true;
}
if (data->type != BT_DATA_SVC_DATA16) {
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;
}
sr_info->broadcast_id = sys_get_le24(data->data + BT_UUID_SIZE_16);
return true;
case BT_DATA_BROADCAST_NAME:
if (!IN_RANGE(data->data_len, BT_AUDIO_BROADCAST_NAME_LEN_MIN,
BT_AUDIO_BROADCAST_NAME_LEN_MAX)) {
return true;
}
utf8_lcpy(sr_info->broadcast_name, data->data, (data->data_len) + 1);
return true;
default:
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(&param.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, &param);
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) {
struct bt_scan_recv_info sr_info = { 0 };
struct bt_bap_broadcast_assistant_add_src_param param = { 0 };
int err;
sr_info.broadcast_id = INVALID_BROADCAST_ID;
if ((auto_scan.broadcast_id == INVALID_BROADCAST_ID) &&
(strlen(auto_scan.broadcast_name) == 0U)) {
/* no op */
return;
}
@ -577,17 +536,62 @@ static void scan_recv_cb(const struct bt_le_scan_recv_info *info,
return;
}
bt_data_parse(ad, broadcast_source_found, (void *)info);
bt_data_parse(ad, broadcast_source_found, (void *)&sr_info);
/* Verify that it is a BAP broadcaster*/
if (sr_info.broadcast_id != INVALID_BROADCAST_ID) {
char addr_str[BT_ADDR_LE_STR_LEN];
bool identified_broadcast = false;
bt_addr_le_to_str(info->addr, addr_str, sizeof(addr_str));
if (sr_info.broadcast_id == auto_scan.broadcast_id) {
identified_broadcast = true;
}
if ((strlen(auto_scan.broadcast_name) != 0U) &&
is_substring(auto_scan.broadcast_name, sr_info.broadcast_name)) {
identified_broadcast = true;
shell_print(ctx_shell, "Found matched broadcast name '%s' with address %s",
sr_info.broadcast_name, addr_str);
}
if (identified_broadcast) {
shell_print(ctx_shell,
"Found BAP broadcast source with address %s and ID 0x%06X\n",
addr_str, sr_info.broadcast_id);
err = bt_le_scan_stop();
if (err) {
shell_error(ctx_shell, "Failed to stop scan: %d", err);
}
bt_addr_le_copy(&param.addr, info->addr);
param.adv_sid = info->sid;
param.pa_interval = info->interval;
param.broadcast_id = sr_info.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, &param);
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;
}
}
}
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;
}
memset(&auto_scan, 0, sizeof(auto_scan));
auto_scan.broadcast_id = INVALID_BROADCAST_ID;
}
static struct bt_le_scan_cb scan_callbacks = {
@ -595,20 +599,42 @@ static struct bt_le_scan_cb scan_callbacks = {
.timeout = scan_timeout_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);
bt_le_scan_cb_register(&scan_callbacks);
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_add_broadcast_id(const struct shell *sh,
size_t argc,
char **argv)
{
struct bt_bap_bass_subgroup subgroup = { 0 };
static bool scan_cbs_registered;
unsigned long broadcast_id;
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");
@ -671,6 +697,75 @@ static int cmd_bap_broadcast_assistant_add_broadcast_id(const struct shell *sh,
/* Store results in the `auto_scan` struct */
auto_scan.broadcast_id = broadcast_id;
memcpy(&auto_scan.subgroup, &subgroup, sizeof(subgroup));
memset(auto_scan.broadcast_name, 0, sizeof(auto_scan.broadcast_name));
return 0;
}
static int cmd_bap_broadcast_assistant_add_broadcast_name(const struct shell *sh,
size_t argc, char **argv)
{
struct bt_bap_bass_subgroup subgroup = { 0 };
char *broadcast_name;
int err = 0;
broadcast_name = argv[1];
if (!IN_RANGE(strlen(broadcast_name), BT_AUDIO_BROADCAST_NAME_LEN_MIN,
BT_AUDIO_BROADCAST_NAME_LEN_MAX)) {
shell_error(sh, "Broadcast name should be minimum %d "
"and maximum %d characters", BT_AUDIO_BROADCAST_NAME_LEN_MIN,
BT_AUDIO_BROADCAST_NAME_LEN_MAX);
return -ENOEXEC;
}
auto_scan.pa_sync = shell_strtobool(argv[2], 0, &err);
if (err != 0) {
shell_error(sh, "Could not parse pa_sync: %d", err);
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 (!VALID_BIS_SYNC(bis_sync)) {
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 */
utf8_lcpy(auto_scan.broadcast_name, broadcast_name, strlen(broadcast_name) + 1);
auto_scan.broadcast_id = INVALID_BROADCAST_ID;
memcpy(&auto_scan.subgroup, &subgroup, sizeof(subgroup));
return 0;
}
@ -1073,6 +1168,10 @@ SHELL_STATIC_SUBCMD_SET_CREATE(
"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_broadcast_name, NULL,
"Add a source by broadcast name <broadcast_name> <sync_pa> "
"[<sync_bis>] [<metadata>]",
cmd_bap_broadcast_assistant_add_broadcast_name, 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 [...]]]]>",

View file

@ -259,15 +259,7 @@ int ead_update_ad(void);
static bool bt_shell_ead_decrypt_scan;
/**
* @brief Compares two strings without case sensitivy
*
* @param substr The substring
* @param str The string to find the substring in
*
* @return true if @substr is a substring of @p, else false
*/
static bool is_substring(const char *substr, const char *str)
bool is_substring(const char *substr, const char *str)
{
const size_t str_len = strlen(str);
const size_t sub_str_len = strlen(substr);

View file

@ -36,4 +36,14 @@ extern struct bt_le_per_adv_sync *per_adv_syncs[CONFIG_BT_PER_ADV_SYNC_MAX];
void conn_addr_str(struct bt_conn *conn, char *addr, size_t len);
/**
* @brief Compares two strings without case sensitivy
*
* @param substr The substring
* @param str The string to find the substring in
*
* @return true if @substr is a substring of @p, else false
*/
bool is_substring(const char *substr, const char *str);
#endif /* __BT_H */