From 12049731778df94960bd78ae88d9ee3f14ea9951 Mon Sep 17 00:00:00 2001 From: Alex van de Sandt Date: Fri, 7 Aug 2026 19:06:58 -0500 Subject: [PATCH] Add volume as a single-param command --- examples/volume.rs | 31 ++++++++++++++ src/codec.rs | 2 +- src/command/bc125at/mod.rs | 2 + src/command/bc125at/volume.rs | 79 +++++++++++++++++++++++++++++++++++ src/command/mod.rs | 7 +++- src/command/no_params.rs | 4 +- src/command/single_param.rs | 21 ++++++++++ 7 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 examples/volume.rs create mode 100644 src/command/bc125at/volume.rs create mode 100644 src/command/single_param.rs diff --git a/examples/volume.rs b/examples/volume.rs new file mode 100644 index 0000000..718f56f --- /dev/null +++ b/examples/volume.rs @@ -0,0 +1,31 @@ +#![allow(clippy::unwrap_used)] + +use arctictis::{ + Command, Scanner, + bc125at::{GetVolume, SetVolume, Volume}, +}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let mut scanner = Scanner::open()?; + println!("{scanner:#?}"); + print_response(&mut scanner, GetVolume).await?; + print_response(&mut scanner, SetVolume(Volume::new(5).unwrap())).await?; + print_response(&mut scanner, GetVolume).await?; + + Ok(()) +} + +async fn print_response( + scanner: &mut Scanner, + cmd: Cmd, +) -> Result<(), Box> +where + Cmd: Command + 'static, + Cmd::Response: std::fmt::Debug, +{ + let name = String::from_utf8_lossy(Cmd::TEXT); + let r = scanner.command(cmd).await?; + println!("{name} => {r:?}"); + Ok(()) +} diff --git a/src/codec.rs b/src/codec.rs index 8e5bd69..91e6103 100644 --- a/src/codec.rs +++ b/src/codec.rs @@ -38,7 +38,7 @@ where dst.extend_from_slice(Cmd::TEXT); for param in params { dst.put_u8(PARAM_DELIMITER); - dst.extend_from_slice(param); + dst.extend_from_slice(param.as_ref()); } dst.put_u8(RETURN_CODE); diff --git a/src/command/bc125at/mod.rs b/src/command/bc125at/mod.rs index c07c05c..f3bce93 100644 --- a/src/command/bc125at/mod.rs +++ b/src/command/bc125at/mod.rs @@ -1,3 +1,5 @@ mod firmware_version; +mod volume; pub use firmware_version::{FirmwareVersion, FirmwareVersionError, GetFirmwareVersion}; +pub use volume::{GetVolume, SetVolume, Volume, VolumeError}; diff --git a/src/command/bc125at/volume.rs b/src/command/bc125at/volume.rs new file mode 100644 index 0000000..6b5f174 --- /dev/null +++ b/src/command/bc125at/volume.rs @@ -0,0 +1,79 @@ +use tokio_util::bytes::Bytes; + +use crate::{ + Command, OkResponse, + command::{IntoParam, NoParams, Response, single_param::SingleParam}, +}; + +#[derive(Clone, Copy, Debug, thiserror::Error)] +#[error("invalid volume")] +pub struct VolumeError; + +#[derive(Clone, Copy, Debug)] +pub struct Volume(u8); + +impl Volume { + pub fn new(volume: u8) -> Option { + (0..=15).contains(&volume).then_some(Self(volume)) + } +} + +impl IntoParam for Volume { + fn into_param(self) -> Bytes { + Bytes::from(format!("{}", self.0)) + } +} + +impl Response for Volume { + type Error = VolumeError; + + fn deserialize<'i, I: Iterator>( + mut raw_values: I, + ) -> Result { + if let Some(val) = raw_values.next() + && raw_values.next().is_none() + { + let level = str::from_utf8(val.as_ref()) + .map_err(|_| VolumeError)? + .parse() + .map_err(|_| VolumeError)?; + Volume::new(level).ok_or(VolumeError) + } else { + Err(VolumeError) + } + } + + fn expected_field_count() -> usize { + 1 + } +} + +#[derive(Clone, Copy, Debug)] +pub struct GetVolume; + +impl Command for GetVolume { + const TEXT: &'static [u8] = b"VOL"; + + type Params = NoParams; + + type Response = Volume; + + fn params(self) -> Self::Params { + NoParams + } +} + +#[derive(Clone, Copy, Debug)] +pub struct SetVolume(pub Volume); + +impl Command for SetVolume { + const TEXT: &'static [u8] = b"VOL"; + + type Params = SingleParam; + + type Response = OkResponse; + + fn params(self) -> Self::Params { + SingleParam(self.0) + } +} diff --git a/src/command/mod.rs b/src/command/mod.rs index 696f84a..4b520ff 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -1,6 +1,7 @@ pub mod bc125at; mod no_params; mod ok_response; +mod single_param; use tokio_util::bytes::Bytes; @@ -16,10 +17,14 @@ pub trait Command { fn params(self) -> Self::Params; } -pub trait Params: IntoIterator { +pub trait Params: IntoIterator { fn size_hint(&self) -> usize; } +pub trait IntoParam { + fn into_param(self) -> Bytes; +} + pub trait Response: Sized { type Error: std::error::Error; diff --git a/src/command/no_params.rs b/src/command/no_params.rs index ebe5549..2c3457e 100644 --- a/src/command/no_params.rs +++ b/src/command/no_params.rs @@ -1,9 +1,11 @@ +use tokio_util::bytes::Bytes; + use crate::command::Params; pub struct NoParams; impl IntoIterator for NoParams { - type Item = &'static [u8]; + type Item = Bytes; type IntoIter = std::iter::Empty; diff --git a/src/command/single_param.rs b/src/command/single_param.rs new file mode 100644 index 0000000..72f515f --- /dev/null +++ b/src/command/single_param.rs @@ -0,0 +1,21 @@ +use tokio_util::bytes::Bytes; + +use crate::command::{IntoParam, Params}; + +pub struct SingleParam(pub T); + +impl IntoIterator for SingleParam { + type Item = Bytes; + + type IntoIter = std::iter::Once; + + fn into_iter(self) -> Self::IntoIter { + std::iter::once(self.0.into_param()) + } +} + +impl Params for SingleParam { + fn size_hint(&self) -> usize { + 1 + } +} -- 2.51.2