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>
where
T: 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, ()>>>
where
P: SerialPort,
F: FnMut() -> Result<P, ()>,
{
open_port: F,
serial_port: PortState<P>,
buffer: IOBuffer,
}
impl<P, F> SerialDriver<P, F>
where
P: 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>
where
P: SerialPort,
F: FnMut() -> Result<P, ()>,
{
fn send<T>(&mut self, command: crate::commands::Frame<T>) -> Result<(), CommandSequenceError>
where
T: 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>
where
P: 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 shit
let 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"