1 /* Bluetooth VOCS - Volume Offset Control Service - Client */
2
3 /*
4 * Copyright (c) 2021 Nordic Semiconductor ASA
5 *
6 * SPDX-License-Identifier: Apache-2.0
7 */
8
9 #include <zephyr.h>
10 #include <zephyr/types.h>
11
12 #include <device.h>
13 #include <init.h>
14 #include <sys/check.h>
15
16 #include <bluetooth/bluetooth.h>
17 #include <bluetooth/l2cap.h>
18 #include <bluetooth/conn.h>
19 #include <bluetooth/gatt.h>
20 #include <bluetooth/audio/vocs.h>
21
22 #include "vocs_internal.h"
23
24 #define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_VOCS_CLIENT)
25 #define LOG_MODULE_NAME bt_vocs_client
26 #include "common/log.h"
27
28 static struct bt_vocs vocs_insts[CONFIG_BT_MAX_CONN * CONFIG_BT_VOCS_CLIENT_MAX_INSTANCE_COUNT];
29
lookup_vocs_by_handle(struct bt_conn * conn,uint16_t handle)30 static struct bt_vocs *lookup_vocs_by_handle(struct bt_conn *conn, uint16_t handle)
31 {
32 __ASSERT(handle != 0, "Handle cannot be 0");
33 __ASSERT(conn, "Conn cannot be NULL");
34
35 for (int i = 0; i < ARRAY_SIZE(vocs_insts); i++) {
36 if (vocs_insts[i].cli.conn == conn &&
37 vocs_insts[i].cli.active &&
38 vocs_insts[i].cli.start_handle <= handle &&
39 vocs_insts[i].cli.end_handle >= handle) {
40 return &vocs_insts[i];
41 }
42 }
43
44 BT_DBG("Could not find VOCS instance with handle 0x%04x", handle);
45 return NULL;
46 }
47
vocs_client_notify_handler(struct bt_conn * conn,struct bt_gatt_subscribe_params * params,const void * data,uint16_t length)48 uint8_t vocs_client_notify_handler(struct bt_conn *conn, struct bt_gatt_subscribe_params *params,
49 const void *data, uint16_t length)
50 {
51 uint16_t handle = params->value_handle;
52 struct bt_vocs *inst = lookup_vocs_by_handle(conn, handle);
53
54 if (!inst) {
55 BT_DBG("Instance not found");
56 return BT_GATT_ITER_STOP;
57 }
58
59 if (!data || !length) {
60 return BT_GATT_ITER_CONTINUE;
61 }
62
63 if (handle == inst->cli.state_handle) {
64 if (length == sizeof(inst->cli.state)) {
65 memcpy(&inst->cli.state, data, length);
66 BT_DBG("Inst %p: Offset %d, counter %u", inst, inst->cli.state.offset,
67 inst->cli.state.change_counter);
68 if (inst->cli.cb && inst->cli.cb->state) {
69 inst->cli.cb->state(inst, 0, inst->cli.state.offset);
70 }
71 } else {
72 BT_DBG("Invalid state length %u", length);
73 }
74 } else if (handle == inst->cli.desc_handle) {
75 char desc[MIN(BT_L2CAP_RX_MTU, BT_ATT_MAX_ATTRIBUTE_LEN) + 1];
76
77 /* Truncate if too large */
78
79 if (length > sizeof(desc) - 1) {
80 BT_DBG("Description truncated from %u to %zu octets",
81 length, sizeof(desc) - 1);
82 }
83 length = MIN(sizeof(desc) - 1, length);
84
85 memcpy(desc, data, length);
86 desc[length] = '\0';
87 BT_DBG("Inst %p: Output description: %s", inst, log_strdup(desc));
88 if (inst->cli.cb && inst->cli.cb->description) {
89 inst->cli.cb->description(inst, 0, desc);
90 }
91 } else if (handle == inst->cli.location_handle) {
92 if (length == sizeof(inst->cli.location)) {
93 memcpy(&inst->cli.location, data, length);
94 BT_DBG("Inst %p: Location %u", inst, inst->cli.location);
95 if (inst->cli.cb && inst->cli.cb->location) {
96 inst->cli.cb->location(inst, 0, inst->cli.location);
97 }
98 } else {
99 BT_DBG("Invalid location length %u", length);
100 }
101 }
102
103 return BT_GATT_ITER_CONTINUE;
104 }
105
vocs_client_read_offset_state_cb(struct bt_conn * conn,uint8_t err,struct bt_gatt_read_params * params,const void * data,uint16_t length)106 static uint8_t vocs_client_read_offset_state_cb(struct bt_conn *conn, uint8_t err,
107 struct bt_gatt_read_params *params,
108 const void *data, uint16_t length)
109 {
110 int cb_err = err;
111 struct bt_vocs *inst = lookup_vocs_by_handle(conn, params->single.handle);
112
113 memset(params, 0, sizeof(*params));
114
115 if (!inst) {
116 BT_DBG("Instance not found");
117 return BT_GATT_ITER_STOP;
118 }
119
120 BT_DBG("Inst %p: err: 0x%02X", inst, err);
121 inst->cli.busy = false;
122
123 if (cb_err) {
124 BT_DBG("Offset state read failed: %d", err);
125 } else if (data) {
126 if (length == sizeof(inst->cli.state)) {
127 memcpy(&inst->cli.state, data, length);
128 BT_DBG("Offset %d, counter %u",
129 inst->cli.state.offset, inst->cli.state.change_counter);
130 } else {
131 BT_DBG("Invalid length %u (expected %zu)", length, sizeof(inst->cli.state));
132 cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
133 }
134 } else {
135 BT_DBG("Invalid state");
136 cb_err = BT_ATT_ERR_UNLIKELY;
137 }
138
139 if (inst->cli.cb && inst->cli.cb->state) {
140 inst->cli.cb->state(inst, cb_err,
141 cb_err ? 0 : inst->cli.state.offset);
142 }
143
144 return BT_GATT_ITER_STOP;
145 }
146
vocs_client_read_location_cb(struct bt_conn * conn,uint8_t err,struct bt_gatt_read_params * params,const void * data,uint16_t length)147 static uint8_t vocs_client_read_location_cb(struct bt_conn *conn, uint8_t err,
148 struct bt_gatt_read_params *params,
149 const void *data, uint16_t length)
150 {
151 int cb_err = err;
152 struct bt_vocs *inst = lookup_vocs_by_handle(conn, params->single.handle);
153
154 memset(params, 0, sizeof(*params));
155
156 if (!inst) {
157 BT_DBG("Instance not found");
158 return BT_GATT_ITER_STOP;
159 }
160
161 BT_DBG("Inst %p: err: 0x%02X", inst, err);
162 inst->cli.busy = false;
163
164 if (cb_err) {
165 BT_DBG("Offset state read failed: %d", err);
166 } else if (data) {
167 if (length == sizeof(inst->cli.location)) {
168 memcpy(&inst->cli.location, data, length);
169 BT_DBG("Location %u", inst->cli.location);
170 } else {
171 BT_DBG("Invalid length %u (expected %zu)",
172 length, sizeof(inst->cli.location));
173 cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
174 }
175 } else {
176 BT_DBG("Invalid location");
177 cb_err = BT_ATT_ERR_UNLIKELY;
178 }
179
180 if (inst->cli.cb && inst->cli.cb->location) {
181 inst->cli.cb->location(inst, cb_err,
182 cb_err ? 0 : inst->cli.location);
183 }
184
185 return BT_GATT_ITER_STOP;
186 }
187
internal_read_volume_offset_state_cb(struct bt_conn * conn,uint8_t err,struct bt_gatt_read_params * params,const void * data,uint16_t length)188 static uint8_t internal_read_volume_offset_state_cb(struct bt_conn *conn, uint8_t err,
189 struct bt_gatt_read_params *params,
190 const void *data, uint16_t length)
191 {
192 int cb_err = err;
193 struct bt_vocs *inst = lookup_vocs_by_handle(conn, params->single.handle);
194
195 memset(params, 0, sizeof(*params));
196
197 if (!inst) {
198 BT_ERR("Instance not found");
199 return BT_GATT_ITER_STOP;
200 }
201
202 if (err) {
203 BT_WARN("Volume offset state read failed: %d", err);
204 cb_err = BT_ATT_ERR_UNLIKELY;
205 } else if (data) {
206 if (length == sizeof(inst->cli.state)) {
207 int write_err;
208
209 memcpy(&inst->cli.state, data, length);
210 BT_DBG("Offset %d, counter %u",
211 inst->cli.state.offset,
212 inst->cli.state.change_counter);
213
214 /* clear busy flag to reuse function */
215 inst->cli.busy = false;
216 write_err = bt_vocs_client_state_set(inst, inst->cli.cp.offset);
217 if (write_err) {
218 cb_err = BT_ATT_ERR_UNLIKELY;
219 }
220 } else {
221 BT_DBG("Invalid length %u (expected %zu)", length, sizeof(inst->cli.state));
222 cb_err = BT_ATT_ERR_UNLIKELY;
223 }
224 } else {
225 BT_DBG("Invalid (empty) offset state read");
226 cb_err = BT_ATT_ERR_UNLIKELY;
227 }
228
229 if (cb_err) {
230 inst->cli.busy = false;
231
232 if (inst->cli.cb && inst->cli.cb->set_offset) {
233 inst->cli.cb->set_offset(inst, err);
234 }
235 }
236
237 return BT_GATT_ITER_STOP;
238 }
239
vcs_client_write_vocs_cp_cb(struct bt_conn * conn,uint8_t err,struct bt_gatt_write_params * params)240 static void vcs_client_write_vocs_cp_cb(struct bt_conn *conn, uint8_t err,
241 struct bt_gatt_write_params *params)
242 {
243 int cb_err = err;
244 struct bt_vocs *inst = lookup_vocs_by_handle(conn, params->handle);
245
246 memset(params, 0, sizeof(*params));
247
248 if (!inst) {
249 BT_DBG("Instance not found");
250 return;
251 }
252
253 BT_DBG("Inst %p: err: 0x%02X", inst, err);
254
255 /* If the change counter is out of data when a write was attempted from the application,
256 * we automatically initiate a read to get the newest state and try again. Once the
257 * change counter has been read, we restart the applications write request. If it fails
258 * the second time, we return an error to the application.
259 */
260 if (cb_err == BT_VOCS_ERR_INVALID_COUNTER && inst->cli.cp_retried) {
261 cb_err = BT_ATT_ERR_UNLIKELY;
262 } else if (cb_err == BT_VOCS_ERR_INVALID_COUNTER && inst->cli.state_handle) {
263 BT_DBG("Invalid change counter. Reading volume offset state from server.");
264
265 inst->cli.read_params.func = internal_read_volume_offset_state_cb;
266 inst->cli.read_params.handle_count = 1;
267 inst->cli.read_params.single.handle = inst->cli.state_handle;
268
269 cb_err = bt_gatt_read(conn, &inst->cli.read_params);
270 if (cb_err) {
271 BT_WARN("Could not read Volume offset state: %d", cb_err);
272 } else {
273 inst->cli.cp_retried = true;
274 /* Wait for read callback */
275 return;
276 }
277 }
278
279 inst->cli.busy = false;
280 inst->cli.cp_retried = false;
281
282 if (inst->cli.cb && inst->cli.cb->set_offset) {
283 inst->cli.cb->set_offset(inst, cb_err);
284 }
285 }
286
vcs_client_read_output_desc_cb(struct bt_conn * conn,uint8_t err,struct bt_gatt_read_params * params,const void * data,uint16_t length)287 static uint8_t vcs_client_read_output_desc_cb(struct bt_conn *conn, uint8_t err,
288 struct bt_gatt_read_params *params,
289 const void *data, uint16_t length)
290 {
291 int cb_err = err;
292 struct bt_vocs *inst = lookup_vocs_by_handle(conn, params->single.handle);
293 char desc[MIN(BT_L2CAP_RX_MTU, BT_ATT_MAX_ATTRIBUTE_LEN) + 1];
294
295 memset(params, 0, sizeof(*params));
296
297 if (!inst) {
298 BT_DBG("Instance not found");
299 return BT_GATT_ITER_STOP;
300 }
301
302 BT_DBG("Inst %p: err: 0x%02X", inst, err);
303 inst->cli.busy = false;
304
305 if (cb_err) {
306 BT_DBG("Description read failed: %d", err);
307 } else {
308 if (data) {
309 BT_HEXDUMP_DBG(data, length, "Output description read");
310
311 if (length > sizeof(desc) - 1) {
312 BT_DBG("Description truncated from %u to %zu octets",
313 length, sizeof(desc) - 1);
314 }
315 length = MIN(sizeof(desc) - 1, length);
316
317 /* TODO: Handle long reads */
318 memcpy(desc, data, length);
319 }
320 desc[length] = '\0';
321 BT_DBG("Output description: %s", log_strdup(desc));
322 }
323
324 if (inst->cli.cb && inst->cli.cb->description) {
325 inst->cli.cb->description(inst, cb_err, cb_err ? NULL : desc);
326 }
327
328 return BT_GATT_ITER_STOP;
329 }
330
valid_inst_discovered(struct bt_vocs * inst)331 static bool valid_inst_discovered(struct bt_vocs *inst)
332 {
333 return inst->cli.state_handle &&
334 inst->cli.control_handle &&
335 inst->cli.location_handle &&
336 inst->cli.desc_handle;
337 }
338
vocs_discover_func(struct bt_conn * conn,const struct bt_gatt_attr * attr,struct bt_gatt_discover_params * params)339 static uint8_t vocs_discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr,
340 struct bt_gatt_discover_params *params)
341 {
342 struct bt_vocs_client *client_inst = CONTAINER_OF(params,
343 struct bt_vocs_client,
344 discover_params);
345 struct bt_vocs *inst = CONTAINER_OF(client_inst, struct bt_vocs, cli);
346
347 if (!attr) {
348 BT_DBG("Discovery complete for VOCS %p", inst);
349 inst->cli.busy = false;
350 (void)memset(params, 0, sizeof(*params));
351
352 if (inst->cli.cb && inst->cli.cb->discover) {
353 int err = valid_inst_discovered(inst) ? 0 : -ENOENT;
354
355 inst->cli.cb->discover(inst, err);
356 }
357
358 return BT_GATT_ITER_STOP;
359 }
360
361 BT_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle);
362
363 if (params->type == BT_GATT_DISCOVER_CHARACTERISTIC) {
364 struct bt_gatt_subscribe_params *sub_params = NULL;
365 struct bt_gatt_chrc *chrc;
366
367 chrc = (struct bt_gatt_chrc *)attr->user_data;
368 if (inst->cli.start_handle == 0) {
369 inst->cli.start_handle = chrc->value_handle;
370 }
371 inst->cli.end_handle = chrc->value_handle;
372
373 if (!bt_uuid_cmp(chrc->uuid, BT_UUID_VOCS_STATE)) {
374 BT_DBG("Volume offset state");
375 inst->cli.state_handle = chrc->value_handle;
376 sub_params = &inst->cli.state_sub_params;
377 } else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_VOCS_LOCATION)) {
378 BT_DBG("Location");
379 inst->cli.location_handle = chrc->value_handle;
380 if (chrc->properties & BT_GATT_CHRC_NOTIFY) {
381 sub_params = &inst->cli.location_sub_params;
382 }
383 if (chrc->properties & BT_GATT_CHRC_WRITE_WITHOUT_RESP) {
384 inst->cli.location_writable = true;
385 }
386 } else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_VOCS_CONTROL)) {
387 BT_DBG("Control point");
388 inst->cli.control_handle = chrc->value_handle;
389 } else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_VOCS_DESCRIPTION)) {
390 BT_DBG("Description");
391 inst->cli.desc_handle = chrc->value_handle;
392 if (chrc->properties & BT_GATT_CHRC_NOTIFY) {
393 sub_params = &inst->cli.desc_sub_params;
394 }
395 if (chrc->properties & BT_GATT_CHRC_WRITE_WITHOUT_RESP) {
396 inst->cli.desc_writable = true;
397 }
398 }
399
400 if (sub_params) {
401 int err;
402
403 sub_params->value = BT_GATT_CCC_NOTIFY;
404 sub_params->value_handle = chrc->value_handle;
405 /*
406 * TODO: Don't assume that CCC is at handle + 2;
407 * do proper discovery;
408 */
409 sub_params->ccc_handle = attr->handle + 2;
410 sub_params->notify = vocs_client_notify_handler;
411 err = bt_gatt_subscribe(conn, sub_params);
412 if (err) {
413 BT_WARN("Could not subscribe to handle %u",
414 sub_params->ccc_handle);
415 }
416 }
417 }
418
419 return BT_GATT_ITER_CONTINUE;
420 }
421
bt_vocs_client_state_get(struct bt_vocs * inst)422 int bt_vocs_client_state_get(struct bt_vocs *inst)
423 {
424 int err;
425
426 CHECKIF(!inst) {
427 BT_DBG("NULL instance");
428 return -EINVAL;
429 }
430
431 CHECKIF(inst->cli.conn == NULL) {
432 BT_DBG("NULL conn");
433 return -EINVAL;
434 }
435
436 if (!inst->cli.state_handle) {
437 BT_DBG("Handle not set");
438 return -EINVAL;
439 }
440
441 if (inst->cli.busy) {
442 BT_DBG("Handle not set");
443 return -EBUSY;
444 }
445
446 inst->cli.read_params.func = vocs_client_read_offset_state_cb;
447 inst->cli.read_params.handle_count = 1;
448 inst->cli.read_params.single.handle = inst->cli.state_handle;
449 inst->cli.read_params.single.offset = 0U;
450
451 err = bt_gatt_read(inst->cli.conn, &inst->cli.read_params);
452 if (!err) {
453 inst->cli.busy = true;
454 }
455
456 return err;
457 }
458
bt_vocs_client_location_set(struct bt_vocs * inst,uint32_t location)459 int bt_vocs_client_location_set(struct bt_vocs *inst, uint32_t location)
460 {
461
462 CHECKIF(!inst) {
463 BT_DBG("NULL instance");
464 return -EINVAL;
465 }
466
467 CHECKIF(inst->cli.conn == NULL) {
468 BT_DBG("NULL conn");
469 return -EINVAL;
470 }
471
472 if (!inst->cli.location_handle) {
473 BT_DBG("Handle not set");
474 return -EINVAL;
475 } else if (inst->cli.busy) {
476 return -EBUSY;
477 } else if (!inst->cli.location_writable) {
478 BT_DBG("Location is not writable on peer service instance");
479 return -EPERM;
480 }
481
482 return bt_gatt_write_without_response(inst->cli.conn,
483 inst->cli.location_handle,
484 &location, sizeof(location),
485 false);
486 }
487
bt_vocs_client_location_get(struct bt_vocs * inst)488 int bt_vocs_client_location_get(struct bt_vocs *inst)
489 {
490 int err;
491
492 CHECKIF(!inst) {
493 BT_DBG("NULL instance");
494 return -EINVAL;
495 }
496
497 CHECKIF(inst->cli.conn == NULL) {
498 BT_DBG("NULL conn");
499 return -EINVAL;
500 }
501
502 if (!inst->cli.location_handle) {
503 BT_DBG("Handle not set");
504 return -EINVAL;
505 } else if (inst->cli.busy) {
506 return -EBUSY;
507 }
508
509 inst->cli.read_params.func = vocs_client_read_location_cb;
510 inst->cli.read_params.handle_count = 1;
511 inst->cli.read_params.single.handle = inst->cli.location_handle;
512 inst->cli.read_params.single.offset = 0U;
513
514 err = bt_gatt_read(inst->cli.conn, &inst->cli.read_params);
515 if (!err) {
516 inst->cli.busy = true;
517 }
518
519 return err;
520 }
521
bt_vocs_client_state_set(struct bt_vocs * inst,int16_t offset)522 int bt_vocs_client_state_set(struct bt_vocs *inst, int16_t offset)
523 {
524 int err;
525
526 CHECKIF(!inst) {
527 BT_DBG("NULL instance");
528 return -EINVAL;
529 }
530
531 CHECKIF(inst->cli.conn == NULL) {
532 BT_DBG("NULL conn");
533 return -EINVAL;
534 }
535
536 if (!inst->cli.control_handle) {
537 BT_DBG("Handle not set");
538 return -EINVAL;
539 } else if (inst->cli.busy) {
540 return -EBUSY;
541 }
542
543 inst->cli.cp.opcode = BT_VOCS_OPCODE_SET_OFFSET;
544 inst->cli.cp.counter = inst->cli.state.change_counter;
545 inst->cli.cp.offset = offset;
546
547 inst->cli.write_params.offset = 0;
548 inst->cli.write_params.data = &inst->cli.cp;
549 inst->cli.write_params.length = sizeof(inst->cli.cp);
550 inst->cli.write_params.handle = inst->cli.control_handle;
551 inst->cli.write_params.func = vcs_client_write_vocs_cp_cb;
552
553 err = bt_gatt_write(inst->cli.conn, &inst->cli.write_params);
554 if (!err) {
555 inst->cli.busy = true;
556 }
557
558 return err;
559 }
560
bt_vocs_client_description_get(struct bt_vocs * inst)561 int bt_vocs_client_description_get(struct bt_vocs *inst)
562 {
563 int err;
564
565 CHECKIF(!inst) {
566 BT_DBG("NULL instance");
567 return -EINVAL;
568 }
569
570 CHECKIF(inst->cli.conn == NULL) {
571 BT_DBG("NULL conn");
572 return -EINVAL;
573 }
574
575 if (!inst->cli.desc_handle) {
576 BT_DBG("Handle not set");
577 return -EINVAL;
578 } else if (inst->cli.busy) {
579 return -EBUSY;
580 }
581
582 inst->cli.read_params.func = vcs_client_read_output_desc_cb;
583 inst->cli.read_params.handle_count = 1;
584 inst->cli.read_params.single.handle = inst->cli.desc_handle;
585 inst->cli.read_params.single.offset = 0U;
586
587 err = bt_gatt_read(inst->cli.conn, &inst->cli.read_params);
588 if (!err) {
589 inst->cli.busy = true;
590 }
591
592 return err;
593 }
594
bt_vocs_client_description_set(struct bt_vocs * inst,const char * description)595 int bt_vocs_client_description_set(struct bt_vocs *inst,
596 const char *description)
597 {
598 CHECKIF(!inst) {
599 BT_DBG("NULL instance");
600 return -EINVAL;
601 }
602
603 CHECKIF(inst->cli.conn == NULL) {
604 BT_DBG("NULL conn");
605 return -EINVAL;
606 }
607
608 if (!inst->cli.desc_handle) {
609 BT_DBG("Handle not set");
610 return -EINVAL;
611 } else if (inst->cli.busy) {
612 return -EBUSY;
613 } else if (!inst->cli.desc_writable) {
614 BT_DBG("Description is not writable on peer service instance");
615 return -EPERM;
616 }
617
618 return bt_gatt_write_without_response(inst->cli.conn,
619 inst->cli.desc_handle,
620 description,
621 strlen(description), false);
622 }
623
bt_vocs_client_free_instance_get(void)624 struct bt_vocs *bt_vocs_client_free_instance_get(void)
625 {
626 for (int i = 0; i < ARRAY_SIZE(vocs_insts); i++) {
627 if (!vocs_insts[i].cli.active) {
628 vocs_insts[i].client_instance = true;
629 vocs_insts[i].cli.active = true;
630 return &vocs_insts[i];
631 }
632 }
633
634 return NULL;
635 }
636
bt_vocs_client_conn_get(const struct bt_vocs * vocs,struct bt_conn ** conn)637 int bt_vocs_client_conn_get(const struct bt_vocs *vocs, struct bt_conn **conn)
638 {
639 CHECKIF(vocs == NULL) {
640 BT_DBG("NULL vocs pointer");
641 return -EINVAL;
642 }
643
644 if (!vocs->client_instance) {
645 BT_DBG("vocs pointer shall be client instance");
646 return -EINVAL;
647 }
648
649 if (vocs->cli.conn == NULL) {
650 BT_DBG("vocs pointer not associated with a connection. "
651 "Do discovery first");
652 return -ENOTCONN;
653 }
654
655 *conn = vocs->cli.conn;
656 return 0;
657 }
658
vocs_client_reset(struct bt_vocs * inst,struct bt_conn * conn)659 static void vocs_client_reset(struct bt_vocs *inst, struct bt_conn *conn)
660 {
661 memset(&inst->cli.state, 0, sizeof(inst->cli.state));
662 inst->cli.location_writable = 0;
663 inst->cli.location = 0;
664 inst->cli.desc_writable = 0;
665 inst->cli.start_handle = 0;
666 inst->cli.end_handle = 0;
667 inst->cli.state_handle = 0;
668 inst->cli.location_handle = 0;
669 inst->cli.control_handle = 0;
670 inst->cli.desc_handle = 0;
671
672 /* It's okay if these fail */
673 (void)bt_gatt_unsubscribe(conn, &inst->cli.state_sub_params);
674 (void)bt_gatt_unsubscribe(conn, &inst->cli.location_sub_params);
675 (void)bt_gatt_unsubscribe(conn, &inst->cli.desc_sub_params);
676 }
677
bt_vocs_discover(struct bt_conn * conn,struct bt_vocs * inst,const struct bt_vocs_discover_param * param)678 int bt_vocs_discover(struct bt_conn *conn, struct bt_vocs *inst,
679 const struct bt_vocs_discover_param *param)
680 {
681 int err = 0;
682
683 CHECKIF(!inst || !conn || !param) {
684 BT_DBG("%s cannot be NULL",
685 inst == NULL ? "inst" : conn == NULL ? "conn" : "param");
686 return -EINVAL;
687 }
688
689 CHECKIF(param->end_handle < param->start_handle) {
690 BT_DBG("start_handle (%u) shall be less than end_handle (%u)",
691 param->start_handle, param->end_handle);
692 return -EINVAL;
693 }
694
695 CHECKIF(!inst->cli.active) {
696 BT_DBG("Inactive instance");
697 return -EINVAL;
698 }
699
700 if (inst->cli.busy) {
701 BT_DBG("Instance is busy");
702 return -EBUSY;
703 }
704
705 vocs_client_reset(inst, conn);
706
707 inst->cli.conn = conn;
708 inst->cli.discover_params.start_handle = param->start_handle;
709 inst->cli.discover_params.end_handle = param->end_handle;
710 inst->cli.discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
711 inst->cli.discover_params.func = vocs_discover_func;
712
713 err = bt_gatt_discover(conn, &inst->cli.discover_params);
714 if (err) {
715 BT_DBG("Discover failed (err %d)", err);
716 } else {
717 inst->cli.busy = true;
718 }
719
720 return err;
721 }
722
bt_vocs_client_cb_register(struct bt_vocs * inst,struct bt_vocs_cb * cb)723 void bt_vocs_client_cb_register(struct bt_vocs *inst, struct bt_vocs_cb *cb)
724 {
725 CHECKIF(!inst) {
726 BT_DBG("inst cannot be NULL");
727 return;
728 }
729
730 inst->cli.cb = cb;
731 }
732