1 /**
2 * @file
3 * @brief Bluetooth Hearing Access Service (HAS) shell.
4 *
5 * Copyright (c) 2022 Codecoup
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 <stdio.h>
14 #include <string.h>
15
16 #include <zephyr/bluetooth/conn.h>
17 #include <zephyr/bluetooth/bluetooth.h>
18 #include <zephyr/bluetooth/audio/has.h>
19 #include <zephyr/shell/shell.h>
20 #include <zephyr/kernel.h>
21 #include <zephyr/shell/shell_string_conv.h>
22
23 #include "host/shell/bt.h"
24
25 static struct bt_has *inst;
26
has_client_discover_cb(struct bt_conn * conn,int err,struct bt_has * has,enum bt_has_hearing_aid_type type,enum bt_has_capabilities caps)27 static void has_client_discover_cb(struct bt_conn *conn, int err, struct bt_has *has,
28 enum bt_has_hearing_aid_type type,
29 enum bt_has_capabilities caps)
30 {
31 if (err) {
32 shell_error(ctx_shell, "HAS discovery (err %d)", err);
33 return;
34 }
35
36 shell_print(ctx_shell, "HAS discovered %p type 0x%02x caps 0x%02x for conn %p",
37 has, type, caps, conn);
38
39 inst = has;
40 }
41
has_client_preset_switch_cb(struct bt_has * has,int err,uint8_t index)42 static void has_client_preset_switch_cb(struct bt_has *has, int err, uint8_t index)
43 {
44 if (err != 0) {
45 shell_error(ctx_shell, "HAS %p preset switch error (err %d)", has, err);
46 } else {
47 shell_print(ctx_shell, "HAS %p preset switch index 0x%02x", has, index);
48 }
49 }
50
has_client_preset_read_rsp_cb(struct bt_has * has,int err,const struct bt_has_preset_record * record,bool is_last)51 static void has_client_preset_read_rsp_cb(struct bt_has *has, int err,
52 const struct bt_has_preset_record *record, bool is_last)
53 {
54 if (err) {
55 shell_error(ctx_shell, "Preset Read operation failed (err %d)", err);
56 return;
57 }
58
59 shell_print(ctx_shell, "Preset Index: 0x%02x\tProperties: 0x%02x\tName: %s",
60 record->index, record->properties, record->name);
61
62 if (is_last) {
63 shell_print(ctx_shell, "Preset Read operation complete");
64 }
65 }
66
67 static const struct bt_has_client_cb has_client_cb = {
68 .discover = has_client_discover_cb,
69 .preset_switch = has_client_preset_switch_cb,
70 .preset_read_rsp = has_client_preset_read_rsp_cb,
71 };
72
cmd_has_client_init(const struct shell * sh,size_t argc,char ** argv)73 static int cmd_has_client_init(const struct shell *sh, size_t argc, char **argv)
74 {
75 int err;
76
77 if (!ctx_shell) {
78 ctx_shell = sh;
79 }
80
81 err = bt_has_client_cb_register(&has_client_cb);
82 if (err != 0) {
83 shell_error(sh, "bt_has_client_cb_register (err %d)", err);
84 }
85
86 return err;
87 }
88
cmd_has_client_discover(const struct shell * sh,size_t argc,char ** argv)89 static int cmd_has_client_discover(const struct shell *sh, size_t argc, char **argv)
90 {
91 int err;
92
93 if (default_conn == NULL) {
94 shell_error(sh, "Not connected");
95 return -ENOEXEC;
96 }
97
98 if (!ctx_shell) {
99 ctx_shell = sh;
100 }
101
102 err = bt_has_client_discover(default_conn);
103 if (err != 0) {
104 shell_error(sh, "bt_has_client_discover (err %d)", err);
105 }
106
107 return err;
108 }
109
cmd_has_client_read_presets(const struct shell * sh,size_t argc,char ** argv)110 static int cmd_has_client_read_presets(const struct shell *sh, size_t argc, char **argv)
111 {
112 int err;
113 const uint8_t index = shell_strtoul(argv[1], 16, &err);
114 const uint8_t count = shell_strtoul(argv[2], 10, &err);
115
116 if (err < 0) {
117 shell_error(sh, "Invalid command parameter (err %d)", err);
118 return err;
119 }
120
121 if (default_conn == NULL) {
122 shell_error(sh, "Not connected");
123 return -ENOEXEC;
124 }
125
126 if (!inst) {
127 shell_error(sh, "No instance discovered");
128 return -ENOEXEC;
129 }
130
131 err = bt_has_client_presets_read(inst, index, count);
132 if (err != 0) {
133 shell_error(sh, "bt_has_client_discover (err %d)", err);
134 }
135
136 return err;
137 }
138
cmd_has_client_preset_set(const struct shell * sh,size_t argc,char ** argv)139 static int cmd_has_client_preset_set(const struct shell *sh, size_t argc, char **argv)
140 {
141 bool sync = false;
142 uint8_t index;
143 int err = 0;
144
145 index = shell_strtoul(argv[1], 16, &err);
146 if (err < 0) {
147 shell_error(sh, "Invalid command parameter (err %d)", err);
148 return -ENOEXEC;
149 }
150
151 for (size_t argn = 2; argn < argc; argn++) {
152 const char *arg = argv[argn];
153
154 if (!strcmp(arg, "sync")) {
155 sync = true;
156 } else {
157 shell_error(sh, "Invalid argument");
158 return -ENOEXEC;
159 }
160 }
161
162 if (default_conn == NULL) {
163 shell_error(sh, "Not connected");
164 return -ENOEXEC;
165 }
166
167 if (!inst) {
168 shell_error(sh, "No instance discovered");
169 return -ENOEXEC;
170 }
171
172 err = bt_has_client_preset_set(inst, index, sync);
173 if (err != 0) {
174 shell_error(sh, "bt_has_client_preset_switch (err %d)", err);
175 return -ENOEXEC;
176 }
177
178 return 0;
179 }
180
cmd_has_client_preset_next(const struct shell * sh,size_t argc,char ** argv)181 static int cmd_has_client_preset_next(const struct shell *sh, size_t argc, char **argv)
182 {
183 bool sync = false;
184 int err;
185
186 for (size_t argn = 1; argn < argc; argn++) {
187 const char *arg = argv[argn];
188
189 if (!strcmp(arg, "sync")) {
190 sync = true;
191 } else {
192 shell_error(sh, "Invalid argument");
193 return -ENOEXEC;
194 }
195 }
196
197 if (default_conn == NULL) {
198 shell_error(sh, "Not connected");
199 return -ENOEXEC;
200 }
201
202 if (!inst) {
203 shell_error(sh, "No instance discovered");
204 return -ENOEXEC;
205 }
206
207 err = bt_has_client_preset_next(inst, sync);
208 if (err != 0) {
209 shell_error(sh, "bt_has_client_preset_next (err %d)", err);
210 return -ENOEXEC;
211 }
212
213 return err;
214 }
215
cmd_has_client_preset_prev(const struct shell * sh,size_t argc,char ** argv)216 static int cmd_has_client_preset_prev(const struct shell *sh, size_t argc, char **argv)
217 {
218 bool sync = false;
219 int err;
220
221 for (size_t argn = 1; argn < argc; argn++) {
222 const char *arg = argv[argn];
223
224 if (!strcmp(arg, "sync")) {
225 sync = true;
226 } else {
227 shell_error(sh, "Invalid argument");
228 return -ENOEXEC;
229 }
230 }
231
232 if (default_conn == NULL) {
233 shell_error(sh, "Not connected");
234 return -ENOEXEC;
235 }
236
237 if (!inst) {
238 shell_error(sh, "No instance discovered");
239 return -ENOEXEC;
240 }
241
242 err = bt_has_client_preset_prev(inst, sync);
243 if (err != 0) {
244 shell_error(sh, "bt_has_client_preset_prev (err %d)", err);
245 return -ENOEXEC;
246 }
247
248 return err;
249 }
250
cmd_has_client(const struct shell * sh,size_t argc,char ** argv)251 static int cmd_has_client(const struct shell *sh, size_t argc, char **argv)
252 {
253 if (argc > 1) {
254 shell_error(sh, "%s unknown parameter: %s", argv[0], argv[1]);
255 } else {
256 shell_error(sh, "%s missing subcomand", argv[0]);
257 }
258
259 return -ENOEXEC;
260 }
261
262 #define HELP_NONE "[none]"
263
264 SHELL_STATIC_SUBCMD_SET_CREATE(has_client_cmds,
265 SHELL_CMD_ARG(init, NULL, HELP_NONE, cmd_has_client_init, 1, 0),
266 SHELL_CMD_ARG(discover, NULL, HELP_NONE, cmd_has_client_discover, 1, 0),
267 SHELL_CMD_ARG(presets_read, NULL, "<start_index_hex> <max_count_dec>",
268 cmd_has_client_read_presets, 3, 0),
269 SHELL_CMD_ARG(preset_set, NULL, "<index_hex> [sync]", cmd_has_client_preset_set, 2, 1),
270 SHELL_CMD_ARG(preset_next, NULL, "[sync]", cmd_has_client_preset_next, 1, 1),
271 SHELL_CMD_ARG(preset_prev, NULL, "[sync]", cmd_has_client_preset_prev, 1, 1),
272 SHELL_SUBCMD_SET_END
273 );
274
275 SHELL_CMD_ARG_REGISTER(has_client, &has_client_cmds, "Bluetooth HAS client shell commands",
276 cmd_has_client, 1, 1);
277