1 /**
2  * @file
3  * @brief Shell APIs for Bluetooth CAP commander
4  *
5  * Copyright (c) 2023 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 <stdint.h>
13 
14 #include <zephyr/autoconf.h>
15 #include <zephyr/bluetooth/audio/csip.h>
16 #include <zephyr/bluetooth/conn.h>
17 #include <zephyr/bluetooth/audio/cap.h>
18 #include <zephyr/bluetooth/audio/vocs.h>
19 #include <zephyr/shell/shell.h>
20 #include <zephyr/shell/shell_string_conv.h>
21 #include <zephyr/sys/util.h>
22 #include <zephyr/types.h>
23 
24 #include "host/shell/bt.h"
25 #include "audio.h"
26 
cap_discover_cb(struct bt_conn * conn,int err,const struct bt_csip_set_coordinator_set_member * member,const struct bt_csip_set_coordinator_csis_inst * csis_inst)27 static void cap_discover_cb(struct bt_conn *conn, int err,
28 			    const struct bt_csip_set_coordinator_set_member *member,
29 			    const struct bt_csip_set_coordinator_csis_inst *csis_inst)
30 {
31 	if (err != 0) {
32 		shell_error(ctx_shell, "discover failed (%d)", err);
33 		return;
34 	}
35 
36 	shell_print(ctx_shell, "discovery completed%s", csis_inst == NULL ? "" : " with CSIS");
37 }
38 
39 #if defined(CONFIG_BT_VCP_VOL_CTLR)
cap_volume_changed_cb(struct bt_conn * conn,int err)40 static void cap_volume_changed_cb(struct bt_conn *conn, int err)
41 {
42 	if (err != 0) {
43 		shell_error(ctx_shell, "Volume change failed (%d)", err);
44 		return;
45 	}
46 
47 	shell_print(ctx_shell, "Volume change completed");
48 }
49 
cap_volume_mute_changed_cb(struct bt_conn * conn,int err)50 static void cap_volume_mute_changed_cb(struct bt_conn *conn, int err)
51 {
52 	if (err != 0) {
53 		shell_error(ctx_shell, "Volume mute change failed (%d)", err);
54 		return;
55 	}
56 
57 	shell_print(ctx_shell, "Volume mute change completed");
58 }
59 #if defined(CONFIG_BT_VCP_VOL_CTLR_VOCS)
cap_volume_offset_changed_cb(struct bt_conn * conn,int err)60 static void cap_volume_offset_changed_cb(struct bt_conn *conn, int err)
61 {
62 	if (err != 0) {
63 		shell_error(ctx_shell, "Volume offset change failed (%d)", err);
64 		return;
65 	}
66 
67 	shell_print(ctx_shell, "Volume offset change completed");
68 }
69 #endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS */
70 #endif /* CONFIG_BT_VCP_VOL_CTLR */
71 
72 #if defined(CONFIG_BT_MICP_MIC_CTLR)
cap_microphone_mute_changed_cb(struct bt_conn * conn,int err)73 static void cap_microphone_mute_changed_cb(struct bt_conn *conn, int err)
74 {
75 	if (err != 0) {
76 		shell_error(ctx_shell, "Microphone mute change failed (%d)", err);
77 		return;
78 	}
79 
80 	shell_print(ctx_shell, "Microphone mute change completed");
81 }
82 
83 #if defined(CONFIG_BT_MICP_MIC_CTLR_AICS)
cap_microphone_gain_changed_cb(struct bt_conn * conn,int err)84 static void cap_microphone_gain_changed_cb(struct bt_conn *conn, int err)
85 {
86 	if (err != 0) {
87 		shell_error(ctx_shell, "Microphone gain change failed (%d)", err);
88 		return;
89 	}
90 
91 	shell_print(ctx_shell, "Microphone gain change completed");
92 }
93 #endif /* CONFIG_BT_MICP_MIC_CTLR_AICS */
94 #endif /* CONFIG_BT_MICP_MIC_CTLR */
95 
96 #if defined(CONFIG_BT_BAP_BROADCAST_ASSISTANT)
cap_broadcast_reception_start_cb(struct bt_conn * conn,int err)97 static void cap_broadcast_reception_start_cb(struct bt_conn *conn, int err)
98 {
99 	if (err != 0) {
100 		shell_error(ctx_shell, "Broadcast reception start failed (%d)", err);
101 		return;
102 	}
103 
104 	shell_print(ctx_shell, "Broadcast reception start completed");
105 }
106 
cap_broadcast_reception_stop_cb(struct bt_conn * conn,int err)107 static void cap_broadcast_reception_stop_cb(struct bt_conn *conn, int err)
108 {
109 	if (err != 0) {
110 		shell_error(ctx_shell, "Broadcast reception stop failed (%d) for conn %p", err,
111 			    (void *)conn);
112 		return;
113 	}
114 
115 	shell_print(ctx_shell, "Broadcast reception stop completed");
116 }
117 
cap_distribute_broadcast_code_cb(struct bt_conn * conn,int err)118 static void cap_distribute_broadcast_code_cb(struct bt_conn *conn, int err)
119 {
120 	if (err != 0) {
121 		shell_error(ctx_shell, "Distribute broadcast code failed (%d) for conn %p", err,
122 			    (void *)conn);
123 		return;
124 	}
125 
126 	shell_print(ctx_shell, "Distribute broadcast code completed");
127 }
128 #endif
129 
130 static struct bt_cap_commander_cb cbs = {
131 	.discovery_complete = cap_discover_cb,
132 #if defined(CONFIG_BT_VCP_VOL_CTLR)
133 	.volume_changed = cap_volume_changed_cb,
134 	.volume_mute_changed = cap_volume_mute_changed_cb,
135 #if defined(CONFIG_BT_VCP_VOL_CTLR_VOCS)
136 	.volume_offset_changed = cap_volume_offset_changed_cb,
137 #endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS */
138 #endif /* CONFIG_BT_VCP_VOL_CTLR */
139 #if defined(CONFIG_BT_MICP_MIC_CTLR)
140 	.microphone_mute_changed = cap_microphone_mute_changed_cb,
141 #if defined(CONFIG_BT_MICP_MIC_CTLR_AICS)
142 	.microphone_gain_changed = cap_microphone_gain_changed_cb,
143 #endif /* CONFIG_BT_MICP_MIC_CTLR_AICS */
144 #endif /* CONFIG_BT_MICP_MIC_CTLR */
145 #if defined(CONFIG_BT_BAP_BROADCAST_ASSISTANT)
146 	.broadcast_reception_start = cap_broadcast_reception_start_cb,
147 	.broadcast_reception_stop = cap_broadcast_reception_stop_cb,
148 	.distribute_broadcast_code = cap_distribute_broadcast_code_cb,
149 #endif /* CONFIG_BT_BAP_BROADCAST_ASSISTANT */
150 };
151 
cmd_cap_commander_cancel(const struct shell * sh,size_t argc,char * argv[])152 static int cmd_cap_commander_cancel(const struct shell *sh, size_t argc, char *argv[])
153 {
154 	int err;
155 
156 	err = bt_cap_commander_cancel();
157 	if (err != 0) {
158 		shell_print(sh, "Failed to cancel CAP commander procedure: %d", err);
159 		return -ENOEXEC;
160 	}
161 
162 	return 0;
163 }
164 
cmd_cap_commander_discover(const struct shell * sh,size_t argc,char * argv[])165 static int cmd_cap_commander_discover(const struct shell *sh, size_t argc, char *argv[])
166 {
167 	static bool cbs_registered;
168 	int err;
169 
170 	if (default_conn == NULL) {
171 		shell_error(sh, "Not connected");
172 		return -ENOEXEC;
173 	}
174 
175 	if (ctx_shell == NULL) {
176 		ctx_shell = sh;
177 	}
178 
179 	if (!cbs_registered) {
180 		bt_cap_commander_register_cb(&cbs);
181 		cbs_registered = true;
182 	}
183 
184 	err = bt_cap_commander_discover(default_conn);
185 	if (err != 0) {
186 		shell_error(sh, "Fail: %d", err);
187 	}
188 
189 	return err;
190 }
191 
192 #if defined(CONFIG_BT_VCP_VOL_CTLR) || defined(CONFIG_BT_MICP_MIC_CTLR_AICS)
populate_connected_conns(struct bt_conn * conn,void * data)193 static void populate_connected_conns(struct bt_conn *conn, void *data)
194 {
195 	struct bt_conn **connected_conns = (struct bt_conn **)data;
196 
197 	for (int i = 0; i < CONFIG_BT_MAX_CONN; i++) {
198 		if (connected_conns[i] == NULL) {
199 			connected_conns[i] = conn;
200 			return;
201 		}
202 	}
203 }
204 #endif /* CONFIG_BT_VCP_VOL_CTLR || CONFIG_BT_MICP_MIC_CTLR_AICS */
205 
206 #if defined(CONFIG_BT_VCP_VOL_CTLR)
cmd_cap_commander_change_volume(const struct shell * sh,size_t argc,char * argv[])207 static int cmd_cap_commander_change_volume(const struct shell *sh, size_t argc, char *argv[])
208 {
209 	struct bt_conn *connected_conns[CONFIG_BT_MAX_CONN] = {0};
210 	union bt_cap_set_member members[CONFIG_BT_MAX_CONN] = {0};
211 	struct bt_cap_commander_change_volume_param param = {
212 		.members = members,
213 	};
214 	unsigned long volume;
215 	int err = 0;
216 
217 	if (default_conn == NULL) {
218 		shell_error(sh, "Not connected");
219 		return -ENOEXEC;
220 	}
221 
222 	volume = shell_strtoul(argv[1], 10, &err);
223 	if (err != 0) {
224 		shell_error(sh, "Failed to parse volume from %s", argv[1]);
225 
226 		return -ENOEXEC;
227 	}
228 
229 	if (volume > UINT8_MAX) {
230 		shell_error(sh, "Invalid volume %lu", volume);
231 
232 		return -ENOEXEC;
233 	}
234 	param.volume = (uint8_t)volume;
235 
236 	param.type = BT_CAP_SET_TYPE_AD_HOC;
237 	/* TODO: Add support for coordinated sets */
238 
239 	/* Populate the array of connected connections */
240 	bt_conn_foreach(BT_CONN_TYPE_LE, populate_connected_conns, (void *)connected_conns);
241 
242 	param.count = 0U;
243 	param.members = members;
244 	for (size_t i = 0; i < ARRAY_SIZE(connected_conns); i++) {
245 		struct bt_conn *conn = connected_conns[i];
246 
247 		if (conn == NULL) {
248 			break;
249 		}
250 
251 		param.members[i].member = conn;
252 		param.count++;
253 	}
254 
255 	shell_print(sh, "Setting volume to %u on %zu connections", param.volume, param.count);
256 
257 	err = bt_cap_commander_change_volume(&param);
258 	if (err != 0) {
259 		shell_print(sh, "Failed to change volume: %d", err);
260 
261 		return -ENOEXEC;
262 	}
263 
264 	return 0;
265 }
266 
cmd_cap_commander_change_volume_mute(const struct shell * sh,size_t argc,char * argv[])267 static int cmd_cap_commander_change_volume_mute(const struct shell *sh, size_t argc, char *argv[])
268 {
269 	struct bt_conn *connected_conns[CONFIG_BT_MAX_CONN] = {0};
270 	union bt_cap_set_member members[CONFIG_BT_MAX_CONN] = {0};
271 	struct bt_cap_commander_change_volume_mute_state_param param = {
272 		.members = members,
273 		.type = BT_CAP_SET_TYPE_AD_HOC, /* TODO: Add support for coordinated sets */
274 	};
275 	int err = 0;
276 
277 	if (default_conn == NULL) {
278 		shell_error(sh, "Not connected");
279 		return -ENOEXEC;
280 	}
281 
282 	param.mute = shell_strtobool(argv[1], 10, &err);
283 	if (err != 0) {
284 		shell_error(sh, "Failed to parse volume mute from %s", argv[1]);
285 
286 		return -ENOEXEC;
287 	}
288 
289 	/* Populate the array of connected connections */
290 	bt_conn_foreach(BT_CONN_TYPE_LE, populate_connected_conns, (void *)connected_conns);
291 
292 	param.count = 0U;
293 	param.members = members;
294 	for (size_t i = 0; i < ARRAY_SIZE(connected_conns); i++) {
295 		struct bt_conn *conn = connected_conns[i];
296 
297 		if (conn == NULL) {
298 			break;
299 		}
300 
301 		param.members[i].member = conn;
302 		param.count++;
303 	}
304 
305 	shell_print(sh, "Setting volume mute to %d on %zu connections", param.mute, param.count);
306 
307 	err = bt_cap_commander_change_volume_mute_state(&param);
308 	if (err != 0) {
309 		shell_print(sh, "Failed to change volume mute: %d", err);
310 
311 		return -ENOEXEC;
312 	}
313 
314 	return 0;
315 }
316 
317 #if defined(CONFIG_BT_VCP_VOL_CTLR_VOCS)
cmd_cap_commander_change_volume_offset(const struct shell * sh,size_t argc,char * argv[])318 static int cmd_cap_commander_change_volume_offset(const struct shell *sh, size_t argc, char *argv[])
319 {
320 	struct bt_cap_commander_change_volume_offset_member_param member_params[CONFIG_BT_MAX_CONN];
321 	const size_t cap_args = argc - 1; /* First argument is the command itself */
322 	struct bt_cap_commander_change_volume_offset_param param = {
323 		.type = BT_CAP_SET_TYPE_AD_HOC,
324 		.param = member_params,
325 	};
326 	struct bt_conn *connected_conns[CONFIG_BT_MAX_CONN] = {0};
327 	size_t conn_cnt = 0U;
328 	int err = 0;
329 
330 	if (default_conn == NULL) {
331 		shell_error(sh, "Not connected");
332 		return -ENOEXEC;
333 	}
334 
335 	/* Populate the array of connected connections */
336 	bt_conn_foreach(BT_CONN_TYPE_LE, populate_connected_conns, (void *)connected_conns);
337 	for (size_t i = 0; i < ARRAY_SIZE(connected_conns); i++) {
338 		struct bt_conn *conn = connected_conns[i];
339 
340 		if (conn == NULL) {
341 			break;
342 		}
343 
344 		conn_cnt++;
345 	}
346 
347 	if (cap_args > conn_cnt) {
348 		shell_error(sh, "Cannot use %zu arguments for %zu connections", argc, conn_cnt);
349 
350 		return -ENOEXEC;
351 	}
352 
353 	/* TODO: Add support for coordinated sets */
354 
355 	for (size_t i = 0U; i < cap_args; i++) {
356 		const char *arg = argv[i + 1];
357 		long volume_offset;
358 
359 		volume_offset = shell_strtol(arg, 10, &err);
360 		if (err != 0) {
361 			shell_error(sh, "Failed to parse volume offset from %s", arg);
362 
363 			return -ENOEXEC;
364 		}
365 
366 		if (!IN_RANGE(volume_offset, BT_VOCS_MIN_OFFSET, BT_VOCS_MAX_OFFSET)) {
367 			shell_error(sh, "Invalid volume_offset %lu", volume_offset);
368 
369 			return -ENOEXEC;
370 		}
371 
372 		member_params[i].offset = (int16_t)volume_offset;
373 		member_params[i].member.member = connected_conns[i];
374 		param.count++;
375 	}
376 
377 	shell_print(sh, "Setting volume offset on %zu connections", param.count);
378 
379 	err = bt_cap_commander_change_volume_offset(&param);
380 	if (err != 0) {
381 		shell_print(sh, "Failed to change volume offset: %d", err);
382 
383 		return -ENOEXEC;
384 	}
385 
386 	return 0;
387 }
388 #endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS */
389 #endif /* CONFIG_BT_VCP_VOL_CTLR */
390 
391 #if defined(CONFIG_BT_MICP_MIC_CTLR)
cmd_cap_commander_change_microphone_mute(const struct shell * sh,size_t argc,char * argv[])392 static int cmd_cap_commander_change_microphone_mute(const struct shell *sh, size_t argc,
393 						    char *argv[])
394 {
395 	struct bt_conn *connected_conns[CONFIG_BT_MAX_CONN] = {0};
396 	union bt_cap_set_member members[CONFIG_BT_MAX_CONN] = {0};
397 	struct bt_cap_commander_change_microphone_mute_state_param param = {
398 		.members = members,
399 		.type = BT_CAP_SET_TYPE_AD_HOC, /* TODO: Add support for coordinated sets */
400 	};
401 	int err = 0;
402 
403 	if (default_conn == NULL) {
404 		shell_error(sh, "Not connected");
405 		return -ENOEXEC;
406 	}
407 
408 	param.mute = shell_strtobool(argv[1], 10, &err);
409 	if (err != 0) {
410 		shell_error(sh, "Failed to parse microphone mute from %s", argv[1]);
411 
412 		return -ENOEXEC;
413 	}
414 
415 	/* Populate the array of connected connections */
416 	bt_conn_foreach(BT_CONN_TYPE_LE, populate_connected_conns, (void *)connected_conns);
417 
418 	param.count = 0U;
419 	param.members = members;
420 	for (size_t i = 0; i < ARRAY_SIZE(connected_conns); i++) {
421 		struct bt_conn *conn = connected_conns[i];
422 
423 		if (conn == NULL) {
424 			break;
425 		}
426 
427 		param.members[i].member = conn;
428 		param.count++;
429 	}
430 
431 	shell_print(sh, "Setting microphone mute to %d on %zu connections", param.mute,
432 		    param.count);
433 
434 	err = bt_cap_commander_change_microphone_mute_state(&param);
435 	if (err != 0) {
436 		shell_print(sh, "Failed to change microphone mute: %d", err);
437 
438 		return -ENOEXEC;
439 	}
440 
441 	return 0;
442 }
443 
444 #if defined(CONFIG_BT_MICP_MIC_CTLR_AICS)
cmd_cap_commander_change_microphone_gain(const struct shell * sh,size_t argc,char * argv[])445 static int cmd_cap_commander_change_microphone_gain(const struct shell *sh, size_t argc,
446 						    char *argv[])
447 {
448 	struct bt_cap_commander_change_microphone_gain_setting_member_param
449 		member_params[CONFIG_BT_MAX_CONN];
450 	const size_t cap_args = argc - 1; /* First argument is the command itself */
451 	struct bt_cap_commander_change_microphone_gain_setting_param param = {
452 		.type = BT_CAP_SET_TYPE_AD_HOC,
453 		.param = member_params,
454 	};
455 	struct bt_conn *connected_conns[CONFIG_BT_MAX_CONN] = {0};
456 	size_t conn_cnt = 0U;
457 	int err = 0;
458 
459 	if (default_conn == NULL) {
460 		shell_error(sh, "Not connected");
461 		return -ENOEXEC;
462 	}
463 
464 	/* Populate the array of connected connections */
465 	bt_conn_foreach(BT_CONN_TYPE_LE, populate_connected_conns, (void *)connected_conns);
466 	for (size_t i = 0; i < ARRAY_SIZE(connected_conns); i++) {
467 		struct bt_conn *conn = connected_conns[i];
468 
469 		if (conn == NULL) {
470 			break;
471 		}
472 
473 		conn_cnt++;
474 	}
475 
476 	if (cap_args > conn_cnt) {
477 		shell_error(sh, "Cannot use %zu arguments for %zu connections", argc, conn_cnt);
478 
479 		return -ENOEXEC;
480 	}
481 
482 	/* TODO: Add support for coordinated sets */
483 
484 	for (size_t i = 0U; i < cap_args; i++) {
485 		const char *arg = argv[i + 1];
486 		long gain;
487 
488 		gain = shell_strtol(arg, 10, &err);
489 		if (err != 0) {
490 			shell_error(sh, "Failed to parse volume offset from %s", arg);
491 
492 			return -ENOEXEC;
493 		}
494 
495 		if (!IN_RANGE(gain, INT8_MIN, INT8_MAX)) {
496 			shell_error(sh, "Invalid gain %lu", gain);
497 
498 			return -ENOEXEC;
499 		}
500 
501 		member_params[i].gain = (int8_t)gain;
502 		member_params[i].member.member = connected_conns[i];
503 		param.count++;
504 	}
505 
506 	shell_print(sh, "Setting microphone gain on %zu connections", param.count);
507 
508 	err = bt_cap_commander_change_microphone_gain_setting(&param);
509 	if (err != 0) {
510 		shell_print(sh, "Failed to change microphone gain: %d", err);
511 
512 		return -ENOEXEC;
513 	}
514 
515 	return 0;
516 }
517 #endif /* CONFIG_BT_MICP_MIC_CTLR_AICS */
518 #endif /* CONFIG_BT_MICP_MIC_CTLR */
519 
520 #if defined(CONFIG_BT_BAP_BROADCAST_ASSISTANT)
cmd_cap_commander_broadcast_reception_start(const struct shell * sh,size_t argc,char * argv[])521 static int cmd_cap_commander_broadcast_reception_start(const struct shell *sh, size_t argc,
522 						       char *argv[])
523 {
524 	struct bt_cap_commander_broadcast_reception_start_member_param
525 		member_params[CONFIG_BT_MAX_CONN] = {0};
526 
527 	struct bt_cap_commander_broadcast_reception_start_param param = {
528 		.type = BT_CAP_SET_TYPE_AD_HOC,
529 		.param = member_params,
530 	};
531 
532 	struct bt_cap_commander_broadcast_reception_start_member_param *member_param =
533 		&member_params[0];
534 	struct bt_bap_bass_subgroup subgroup = {0};
535 
536 	struct bt_conn *connected_conns[CONFIG_BT_MAX_CONN] = {0};
537 	size_t conn_cnt = 0U;
538 	unsigned long broadcast_id;
539 	unsigned long adv_sid;
540 
541 	int err = 0;
542 
543 	if (default_conn == NULL) {
544 		shell_error(sh, "Not connected");
545 		return -ENOEXEC;
546 	}
547 
548 	/* TODO: Add support for coordinated sets */
549 
550 	/* Populate the array of connected connections */
551 	bt_conn_foreach(BT_CONN_TYPE_LE, populate_connected_conns, (void *)connected_conns);
552 	for (size_t i = 0; i < ARRAY_SIZE(connected_conns); i++) {
553 		struct bt_conn *conn = connected_conns[i];
554 
555 		if (conn == NULL) {
556 			break;
557 		}
558 
559 		conn_cnt++;
560 	}
561 
562 	err = bt_addr_le_from_str(argv[1], argv[2], &member_param->addr);
563 	if (err) {
564 		shell_error(sh, "Invalid peer address (err %d)", err);
565 
566 		return -ENOEXEC;
567 	}
568 
569 	adv_sid = shell_strtoul(argv[3], 0, &err);
570 	if (err != 0) {
571 		shell_error(sh, "Could not parse adv_sid: %d", err);
572 
573 		return -ENOEXEC;
574 	}
575 
576 	if (adv_sid > BT_GAP_SID_MAX) {
577 		shell_error(sh, "Invalid adv_sid: %lu", adv_sid);
578 
579 		return -ENOEXEC;
580 	}
581 
582 	member_param->adv_sid = adv_sid;
583 
584 	broadcast_id = shell_strtoul(argv[4], 0, &err);
585 	if (err != 0) {
586 		shell_error(sh, "Could not parse broadcast_id: %d", err);
587 
588 		return -ENOEXEC;
589 	}
590 
591 	if (broadcast_id > BT_AUDIO_BROADCAST_ID_MAX) {
592 		shell_error(sh, "Invalid broadcast_id: %lu", broadcast_id);
593 
594 		return -ENOEXEC;
595 	}
596 
597 	member_param->broadcast_id = broadcast_id;
598 
599 	if (argc > 5) {
600 		unsigned long pa_interval;
601 
602 		pa_interval = shell_strtoul(argv[5], 0, &err);
603 		if (err) {
604 			shell_error(sh, "Could not parse pa_interval: %d", err);
605 
606 			return -ENOEXEC;
607 		}
608 
609 		if (!IN_RANGE(pa_interval, BT_GAP_PER_ADV_MIN_INTERVAL,
610 			      BT_GAP_PER_ADV_MAX_INTERVAL)) {
611 			shell_error(sh, "Invalid pa_interval: %lu", pa_interval);
612 
613 			return -ENOEXEC;
614 		}
615 
616 		member_param->pa_interval = pa_interval;
617 	} else {
618 		member_param->pa_interval = BT_BAP_PA_INTERVAL_UNKNOWN;
619 	}
620 
621 	/* TODO: Support multiple subgroups */
622 	if (argc > 6) {
623 		unsigned long bis_sync;
624 
625 		bis_sync = shell_strtoul(argv[6], 0, &err);
626 		if (err) {
627 			shell_error(sh, "Could not parse bis_sync: %d", err);
628 
629 			return -ENOEXEC;
630 		}
631 
632 		if (!BT_BAP_BASS_VALID_BIT_BITFIELD(bis_sync)) {
633 			shell_error(sh, "Invalid bis_sync: %lu", bis_sync);
634 
635 			return -ENOEXEC;
636 		}
637 
638 		subgroup.bis_sync = bis_sync;
639 	} else {
640 		subgroup.bis_sync = BT_BAP_BIS_SYNC_NO_PREF;
641 	}
642 
643 	if (argc > 7) {
644 		size_t metadata_len;
645 
646 		metadata_len = hex2bin(argv[7], strlen(argv[7]), subgroup.metadata,
647 				       sizeof(subgroup.metadata));
648 
649 		if (metadata_len == 0U) {
650 			shell_error(sh, "Could not parse metadata");
651 
652 			return -ENOEXEC;
653 		}
654 
655 		/* sizeof(subgroup.metadata) can always fit in uint8_t */
656 
657 		subgroup.metadata_len = metadata_len;
658 	}
659 
660 	member_param->num_subgroups = 1;
661 	memcpy(member_param->subgroups, &subgroup, sizeof(struct bt_bap_bass_subgroup));
662 
663 	member_param->member.member = connected_conns[0];
664 
665 	/* each connection has its own member_params field
666 	 * here we use the same values for all connections, so we copy
667 	 * the parameters
668 	 */
669 	for (size_t i = 1U; i < conn_cnt; i++) {
670 		memcpy(&member_params[i], member_param, sizeof(*member_param));
671 
672 		/* the member value is different for each, so we can not just copy this value */
673 		member_params[i].member.member = connected_conns[i];
674 	}
675 
676 	param.count = conn_cnt;
677 
678 	shell_print(sh, "Starting broadcast reception on %zu connection(s)", param.count);
679 
680 	err = bt_cap_commander_broadcast_reception_start(&param);
681 	if (err != 0) {
682 		shell_print(sh, "Failed to start broadcast reception: %d", err);
683 
684 		return -ENOEXEC;
685 	}
686 
687 	return 0;
688 }
689 
cmd_cap_commander_broadcast_reception_stop(const struct shell * sh,size_t argc,char * argv[])690 static int cmd_cap_commander_broadcast_reception_stop(const struct shell *sh, size_t argc,
691 						      char *argv[])
692 {
693 	struct bt_cap_commander_broadcast_reception_stop_member_param
694 		member_params[CONFIG_BT_MAX_CONN] = {0};
695 	const size_t cap_args = argc - 1; /* First argument is the command itself */
696 	struct bt_cap_commander_broadcast_reception_stop_param param = {
697 		.type = BT_CAP_SET_TYPE_AD_HOC,
698 		.param = member_params,
699 	};
700 
701 	struct bt_conn *connected_conns[CONFIG_BT_MAX_CONN] = {0};
702 	size_t conn_cnt = 0U;
703 	int err = 0;
704 
705 	if (default_conn == NULL) {
706 		shell_error(sh, "Not connected");
707 		return -ENOEXEC;
708 	}
709 
710 	/* TODO: Add support for coordinated sets */
711 
712 	/* Populate the array of connected connections */
713 	bt_conn_foreach(BT_CONN_TYPE_LE, populate_connected_conns, (void *)connected_conns);
714 	for (size_t i = 0; i < ARRAY_SIZE(connected_conns); i++) {
715 		struct bt_conn *conn = connected_conns[i];
716 
717 		if (conn == NULL) {
718 			break;
719 		}
720 
721 		conn_cnt++;
722 	}
723 
724 	if (cap_args > conn_cnt) {
725 		shell_error(sh, "Cannot use %zu arguments for %zu connections", argc, conn_cnt);
726 
727 		return -ENOEXEC;
728 	}
729 
730 	for (size_t i = 0U; i < cap_args; i++) {
731 		const char *arg = argv[i + 1];
732 		unsigned long src_id;
733 
734 		src_id = shell_strtoul(arg, 0, &err);
735 		if (err != 0) {
736 			shell_error(sh, "Could not parse src_id: %d", err);
737 
738 			return -ENOEXEC;
739 		}
740 
741 		if (src_id > UINT8_MAX) {
742 			shell_error(sh, "Invalid src_id: %lu", src_id);
743 
744 			return -ENOEXEC;
745 		}
746 
747 		/* TODO: Allow for multiple subgroups */
748 		member_params[i].num_subgroups = 1;
749 		member_params[i].src_id = src_id;
750 		member_params[i].member.member = connected_conns[i];
751 		param.count++;
752 	}
753 
754 	shell_print(sh, "Stopping broadcast reception on %zu connection(s)", param.count);
755 
756 	err = bt_cap_commander_broadcast_reception_stop(&param);
757 	if (err != 0) {
758 		shell_print(sh, "Failed to initiate broadcast reception stop: %d", err);
759 
760 		return -ENOEXEC;
761 	}
762 
763 	return 0;
764 }
765 
cmd_cap_commander_distribute_broadcast_code(const struct shell * sh,size_t argc,char * argv[])766 static int cmd_cap_commander_distribute_broadcast_code(const struct shell *sh, size_t argc,
767 						       char *argv[])
768 {
769 	struct bt_cap_commander_distribute_broadcast_code_member_param
770 		member_params[CONFIG_BT_MAX_CONN] = {0};
771 	const size_t cap_argc = argc - 1; /* First argument is the command itself */
772 	struct bt_cap_commander_distribute_broadcast_code_param param = {
773 		.type = BT_CAP_SET_TYPE_AD_HOC,
774 		.param = member_params,
775 	};
776 
777 	struct bt_conn *connected_conns[CONFIG_BT_MAX_CONN] = {0};
778 	size_t conn_cnt = 0U;
779 	int err = 0;
780 
781 	if (default_conn == NULL) {
782 		shell_error(sh, "Not connected");
783 		return -ENOEXEC;
784 	}
785 
786 	/* TODO Add support for coordinated sets */
787 
788 	/* Populate the array of connected connections */
789 	bt_conn_foreach(BT_CONN_TYPE_LE, populate_connected_conns, (void *)connected_conns);
790 	for (size_t i = 0; i < ARRAY_SIZE(connected_conns); i++) {
791 		struct bt_conn *conn = connected_conns[i];
792 
793 		if (conn == NULL) {
794 			break;
795 		}
796 		conn_cnt++;
797 	}
798 
799 	/* The number of cap_args needs to be the number of connections + 1 since the last argument
800 	 * is the broadcast code
801 	 */
802 	if (cap_argc != conn_cnt + 1) {
803 		shell_error(sh, "Cannot use %zu arguments for %zu connections", argc, conn_cnt);
804 		return -ENOEXEC;
805 	}
806 
807 	/* the last argument is the broadcast code */
808 	if (strlen(argv[cap_argc]) > BT_ISO_BROADCAST_CODE_SIZE) {
809 		shell_error(sh, "Broadcast code can be maximum %d characters",
810 			    BT_ISO_BROADCAST_CODE_SIZE);
811 		return -ENOEXEC;
812 	}
813 
814 	for (size_t i = 0; i < conn_cnt; i++) {
815 		const char *arg = argv[i + 1];
816 		unsigned long src_id;
817 
818 		src_id = shell_strtoul(arg, 0, &err);
819 		if (err != 0) {
820 			shell_error(sh, "Could not parce src_id: %d", err);
821 			return -ENOEXEC;
822 		}
823 		if (src_id > UINT8_MAX) {
824 			shell_error(sh, "Invalid src_id: %lu", src_id);
825 			return -ENOEXEC;
826 		}
827 
828 		member_params[i].src_id = src_id;
829 		member_params[i].member.member = connected_conns[i];
830 		param.count++;
831 	}
832 
833 	memcpy(param.broadcast_code, argv[cap_argc], strlen(argv[cap_argc]));
834 	shell_print(sh, "Distributing broadcast code on %zu connection(s)", param.count);
835 	err = bt_cap_commander_distribute_broadcast_code(&param);
836 	if (err != 0) {
837 		shell_print(sh, "Failed to initiate distribute broadcast code: %d", err);
838 		return -ENOEXEC;
839 	}
840 
841 	return 0;
842 }
843 
844 #endif /* CONFIG_BT_BAP_BROADCAST_ASSISTANT */
845 
cmd_cap_commander(const struct shell * sh,size_t argc,char ** argv)846 static int cmd_cap_commander(const struct shell *sh, size_t argc, char **argv)
847 {
848 	if (argc > 1) {
849 		shell_error(sh, "%s unknown parameter: %s", argv[0], argv[1]);
850 	} else {
851 		shell_error(sh, "%s Missing subcommand", argv[0]);
852 	}
853 
854 	return -ENOEXEC;
855 }
856 
857 SHELL_STATIC_SUBCMD_SET_CREATE(
858 	cap_commander_cmds,
859 	SHELL_CMD_ARG(discover, NULL, "Discover CAS", cmd_cap_commander_discover, 1, 0),
860 	SHELL_CMD_ARG(cancel, NULL, "CAP commander cancel current procedure",
861 		      cmd_cap_commander_cancel, 1, 0),
862 #if defined(CONFIG_BT_VCP_VOL_CTLR)
863 	SHELL_CMD_ARG(change_volume, NULL, "Change volume on all connections <volume>",
864 		      cmd_cap_commander_change_volume, 2, 0),
865 	SHELL_CMD_ARG(change_volume_mute, NULL,
866 		      "Change volume mute state on all connections <mute>",
867 		      cmd_cap_commander_change_volume_mute, 2, 0),
868 #if defined(CONFIG_BT_VCP_VOL_CTLR_VOCS)
869 	SHELL_CMD_ARG(change_volume_offset, NULL,
870 		      "Change volume offset per connection <volume_offset [volume_offset [...]]>",
871 		      cmd_cap_commander_change_volume_offset, 2, CONFIG_BT_MAX_CONN - 1),
872 #endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS */
873 #endif /* CONFIG_BT_VCP_VOL_CTLR */
874 #if defined(CONFIG_BT_MICP_MIC_CTLR)
875 	SHELL_CMD_ARG(change_microphone_mute, NULL,
876 		      "Change microphone mute state on all connections <mute>",
877 		      cmd_cap_commander_change_microphone_mute, 2, 0),
878 #if defined(CONFIG_BT_MICP_MIC_CTLR_AICS)
879 	SHELL_CMD_ARG(change_microphone_gain, NULL,
880 		      "Change microphone gain per connection <gain [gain [...]]>",
881 		      cmd_cap_commander_change_microphone_gain, 2, CONFIG_BT_MAX_CONN - 1),
882 #endif /* CONFIG_BT_MICP_MIC_CTLR_AICS */
883 #endif /* CONFIG_BT_MICP_MIC_CTLR */
884 #if defined(CONFIG_BT_BAP_BROADCAST_ASSISTANT)
885 	SHELL_CMD_ARG(broadcast_reception_start, NULL,
886 		      "Start broadcast reception "
887 		      "with source <address: XX:XX:XX:XX:XX:XX> "
888 		      "<type: public/random> <adv_sid> "
889 		      "<broadcast_id> [<pa_interval>] [<sync_bis>] "
890 		      "[<metadata>]",
891 		      cmd_cap_commander_broadcast_reception_start, 5, 3),
892 	SHELL_CMD_ARG(broadcast_reception_stop, NULL,
893 		      "Stop broadcast reception "
894 		      "<src_id [...]>",
895 		      cmd_cap_commander_broadcast_reception_stop, 2, 0),
896 	SHELL_CMD_ARG(distribute_broadcast_code, NULL,
897 		      "Distribute broadcast code <src_id [...]> <broadcast_code>",
898 		      cmd_cap_commander_distribute_broadcast_code, 2, CONFIG_BT_MAX_CONN - 1),
899 #endif /* CONFIG_BT_BAP_BROADCAST_ASSISTANT */
900 	SHELL_SUBCMD_SET_END);
901 
902 SHELL_CMD_ARG_REGISTER(cap_commander, &cap_commander_cmds, "Bluetooth CAP commander shell commands",
903 		       cmd_cap_commander, 1, 1);
904