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