230 lines
6.4 KiB
Rust
230 lines
6.4 KiB
Rust
//! # 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<u8> for Settings {
|
|
type Error = ();
|
|
|
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
|
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<CommandPacket, Error>, 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<CommandPacket, Error> {
|
|
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;
|
|
}
|
|
}
|