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