1use crate::error::Error;
9use crate::payload_util::require_positive_multiple_of;
10use alloc::vec::Vec;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14#[repr(u8)]
15#[allow(missing_docs)]
16pub enum LedColor {
17 Black = 0,
18 Red = 1,
19 Green = 2,
20 Amber = 3,
21 Blue = 4,
22 Magenta = 5,
23 Cyan = 6,
24 White = 7,
25}
26
27impl LedColor {
28 pub const fn from_byte(b: u8) -> Self {
30 match b {
31 0 => Self::Black,
32 1 => Self::Red,
33 2 => Self::Green,
34 3 => Self::Amber,
35 4 => Self::Blue,
36 5 => Self::Magenta,
37 6 => Self::Cyan,
38 7 => Self::White,
39 _ => Self::Black,
40 }
41 }
42
43 pub const fn as_byte(self) -> u8 {
45 self as u8
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub struct LedTemporary {
52 pub control: u8,
54 pub on_time: u8,
56 pub off_time: u8,
58 pub on_color: LedColor,
60 pub off_color: LedColor,
62 pub timer: u16,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub struct LedPermanent {
69 pub control: u8,
71 pub on_time: u8,
73 pub off_time: u8,
75 pub on_color: LedColor,
77 pub off_color: LedColor,
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub struct LedRecord {
84 pub reader: u8,
86 pub led: u8,
88 pub temporary: LedTemporary,
90 pub permanent: LedPermanent,
92}
93
94impl LedRecord {
95 pub const WIRE_LEN: usize = 14;
97
98 fn encode_into(&self, out: &mut Vec<u8>) {
99 out.push(self.reader);
100 out.push(self.led);
101
102 out.push(self.temporary.control);
103 out.push(self.temporary.on_time);
104 out.push(self.temporary.off_time);
105 out.push(self.temporary.on_color.as_byte());
106 out.push(self.temporary.off_color.as_byte());
107 out.extend_from_slice(&self.temporary.timer.to_le_bytes());
108
109 out.push(self.permanent.control);
110 out.push(self.permanent.on_time);
111 out.push(self.permanent.off_time);
112 out.push(self.permanent.on_color.as_byte());
113 out.push(self.permanent.off_color.as_byte());
114 }
115
116 fn decode(buf: &[u8]) -> Self {
117 Self {
118 reader: buf[0],
119 led: buf[1],
120 temporary: LedTemporary {
121 control: buf[2],
122 on_time: buf[3],
123 off_time: buf[4],
124 on_color: LedColor::from_byte(buf[5]),
125 off_color: LedColor::from_byte(buf[6]),
126 timer: u16::from_le_bytes([buf[7], buf[8]]),
127 },
128 permanent: LedPermanent {
129 control: buf[9],
130 on_time: buf[10],
131 off_time: buf[11],
132 on_color: LedColor::from_byte(buf[12]),
133 off_color: LedColor::from_byte(buf[13]),
134 },
135 }
136 }
137}
138
139#[derive(Debug, Clone, PartialEq, Eq)]
141pub struct LedControl {
142 pub records: Vec<LedRecord>,
144}
145
146impl LedControl {
147 pub fn new(records: Vec<LedRecord>) -> Self {
149 Self { records }
150 }
151
152 pub fn encode(&self) -> Result<Vec<u8>, Error> {
154 if self.records.is_empty() {
155 return Err(Error::MalformedPayload {
156 code: 0x69,
157 reason: "LED requires at least one record",
158 });
159 }
160 let mut out = Vec::with_capacity(self.records.len() * LedRecord::WIRE_LEN);
161 for r in &self.records {
162 r.encode_into(&mut out);
163 }
164 Ok(out)
165 }
166
167 pub fn decode(data: &[u8]) -> Result<Self, Error> {
169 require_positive_multiple_of(data, LedRecord::WIRE_LEN, 0x69)?;
170 let records = data
171 .chunks_exact(LedRecord::WIRE_LEN)
172 .map(LedRecord::decode)
173 .collect();
174 Ok(Self { records })
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn roundtrip() {
184 let body = LedControl::new(alloc::vec![LedRecord {
185 reader: 0,
186 led: 1,
187 temporary: LedTemporary {
188 control: 2,
189 on_time: 5,
190 off_time: 5,
191 on_color: LedColor::Green,
192 off_color: LedColor::Black,
193 timer: 50,
194 },
195 permanent: LedPermanent {
196 control: 1,
197 on_time: 0xFF,
198 off_time: 0,
199 on_color: LedColor::Red,
200 off_color: LedColor::Black,
201 },
202 }]);
203 let bytes = body.encode().unwrap();
204 assert_eq!(bytes.len(), 14);
205 let decoded = LedControl::decode(&bytes).unwrap();
206 assert_eq!(decoded, body);
207 }
208}