1use bt_common::packet_encoding::Encodable;
6use bt_common::Uuid;
7use bt_gatt::{
8 client::{FromCharacteristic, PeerService, PeerServiceHandle},
9 types::WriteMode,
10 Characteristic, Client,
11};
12
13use futures::TryFutureExt;
14use parking_lot::Mutex;
15use std::sync::Arc;
16use thiserror::Error;
17
18pub const VCS_UUID: Uuid = Uuid::from_u16(0x1844);
19
20pub mod debug;
21
22#[derive(Debug, Clone)]
25pub struct VolumeState {
26 handle: bt_gatt::types::Handle,
27 setting: u8,
29 mute: bool,
32 change_counter: u8,
35}
36
37impl VolumeState {
38 fn from_value(
39 handle: bt_gatt::types::Handle,
40 value: &[u8],
41 ) -> core::result::Result<Self, bt_common::packet_encoding::Error> {
42 let mut val = Self { handle, setting: 0, mute: false, change_counter: 0 };
43 val.update_from_value(value)?;
44 Ok(val)
45 }
46
47 fn update_from_value(
48 &mut self,
49 value: &[u8],
50 ) -> core::result::Result<(), bt_common::packet_encoding::Error> {
51 if value.len() < 3 {
52 return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
53 }
54 self.setting = value[0];
55 self.mute = match value[1] {
56 0 => false,
57 1 => true,
58 _ => return Err(bt_common::packet_encoding::Error::OutOfRange),
59 };
60 self.change_counter = value[2];
61 Ok(())
62 }
63}
64
65impl FromCharacteristic for VolumeState {
66 const UUID: Uuid = Uuid::from_u16(0x2B7D);
67
68 fn from_chr(
69 characteristic: Characteristic,
70 value: &[u8],
71 ) -> core::result::Result<Self, bt_common::packet_encoding::Error> {
72 Self::from_value(characteristic.handle, value)
73 }
74
75 fn update(
76 &mut self,
77 new_value: &[u8],
78 ) -> core::result::Result<&mut Self, bt_common::packet_encoding::Error> {
79 self.update_from_value(new_value)?;
80 Ok(self)
81 }
82}
83
84#[derive(Debug, Clone)]
87pub struct VolumeControlPoint {
88 handle: bt_gatt::types::Handle,
89}
90
91impl VolumeControlPoint {
92 const UUID: Uuid = Uuid::from_u16(0x2B7E);
93}
94
95#[derive(Debug, Clone, PartialEq)]
96pub enum VcpProcedure {
97 RelativeVolumeDown,
98 RelativeVolumeUp,
99 UnmuteRelativeVolumeDown,
100 UnmuteRelativeVolumeUp,
101 SetAbsoluteVolume { setting: u8 },
102 Unmute,
103 Mute,
104}
105
106impl VcpProcedure {
107 fn opcode(&self) -> u8 {
108 match self {
109 VcpProcedure::RelativeVolumeDown => 0x00,
110 VcpProcedure::RelativeVolumeUp => 0x01,
111 VcpProcedure::UnmuteRelativeVolumeDown => 0x02,
112 VcpProcedure::UnmuteRelativeVolumeUp => 0x03,
113 VcpProcedure::SetAbsoluteVolume { .. } => 0x04,
114 VcpProcedure::Unmute => 0x05,
115 VcpProcedure::Mute => 0x06,
116 }
117 }
118}
119
120pub struct VolumeControlPointOperation {
121 procedure: VcpProcedure,
122 change_counter: u8,
123}
124
125impl Encodable for VolumeControlPointOperation {
126 type Error = bt_common::packet_encoding::Error;
127
128 fn encoded_len(&self) -> core::primitive::usize {
129 if let VcpProcedure::SetAbsoluteVolume { .. } = self.procedure {
130 return 3;
131 }
132 2
134 }
135
136 fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
137 if buf.len() < self.encoded_len() {
138 return Err(bt_common::packet_encoding::Error::BufferTooSmall);
139 }
140
141 buf[0] = self.procedure.opcode();
142 buf[1] = self.change_counter;
143 if let VcpProcedure::SetAbsoluteVolume { setting } = self.procedure {
144 buf[2] = setting;
145 }
146 Ok(())
147 }
148}
149
150#[derive(Debug, Clone, Copy, PartialEq)]
151pub enum VolumeSettingPersisted {
152 Reset,
153 UserSet,
154}
155
156impl TryFrom<&[u8]> for VolumeSettingPersisted {
157 type Error = bt_common::packet_encoding::Error;
158
159 fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
160 if value.len() < 1 {
161 return Err(bt_common::packet_encoding::Error::UnexpectedDataLength);
162 }
163 if (value[0] & 0x01) != 0 {
164 Ok(VolumeSettingPersisted::UserSet)
165 } else {
166 Ok(VolumeSettingPersisted::Reset)
167 }
168 }
169}
170
171#[derive(Debug, Clone)]
174pub struct VolumeFlags {
175 handle: bt_gatt::types::Handle,
176 persisted: VolumeSettingPersisted,
177}
178
179impl FromCharacteristic for VolumeFlags {
180 const UUID: Uuid = Uuid::from_u16(0x2B7F);
181
182 fn from_chr(
183 characteristic: Characteristic,
184 value: &[u8],
185 ) -> core::result::Result<Self, bt_common::packet_encoding::Error> {
186 let persisted = VolumeSettingPersisted::try_from(value)?;
187 Ok(Self { handle: characteristic.handle, persisted })
188 }
189
190 fn update(
191 &mut self,
192 new_value: &[u8],
193 ) -> core::result::Result<&mut Self, bt_common::packet_encoding::Error> {
194 self.persisted = VolumeSettingPersisted::try_from(new_value)?;
195 Ok(self)
196 }
197}
198
199pub struct VolumeControlClient<T: bt_gatt::GattTypes> {
200 service: T::PeerService,
201 state: Arc<Mutex<VolumeState>>,
202 control_point: VolumeControlPoint,
203 flags: Arc<Mutex<VolumeFlags>>,
204}
205
206impl<T: bt_gatt::GattTypes> std::fmt::Display for VolumeControlClient<T> {
207 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
208 let VolumeState { change_counter, setting, mute, .. } = self.state.lock().clone();
209 let VolumeFlags { persisted, .. } = self.flags.lock().clone();
210 let mute_str = mute.then_some("MUTED ").unwrap_or("");
211 write!(
212 f,
213 "Volume Control @ {change_counter}: Current Volume: {setting} {mute_str}from {persisted:?}"
214 )
215 }
216}
217
218#[derive(Error, Debug)]
219pub enum Error {
220 #[error("Issue encoding / decoding: {0}")]
221 Decoding(#[from] bt_common::packet_encoding::Error),
222 #[error("GATT error: {0}")]
223 Gatt(bt_gatt::types::Error),
224 #[error("Required Characteristic was not found: {0}")]
225 RequiredCharNotFound(&'static str),
226 #[error("Change Counter Mismatch")]
227 ChangeCounterMismatch,
228 #[error("Opcode Not Supported")]
229 OpcodeNotSupported,
230}
231
232impl From<bt_gatt::types::Error> for Error {
233 fn from(value: bt_gatt::types::Error) -> Self {
234 use bt_gatt::types::GattError::*;
235 match value {
236 bt_gatt::types::Error::Gatt(ApplicationError80) => Self::ChangeCounterMismatch,
237 bt_gatt::types::Error::Gatt(ApplicationError81) => Self::OpcodeNotSupported,
238 other => Self::Gatt(other),
239 }
240 }
241}
242
243impl<T: bt_gatt::GattTypes> VolumeControlClient<T> {
245 pub async fn from_service(service: T::PeerService) -> Result<Self, Error> {
246 let mut state = None;
247 let mut control_point = None;
248 let mut flags = None;
249 for chr in service.discover_characteristics(None).await? {
250 if chr.uuid == VolumeState::UUID {
251 let _ = state.insert(VolumeState::try_read::<T>(chr, &service).await?);
252 } else if chr.uuid == VolumeControlPoint::UUID {
253 let _ = control_point.insert(VolumeControlPoint { handle: chr.handle });
254 } else if chr.uuid == VolumeFlags::UUID {
255 let _ = flags.insert(VolumeFlags::try_read::<T>(chr, &service).await?);
256 }
257 }
258
259 if state.is_none() {
260 return Err(Error::RequiredCharNotFound("Volume State"));
261 }
262 if control_point.is_none() {
263 return Err(Error::RequiredCharNotFound("Volume Conrol Point"));
264 }
265 if flags.is_none() {
266 return Err(Error::RequiredCharNotFound("Volume Flags"));
267 }
268
269 Ok(Self {
270 service,
271 state: Arc::new(Mutex::new(state.unwrap())),
272 control_point: control_point.unwrap(),
273 flags: Arc::new(Mutex::new(flags.unwrap())),
274 })
275 }
276
277 pub async fn connect(client: &T::Client) -> Result<Option<Self>, Error> {
278 let handles = client.find_service(VCS_UUID).await?;
279 let Some(handle) = handles.into_iter().next() else {
280 return Ok(None);
281 };
282 Ok(Some(handle.connect().map_err(Into::into).and_then(Self::from_service).await?))
283 }
284
285 fn change_counter(&self) -> u8 {
286 self.state.lock().change_counter
287 }
288
289 pub async fn update(&self) -> Result<u8, Error> {
292 let state_handle = self.state.lock().handle;
293
294 let mut state_buf = [0; 3];
295 let _ = self.service.read_characteristic(&state_handle, 0, &mut state_buf).await?;
296 let setting = self.state.lock().update(&state_buf)?.setting;
297
298 let flags_handle = self.flags.lock().handle;
299 let mut flags_buf = [0; 1];
300 let _ = self.service.read_characteristic(&flags_handle, 0, &mut flags_buf).await?;
301 let _ = self.flags.lock().update(&flags_buf)?;
302
303 Ok(setting)
304 }
305
306 async fn send_control_pt(&self, procedure: VcpProcedure) -> Result<(), Error> {
307 let change_counter = self.change_counter();
308 let op = VolumeControlPointOperation { procedure, change_counter };
309 let mut buf = [0; 2];
310 op.encode(&mut buf)?;
311 self.service
312 .write_characteristic(&self.control_point.handle, WriteMode::None, 0, &buf)
313 .await
314 .map_err(Into::into)
315 }
316
317 pub async fn volume_up(&self, unmute: bool) -> Result<(), Error> {
322 let procedure = if unmute {
323 VcpProcedure::UnmuteRelativeVolumeUp
324 } else {
325 VcpProcedure::RelativeVolumeUp
326 };
327 self.send_control_pt(procedure).await
328 }
329
330 pub async fn volume_down(&self, unmute: bool) -> Result<(), Error> {
335 let procedure = if unmute {
336 VcpProcedure::UnmuteRelativeVolumeDown
337 } else {
338 VcpProcedure::RelativeVolumeDown
339 };
340 self.send_control_pt(procedure).await
341 }
342
343 pub async fn mute(&self) -> Result<(), Error> {
344 self.send_control_pt(VcpProcedure::Mute).await
345 }
346
347 pub async fn unmute(&self) -> Result<(), Error> {
348 self.send_control_pt(VcpProcedure::Unmute).await
349 }
350
351 pub async fn set_absolute_volume(&self, setting: u8) -> Result<(), Error> {
352 let change_counter = self.change_counter();
353 let op = VolumeControlPointOperation {
354 procedure: VcpProcedure::SetAbsoluteVolume { setting },
355 change_counter,
356 };
357 let mut buf = [0; 3];
358 op.encode(&mut buf)?;
359 self.service
360 .write_characteristic(&self.control_point.handle, WriteMode::None, 0, &buf)
361 .await
362 .map_err(Into::into)
363 }
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369
370 use bt_gatt::test_utils::*;
371 use futures::Future;
372
373 #[test]
374 fn volume_state_decode() {
375 let handle = bt_gatt::types::Handle(1);
376 assert!(VolumeState::from_value(handle, &[]).is_err());
378 assert!(VolumeState::from_value(handle, &[1, 2, 3, 4]).is_err());
380 let state = VolumeState::from_value(handle, &[1, 1, 3, 4]).expect("okay");
382 assert_eq!(state.mute, true);
383 assert_eq!(state.setting, 1);
384 assert_eq!(state.change_counter, 3);
385 let state = VolumeState::from_value(handle, &[3, 0, 1]).expect("okay");
387 assert_eq!(state.mute, false);
388 assert_eq!(state.setting, 3);
389 assert_eq!(state.change_counter, 1);
390 }
391
392 #[test]
393 fn volume_flags_decode() {
394 use bt_gatt::types::*;
395 let chr = Characteristic {
396 handle: Handle(1),
397 uuid: VolumeFlags::UUID,
398 properties: CharacteristicProperty::Read.into(),
399 permissions: AttributePermissions {
400 read: Some(SecurityLevels::default()),
401 ..Default::default()
402 },
403 descriptors: Vec::new(),
404 };
405 assert!(VolumeFlags::from_chr(chr.clone(), &[]).is_err());
407 let flags = VolumeFlags::from_chr(chr.clone(), &[1]).expect("okay");
409 assert_eq!(flags.persisted, VolumeSettingPersisted::UserSet);
410 let flags = VolumeFlags::from_chr(chr, &[2]).expect("okay");
412 assert_eq!(flags.persisted, VolumeSettingPersisted::Reset);
413 }
414
415 #[track_caller]
416 fn is_ready<T>(fut: impl Future<Output = T>) -> T {
417 use futures::FutureExt;
418 let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
419 let mut fut_pinned = std::pin::pin!(fut);
420 let futures::task::Poll::Ready(x) = fut_pinned.poll_unpin(&mut noop_cx) else {
421 panic!("Future is not ready");
422 };
423 x
424 }
425
426 fn try_from_service(
427 service: FakePeerService,
428 ) -> core::result::Result<VolumeControlClient<FakeTypes>, crate::Error> {
429 is_ready(VolumeControlClient::<FakeTypes>::from_service(service))
430 }
431
432 const STATE_HANDLE: bt_gatt::types::Handle = bt_gatt::types::Handle(1);
433 const FLAGS_HANDLE: bt_gatt::types::Handle = bt_gatt::types::Handle(2);
434 const CP_HANDLE: bt_gatt::types::Handle = bt_gatt::types::Handle(3);
435
436 fn state_chr() -> bt_gatt::types::Characteristic {
437 use bt_gatt::types::*;
438 Characteristic {
439 handle: STATE_HANDLE,
440 uuid: VolumeState::UUID,
441 properties: CharacteristicProperty::Read | CharacteristicProperty::Notify,
442 permissions: AttributePermissions {
443 read: Some(SecurityLevels::default()),
444 update: Some(SecurityLevels::default()),
445 ..Default::default()
446 },
447 descriptors: Vec::new(),
448 }
449 }
450
451 fn build_fake_service() -> FakePeerService {
452 use bt_gatt::types::*;
453
454 let mut service = FakePeerService::new();
455
456 assert!(try_from_service(service.clone()).is_err());
459 service.add_characteristic(state_chr(), vec![1, 0, 1]);
460 assert!(try_from_service(service.clone()).is_err());
461 service.add_characteristic(
462 Characteristic {
463 handle: FLAGS_HANDLE,
464 uuid: VolumeFlags::UUID,
465 properties: CharacteristicProperty::Read.into(),
466 permissions: AttributePermissions {
467 read: Some(SecurityLevels::default()),
468 ..Default::default()
469 },
470 descriptors: Vec::new(),
471 },
472 vec![1],
473 );
474 assert!(try_from_service(service.clone()).is_err());
475 service.add_characteristic(
476 Characteristic {
477 handle: CP_HANDLE,
478 uuid: VolumeControlPoint::UUID,
479 properties: CharacteristicProperty::Write.into(),
480 permissions: AttributePermissions {
481 write: Some(SecurityLevels::default()),
482 ..Default::default()
483 },
484 descriptors: Vec::new(),
485 },
486 vec![],
487 );
488
489 service
490 }
491
492 #[test]
493 fn build_from_service() {
494 use futures::{task::Poll, FutureExt};
495
496 let mut service = build_fake_service();
497 let client = try_from_service(service.clone()).unwrap();
498 assert_eq!(client.change_counter(), 1);
499
500 service.add_characteristic(state_chr(), vec![100, 1, 3]);
501 let mut update_fut = Box::pin(client.update());
502 let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref());
503 match update_fut.poll_unpin(&mut noop_cx) {
504 Poll::Ready(Ok(volume)) => assert_eq!(volume, 100),
505 x => panic!("Didn't update right: {x:?}"),
506 }
507 }
508
509 #[test]
510 fn connect() {
511 let mut service = build_fake_service();
512 let mut client = FakeClient::new();
513
514 assert!(is_ready(VolumeControlClient::<FakeTypes>::connect(&client)).unwrap().is_none());
516
517 client.add_service(VCS_UUID, true, service.clone());
518
519 let client = is_ready(VolumeControlClient::<FakeTypes>::connect(&client)).unwrap().unwrap();
520 assert_eq!(client.change_counter(), 1);
521 service.add_characteristic(state_chr(), vec![250, 1, 3]);
522 assert_eq!(250, is_ready(client.update()).unwrap());
523 }
524
525 #[test]
526 fn volume() {
527 let mut service = build_fake_service();
528 let client = try_from_service(service.clone()).unwrap();
529 assert_eq!(client.change_counter(), 1);
530
531 service.expect_characteristic_value(&CP_HANDLE, vec![0x01, client.change_counter()]);
532 assert!(is_ready(client.volume_up(false)).is_ok());
533
534 service.expect_characteristic_value(&CP_HANDLE, vec![0x03, client.change_counter()]);
535 assert!(is_ready(client.volume_up(true)).is_ok());
536
537 service.expect_characteristic_value(&CP_HANDLE, vec![0x02, client.change_counter()]);
538 assert!(is_ready(client.volume_down(true)).is_ok());
539
540 service.add_characteristic(state_chr(), vec![250, 1, 3]);
541 assert_eq!(250, is_ready(client.update()).unwrap());
542
543 service.expect_characteristic_value(&CP_HANDLE, vec![0x00, 3]);
544 assert!(is_ready(client.volume_down(false)).is_ok());
545 }
546
547 #[test]
548 fn mutes() {
549 let mut service = build_fake_service();
550 let client = try_from_service(service.clone()).unwrap();
551 assert_eq!(client.change_counter(), 1);
552
553 service.expect_characteristic_value(&CP_HANDLE, vec![0x06, 1]);
554 assert!(is_ready(client.mute()).is_ok());
555
556 service.expect_characteristic_value(&CP_HANDLE, vec![0x05, 1]);
557 assert!(is_ready(client.unmute()).is_ok());
558
559 service.expect_characteristic_value(&CP_HANDLE, vec![0x05, 1]);
560 assert!(is_ready(client.unmute()).is_ok());
561 }
562
563 #[test]
564 fn absolute_volume() {
565 let mut service = build_fake_service();
566 let client = try_from_service(service.clone()).unwrap();
567 assert_eq!(client.change_counter(), 1);
568
569 service.expect_characteristic_value(&CP_HANDLE, vec![0x04, 1, 123]);
570 assert!(is_ready(client.set_absolute_volume(123)).is_ok());
571 }
572}