From 3d08b836f6b854249ba98c66da0ed9392a603abc Mon Sep 17 00:00:00 2001 From: Emil Gydesen Date: Mon, 25 Aug 2025 10:04:23 +0200 Subject: [PATCH 1/7] Bluetooth: CAP: Shell: Add handover u->b shell command Add shell command for unicast to broadcast for the CAP handover procedures. Signed-off-by: Emil Gydesen --- subsys/bluetooth/audio/shell/CMakeLists.txt | 4 + subsys/bluetooth/audio/shell/audio.h | 3 +- subsys/bluetooth/audio/shell/bap.c | 6 + subsys/bluetooth/audio/shell/cap_handover.c | 329 +++++++++++++++++++ subsys/bluetooth/audio/shell/cap_initiator.c | 75 ++--- tests/bluetooth/shell/audio.conf | 1 + 6 files changed, 377 insertions(+), 41 deletions(-) create mode 100644 subsys/bluetooth/audio/shell/cap_handover.c diff --git a/subsys/bluetooth/audio/shell/CMakeLists.txt b/subsys/bluetooth/audio/shell/CMakeLists.txt index db1641a86f893..0486d22e3eb8d 100644 --- a/subsys/bluetooth/audio/shell/CMakeLists.txt +++ b/subsys/bluetooth/audio/shell/CMakeLists.txt @@ -71,6 +71,10 @@ zephyr_library_sources_ifdef( CONFIG_BT_CAP_COMMANDER cap_commander.c ) +zephyr_library_sources_ifdef( + CONFIG_BT_CAP_HANDOVER + cap_handover.c + ) zephyr_library_sources_ifdef( CONFIG_BT_HAS_CLIENT has_client.c diff --git a/subsys/bluetooth/audio/shell/audio.h b/subsys/bluetooth/audio/shell/audio.h index 9695e0c869580..33de5f0e3acf1 100644 --- a/subsys/bluetooth/audio/shell/audio.h +++ b/subsys/bluetooth/audio/shell/audio.h @@ -168,7 +168,8 @@ void bap_usb_get_frame(struct shell_stream *sh_stream, enum bt_audio_location ch size_t bap_usb_get_frame_size(const struct shell_stream *sh_stream); struct broadcast_source { - bool is_cap; + bool is_cap: 1; + bool handover_in_progress: 1; union { struct bt_bap_broadcast_source *bap_source; struct bt_cap_broadcast_source *cap_source; diff --git a/subsys/bluetooth/audio/shell/bap.c b/subsys/bluetooth/audio/shell/bap.c index d2f562e7fdac4..e93a78fc4f961 100644 --- a/subsys/bluetooth/audio/shell/bap.c +++ b/subsys/bluetooth/audio/shell/bap.c @@ -3218,6 +3218,12 @@ static int cmd_create_broadcast(const struct shell *sh, size_t argc, return -ENOEXEC; } + if (IS_ENABLED(CONFIG_BT_CAP_HANDOVER) && default_source.handover_in_progress) { + shell_info(sh, "CAP Handover in progress"); + + return -ENOEXEC; + } + named_preset = &default_broadcast_source_preset; for (size_t i = 1U; i < argc; i++) { diff --git a/subsys/bluetooth/audio/shell/cap_handover.c b/subsys/bluetooth/audio/shell/cap_handover.c new file mode 100644 index 0000000000000..8210ee6bb27aa --- /dev/null +++ b/subsys/bluetooth/audio/shell/cap_handover.c @@ -0,0 +1,329 @@ +/** + * @file + * @brief Shell APIs for Bluetooth CAP handover + * + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/bt_shell_private.h" +#include "host/shell/bt.h" +#include "audio.h" + +static void unicast_to_broadcast_complete_cb(int err, struct bt_conn *conn, + struct bt_cap_unicast_group *unicast_group, + struct bt_cap_broadcast_source *broadcast_source) +{ + if (err == -ECANCELED) { + bt_shell_print("Unicast to broadcast handover was cancelled for conn %p", conn); + + default_source.broadcast_id = BT_BAP_INVALID_BROADCAST_ID; + } else if (err != 0) { + bt_shell_error("Unicast to broadcast handover failed for conn %p (%d)", conn, err); + + default_source.broadcast_id = BT_BAP_INVALID_BROADCAST_ID; + } else { + bt_shell_print( + "Unicast to broadcast handover completed with new broadcast source %p", + (void *)broadcast_source); + + default_source.cap_source = broadcast_source; + default_source.is_cap = true; + } + + default_source.handover_in_progress = false; +} + +static void broadcast_to_unicast_complete_cb(int err, struct bt_conn *conn, + struct bt_cap_broadcast_source *broadcast_source, + struct bt_cap_unicast_group *unicast_group) +{ + if (err == -ECANCELED) { + bt_shell_print("Broadcast to unicast handover was cancelled for conn %p", conn); + } else if (err != 0) { + bt_shell_error("Broadcast to unicast handover failed for conn %p (%d)", conn, err); + } else { + bt_shell_print("Broadcast to unicast handover completed with new group %p", + (void *)unicast_group); + } +} + +static struct bt_cap_handover_cb cbs = { + .unicast_to_broadcast_complete = unicast_to_broadcast_complete_cb, + .broadcast_to_unicast_complete = broadcast_to_unicast_complete_cb, +}; + +struct cap_unicast_group_stream_lookup { + struct bt_cap_stream *active_sink_streams[CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT]; + size_t active_sink_streams_cnt; +}; + +static bool unicast_group_foreach_stream_cb(struct bt_cap_stream *cap_stream, void *user_data) +{ + struct cap_unicast_group_stream_lookup *data = user_data; + const struct bt_bap_stream *bap_stream = &cap_stream->bap_stream; + + __ASSERT_NO_MSG(data->active_sink_streams_cnt < ARRAY_SIZE(data->active_sink_streams)); + + if (bap_stream->ep != NULL) { + struct bt_bap_ep_info ep_info; + int err; + + err = bt_bap_ep_get_info(bap_stream->ep, &ep_info); + __ASSERT_NO_MSG(err == 0); + + /* Only consider sink streams for handover to broadcast */ + if (ep_info.state == BT_BAP_EP_STATE_STREAMING && + ep_info.dir == BT_AUDIO_DIR_SINK) { + data->active_sink_streams[data->active_sink_streams_cnt++] = cap_stream; + } + } + + return false; +} + +static int register_callbacks(void) +{ + static bool registered; + + if (!registered) { + const int err = bt_cap_handover_register_cb(&cbs); + + if (err != 0) { + return err; + } + + registered = true; + } + + return 0; +} + +static int cmd_cap_handover_unicast_to_broadcast(const struct shell *sh, size_t argc, char *argv[]) +{ + static struct bt_cap_initiator_broadcast_stream_param + stream_params[CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT]; + static struct bt_cap_initiator_broadcast_subgroup_param subgroup_param = {0}; + static struct bt_cap_initiator_broadcast_create_param broadcast_create_param; + /* Struct containing the converted unicast group configuration */ + struct bt_cap_handover_unicast_to_broadcast_param param = {0}; + struct cap_unicast_group_stream_lookup lookup_data = {0}; + struct bt_le_ext_adv *adv = adv_sets[selected_adv]; + const struct named_lc3_preset *named_preset; + uint32_t broadcast_id = 0U; + int err; + + if (adv == NULL) { + shell_error(sh, "Extended advertising set is NULL"); + + return -ENOEXEC; + } + + if (default_unicast_group.cap_group == NULL) { + shell_error(sh, "CAP unicast group not created"); + + return -ENOEXEC; + } + + if (!default_unicast_group.is_cap) { + shell_error(sh, "Unicast group is not CAP"); + + return -ENOEXEC; + } + + if (default_source.cap_source != NULL) { + shell_error(sh, "CAP Broadcast source already created"); + + return -ENOEXEC; + } + + if (default_source.handover_in_progress) { + shell_info(sh, "CAP Handover in progress"); + + return -ENOEXEC; + } + + named_preset = &default_broadcast_source_preset; + + for (size_t i = 1U; i < argc; i++) { + char *arg = argv[i]; + + if (strcmp(arg, "enc") == 0) { + if (argc > i) { + size_t bcode_len; + + i++; + arg = argv[i]; + + bcode_len = hex2bin(arg, strlen(arg), + broadcast_create_param.broadcast_code, + sizeof(broadcast_create_param.broadcast_code)); + + if (bcode_len != sizeof(broadcast_create_param.broadcast_code)) { + shell_error(sh, "Invalid Broadcast Code Length: %zu", + bcode_len); + + return -ENOEXEC; + } + + broadcast_create_param.encryption = true; + } else { + shell_help(sh); + + return SHELL_CMD_HELP_PRINTED; + } + } else if (strcmp(arg, "preset") == 0) { + if (argc > i) { + + i++; + arg = argv[i]; + + named_preset = bap_get_named_preset(false, BT_AUDIO_DIR_SINK, arg); + if (named_preset == NULL) { + shell_error(sh, "Unable to parse named_preset %s", arg); + + return -ENOEXEC; + } + } else { + shell_help(sh); + + return SHELL_CMD_HELP_PRINTED; + } + } + } + + err = register_callbacks(); + if (err != 0) { + shell_error(sh, "Failed to register callbacks: %d", err); + + return -ENOEXEC; + } + + /* Lookup all active sink streams */ + err = bt_cap_unicast_group_foreach_stream(default_unicast_group.cap_group, + unicast_group_foreach_stream_cb, &lookup_data); + __ASSERT_NO_MSG(err == 0); + + if (lookup_data.active_sink_streams_cnt == 0U) { + shell_error(sh, "No active sink streams in group, cannot handover"); + + return -ENOEXEC; + } + + /* Generate the broadcast_id here even though the broadcast source is created later - + * We need to provide the broadcast ID that we expect to use when it is started + */ + err = bt_rand(&broadcast_id, BT_AUDIO_BROADCAST_ID_SIZE); + if (err != 0) { + shell_error(sh, "Unable to generate broadcast ID: %d\n", err); + + return -ENOEXEC; + } + + shell_print(sh, "Generated broadcast_id 0x%06X", broadcast_id); + + copy_broadcast_source_preset(&default_source, named_preset); + + subgroup_param.stream_count = lookup_data.active_sink_streams_cnt; + subgroup_param.stream_params = stream_params; + subgroup_param.codec_cfg = &default_source.codec_cfg; + for (size_t i = 0U; i < lookup_data.active_sink_streams_cnt; i++) { + subgroup_param.stream_params[i].stream = lookup_data.active_sink_streams[i]; + stream_params[i].data_len = 0U; + stream_params[i].data = NULL; + } + + (void)memset(&broadcast_create_param, 0, sizeof(broadcast_create_param)); + broadcast_create_param.subgroup_count = 1U; + broadcast_create_param.subgroup_params = &subgroup_param; + broadcast_create_param.qos = &default_source.qos; + broadcast_create_param.packing = BT_ISO_PACKING_SEQUENTIAL; + broadcast_create_param.encryption = false; + + param.type = BT_CAP_SET_TYPE_AD_HOC; + param.unicast_group = default_unicast_group.cap_group; + param.broadcast_create_param = &broadcast_create_param; + param.ext_adv = adv; + param.pa_interval = BT_BAP_PA_INTERVAL_UNKNOWN; + param.broadcast_id = broadcast_id; + + err = bt_cap_handover_unicast_to_broadcast(¶m); + if (err != 0) { + shell_error(sh, "Failed to handover unicast audio to broadcast: %d", err); + + return -ENOEXEC; + } + + default_source.handover_in_progress = true; + default_source.broadcast_id = broadcast_id; + + return 0; +} + +static int cmd_cap_handover_broadcast_to_unicast(const struct shell *sh, size_t argc, char *argv[]) +{ + int err; + + err = register_callbacks(); + if (err != 0) { + shell_error(sh, "Failed to register callbacks: %d", err); + + return -ENOEXEC; + } + + return 0; +} + +static int cmd_cap_handover(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( + cap_handover_cmds, + SHELL_CMD_ARG(unicast_to_broadcast, NULL, + "Handover current unicast group to broadcast (unicast group will be deleted)", + cmd_cap_handover_unicast_to_broadcast, 1, 0), + SHELL_CMD_ARG( + broadcast_to_unicast, NULL, + "Handover current broadcast source to unicast (broadcast source will be deleted)", + cmd_cap_handover_broadcast_to_unicast, 1, 0), + SHELL_SUBCMD_SET_END); + +SHELL_CMD_ARG_REGISTER(cap_handover, &cap_handover_cmds, "Bluetooth CAP handover shell commands", + cmd_cap_handover, 1, 1); diff --git a/subsys/bluetooth/audio/shell/cap_initiator.c b/subsys/bluetooth/audio/shell/cap_initiator.c index 9755e07e791b5..7c10916286b20 100644 --- a/subsys/bluetooth/audio/shell/cap_initiator.c +++ b/subsys/bluetooth/audio/shell/cap_initiator.c @@ -53,8 +53,7 @@ static void cap_discover_cb(struct bt_conn *conn, int err, return; } - bt_shell_print("discovery completed%s", - csis_inst == NULL ? "" : " with CSIS"); + bt_shell_print("discovery completed%s", csis_inst == NULL ? "" : " with CSIS"); } static void cap_unicast_start_complete_cb(int err, struct bt_conn *conn) @@ -108,8 +107,7 @@ static struct bt_cap_initiator_cb cbs = { .unicast_stop_complete = unicast_stop_complete_cb, }; -static int cmd_cap_initiator_discover(const struct shell *sh, size_t argc, - char *argv[]) +static int cmd_cap_initiator_discover(const struct shell *sh, size_t argc, char *argv[]) { static bool cbs_registered; int err; @@ -144,8 +142,7 @@ static void populate_connected_conns(struct bt_conn *conn, void *data) } } -static int cmd_cap_initiator_unicast_start(const struct shell *sh, size_t argc, - char *argv[]) +static int cmd_cap_initiator_unicast_start(const struct shell *sh, size_t argc, char *argv[]) { struct bt_cap_unicast_group_stream_param group_stream_params[CAP_UNICAST_CLIENT_STREAM_COUNT] = {0}; @@ -214,8 +211,8 @@ static int cmd_cap_initiator_unicast_start(const struct shell *sh, size_t argc, } if (err != 0) { - shell_error(sh, "Failed to parse argument: %s: %s (%d)", - arg, argv[argn], err); + shell_error(sh, "Failed to parse argument: %s: %s (%d)", arg, argv[argn], + err); return err; } @@ -226,8 +223,7 @@ static int cmd_cap_initiator_unicast_start(const struct shell *sh, size_t argc, /* Populate the array of connected connections */ (void)memset(connected_conns, 0, sizeof(connected_conns)); - bt_conn_foreach(BT_CONN_TYPE_LE, populate_connected_conns, - (void *)connected_conns); + bt_conn_foreach(BT_CONN_TYPE_LE, populate_connected_conns, (void *)connected_conns); start_param.count = 0U; start_param.stream_params = stream_param; @@ -243,15 +239,14 @@ static int cmd_cap_initiator_unicast_start(const struct shell *sh, size_t argc, #if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 conn_snk_cnt = sink_cnt; for (size_t j = 0U; j < sink_cnt; j++) { - struct bt_cap_stream *stream = - &unicast_streams[start_param.count].stream; + struct bt_cap_stream *stream = &unicast_streams[start_param.count].stream; struct shell_stream *uni_stream = CONTAINER_OF(stream, struct shell_stream, stream); struct bt_bap_ep *snk_ep = snks[bt_conn_index(conn)][j]; if (snk_ep == NULL) { - shell_info(sh, "Could only setup %zu/%zu sink endpoints", - j, sink_cnt); + shell_info(sh, "Could only setup %zu/%zu sink endpoints", j, + sink_cnt); conn_snk_cnt = j; break; } @@ -275,15 +270,14 @@ static int cmd_cap_initiator_unicast_start(const struct shell *sh, size_t argc, #if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 conn_src_cnt = source_cnt; for (size_t j = 0U; j < source_cnt; j++) { - struct bt_cap_stream *stream = - &unicast_streams[start_param.count].stream; + struct bt_cap_stream *stream = &unicast_streams[start_param.count].stream; struct shell_stream *uni_stream = CONTAINER_OF(stream, struct shell_stream, stream); struct bt_bap_ep *src_ep = srcs[bt_conn_index(conn)][j]; if (src_ep == NULL) { - shell_info(sh, "Could only setup %zu/%zu source endpoints", - j, source_cnt); + shell_info(sh, "Could only setup %zu/%zu source endpoints", j, + source_cnt); conn_src_cnt = j; break; } @@ -342,8 +336,7 @@ static int cmd_cap_initiator_unicast_start(const struct shell *sh, size_t argc, return 0; } -static int cmd_cap_initiator_unicast_list(const struct shell *sh, size_t argc, - char *argv[]) +static int cmd_cap_initiator_unicast_list(const struct shell *sh, size_t argc, char *argv[]) { for (size_t i = 0U; i < ARRAY_SIZE(unicast_streams); i++) { if (unicast_streams[i].stream.bap_stream.conn == NULL) { @@ -355,8 +348,7 @@ static int cmd_cap_initiator_unicast_list(const struct shell *sh, size_t argc, return 0; } -static int cmd_cap_initiator_unicast_update(const struct shell *sh, size_t argc, - char *argv[]) +static int cmd_cap_initiator_unicast_update(const struct shell *sh, size_t argc, char *argv[]) { struct bt_cap_unicast_audio_update_stream_param stream_params[CAP_UNICAST_CLIENT_STREAM_COUNT] = {0}; @@ -408,15 +400,14 @@ static int cmd_cap_initiator_unicast_update(const struct shell *sh, size_t argc, struct bt_bap_ep_info ep_info; if (err != 0) { - shell_error(sh, "Failed to parse stream argument %s: %d", - argv[i], err); + shell_error(sh, "Failed to parse stream argument %s: %d", argv[i], + err); return err; } if (!PART_OF_ARRAY(unicast_streams, stream)) { - shell_error(sh, "Pointer %p is not a CAP stream pointer", - stream); + shell_error(sh, "Pointer %p is not a CAP stream pointer", stream); return -ENOEXEC; } @@ -462,8 +453,7 @@ static int cmd_cap_initiator_unicast_update(const struct shell *sh, size_t argc, return err; } -static int cmd_cap_initiator_unicast_stop(const struct shell *sh, size_t argc, - char *argv[]) +static int cmd_cap_initiator_unicast_stop(const struct shell *sh, size_t argc, char *argv[]) { struct bt_cap_stream *streams[CAP_UNICAST_CLIENT_STREAM_COUNT]; struct bt_cap_unicast_audio_stop_param param = {0}; @@ -602,15 +592,15 @@ static int cap_ac_unicast_start(const struct cap_unicast_ac_param *param, } if (snk_ep_cnt != snk_cnt) { - bt_shell_error("Sink endpoint and stream count mismatch: %zu != %zu", - snk_ep_cnt, snk_cnt); + bt_shell_error("Sink endpoint and stream count mismatch: %zu != %zu", snk_ep_cnt, + snk_cnt); return -ENOEXEC; } if (src_ep_cnt != src_cnt) { - bt_shell_error("Source endpoint and stream count mismatch: %zu != %zu", - src_ep_cnt, src_cnt); + bt_shell_error("Source endpoint and stream count mismatch: %zu != %zu", src_ep_cnt, + src_cnt); return -ENOEXEC; } @@ -1303,6 +1293,7 @@ static int cmd_broadcast_delete(const struct shell *sh, size_t argc, char *argv[ default_source.cap_source = NULL; default_source.is_cap = false; + default_source.broadcast_id = BT_BAP_INVALID_BROADCAST_ID; return 0; } @@ -1315,10 +1306,10 @@ int cap_ac_broadcast(const struct shell *sh, size_t argc, char **argv, uint8_t stereo_data[] = { BT_AUDIO_CODEC_DATA(BT_AUDIO_CODEC_CFG_CHAN_ALLOC, BT_AUDIO_LOCATION_FRONT_RIGHT | BT_AUDIO_LOCATION_FRONT_LEFT)}; - uint8_t right_data[] = {BT_AUDIO_CODEC_DATA(BT_AUDIO_CODEC_CFG_CHAN_ALLOC, - BT_AUDIO_LOCATION_FRONT_RIGHT)}; - uint8_t left_data[] = {BT_AUDIO_CODEC_DATA(BT_AUDIO_CODEC_CFG_CHAN_ALLOC, - BT_AUDIO_LOCATION_FRONT_LEFT)}; + uint8_t right_data[] = { + BT_AUDIO_CODEC_DATA(BT_AUDIO_CODEC_CFG_CHAN_ALLOC, BT_AUDIO_LOCATION_FRONT_RIGHT)}; + uint8_t left_data[] = { + BT_AUDIO_CODEC_DATA(BT_AUDIO_CODEC_CFG_CHAN_ALLOC, BT_AUDIO_LOCATION_FRONT_LEFT)}; struct bt_cap_initiator_broadcast_subgroup_param subgroup_param = {0}; struct bt_cap_initiator_broadcast_create_param create_param = {0}; uint32_t broadcast_id = 0U; @@ -1330,6 +1321,12 @@ int cap_ac_broadcast(const struct shell *sh, size_t argc, char **argv, return -ENOEXEC; } + if (IS_ENABLED(CONFIG_BT_CAP_HANDOVER) && default_source.handover_in_progress) { + shell_info(sh, "CAP Handover in progress"); + + return -ENOEXEC; + } + adv = adv_sets[selected_adv]; if (adv == NULL) { shell_error(sh, "Extended advertising set is NULL"); @@ -1430,8 +1427,7 @@ static int cmd_cap_ac_14(const struct shell *sh, size_t argc, char **argv) static int cmd_cap_initiator(const struct shell *sh, size_t argc, char **argv) { if (argc > 1) { - shell_error(sh, "%s unknown parameter: %s", - argv[0], argv[1]); + shell_error(sh, "%s unknown parameter: %s", argv[0], argv[1]); } else { shell_error(sh, "%s Missing subcommand", argv[0]); } @@ -1523,8 +1519,7 @@ SHELL_STATIC_SUBCMD_SET_CREATE( #endif /* CONFIG_BT_BAP_BROADCAST_SOURCE */ SHELL_SUBCMD_SET_END); -SHELL_CMD_ARG_REGISTER(cap_initiator, &cap_initiator_cmds, - "Bluetooth CAP initiator shell commands", +SHELL_CMD_ARG_REGISTER(cap_initiator, &cap_initiator_cmds, "Bluetooth CAP initiator shell commands", cmd_cap_initiator, 1, 1); size_t cap_initiator_pa_data_add(struct bt_data *data_array, const size_t data_array_size) diff --git a/tests/bluetooth/shell/audio.conf b/tests/bluetooth/shell/audio.conf index bae871f97cb2c..e20b20d1edbdf 100644 --- a/tests/bluetooth/shell/audio.conf +++ b/tests/bluetooth/shell/audio.conf @@ -210,6 +210,7 @@ CONFIG_LOG=y # CONFIG_BT_CAP_ACCEPTOR_LOG_LEVEL_DBG=y # CONFIG_BT_CAP_COMMANDER_LOG_LEVEL_DBG=y # CONFIG_BT_CAP_COMMON_LOG_LEVEL_DBG=y +# CONFIG_BT_CAP_HANDOVER_LOG_LEVEL_DBG=y # CONFIG_BT_CAP_INITIATOR_LOG_LEVEL_DBG=y # CONFIG_BT_CSIP_SET_COORDINATOR_LOG_LEVEL_DBG=y # CONFIG_BT_CSIP_SET_MEMBER_LOG_LEVEL_DBG=y From 1b7068a23565a3bf1bece2cc96f0b8ddf071fc60 Mon Sep 17 00:00:00 2001 From: Emil Gydesen Date: Thu, 28 Aug 2025 14:38:27 +0200 Subject: [PATCH 2/7] doc: Bluetooth: CAP: Document CAP handover u->b command Add documentation and example usage of the unicast_to_broadcast CAP handover command. Signed-off-by: Emil Gydesen --- .../bluetooth/shell/audio/cap.rst | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/doc/connectivity/bluetooth/shell/audio/cap.rst b/doc/connectivity/bluetooth/shell/audio/cap.rst index 47ff4781e0cac..db484b24fe86e 100644 --- a/doc/connectivity/bluetooth/shell/audio/cap.rst +++ b/doc/connectivity/bluetooth/shell/audio/cap.rst @@ -485,3 +485,64 @@ Distributing the broadcast code Broadcast reception start completed uart:~$ cap_commander distribute_broadcast_code 0 "BroadcastCode" Distribute broadcast code completed + +CAP Handover +************ + +The handover procedures allow the user to switch between unicast and broadcast streams. Since +broadcast streams are always unidirectional, the procedures will only work for streams with audio +direction from the Initiator to the Acceptor (sink streams). + +Using the CAP Hanover procedures +================================ + +When the Bluetooth stack has been initialized (:code:`bt init`), +,one or more remote CAP acceptor devices have been connected, +and audio streams have been set up, +the handover procedures can be used to switch between unicast and broadcast. +Before any of the handover procedures can be used, +the :code:`bap discover`, :code:`cap_initiator discover` +and :code:`bap_broadcast_assistant discover` commands must have been issued and completed. + +.. code-block:: console + + cap_handover --help + cap_handover - Bluetooth CAP handover shell commands + Subcommands: + unicast_to_broadcast : Handover current unicast group to broadcast (unicast + group will be deleted) + broadcast_to_unicast : Handover current broadcast source to unicast + (broadcast source will be deleted) + + + +Handover unicast to broadcast +----------------------------- + +This command hands over one or more unicast streams from unicast to broadcast. + +.. code-block:: console + + uart:~$ bt init + uart:~$ bap init + uart:~$ bt connect + + # Discover necessary services + uart:~$ bap discover + uart:~$ cap_initiator discover + uart:~$ bap_broadcast_assistant discover + + # Setup unicast audio e.g. using the ac_1 + uart:~$ cap_initiator ac_1 + + # Create a non-connectable and non-scannable extended advertising set for broadcast + uart:~$ bt adv-create nconn-nscan ext-adv + uart:~$ bt per-adv-param + + # Perform the handover and update the advertising data to contain the broadcast ID + uart:~$ cap_handover unicast_to_broadcast + uart:~$ bt adv-data dev-name discov + uart:~$ bt per-adv-data + + # Enable periodic advertising (extended advertising is enabled as part of handover) + uart:~$ bt per-adv on From dcde9d43f580283d716dcefd86136c3800023bd5 Mon Sep 17 00:00:00 2001 From: Emil Gydesen Date: Mon, 8 Sep 2025 09:57:45 +0200 Subject: [PATCH 3/7] Bluetooth: BAP: Shell: 0-init some global variables 0-init some non-static global variables to avoid issues with uninitialized values. Also modify the printing of the broadcast sources found slightly. Signed-off-by: Emil Gydesen --- subsys/bluetooth/audio/shell/bap.c | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/subsys/bluetooth/audio/shell/bap.c b/subsys/bluetooth/audio/shell/bap.c index e93a78fc4f961..31714f4e7edae 100644 --- a/subsys/bluetooth/audio/shell/bap.c +++ b/subsys/bluetooth/audio/shell/bap.c @@ -61,24 +61,24 @@ #if defined(CONFIG_BT_BAP_UNICAST) -struct shell_stream unicast_streams[CONFIG_BT_MAX_CONN * - MAX(UNICAST_SERVER_STREAM_COUNT, UNICAST_CLIENT_STREAM_COUNT)]; +struct shell_stream unicast_streams[CONFIG_BT_MAX_CONN * MAX(UNICAST_SERVER_STREAM_COUNT, + UNICAST_CLIENT_STREAM_COUNT)] = {0}; #if defined(CONFIG_BT_BAP_UNICAST_CLIENT) -struct unicast_group default_unicast_group; +struct unicast_group default_unicast_group = {0}; static struct bt_bap_unicast_client_cb unicast_client_cbs; #if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 -struct bt_bap_ep *snks[CONFIG_BT_MAX_CONN][CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT]; +struct bt_bap_ep *snks[CONFIG_BT_MAX_CONN][CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT] = {0}; #endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 */ #if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 -struct bt_bap_ep *srcs[CONFIG_BT_MAX_CONN][CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT]; +struct bt_bap_ep *srcs[CONFIG_BT_MAX_CONN][CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT] = {0}; #endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 */ #endif /* CONFIG_BT_BAP_UNICAST_CLIENT */ #endif /* CONFIG_BT_BAP_UNICAST */ #if defined(CONFIG_BT_BAP_BROADCAST_SOURCE) -struct shell_stream broadcast_source_streams[CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT]; -struct broadcast_source default_source; +struct shell_stream broadcast_source_streams[CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT] = {0}; +struct broadcast_source default_source = {0}; #endif /* CONFIG_BT_BAP_BROADCAST_SOURCE */ #if defined(CONFIG_BT_BAP_BROADCAST_SINK) static struct shell_stream broadcast_sink_streams[CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT]; @@ -2384,8 +2384,11 @@ static void broadcast_scan_recv(const struct bt_le_scan_recv_info *info, struct bt_addr_le_to_str(info->addr, addr_str, sizeof(addr_str)); - bt_shell_print("Found broadcaster with ID 0x%06X (%s) and addr %s and sid 0x%02X ", - sr_info.broadcast_id, sr_info.broadcast_name, addr_str, info->sid); + bt_shell_print("Found broadcaster with ID 0x%06X (%s) and addr %s and sid 0x%02X (scanning " + "for 0x%06X (%s))", + sr_info.broadcast_id, sr_info.broadcast_name, addr_str, info->sid, + auto_scan.broadcast_info.broadcast_id, + auto_scan.broadcast_info.broadcast_name); if ((auto_scan.broadcast_info.broadcast_id == BT_BAP_INVALID_BROADCAST_ID) && (strlen(auto_scan.broadcast_info.broadcast_name) == 0U)) { @@ -2399,11 +2402,13 @@ static void broadcast_scan_recv(const struct bt_le_scan_recv_info *info, struct is_substring(auto_scan.broadcast_info.broadcast_name, sr_info.broadcast_name)) { auto_scan.broadcast_info.broadcast_id = sr_info.broadcast_id; identified_broadcast = true; - - bt_shell_print("Found matched broadcast name '%s' with address %s", - sr_info.broadcast_name, addr_str); + } else { + /* no op */ + return; } + bt_shell_print("Found matched broadcast with address %s", addr_str); + if (identified_broadcast && (auto_scan.broadcast_sink != NULL) && (auto_scan.broadcast_sink->pa_sync == NULL)) { struct bt_le_per_adv_sync_param create_params = {0}; From 92ababfc93d0c11725fa50003a503a7586aae9cd Mon Sep 17 00:00:00 2001 From: Emil Gydesen Date: Tue, 16 Sep 2025 15:08:13 +0200 Subject: [PATCH 4/7] Bluetooth: BAP: Shell: Refactor receive states Refactor how receive states are handled in the BAP shell. The scan delegator's receive states are not available from the broadcast sink, and the broadcast sink makes sure to properly set the pa_sync object in the receive state struct. The receive states are now also using the BAP service data to identity the receive state when using PAST. The broadcast assistant will now cache data from a remote device, and makes it available to e.g. CAP. Additionally if there is a broadcast source, we cache the values neccesary to identity if a remote receive state represents our local broadcast source. Signed-off-by: Emil Gydesen --- subsys/bluetooth/audio/shell/audio.h | 50 +++- subsys/bluetooth/audio/shell/bap.c | 154 +++++++++--- .../audio/shell/bap_broadcast_assistant.c | 68 +++++- .../audio/shell/bap_scan_delegator.c | 224 ++++++++++++------ subsys/bluetooth/audio/shell/cap_initiator.c | 13 +- 5 files changed, 391 insertions(+), 118 deletions(-) diff --git a/subsys/bluetooth/audio/shell/audio.h b/subsys/bluetooth/audio/shell/audio.h index 33de5f0e3acf1..fa0861d41888d 100644 --- a/subsys/bluetooth/audio/shell/audio.h +++ b/subsys/bluetooth/audio/shell/audio.h @@ -5,7 +5,7 @@ */ /* - * Copyright (c) 2023 Nordic Semiconductor ASA + * Copyright (c) 2023-2025 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -32,8 +33,10 @@ #include #include #include +#include #include "common/bt_shell_private.h" +#include "host/shell/bt.h" #define SHELL_PRINT_INDENT_LEVEL_SIZE 2 #define MAX_CODEC_FRAMES_PER_SDU 4U @@ -176,7 +179,9 @@ struct broadcast_source { }; struct bt_audio_codec_cfg codec_cfg; struct bt_bap_qos_cfg qos; - uint32_t broadcast_id; + uint32_t broadcast_id; /* BT_BAP_INVALID_BROADCAST_ID when not in use */ + uint8_t addr_type; + uint8_t adv_sid; /* BT_GAP_SID_INVALID when not in use */ }; struct broadcast_sink { @@ -184,7 +189,6 @@ struct broadcast_sink { struct bt_le_per_adv_sync *pa_sync; uint8_t received_base[UINT8_MAX]; uint8_t base_size; - uint32_t broadcast_id; size_t stream_cnt; bool syncable; }; @@ -197,6 +201,32 @@ struct unicast_group { }; }; +struct broadcast_assistant_recv_state { + /* Number of receive state on the remote device */ + uint8_t recv_state_count; + +#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE) + /* Contains the src_id representing our local default_broadcast */ + uint8_t default_source_src_id; + uint8_t default_source_subgroup_count; + bool default_source_big_synced; +#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE */ +}; + +struct scan_delegator_sync_state { + uint8_t broadcast_code[BT_ISO_BROADCAST_CODE_SIZE]; + const struct bt_bap_scan_delegator_recv_state *recv_state; + struct bt_le_per_adv_sync *pa_sync; + struct bt_conn *conn; + struct k_work_delayable pa_timer; + uint32_t broadcast_id; + uint16_t pa_interval; + bool active; + bool pa_syncing; + bool past_avail; + uint8_t src_id; +}; + #define BAP_UNICAST_AC_MAX_CONN 2U #define BAP_UNICAST_AC_MAX_SNK (2U * BAP_UNICAST_AC_MAX_CONN) #define BAP_UNICAST_AC_MAX_SRC (2U * BAP_UNICAST_AC_MAX_CONN) @@ -239,6 +269,20 @@ int cap_ac_unicast(const struct shell *sh, const struct cap_unicast_ac_param *pa #endif /* CONFIG_BT_BAP_UNICAST_CLIENT */ #endif /* CONFIG_BT_BAP_UNICAST */ +#if defined(CONFIG_BT_BAP_BROADCAST_ASSISTANT) +extern struct broadcast_assistant_recv_state broadcast_assistant_recv_states[CONFIG_BT_MAX_CONN]; +#endif /* CONFIG_BT_BAP_BROADCAST_ASSISTANT */ +#if defined(CONFIG_BT_BAP_SCAN_DELEGATOR) +extern struct scan_delegator_sync_state + scan_delegator_sync_states[CONFIG_BT_BAP_SCAN_DELEGATOR_RECV_STATE_COUNT]; + +struct scan_delegator_sync_state *scan_delegator_sync_state_new(void); +struct scan_delegator_sync_state * +scan_delegator_sync_state_get_by_pa(struct bt_le_per_adv_sync *sync); +struct scan_delegator_sync_state * +scan_delegator_sync_state_get_by_values(uint32_t broadcast_id, uint8_t addr_type, uint8_t sid); +#endif /* CONFIG_BT_BAP_SCAN_DELEGATOR */ + static inline void print_qos(const struct bt_bap_qos_cfg *qos) { #if defined(CONFIG_BT_BAP_BROADCAST_SOURCE) || defined(CONFIG_BT_BAP_UNICAST) diff --git a/subsys/bluetooth/audio/shell/bap.c b/subsys/bluetooth/audio/shell/bap.c index 31714f4e7edae..cd3c940f45a2b 100644 --- a/subsys/bluetooth/audio/shell/bap.c +++ b/subsys/bluetooth/audio/shell/bap.c @@ -5,7 +5,7 @@ /* * Copyright (c) 2020 Intel Corporation - * Copyright (c) 2022-2023 Nordic Semiconductor ASA + * Copyright (c) 2022-2025 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ @@ -390,7 +390,6 @@ static bool encode_frame(struct shell_stream *sh_stream, uint8_t index, size_t f false; } - /* TODO: Move the following to a function in bap_usb.c*/ bap_usb_get_frame(sh_stream, chan_alloc, lc3_tx_buf); } else { /* Generate sine wave */ @@ -817,6 +816,20 @@ static int set_metadata(struct bt_audio_codec_cfg *codec_cfg, const char *meta_s } #if defined(CONFIG_BT_BAP_UNICAST_CLIENT) +static void disconnected_cb(struct bt_conn *conn, uint8_t reason) +{ +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 + (void)memset(snks[bt_conn_index(conn)], 0, sizeof(snks[0])); +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 */ +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 + (void)memset(srcs[bt_conn_index(conn)], 0, sizeof(srcs[0])); +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 */ +} + +BT_CONN_CB_DEFINE(conn_callbacks) = { + .disconnected = disconnected_cb, +}; + static uint8_t stream_dir(const struct bt_bap_stream *stream) { if (stream->conn) { @@ -2299,7 +2312,6 @@ struct bt_broadcast_info { static struct broadcast_sink_auto_scan { struct broadcast_sink *broadcast_sink; struct bt_broadcast_info broadcast_info; - struct bt_le_per_adv_sync **out_sync; } auto_scan = { .broadcast_info = { .broadcast_id = BT_BAP_INVALID_BROADCAST_ID, @@ -2363,6 +2375,49 @@ static bool scan_check_and_get_broadcast_values(struct bt_data *data, void *user } } +static void pa_sync_broadcast_sink(const struct bt_le_scan_recv_info *info) +{ + struct bt_le_per_adv_sync_param create_params = {0}; + int err; + + err = bt_le_scan_stop(); + if (err != 0) { + bt_shell_error("Could not stop scan: %d", err); + } + + bt_addr_le_copy(&create_params.addr, info->addr); + create_params.options = BT_LE_PER_ADV_SYNC_OPT_FILTER_DUPLICATE; + create_params.sid = info->sid; + create_params.skip = PA_SYNC_SKIP; + create_params.timeout = interval_to_sync_timeout(info->interval); + + bt_shell_print("Attempting to PA sync to the broadcaster"); + err = bt_le_per_adv_sync_create(&create_params, &per_adv_syncs[selected_per_adv_sync]); + if (err != 0) { + bt_shell_error("Could not create Broadcast PA sync: %d", err); + } else { + struct bt_le_per_adv_sync *pa_sync = per_adv_syncs[selected_per_adv_sync]; + struct scan_delegator_sync_state *sync_state = NULL; + + default_broadcast_sink.pa_sync = pa_sync; + + sync_state = scan_delegator_sync_state_get_by_values( + auto_scan.broadcast_info.broadcast_id, info->addr->type, info->sid); + if (sync_state == NULL) { + sync_state = scan_delegator_sync_state_new(); + + if (sync_state == NULL) { + bt_shell_error("Could not get new sync state"); + + return; + } + } + + sync_state->pa_sync = pa_sync; + sync_state->broadcast_id = auto_scan.broadcast_info.broadcast_id; + } +} + static void broadcast_scan_recv(const struct bt_le_scan_recv_info *info, struct net_buf_simple *ad) { struct bt_broadcast_info sr_info = {0}; @@ -2407,31 +2462,11 @@ static void broadcast_scan_recv(const struct bt_le_scan_recv_info *info, struct return; } - bt_shell_print("Found matched broadcast with address %s", addr_str); - - if (identified_broadcast && (auto_scan.broadcast_sink != NULL) && - (auto_scan.broadcast_sink->pa_sync == NULL)) { - struct bt_le_per_adv_sync_param create_params = {0}; - int err; + bt_shell_print("Found matched broadcast with address %s%s", addr_str, + info->interval > 0U ? "" : " but is not syncable"); - err = bt_le_scan_stop(); - if (err != 0) { - bt_shell_error("Could not stop scan: %d", err); - } - - bt_addr_le_copy(&create_params.addr, info->addr); - create_params.options = BT_LE_PER_ADV_SYNC_OPT_FILTER_DUPLICATE; - create_params.sid = info->sid; - create_params.skip = PA_SYNC_SKIP; - create_params.timeout = interval_to_sync_timeout(info->interval); - - bt_shell_print("Attempting to PA sync to the broadcaster"); - err = bt_le_per_adv_sync_create(&create_params, auto_scan.out_sync); - if (err != 0) { - bt_shell_error("Could not create Broadcast PA sync: %d", err); - } else { - auto_scan.broadcast_sink->pa_sync = *auto_scan.out_sync; - } + if (info->interval > 0U && identified_broadcast && auto_scan.broadcast_sink != NULL) { + pa_sync_broadcast_sink(info); } } @@ -2465,8 +2500,7 @@ static void syncable(struct bt_bap_broadcast_sink *sink, const struct bt_iso_big static void bap_pa_sync_synced_cb(struct bt_le_per_adv_sync *sync, struct bt_le_per_adv_sync_synced_info *info) { - if (auto_scan.broadcast_sink != NULL && auto_scan.out_sync != NULL && - sync == *auto_scan.out_sync) { + if (auto_scan.broadcast_sink != NULL && auto_scan.broadcast_sink->pa_sync == sync) { bt_shell_print("PA synced to broadcast with broadcast ID 0x%06x", auto_scan.broadcast_info.broadcast_id); @@ -3075,7 +3109,6 @@ static void clear_stream_data(struct shell_stream *sh_stream) /* All streams in the broadcast sink has been terminated */ memset(&default_broadcast_sink.received_base, 0, sizeof(default_broadcast_sink.received_base)); - default_broadcast_sink.broadcast_id = 0; default_broadcast_sink.syncable = false; } } @@ -3326,6 +3359,7 @@ static int cmd_start_broadcast(const struct shell *sh, size_t argc, char *argv[]) { struct bt_le_ext_adv *adv = adv_sets[selected_adv]; + struct bt_le_ext_adv_info adv_info; int err; if (adv == NULL) { @@ -3333,6 +3367,12 @@ static int cmd_start_broadcast(const struct shell *sh, size_t argc, return -ENOEXEC; } + err = bt_le_ext_adv_get_info(adv, &adv_info); + if (err != 0) { + shell_error(sh, "Failed to get adv info: %d\n", err); + return -ENOEXEC; + } + if (default_source.bap_source == NULL || default_source.is_cap) { shell_info(sh, "Broadcast source not created"); return -ENOEXEC; @@ -3344,6 +3384,9 @@ static int cmd_start_broadcast(const struct shell *sh, size_t argc, return err; } + default_source.addr_type = adv_info.addr->type; + default_source.adv_sid = adv_info.sid; + return 0; } @@ -3362,6 +3405,8 @@ static int cmd_stop_broadcast(const struct shell *sh, size_t argc, char *argv[]) return err; } + default_source.adv_sid = BT_GAP_SID_INVALID; + return 0; } @@ -3426,7 +3471,6 @@ static int cmd_create_broadcast_sink(const struct shell *sh, size_t argc, char * auto_scan.broadcast_sink = &default_broadcast_sink; auto_scan.broadcast_info.broadcast_id = broadcast_id; - auto_scan.out_sync = &per_adv_syncs[selected_per_adv_sync]; } else { shell_print(sh, "Creating broadcast sink with broadcast ID 0x%06X", (uint32_t)broadcast_id); @@ -3438,6 +3482,52 @@ static int cmd_create_broadcast_sink(const struct shell *sh, size_t argc, char * shell_error(sh, "Failed to create broadcast sink: %d", err); return -ENOEXEC; + } else { + struct scan_delegator_sync_state *sync_state = NULL; + + default_broadcast_sink.pa_sync = per_adv_sync; + + /* Lookup sync_state by PA sync or by values */ + sync_state = scan_delegator_sync_state_get_by_pa(per_adv_sync); + if (sync_state == NULL) { + struct bt_le_per_adv_sync_info sync_info; + + err = bt_le_per_adv_sync_get_info(per_adv_sync, &sync_info); + if (err != 0) { + bt_shell_error("Failed to get sync info: %d", err); + err = bt_bap_broadcast_sink_delete( + default_broadcast_sink.bap_sink); + if (err != 0) { + bt_shell_error( + "Failed to delete broadcast sink: %d", err); + } + + return -ENOEXEC; + } + + sync_state = scan_delegator_sync_state_get_by_values( + auto_scan.broadcast_info.broadcast_id, sync_info.addr.type, + sync_info.sid); + } + + if (sync_state == NULL) { + sync_state = scan_delegator_sync_state_new(); + + if (sync_state == NULL) { + bt_shell_error("Could not get new sync state"); + err = bt_bap_broadcast_sink_delete( + default_broadcast_sink.bap_sink); + if (err != 0) { + bt_shell_error( + "Failed to delete broadcast sink: %d", err); + } + + return -ENOEXEC; + } + } + + sync_state->pa_sync = per_adv_sync; + sync_state->broadcast_id = (uint32_t)broadcast_id; } } @@ -3479,7 +3569,6 @@ static int cmd_create_sink_by_name(const struct shell *sh, size_t argc, char *ar auto_scan.broadcast_info.broadcast_id = BT_BAP_INVALID_BROADCAST_ID; auto_scan.broadcast_sink = &default_broadcast_sink; - auto_scan.out_sync = &per_adv_syncs[selected_per_adv_sync]; return 0; } @@ -3860,6 +3949,7 @@ static int cmd_init(const struct shell *sh, size_t argc, char *argv[]) } default_source.broadcast_id = BT_BAP_INVALID_BROADCAST_ID; + default_source.adv_sid = BT_GAP_SID_INVALID; #endif /* CONFIG_BT_BAP_BROADCAST_SOURCE */ #if defined(CONFIG_LIBLC3) diff --git a/subsys/bluetooth/audio/shell/bap_broadcast_assistant.c b/subsys/bluetooth/audio/shell/bap_broadcast_assistant.c index 8734805f0bcf0..3f3a849e5ea0d 100644 --- a/subsys/bluetooth/audio/shell/bap_broadcast_assistant.c +++ b/subsys/bluetooth/audio/shell/bap_broadcast_assistant.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,18 @@ struct bt_scan_recv_info { char broadcast_name[BT_AUDIO_BROADCAST_NAME_LEN_MAX + 1]; }; +struct broadcast_assistant_recv_state broadcast_assistant_recv_states[CONFIG_BT_MAX_CONN]; + +static void disconnected_cb(struct bt_conn *conn, uint8_t reason) +{ + (void)memset(&broadcast_assistant_recv_states[bt_conn_index(conn)], 0, + sizeof(broadcast_assistant_recv_states[0])); +} + +BT_CONN_CB_DEFINE(conn_callbacks) = { + .disconnected = disconnected_cb, +}; + 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); @@ -98,6 +111,8 @@ static void bap_broadcast_assistant_discover_cb(struct bt_conn *conn, int err, bt_shell_error("BASS discover failed (%d)", err); } else { bt_shell_print("BASS discover done with %u recv states", recv_state_count); + broadcast_assistant_recv_states[bt_conn_index(conn)].recv_state_count = + recv_state_count; } } @@ -166,6 +181,7 @@ static void bap_broadcast_assistant_recv_state_cb( struct bt_le_ext_adv *ext_adv = NULL; /* Lookup matching PA sync */ + /* TODO: Need to consider SID and Broadcast ID as well */ for (size_t i = 0U; i < ARRAY_SIZE(per_adv_syncs); i++) { if (per_adv_syncs[i] != NULL && bt_addr_le_eq(&per_adv_syncs[i]->addr, &state->addr)) { @@ -176,12 +192,33 @@ static void bap_broadcast_assistant_recv_state_cb( } if (per_adv_sync && IS_ENABLED(CONFIG_BT_PER_ADV_SYNC_TRANSFER_SENDER)) { - bt_shell_print("Sending PAST"); - err = bt_le_per_adv_sync_transfer(per_adv_sync, - conn, - BT_UUID_BASS_VAL); + struct bt_le_per_adv_sync_info sync_info; + const bool adva_matches_ea = false; /* don't know */ + bool adva_matches_src_addr; + uint16_t service_data = 0U; + + err = bt_le_per_adv_sync_get_info(per_adv_sync, &sync_info); + if (err != 0) { + bt_shell_error("Failed to get sync info: %d", err); + + return; + } + + adva_matches_src_addr = bt_addr_le_eq(&sync_info.addr, &state->addr); + if (!adva_matches_ea) { + service_data |= BIT(0); + } + if (!adva_matches_src_addr) { + service_data |= BIT(1); + } + + service_data |= ((uint16_t)state->src_id << 8); + + bt_shell_print("Sending PAST"); + + err = bt_le_per_adv_sync_transfer(per_adv_sync, conn, service_data); if (err != 0) { bt_shell_error("Could not transfer periodic adv sync: %d", err); } @@ -225,6 +262,29 @@ static void bap_broadcast_assistant_recv_state_cb( bt_shell_error("Could not send PA to Scan Delegator"); } } + +#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE) + /* The combination of broadcast ID, address type and SID is what makes a receive state + * unique - Use that to compare when storing the src_id related to our broadcast + */ + if (err == 0 && state->broadcast_id == default_source.broadcast_id && + state->addr.type == default_source.addr_type && + state->adv_sid == default_source.adv_sid) { + struct broadcast_assistant_recv_state *recv_state = + &broadcast_assistant_recv_states[bt_conn_index(conn)]; + + recv_state->default_source_src_id = state->src_id; + recv_state->default_source_subgroup_count = state->num_subgroups; + + recv_state->default_source_big_synced = false; + for (uint8_t i = 0U; i < state->num_subgroups; i++) { + if (state->subgroups[i].bis_sync != 0) { + recv_state->default_source_big_synced = true; + break; + } + } + } +#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE */ } static void bap_broadcast_assistant_recv_state_removed_cb(struct bt_conn *conn, uint8_t src_id) diff --git a/subsys/bluetooth/audio/shell/bap_scan_delegator.c b/subsys/bluetooth/audio/shell/bap_scan_delegator.c index e88b153c332fb..e63be874a169c 100644 --- a/subsys/bluetooth/audio/shell/bap_scan_delegator.c +++ b/subsys/bluetooth/audio/shell/bap_scan_delegator.c @@ -32,23 +32,15 @@ #include #include