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(¶m);
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(¶m);
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(¶m);
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(¶m);
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(¶m);
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(¶m);
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(¶m);
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(¶m);
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