diff --git a/crates/windlass/src/application.rs b/crates/windlass/src/application.rs index 57e06dc..f7715ec 100644 --- a/crates/windlass/src/application.rs +++ b/crates/windlass/src/application.rs @@ -1,17 +1,25 @@ //! Application layer protocol - deals with commands, responses, etc -use std::{io::Write, time::Instant}; +use std::{io::Write, num::TryFromIntError, time::Instant}; -use bytes::BytesMut; +use bytes::{Bytes, BytesMut}; use thiserror::Error; -use tracing::{debug, trace}; +use tracing::{trace, warn}; + +pub use crate::dictionary::{ + CanonicalId, ConfigVar, Dictionary, EnumDef, Error as DictionaryError, RealId, +}; use crate::{ - dictionary::{self, Dictionary}, + dictionary, encoding::{parse_vlq_int, MessageDecodeError}, + messages::{Identify, IdentifyResponse, Message, Response}, transport, }; +const IDENTIFY_CMD_ID: RealId = 1; +const IDENTIFY_RESP_ID: RealId = 0; + /// Application-level connection to a klipper MCU pub struct Connection { transport: transport::Connection, @@ -23,10 +31,9 @@ impl Connection { pub fn receive_data(&mut self, buf: &[u8], receive_time: Instant) -> Result<(), Error> { self.transport.receive_data(buf, receive_time); - while let Some(frame) = self.transport.pop_frame() { - let mut rest = &*frame.payload; - while !rest.is_empty() { - self.receive_next_message(&mut rest, frame.receive_time)?; + while let Some(mut frame) = self.transport.pop_frame() { + while !frame.payload.is_empty() { + self.receive_next_message(&mut frame.payload, receive_time)?; } } @@ -43,18 +50,23 @@ impl Connection { self.transport.write_data(w).map_err(Into::into) } + /// Get the data dictionary sent by the MCU, if it is fully received. + pub const fn dictionary(&self) -> Option<&Dictionary> { + self.dictionary.get() + } + fn receive_next_message( &mut self, - payload: &mut &[u8], + payload: &mut Bytes, receive_time: Instant, ) -> Result<(), Error> { - let msg_id = parse_vlq_int(payload)?; - trace!(msg_id = msg_id, "processing message"); + let real_id = u16::try_from(parse_vlq_int(payload)?)?; + trace!(real_id = real_id, "processing message"); - let Some(_) = self.dictionary.get() else { + let Some(dict) = self.dictionary.get() else { // message must be an identify response, otherwise its probably just garbage - if msg_id != 0 { - trace!(msg_id = msg_id, "not an identify response, skipping"); + if real_id != IDENTIFY_RESP_ID { + trace!(msg_id = real_id, "not an identify response, skipping"); return Ok(()); } @@ -64,7 +76,24 @@ impl Connection { return Ok(()); }; - debug!("received message with {}", msg_id); + let Some(canonical_id) = dict.resp_real_to_canonical(real_id) else { + return Ok(()); + }; + trace!( + real_id = real_id, + canonical_id = canonical_id, + "resolved canonical id" + ); + + match Response::deserialise(canonical_id, payload) { + Ok(resp) => { + trace!(resp = ?resp, "parsed response message"); + } + Err(e) => { + warn!(err = ?e, "message decode error encountered, trying to continue anyway"); + } + } + Ok(()) } } @@ -74,7 +103,13 @@ impl Default for Connection { let mut transport = transport::Connection::default(); // Immediately send request for first part of dictionary. - transport.push_message(&Identify::encode(0, 40).payload.content); + let mut buf = BytesMut::new(); + Identify { + offset: 0, + count: 40, + } + .encode(&mut buf, IDENTIFY_CMD_ID); + transport.push_message(buf.freeze()); Self { transport, dictionary: DictionaryReceiveState::InProgress { @@ -102,7 +137,7 @@ impl DictionaryReceiveState { pub fn receive_response( &mut self, transport: &mut transport::Connection, - payload: &mut &[u8], + payload: &mut Bytes, receive_time: Instant, ) -> Result<(), Error> { trace!("decoding part of identify response"); @@ -119,7 +154,7 @@ impl DictionaryReceiveState { return Ok(()); } - so_far.extend(resp.data); + so_far.extend(&resp.data); if !resp.data.is_empty() { trace!( @@ -127,21 +162,20 @@ impl DictionaryReceiveState { len = resp.data.len(), "received more of dictionary" ); - transport.push_message( - &Identify::encode( - u32::try_from(so_far.len()).expect("identify data length overflowed u32!"), - 40, - ) - .payload - .content, - ); + let mut buf = BytesMut::new(); + Identify { + offset: u32::try_from(so_far.len()).expect("identify data length overflowed u32!"), + count: 40, + } + .encode(&mut buf, IDENTIFY_CMD_ID); + transport.push_message(buf.freeze()); return Ok(()); } // done receiving dictionary trace!("done receiving dictionary, attempting decode"); - let dict = Dictionary::from_complete_lookup_resp(so_far)?; + let dict = Dictionary::from_complete_identify_resp(so_far)?; trace!(dict = ?dict, "dictionary in place"); *self = Self::Done(dict); @@ -162,12 +196,7 @@ pub enum Error { #[error("message decode error: {0}")] MessageDecodeError(#[from] MessageDecodeError), -} -mcu_command!(Identify, "identify" = 1, offset: u32, count: u8); -mcu_reply!( - IdentifyResponse, - "identify_response" = 0, - offset: u32, - data: Vec, -); + #[error("message id out of bounds: {0}")] + MessageIdOutOfBounds(#[from] TryFromIntError), +} diff --git a/crates/windlass/src/dictionary.rs b/crates/windlass/src/dictionary.rs index 2f5982c..e34a3ab 100644 --- a/crates/windlass/src/dictionary.rs +++ b/crates/windlass/src/dictionary.rs @@ -1,31 +1,39 @@ //! Metadata about the Application-layer protocol in use use std::{ - collections::BTreeMap, + collections::{BTreeMap, HashMap}, io::{self, Read}, ops::RangeInclusive, }; +use tracing::trace; + use crate::{ encoding::encode_vlq_int, - messages::{MessageParser, MessageSkipperError}, + messages::{cmd_name_to_canonical, resp_name_to_canonical}, }; +/// A message/response ID as used on the wire, which can vary per-session +pub type RealId = u16; + +/// A message/response ID fixed at compile time +pub type CanonicalId = usize; + /// Deserialised data dictionary returned by the microcontroller. /// See [klipper protocol docs](https://www.klipper3d.org/Protocol.html#data-dictionary) #[derive(Debug)] pub struct Dictionary { - /// Map of message names to message IDs - message_ids: BTreeMap, + /// Map of canonical command IDs to real ones + cmd_real: HashMap, - /// Map of message IDs to parsers - message_parsers: BTreeMap, + /// Map of real command IDs to canonical ones + resp_canonical: HashMap, /// Map of config variable name to variable values - config: BTreeMap, + config: HashMap, /// Map of declared [enumerations](https://www.klipper3d.org/Protocol.html#declaring-enumerations) - enumerations: BTreeMap, + enumerations: HashMap, /// Build version, if specified build_version: Option, @@ -38,24 +46,6 @@ pub struct Dictionary { } impl Dictionary { - /// Get message id by name - pub fn message_id(&self, name: &str) -> Option { - self.message_ids.get(name).copied() - } - - /// Get message name by id - pub fn message_name(&self, id: u16) -> Option<&str> { - self.message_ids - .iter() - .find(|(_, i)| **i == id) - .map(|(name, _)| name.as_str()) - } - - /// Get message name by id - pub fn message_parser(&self, id: u16) -> Option<&MessageParser> { - self.message_parsers.get(&id) - } - /// Get config variable by name pub fn config_var(&self, var_name: &str) -> Option<&ConfigVar> { self.config.get(var_name) @@ -81,6 +71,16 @@ impl Dictionary { &self.extra } + /// Map a real ID (from an MCU response) to a canonical ID. + pub(crate) fn resp_real_to_canonical(&self, real_id: RealId) -> Option { + self.resp_canonical.get(&real_id).copied() + } + + /// Map a canonical ID (from a pending MCU command) to a real ID. + pub(crate) fn cmd_canonical_to_real(&self, canonical_id: CanonicalId) -> Option { + self.cmd_real.get(&canonical_id).copied() + } + /// Figure out the actual value of a command tag #[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)] fn map_tag(tag: i16) -> Result { @@ -99,12 +99,15 @@ impl Dictionary { } } - pub fn from_complete_lookup_resp(data: &[u8]) -> Result { + /// Load the dictionary from a complete identify response + /// The identify response returns a zlib compressed JSON file + pub(crate) fn from_complete_identify_resp(data: &[u8]) -> Result { + trace!(len_before = data.len(), "decoding dictionary zlib data"); let mut decoder = flate2::read::ZlibDecoder::new(data); - let mut buf = Vec::new(); decoder.read_to_end(&mut buf).map_err(Error::IO)?; + trace!("deserialising"); let raw_dict: Raw = serde_json::from_slice(&buf).map_err(Error::Json)?; let dict = Self::try_from(raw_dict)?; @@ -116,39 +119,37 @@ impl TryFrom for Dictionary { type Error = Error; fn try_from(raw: Raw) -> Result { - let mut message_ids = BTreeMap::new(); - let mut message_parsers = BTreeMap::new(); - - for (cmd, tag) in raw.commands { - let mut split = cmd.split(' '); - let name = split.next().ok_or(Error::EmptyCommand)?; - let parser = MessageParser::new(name, split) - .map_err(|e| Error::InvalidCommandFormat(name.to_string(), e))?; - let tag = Self::map_tag(tag)?; - message_parsers.insert(tag, parser); - message_ids.insert(name.to_string(), tag); - } - - for (resp, tag) in raw.responses { - let mut split = resp.split(' '); - let name = split.next().ok_or(Error::EmptyCommand)?; - let parser = MessageParser::new(name, split) - .map_err(|e| Error::InvalidCommandFormat(name.to_string(), e))?; - let tag = Self::map_tag(tag)?; - message_parsers.insert(tag, parser); - message_ids.insert(name.to_string(), tag); - } - - for (msg, tag) in raw.output { - let parser = MessageParser::new_output(&msg) - .map_err(|e| Error::InvalidCommandFormat(msg.to_string(), e))?; - let tag = Self::map_tag(tag)?; - message_parsers.insert(tag, parser); - } + let cmd_real = raw + .commands + .into_iter() + .map(|(n, real_tag)| Ok((n, Self::map_tag(real_tag)?))) + .filter_map(|res| -> Option> { + let (name, real_id) = match res { + Ok(x) => x, + Err(e) => return Some(Err(e)), + }; + let canonical_id = cmd_name_to_canonical(name.split(' ').next().unwrap())?; + Some(Ok((canonical_id, real_id))) + }) + .collect::, _>>()?; + + let resp_canonical = raw + .responses + .into_iter() + .map(|(n, real_tag)| Ok((n, Self::map_tag(real_tag)?))) + .filter_map(|res| -> Option> { + let (name, real_id) = match res { + Ok(x) => x, + Err(e) => return Some(Err(e)), + }; + let canonical_id = resp_name_to_canonical(name.split(' ').next().unwrap())?; + Some(Ok((real_id, canonical_id))) + }) + .collect::, _>>()?; Ok(Self { - message_ids, - message_parsers, + cmd_real, + resp_canonical, config: raw.config, enumerations: raw .enumerations @@ -164,7 +165,7 @@ impl TryFrom for Dictionary { /// Definition of an [enumerations](https://www.klipper3d.org/Protocol.html#declaring-enumerations)'s possible variables. #[derive(Debug)] -pub struct EnumDef(BTreeMap); +pub struct EnumDef(HashMap); impl EnumDef { /// Get the range of valid values for this enum @@ -214,16 +215,8 @@ pub enum Error { #[error("empty command found")] EmptyCommand, - /// Received a command in an invalid format - #[error("invalid command format: {0}")] - InvalidCommandFormat(String, MessageSkipperError), - - /// Received an output string with an invalid format - #[error("invalid output format: {0}")] - InvalidOutputFormat(String, MessageSkipperError), - /// Received a command with an invalid tag - #[error("command tag {0} output valid range of -32..95")] + #[error("command tag {0} has tag outside of -32..95")] InvalidCommandTag(u16), #[error("error decompressing zlib in identify response")] @@ -235,22 +228,22 @@ pub enum Error { /// The raw JSON data dictionary response from the microcontroller #[derive(Debug, serde::Deserialize)] -pub struct Raw { - #[serde(default)] - config: BTreeMap, - +struct Raw { #[serde(default)] - enumerations: BTreeMap>, + config: HashMap, #[serde(default)] - commands: BTreeMap, + enumerations: HashMap>, #[serde(default)] - responses: BTreeMap, + commands: HashMap, #[serde(default)] - output: BTreeMap, + responses: HashMap, + // TODO + // #[serde(default)] + // output: BTreeMap, #[serde(default)] build_versions: Option, @@ -273,7 +266,7 @@ pub enum ConfigVar { /// Either one string corresponds to a single integer value, or it ends in a number and corresponds to a range of integer values. #[derive(Debug, serde::Deserialize)] #[serde(untagged)] -enum EnumValue { +pub enum EnumValue { Single(i64), Range(i64, i64), } diff --git a/crates/windlass/src/encoding.rs b/crates/windlass/src/encoding.rs index 213a83e..530f147 100644 --- a/crates/windlass/src/encoding.rs +++ b/crates/windlass/src/encoding.rs @@ -1,6 +1,4 @@ -use std::fmt::Display; - -use crate::messages::MessageSkipperError; +use bytes::{Buf, BufMut, Bytes, BytesMut}; /// Calculate the CRC-16 of the given buffer pub fn crc16(buf: &[u8]) -> u16 { @@ -16,200 +14,78 @@ pub fn crc16(buf: &[u8]) -> u16 { /// Encode the given integer as a [VLQ](https://www.klipper3d.org/Protocol.html#variable-length-quantities), pushing it to the back of `output`. #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)] -pub fn encode_vlq_int(output: &mut Vec, v: u32) { +pub fn encode_vlq_int(output: &mut impl BufMut, v: u32) { // This is ok, as we're just doing bit fiddling and the signedness // will be figured out at the other side again. let sv = v as i32; // Likewise for these casts if !(-(1 << 26)..(3 << 26)).contains(&sv) { - output.push(((sv >> 28) & 0x7F) as u8 | 0x80); + output.put_u8(((sv >> 28) & 0x7F) as u8 | 0x80); } if !(-(1 << 19)..(3 << 19)).contains(&sv) { - output.push(((sv >> 21) & 0x7F) as u8 | 0x80); + output.put_u8(((sv >> 21) & 0x7F) as u8 | 0x80); } if !(-(1 << 12)..(3 << 12)).contains(&sv) { - output.push(((sv >> 14) & 0x7F) as u8 | 0x80); + output.put_u8(((sv >> 14) & 0x7F) as u8 | 0x80); } if !(-(1 << 5)..(3 << 5)).contains(&sv) { - output.push(((sv >> 7) & 0x7F) as u8 | 0x80); + output.put_u8(((sv >> 7) & 0x7F) as u8 | 0x80); } - output.push((sv & 0x7F) as u8); + output.put_u8((sv & 0x7F) as u8); } /// Parse a [VLQ](https://www.klipper3d.org/Protocol.html#variable-length-quantities) from the top of `data`. #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)] -pub fn parse_vlq_int(data: &mut &[u8]) -> Result { +pub fn parse_vlq_int(data: &mut impl Buf) -> Result { // Casting is fine here, as we're just doing bit fiddling let mut c = u32::from(next_byte(data)?); - let mut v = c & 0x7F; - if (c & 0x60) == 0x60 { - v |= (-0x20i32) as u32; - } - while c & 0x80 != 0 { + let mut v = c & 0b0111_1111; + // TODO: broken? something to do with klipper's negative numbers + // if (c & 0b0110_0000) == 0b0110_0000 { + // v |= (-0x20i32) as u32; + // } + while c & 0b1000_0000 != 0 { c = u32::from(next_byte(data)?); - v = (v << 7) | (c & 0x7F); + v = (v << 7) | (c & 0b0111_1111); } Ok(v) } /// Read the next byte from `data`, or error -pub fn next_byte(data: &mut &[u8]) -> Result { - if data.is_empty() { - Err(MessageDecodeError::UnexpectedEof) +pub fn next_byte(data: &mut impl Buf) -> Result { + if data.has_remaining() { + Ok(data.get_u8()) } else { - let v = data[0]; - *data = &data[1..]; - Ok(v) + Err(MessageDecodeError::UnexpectedEof) } } /// A type which can possibly be read from the top of a data buffer. -pub trait Readable<'de>: Sized { +pub trait Readable: Sized { /// Attempt to deserialise a value from the top of `data`. - fn read(data: &mut &'de [u8]) -> Result; - - /// Skip over a value of this type at the top of `data`. - fn skip(data: &mut &'de [u8]) -> Result<(), MessageDecodeError> { - Self::read(data).map(|_| ()) - } + fn read(data: &mut Bytes) -> Result; } /// A type which can be written to the back of a data buffer. pub trait Writable: Sized { /// Write this type to the back of `output`. - fn write(&self, output: &mut Vec); -} - -/// TODO: Docs -pub trait Borrowable: Sized { - type Borrowed<'a> - where - Self: 'a; - fn from_borrowed(src: Self::Borrowed<'_>) -> Self; -} - -/// The type of an encoded field -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum FieldType { - U32, - I32, - U16, - I16, - U8, - String, - ByteArray, -} - -impl FieldType { - /// Skip over a field of this type at the top of `input`. - pub(crate) fn skip(self, input: &mut &[u8]) -> Result<(), MessageDecodeError> { - match self { - Self::U32 => ::skip(input), - Self::I32 => ::skip(input), - Self::U16 => ::skip(input), - Self::I16 => ::skip(input), - Self::U8 => ::skip(input), - Self::String => <&str as Readable>::skip(input), - Self::ByteArray => <&[u8] as Readable>::skip(input), - } - } - - /// Read a field of this type from the top of `input`. - pub(crate) fn read(self, input: &mut &[u8]) -> Result { - Ok(match self { - Self::U32 => FieldValue::U32(::read(input)?), - Self::I32 => FieldValue::I32(::read(input)?), - Self::U16 => FieldValue::U16(::read(input)?), - Self::I16 => FieldValue::I16(::read(input)?), - Self::U8 => FieldValue::U8(::read(input)?), - Self::String => FieldValue::String(<&str as Readable>::read(input)?.into()), - Self::ByteArray => FieldValue::ByteArray(<&[u8] as Readable>::read(input)?.into()), - }) - } - - /// Deserialise the field type from a printf style declaration - pub(crate) fn from_msg(s: &str) -> Result { - match s { - "%u" => Ok(Self::U32), - "%i" => Ok(Self::I32), - "%hu" => Ok(Self::U16), - "%hi" => Ok(Self::I16), - "%c" => Ok(Self::U8), - "%s" => Ok(Self::String), - "%*s" | "%.*s" => Ok(Self::ByteArray), - s => Err(MessageSkipperError::InvalidFormatFieldType(s.to_string())), - } - } - - /// Deserialise the next field type from a printf style declaration, also returning the rest of the string. - #[allow(clippy::option_if_let_else)] - pub(crate) fn from_format(s: &str) -> Result<(Self, &str), MessageSkipperError> { - if let Some(rest) = s.strip_prefix("%u") { - Ok((Self::U32, rest)) - } else if let Some(rest) = s.strip_prefix("%i") { - Ok((Self::I32, rest)) - } else if let Some(rest) = s.strip_prefix("%hu") { - Ok((Self::U16, rest)) - } else if let Some(rest) = s.strip_prefix("%hi") { - Ok((Self::I16, rest)) - } else if let Some(rest) = s.strip_prefix("%c") { - Ok((Self::U8, rest)) - } else if let Some(rest) = s.strip_prefix("%.*s") { - Ok((Self::ByteArray, rest)) - } else if let Some(rest) = s.strip_prefix("%*s") { - Ok((Self::String, rest)) - } else { - Err(MessageSkipperError::InvalidFormatFieldType(s.to_string())) - } - } -} - -/// The decoded value of a field -#[derive(Debug)] -pub enum FieldValue { - U32(u32), - I32(i32), - U16(u16), - I16(i16), - U8(u8), - String(String), - ByteArray(Vec), -} - -impl Display for FieldValue { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::U32(v) => write!(f, "{v}"), - Self::I32(v) => write!(f, "{v}"), - Self::U16(v) => write!(f, "{v}"), - Self::I16(v) => write!(f, "{v}"), - Self::U8(v) => write!(f, "{v}"), - Self::String(v) => write!(f, "{v}"), - Self::ByteArray(v) => write!(f, "{v:?}"), - } - } -} - -/// A type which can be expressed as a [`FieldType`] -pub trait ToFieldType: Sized { - /// Get the corresponding [`FieldType`]. - fn as_field_type() -> FieldType; + fn write(&self, output: &mut BytesMut); } /// Implements [`Readable`], [`Writable`], and [`ToFieldType`] for the given integer type. macro_rules! int_readwrite { ( $type:tt, $field_type:expr ) => { - impl Readable<'_> for $type { - fn read(data: &mut &[u8]) -> Result { + impl Readable for $type { + fn read(data: &mut bytes::Bytes) -> Result { #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] parse_vlq_int(data).map(|v| v as $type) } } impl Writable for $type { - fn write(&self, output: &mut Vec) { + fn write(&self, output: &mut bytes::BytesMut) { #[allow( clippy::cast_possible_wrap, clippy::cast_lossless, @@ -218,19 +94,6 @@ macro_rules! int_readwrite { encode_vlq_int(output, *self as u32) } } - - impl Borrowable for $type { - type Borrowed<'a> = Self; - fn from_borrowed(src: Self::Borrowed<'_>) -> Self { - src - } - } - - impl ToFieldType for $type { - fn as_field_type() -> FieldType { - $field_type - } - } }; } @@ -240,100 +103,51 @@ int_readwrite!(u16, FieldType::U16); int_readwrite!(i16, FieldType::I16); int_readwrite!(u8, FieldType::U8); -impl Readable<'_> for bool { - fn read(data: &mut &[u8]) -> Result { +impl Readable for bool { + fn read(data: &mut Bytes) -> Result { parse_vlq_int(data).map(|v| v != 0) } } impl Writable for bool { - fn write(&self, output: &mut Vec) { + fn write(&self, output: &mut BytesMut) { encode_vlq_int(output, u32::from(*self)); } } -impl Borrowable for bool { - type Borrowed<'a> = Self; - fn from_borrowed(src: Self::Borrowed<'_>) -> Self { - src - } -} - -impl ToFieldType for bool { - fn as_field_type() -> FieldType { - FieldType::U8 - } -} - -impl<'de> Readable<'de> for &'de [u8] { - fn read(data: &mut &'de [u8]) -> Result<&'de [u8], MessageDecodeError> { - let len = parse_vlq_int(data)? as usize; - if data.len() < len { - Err(MessageDecodeError::UnexpectedEof) - } else { - let ret = &data[..len]; - *data = &data[len..]; - Ok(ret) - } - } - - fn skip(data: &mut &[u8]) -> Result<(), MessageDecodeError> { +impl Readable for Bytes { + fn read(data: &mut Bytes) -> Result { let len = parse_vlq_int(data)? as usize; if data.len() < len { Err(MessageDecodeError::UnexpectedEof) } else { - *data = &data[len..]; - Ok(()) + Ok(data.split_to(len)) } } } -impl Writable for &[u8] { - fn write(&self, output: &mut Vec) { +impl Writable for Bytes { + fn write(&self, output: &mut BytesMut) { #[allow(clippy::cast_possible_truncation)] encode_vlq_int(output, self.len() as u32); output.extend_from_slice(self); } } -impl Borrowable for Vec { - type Borrowed<'a> = &'a [u8]; - fn from_borrowed(src: Self::Borrowed<'_>) -> Self { - src.into() - } -} - -impl ToFieldType for Vec { - fn as_field_type() -> FieldType { - FieldType::ByteArray - } -} - -impl<'de> Readable<'de> for &'de str { - fn read(data: &mut &'de [u8]) -> Result<&'de str, MessageDecodeError> { - let len = parse_vlq_int(data)? as usize; - if data.len() < len { - Err(MessageDecodeError::UnexpectedEof) - } else { - let ret = &data[..len]; - *data = &data[len..]; - Ok(std::str::from_utf8(ret)?) - } - } - - fn skip(data: &mut &[u8]) -> Result<(), MessageDecodeError> { +impl Readable for String { + fn read(data: &mut Bytes) -> Result { let len = parse_vlq_int(data)? as usize; if data.len() < len { Err(MessageDecodeError::UnexpectedEof) } else { - *data = &data[len..]; - Ok(()) + let ret = data.split_to(len); + Ok(Self::from_utf8(ret.to_vec())?) } } } impl Writable for &str { - fn write(&self, output: &mut Vec) { + fn write(&self, output: &mut BytesMut) { let bytes = self.as_bytes(); #[allow(clippy::cast_possible_truncation)] encode_vlq_int(output, bytes.len() as u32); @@ -341,12 +155,6 @@ impl Writable for &str { } } -impl ToFieldType for String { - fn as_field_type() -> FieldType { - FieldType::String - } -} - /// Error encountered when decoding a message #[derive(thiserror::Error, Debug, Clone)] pub enum MessageDecodeError { @@ -356,5 +164,5 @@ pub enum MessageDecodeError { /// A received string could not be decoded as UTF8 #[error("invalid utf8 string")] - Utf8Error(#[from] std::str::Utf8Error), + Utf8Error(#[from] std::string::FromUtf8Error), } diff --git a/crates/windlass/src/macros.rs b/crates/windlass/src/macros.rs index 513ff77..d43e9a6 100644 --- a/crates/windlass/src/macros.rs +++ b/crates/windlass/src/macros.rs @@ -1,205 +1,82 @@ -/// Declare a host -> device message -/// -/// Declares the format of a message sent from host to device. The general format is as follows: -/// -/// ```text -/// mcu_command!(, ""( = ) [, arg: type, ..]); -/// ``` -/// -/// A struct with the given name will be defined, with relevant interfaces. The message name and -/// arguments will be matched to the MCU at runtime. The struct name has no restrictions, but it is -/// recommended to pick a `SnakeCased` version of the command name. -/// -/// Optionally an `id` can be directly specified. Generally this is not needed. When not specified, -/// it will be automatically inferred at runtime and matched to the dictionary retrieved from the -/// target MCU. -/// -/// Arguments can be specified, they will be mapped to the relevant Klipper argument types. The -/// supported types and mappings are as follows: -/// -/// | Rust type | Format string | -/// |------------|---------------| -/// | `u32` | `%u` | -/// | `i32` | `%i` | -/// | `u16` | `%hu` | -/// | `i16` | `%hi` | -/// | `u8` | `%c` | -/// | `&'a [u8]` | `%.*s`, `%*s` | -/// | `&'a str` | `%s` | -/// -/// Note that the buffer types take a lifetime. This must always be `'a`. -/// -/// # Examples -/// -/// ```ignore -/// // This defines 'config_endstop oid=%c pin=%c pull_up=%c' -/// mcu_command!(ConfigEndstop, "config_endstop", oid: u8, pin: u8, pull_up: u8); -/// ``` -#[macro_export] -macro_rules! mcu_command { - ($ty_name:ident, $cmd_name:literal $(, $arg:ident : $kind:ty)* $(,)?) => { - $crate::mcu_message_impl!($ty_name, $cmd_name = None $(, $arg: $kind)*); - }; - ($ty_name:ident, $cmd_name:literal = $cmd_id:literal $(, $arg:ident : $kind:ty)* $(,)?) => { - $crate::mcu_message_impl!($ty_name, $cmd_name = Some($cmd_id) $(, $arg: $kind)*); - }; -} - -/// Declare a device -> host message -/// -/// Declares the format of a message sent from device to host. The general format is as follows: +/// Declares the format of a message in either direction. The general format is as follows: /// /// ```text -/// mcu_reply!(, ""( = ) [, arg: type, ..]); +/// mcu_command!( = => slug arg=%a arg2=%b ); /// ``` /// -/// For more information on the various fields, see the documentation for [`mcu_command`]. +/// A *canonical* `id` must be specified. This is not necessarily the same as the real id which is +/// part of the dictionary, but is instead used to map between the two quickly. /// -/// # Examples -/// -/// ```ignore -/// // This defines 'config_endstop oid=%c pin=%c pull_up=%c' -/// mcu_reply!(Uptime, "uptime", high: u32, clock: u32); -/// ``` +/// Arguments can be specified, they will be mapped to the relevant Klipper argument types. See +/// [`crate::chr_to_kind`] for allowed specifiers #[macro_export] -macro_rules! mcu_reply { - ($ty_name:ident, $cmd_name:literal $(, $arg:ident : $kind:ty)* $(,)?) => { - $crate::mcu_message_impl!($ty_name, $cmd_name = None $(, $arg: $kind)*); - }; - ($ty_name:ident, $cmd_name:literal = $cmd_id:literal $(, $arg:ident : $kind:ty)* $(,)?) => { - $crate::mcu_message_impl!($ty_name, $cmd_name = Some($cmd_id) $(, $arg: $kind)*); - }; +macro_rules! mcu_message { + ($ty_name:ident = $canon_cmd_id:expr => $slug:ident $($arg:ident=%$chr:ident) *) => { + mcu_message_impl!($ty_name, $slug = $canon_cmd_id $(, $arg : chr_to_kind!($chr) )*); + } } +/// Defines a message with the given type and fields, and implements [`Message`] for it #[macro_export] #[doc(hidden)] macro_rules! mcu_message_impl { - ($ty_name:ident, $cmd_name:literal = $cmd_id:expr $(, $arg:ident : $kind:ty)*) => { + ($ty_name:ident, $cmd_name:ident = $canon_cmd_id:expr $(, $arg:ident : $kind:ty)*) => { paste::paste! { - #[derive(Debug)] - struct $ty_name; - - #[allow(dead_code)] - impl $ty_name { - #[allow(clippy::extra_unused_lifetimes)] - pub fn encode<'a>($($arg: <$kind as $crate::encoding::Borrowable>::Borrowed<'a>, )*) -> $crate::messages::EncodedMessage { - let payload = [<$ty_name Data>] { - $($arg,)* - _lifetime: Default::default(), - }.encode(); - $crate::messages::EncodedMessage { - payload, - _message_kind: Default::default(), - } - } - - pub fn decode<'a>(input: &mut &'a [u8]) -> Result<[<$ty_name Data>]<'a>, $crate::encoding::MessageDecodeError> { - [<$ty_name Data>]::decode(input) - } - } - - #[allow(dead_code)] - struct [<$ty_name Data>]<'a> { - // $(pub $arg: $kind,)* - $(pub $arg: <$kind as $crate::encoding::Borrowable>::Borrowed<'a>,)* - - _lifetime: std::marker::PhantomData<&'a ()>, - } - - #[allow(dead_code)] - impl<'a> [<$ty_name Data>]<'a> { - fn encode(&self) -> $crate::messages::FrontTrimmableBuffer { - use $crate::encoding::Writable; - let mut buf = Vec::with_capacity($crate::transport::MESSAGE_LENGTH_PAYLOAD_MAX); - if let Some(id) = $cmd_id { - $crate::encoding::encode_vlq_int(&mut buf, id); - } else { - // todo: assumes this will be replaced later - // not sure why we add two though - buf.push(0); - buf.push(0); - } - $(self.$arg.write(&mut buf);)* - $crate::messages::FrontTrimmableBuffer { content: buf, offset: 0 } - } - - fn decode(input: &mut &'a [u8]) -> Result { - $(let $arg = $crate::encoding::Readable::read(input)?;)* - Ok(Self { $($arg,)* _lifetime: Default::default() }) - } - - } - - impl<'a> std::fmt::Debug for [<$ty_name Data>]<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut ds = f.debug_struct(stringify!([<$ty_name Data>])); - $( let ds = ds.field(stringify!($arg), &self.$arg); )* - ds.finish() - } - } - - #[derive(Clone)] - #[allow(dead_code)] - struct [<$ty_name DataOwned>] { + #[derive(Debug, Clone)] + pub struct $ty_name { $(pub $arg: $kind,)* } - impl<'a> std::convert::From<[<$ty_name Data>]<'a>> for [<$ty_name DataOwned>] { - fn from(value: [<$ty_name Data>]) -> Self { - Self { - $($arg: $crate::encoding::Borrowable::from_borrowed(value.$arg),)* - } - } - } - - impl std::fmt::Debug for [<$ty_name DataOwned>] { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut ds = f.debug_struct(stringify!([<$ty_name DataOwned>])); - $( let ds = ds.field(stringify!($arg), &self.$arg); )* - ds.finish() - } - } - - #[allow(dead_code)] impl $crate::messages::Message for $ty_name { - type Pod<'a> = [<$ty_name Data>]<'a>; - type PodOwned = [<$ty_name DataOwned>]; - - fn get_id(dict: Option<&$crate::dictionary::Dictionary>) -> Option { - $cmd_id.or_else(|| dict.and_then(|dict| dict.message_id($cmd_name))) - } - - fn get_name() -> &'static str { - $cmd_name - } + #[allow(unused_imports, unused_variables)] + fn encode(&self, buf: &mut bytes::BytesMut, real_cmd_id: $crate::dictionary::RealId) { + use $crate::encoding::Writable; + $crate::encoding::encode_vlq_int(buf, real_cmd_id.into()); + $(self.$arg.write(buf);)* - fn decode<'a>(input: &mut &'a [u8]) -> Result, $crate::encoding::MessageDecodeError> { - Self::decode(input) } - fn fields<'a>() -> Vec<(&'static str, $crate::encoding::FieldType)> { - vec![ - $( ( stringify!($arg), <$kind as $crate::encoding::ToFieldType>::as_field_type() ), )* - ] + #[allow(unused_imports, unused_variables)] + fn decode(buf: &mut bytes::Bytes) -> Result { + $(let $arg = $crate::encoding::Readable::read(buf)?;)* + Ok(Self { $($arg,)* }) } } - $crate::mcu_message_impl_oid_check!($ty_name $(, $arg)*); } }; } +/// | Rust type | Format string | +/// |------------|---------------| +/// | `u32` | `%u` | +/// | `i32` | `%i` | +/// | `u16` | `%hu` | +/// | `i16` | `%hi` | +/// | `u8` | `%c` | +/// | `Bytes` | `%b` | +/// | `String` | `%s` | #[macro_export] -#[doc(hidden)] -/// Implement the [`WithOid`](crate::messages::WithOid) / [`WithoutOid`](crate::messages::WithoutOid) marker type for an MCU message type. -macro_rules! mcu_message_impl_oid_check { - ($ty_name:ident) => { - impl $crate::messages::WithoutOid for $ty_name {} +macro_rules! chr_to_kind { + (u) => { + u32 + }; + (i) => { + i32 + }; + (hu) => { + u16 + }; + (hi) => { + i16 + }; + (c) => { + u8 }; - ($ty_name:ident, oid $(, $args:ident)*) => { - impl $crate::messages::WithOid for $ty_name {} + (b) => { + Bytes }; - ($ty_name:ident, $arg:ident $(, $args:ident)*) => { - $crate::mcu_message_impl_oid_check!($ty_name $(, $args)*); + (s) => { + String }; } diff --git a/crates/windlass/src/messages.rs b/crates/windlass/src/messages.rs index 71cb32b..b561ab7 100644 --- a/crates/windlass/src/messages.rs +++ b/crates/windlass/src/messages.rs @@ -1,241 +1,186 @@ -use std::collections::BTreeMap; +//! Commands and responses for klipper MCUs -use crate::dictionary::Dictionary; -use crate::encoding::{FieldType, FieldValue, MessageDecodeError}; +use bytes::{Bytes, BytesMut}; -/// A parser for a single message type -pub struct MessageParser { - /// The name of the message - pub name: String, +use crate::{ + dictionary::{CanonicalId, RealId}, + encoding::MessageDecodeError, +}; - /// The fields of the message, and their types. - pub fields: Vec<(String, FieldType)>, - - /// How the message should be debug printed - pub output: Option, +pub trait Message: Sized { + /// Decode the message from the top of `buf`. + /// The message ID should have already been read + fn decode(buf: &mut Bytes) -> Result; + fn encode(&self, buf: &mut BytesMut, real_cmd_id: RealId); } -impl MessageParser { - /// Create a parser for a message with the given name and printf declaration parts - pub(crate) fn new<'a>( - name: &str, - parts: impl Iterator, - ) -> Result { - let mut fields = vec![]; - for part in parts { - let (arg, ty) = part - .split_once('=') - .ok_or_else(|| MessageSkipperError::InvalidArgumentFormat(part.into()))?; - - let field_type = FieldType::from_msg(ty)?; - fields.push((arg.to_string(), field_type)); - } - Ok(Self { - name: name.to_string(), - fields, - output: None, - }) - } - - /// Create a parser for a message type with the given printf-style specifier - pub(crate) fn new_output(msg: &str) -> Result { - let mut fields = vec![]; - let mut parts = vec![]; - - let mut work = msg; - while let Some(pos) = work.find('%') { - let (pre, rest) = work.split_at(pos); - if !pre.is_empty() { - parts.push(FormatBlock::Static(pre.to_string())); - } - if let Some(rest) = rest.strip_prefix("%%") { - parts.push(FormatBlock::Static("%".to_string())); - work = rest; - break; +macro_rules! define_commands { + ($($ty_name:ident = $canon_cmd_id:expr => $slug:ident $($arg:ident=%$chr:ident) *;)*) => { + $(mcu_message!($ty_name = $canon_cmd_id => $slug $($arg=%$chr) *);)* + pub(crate) fn cmd_name_to_canonical(name: &str) -> Option { + match name { + $(stringify!($slug) => Some($canon_cmd_id),)* + _ => None } - let (format, rest) = FieldType::from_format(rest)?; - parts.push(FormatBlock::Field); - fields.push((format!("field_{}", fields.len()), format)); - work = rest; } - if !work.is_empty() { - parts.push(FormatBlock::Static(work.to_string())); - } - - Ok(Self { - name: msg.to_string(), - fields, - output: Some(OutputFormat { parts }), - }) - } - - /// Skip over this message at the top of `input`. - #[allow(dead_code)] - pub fn skip(&self, input: &mut &[u8]) -> Result<(), MessageDecodeError> { - for (_, field) in &self.fields { - field.skip(input)?; - } - Ok(()) } +} - /// Skip over this message at the top of `input`, but try to read the `oid` field if it is part of this message. - pub fn skip_with_oid(&self, input: &mut &[u8]) -> Result, MessageDecodeError> { - let mut oid = None; - for (name, field) in &self.fields { - if name == "oid" { - if let FieldValue::U8(read_oid) = field.read(input)? { - oid = Some(read_oid); - } - } else { - field.skip(input)?; +macro_rules! define_responses { + ($($ty_name:ident = $canon_cmd_id:expr => $slug:ident $($arg:ident=%$chr:ident) *;)*) => { + $(mcu_message!($ty_name = $canon_cmd_id => $slug $($arg=%$chr) *);)* + pub(crate) fn resp_name_to_canonical(name: &str) -> Option { + match name { + $(stringify!($slug) => Some($canon_cmd_id),)* + _ => None } } - Ok(oid) - } - /// Parse a message of this type from the top of `input`. - pub fn parse( - &self, - input: &mut &[u8], - ) -> Result, MessageDecodeError> { - let mut output = BTreeMap::new(); - for (name, field) in &self.fields { - output.insert(name.to_string(), field.read(input)?); + #[derive(Debug, Clone)] + /// All messages that the MCU may send to the host. + pub enum Response { + $($ty_name ( $ty_name ),)* } - Ok(output) - } -} - -/// A message type -pub trait Message: 'static { - type Pod<'a>: Into + std::fmt::Debug; - type PodOwned: Clone + Send + std::fmt::Debug + 'static; - - /// Get the message ID from the given data dictionary - fn get_id(dict: Option<&Dictionary>) -> Option; - - /// Get the message name - // TODO: this could be an associated constant? - fn get_name() -> &'static str; - - /// Decode this message type from the top of `input`. - fn decode<'a>(input: &mut &'a [u8]) -> Result, MessageDecodeError>; - - /// Get a list of field names and types - fn fields() -> Vec<(&'static str, FieldType)>; -} - -/// Marker trait for messages with an oid field -pub trait WithOid: 'static {} - -/// Marker trait for messages without an oid field -pub trait WithoutOid: 'static {} - -/// Represents an encoded message, with a type-level link to the message kind -pub struct EncodedMessage { - pub payload: FrontTrimmableBuffer, - pub _message_kind: std::marker::PhantomData, -} - -/// Wraps a `Vec` allowing removal of front bytes in a zero-copy way -pub struct FrontTrimmableBuffer { - pub content: Vec, - pub offset: usize, -} -impl FrontTrimmableBuffer { - /// Get the rest of the buffer as a slice - pub fn as_slice(&self) -> &[u8] { - &self.content[self.offset..] - } -} - -/// Holds the format of a `output()` style debug message -#[derive(Debug)] -pub struct OutputFormat { - parts: Vec, -} - -impl OutputFormat { - /// Format the given fields according to this output format. - pub fn format<'a>(&self, mut fields: impl Iterator) -> String { - let mut buf = String::new(); - for part in &self.parts { - match part { - FormatBlock::Static(s) => buf.push_str(s), - FormatBlock::Field => { - if let Some(v) = fields.next() { - std::fmt::write(&mut buf, format_args!("{v}")).ok(); - } + impl Response { + /// Deserialise a respones from the top of `buf`. + /// # Panics + /// Panics if `msg_id` is not a valid [`CanonicalId`]. + pub fn deserialise(msg_id: CanonicalId, buf: &mut Bytes) -> Result { + match msg_id { + $($canon_cmd_id => Ok(Self::$ty_name ( $ty_name::decode(buf)? )),)* + _ => panic!("deserialise called with unknown canonical id") } } } - buf - } -} - -/// Part of an [`OutputFormat`]. -#[derive(Debug)] -enum FormatBlock { - Static(String), - Field, -} - -/// Format the given name and type pairs as a printf style declaration. -pub fn format_command_args<'a>(fields: impl Iterator) -> String { - let mut buf = String::new(); - for (idx, (name, ty)) in fields.enumerate() { - if idx != 0 { - buf.push(' '); - } - buf.push_str(name); - buf.push('='); - buf.push_str(match ty { - FieldType::U32 => "%u", - FieldType::I32 => "%i", - FieldType::U16 => "%hu", - FieldType::I16 => "%hi", - FieldType::U8 => "%c", - FieldType::String => "%s", - FieldType::ByteArray => "%*s", - }); } - buf } -/// An error enountered when parsing a message format string -#[derive(thiserror::Error, Debug)] -pub enum MessageSkipperError { - #[error("invalid argument format: {0}")] - InvalidArgumentFormat(String), - - #[error("unknown type '{1}' for argument '{0}'")] - UnknownType(String, String), - - #[error("invalid format field type '%{0}'")] - InvalidFormatFieldType(String), -} - -impl std::fmt::Debug for FrontTrimmableBuffer { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Debug::fmt(self.as_slice(), f) - } -} - -impl std::fmt::Debug for MessageParser { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_map() - .entry(&"name", &self.name) - .entry(&"fields", &self.fields) - .finish() - } -} - -impl std::fmt::Debug for EncodedMessage { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("EncodedMessage") - .field("kind", &R::get_name()) - .field("payload", &self.payload) - .finish() - } -} +define_commands!( + AllocateOids = 0 => allocate_oids count=%c; + ButtonsAck = 1 => buttons_ack oid=%c count=%c; + ButtonsAdd = 2 => buttons_add oid=%c pos=%c pin=%u pull_up=%c; + ButtonsQuery = 3 => buttons_query oid=%c clock=%u rest_ticks=%u retransmit_count=%c invert=%c; + ClearShutdown = 4 => clear_shutdown; + ConfigAds1220 = 5 => config_ads1220 oid=%c spi_oid=%c data_ready_pin=%u; + ConfigAdxl345 = 6 => config_adxl345 oid=%c spi_oid=%c; + ConfigAnalogIn = 7 => config_analog_in oid=%c pin=%u; + ConfigButtons = 8 => config_buttons oid=%c button_count=%c; + ConfigCounter = 9 => config_counter oid=%c pin=%u pull_up=%c; + ConfigDigitalOut = 10 => config_digital_out oid=%c pin=%u value=%c default_value=%c max_duration=%u; + ConfigDs18b20 = 11 => config_ds18b20 oid=%c serial=%b max_error_count=%c; + ConfigEndstop = 12 => config_endstop oid=%c pin=%c pull_up=%c; + ConfigHd44780 = 13 => config_hd44780 oid=%c rs_pin=%u e_pin=%u d4_pin=%u d5_pin=%u d6_pin=%u d7_pin=%u delay_ticks=%u; + ConfigHx71x = 14 => config_hx71x oid=%c gain_channel=%c dout_pin=%u sclk_pin=%u; + ConfigI2c = 15 => config_i2c oid=%c; + ConfigLdc1612 = 16 => config_ldc1612 oid=%c i2c_oid=%c; + ConfigLdc1612WithIntb = 17 => config_ldc1612_with_intb oid=%c i2c_oid=%c intb_pin=%c; + ConfigLis2dw = 18 => config_lis2dw oid=%c spi_oid=%c; + ConfigMpu9250 = 19 => config_mpu9250 oid=%c i2c_oid=%c; + ConfigNeopixel = 20 => config_neopixel oid=%c pin=%u data_size=%hu bit_max_ticks=%u reset_min_ticks=%u; + ConfigPca9685 = 21 => config_pca9685 oid=%c bus=%c addr=%c channel=%c cycle_ticks=%u value=%hu default_value=%hu max_duration=%u; + ConfigPwmOut = 22 => config_pwm_out oid=%c pin=%u cycle_ticks=%u value=%hu default_value=%hu max_duration=%u; + ConfigReset = 23 => config_reset; + ConfigSpi = 24 => config_spi oid=%c pin=%u cs_active_high=%c; + ConfigSpiAngle = 25 => config_spi_angle oid=%c spi_oid=%c spi_angle_type=%c; + ConfigSpiShutdown = 26 => config_spi_shutdown oid=%c spi_oid=%c shutdown_msg=%b; + ConfigSpiWithoutCs = 27 => config_spi_without_cs oid=%c; + ConfigSt7920 = 28 => config_st7920 oid=%c cs_pin=%u sclk_pin=%u sid_pin=%u sync_delay_ticks=%u cmd_delay_ticks=%u; + ConfigStepper = 29 => config_stepper oid=%c step_pin=%c dir_pin=%c invert_step=%c step_pulse_ticks=%u; + ConfigThermocouple = 30 => config_thermocouple oid=%c spi_oid=%c thermocouple_type=%c; + ConfigTmcuart = 31 => config_tmcuart oid=%c rx_pin=%u pull_up=%c tx_pin=%u bit_time=%u; + ConfigTrsync = 32 => config_trsync oid=%c; + DebugNop = 33 => debug_nop; + DebugPing = 34 => debug_ping data=%b; + DebugRead = 35 => debug_read order=%c addr=%u; + DebugWrite = 36 => debug_write order=%c addr=%u val=%u; + EmergencyStop = 37 => emergency_stop; + EndstopHome = 38 => endstop_home oid=%c clock=%u sample_ticks=%u sample_count=%c rest_ticks=%u pin_value=%c trsync_oid=%c trigger_reason=%c; + EndstopQueryState = 39 => endstop_query_state oid=%c; + FinalizeConfig = 40 => finalize_config crc=%u; + GetClock = 41 => get_clock; + GetConfig = 42 => get_config; + GetUptime = 43 => get_uptime; + Hd44780SendCmds = 44 => hd44780_send_cmds oid=%c cmds=%b; + Hd44780SendData = 45 => hd44780_send_data oid=%c data=%b; + I2cModify = 46 => i2c_modify_bits oid=%c reg=%b clear_set_bits=%b; + I2cRead = 47 => i2c_read oid=%c reg=%b read_len=%u; + I2cSetBus = 48 => i2c_set_bus oid=%c i2c_bus=%u rate=%u address=%u; + I2cSetSoftwareBus = 49 => i2c_set_software_bus oid=%c scl_pin=%u sda_pin=%u rate=%u address=%u; + I2cWrite = 50 => i2c_write oid=%c data=%b; + Identify = 51 => identify offset=%u count=%c; + Ldc1612SetupHome = 52 => ldc1612_setup_home oid=%c clock=%u threshold=%u trsync_oid=%c trigger_reason=%c error_reason=%c; + NeopixelSend = 53 => neopixel_send oid=%c; + NeopixelUpdate = 54 => neopixel_update oid=%c pos=%hu data=%b; + QueryAds1220 = 55 => query_ads1220 oid=%c rest_ticks=%u; + QueryAds1220Status = 56 => query_ads1220_status oid=%c; + QueryAdxl345 = 57 => query_adxl345 oid=%c rest_ticks=%u; + QueryAdxl345Status = 58 => query_adxl345_status oid=%c; + QueryAnalogIn = 59 => query_analog_in oid=%c clock=%u sample_ticks=%u sample_count=%c rest_ticks=%u min_value=%hu max_value=%hu range_check_count=%c; + QueryCounter = 60 => query_counter oid=%c clock=%u poll_ticks=%u sample_ticks=%u; + QueryDs18b20 = 61 => query_ds18b20 oid=%c clock=%u rest_ticks=%u min_value=%i max_value=%i; + QueryHx71x = 62 => query_hx71x oid=%c rest_ticks=%u; + QueryHx71xStatus = 63 => query_hx71x_status oid=%c; + QueryLdc1612 = 64 => query_ldc1612 oid=%c rest_ticks=%u; + QueryLdc1612HomeState = 65 => query_ldc1612_home_state oid=%c; + QueryLis2dw = 66 => query_lis2dw oid=%c rest_ticks=%u; + QueryLis2dwStatus = 67 => query_lis2dw_status oid=%c; + QueryMpu9250 = 68 => query_mpu9250 oid=%c rest_ticks=%u; + QueryMpu9250Status = 69 => query_mpu9250_status oid=%c; + QuerySpiAngle = 70 => query_spi_angle oid=%c clock=%u rest_ticks=%u time_shift=%c; + QueryStatusLdc1612 = 71 => query_status_ldc1612 oid=%c; + QueryThermocouple = 72 => query_thermocouple oid=%c clock=%u rest_ticks=%u min_value=%u max_value=%u max_invalid_count=%c; + QueueDigital = 73 => queue_digital_out oid=%c clock=%u on_ticks=%u; + QueuePca9685 = 74 => queue_pca9685_out oid=%c clock=%u value=%hu; + QueuePwmOut = 75 => queue_pwm_out oid=%c clock=%u value=%hu; + QueueStep = 76 => queue_step oid=%c interval=%u count=%hu add=%hi; + ResetStep = 77 => reset_step_clock oid=%c clock=%u; + SetDigitalOut = 78 => set_digital_out pin=%u value=%c; + SetDigitalOutPwmCycle = 79 => set_digital_out_pwm_cycle oid=%c cycle_ticks=%u; + SetNextStepDir = 80 => set_next_step_dir oid=%c dir=%c; + SetPca9685Out = 81 => set_pca9685_out bus=%c addr=%c channel=%c cycle_ticks=%u value=%hu; + SetPwmOut = 82 => set_pwm_out pin=%u cycle_ticks=%u value=%hu; + SpiAngleTransfer = 83 => spi_angle_transfer oid=%c data=%b; + SpiSend = 84 => spi_send oid=%c data=%b; + SpiSetBus = 85 => spi_set_bus oid=%c spi_bus=%u mode=%u rate=%u; + SpiSetSoftwareBus = 86 => spi_set_software_bus oid=%c miso_pin=%u mosi_pin=%u sclk_pin=%u mode=%u rate=%u; + SpiTransfer = 87 => spi_transfer oid=%c data=%b; + St7920SendCmds = 88 => st7920_send_cmds oid=%c cmds=%b; + St7920SendData = 89 => st7920_send_data oid=%c data=%b; + StepperGetPosition = 90 => stepper_get_position oid=%c; + StepperStopOnTrigger = 91 => stepper_stop_on_trigger oid=%c trsync_oid=%c; + TmcuartSend = 92 => tmcuart_send oid=%c write=%b read=%c; + TrsyncSet = 93 => trsync_set_timeout oid=%c clock=%u; + TrsyncStart = 94 => trsync_start oid=%c report_clock=%u report_ticks=%u expire_reason=%c; + TrsyncTrigger = 95 => trsync_trigger oid=%c reason=%c; + UpdateDigitalOut = 96 => update_digital_out oid=%c value=%c; +); + +define_responses!( + AnalogInState = 0 => analog_in_state oid=%c next_clock=%u value=%hu; + ButtonsState = 1 => buttons_state oid=%c ack_count=%c state=%b; + Clock = 3 => clock clock=%u; + Config = 4 => config is_config=%c crc=%u is_shutdown=%c move_count=%hu; + CounterState = 5 => counter_state oid=%c next_clock=%u count=%u count_clock=%u; + DebugResult = 6 => debug_result val=%u; + Ds18b20Result = 7 => ds18b20_result oid=%c next_clock=%u value=%i fault=%u; + EndstopState = 8 => endstop_state oid=%c homing=%c next_clock=%u pin_value=%c; + I2cReadResponse = 9 => i2c_read_response oid=%c response=%b; + IdentifyResponse = 11 => identify_response offset=%u data=%b; + IsShutdown = 12 => is_shutdown static_string_id=%hu; + Ldc1612HomeState = 13 => ldc1612_home_state oid=%c homing=%c trigger_clock=%u; + NeopixelResult = 14 => neopixel_result oid=%c success=%c; + Pong = 15 => pong data=%b; + SensorBulkData = 16 => sensor_bulk_data oid=%c sequence=%hu data=%b; + SensorBulkStatus = 17 => sensor_bulk_status oid=%c clock=%u query_ticks=%u next_sequence=%hu buffered=%u possible_overflows=%hu; + Shutdown = 18 => shutdown clock=%u static_string_id=%hu; + SpiAngleTransferResponse = 19 => spi_angle_transfer_response oid=%c clock=%u response=%b; + SpiTransferResponse = 20 => spi_transfer_response oid=%c response=%b; + Starting = 21 => starting; + Stats = 22 => stats count=%u sum=%u sumsq=%u; + StepperPosition = 23 => stepper_position oid=%c pos=%i; + ThermocoupleResult = 24 => thermocouple_result oid=%c next_clock=%u value=%u fault=%c; + TmcuartResponse = 25 => tmcuart_response oid=%c read=%b; + TrsyncState = 26 => trsync_state oid=%c can_trigger=%c trigger_reason=%c clock=%u; + Uptime = 27 => uptime high=%u clock=%u; +); diff --git a/crates/windlass/src/transport/mod.rs b/crates/windlass/src/transport/mod.rs index 75bd141..87c7c60 100644 --- a/crates/windlass/src/transport/mod.rs +++ b/crates/windlass/src/transport/mod.rs @@ -47,11 +47,9 @@ impl Connection { } /// Queue a new message to be transmitted - pub fn push_message(&mut self, msg: &[u8]) { + pub fn push_message(&mut self, msg: Bytes) { trace!(msg = ?msg, "queued outgoing message"); - self.pending_buf.extend_from_slice(msg); - self.pending_messages - .push_back(self.pending_buf.split().freeze()); + self.pending_messages.push_back(msg); } /// Process received data and update internal state