diff --git a/Cargo.lock b/Cargo.lock index 62b5086..5dce532 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,19 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "arctictis" +version = "0.1.0" +dependencies = [ + "bytes", + "futures-util", + "itoa", + "thiserror", + "tokio", + "tokio-serial", + "tokio-util", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -391,19 +404,6 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" -[[package]] -name = "uniden" -version = "0.1.0" -dependencies = [ - "bytes", - "futures-util", - "itoa", - "thiserror", - "tokio", - "tokio-serial", - "tokio-util", -] - [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index ceb7e50..9690f6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "uniden" +name = "arctictis" version = "0.1.0" edition = "2024" diff --git a/examples/version.rs b/examples/version.rs index 8fb7ef1..d240ed8 100644 --- a/examples/version.rs +++ b/examples/version.rs @@ -1,4 +1,7 @@ -use uniden::{Command, GetFirmwareVersion, GetModelInfo, Scanner}; +use arctictis::{ + Command, Scanner, + command::{GetFirmwareVersion, GetModelInfo}, +}; #[tokio::main] async fn main() -> Result<(), Box> { @@ -14,7 +17,8 @@ async fn print_response(scanner: &mut Scanner, cmd: T) -> Result<(), Box {r:#?}", String::from_utf8_lossy(cmd.as_bytes())); + println!("{name} => {r:#?}"); Ok(()) } diff --git a/src/codec/command.rs b/src/codec/command.rs index 222eaf4..4f81bdf 100644 --- a/src/codec/command.rs +++ b/src/codec/command.rs @@ -1,4 +1,10 @@ -pub trait Command: Send + Sync + Clone + std::fmt::Debug { +mod private { + pub trait Sealed {} +} + +pub trait Command: + private::Sealed + Send + Sync + Clone + std::fmt::Display + std::fmt::Debug +{ fn as_bytes(&self) -> &'static [u8]; fn params(&self) -> impl IntoIterator { @@ -6,10 +12,11 @@ pub trait Command: Send + Sync + Clone + std::fmt::Debug { } } -pub trait Param { +pub trait Param: private::Sealed + Send + Sync + std::fmt::Debug { fn write_bytes(&self, dst: &mut tokio_util::bytes::BytesMut); } +impl private::Sealed for u8 {} impl Param for u8 { fn write_bytes(&self, dst: &mut tokio_util::bytes::BytesMut) { let mut buff = itoa::Buffer::new(); @@ -21,6 +28,9 @@ macro_rules! command { ($cmd:literal, $name:ident) => { #[derive(Clone, Debug)] pub struct $name; + + command!(@traits $cmd, $name); + impl Command for $name { fn as_bytes(&self) -> &'static [u8] { $cmd } } @@ -28,6 +38,9 @@ macro_rules! command { ($cmd:literal, $name: ident ( $param_ty:ty )) => { #[derive(Clone, Debug)] pub struct $name(pub $param_ty); + + command!(@traits $cmd, $name); + impl Command for $name { fn as_bytes(&self) -> &'static [u8] { $cmd } fn params(&self) -> impl IntoIterator { @@ -38,6 +51,9 @@ macro_rules! command { ($cmd:literal, $name: ident { $($param:ident: $param_ty:ty),+ }) => { #[derive(Clone, Debug)] pub struct $name { $(pub $param: $param_ty),+ } + + command!(@traits $cmd, $name); + impl Command for $name { fn as_bytes(&self) -> &'static [u8] { $cmd } fn params(&self) -> impl IntoIterator { @@ -45,6 +61,14 @@ macro_rules! command { } } }; + (@traits $cmd:literal, $name:ident) => { + impl private::Sealed for $name {} + impl std::fmt::Display for $name { + fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + String::from_utf8_lossy($cmd).fmt(f) + } + } + }; } macro_rules! param { @@ -53,6 +77,9 @@ macro_rules! param { pub enum $name { $($variant),+ } + + param!(@traits $name); + impl Param for $name { fn write_bytes(&self, dst: &mut tokio_util::bytes::BytesMut) { let s = match self { @@ -65,6 +92,9 @@ macro_rules! param { (pub range $name:ident ($range:expr)) => { #[derive(Clone, Debug)] pub struct $name(u8); + + param!(@traits $name); + impl $name { pub fn new(value: u8) -> Self { assert!($range.contains(&value)); @@ -77,6 +107,9 @@ macro_rules! param { } } }; + (@traits $name:ident) => { + impl private::Sealed for $name {} + }; } command!(b"PRG", EnterProgramMode); @@ -85,13 +118,7 @@ command!(b"MDL", GetModelInfo); command!(b"VER", GetFirmwareVersion); command!(b"BLT", GetBacklight); -command!( - b"BLT", - SetBacklight { - backlight: Backlight - } -); - +command!(b"BLT", SetBacklight(Backlight)); param!(pub enum Backlight { AlwaysOn => b"AO", AlwaysOff => b"AF", diff --git a/src/lib.rs b/src/lib.rs index 239280f..cd75fc9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,76 +1,7 @@ mod codec; +mod scanner; -use std::time::Duration; - -use futures_util::{SinkExt, StreamExt}; -use tokio_serial::{SerialPortBuilderExt, SerialPortType, SerialStream}; -use tokio_util::codec::{AnyDelimiterCodecError, Framed}; - -use crate::codec::Codec; - -pub use crate::codec::command::{ - Backlight, BandPlan, BatteryChargeTime, BeepLevel, ClearAllMemory, Command, EnterProgramMode, - ExitProgramMode, GetBacklight, GetBandPlan, GetBatteryInfo, GetFirmwareVersion, GetKeyBeep, - GetModelInfo, KeyLockStatus, SetBacklight, SetBandPlan, SetBatteryInfo, SetKeyBeep, +pub use crate::{ + codec::command::{self, Command}, + scanner::{Scanner, ScannerError}, }; - -const VENDOR_ID: u16 = 0x1965; -const PRODUCT_ID: u16 = 0x0017; - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("port closed")] - PortClosed, - - #[error("scanner not found")] - ScannerNotFound, - - #[error(transparent)] - Codec(#[from] AnyDelimiterCodecError), - - #[error(transparent)] - Io(#[from] std::io::Error), - - #[error(transparent)] - Serial(#[from] tokio_serial::Error), -} - -#[derive(Debug)] -pub struct Scanner(Framed); - -impl Scanner { - pub fn open() -> Result { - let ports = tokio_serial::available_ports()?; - let Some(scanner_port_path) = ports.iter().find_map(|port| { - let SerialPortType::UsbPort(usb_port_info) = &port.port_type else { - return None; - }; - (usb_port_info.vid == VENDOR_ID && usb_port_info.pid == PRODUCT_ID) - .then_some(port.port_name.clone()) - }) else { - return Err(Error::ScannerNotFound); - }; - - let port = tokio_serial::new(&scanner_port_path, 115200) - .timeout(Duration::from_secs(120)) - .open_native_async()?; - - let framed = Framed::new(port, Codec::new()); - - Ok(Self(framed)) - } - - pub async fn response(&mut self) -> Result { - let r = self.0.next().await.ok_or(Error::PortClosed)??; - Ok(r) - } - - pub async fn command(&mut self, cmd: T) -> Result - where - T: Command, - { - self.0.send(cmd).await?; - let r = self.0.next().await.ok_or(Error::PortClosed)??; - Ok(r) - } -} diff --git a/src/scanner.rs b/src/scanner.rs new file mode 100644 index 0000000..26c42a6 --- /dev/null +++ b/src/scanner.rs @@ -0,0 +1,68 @@ +use std::time::Duration; + +use futures_util::{SinkExt, StreamExt}; +use tokio_serial::{SerialPortBuilderExt, SerialPortType, SerialStream}; +use tokio_util::codec::{AnyDelimiterCodecError, Framed}; + +use crate::{codec::Codec, command::Command}; + +const VENDOR_ID: u16 = 0x1965; +const PRODUCT_ID: u16 = 0x0017; + +#[derive(Debug, thiserror::Error)] +pub enum ScannerError { + #[error("port closed")] + PortClosed, + + #[error("scanner not found")] + ScannerNotFound, + + #[error(transparent)] + Codec(#[from] AnyDelimiterCodecError), + + #[error(transparent)] + Io(#[from] std::io::Error), + + #[error(transparent)] + Serial(#[from] tokio_serial::Error), +} + +#[derive(Debug)] +pub struct Scanner(Framed); + +impl Scanner { + pub fn open() -> Result { + let ports = tokio_serial::available_ports()?; + let Some(scanner_port_path) = ports.iter().find_map(|port| { + let SerialPortType::UsbPort(usb_port_info) = &port.port_type else { + return None; + }; + (usb_port_info.vid == VENDOR_ID && usb_port_info.pid == PRODUCT_ID) + .then_some(port.port_name.clone()) + }) else { + return Err(ScannerError::ScannerNotFound); + }; + + let port = tokio_serial::new(&scanner_port_path, 115200) + .timeout(Duration::from_secs(120)) + .open_native_async()?; + + let framed = Framed::new(port, Codec::new()); + + Ok(Self(framed)) + } + + pub async fn response(&mut self) -> Result { + let r = self.0.next().await.ok_or(ScannerError::PortClosed)??; + Ok(r) + } + + pub async fn command(&mut self, cmd: T) -> Result + where + T: Command, + { + self.0.send(cmd).await?; + let r = self.0.next().await.ok_or(ScannerError::PortClosed)??; + Ok(r) + } +}