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