Modify the bt_bap_scan_delegator_add_src to take an address and a sid instead of a PA sync object, so that the scan delegator can add a source without syncing to the PA. Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
1031 lines
24 KiB
C
1031 lines
24 KiB
C
/**
|
|
* @file
|
|
* @brief Shell APIs for Bluetooth BAP scan delegator
|
|
*
|
|
* Copyright (c) 2020-2022 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include <zephyr/autoconf.h>
|
|
#include <zephyr/bluetooth/audio/audio.h>
|
|
#include <zephyr/bluetooth/audio/bap.h>
|
|
#include <zephyr/bluetooth/addr.h>
|
|
#include <zephyr/bluetooth/bluetooth.h>
|
|
#include <zephyr/bluetooth/conn.h>
|
|
#include <zephyr/bluetooth/gap.h>
|
|
#include <zephyr/bluetooth/gatt.h>
|
|
#include <zephyr/bluetooth/uuid.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/shell/shell.h>
|
|
#include <zephyr/shell/shell_string_conv.h>
|
|
#include <zephyr/sys/__assert.h>
|
|
#include <zephyr/sys/printk.h>
|
|
#include <zephyr/sys/util.h>
|
|
#include <zephyr/types.h>
|
|
|
|
#include <audio/bap_internal.h>
|
|
#include "shell/bt.h"
|
|
|
|
#define PA_SYNC_INTERVAL_TO_TIMEOUT_RATIO 20 /* Set the timeout relative to interval */
|
|
#define PA_SYNC_SKIP 5
|
|
|
|
static struct sync_state {
|
|
bool pa_syncing;
|
|
bool past_avail;
|
|
uint8_t src_id;
|
|
uint16_t pa_interval;
|
|
struct k_work_delayable pa_timer;
|
|
struct bt_conn *conn;
|
|
struct bt_le_per_adv_sync *pa_sync;
|
|
const struct bt_bap_scan_delegator_recv_state *recv_state;
|
|
uint8_t broadcast_code[BT_AUDIO_BROADCAST_CODE_SIZE];
|
|
} sync_states[CONFIG_BT_BAP_SCAN_DELEGATOR_RECV_STATE_COUNT];
|
|
|
|
static bool past_preference = true;
|
|
|
|
size_t bap_scan_delegator_ad_data_add(struct bt_data data[], size_t data_size)
|
|
{
|
|
static uint8_t ad_bap_scan_delegator[2] = {
|
|
BT_UUID_16_ENCODE(BT_UUID_BASS_VAL),
|
|
};
|
|
|
|
__ASSERT(data_size > 0, "No space for ad_bap_scan_delegator");
|
|
data[0].type = BT_DATA_SVC_DATA16;
|
|
data[0].data_len = ARRAY_SIZE(ad_bap_scan_delegator);
|
|
data[0].data = &ad_bap_scan_delegator[0];
|
|
|
|
return 1U;
|
|
}
|
|
|
|
static struct sync_state *sync_state_get(const struct bt_bap_scan_delegator_recv_state *recv_state)
|
|
{
|
|
for (size_t i = 0U; i < ARRAY_SIZE(sync_states); i++) {
|
|
if (sync_states[i].recv_state == recv_state) {
|
|
return &sync_states[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct sync_state *sync_state_get_or_new(
|
|
const struct bt_bap_scan_delegator_recv_state *recv_state)
|
|
{
|
|
struct sync_state *free_state = NULL;
|
|
|
|
for (size_t i = 0U; i < ARRAY_SIZE(sync_states); i++) {
|
|
if (sync_states[i].recv_state == NULL &&
|
|
free_state == NULL) {
|
|
free_state = &sync_states[i];
|
|
}
|
|
|
|
if (sync_states[i].recv_state == recv_state) {
|
|
return &sync_states[i];
|
|
}
|
|
}
|
|
|
|
return free_state;
|
|
}
|
|
|
|
static struct sync_state *sync_state_get_by_pa(struct bt_le_per_adv_sync *sync)
|
|
{
|
|
for (size_t i = 0U; i < ARRAY_SIZE(sync_states); i++) {
|
|
if (sync_states[i].pa_sync == sync) {
|
|
return &sync_states[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct sync_state *
|
|
sync_state_get_by_sync_info(const struct bt_le_per_adv_sync_synced_info *info)
|
|
{
|
|
for (size_t i = 0U; i < ARRAY_SIZE(sync_states); i++) {
|
|
if (sync_states[i].recv_state != NULL &&
|
|
bt_addr_le_eq(info->addr, &sync_states[i].recv_state->addr) &&
|
|
info->sid == sync_states[i].recv_state->adv_sid) {
|
|
|
|
return &sync_states[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct sync_state *sync_state_new(void)
|
|
{
|
|
for (size_t i = 0U; i < ARRAY_SIZE(sync_states); i++) {
|
|
if (sync_states[i].recv_state == NULL) {
|
|
return &sync_states[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct sync_state *sync_state_get_by_src_id(uint8_t src_id)
|
|
{
|
|
for (size_t i = 0U; i < ARRAY_SIZE(sync_states); i++) {
|
|
if (sync_states[i].src_id == src_id) {
|
|
return &sync_states[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static uint16_t interval_to_sync_timeout(uint16_t pa_interval)
|
|
{
|
|
uint16_t pa_timeout;
|
|
|
|
if (pa_interval == BT_BAP_PA_INTERVAL_UNKNOWN) {
|
|
/* Use maximum value to maximize chance of success */
|
|
pa_timeout = BT_GAP_PER_ADV_MAX_TIMEOUT;
|
|
} else {
|
|
uint32_t interval_ms;
|
|
uint32_t timeout;
|
|
|
|
/* Add retries and convert to unit in 10's of ms */
|
|
interval_ms = BT_GAP_PER_ADV_INTERVAL_TO_MS(pa_interval);
|
|
timeout = (interval_ms * PA_SYNC_INTERVAL_TO_TIMEOUT_RATIO) / 10;
|
|
|
|
/* Enforce restraints */
|
|
pa_timeout = CLAMP(timeout, BT_GAP_PER_ADV_MIN_TIMEOUT, BT_GAP_PER_ADV_MAX_TIMEOUT);
|
|
}
|
|
|
|
return pa_timeout;
|
|
}
|
|
|
|
static void pa_timer_handler(struct k_work *work)
|
|
{
|
|
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
|
|
struct sync_state *state = CONTAINER_OF(dwork, struct sync_state, pa_timer);
|
|
|
|
state->pa_syncing = false;
|
|
|
|
if (state->recv_state != NULL) {
|
|
enum bt_bap_pa_state pa_state;
|
|
|
|
if (state->recv_state->pa_sync_state == BT_BAP_PA_STATE_INFO_REQ) {
|
|
pa_state = BT_BAP_PA_STATE_NO_PAST;
|
|
} else {
|
|
pa_state = BT_BAP_PA_STATE_FAILED;
|
|
}
|
|
|
|
bt_bap_scan_delegator_set_pa_state(state->recv_state->src_id,
|
|
pa_state);
|
|
|
|
shell_info(ctx_shell, "PA timeout for %p", state->recv_state);
|
|
}
|
|
}
|
|
|
|
#if defined(CONFIG_BT_PER_ADV_SYNC_TRANSFER_SENDER)
|
|
static int pa_sync_past(struct bt_conn *conn,
|
|
struct sync_state *state,
|
|
uint16_t pa_interval)
|
|
{
|
|
struct bt_le_per_adv_sync_transfer_param param = { 0 };
|
|
int err;
|
|
|
|
param.skip = PA_SYNC_SKIP;
|
|
param.timeout = interval_to_sync_timeout(pa_interval);
|
|
|
|
err = bt_le_per_adv_sync_transfer_subscribe(conn, ¶m);
|
|
if (err != 0) {
|
|
shell_info(ctx_shell, "Could not do PAST subscribe: %d", err);
|
|
} else {
|
|
shell_info(ctx_shell, "Syncing with PAST: %d", err);
|
|
state->pa_syncing = true;
|
|
k_work_init_delayable(&state->pa_timer, pa_timer_handler);
|
|
(void)k_work_reschedule(&state->pa_timer,
|
|
K_MSEC(param.timeout * 10));
|
|
}
|
|
|
|
return err;
|
|
}
|
|
#endif /* CONFIG_BT_PER_ADV_SYNC_TRANSFER_SENDER */
|
|
|
|
static int pa_sync_no_past(struct sync_state *state,
|
|
uint16_t pa_interval)
|
|
{
|
|
const struct bt_bap_scan_delegator_recv_state *recv_state;
|
|
struct bt_le_per_adv_sync_param param = { 0 };
|
|
int err;
|
|
|
|
recv_state = state->recv_state;
|
|
|
|
bt_addr_le_copy(¶m.addr, &recv_state->addr);
|
|
param.options = BT_LE_PER_ADV_SYNC_OPT_FILTER_DUPLICATE;
|
|
param.sid = recv_state->adv_sid;
|
|
param.skip = PA_SYNC_SKIP;
|
|
param.timeout = interval_to_sync_timeout(pa_interval);
|
|
|
|
/* TODO: Validate that the advertise is broadcasting the same
|
|
* broadcast_id that the receive state has
|
|
*/
|
|
err = bt_le_per_adv_sync_create(¶m, &state->pa_sync);
|
|
if (err != 0) {
|
|
shell_info(ctx_shell, "Could not sync per adv: %d", err);
|
|
} else {
|
|
char addr_str[BT_ADDR_LE_STR_LEN];
|
|
|
|
bt_addr_le_to_str(&recv_state->addr, addr_str, sizeof(addr_str));
|
|
shell_info(ctx_shell, "PA sync pending for addr %s", addr_str);
|
|
state->pa_syncing = true;
|
|
k_work_init_delayable(&state->pa_timer, pa_timer_handler);
|
|
(void)k_work_reschedule(&state->pa_timer,
|
|
K_MSEC(param.timeout * 10));
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int pa_sync_term(struct sync_state *state)
|
|
{
|
|
int err;
|
|
|
|
(void)k_work_cancel_delayable(&state->pa_timer);
|
|
|
|
if (state->pa_sync == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
shell_info(ctx_shell, "Deleting PA sync");
|
|
|
|
err = bt_le_per_adv_sync_delete(state->pa_sync);
|
|
if (err != 0) {
|
|
shell_error(ctx_shell, "Could not delete per adv sync: %d",
|
|
err);
|
|
} else {
|
|
state->pa_syncing = false;
|
|
state->pa_sync = NULL;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static void recv_state_updated_cb(struct bt_conn *conn,
|
|
const struct bt_bap_scan_delegator_recv_state *recv_state)
|
|
{
|
|
shell_info(ctx_shell, "Receive state with ID %u updated", recv_state->src_id);
|
|
}
|
|
|
|
static int pa_sync_req_cb(struct bt_conn *conn,
|
|
const struct bt_bap_scan_delegator_recv_state *recv_state,
|
|
bool past_avail, uint16_t pa_interval)
|
|
{
|
|
struct sync_state *state;
|
|
|
|
shell_info(ctx_shell,
|
|
"PA Sync request: past_avail %u, broadcast_id 0x%06X, pa_interval 0x%04x: %p",
|
|
past_avail, recv_state->broadcast_id, pa_interval,
|
|
recv_state);
|
|
|
|
state = sync_state_get_or_new(recv_state);
|
|
if (state == NULL) {
|
|
shell_error(ctx_shell, "Could not get state");
|
|
|
|
return -1;
|
|
}
|
|
|
|
state->recv_state = recv_state;
|
|
state->src_id = recv_state->src_id;
|
|
|
|
if (recv_state->pa_sync_state == BT_BAP_PA_STATE_SYNCED ||
|
|
recv_state->pa_sync_state == BT_BAP_PA_STATE_INFO_REQ) {
|
|
/* Already syncing */
|
|
/* TODO: Terminate existing sync and then sync to new?*/
|
|
return -1;
|
|
}
|
|
|
|
if (past_avail) {
|
|
state->past_avail = past_preference;
|
|
state->conn = bt_conn_ref(conn);
|
|
} else {
|
|
state->past_avail = false;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pa_sync_term_req_cb(struct bt_conn *conn,
|
|
const struct bt_bap_scan_delegator_recv_state *recv_state)
|
|
{
|
|
struct sync_state *state;
|
|
|
|
shell_info(ctx_shell, "PA Sync term request for %p", recv_state);
|
|
|
|
state = sync_state_get(recv_state);
|
|
if (state == NULL) {
|
|
shell_error(ctx_shell, "Could not get state");
|
|
|
|
return -1;
|
|
}
|
|
|
|
return pa_sync_term(state);
|
|
}
|
|
|
|
static void broadcast_code_cb(struct bt_conn *conn,
|
|
const struct bt_bap_scan_delegator_recv_state *recv_state,
|
|
const uint8_t broadcast_code[BT_AUDIO_BROADCAST_CODE_SIZE])
|
|
{
|
|
struct sync_state *state;
|
|
|
|
shell_info(ctx_shell, "Broadcast code received for %p", recv_state);
|
|
shell_hexdump(ctx_shell, broadcast_code, BT_AUDIO_BROADCAST_CODE_SIZE);
|
|
|
|
state = sync_state_get(recv_state);
|
|
if (state == NULL) {
|
|
shell_error(ctx_shell, "Could not get state");
|
|
|
|
return;
|
|
}
|
|
|
|
(void)memcpy(state->broadcast_code, broadcast_code, BT_AUDIO_BROADCAST_CODE_SIZE);
|
|
}
|
|
|
|
static int bis_sync_req_cb(struct bt_conn *conn,
|
|
const struct bt_bap_scan_delegator_recv_state *recv_state,
|
|
const uint32_t bis_sync_req[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS])
|
|
{
|
|
printk("BIS sync request received for %p\n", recv_state);
|
|
|
|
for (int i = 0; i < CONFIG_BT_BAP_BASS_MAX_SUBGROUPS; i++) {
|
|
printk(" [%d]: 0x%08x\n", i, bis_sync_req[i]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct bt_bap_scan_delegator_cb scan_delegator_cb = {
|
|
.recv_state_updated = recv_state_updated_cb,
|
|
.pa_sync_req = pa_sync_req_cb,
|
|
.pa_sync_term_req = pa_sync_term_req_cb,
|
|
.broadcast_code = broadcast_code_cb,
|
|
.bis_sync_req = bis_sync_req_cb,
|
|
};
|
|
|
|
static void pa_synced_cb(struct bt_le_per_adv_sync *sync,
|
|
struct bt_le_per_adv_sync_synced_info *info)
|
|
{
|
|
struct sync_state *state;
|
|
|
|
shell_info(ctx_shell, "PA %p synced", sync);
|
|
|
|
if (info->conn == NULL) {
|
|
state = sync_state_get_by_pa(sync);
|
|
} else {
|
|
/* In case of PAST we need to use the addr instead */
|
|
state = sync_state_get_by_sync_info(info);
|
|
}
|
|
|
|
if (state == NULL) {
|
|
shell_info(ctx_shell,
|
|
"Could not get sync state from PA sync %p",
|
|
sync);
|
|
return;
|
|
}
|
|
|
|
if (state->conn != NULL) {
|
|
bt_conn_unref(state->conn);
|
|
state->conn = NULL;
|
|
}
|
|
|
|
k_work_cancel_delayable(&state->pa_timer);
|
|
}
|
|
|
|
static void pa_term_cb(struct bt_le_per_adv_sync *sync,
|
|
const struct bt_le_per_adv_sync_term_info *info)
|
|
{
|
|
struct sync_state *state;
|
|
|
|
shell_info(ctx_shell, "PA %p sync terminated", sync);
|
|
|
|
state = sync_state_get_by_pa(sync);
|
|
if (state == NULL) {
|
|
shell_error(ctx_shell,
|
|
"Could not get sync state from PA sync %p",
|
|
sync);
|
|
return;
|
|
}
|
|
|
|
if (state->conn != NULL) {
|
|
bt_conn_unref(state->conn);
|
|
state->conn = NULL;
|
|
}
|
|
|
|
k_work_cancel_delayable(&state->pa_timer);
|
|
}
|
|
|
|
static struct bt_le_per_adv_sync_cb pa_sync_cb = {
|
|
.synced = pa_synced_cb,
|
|
.term = pa_term_cb,
|
|
};
|
|
|
|
static int cmd_bap_scan_delegator_init(const struct shell *sh, size_t argc,
|
|
char **argv)
|
|
{
|
|
static bool registered;
|
|
|
|
if (!registered) {
|
|
bt_le_per_adv_sync_cb_register(&pa_sync_cb);
|
|
bt_bap_scan_delegator_register_cb(&scan_delegator_cb);
|
|
|
|
registered = true;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
static int cmd_bap_scan_delegator_set_past_pref(const struct shell *sh,
|
|
size_t argc, char **argv)
|
|
{
|
|
bool past_pref;
|
|
int err;
|
|
|
|
err = 0;
|
|
|
|
past_pref = shell_strtobool(argv[1], 10, &err);
|
|
if (err != 0) {
|
|
shell_error(sh, "Failed to parse past_pref from %s", argv[1]);
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
past_preference = past_pref;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_bap_scan_delegator_sync_pa(const struct shell *sh, size_t argc,
|
|
char **argv)
|
|
{
|
|
struct sync_state *state;
|
|
unsigned long src_id;
|
|
int err;
|
|
|
|
err = 0;
|
|
|
|
src_id = shell_strtoul(argv[1], 16, &err);
|
|
if (err != 0) {
|
|
shell_error(sh, "Failed to parse src_id from %s", argv[1]);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (src_id > UINT8_MAX) {
|
|
shell_error(sh, "src_id shall be 0x00-0xff");
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
state = sync_state_get_by_src_id((uint8_t)src_id);
|
|
if (state == NULL) {
|
|
shell_error(ctx_shell, "Could not get state");
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (0) {
|
|
#if defined(CONFIG_BT_PER_ADV_SYNC_TRANSFER_SENDER)
|
|
} else if (past_preference &&
|
|
state->past_avail &&
|
|
state->conn != NULL) {
|
|
shell_info(sh, "Syncing with PAST");
|
|
|
|
err = pa_sync_past(state->conn, state, state->pa_interval);
|
|
if (err == 0) {
|
|
err = bt_bap_scan_delegator_set_pa_state(src_id,
|
|
BT_BAP_PA_STATE_INFO_REQ);
|
|
if (err != 0) {
|
|
shell_error(sh,
|
|
"Failed to set INFO_REQ state: %d",
|
|
err);
|
|
}
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
#endif /* CONFIG_BT_PER_ADV_SYNC_TRANSFER_SENDER */
|
|
} else {
|
|
shell_info(sh, "Syncing without PAST");
|
|
err = pa_sync_no_past(state, state->pa_interval);
|
|
}
|
|
|
|
if (err != 0) {
|
|
shell_error(sh, "Failed PA sync: %d", err);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_bap_scan_delegator_term_pa(const struct shell *sh, size_t argc,
|
|
char **argv)
|
|
{
|
|
struct sync_state *state;
|
|
unsigned long src_id;
|
|
int err;
|
|
|
|
err = 0;
|
|
|
|
src_id = shell_strtoul(argv[1], 16, &err);
|
|
if (err != 0) {
|
|
shell_error(sh, "Failed to parse src_id from %s", argv[1]);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (src_id > UINT8_MAX) {
|
|
shell_error(sh, "src_id shall be 0x00-0xff");
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
state = sync_state_get_by_src_id((uint8_t)src_id);
|
|
if (state == NULL) {
|
|
shell_error(ctx_shell, "Could not get state");
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
err = pa_sync_term(state);
|
|
if (err != 0) {
|
|
shell_error(ctx_shell, "Failed to terminate PA sync: %d", err);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_bap_scan_delegator_add_src(const struct shell *sh, size_t argc, char **argv)
|
|
{
|
|
struct bt_bap_scan_delegator_add_src_param param = {0};
|
|
struct bt_bap_bass_subgroup *subgroup_param;
|
|
unsigned long broadcast_id;
|
|
struct sync_state *state;
|
|
unsigned long enc_state;
|
|
unsigned long adv_sid;
|
|
int err;
|
|
|
|
err = bt_addr_le_from_str(argv[1], argv[2], ¶m.addr);
|
|
if (err != 0) {
|
|
shell_error(sh, "Invalid peer address (err %d)", err);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
adv_sid = shell_strtoul(argv[3], 0, &err);
|
|
if (err != 0) {
|
|
shell_error(sh, "Could not parse adv_sid: %d", err);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (adv_sid > BT_GAP_SID_MAX) {
|
|
shell_error(sh, "Invalid adv_sid: %lu", adv_sid);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
param.sid = adv_sid;
|
|
|
|
broadcast_id = shell_strtoul(argv[4], 16, &err);
|
|
if (err != 0) {
|
|
shell_error(sh, "Failed to parse broadcast_id from %s", argv[1]);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (broadcast_id > BT_AUDIO_BROADCAST_ID_MAX) {
|
|
shell_error(sh, "Invalid broadcast_id %lu", broadcast_id);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
enc_state = shell_strtoul(argv[5], 16, &err);
|
|
if (err != 0) {
|
|
shell_error(sh, "Failed to parse enc_state from %s", argv[2]);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (enc_state > BT_BAP_BIG_ENC_STATE_BAD_CODE) {
|
|
shell_error(sh, "Invalid enc_state %s", bt_bap_big_enc_state_str(enc_state));
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* TODO: Support multiple subgroups */
|
|
subgroup_param = ¶m.subgroups[0];
|
|
if (argc > 6) {
|
|
unsigned long bis_sync;
|
|
|
|
bis_sync = shell_strtoul(argv[6], 16, &err);
|
|
if (err != 0) {
|
|
shell_error(sh, "Failed to parse bis_sync from %s", argv[3]);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (bis_sync > BT_BAP_BIS_SYNC_NO_PREF) {
|
|
shell_error(sh, "Invalid bis_sync %lu", bis_sync);
|
|
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
subgroup_param->bis_sync = 0U;
|
|
}
|
|
|
|
if (argc > 7) {
|
|
subgroup_param->metadata_len =
|
|
hex2bin(argv[4], strlen(argv[7]), subgroup_param->metadata,
|
|
sizeof(subgroup_param->metadata));
|
|
|
|
if (subgroup_param->metadata_len == 0U) {
|
|
shell_error(sh, "Could not parse metadata");
|
|
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
subgroup_param->metadata_len = 0U;
|
|
}
|
|
|
|
state = sync_state_new();
|
|
if (state == NULL) {
|
|
shell_error(ctx_shell, "Could not get new state");
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
param.encrypt_state = (enum bt_bap_big_enc_state)enc_state;
|
|
param.broadcast_id = broadcast_id;
|
|
param.num_subgroups = 1U;
|
|
|
|
err = bt_bap_scan_delegator_add_src(¶m);
|
|
if (err < 0) {
|
|
shell_error(ctx_shell, "Failed to add source: %d", err);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
state->src_id = (uint8_t)err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_bap_scan_delegator_add_src_by_pa_sync(const struct shell *sh, size_t argc,
|
|
char **argv)
|
|
{
|
|
struct bt_le_per_adv_sync *pa_sync = per_adv_syncs[selected_per_adv_sync];
|
|
struct bt_bap_scan_delegator_add_src_param param = {0};
|
|
struct bt_bap_bass_subgroup *subgroup_param;
|
|
struct bt_le_per_adv_sync_info sync_info;
|
|
unsigned long broadcast_id;
|
|
struct sync_state *state;
|
|
unsigned long enc_state;
|
|
int err;
|
|
|
|
err = bt_le_per_adv_sync_get_info(pa_sync, &sync_info);
|
|
if (err != 0) {
|
|
shell_error(sh, "Failed to get sync info: %d", err);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
bt_addr_le_copy(¶m.addr, &sync_info.addr);
|
|
param.sid = sync_info.sid;
|
|
|
|
broadcast_id = shell_strtoul(argv[1], 16, &err);
|
|
if (err != 0) {
|
|
shell_error(sh, "Failed to parse broadcast_id from %s", argv[1]);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (broadcast_id > BT_AUDIO_BROADCAST_ID_MAX) {
|
|
shell_error(sh, "Invalid broadcast_id %lu", broadcast_id);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
enc_state = shell_strtoul(argv[2], 16, &err);
|
|
if (err != 0) {
|
|
shell_error(sh, "Failed to parse enc_state from %s", argv[2]);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (enc_state > BT_BAP_BIG_ENC_STATE_BAD_CODE) {
|
|
shell_error(sh, "Invalid enc_state %s", bt_bap_big_enc_state_str(enc_state));
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* TODO: Support multiple subgroups */
|
|
subgroup_param = ¶m.subgroups[0];
|
|
if (argc > 3) {
|
|
unsigned long bis_sync;
|
|
|
|
bis_sync = shell_strtoul(argv[3], 16, &err);
|
|
if (err != 0) {
|
|
shell_error(sh, "Failed to parse bis_sync from %s", argv[3]);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (bis_sync > BT_BAP_BIS_SYNC_NO_PREF) {
|
|
shell_error(sh, "Invalid bis_sync %lu", bis_sync);
|
|
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
subgroup_param->bis_sync = 0U;
|
|
}
|
|
|
|
if (argc > 4) {
|
|
subgroup_param->metadata_len =
|
|
hex2bin(argv[4], strlen(argv[4]), subgroup_param->metadata,
|
|
sizeof(subgroup_param->metadata));
|
|
|
|
if (subgroup_param->metadata_len == 0U) {
|
|
shell_error(sh, "Could not parse metadata");
|
|
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
subgroup_param->metadata_len = 0U;
|
|
}
|
|
|
|
state = sync_state_new();
|
|
if (state == NULL) {
|
|
shell_error(ctx_shell, "Could not get new state");
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
param.encrypt_state = (enum bt_bap_big_enc_state)enc_state;
|
|
param.broadcast_id = broadcast_id;
|
|
param.num_subgroups = 1U;
|
|
|
|
err = bt_bap_scan_delegator_add_src(¶m);
|
|
if (err < 0) {
|
|
shell_error(ctx_shell, "Failed to add source: %d", err);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
state->src_id = (uint8_t)err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_bap_scan_delegator_mod_src(const struct shell *sh, size_t argc,
|
|
char **argv)
|
|
{
|
|
struct bt_bap_bass_subgroup *subgroup_param;
|
|
struct bt_bap_scan_delegator_mod_src_param param;
|
|
unsigned long broadcast_id;
|
|
unsigned long enc_state;
|
|
unsigned long src_id;
|
|
int err;
|
|
|
|
err = 0;
|
|
|
|
src_id = shell_strtoul(argv[1], 16, &err);
|
|
if (err != 0) {
|
|
shell_error(sh, "Failed to parse src_id from %s", argv[1]);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (src_id > UINT8_MAX) {
|
|
shell_error(sh, "Invalid src_id %lu", src_id);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
broadcast_id = shell_strtoul(argv[2], 16, &err);
|
|
if (err != 0) {
|
|
shell_error(sh, "Failed to parse broadcast_id from %s", argv[2]);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (broadcast_id > BT_AUDIO_BROADCAST_ID_MAX) {
|
|
shell_error(sh, "Invalid broadcast_id %lu", broadcast_id);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
enc_state = shell_strtoul(argv[3], 16, &err);
|
|
if (err != 0) {
|
|
shell_error(sh, "Failed to parse enc_state from %s", argv[3]);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (enc_state > BT_BAP_BIG_ENC_STATE_BAD_CODE) {
|
|
shell_error(sh, "Invalid enc_state %s", bt_bap_big_enc_state_str(enc_state));
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* TODO: Support multiple subgroups */
|
|
subgroup_param = ¶m.subgroups[0];
|
|
if (argc > 4) {
|
|
unsigned long bis_sync;
|
|
|
|
bis_sync = shell_strtoul(argv[4], 16, &err);
|
|
if (err != 0) {
|
|
shell_error(sh, "Failed to parse bis_sync from %s", argv[4]);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (bis_sync > BT_BAP_BIS_SYNC_NO_PREF) {
|
|
shell_error(sh, "Invalid bis_sync %lu", bis_sync);
|
|
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
subgroup_param->bis_sync = 0U;
|
|
}
|
|
|
|
if (argc > 5) {
|
|
subgroup_param->metadata_len = hex2bin(argv[5], strlen(argv[5]),
|
|
subgroup_param->metadata,
|
|
sizeof(subgroup_param->metadata));
|
|
|
|
if (subgroup_param->metadata_len == 0U) {
|
|
shell_error(sh, "Could not parse metadata");
|
|
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
subgroup_param->metadata_len = 0U;
|
|
}
|
|
|
|
|
|
param.src_id = (uint8_t)src_id;
|
|
param.encrypt_state = (enum bt_bap_big_enc_state)enc_state;
|
|
param.broadcast_id = broadcast_id;
|
|
param.num_subgroups = 1U;
|
|
|
|
err = bt_bap_scan_delegator_mod_src(¶m);
|
|
if (err < 0) {
|
|
shell_error(ctx_shell, "Failed to modify source: %d", err);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_bap_scan_delegator_rem_src(const struct shell *sh, size_t argc,
|
|
char **argv)
|
|
{
|
|
unsigned long src_id;
|
|
int err;
|
|
|
|
err = 0;
|
|
|
|
src_id = shell_strtoul(argv[1], 16, &err);
|
|
if (err != 0) {
|
|
shell_error(sh, "Failed to parse src_id from %s", argv[1]);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (src_id > UINT8_MAX) {
|
|
shell_error(sh, "Invalid src_id %lu", src_id);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = bt_bap_scan_delegator_rem_src((uint8_t)src_id);
|
|
if (err < 0) {
|
|
shell_error(ctx_shell, "Failed to remove source source: %d",
|
|
err);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_bap_scan_delegator_bis_synced(const struct shell *sh, size_t argc,
|
|
char **argv)
|
|
{
|
|
uint32_t bis_syncs[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS];
|
|
unsigned long pa_sync_state;
|
|
unsigned long bis_synced;
|
|
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;
|
|
}
|
|
|
|
pa_sync_state = shell_strtoul(argv[2], 0, &result);
|
|
if (result != 0) {
|
|
shell_error(sh, "Could not parse pa_sync_state: %d", result);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (pa_sync_state > BT_BAP_PA_STATE_NO_PAST) {
|
|
shell_error(sh, "Invalid pa_sync_state %s", bt_bap_pa_state_str(pa_sync_state));
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
bis_synced = shell_strtoul(argv[3], 0, &result);
|
|
if (result != 0) {
|
|
shell_error(sh, "Could not parse bis_synced: %d", result);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (bis_synced > UINT32_MAX) {
|
|
shell_error(sh, "Invalid bis_synced %ld", bis_synced);
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
for (size_t i = 0U; i < ARRAY_SIZE(bis_syncs); i++) {
|
|
bis_syncs[i] = bis_synced;
|
|
}
|
|
|
|
result = bt_bap_scan_delegator_set_bis_sync_state(src_id, bis_syncs);
|
|
if (result != 0) {
|
|
shell_print(sh, "Fail: %d", result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static int cmd_bap_scan_delegator(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_scan_delegator_cmds,
|
|
SHELL_CMD_ARG(init, NULL,
|
|
"Initialize the service and register callbacks",
|
|
cmd_bap_scan_delegator_init, 1, 0),
|
|
SHELL_CMD_ARG(set_past_pref, NULL,
|
|
"Set PAST preference <true || false>",
|
|
cmd_bap_scan_delegator_set_past_pref, 2, 0),
|
|
SHELL_CMD_ARG(sync_pa, NULL,
|
|
"Sync to PA <src_id>",
|
|
cmd_bap_scan_delegator_sync_pa, 2, 0),
|
|
SHELL_CMD_ARG(term_pa, NULL,
|
|
"Terminate PA sync <src_id>",
|
|
cmd_bap_scan_delegator_term_pa, 2, 0),
|
|
SHELL_CMD_ARG(add_src, NULL,
|
|
"Add a PA as source <addr> <sid> <broadcast_id> <enc_state> "
|
|
"[bis_sync [metadata]]",
|
|
cmd_bap_scan_delegator_add_src, 5, 2),
|
|
SHELL_CMD_ARG(add_src_by_pa_sync, NULL,
|
|
"Add a PA as source <broadcast_id> <enc_state> [bis_sync [metadata]]",
|
|
cmd_bap_scan_delegator_add_src_by_pa_sync, 3, 2),
|
|
SHELL_CMD_ARG(mod_src, NULL,
|
|
"Modify source <src_id> <broadcast_id> <enc_state> [bis_sync [metadata]]",
|
|
cmd_bap_scan_delegator_mod_src, 4, 2),
|
|
SHELL_CMD_ARG(rem_src, NULL,
|
|
"Remove source <src_id>",
|
|
cmd_bap_scan_delegator_rem_src, 2, 0),
|
|
SHELL_CMD_ARG(synced, NULL,
|
|
"Set server scan state <src_id> <bis_syncs>",
|
|
cmd_bap_scan_delegator_bis_synced, 3, 0),
|
|
SHELL_SUBCMD_SET_END,
|
|
);
|
|
|
|
SHELL_CMD_ARG_REGISTER(bap_scan_delegator, &bap_scan_delegator_cmds,
|
|
"Bluetooth BAP scan delegator shell commands",
|
|
cmd_bap_scan_delegator, 1, 1);
|