1 /** @file
2 * @brief Audio Video Remote Control Profile shell functions.
3 */
4
5 /*
6 * Copyright (c) 2024 Xiaomi InC.
7 *
8 * SPDX-License-Identifier: Apache-2.0
9 */
10
11 #include <errno.h>
12 #include <zephyr/types.h>
13 #include <stddef.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <zephyr/sys/byteorder.h>
17 #include <zephyr/kernel.h>
18
19 #include <zephyr/settings/settings.h>
20
21 #include <zephyr/bluetooth/bluetooth.h>
22 #include <zephyr/bluetooth/classic/avrcp.h>
23 #include <zephyr/bluetooth/conn.h>
24 #include <zephyr/bluetooth/hci.h>
25 #include <zephyr/bluetooth/l2cap.h>
26
27 #include <zephyr/shell/shell.h>
28
29 #include "host/shell/bt.h"
30 #include "common/bt_shell_private.h"
31
32 struct bt_avrcp_ct *default_ct;
33 struct bt_avrcp_tg *default_tg;
34 static bool avrcp_ct_registered;
35 static bool avrcp_tg_registered;
36 static uint8_t local_tid;
37 static uint8_t tg_tid;
38
get_next_tid(void)39 static uint8_t get_next_tid(void)
40 {
41 uint8_t ret = local_tid;
42
43 local_tid++;
44 local_tid &= 0x0F;
45
46 return ret;
47 }
48
avrcp_ct_connected(struct bt_conn * conn,struct bt_avrcp_ct * ct)49 static void avrcp_ct_connected(struct bt_conn *conn, struct bt_avrcp_ct *ct)
50 {
51 bt_shell_print("AVRCP CT connected");
52 default_ct = ct;
53 local_tid = 0;
54 }
55
avrcp_ct_disconnected(struct bt_avrcp_ct * ct)56 static void avrcp_ct_disconnected(struct bt_avrcp_ct *ct)
57 {
58 bt_shell_print("AVRCP CT disconnected");
59 local_tid = 0;
60 default_ct = NULL;
61 }
62
avrcp_get_cap_rsp(struct bt_avrcp_ct * ct,uint8_t tid,const struct bt_avrcp_get_cap_rsp * rsp)63 static void avrcp_get_cap_rsp(struct bt_avrcp_ct *ct, uint8_t tid,
64 const struct bt_avrcp_get_cap_rsp *rsp)
65 {
66 uint8_t i;
67
68 switch (rsp->cap_id) {
69 case BT_AVRCP_CAP_COMPANY_ID:
70 for (i = 0; i < rsp->cap_cnt; i++) {
71 bt_shell_print("Remote CompanyID = 0x%06x",
72 sys_get_be24(&rsp->cap[BT_AVRCP_COMPANY_ID_SIZE * i]));
73 }
74 break;
75 case BT_AVRCP_CAP_EVENTS_SUPPORTED:
76 for (i = 0; i < rsp->cap_cnt; i++) {
77 bt_shell_print("Remote supported EventID = 0x%02x", rsp->cap[i]);
78 }
79 break;
80 }
81 }
82
avrcp_unit_info_rsp(struct bt_avrcp_ct * ct,uint8_t tid,struct bt_avrcp_unit_info_rsp * rsp)83 static void avrcp_unit_info_rsp(struct bt_avrcp_ct *ct, uint8_t tid,
84 struct bt_avrcp_unit_info_rsp *rsp)
85 {
86 bt_shell_print("AVRCP unit info received, unit type = 0x%02x, company_id = 0x%06x",
87 rsp->unit_type, rsp->company_id);
88 }
89
avrcp_subunit_info_rsp(struct bt_avrcp_ct * ct,uint8_t tid,struct bt_avrcp_subunit_info_rsp * rsp)90 static void avrcp_subunit_info_rsp(struct bt_avrcp_ct *ct, uint8_t tid,
91 struct bt_avrcp_subunit_info_rsp *rsp)
92 {
93 int i;
94
95 bt_shell_print("AVRCP subunit info received, subunit type = 0x%02x, extended subunit = %d",
96 rsp->subunit_type, rsp->max_subunit_id);
97 for (i = 0; i < rsp->max_subunit_id; i++) {
98 bt_shell_print("extended subunit id = %d, subunit type = 0x%02x",
99 rsp->extended_subunit_id[i], rsp->extended_subunit_type[i]);
100 }
101 }
102
avrcp_passthrough_rsp(struct bt_avrcp_ct * ct,uint8_t tid,bt_avrcp_rsp_t result,const struct bt_avrcp_passthrough_rsp * rsp)103 static void avrcp_passthrough_rsp(struct bt_avrcp_ct *ct, uint8_t tid, bt_avrcp_rsp_t result,
104 const struct bt_avrcp_passthrough_rsp *rsp)
105 {
106 if (result == BT_AVRCP_RSP_ACCEPTED) {
107 bt_shell_print(
108 "AVRCP passthough command accepted, operation id = 0x%02x, state = %d",
109 BT_AVRCP_PASSTHROUGH_GET_OPID(rsp), BT_AVRCP_PASSTHROUGH_GET_STATE(rsp));
110 } else {
111 bt_shell_print("AVRCP passthough command rejected, operation id = 0x%02x, state = "
112 "%d, response = %d",
113 BT_AVRCP_PASSTHROUGH_GET_OPID(rsp),
114 BT_AVRCP_PASSTHROUGH_GET_STATE(rsp), result);
115 }
116 }
117
118 static struct bt_avrcp_ct_cb app_avrcp_ct_cb = {
119 .connected = avrcp_ct_connected,
120 .disconnected = avrcp_ct_disconnected,
121 .get_cap_rsp = avrcp_get_cap_rsp,
122 .unit_info_rsp = avrcp_unit_info_rsp,
123 .subunit_info_rsp = avrcp_subunit_info_rsp,
124 .passthrough_rsp = avrcp_passthrough_rsp,
125 };
126
avrcp_tg_connected(struct bt_conn * conn,struct bt_avrcp_tg * tg)127 static void avrcp_tg_connected(struct bt_conn *conn, struct bt_avrcp_tg *tg)
128 {
129 bt_shell_print("AVRCP TG connected");
130 default_tg = tg;
131 }
132
avrcp_tg_disconnected(struct bt_avrcp_tg * tg)133 static void avrcp_tg_disconnected(struct bt_avrcp_tg *tg)
134 {
135 bt_shell_print("AVRCP TG disconnected");
136 default_tg = NULL;
137 }
138
avrcp_unit_info_req(struct bt_avrcp_tg * tg,uint8_t tid)139 static void avrcp_unit_info_req(struct bt_avrcp_tg *tg, uint8_t tid)
140 {
141 bt_shell_print("AVRCP unit info request received");
142 tg_tid = tid;
143 }
144
145 static struct bt_avrcp_tg_cb app_avrcp_tg_cb = {
146 .connected = avrcp_tg_connected,
147 .disconnected = avrcp_tg_disconnected,
148 .unit_info_req = avrcp_unit_info_req,
149 };
150
register_ct_cb(const struct shell * sh)151 static int register_ct_cb(const struct shell *sh)
152 {
153 int err;
154
155 if (avrcp_ct_registered) {
156 return 0;
157 }
158
159 err = bt_avrcp_ct_register_cb(&app_avrcp_ct_cb);
160 if (!err) {
161 avrcp_ct_registered = true;
162 shell_print(sh, "AVRCP CT callbacks registered");
163 } else {
164 shell_print(sh, "failed to register AVRCP CT callbacks");
165 }
166
167 return err;
168 }
169
cmd_register_ct_cb(const struct shell * sh,int32_t argc,char * argv[])170 static int cmd_register_ct_cb(const struct shell *sh, int32_t argc, char *argv[])
171 {
172 if (avrcp_ct_registered) {
173 shell_print(sh, "already registered");
174 return 0;
175 }
176
177 register_ct_cb(sh);
178
179 return 0;
180 }
181
register_tg_cb(const struct shell * sh)182 static int register_tg_cb(const struct shell *sh)
183 {
184 int err;
185
186 if (avrcp_tg_registered) {
187 return 0;
188 }
189
190 err = bt_avrcp_tg_register_cb(&app_avrcp_tg_cb);
191 if (!err) {
192 avrcp_tg_registered = true;
193 shell_print(sh, "AVRCP TG callbacks registered");
194 } else {
195 shell_print(sh, "failed to register AVRCP TG callbacks");
196 }
197
198 return err;
199 }
200
cmd_register_tg_cb(const struct shell * sh,int32_t argc,char * argv[])201 static int cmd_register_tg_cb(const struct shell *sh, int32_t argc, char *argv[])
202 {
203 if (avrcp_tg_registered) {
204 shell_print(sh, "already registered");
205 return 0;
206 }
207
208 register_tg_cb(sh);
209
210 return 0;
211 }
212
cmd_connect(const struct shell * sh,int32_t argc,char * argv[])213 static int cmd_connect(const struct shell *sh, int32_t argc, char *argv[])
214 {
215 int err;
216
217 if (!avrcp_ct_registered && register_ct_cb(sh) != 0) {
218 return -ENOEXEC;
219 }
220
221 if (!avrcp_tg_registered && register_tg_cb(sh) != 0) {
222 return -ENOEXEC;
223 }
224
225 if (!default_conn) {
226 shell_error(sh, "BR/EDR not connected");
227 return -ENOEXEC;
228 }
229
230 err = bt_avrcp_connect(default_conn);
231 if (err) {
232 shell_error(sh, "fail to connect AVRCP");
233 }
234
235 return 0;
236 }
237
cmd_disconnect(const struct shell * sh,int32_t argc,char * argv[])238 static int cmd_disconnect(const struct shell *sh, int32_t argc, char *argv[])
239 {
240 if ((!avrcp_ct_registered) && (!avrcp_tg_registered)) {
241 shell_error(sh, "Neither CT nor TG callbacks are registered.");
242 return -ENOEXEC;
243 }
244
245 if (!default_conn) {
246 shell_print(sh, "Not connected");
247 return -ENOEXEC;
248 }
249
250 if ((default_ct != NULL) || (default_tg != NULL)) {
251 bt_avrcp_disconnect(default_conn);
252 } else {
253 shell_error(sh, "AVRCP is not connected");
254 }
255
256 return 0;
257 }
258
cmd_get_unit_info(const struct shell * sh,int32_t argc,char * argv[])259 static int cmd_get_unit_info(const struct shell *sh, int32_t argc, char *argv[])
260 {
261 if (!avrcp_ct_registered && register_ct_cb(sh) != 0) {
262 return -ENOEXEC;
263 }
264
265 if (default_ct != NULL) {
266 bt_avrcp_ct_get_unit_info(default_ct, get_next_tid());
267 } else {
268 shell_error(sh, "AVRCP is not connected");
269 }
270
271 return 0;
272 }
273
cmd_send_unit_info_rsp(const struct shell * sh,int32_t argc,char * argv[])274 static int cmd_send_unit_info_rsp(const struct shell *sh, int32_t argc, char *argv[])
275 {
276 struct bt_avrcp_unit_info_rsp rsp;
277 int err;
278
279 if (!avrcp_tg_registered && register_tg_cb(sh) != 0) {
280 return -ENOEXEC;
281 }
282
283 rsp.unit_type = BT_AVRCP_SUBUNIT_TYPE_PANEL;
284 rsp.company_id = BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG;
285
286 if (default_tg != NULL) {
287 err = bt_avrcp_tg_send_unit_info_rsp(default_tg, tg_tid, &rsp);
288 if (!err) {
289 shell_print(sh, "AVRCP send unit info response");
290 } else {
291 shell_error(sh, "Failed to send unit info response");
292 }
293 } else {
294 shell_error(sh, "AVRCP is not connected");
295 }
296
297 return 0;
298 }
299
cmd_get_subunit_info(const struct shell * sh,int32_t argc,char * argv[])300 static int cmd_get_subunit_info(const struct shell *sh, int32_t argc, char *argv[])
301 {
302 if (!avrcp_ct_registered && register_ct_cb(sh) != 0) {
303 return -ENOEXEC;
304 }
305
306 if (default_ct != NULL) {
307 bt_avrcp_ct_get_subunit_info(default_ct, get_next_tid());
308 } else {
309 shell_error(sh, "AVRCP is not connected");
310 }
311
312 return 0;
313 }
314
cmd_passthrough(const struct shell * sh,bt_avrcp_opid_t opid,const uint8_t * payload,uint8_t len)315 static int cmd_passthrough(const struct shell *sh, bt_avrcp_opid_t opid, const uint8_t *payload,
316 uint8_t len)
317 {
318 if (!avrcp_ct_registered && register_ct_cb(sh) != 0) {
319 return -ENOEXEC;
320 }
321
322 if (default_ct != NULL) {
323 bt_avrcp_ct_passthrough(default_ct, get_next_tid(), opid, BT_AVRCP_BUTTON_PRESSED,
324 payload, len);
325 bt_avrcp_ct_passthrough(default_ct, get_next_tid(), opid, BT_AVRCP_BUTTON_RELEASED,
326 payload, len);
327 } else {
328 shell_error(sh, "AVRCP is not connected");
329 }
330
331 return 0;
332 }
333
cmd_play(const struct shell * sh,int32_t argc,char * argv[])334 static int cmd_play(const struct shell *sh, int32_t argc, char *argv[])
335 {
336 return cmd_passthrough(sh, BT_AVRCP_OPID_PLAY, NULL, 0);
337 }
338
cmd_pause(const struct shell * sh,int32_t argc,char * argv[])339 static int cmd_pause(const struct shell *sh, int32_t argc, char *argv[])
340 {
341 return cmd_passthrough(sh, BT_AVRCP_OPID_PAUSE, NULL, 0);
342 }
343
cmd_get_cap(const struct shell * sh,int32_t argc,char * argv[])344 static int cmd_get_cap(const struct shell *sh, int32_t argc, char *argv[])
345 {
346 const char *cap_id;
347
348 if (!avrcp_ct_registered && register_ct_cb(sh) != 0) {
349 return -ENOEXEC;
350 }
351
352 if (default_ct == NULL) {
353 shell_error(sh, "AVRCP is not connected");
354 return 0;
355 }
356
357 cap_id = argv[1];
358 if (!strcmp(cap_id, "company")) {
359 bt_avrcp_ct_get_cap(default_ct, get_next_tid(), BT_AVRCP_CAP_COMPANY_ID);
360 } else if (!strcmp(cap_id, "events")) {
361 bt_avrcp_ct_get_cap(default_ct, get_next_tid(), BT_AVRCP_CAP_EVENTS_SUPPORTED);
362 }
363
364 return 0;
365 }
366
367 SHELL_STATIC_SUBCMD_SET_CREATE(
368 ct_cmds,
369 SHELL_CMD_ARG(register_cb, NULL, "register avrcp ct callbacks", cmd_register_ct_cb, 1, 0),
370 SHELL_CMD_ARG(get_unit, NULL, "get unit info", cmd_get_unit_info, 1, 0),
371 SHELL_CMD_ARG(get_subunit, NULL, "get subunit info", cmd_get_subunit_info, 1, 0),
372 SHELL_CMD_ARG(get_cap, NULL, "get capabilities <cap_id: company or events>", cmd_get_cap, 2,
373 0),
374 SHELL_CMD_ARG(play, NULL, "request a play at the remote player", cmd_play, 1, 0),
375 SHELL_CMD_ARG(pause, NULL, "request a pause at the remote player", cmd_pause, 1, 0),
376 SHELL_SUBCMD_SET_END);
377
378 SHELL_STATIC_SUBCMD_SET_CREATE(
379 tg_cmds,
380 SHELL_CMD_ARG(register_cb, NULL, "register avrcp tg callbacks", cmd_register_tg_cb, 1, 0),
381 SHELL_CMD_ARG(send_unit_rsp, NULL, "send unit info response", cmd_send_unit_info_rsp, 1, 0),
382 SHELL_SUBCMD_SET_END);
383
cmd_avrcp(const struct shell * sh,size_t argc,char ** argv)384 static int cmd_avrcp(const struct shell *sh, size_t argc, char **argv)
385 {
386 if (argc == 1) {
387 shell_help(sh);
388 /* sh returns 1 when help is printed */
389 return 1;
390 }
391
392 shell_error(sh, "%s unknown parameter: %s", argv[0], argv[1]);
393
394 return -ENOEXEC;
395 }
396
397 SHELL_STATIC_SUBCMD_SET_CREATE(
398 avrcp_cmds,
399 SHELL_CMD_ARG(connect, NULL, "connect AVRCP", cmd_connect, 1, 0),
400 SHELL_CMD_ARG(disconnect, NULL, "disconnect AVRCP", cmd_disconnect, 1, 0),
401 SHELL_CMD(ct, &ct_cmds, "AVRCP CT shell commands", cmd_avrcp),
402 SHELL_CMD(tg, &tg_cmds, "AVRCP TG shell commands", cmd_avrcp),
403 SHELL_SUBCMD_SET_END);
404
405 SHELL_CMD_ARG_REGISTER(avrcp, &avrcp_cmds, "Bluetooth AVRCP sh commands", cmd_avrcp, 1, 1);
406