//! # Configuration protocol //! //! Configuration and runtime management is done over a separate TCP port, to //! simplify management. It follows a simple binary protocol, described here. //! //! The protocol is packet-based, 2 bytes start sequence, 1 byte command, 4 byte //! data, 1 byte CRC. CRC includes the start sequence, and is MAXIM-DOW. Chosen //! because the crc python package had it as well. //! //! The 4 bytes of data are little-endian. //! //! For the settings that can be set, see [Settings]. //! //! ## Sending a command //! //! The command is simply the index of the setting to read or write, with the //! top bit set to indicate a write. Set the value to 0 during a read. //! //! ## Receiving a response //! //! The command is the index of the setting given, with the top bit set if it's //! an error. The value is either the value saved in the setting, or an error //! code. Error codes are defined in [ErrorCodes] /// Start sequence to indicate the beginning of a packet. const START_SEQUENCE: [u8; 2] = [0xDE, 0xAD]; /// Possible return values from a command #[derive(Clone, Copy, Debug)] pub enum ErrorCodes { /// Command accepted Success = 0, /// Value was not valid for the provided setting ValueOutOfRange = 1, /// Setting index provided does not exist InvalidSetting = 2, } /// Setting indices #[derive(Clone, Copy, Debug)] pub enum Settings { /// Trigger threshold (in ADC counts, peak to peak is 1024) TriggerThreshold = 0, /// Number of samples that diff must be higher than threshold to trigger TriggerPeriod = 1, /// Value to decay difference every DecayPeriod cycles DecayValue = 2, /// The number of cycles between every decay DecayPeriod = 3, /// Gain setting of preprocessor (value in dB) Gain = 4, /// Center frequency to set preprocessor filter to (value in Hz) CenterFreq = 5, /// Sampling enabled, 1 to enable, 0 to disable SamplingEnabled = 6, } #[derive(Clone, Copy, Debug)] pub enum Error { /// Start sequence incorrect, no packet BadStartSequence, /// CRC was bad BadCrc, /// State machine error StateMachineError, /// No packet found in data NoPacket, } impl TryFrom for Settings { type Error = (); fn try_from(value: u8) -> Result { match value { 0 => Ok(Self::TriggerThreshold), 1 => Ok(Self::TriggerPeriod), 2 => Ok(Self::DecayValue), 3 => Ok(Self::DecayPeriod), 4 => Ok(Self::Gain), 5 => Ok(Self::CenterFreq), 6 => Ok(Self::SamplingEnabled), _ => Err(()), } } } pub struct CommandPacket { pub is_write: bool, pub setting: u8, pub value: u32, } pub struct ResponsePacket { pub is_error: bool, pub setting: u8, pub value: u32, } /// Serialize a valid response pub fn serialize_response_value(setting: Settings, value: u32) -> [u8; 8] { serialize_response(setting as u8, value) } /// Serialize an error response pub fn serialize_response_error(setting: u8, error: ErrorCodes) -> [u8; 8] { serialize_response(0x80 | setting, error as u32) } fn serialize_response(command: u8, value: u32) -> [u8; 8] { let mut crc = crc_any::CRCu8::crc8maxim(); let mut buf = [0u8; 8]; // Construct packet data &buf[0..2].copy_from_slice(&START_SEQUENCE); buf[2] = command; for i in 0..4 { buf[3 + i] = (value >> (8 * i)) as u8; } // Run CRC and append value crc.digest(&buf[0..7]); buf[7] = crc.get_crc(); buf } pub struct PacketParser { packet_index: u8, crc: crc_any::CRCu8, is_write: bool, setting: u8, value: u32, } impl PacketParser { pub fn new() -> Self { Self { packet_index: 0, crc: crc_any::CRCu8::crc8maxim(), is_write: false, setting: 0, value: 0, } } /// Try and parse a packet from a received buffer. Returns Some(packet) if a /// packet was found, and the number of bytes processed from the buffer. Bytes /// processed may be smaller than the buffer length if a packet was found. pub fn try_parse(&mut self, buf: &[u8]) -> (Result, usize) { let mut bytes_processed = 0; for byte in buf { bytes_processed += 1; match self.process_byte(*byte) { Ok(packet) => return (Ok(packet), bytes_processed), // We don't care to pass errors to the user // TODO log _ => (), } } (Err(Error::NoPacket), bytes_processed) } /// Process incoming byte using a simple state machine. pub fn process_byte(&mut self, byte: u8) -> Result { match self.packet_index { // Check the start sequence 0 => { if byte != START_SEQUENCE[0] { self.reset(); return Err(Error::BadStartSequence); } } 1 => { if byte != START_SEQUENCE[1] { self.reset(); return Err(Error::BadStartSequence); } } // Save data 2 => { self.is_write = byte & 0x80 != 0; self.setting = byte & 0x7F; } 3..=6 => { self.value |= (byte << (8 * (self.packet_index - 3))) as u32; } // Check CRC 7 => { if self.crc.get_crc() == byte { return Ok(CommandPacket { is_write: self.is_write, setting: self.setting, value: self.value, }); } else { self.reset(); return Err(Error::BadCrc); } } // Should be unreachable, just reset _ => { self.reset(); return Err(Error::StateMachineError); } } // Increment index self.packet_index += 1; // Process CRC (sucks I can't just give it one byte) self.crc.digest(&[byte]); Err(Error::NoPacket) } /// Reset values to start parsing a packet again fn reset(&mut self) { self.packet_index = 0; self.crc = crc_any::CRCu8::crc8maxim(); self.value = 0; } }