use irp::{Irp, Vartable};
use super::{map_speed, Channel, Output};
use crate::{Error, Result};
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SingleOutputDiscrete {
ToggleFullForward = 0b0000,
ToggleDirection = 0b0001,
IncrementNumericalPwm = 0b0010,
DecrementNumericalPwm = 0b0011,
IncrementPwm = 0b0100,
DecrementPwm = 0b0101,
FullForward = 0b0110,
FullBackward = 0b0111,
ToggleFullForwardBackward = 0b1000,
ClearC1 = 0b1001,
SetC1 = 0b1010,
ToggleC1 = 0b1011,
ClearC2 = 0b1100,
SetC2 = 0b1101,
ToggleC2 = 0b1110,
ToggleFullBackward = 0b1111,
}
#[derive(Debug, Clone, Copy)]
pub enum SingleOutputCommand {
PWM(i8),
Discrete(SingleOutputDiscrete),
}
#[derive(Debug, Clone, Copy)]
struct SingleOutputMessage {
toggle: u8,
channel: u8,
address: u8,
mode: u8, output: u8, data: u8,
}
pub struct SingleOutputProtocol {
irp: Irp,
toggle: u8,
}
const LEGO_SINGLE_OUTPUT_IRP: &str = "\
{38k,33%,26.3157894737,msb}\
<6,-10|6,-21>\
(6,-39, T:1, 0:1, C:2, a:1, 1:1, M:1, O:1, D:4, L:4, 6,-39)\
{L = 0xF^((T*8+C)^((a<<3)|(1<<2)|(M<<1)|O)^D)}\
[T:0..1, C:0..3, a:0..1, M:0..1, O:0..1, D:0..15]\
";
impl SingleOutputProtocol {
pub fn new() -> Result<Self> {
let irp = Irp::parse(LEGO_SINGLE_OUTPUT_IRP).map_err(Error::ProtocolError)?;
Ok(Self { irp, toggle: 0 })
}
fn encode_msg(&self, msg: SingleOutputMessage) -> Result<Vec<u32>> {
let mut vars = Vartable::new();
vars.set("T".into(), msg.toggle.into());
vars.set("C".into(), msg.channel.into());
vars.set("a".into(), msg.address.into());
vars.set("M".into(), msg.mode.into());
vars.set("O".into(), msg.output.into());
vars.set("D".into(), msg.data.into());
self.irp
.encode_raw(vars, 1)
.map(|res| res.raw)
.map_err(Error::ProtocolError)
}
pub fn encode_cmd(
&mut self,
channel: Channel,
output: Output,
cmd: SingleOutputCommand,
) -> Result<Vec<u32>> {
let (mode, data) = match cmd {
SingleOutputCommand::PWM(speed) => (0, map_speed(speed)),
SingleOutputCommand::Discrete(discrete) => (1, discrete as u8),
};
let msg = SingleOutputMessage {
toggle: self.toggle,
channel: channel as u8,
address: 0,
mode,
output: output as u8,
data,
};
let pulses = self.encode_msg(msg)?;
if mode == 0 {
self.toggle ^= 1;
}
Ok(pulses)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocols::{Channel, Output};
#[test]
fn test_single_output_pwm_encode_cmd() {
let mut proto = SingleOutputProtocol::new().unwrap();
let pulses = proto
.encode_cmd(Channel::One, Output::RED, SingleOutputCommand::PWM(5))
.expect("PWM 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, 552, 157, 263, 157,
263, 157, 263, 157, 552, 157, 263, 157, 552, 157, 552, 157, 552, 157, 552, 157, 263,
157, 1026,
];
assert_eq!(pulses, expected, "Pulse sequence does not match expected");
}
#[test]
fn test_single_output_discrete_encode_cmd() {
let mut proto = SingleOutputProtocol::new().unwrap();
let pulses = proto
.encode_cmd(
Channel::One,
Output::BLUE,
SingleOutputCommand::Discrete(SingleOutputDiscrete::ToggleDirection),
)
.expect("Discrete encoding should succeed");
assert!(!pulses.is_empty());
let expected: Vec<u32> = vec![
157, 1026, 157, 263, 157, 263, 157, 263, 157, 263, 157, 263, 157, 552, 157, 552, 157,
552, 157, 263, 157, 263, 157, 263, 157, 552, 157, 552, 157, 263, 157, 263, 157, 552,
157, 1026,
];
assert_eq!(pulses, expected, "Pulse sequence does not match expected");
}
#[test]
fn test_single_output_pwm_full_range() {
let mut proto = SingleOutputProtocol::new().unwrap();
for speed in -7..=8 {
let pulses =
proto.encode_cmd(Channel::One, Output::RED, SingleOutputCommand::PWM(speed));
assert!(pulses.is_ok(), "Encoding failed for speed={}", speed);
}
}
#[test]
fn test_single_output_discrete_commands() {
let mut proto = SingleOutputProtocol::new().unwrap();
let commands = [
SingleOutputDiscrete::ToggleFullForward,
SingleOutputDiscrete::ToggleDirection,
SingleOutputDiscrete::IncrementNumericalPwm,
SingleOutputDiscrete::DecrementNumericalPwm,
SingleOutputDiscrete::FullForward,
SingleOutputDiscrete::FullBackward,
];
for cmd in commands {
let pulses = proto.encode_cmd(
Channel::One,
Output::BLUE,
SingleOutputCommand::Discrete(cmd),
);
assert!(pulses.is_ok(), "Encoding failed for discrete cmd={:?}", cmd);
}
}
}