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 
10 #include <errno.h>
11 #include <stdbool.h>
12 #include <stddef.h>
13 #include <stdint.h>
14 #include <string.h>
15 
16 #include <zephyr/bluetooth/audio/has.h>
17 #include <zephyr/bluetooth/bluetooth.h>
18 #include <zephyr/bluetooth/conn.h>
19 #include <zephyr/kernel.h>
20 #include <zephyr/shell/shell.h>
21 #include <zephyr/shell/shell_string_conv.h>
22 
23 #include "host/shell/bt.h"
24 
preset_select(uint8_t index,bool sync)25 static int preset_select(uint8_t index, bool sync)
26 {
27 	return 0;
28 }
29 
preset_name_changed(uint8_t index,const char * name)30 static void preset_name_changed(uint8_t index, const char *name)
31 {
32 	shell_print(ctx_shell, "Preset name changed index %u name %s", index, name);
33 }
34 
35 static const struct bt_has_preset_ops preset_ops = {
36 	.select = preset_select,
37 	.name_changed = preset_name_changed,
38 };
39 
cmd_preset_reg(const struct shell * sh,size_t argc,char ** argv)40 static int cmd_preset_reg(const struct shell *sh, size_t argc, char **argv)
41 {
42 	int err = 0;
43 	struct bt_has_preset_register_param param = {
44 		.index = shell_strtoul(argv[1], 16, &err),
45 		.properties = shell_strtoul(argv[2], 16, &err),
46 		.name = argv[3],
47 		.ops = &preset_ops,
48 	};
49 
50 	if (err < 0) {
51 		shell_print(sh, "Invalid command parameter (err %d)", err);
52 		return err;
53 	}
54 
55 	err = bt_has_preset_register(&param);
56 	if (err < 0) {
57 		shell_error(sh, "Preset register failed (err %d)", err);
58 		return err;
59 	}
60 
61 	return 0;
62 }
63 
cmd_preset_unreg(const struct shell * sh,size_t argc,char ** argv)64 static int cmd_preset_unreg(const struct shell *sh, size_t argc, char **argv)
65 {
66 	int err = 0;
67 	const uint8_t index = shell_strtoul(argv[1], 16, &err);
68 
69 	if (err < 0) {
70 		shell_print(sh, "Invalid command parameter (err %d)", err);
71 		return err;
72 	}
73 
74 	err = bt_has_preset_unregister(index);
75 	if (err < 0) {
76 		shell_print(sh, "Preset unregister failed (err %d)", err);
77 		return err;
78 	}
79 
80 	return 0;
81 }
82 
cmd_features_set(const struct shell * sh,size_t argc,char ** argv)83 static int cmd_features_set(const struct shell *sh, size_t argc, char **argv)
84 {
85 	int err;
86 	struct bt_has_features_param param = {
87 		.type = BT_HAS_HEARING_AID_TYPE_MONAURAL,
88 		.preset_sync_support = false,
89 		.independent_presets = false
90 	};
91 
92 	for (size_t argn = 1; argn < argc; argn++) {
93 		const char *arg = argv[argn];
94 
95 		if (strcmp(arg, "binaural") == 0) {
96 			param.type = BT_HAS_HEARING_AID_TYPE_BINAURAL;
97 		} else if (strcmp(arg, "monaural") == 0) {
98 			param.type = BT_HAS_HEARING_AID_TYPE_MONAURAL;
99 		} else if (strcmp(arg, "banded") == 0) {
100 			param.type = BT_HAS_HEARING_AID_TYPE_BANDED;
101 		} else if (strcmp(arg, "sync") == 0) {
102 			param.preset_sync_support = true;
103 		} else if (strcmp(arg, "independent") == 0) {
104 			param.independent_presets = true;
105 		} else {
106 			shell_help(sh);
107 			return SHELL_CMD_HELP_PRINTED;
108 		}
109 	}
110 
111 	err = bt_has_features_set(&param);
112 	if (err != 0) {
113 		shell_error(sh, "Could not set features: %d", err);
114 		return err;
115 	}
116 
117 	return 0;
118 }
119 
cmd_has_register(const struct shell * sh,size_t argc,char ** argv)120 static int cmd_has_register(const struct shell *sh, size_t argc, char **argv)
121 {
122 	int err;
123 	struct bt_has_features_param param = {
124 		.type = BT_HAS_HEARING_AID_TYPE_MONAURAL,
125 		.preset_sync_support = false,
126 		.independent_presets = false
127 	};
128 
129 	for (size_t argn = 1; argn < argc; argn++) {
130 		const char *arg = argv[argn];
131 
132 		if (strcmp(arg, "binaural") == 0) {
133 			param.type = BT_HAS_HEARING_AID_TYPE_BINAURAL;
134 		} else if (strcmp(arg, "monaural") == 0) {
135 			param.type = BT_HAS_HEARING_AID_TYPE_MONAURAL;
136 		} else if (strcmp(arg, "banded") == 0) {
137 			param.type = BT_HAS_HEARING_AID_TYPE_BANDED;
138 		} else if (strcmp(arg, "sync") == 0) {
139 			param.preset_sync_support = true;
140 		} else if (strcmp(arg, "independent") == 0) {
141 			param.independent_presets = true;
142 		} else {
143 			shell_help(sh);
144 			return SHELL_CMD_HELP_PRINTED;
145 		}
146 	}
147 
148 	err = bt_has_register(&param);
149 	if (err != 0) {
150 		shell_error(sh, "Could not register HAS: %d", err);
151 		return err;
152 	}
153 
154 	return 0;
155 }
156 
157 struct print_list_entry_data {
158 	int num;
159 	const struct shell *sh;
160 };
161 
print_list_entry(uint8_t index,enum bt_has_properties properties,const char * name,void * user_data)162 static uint8_t print_list_entry(uint8_t index, enum bt_has_properties properties,
163 				const char *name, void *user_data)
164 {
165 	struct print_list_entry_data *data = user_data;
166 
167 	shell_print(data->sh, "%d: index 0x%02x prop 0x%02x name %s", ++data->num, index,
168 		    properties, name);
169 
170 	return BT_HAS_PRESET_ITER_CONTINUE;
171 }
172 
cmd_preset_list(const struct shell * sh,size_t argc,char ** argv)173 static int cmd_preset_list(const struct shell *sh, size_t argc, char **argv)
174 {
175 	struct print_list_entry_data data = {
176 		.sh = sh,
177 	};
178 
179 	bt_has_preset_foreach(0, print_list_entry, &data);
180 
181 	if (data.num == 0) {
182 		shell_print(sh, "No presets registered");
183 	}
184 
185 	return 0;
186 }
187 
cmd_preset_avail(const struct shell * sh,size_t argc,char ** argv)188 static int cmd_preset_avail(const struct shell *sh, size_t argc, char **argv)
189 {
190 	int err = 0;
191 	const uint8_t index = shell_strtoul(argv[1], 16, &err);
192 
193 	if (err < 0) {
194 		shell_print(sh, "Invalid command parameter (err %d)", err);
195 		return err;
196 	}
197 
198 	err = bt_has_preset_available(index);
199 	if (err < 0) {
200 		shell_print(sh, "Preset availability set failed (err %d)", err);
201 		return err;
202 	}
203 
204 	return 0;
205 }
206 
cmd_preset_unavail(const struct shell * sh,size_t argc,char ** argv)207 static int cmd_preset_unavail(const struct shell *sh, size_t argc, char **argv)
208 {
209 	int err = 0;
210 	const uint8_t index = shell_strtoul(argv[1], 16, &err);
211 
212 	if (err < 0) {
213 		shell_print(sh, "Invalid command parameter (err %d)", err);
214 		return err;
215 	}
216 
217 	err = bt_has_preset_unavailable(index);
218 	if (err < 0) {
219 		shell_print(sh, "Preset availability set failed (err %d)", err);
220 		return err;
221 	}
222 
223 	return 0;
224 }
225 
cmd_preset_active_set(const struct shell * sh,size_t argc,char ** argv)226 static int cmd_preset_active_set(const struct shell *sh, size_t argc, char **argv)
227 {
228 	int err = 0;
229 	const uint8_t index = shell_strtoul(argv[1], 16, &err);
230 
231 	if (err < 0) {
232 		shell_print(sh, "Invalid command parameter (err %d)", err);
233 		return err;
234 	}
235 
236 	err = bt_has_preset_active_set(index);
237 	if (err < 0) {
238 		shell_print(sh, "Preset selection failed (err %d)", err);
239 		return err;
240 	}
241 
242 	return 0;
243 }
244 
cmd_preset_active_get(const struct shell * sh,size_t argc,char ** argv)245 static int cmd_preset_active_get(const struct shell *sh, size_t argc, char **argv)
246 {
247 	const uint8_t index = bt_has_preset_active_get();
248 
249 	shell_print(sh, "Active index 0x%02x", index);
250 
251 	return 0;
252 }
253 
cmd_preset_active_clear(const struct shell * sh,size_t argc,char ** argv)254 static int cmd_preset_active_clear(const struct shell *sh, size_t argc, char **argv)
255 {
256 	int err;
257 
258 	err = bt_has_preset_active_clear();
259 	if (err < 0) {
260 		shell_print(sh, "Preset selection failed (err %d)", err);
261 		return err;
262 	}
263 
264 	return 0;
265 }
266 
cmd_preset_name_set(const struct shell * sh,size_t argc,char ** argv)267 static int cmd_preset_name_set(const struct shell *sh, size_t argc, char **argv)
268 {
269 	int err = 0;
270 	const uint8_t index = shell_strtoul(argv[1], 16, &err);
271 
272 	if (err < 0) {
273 		shell_print(sh, "Invalid command parameter (err %d)", err);
274 		return err;
275 	}
276 
277 	err = bt_has_preset_name_change(index, argv[2]);
278 	if (err < 0) {
279 		shell_print(sh, "Preset name change failed (err %d)", err);
280 		return err;
281 	}
282 
283 	return 0;
284 }
285 
cmd_has(const struct shell * sh,size_t argc,char ** argv)286 static int cmd_has(const struct shell *sh, size_t argc, char **argv)
287 {
288 	if (argc > 1) {
289 		shell_error(sh, "%s unknown parameter: %s", argv[0], argv[1]);
290 	} else {
291 		shell_error(sh, "%s missing subcomand", argv[0]);
292 	}
293 
294 	return -ENOEXEC;
295 }
296 
297 SHELL_STATIC_SUBCMD_SET_CREATE(has_cmds,
298 	SHELL_CMD_ARG(register, NULL,
299 		      "Initialize the service and register type "
300 		      "[binaural | monaural(default) | banded] [sync] [independent]",
301 		      cmd_has_register, 1, 3),
302 	SHELL_CMD_ARG(preset_reg, NULL, "Register preset <index> <properties> <name>",
303 		      cmd_preset_reg, 4, 0),
304 	SHELL_CMD_ARG(preset_unreg, NULL, "Unregister preset <index>", cmd_preset_unreg, 2, 0),
305 	SHELL_CMD_ARG(preset_list, NULL, "List all presets", cmd_preset_list, 1, 0),
306 	SHELL_CMD_ARG(preset_set_avail, NULL, "Set preset as available <index>",
307 		      cmd_preset_avail, 2, 0),
308 	SHELL_CMD_ARG(preset_set_unavail, NULL, "Set preset as unavailable <index>",
309 		      cmd_preset_unavail, 2, 0),
310 	SHELL_CMD_ARG(preset_active_set, NULL, "Set active preset <index>",
311 		      cmd_preset_active_set, 2, 0),
312 	SHELL_CMD_ARG(preset_active_get, NULL, "Get active preset", cmd_preset_active_get, 1, 0),
313 	SHELL_CMD_ARG(preset_active_clear, NULL, "Clear selected preset",
314 		      cmd_preset_active_clear, 1, 0),
315 	SHELL_CMD_ARG(set_name, NULL, "Set preset name <index> <name>", cmd_preset_name_set, 3, 0),
316 	SHELL_CMD_ARG(features_set, NULL, "Set hearing aid features "
317 		      "[binaural | monaural(default) | banded] [sync] [independent]",
318 		      cmd_features_set, 1, 3),
319 	SHELL_SUBCMD_SET_END
320 );
321 
322 SHELL_CMD_ARG_REGISTER(has, &has_cmds, "Bluetooth HAS shell commands", cmd_has, 1, 1);
323