1 /* Bluetooth VCP */
2
3 /*
4 * Copyright (c) 2018 Intel Corporation
5 * Copyright (c) 2019-2020 Bose Corporation
6 * Copyright (c) 2020-2022 Nordic Semiconductor ASA
7 *
8 * SPDX-License-Identifier: Apache-2.0
9 */
10
11 #include <errno.h>
12 #include <stdbool.h>
13 #include <stddef.h>
14 #include <stdint.h>
15 #include <sys/types.h>
16
17 #include <zephyr/autoconf.h>
18 #include <zephyr/bluetooth/att.h>
19 #include <zephyr/bluetooth/audio/aics.h>
20 #include <zephyr/bluetooth/audio/vcp.h>
21 #include <zephyr/bluetooth/audio/vocs.h>
22 #include <zephyr/bluetooth/bluetooth.h>
23 #include <zephyr/bluetooth/conn.h>
24 #include <zephyr/bluetooth/gatt.h>
25 #include <zephyr/bluetooth/uuid.h>
26 #include <zephyr/device.h>
27 #include <zephyr/init.h>
28 #include <zephyr/kernel.h>
29 #include <zephyr/logging/log.h>
30 #include <zephyr/sys/__assert.h>
31 #include <zephyr/sys/atomic.h>
32 #include <zephyr/sys/byteorder.h>
33 #include <zephyr/sys/check.h>
34 #include <zephyr/sys/time_units.h>
35 #include <zephyr/sys/util.h>
36 #include <zephyr/sys/util_macro.h>
37 #include <zephyr/sys_clock.h>
38
39 #include "audio_internal.h"
40 #include "vcp_internal.h"
41
42 #define LOG_LEVEL CONFIG_BT_VCP_VOL_REND_LOG_LEVEL
43
44 LOG_MODULE_REGISTER(bt_vcp_vol_rend);
45
46 #define VOLUME_DOWN(current_vol) \
47 ((uint8_t)MAX(0, (int)current_vol - vol_rend.volume_step))
48 #define VOLUME_UP(current_vol) \
49 ((uint8_t)MIN(UINT8_MAX, (int)current_vol + vol_rend.volume_step))
50
51 #define VALID_VCP_OPCODE(opcode) ((opcode) <= BT_VCP_OPCODE_MUTE)
52
53 enum vol_rend_notify {
54 NOTIFY_STATE,
55 NOTIFY_FLAGS,
56 NOTIFY_NUM,
57 };
58
59 struct bt_vcp_vol_rend {
60 struct vcs_state state;
61 uint8_t flags;
62 struct bt_vcp_vol_rend_cb *cb;
63 uint8_t volume_step;
64
65 struct bt_gatt_service *service_p;
66 struct bt_vocs *vocs_insts[CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT];
67 struct bt_aics *aics_insts[CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT];
68
69 ATOMIC_DEFINE(notify, NOTIFY_NUM);
70 struct k_work_delayable notify_work;
71 };
72
73 static struct bt_vcp_vol_rend vol_rend;
74
volume_state_cfg_changed(const struct bt_gatt_attr * attr,uint16_t value)75 static void volume_state_cfg_changed(const struct bt_gatt_attr *attr,
76 uint16_t value)
77 {
78 LOG_DBG("value 0x%04x", value);
79 }
80
read_vol_state(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)81 static ssize_t read_vol_state(struct bt_conn *conn,
82 const struct bt_gatt_attr *attr, void *buf,
83 uint16_t len, uint16_t offset)
84 {
85 LOG_DBG("Volume %u, mute %u, counter %u",
86 vol_rend.state.volume, vol_rend.state.mute,
87 vol_rend.state.change_counter);
88
89 return bt_gatt_attr_read(conn, attr, buf, len, offset,
90 &vol_rend.state, sizeof(vol_rend.state));
91 }
92
vol_rend_notify_str(enum vol_rend_notify notify)93 static const char *vol_rend_notify_str(enum vol_rend_notify notify)
94 {
95 switch (notify) {
96 case NOTIFY_STATE:
97 return "state";
98 case NOTIFY_FLAGS:
99 return "flags";
100 default:
101 return "unknown";
102 }
103 }
104
notify_work_reschedule(struct bt_vcp_vol_rend * inst,enum vol_rend_notify notify,k_timeout_t delay)105 static void notify_work_reschedule(struct bt_vcp_vol_rend *inst, enum vol_rend_notify notify,
106 k_timeout_t delay)
107 {
108 int err;
109
110 atomic_set_bit(inst->notify, notify);
111
112 err = k_work_reschedule(&inst->notify_work, delay);
113 if (err < 0) {
114 LOG_ERR("Failed to reschedule %s notification err %d", vol_rend_notify_str(notify),
115 err);
116 } else if (!K_TIMEOUT_EQ(delay, K_NO_WAIT)) {
117 LOG_DBG("%s notification scheduled in %dms", vol_rend_notify_str(notify),
118 k_ticks_to_ms_floor32(k_work_delayable_remaining_get(&inst->notify_work)));
119 }
120 }
121
notify(struct bt_vcp_vol_rend * inst,enum vol_rend_notify notify,const struct bt_uuid * uuid,const void * data,uint16_t len)122 static void notify(struct bt_vcp_vol_rend *inst, enum vol_rend_notify notify,
123 const struct bt_uuid *uuid, const void *data, uint16_t len)
124 {
125 int err;
126
127 err = bt_gatt_notify_uuid(NULL, uuid, inst->service_p->attrs, data, len);
128 if (err == -ENOMEM) {
129 notify_work_reschedule(inst, notify, K_USEC(BT_AUDIO_NOTIFY_RETRY_DELAY_US));
130 } else if (err < 0 && err != -ENOTCONN) {
131 LOG_ERR("Notify %s err %d", vol_rend_notify_str(notify), err);
132 }
133 }
134
notify_work_handler(struct k_work * work)135 static void notify_work_handler(struct k_work *work)
136 {
137 struct k_work_delayable *d_work = k_work_delayable_from_work(work);
138 struct bt_vcp_vol_rend *inst = CONTAINER_OF(d_work, struct bt_vcp_vol_rend, notify_work);
139
140 if (atomic_test_and_clear_bit(inst->notify, NOTIFY_STATE)) {
141 notify(inst, NOTIFY_STATE, BT_UUID_VCS_STATE, &inst->state, sizeof(inst->state));
142 }
143
144 if (IS_ENABLED(CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE) &&
145 atomic_test_and_clear_bit(inst->notify, NOTIFY_FLAGS)) {
146 notify(inst, NOTIFY_FLAGS, BT_UUID_VCS_FLAGS, &inst->flags, sizeof(inst->flags));
147 }
148 }
149
value_changed(struct bt_vcp_vol_rend * inst,enum vol_rend_notify notify)150 static void value_changed(struct bt_vcp_vol_rend *inst, enum vol_rend_notify notify)
151 {
152 notify_work_reschedule(inst, notify, K_NO_WAIT);
153 }
154
write_vcs_control(struct bt_conn * conn,const struct bt_gatt_attr * attr,const void * buf,uint16_t len,uint16_t offset,uint8_t flags)155 static ssize_t write_vcs_control(struct bt_conn *conn,
156 const struct bt_gatt_attr *attr,
157 const void *buf, uint16_t len, uint16_t offset,
158 uint8_t flags)
159 {
160 const struct vcs_control_vol *cp_val = buf;
161 bool notify = false;
162 bool volume_change = false;
163 uint8_t opcode;
164
165 if (offset > 0) {
166 return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
167 }
168
169 if (len == 0 || buf == NULL) {
170 return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
171 }
172
173 /* Check opcode before length */
174 if (!VALID_VCP_OPCODE(cp_val->cp.opcode)) {
175 LOG_DBG("Invalid opcode %u", cp_val->cp.opcode);
176 return BT_GATT_ERR(BT_VCP_ERR_OP_NOT_SUPPORTED);
177 }
178
179 if ((len < sizeof(struct vcs_control)) ||
180 (len == sizeof(struct vcs_control_vol) &&
181 cp_val->cp.opcode != BT_VCP_OPCODE_SET_ABS_VOL) ||
182 (len > sizeof(struct vcs_control_vol))) {
183 return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
184 }
185
186 opcode = cp_val->cp.opcode;
187
188 LOG_DBG("Opcode %u, counter %u", opcode, cp_val->cp.counter);
189
190 if (cp_val->cp.counter != vol_rend.state.change_counter) {
191 return BT_GATT_ERR(BT_VCP_ERR_INVALID_COUNTER);
192 }
193
194 switch (opcode) {
195 case BT_VCP_OPCODE_REL_VOL_DOWN:
196 LOG_DBG("Relative Volume Down (0x%x)", opcode);
197 if (vol_rend.state.volume > 0) {
198 vol_rend.state.volume = VOLUME_DOWN(vol_rend.state.volume);
199 notify = true;
200 }
201 volume_change = true;
202 break;
203 case BT_VCP_OPCODE_REL_VOL_UP:
204 LOG_DBG("Relative Volume Up (0x%x)", opcode);
205 if (vol_rend.state.volume != UINT8_MAX) {
206 vol_rend.state.volume = VOLUME_UP(vol_rend.state.volume);
207 notify = true;
208 }
209 volume_change = true;
210 break;
211 case BT_VCP_OPCODE_UNMUTE_REL_VOL_DOWN:
212 LOG_DBG("(Unmute) relative Volume Down (0x%x)", opcode);
213 if (vol_rend.state.volume > 0) {
214 vol_rend.state.volume = VOLUME_DOWN(vol_rend.state.volume);
215 notify = true;
216 }
217 if (vol_rend.state.mute) {
218 vol_rend.state.mute = BT_VCP_STATE_UNMUTED;
219 notify = true;
220 }
221 volume_change = true;
222 break;
223 case BT_VCP_OPCODE_UNMUTE_REL_VOL_UP:
224 LOG_DBG("(Unmute) relative Volume Up (0x%x)", opcode);
225 if (vol_rend.state.volume != UINT8_MAX) {
226 vol_rend.state.volume = VOLUME_UP(vol_rend.state.volume);
227 notify = true;
228 }
229 if (vol_rend.state.mute) {
230 vol_rend.state.mute = BT_VCP_STATE_UNMUTED;
231 notify = true;
232 }
233 volume_change = true;
234 break;
235 case BT_VCP_OPCODE_SET_ABS_VOL:
236 LOG_DBG("Set Absolute Volume (0x%x): Current volume %u",
237 opcode, vol_rend.state.volume);
238 if (vol_rend.state.volume != cp_val->volume) {
239 vol_rend.state.volume = cp_val->volume;
240 notify = true;
241 }
242 volume_change = true;
243 break;
244 case BT_VCP_OPCODE_UNMUTE:
245 LOG_DBG("Unmute (0x%x)", opcode);
246 if (vol_rend.state.mute) {
247 vol_rend.state.mute = BT_VCP_STATE_UNMUTED;
248 notify = true;
249 }
250 break;
251 case BT_VCP_OPCODE_MUTE:
252 LOG_DBG("Mute (0x%x)", opcode);
253 if (vol_rend.state.mute == BT_VCP_STATE_UNMUTED) {
254 vol_rend.state.mute = BT_VCP_STATE_MUTED;
255 notify = true;
256 }
257 break;
258 default:
259 LOG_DBG("Unknown opcode (0x%x)", opcode);
260 return BT_GATT_ERR(BT_VCP_ERR_OP_NOT_SUPPORTED);
261 }
262
263 if (notify) {
264 vol_rend.state.change_counter++;
265 LOG_DBG("New state: volume %u, mute %u, counter %u",
266 vol_rend.state.volume, vol_rend.state.mute,
267 vol_rend.state.change_counter);
268
269 value_changed(&vol_rend, NOTIFY_STATE);
270
271 if (vol_rend.cb && vol_rend.cb->state) {
272 vol_rend.cb->state(conn, 0, vol_rend.state.volume, vol_rend.state.mute);
273 }
274 }
275
276 if (volume_change && !vol_rend.flags) {
277 vol_rend.flags = 1;
278
279 if (IS_ENABLED(CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE)) {
280 value_changed(&vol_rend, NOTIFY_FLAGS);
281 }
282
283 if (vol_rend.cb && vol_rend.cb->flags) {
284 vol_rend.cb->flags(conn, 0, vol_rend.flags);
285 }
286 }
287 return len;
288 }
289
290 #if defined(CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE)
flags_cfg_changed(const struct bt_gatt_attr * attr,uint16_t value)291 static void flags_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
292 {
293 LOG_DBG("value 0x%04x", value);
294 }
295 #endif /* CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE */
296
read_flags(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)297 static ssize_t read_flags(struct bt_conn *conn, const struct bt_gatt_attr *attr,
298 void *buf, uint16_t len, uint16_t offset)
299 {
300 LOG_DBG("0x%02x", vol_rend.flags);
301 return bt_gatt_attr_read(conn, attr, buf, len, offset, &vol_rend.flags,
302 sizeof(vol_rend.flags));
303 }
304
305 #define DUMMY_INCLUDE(i, _) BT_GATT_INCLUDE_SERVICE(NULL),
306 #define VOCS_INCLUDES(cnt) LISTIFY(cnt, DUMMY_INCLUDE, ())
307 #define AICS_INCLUDES(cnt) LISTIFY(cnt, DUMMY_INCLUDE, ())
308
309 /* Volume Control Service GATT Attributes */
310 static struct bt_gatt_attr vcs_attrs[] = {
311 BT_GATT_PRIMARY_SERVICE(BT_UUID_VCS),
312 VOCS_INCLUDES(CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT)
313 AICS_INCLUDES(CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT)
314 BT_AUDIO_CHRC(BT_UUID_VCS_STATE,
315 BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
316 BT_GATT_PERM_READ_ENCRYPT,
317 read_vol_state, NULL, NULL),
318 BT_AUDIO_CCC(volume_state_cfg_changed),
319 BT_AUDIO_CHRC(BT_UUID_VCS_CONTROL,
320 BT_GATT_CHRC_WRITE,
321 BT_GATT_PERM_WRITE_ENCRYPT,
322 NULL, write_vcs_control, NULL),
323 #if defined(CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE)
324 BT_AUDIO_CHRC(BT_UUID_VCS_FLAGS,
325 BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
326 BT_GATT_PERM_READ_ENCRYPT,
327 read_flags, NULL, NULL),
328 BT_AUDIO_CCC(flags_cfg_changed)
329 #else
330 BT_AUDIO_CHRC(BT_UUID_VCS_FLAGS,
331 BT_GATT_CHRC_READ,
332 BT_GATT_PERM_READ_ENCRYPT,
333 read_flags, NULL, NULL)
334 #endif /* CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE */
335 };
336
337 static struct bt_gatt_service vcs_svc;
338
prepare_vocs_inst(struct bt_vcp_vol_rend_register_param * param)339 static int prepare_vocs_inst(struct bt_vcp_vol_rend_register_param *param)
340 {
341 int err;
342 int j;
343 int i;
344
345 if (CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT == 0) {
346 return 0;
347 }
348
349 __ASSERT(param, "NULL param");
350
351 for (j = 0, i = 0; i < ARRAY_SIZE(vcs_attrs); i++) {
352 if (bt_uuid_cmp(vcs_attrs[i].uuid, BT_UUID_GATT_INCLUDE) == 0 &&
353 !vcs_attrs[i].user_data) {
354
355 vol_rend.vocs_insts[j] = bt_vocs_free_instance_get();
356
357 if (vol_rend.vocs_insts[j] == NULL) {
358 LOG_ERR("Could not get free VOCS instances[%d]",
359 j);
360 return -ENOMEM;
361 }
362
363 err = bt_vocs_register(vol_rend.vocs_insts[j],
364 ¶m->vocs_param[j]);
365 if (err != 0) {
366 LOG_DBG("Could not register VOCS instance[%d]: %d",
367 j, err);
368 return err;
369 }
370
371 vcs_attrs[i].user_data = bt_vocs_svc_decl_get(vol_rend.vocs_insts[j]);
372 j++;
373
374 if (j == CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT) {
375 break;
376 }
377 }
378 }
379
380 __ASSERT(j == CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT,
381 "Invalid VOCS instance count");
382
383 return 0;
384 }
385
prepare_aics_inst(struct bt_vcp_vol_rend_register_param * param)386 static int prepare_aics_inst(struct bt_vcp_vol_rend_register_param *param)
387 {
388 int err;
389 int j;
390 int i;
391
392 if (CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT == 0) {
393 return 0;
394 }
395
396 __ASSERT(param, "NULL param");
397
398 for (j = 0, i = 0; i < ARRAY_SIZE(vcs_attrs); i++) {
399 if (bt_uuid_cmp(vcs_attrs[i].uuid, BT_UUID_GATT_INCLUDE) == 0 &&
400 !vcs_attrs[i].user_data) {
401 vol_rend.aics_insts[j] = bt_aics_free_instance_get();
402
403 if (vol_rend.aics_insts[j] == NULL) {
404 LOG_ERR("Could not get free AICS instances[%d]",
405 j);
406 return -ENOMEM;
407 }
408
409 err = bt_aics_register(vol_rend.aics_insts[j],
410 ¶m->aics_param[j]);
411 if (err != 0) {
412 LOG_DBG("Could not register AICS instance[%d]: %d",
413 j, err);
414 return err;
415 }
416
417 vcs_attrs[i].user_data = bt_aics_svc_decl_get(vol_rend.aics_insts[j]);
418 j++;
419
420 LOG_DBG("AICS P %p", vcs_attrs[i].user_data);
421
422 if (j == CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT) {
423 break;
424 }
425 }
426 }
427
428 __ASSERT(j == CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT,
429 "Invalid AICS instance count");
430
431 return 0;
432 }
433
434 /****************************** PUBLIC API ******************************/
bt_vcp_vol_rend_register(struct bt_vcp_vol_rend_register_param * param)435 int bt_vcp_vol_rend_register(struct bt_vcp_vol_rend_register_param *param)
436 {
437 static bool registered;
438 int err;
439
440 CHECKIF(param == NULL) {
441 LOG_DBG("param is NULL");
442 return -EINVAL;
443 }
444
445 CHECKIF(param->mute > BT_VCP_STATE_MUTED) {
446 LOG_DBG("Invalid mute value: %u", param->mute);
447 return -EINVAL;
448 }
449
450 CHECKIF(param->step == 0) {
451 LOG_DBG("Invalid step value: %u", param->step);
452 return -EINVAL;
453 }
454
455 if (registered) {
456 return -EALREADY;
457 }
458
459 vcs_svc = (struct bt_gatt_service)BT_GATT_SERVICE(vcs_attrs);
460
461 if (CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT > 0) {
462 err = prepare_vocs_inst(param);
463
464 if (err != 0) {
465 return err;
466 }
467 }
468
469 if (CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT > 0) {
470 err = prepare_aics_inst(param);
471
472 if (err != 0) {
473 return err;
474 }
475 }
476
477
478 vol_rend.state.volume = param->volume;
479 vol_rend.state.mute = param->mute;
480 vol_rend.volume_step = param->step;
481 vol_rend.service_p = &vcs_svc;
482
483 err = bt_gatt_service_register(&vcs_svc);
484 if (err != 0) {
485 LOG_DBG("VCP service register failed: %d", err);
486 }
487
488 vol_rend.cb = param->cb;
489
490 atomic_clear(vol_rend.notify);
491 k_work_init_delayable(&vol_rend.notify_work, notify_work_handler);
492
493 registered = true;
494
495 return err;
496 }
497
bt_vcp_vol_rend_included_get(struct bt_vcp_included * included)498 int bt_vcp_vol_rend_included_get(struct bt_vcp_included *included)
499 {
500 if (included == NULL) {
501 return -EINVAL;
502 }
503
504 included->vocs_cnt = ARRAY_SIZE(vol_rend.vocs_insts);
505 included->vocs = vol_rend.vocs_insts;
506
507 included->aics_cnt = ARRAY_SIZE(vol_rend.aics_insts);
508 included->aics = vol_rend.aics_insts;
509
510 return 0;
511 }
512
bt_vcp_vol_rend_set_step(uint8_t volume_step)513 int bt_vcp_vol_rend_set_step(uint8_t volume_step)
514 {
515 if (volume_step > 0) {
516 vol_rend.volume_step = volume_step;
517 return 0;
518 } else {
519 return -EINVAL;
520 }
521 }
522
bt_vcp_vol_rend_get_state(void)523 int bt_vcp_vol_rend_get_state(void)
524 {
525 if (vol_rend.cb && vol_rend.cb->state) {
526 vol_rend.cb->state(NULL, 0, vol_rend.state.volume, vol_rend.state.mute);
527 }
528
529 return 0;
530 }
531
bt_vcp_vol_rend_get_flags(void)532 int bt_vcp_vol_rend_get_flags(void)
533 {
534 if (vol_rend.cb && vol_rend.cb->flags) {
535 vol_rend.cb->flags(NULL, 0, vol_rend.flags);
536 }
537
538 return 0;
539 }
540
bt_vcp_vol_rend_vol_down(void)541 int bt_vcp_vol_rend_vol_down(void)
542 {
543 const struct vcs_control cp = {
544 .opcode = BT_VCP_OPCODE_REL_VOL_DOWN,
545 .counter = vol_rend.state.change_counter,
546 };
547 int err;
548
549 err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0);
550
551 return err > 0 ? 0 : err;
552 }
553
bt_vcp_vol_rend_vol_up(void)554 int bt_vcp_vol_rend_vol_up(void)
555 {
556 const struct vcs_control cp = {
557 .opcode = BT_VCP_OPCODE_REL_VOL_UP,
558 .counter = vol_rend.state.change_counter,
559 };
560 int err;
561
562 err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0);
563
564 return err > 0 ? 0 : err;
565 }
566
bt_vcp_vol_rend_unmute_vol_down(void)567 int bt_vcp_vol_rend_unmute_vol_down(void)
568 {
569 const struct vcs_control cp = {
570 .opcode = BT_VCP_OPCODE_UNMUTE_REL_VOL_DOWN,
571 .counter = vol_rend.state.change_counter,
572 };
573 int err;
574
575 err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0);
576
577 return err > 0 ? 0 : err;
578 }
579
bt_vcp_vol_rend_unmute_vol_up(void)580 int bt_vcp_vol_rend_unmute_vol_up(void)
581 {
582 const struct vcs_control cp = {
583 .opcode = BT_VCP_OPCODE_UNMUTE_REL_VOL_UP,
584 .counter = vol_rend.state.change_counter,
585 };
586 int err;
587
588 err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0);
589
590 return err > 0 ? 0 : err;
591 }
592
bt_vcp_vol_rend_set_vol(uint8_t volume)593 int bt_vcp_vol_rend_set_vol(uint8_t volume)
594 {
595 const struct vcs_control_vol cp = {
596 .cp = {
597 .opcode = BT_VCP_OPCODE_SET_ABS_VOL,
598 .counter = vol_rend.state.change_counter
599 },
600 .volume = volume
601 };
602 int err;
603
604 err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0);
605
606 return err > 0 ? 0 : err;
607 }
608
bt_vcp_vol_rend_unmute(void)609 int bt_vcp_vol_rend_unmute(void)
610 {
611 const struct vcs_control cp = {
612 .opcode = BT_VCP_OPCODE_UNMUTE,
613 .counter = vol_rend.state.change_counter,
614 };
615 int err;
616
617 err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0);
618
619 return err > 0 ? 0 : err;
620 }
621
bt_vcp_vol_rend_mute(void)622 int bt_vcp_vol_rend_mute(void)
623 {
624 const struct vcs_control cp = {
625 .opcode = BT_VCP_OPCODE_MUTE,
626 .counter = vol_rend.state.change_counter,
627 };
628 int err;
629
630 err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0);
631
632 return err > 0 ? 0 : err;
633 }
634