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