Skip to content

Commit 2ab528f

Browse files
committed
Bluetooth: CAP: Shell: Add handover shell commands
Add shell commands for the CAP handover procedures. Signed-off-by: Emil Gydesen <[email protected]>
1 parent 445c8db commit 2ab528f

File tree

1 file changed

+262
-0
lines changed

1 file changed

+262
-0
lines changed
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
/**
2+
* @file
3+
* @brief Shell APIs for Bluetooth CAP handover
4+
*
5+
* Copyright (c) 2025 Nordic Semiconductor ASA
6+
*
7+
* SPDX-License-Identifier: Apache-2.0
8+
*/
9+
#include <errno.h>
10+
#include <stdbool.h>
11+
#include <stddef.h>
12+
#include <stdlib.h>
13+
#include <string.h>
14+
#include <sys/types.h>
15+
16+
#include <zephyr/autoconf.h>
17+
#include <zephyr/bluetooth/audio/audio.h>
18+
#include <zephyr/bluetooth/audio/bap.h>
19+
#include <zephyr/bluetooth/audio/csip.h>
20+
#include <zephyr/bluetooth/crypto.h>
21+
#include <zephyr/bluetooth/gap.h>
22+
#include <zephyr/bluetooth/iso.h>
23+
#include <zephyr/bluetooth/uuid.h>
24+
#include <zephyr/net_buf.h>
25+
#include <zephyr/shell/shell_string_conv.h>
26+
#include <zephyr/sys/__assert.h>
27+
#include <zephyr/sys/byteorder.h>
28+
#include <zephyr/sys/printk.h>
29+
#include <zephyr/sys/util.h>
30+
#include <zephyr/types.h>
31+
#include <zephyr/shell/shell.h>
32+
#include <zephyr/bluetooth/conn.h>
33+
#include <zephyr/bluetooth/gatt.h>
34+
#include <zephyr/bluetooth/bluetooth.h>
35+
#include <zephyr/bluetooth/audio/cap.h>
36+
37+
#include "common/bt_shell_private.h"
38+
#include "host/shell/bt.h"
39+
#include "audio.h"
40+
41+
static void unicast_to_broadcast_complete_cb(int err, struct bt_conn *conn,
42+
struct bt_cap_unicast_group *unicast_group,
43+
struct bt_cap_broadcast_source *broadcast_source)
44+
{
45+
if (err == -ECANCELED) {
46+
bt_shell_print("Unicast to broadcast handover was cancelled for conn %p", conn);
47+
} else if (err != 0) {
48+
bt_shell_error("Unicast to broadcast handover failed for conn %p (%d)", conn, err);
49+
} else {
50+
bt_shell_print(
51+
"Unicast to broadcast handover completed with new broadcast source %p",
52+
(void *)broadcast_source);
53+
}
54+
}
55+
56+
static void broadcast_to_unicast_complete_cb(int err, struct bt_conn *conn,
57+
struct bt_cap_broadcast_source *broadcast_source,
58+
struct bt_cap_unicast_group *unicast_group)
59+
{
60+
if (err == -ECANCELED) {
61+
bt_shell_print("Broadcast to unicast handover was cancelled for conn %p", conn);
62+
} else if (err != 0) {
63+
bt_shell_error("Broadcast to unicast handover failed for conn %p (%d)", conn, err);
64+
} else {
65+
bt_shell_print("Broadcast to unicast handover completed with new group %p",
66+
(void *)unicast_group);
67+
}
68+
}
69+
70+
static struct bt_cap_handover_cb cbs = {
71+
.unicast_to_broadcast_complete = unicast_to_broadcast_complete_cb,
72+
.broadcast_to_unicast_complete = broadcast_to_unicast_complete_cb,
73+
};
74+
75+
struct cap_unicast_group_stream_lookup {
76+
struct bt_cap_stream *active_sink_streams[CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT];
77+
size_t active_sink_streams_cnt;
78+
};
79+
80+
static bool unicast_group_foreach_stream_cb(struct bt_cap_stream *cap_stream, void *user_data)
81+
{
82+
struct cap_unicast_group_stream_lookup *data = user_data;
83+
const struct bt_bap_stream *bap_stream = &cap_stream->bap_stream;
84+
85+
__ASSERT_NO_MSG(data->active_sink_streams_cnt < ARRAY_SIZE(data->active_sink_streams));
86+
87+
if (bap_stream->ep != NULL) {
88+
struct bt_bap_ep_info ep_info;
89+
int err;
90+
91+
err = bt_bap_ep_get_info(bap_stream->ep, &ep_info);
92+
__ASSERT_NO_MSG(err == 0);
93+
94+
/* Only consider sink streams for handover to broadcast */
95+
if (ep_info.state == BT_BAP_EP_STATE_STREAMING &&
96+
ep_info.dir == BT_AUDIO_DIR_SINK) {
97+
data->active_sink_streams[data->active_sink_streams_cnt++] = cap_stream;
98+
}
99+
}
100+
101+
return false;
102+
}
103+
104+
static int cmd_cap_handover_unicast_to_broadcast(const struct shell *sh, size_t argc, char *argv[])
105+
{
106+
static struct bt_cap_initiator_broadcast_stream_param
107+
stream_params[CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT];
108+
static struct bt_cap_initiator_broadcast_subgroup_param subgroup_param = {0};
109+
struct bt_cap_initiator_broadcast_create_param broadcast_create_param;
110+
/* Struct containing the converted unicast group configuration */
111+
struct bt_cap_handover_unicast_to_broadcast_param param = {0};
112+
struct cap_unicast_group_stream_lookup lookup_data = {0};
113+
struct bt_le_ext_adv *adv = adv_sets[selected_adv];
114+
const struct named_lc3_preset *named_preset;
115+
int err;
116+
117+
if (adv == NULL) {
118+
shell_error(sh, "Extended advertising set is NULL");
119+
120+
return -ENOEXEC;
121+
}
122+
123+
if (default_unicast_group->cap_group == NULL) {
124+
shell_error(sh, "CAP unicast group not created");
125+
126+
return -ENOEXEC;
127+
}
128+
129+
if (!default_unicast_group->is_cap) {
130+
shell_error(sh, "Unicast group is not CAP");
131+
132+
return -ENOEXEC;
133+
}
134+
135+
if (default_source.cap_source != NULL) {
136+
shell_error(sh, "CAP Broadcast source already created");
137+
138+
return -ENOEXEC;
139+
}
140+
141+
for (size_t i = 1U; i < argc; i++) {
142+
char *arg = argv[i];
143+
144+
if (strcmp(arg, "enc") == 0) {
145+
if (argc > i) {
146+
size_t bcode_len;
147+
148+
i++;
149+
arg = argv[i];
150+
151+
bcode_len = hex2bin(arg, strlen(arg),
152+
broadcast_create_param.broadcast_code,
153+
sizeof(broadcast_create_param.broadcast_code));
154+
155+
if (bcode_len != sizeof(broadcast_create_param.broadcast_code)) {
156+
shell_error(sh, "Invalid Broadcast Code Length: %zu",
157+
bcode_len);
158+
159+
return -ENOEXEC;
160+
}
161+
162+
broadcast_create_param.encryption = true;
163+
} else {
164+
shell_help(sh);
165+
166+
return SHELL_CMD_HELP_PRINTED;
167+
}
168+
} else if (strcmp(arg, "preset") == 0) {
169+
if (argc > i) {
170+
171+
i++;
172+
arg = argv[i];
173+
174+
named_preset =
175+
bap_get_named_preset(false, BT_AUDIO_DIR_SOURCE, arg);
176+
if (named_preset == NULL) {
177+
shell_error(sh, "Unable to parse named_preset %s", arg);
178+
179+
return -ENOEXEC;
180+
}
181+
} else {
182+
shell_help(sh);
183+
184+
return SHELL_CMD_HELP_PRINTED;
185+
}
186+
}
187+
}
188+
189+
err = bt_cap_unicast_group_foreach_stream(default_unicast_group->cap_group,
190+
unicast_group_foreach_stream_cb, &lookup_data);
191+
__ASSERT_NO_MSG(err == 0);
192+
193+
if (lookup_data.active_sink_streams_cnt == 0U) {
194+
shell_error(sh, "No active sink streams in group, cannot handover");
195+
196+
return -ENOEXEC;
197+
}
198+
199+
copy_broadcast_source_preset(&default_source, named_preset);
200+
201+
subgroup_param.stream_count = lookup_data.active_sink_streams_cnt;
202+
subgroup_param.stream_params = stream_params;
203+
subgroup_param.codec_cfg = &default_source.codec_cfg;
204+
for (size_t i = 0U; i < lookup_data.active_sink_streams_cnt; i++) {
205+
subgroup_param.stream_params[i].stream = lookup_data.active_sink_streams[i];
206+
stream_params[i].data_len = 0U;
207+
stream_params[i].data = NULL;
208+
}
209+
210+
broadcast_create_param.subgroup_count = 1U;
211+
broadcast_create_param.subgroup_params = &subgroup_param;
212+
broadcast_create_param.qos = &default_source.qos;
213+
broadcast_create_param.packing = BT_ISO_PACKING_SEQUENTIAL;
214+
broadcast_create_param.encryption = false;
215+
216+
param.type = BT_CAP_SET_TYPE_AD_HOC;
217+
param.unicast_group = default_unicast_group->cap_group;
218+
param.broadcast_create_param = &broadcast_create_param;
219+
param.ext_adv = adv;
220+
param.sid = adv_sid;
221+
param.pa_interval = BT_BAP_PA_INTERVAL_UNKNOWN;
222+
param.broadcast_id = broadcast_id;
223+
224+
err = bt_cap_handover_unicast_to_broadcast(&param);
225+
if (err != 0) {
226+
shell_error("Failed to handover unicast audio to broadcast: %d", err);
227+
228+
return -ENOEXEC;
229+
}
230+
231+
return 0;
232+
}
233+
234+
static int cmd_cap_handover_broadcast_to_unicast(const struct shell *sh, size_t argc, char *argv[])
235+
{
236+
return 0;
237+
}
238+
239+
static int cmd_cap_handover(const struct shell *sh, size_t argc, char **argv)
240+
{
241+
if (argc > 1) {
242+
shell_error(sh, "%s unknown parameter: %s", argv[0], argv[1]);
243+
} else {
244+
shell_error(sh, "%s Missing subcommand", argv[0]);
245+
}
246+
247+
return -ENOEXEC;
248+
}
249+
250+
SHELL_STATIC_SUBCMD_SET_CREATE(
251+
cap_initiator_cmds,
252+
SHELL_CMD_ARG(unicast_to_broadcast, NULL,
253+
"Handover current unicast group to broadcast (unicast group will be deleted)",
254+
cmd_cap_handover_unicast_to_broadcast, 1, 0),
255+
SHELL_CMD_ARG(
256+
broadcast_to_unicast, NULL,
257+
"Handover current broadcast source to unicast (broadcast source will be deleted)",
258+
cmd_cap_handover_broadcast_to_unicast, 1, 0),
259+
SHELL_SUBCMD_SET_END);
260+
261+
SHELL_CMD_ARG_REGISTER(cap_initiator, &cap_initiator_cmds, "Bluetooth CAP initiator shell commands",
262+
cmd_cap_handover, 1, 1);

0 commit comments

Comments
 (0)