V3N7ZSMUNTM4CK6DPSL3R5QZTAAOI25ROQ5OFJME32WGSSO4B7EQC mod timeout;pub use timeout::Timeout;
use std::time::{Duration, Instant};#[derive(Clone, Copy, PartialEq, Eq, Hash)]pub struct Timeout {start: Instant,end: Option<Duration>,}impl Timeout {pub fn new(duration: Duration) -> Self {Self {start: Instant::now(),end: Some(duration),}}pub fn infinite() -> Self {Self {start: Instant::now(),end: None,}}pub fn from_millis(millis: u64) -> Self {Self::new(Duration::from_millis(millis))}pub fn is_infinite(&self) -> bool {self.end.is_none()}pub fn is_expired(&self) -> bool {match self.end {Some(duration) if self.elapsed() == duration => true,_ => false,}}pub fn should_continue(&self) -> bool {!self.is_expired()}pub fn elapsed(&self) -> Duration {let val = Instant::now().saturating_duration_since(self.start);match self.end {Some(duration) => duration.min(val),None => val,}}pub fn remaining(&self) -> Option<Duration> {self.end.map(|duration| duration.saturating_sub(self.elapsed()))}}impl std::fmt::Debug for Timeout {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {let mut f = f.debug_struct("Timeout");let f = match self.remaining() {Some(remaining) if remaining < Duration::ZERO => f.field("is_expired", &true),Some(remaining) => f.field("remaining", &remaining),None => f.field("is_infinite", &true),};f.finish()}}
#![allow(unused)]mod commands;mod driver;mod util;use driver::{Driver, SerialDriverBuilder};use tracing::metadata::LevelFilter;fn main() {let subscriber = tracing_subscriber::FmtSubscriber::builder().with_max_level(LevelFilter::TRACE).finish();tracing::subscriber::set_global_default(subscriber).unwrap();let native = SerialDriverBuilder::new("/dev/ttyUSB0");let mut device = native.build();loop {let r = device.read_frame();println!("{:?}", r);}}
#![allow(unused)]mod commands;mod driver;mod util;
use crate::commands::{DeviceCommand, Frame, RawCommandData};use thiserror::Error;mod serial;pub use serial::serial_driver::{SerialDriver, SerialDriverBuilder};#[derive(Debug, Error)]pub enum FrameReadError {#[error("timed out")]Timeout,}#[derive(Debug, Error)]pub enum CommandSequenceError {#[error("the device needs to be reset")]DeviceReset,#[error("the command sequence requested to be cancelled")]Cancel,}#[derive(Debug, Error)]pub enum DriverError {#[error("the device needs to be reset")]DeviceReset,}impl From<DriverError> for CommandSequenceError {fn from(e: DriverError) -> Self {match e {DriverError::DeviceReset => CommandSequenceError::DeviceReset,}}}pub(crate) trait Driver {fn connect(&mut self);fn read_frame(&mut self) -> Result<Frame<DeviceCommand>, FrameReadError>;fn execute_command(&mut self, command: impl CommandSequence);}pub trait CommandSequenceContext {fn send<T>(&mut self, command: Frame<T>) -> Result<(), CommandSequenceError>whereT: Into<RawCommandData>;// FIXME: Do this// fn register_callback(&mut self, command_type: u8, callback: impl CommandSequenceCallbackContext);}pub trait CommandSequence {fn get_name(&self) -> &str;fn execute(&mut self,context: &mut impl CommandSequenceContext,) -> Result<(), CommandSequenceError>;}// FIXME: Do this// pub trait CommandSequenceCallbackContext {// fn try_handle_callback(// &mut self,// context: &mut impl CommandSequenceContext,// command_type: u8,// parameters: impl AsRef<[u8]>,// ) -> bool;// }
use serialport::{DataBits, FlowControl, Parity, SerialPort, StopBits};use std::{borrow::{Borrow, Cow},fmt::Display,io::{Read, Write},ops::Deref,sync::mpsc::{Receiver, Sender, SyncSender},time::Duration,};use thiserror::Error;use tracing::warn;use crate::{commands::{Frame, HostCommand, RawCommandData},driver::{CommandSequence, CommandSequenceContext, CommandSequenceError},util::Timeout,};mod io_buffer;pub mod serial_driver;const BAUD_RATE: u32 = 115_200;const DATA_BITS: DataBits = DataBits::Eight;const STOP_BITS: StopBits = StopBits::One;const PARITY: Parity = Parity::None;const FLOW_CONTROL: FlowControl = FlowControl::None;const SOFT_RESET_DELAY: Duration = Duration::from_millis(1500);const ACK_TIMEOUT: Duration = Duration::from_millis(1550);const READ_TIMEOUT: Duration = Duration::from_millis(100);const BACKOFF_TIME_BASE: Duration = Duration::from_millis(100);const BACKOFF_TIME_FACTOR: Duration = Duration::from_millis(1000);const MAX_PARAMETERS: usize = 256;/// A Z-Wave message has a length field that is 1 byte long. This means that the/// maximum length of a Z-Wave message is 256 bytes, plus the two fields that are/// not included in the length field: the SOF and the checksum.const MAX_MESSAGE_SIZE: usize = MAX_PARAMETERS + 2;#[derive(Debug)]struct SoftResetSequence {}impl Display for SoftResetSequence {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {f.write_str(self.get_name())}}impl CommandSequence for SoftResetSequence {fn execute(&mut self,context: &mut impl CommandSequenceContext,) -> Result<(), CommandSequenceError> {context.send(Frame::Request(HostCommand::SoftReset))?;Ok(())}fn get_name(&self) -> &str {&"connect"}}
use std::{borrow::{Borrow, Cow},io::{Read, Write},marker::PhantomData,time::Duration,};use num_enum::{IntoPrimitive, TryFromPrimitive};use serialport::SerialPort;use thiserror::Error;use tracing::{debug, info, instrument, trace, warn};use crate::{commands::{DeviceCommand, Frame, RawCommandData},driver::{CommandSequenceError, Driver, DriverError, FrameReadError},util::Timeout,};use super::{io_buffer::IOBuffer, CommandSequence, CommandSequenceContext, SoftResetSequence, ACK_TIMEOUT,BACKOFF_TIME_BASE, BACKOFF_TIME_FACTOR, BAUD_RATE, DATA_BITS, FLOW_CONTROL, PARITY, READ_TIMEOUT,SOFT_RESET_DELAY, STOP_BITS,};#[cfg(test)]mod test;#[cfg(unix)]type PlatformNativePort = serialport::TTYPort;#[cfg(windows)]type PlatformNativePort = serialport::COMPort;pub struct SerialDriverBuilder {path: Box<str>,}impl SerialDriverBuilder {pub fn new(path: impl AsRef<str>) -> Self {Self {path: path.as_ref().into(),}}pub fn build<'a>(self) -> SerialDriver<PlatformNativePort> {SerialDriver {open_port: Box::new(move || self.open_serial_port()),serial_port: PortState::Disconnected,buffer: IOBuffer::new(),}}#[instrument(skip(self))]fn open_serial_port<'a>(&self) -> Result<PlatformNativePort, ()> {info!("connecting to serial port: {}", &self.path);serialport::new(Cow::Borrowed(self.path.borrow()), BAUD_RATE).data_bits(DATA_BITS).stop_bits(STOP_BITS).parity(PARITY).flow_control(FLOW_CONTROL).timeout(READ_TIMEOUT).open_native().map_err(|e| {warn!("failed to open native serial device: {}", e);Default::default()})}}enum PortState<P> {Disconnected,Resetting(P),Connected(P),}enum ReadError {TimeoutOrBadFrame,DeviceReset,}impl From<DriverError> for ReadError {fn from(e: DriverError) -> Self {match e {DriverError::DeviceReset => Self::DeviceReset,}}}#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, Eq, PartialEq, Hash)]#[repr(u8)]enum FrameType {Ack = 0x06,Nak = 0x15,Sof = 0x01,Can = 0x18,}#[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, Eq, PartialEq, Hash)]#[repr(u8)]enum DataFrameType {Req = 0x00,Res = 0x01,}pub struct SerialDriver<P = PlatformNativePort, F = Box<dyn FnMut() -> Result<P, ()>>>whereP: SerialPort,F: FnMut() -> Result<P, ()>,{open_port: F,serial_port: PortState<P>,buffer: IOBuffer,}impl<P, F> SerialDriver<P, F>whereP: SerialPort,F: FnMut() -> Result<P, ()>,{#[instrument(skip(self))]fn reset_port(&mut self) {trace!("closing port");match self.serial_port {PortState::Resetting(_) => {}_ => self.serial_port = PortState::Disconnected,}}#[instrument(skip(self, f))]fn use_port<T>(&mut self, f: impl FnOnce(&mut IOBuffer, &mut P) -> T) -> T {match self.serial_port {PortState::Disconnected => {}PortState::Resetting(ref mut port) => return f(&mut self.buffer, port),PortState::Connected(ref mut port) => return f(&mut self.buffer, port),}trace!("port is closed, connecting...");'reconnect: loop {self.serial_port = PortState::Disconnected;match (&mut self.open_port)() {Ok(mut port) => {port.clear(serialport::ClearBuffer::All);if port.write_all(&[FrameType::Nak.into()]).is_err() {continue 'reconnect;}self.serial_port = PortState::Resetting(port);match (SoftResetSequence {}.execute(self)) {Ok(_) => {}Err(e) => {debug!("failed to execute connect command: {:?}", e);continue 'reconnect;}}match std::mem::replace(&mut self.serial_port, PortState::Disconnected) {PortState::Connected(_) => {unreachable!("a port shouldn't transition to connected during reset")}PortState::Resetting(port) => {trace!("connected to serial port");self.serial_port = PortState::Connected(port);}PortState::Disconnected => continue 'reconnect,}match self.serial_port {PortState::Connected(ref mut port) => return f(&mut self.buffer, port),_ => unreachable!(),}}Err(e) => {std::thread::sleep(SOFT_RESET_DELAY);// FIXME: Backoff}}}}fn write_one(&mut self, byte: impl Into<u8>) -> Result<(), DriverError> {self.write_all(&[byte.into()])}#[instrument(skip(self, data))]fn write_all(&mut self, data: impl AsRef<[u8]>) -> Result<(), DriverError> {match self.use_port(|_, f| f.write_all(data.as_ref())) {Err(_) => Err(DriverError::DeviceReset),Ok(_) => Ok(()),}}#[instrument(skip(self, length, timeout))]fn read_at_least(&mut self, length: usize, timeout: Timeout) -> Result<(), ReadError> {self.use_port(|b, p| {while b.available() < length {b.append(length - b.available(), |b| match p.read(b) {Err(e) if e.kind() == std::io::ErrorKind::TimedOut => {if timeout.is_expired() {(0, Err(ReadError::TimeoutOrBadFrame))} else {(0, Ok(()))}}Ok(0) | Err(_) => (0, Err(ReadError::DeviceReset)),Ok(l) => (l, Ok(())),})?;}Ok(())})}#[instrument(skip(self, timeout))]fn receive_frame(&mut self, timeout: Timeout) -> Result<Frame<DeviceCommand>, ReadError> {loop {self.read_at_least(1, timeout)?;let typ = self.buffer.consume(1)[0];match typ.try_into() {Ok(FrameType::Sof) => {}_ => continue,}trace!("received start of frame");match self.read_data_frame(Timeout::new(READ_TIMEOUT)) {Ok(r) => return Ok(r),Err(ReadError::TimeoutOrBadFrame) => {}_ => return Err(ReadError::DeviceReset),}}}#[instrument(skip(self, timeout))]fn read_data_frame(&mut self, timeout: Timeout) -> Result<Frame<DeviceCommand>, ReadError> {self.read_at_least(1, timeout)?;let len = self.buffer.consume(1)[0];trace!("expecting a frame of {} bytes", len);let count = (len) as usize;self.read_at_least(count, timeout)?;let payload = self.buffer.consume(count);let (data, actual_checksum) = payload.split_at(count - 1);let expected_checksum = data.iter().fold(0xff ^ len, |a, b| a ^ b);if actual_checksum[0] != expected_checksum {debug!("received a frame with an invalid checksum: {} != {}",actual_checksum[0], expected_checksum);drop(payload);debug!("sending nak");self.write_one(FrameType::Nak)?;return Err(ReadError::TimeoutOrBadFrame);}let raw_command = RawCommandData::new(data[1], &data[2..]);let command = match raw_command.try_into() {Err(e) => {debug!("received a frame with invalid data: {:?}", e);drop(payload);self.write_one(FrameType::Nak)?;return Err(ReadError::TimeoutOrBadFrame);}Ok(v) => match data[0] {0x00 => Frame::Request(v),0x01 => Frame::Response(v),v => {debug!("received a frame with invalid type: {:02x}", v);drop(payload);self.write_one(FrameType::Nak)?;return Err(ReadError::TimeoutOrBadFrame);}},};Ok(command)}#[instrument(skip(self, frame_type, command))]fn write_data_frame(&mut self,frame_type: DataFrameType,command: RawCommandData,) -> Result<(), DriverError> {let len = command.parameters().len() as u8 + 3;let mut frame_data = Vec::with_capacity(len as usize);let data_frame_type = frame_type.into();frame_data.push(FrameType::Sof.into());frame_data.push(len);frame_data.push(data_frame_type);frame_data.push(command.command_id());frame_data.extend_from_slice(command.parameters());let checksum = command.parameters().iter().fold(0xff ^ len ^ data_frame_type ^ command.command_id(),|a, b| a ^ b,);frame_data.push(checksum);for retry in 0..3 {self.write_all(&frame_data)?;let timeout = Timeout::new(ACK_TIMEOUT);match self.read_at_least(1, timeout) {Err(ReadError::DeviceReset) => return Err(DriverError::DeviceReset),Ok(_) => {}_ => continue,}let frame_type = self.buffer.consume(1)[0].try_into().unwrap_or(FrameType::Nak);trace!("received a {:?} frame", frame_type);match frame_type {FrameType::Sof => {debug!("received a Sof when expecting an ack");match self.write_one(FrameType::Can) {Ok(_) => {}Err(_) => return Err(DriverError::DeviceReset),}}FrameType::Ack => return Ok(()),FrameType::Can | FrameType::Nak => {}};let elapsed = timeout.remaining().expect("this was explicitly set to a finite timeout").min(ACK_TIMEOUT);let duration_to_wait =(BACKOFF_TIME_BASE + BACKOFF_TIME_FACTOR * retry).saturating_sub(elapsed);trace!("waiting for {:?} before retrying send", &duration_to_wait);std::thread::sleep(duration_to_wait);}Err(DriverError::DeviceReset)}}impl<P, F> CommandSequenceContext for SerialDriver<P, F>whereP: SerialPort,F: FnMut() -> Result<P, ()>,{fn send<T>(&mut self, command: crate::commands::Frame<T>) -> Result<(), CommandSequenceError>whereT: Into<RawCommandData>,{match command {Frame::Request(command) => self.write_data_frame(DataFrameType::Req, command.into()).map_err(Into::into),Frame::Response(command) => self.write_data_frame(DataFrameType::Res, command.into()).map_err(Into::into),}}}impl<P, F> Driver for SerialDriver<P, F>whereP: SerialPort,F: FnMut() -> Result<P, ()>,{fn connect(&mut self) {self.use_port(|_, _| ())}fn read_frame(&mut self) -> Result<Frame<DeviceCommand>, FrameReadError> {loop {match self.receive_frame(Timeout::new(READ_TIMEOUT)) {Ok(f) => return Ok(f),Err(ReadError::TimeoutOrBadFrame) => return Err(FrameReadError::Timeout),_ => self.reset_port(),}}}#[instrument(skip(self, command))]fn execute_command(&mut self, mut command: impl CommandSequence) {loop {match command.execute(self) {Err(CommandSequenceError::Cancel) => break,Err(_) => {}Ok(_) => break,}}}}
use mockall::{mock, Sequence};use std::{io::{Read, Write},marker::PhantomData,ops::{Deref, DerefMut},};use serialport::SerialPort;use super::PortState;use crate::driver::{serial::{io_buffer::IOBuffer, serial_driver::FrameType},Driver,};type SerialDriver<'a> =super::SerialDriver<MockPort, Box<dyn FnMut() -> Result<MockPort, ()> + 'a>>;mock!(pub(crate) Port {}impl Write for Port {fn write(&mut self, buf: &[u8]) -> std::io::Result<usize>;fn flush(&mut self) -> std::io::Result<()>;}impl Read for Port {fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize>;}impl SerialPort for Port {fn name(&self) -> Option<String>;fn baud_rate(&self) -> serialport::Result<u32>;fn data_bits(&self) -> serialport::Result<serialport::DataBits>;fn flow_control(&self) -> serialport::Result<serialport::FlowControl>;fn parity(&self) -> serialport::Result<serialport::Parity>;fn stop_bits(&self) -> serialport::Result<serialport::StopBits>;fn timeout(&self) -> std::time::Duration;fn set_baud_rate(&mut self, baud_rate: u32) -> serialport::Result<()>;fn set_data_bits(&mut self, data_bits: serialport::DataBits) -> serialport::Result<()>;fn set_flow_control(&mut self, flow_control: serialport::FlowControl) -> serialport::Result<()>;fn set_parity(&mut self, parity: serialport::Parity) -> serialport::Result<()>;fn set_stop_bits(&mut self, stop_bits: serialport::StopBits) -> serialport::Result<()>;fn set_timeout(&mut self, timeout: std::time::Duration) -> serialport::Result<()>;fn write_request_to_send(&mut self, level: bool) -> serialport::Result<()>;fn write_data_terminal_ready(&mut self, level: bool) -> serialport::Result<()>;fn read_clear_to_send(&mut self) -> serialport::Result<bool>;fn read_data_set_ready(&mut self) -> serialport::Result<bool>;fn read_ring_indicator(&mut self) -> serialport::Result<bool>;fn read_carrier_detect(&mut self) -> serialport::Result<bool>;fn bytes_to_read(&self) -> serialport::Result<u32>;fn bytes_to_write(&self) -> serialport::Result<u32>;fn clear(&self, buffer_to_clear: serialport::ClearBuffer) -> serialport::Result<()>;fn try_clone(&self) -> serialport::Result<Box<dyn SerialPort>>;fn set_break(&self) -> serialport::Result<()>;fn clear_break(&self) -> serialport::Result<()>;});impl Drop for MockPort {fn drop(&mut self) {self.checkpoint()}}fn create_driver<'a>(f: impl FnMut() -> Result<MockPort, ()> + 'a) -> SerialDriver<'a> {SerialDriver {buffer: IOBuffer::new(),serial_port: PortState::Disconnected,open_port: Box::new(f),}}fn driver_checkpoint<'a>(d: &mut SerialDriver<'a>) {match &mut d.serial_port {PortState::Connected(p) | PortState::Resetting(p) => p.checkpoint(),_ => panic!("expected a state with an active port"),}}#[test]fn serial_driver_reconnect() {let mut foo = 1;let mut factory = || {foo += 1;if foo < 3 {return Err(());}let mut port = MockPort::new();port.expect_clear().returning(|_| serialport::Result::Ok(()));port.expect_write().returning(|b| std::io::Result::Ok(b.len()));port.expect_read().return_once(|b| {b[0] = FrameType::Ack.into();std::io::Result::Ok(1)});Ok(port)};let mut driver = create_driver(&mut factory);driver.connect();drop(driver);assert_eq!(foo, 3);}#[test]fn serial_driver_reconnect_with_nak() {let mut factory = || {let mut seq = Sequence::new();let mut port = MockPort::new();port.expect_clear().returning(|_| serialport::Result::Ok(()));port.expect_write().returning(|b| std::io::Result::Ok(b.len()));port.expect_read().times(1).in_sequence(&mut seq).return_once(|b| {b[0] = FrameType::Nak.into();std::io::Result::Ok(1)});port.expect_read().times(1).in_sequence(&mut seq).return_once(|b| {b[0] = FrameType::Ack.into();std::io::Result::Ok(1)});Ok(port)};let mut driver = create_driver(&mut factory);driver.connect();}
use std::{fmt::Debug, ops::Deref};const BUFFER_SIZE: usize = 4096;pub struct IOBuffer {buffer: Box<[u8; BUFFER_SIZE]>,read_offset: u16,write_offset: u16,}impl Debug for IOBuffer {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {f.debug_struct("IOBuffer").field("available",&&self.buffer[(self.read_offset as usize)..(self.write_offset as usize)],).field("remaining_capacity",&(BUFFER_SIZE as u16 - self.write_offset),).finish()}}impl IOBuffer {pub fn new() -> Self {Self {buffer: Box::new([0u8; BUFFER_SIZE]),read_offset: 0,write_offset: 0,}}pub fn consume<'a>(&'a mut self, length: usize) -> ConsumingIOBuffer<'a> {assert!(length <= self.available(),"cannot consume more data than is contained in the buffer");ConsumingIOBuffer {target: self,take: length as u16,}}pub fn append<T>(&mut self,length_estimate: usize,f: impl FnOnce(&mut [u8]) -> (usize, T),) -> T {if self.write_offset as usize + length_estimate > BUFFER_SIZE {self.buffer.copy_within((self.read_offset as usize)..(self.write_offset as usize), 0);self.write_offset -= self.read_offset;self.read_offset = 0;}assert!(self.write_offset as usize + length_estimate <= BUFFER_SIZE,"the buffer does not have enough room for the requested length estimate");let (append, result) = f(&mut self.buffer[(self.write_offset as usize)..]);assert!(self.write_offset as usize + append <= BUFFER_SIZE,"the function wrote more bytes than available in the buffer");self.write_offset += append as u16;result}pub fn available(&self) -> usize {(self.write_offset - self.read_offset) as usize}}pub struct ConsumingIOBuffer<'a> {target: &'a mut IOBuffer,take: u16,}impl<'a> Debug for ConsumingIOBuffer<'a> {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {f.debug_struct("ConsumingIOBuffer").field("target", &self.target).field("take", &self.take).finish()}}impl<'a> Drop for ConsumingIOBuffer<'a> {fn drop(&mut self) {self.target.read_offset += self.take;}}impl<'a> Deref for ConsumingIOBuffer<'a> {type Target = [u8];fn deref(&self) -> &Self::Target {let start = self.target.read_offset as usize;let end = start + self.take as usize;&self.target.buffer[start..end]}}#[cfg(test)]mod test {use super::*;#[test]#[should_panic(expected = "cannot consume more data than is contained in the buffer")]fn io_buffer_consume_too_much() {let mut sut = IOBuffer::new();sut.append(5, |f| {f[..5].copy_from_slice(&[1, 2, 3, 4, 5]);(5, ())});sut.consume(6);}#[test]#[should_panic(expected = "the buffer does not have enough room for the requested length estimate")]fn io_buffer_append_too_much() {let mut sut = IOBuffer::new();sut.append(BUFFER_SIZE + 1, |f| (5, ()));}#[test]#[should_panic(expected = "the function wrote more bytes than available in the buffer")]fn io_buffer_write_too_much() {let mut sut = IOBuffer::new();sut.append(1, |f| (BUFFER_SIZE + 1, ()));}#[test]fn io_buffer_append() {let mut sut = IOBuffer::new();sut.append(5, |f| {f[..5].copy_from_slice(&[1, 2, 3, 4, 5]);(5, ())});assert_eq!(sut.available(), 5);let cons = sut.consume(5);assert_eq!(cons.deref(), &[1, 2, 3, 4, 5]);drop(cons);assert_eq!(sut.available(), 0);}#[test]fn io_write_read_write_read() {let mut sut = IOBuffer::new();sut.append(5, |f| {f[..5].copy_from_slice(&[1, 2, 3, 4, 5]);(5, ())});assert_eq!(sut.available(), 5);let cons = sut.consume(5);assert_eq!(cons.deref(), &[1, 2, 3, 4, 5]);drop(cons);sut.append(5, |f| {f[..5].copy_from_slice(&[6, 7, 8, 9, 10]);(5, ())});assert_eq!(sut.available(), 5);let cons = sut.consume(5);assert_eq!(cons.deref(), &[6, 7, 8, 9, 10]);drop(cons);assert_eq!(sut.available(), 0);}#[test]fn io_write_write_read_read() {let mut sut = IOBuffer::new();sut.append(5, |f| {f[..6].copy_from_slice(&[1, 2, 3, 4, 5, 6]);(6, ())});assert_eq!(sut.available(), 6);sut.append(5, |f| {f[..4].copy_from_slice(&[7, 8, 9, 10]);(4, ())});assert_eq!(sut.available(), 10);let cons = sut.consume(3);assert_eq!(cons.deref(), &[1, 2, 3]);drop(cons);assert_eq!(sut.available(), 7);let cons = sut.consume(7);assert_eq!(cons.deref(), &[4, 5, 6, 7, 8, 9, 10]);drop(cons);assert_eq!(sut.available(), 0);}#[test]fn io_write_read_many() {for _ in 0..1000 {let mut sut = IOBuffer::new();sut.append(5, |f| {f[..6].copy_from_slice(&[1, 2, 3, 4, 5, 6]);(6, ())});assert_eq!(sut.available(), 6);sut.append(5, |f| {f[..4].copy_from_slice(&[7, 8, 9, 10]);(4, ())});assert_eq!(sut.available(), 10);let cons = sut.consume(3);assert_eq!(cons.deref(), &[1, 2, 3]);drop(cons);assert_eq!(sut.available(), 7);let cons = sut.consume(7);assert_eq!(cons.deref(), &[4, 5, 6, 7, 8, 9, 10]);drop(cons);assert_eq!(sut.available(), 0);}}}
use num_enum::{Default, IntoPrimitive, TryFromPrimitive};#[derive(Debug, Clone)]pub enum Frame<T> {Request(T),Response(T),}#[derive(Debug, Clone)]pub struct RawCommandData {command_id: u8,parameters: Vec<u8>,}impl RawCommandData {pub fn new(command_id: u8, parameters: impl AsRef<[u8]>) -> Self {Self {command_id,parameters: parameters.as_ref().to_vec(),}}}impl Default for RawCommandData {fn default() -> Self {Self {command_id: Default::default(),parameters: Default::default(),}}}impl RawCommandData {pub fn command_id(&self) -> u8 {self.command_id}pub fn parameters(&self) -> &[u8] {self.parameters.as_ref()}}#[derive(Debug)]pub enum DeviceCommand {SerialApiStarted(SerialApiStarted),Unknown(RawCommandData),}impl TryFrom<RawCommandData> for DeviceCommand {type Error = ();fn try_from(value: RawCommandData) -> Result<Self, Self::Error> {match value.command_id() {0x0A => Ok(DeviceCommand::SerialApiStarted(value.try_into()?)),_ => Ok(DeviceCommand::Unknown(value)),}}}#[derive(Debug)]pub enum HostCommand {SoftReset,Unknown(RawCommandData),}impl From<HostCommand> for RawCommandData {fn from(command: HostCommand) -> Self {match command {HostCommand::SoftReset => Self {command_id: 0x08,..Default::default()},HostCommand::Unknown(data) => data,}}}#[derive(Debug, Clone, Copy, IntoPrimitive, TryFromPrimitive, PartialEq, Eq, PartialOrd, Ord, Hash,)]#[repr(u8)]pub enum SerialApiWakeUpReason {/// The Z-Wave API Module has been woken up by reset or external interrupt.Reset = 0x00,/// The Z-Wave API Module has been woken up by a timer.WakeUpTimer = 0x01,/// The Z-Wave API Module has been woken up by a Wake Up Beam.WakeUpBeam = 0x02,/// The Z-Wave API Module has been woken up by a reset triggered by the watchdog.WatchdogReset = 0x03,/// The Z-Wave API Module has been woken up by an external interrupt.ExternalInterrupt = 0x04,/// The Z-Wave API Module has been woken up by a powering up.PowerUp = 0x05,/// The Z-Wave API Module has been woken up by USB Suspend.USBSuspend = 0x06,/// The Z-Wave API Module has been woken up by a reset triggered by software.SoftwareReset = 0x07,/// The Z-Wave API Module has been woken up by an emergency watchdog reset.EmergencyWatchdogReset = 0x08,/// The Z-Wave API Module has been woken up by a reset triggered by brownout circuit.BrownoutCircuit = 0x09,/// The Z-Wave API Module has been woken up by an unknown reason.Unknown = 0xff,}#[derive(Debug)]pub struct SerialApiStarted {wake_up_reason: SerialApiWakeUpReason,watch_dog_enabled: bool,device_options: DeviceOptions,generic_device_class: u8,specific_device_class: u8,supported_command_classes: Vec<u8>,supported_protocols: DeviceProtocols,}#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]pub struct DeviceOptions(u8);impl DeviceOptions {pub fn value(&self) -> u8 {self.0}pub fn listening(&self) -> bool {(self.0 & 0b1000_0000) != 0}}impl core::fmt::Debug for DeviceOptions {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {let mut f = f.debug_set();let mut f = if (self.listening()) {f.entry(&"listening")} else {&mut f};f.finish()}}impl From<u8> for DeviceOptions {fn from(byte: u8) -> Self {Self(byte)}}pub struct DeviceProtocols(u8);impl DeviceProtocols {pub fn value(&self) -> u8 {self.0}pub fn long_range(&self) -> bool {(self.0 & 0b0000_0001) != 0}}impl Default for DeviceProtocols {fn default() -> Self {Self(Default::default())}}impl core::fmt::Debug for DeviceProtocols {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {let mut f = f.debug_set();let mut f = if (self.long_range()) {f.entry(&"long_range")} else {&mut f};f.finish()}}impl From<u8> for DeviceProtocols {fn from(byte: u8) -> Self {Self(byte)}}impl TryFrom<RawCommandData> for SerialApiStarted {type Error = ();fn try_from(value: RawCommandData) -> Result<Self, Self::Error> {if value.command_id() != 0x0A {return Err(());}// FIXME: All of this is shitlet wake_up_reason = value.parameters()[0].try_into().map_err(|_| ())?;let watch_dog_enabled = value.parameters()[1] != 0;let device_options = value.parameters()[2].into();let generic_device_class = value.parameters()[3];let specific_device_class = value.parameters()[4];let supported_command_classes_len = value.parameters()[5] as usize;let supported_command_classes =value.parameters()[6..(6 + supported_command_classes_len)].to_vec();let supported_protocols = if value.parameters().len() > 6 + supported_command_classes_len {value.parameters()[6 + supported_command_classes_len].into()} else {Default::default()};Ok(Self {wake_up_reason,watch_dog_enabled,device_options,generic_device_class,specific_device_class,supported_command_classes,supported_protocols,})}}
[package]name = "sockt-ecosystem-zwave"version = "0.1.0"edition = "2021"[dependencies]serialport = "4.2"thiserror = "1.0"tracing = "0.1"tracing-subscriber = "0.3"num_enum = "0.5"[dev-dependencies]mockall = "0.11"