use super::Channel;
use crate::{Error, Result};
use irp::{Irp, Vartable};
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExtendedCommand {
BrakeThenFloatOnRedOutput = 0b0000,
IncrementSpeedOnRedOutput = 0b0001,
DecrementSpeedOnRedOutput = 0b0010,
ToggleForwardOrFloatOnBlueOutput = 0b0100,
ToggleAddress = 0b0110,
AlignToggle = 0b0111,
}
#[derive(Debug, Clone, Copy)]
struct ExtendedMessage {
toggle: u8,
channel: u8,
address: u8,
function: u8,
}
pub struct ExtendedProtocol {
irp: Irp,
toggle: u8,
address: u8, }
pub const LEGO_EXTENDED_IRP: &str = "\
{38k,33%,26.3157894737,msb}\
<6,-10|6,-21>\
(6,-39, T:1, E:1, C:2, a:1, M:3, F:4, L:4, 6,-39)\
{L = 0xF^( (T*8+E*4+C)^(a*8+M)^F )}\
[T:0..1,E:0..1,C:0..3,a:0..1,M:0..7,F:0..15]\
";
impl ExtendedProtocol {
pub fn new() -> Result<Self> {
let irp = Irp::parse(LEGO_EXTENDED_IRP).map_err(Error::ProtocolError)?;
Ok(Self {
irp,
toggle: 0,
address: 0,
})
}
fn encode_msg(&self, msg: ExtendedMessage) -> Result<Vec<u32>> {
let mut vars = Vartable::new();
vars.set("T".into(), msg.toggle.into());
vars.set("E".into(), 0u8.into());
vars.set("C".into(), msg.channel.into());
vars.set("a".into(), msg.address.into());
vars.set("M".into(), 0u8.into());
vars.set("F".into(), msg.function.into());
self.irp
.encode_raw(vars, 1)
.map(|res| res.raw)
.map_err(Error::ProtocolError)
}
pub fn encode_cmd(&mut self, channel: Channel, cmd: ExtendedCommand) -> Result<Vec<u32>> {
let msg = ExtendedMessage {
toggle: self.toggle,
channel: channel as u8,
address: self.address,
function: cmd as u8,
};
let pulses = self.encode_msg(msg)?;
self.toggle ^= 1;
if cmd == ExtendedCommand::ToggleAddress {
self.address = 1 - self.address;
}
Ok(pulses)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocols::Channel;
#[test]
fn test_extended_encode_cmd() {
let mut proto = ExtendedProtocol::new().unwrap();
let pulses = proto
.encode_cmd(Channel::One, ExtendedCommand::BrakeThenFloatOnRedOutput)
.expect("Encoding should succeed");
assert!(!pulses.is_empty());
}
}
#[cfg(test)]
mod extended_protocol_tests {
use super::*;
use crate::protocols::Channel;
use crate::protocols::ExtendedCommand;
#[test]
fn test_extended_brake_command_structure() {
let mut proto = ExtendedProtocol::new().unwrap();
let pulses = proto
.encode_cmd(Channel::One, ExtendedCommand::BrakeThenFloatOnRedOutput)
.expect("Encoding should succeed");
assert!(!pulses.is_empty());
assert_eq!(pulses.len(), 36, "Unexpected pulse sequence length");
let expected: Vec<u32> = vec![
157, 1026, 157, 263, 157, 263, 157, 263, 157, 263, 157, 263, 157, 263, 157, 263, 157,
263, 157, 263, 157, 263, 157, 263, 157, 263, 157, 552, 157, 552, 157, 552, 157, 552,
157, 1026,
];
assert_eq!(pulses, expected, "Pulse sequence does not match expected");
}
#[test]
fn test_extended_toggle_forward_command_structure() {
let mut proto = ExtendedProtocol::new().unwrap();
let pulses = proto
.encode_cmd(
Channel::One,
ExtendedCommand::ToggleForwardOrFloatOnBlueOutput,
)
.expect("Encoding should succeed");
assert!(!pulses.is_empty());
assert_eq!(pulses.len(), 36, "Unexpected pulse sequence length");
let expected: Vec<u32> = vec![
157, 1026, 157, 263, 157, 263, 157, 263, 157, 263, 157, 263, 157, 263, 157, 263, 157,
263, 157, 263, 157, 552, 157, 263, 157, 263, 157, 552, 157, 263, 157, 552, 157, 552,
157, 1026,
];
assert_eq!(pulses, expected, "Pulse sequence does not match expected");
}
#[test]
fn test_extended_toggle_address_changes_internal_state() {
let mut proto = ExtendedProtocol::new().unwrap();
let initial_address = proto.address;
let pulses = proto
.encode_cmd(Channel::One, ExtendedCommand::ToggleAddress)
.expect("Encoding should succeed");
assert!(!pulses.is_empty());
assert_eq!(
proto.address,
1 - initial_address,
"ToggleAddress should invert the internal address"
);
let _ = proto
.encode_cmd(Channel::One, ExtendedCommand::ToggleAddress)
.expect("Encoding should succeed");
assert_eq!(
proto.address, initial_address,
"ToggleAddress should invert the internal address back to its original state"
);
}
}