1#![no_std]
2
3use core::{fmt::Debug, hint::unreachable_unchecked, iter::once};
4
5#[derive(Clone, Copy, Debug, PartialEq)]
6pub enum Code {
7 Start,
8 Continue,
9 End,
10
11 Short, Long, }
14
15impl From<bool> for Code {
16 fn from(value: bool) -> Self {
17 if value {
18 Code::Long
19 } else {
20 Code::Short
21 }
22 }
23}
24
25impl TryInto<u8> for &Code {
26 type Error = DecodeError;
27
28 fn try_into(self) -> Result<u8, Self::Error> {
29 match self {
30 Code::Start | Code::Continue | Code::End => Err(DecodeError::UnexpectedMarker),
31 Code::Short => Ok(0),
32 Code::Long => Ok(1),
33 }
34 }
35}
36
37impl TryInto<bool> for &Code {
38 type Error = DecodeError;
39
40 fn try_into(self) -> Result<bool, Self::Error> {
41 match self {
42 Code::Start | Code::Continue | Code::End => Err(DecodeError::UnexpectedMarker),
43 Code::Short => Ok(false),
44 Code::Long => Ok(true),
45 }
46 }
47}
48
49#[derive(Clone)]
50pub struct Message {
51 remote_state: [u8; 8],
52}
53
54impl Message {
55 pub fn new() -> Self {
56 let mut msg = Self {
57 remote_state: [0, 0, 0, 0b01010000, 0, 0b00100000, 0, 0],
58 };
59 msg.update_checksum();
60 msg
61 }
62
63 pub fn raw(&self) -> &[u8; 8] {
64 &self.remote_state
65 }
66
67 pub fn encode(&self) -> impl Iterator<Item = Code> + '_ {
68 let byte_to_codes = |x| (0..8).map(move |i| Code::from(x >> i & 1u8 != 0u8));
69 let code1 = self.remote_state[..4].iter().flat_map(byte_to_codes);
70 let code2 = self.remote_state[4..].iter().flat_map(byte_to_codes);
71 once(Code::Start)
72 .chain(code1)
73 .chain(MAGIC_3.into_iter())
74 .chain(once(Code::Continue))
75 .chain(code2)
76 .chain(once(Code::End))
77 }
78
79 pub fn decode(codes: &[Code; 70]) -> Result<Self, DecodeError> {
80 let mut message = Self::new();
81 let mut iter = codes.iter();
82 let Code::Start = iter.next().ok_or(DecodeError::Eof)? else {
84 return Err(DecodeError::InvalidMarker);
85 };
86 for v in message.remote_state[..4].iter_mut() {
88 for i in 0..8 {
89 let t: &Code = iter.next().ok_or(DecodeError::Eof)?;
90 *v |= TryInto::<u8>::try_into(t)? << i;
91 }
92 }
93 check_magic_code3(&mut iter)?;
94 let Code::Continue = iter.next().ok_or(DecodeError::Eof)? else {
96 return Err(DecodeError::InvalidMarker);
97 };
98 for v in message.remote_state[4..].iter_mut() {
100 for i in 0..8 {
101 let t: &Code = iter.next().ok_or(DecodeError::Eof)?;
102 *v |= TryInto::<u8>::try_into(t)? << i;
103 }
104 }
105 let Code::End = iter.next().ok_or(DecodeError::Eof)? else {
107 return Err(DecodeError::InvalidMarker);
108 };
109 if message.checksum() != message.remote_state[7] >> 4 {
111 return Err(DecodeError::Checksum);
112 }
113 Ok(message)
114 }
115
116 fn checksum(&self) -> u8 {
117 let mut sum = 10;
118 for v in self.remote_state.iter().take(4) {
120 sum += *v & 0xF;
121 }
122 for v in self.remote_state[4..].iter().take(3) {
124 sum += *v >> 4;
125 }
126 sum & 0xF
128 }
129
130 fn update_checksum(&mut self) {
131 self.remote_state[7] &= 0x0F;
132 self.remote_state[7] |= self.checksum() << 4;
133 }
134
135 pub fn mode(&self) -> Result<Mode, DecodeError> {
136 match self.remote_state[0] & 0b111 {
137 0 => Ok(Mode::Auto),
138 1 => Ok(Mode::Cold),
139 2 => Ok(Mode::Dry),
140 3 => Ok(Mode::Wind),
141 4 => Ok(Mode::Hot),
142 _ => Err(DecodeError::InvalidMode),
143 }
144 }
145
146 pub fn set_mode(&mut self, mode: Mode) {
147 self.remote_state[0] = self.remote_state[0] & 0b1111_1000 | mode as u8;
148 self.update_checksum();
149 }
150
151 pub fn is_on(&self) -> bool {
152 self.remote_state[0] >> 3 & 1 != 0
153 }
154
155 pub fn set_on(&mut self, on: bool) {
156 self.remote_state[0] = self.remote_state[0] & 0b1111_0111 | (on as u8) << 3;
157 self.update_checksum();
158 }
159
160 pub fn fan(&self) -> Fan {
161 match self.remote_state[0] >> 4 & 0b11 {
162 0 => Fan::Auto,
163 1 => Fan::Level1,
164 2 => Fan::Level2,
165 3 => Fan::Level3,
166 _ => unsafe { unreachable_unchecked() },
167 }
168 }
169
170 pub fn set_fan(&mut self, fan: Fan) {
171 self.remote_state[0] = self.remote_state[0] & 0b1100_1111 | (fan as u8) << 4;
172 self.update_checksum();
173 }
174
175 pub fn swing(&self) -> bool {
176 self.remote_state[0] >> 6 & 1 != 0
177 }
178
179 pub fn set_swing(&mut self, swing: bool) {
180 self.remote_state[0] = self.remote_state[0] & 0b1011_1111 | (swing as u8) << 6;
181 self.update_checksum();
182 }
183
184 pub fn sleep(&self) -> bool {
185 self.remote_state[0] >> 7 & 1 != 0
186 }
187
188 pub fn set_sleep(&mut self, sleep: bool) {
189 self.remote_state[0] = self.remote_state[0] & 0b0111_1111 | (sleep as u8) << 7;
190 self.update_checksum();
191 }
192
193 pub fn temperature(&self) -> Result<Temperature, DecodeError> {
194 let value = self.remote_state[1] & 0x0F;
196 if value <= 30 - 16 {
197 Ok(Temperature::Centigrade(value + 16))
198 } else {
199 Err(DecodeError::InvalidTemperature)
200 }
201 }
202
203 pub fn set_temperature(&mut self, temp: Temperature) {
204 let value = match temp {
205 Temperature::Centigrade(degree) if degree >= 16 && degree <= 30 => degree - 16,
206 _ => 25 - 16,
207 };
208 self.remote_state[1] = self.remote_state[1] & 0xF0 | value;
209 self.update_checksum();
210 }
211
212 pub fn timer(&self) -> Result<TimerSetting, DecodeError> {
213 TimerSetting::try_from(self.remote_state[1] >> 4 | self.remote_state[2] << 4)
214 }
215
216 pub fn set_timer(&mut self, setting: &TimerSetting) {
217 let value: u8 = setting.into();
218 self.remote_state[1] = self.remote_state[1] & 0x0F | value << 4;
219 self.remote_state[2] = self.remote_state[2] & 0xF0 | value & 0x0F;
220 self.update_checksum();
221 }
222
223 pub fn turbo(&self) -> bool {
224 self.remote_state[2] >> 4 & 1 != 0
225 }
226
227 pub fn set_turbo(&mut self, turbo: bool) {
228 self.remote_state[2] = self.remote_state[2] & 0b1110_1111 | (turbo as u8) << 4;
229 self.update_checksum();
230 }
231
232 pub fn light(&self) -> bool {
233 self.remote_state[2] >> 5 & 1 != 0
234 }
235
236 pub fn set_light(&mut self, light: bool) {
237 self.remote_state[2] = self.remote_state[2] & 0b1101_1111 | (light as u8) << 5;
238 self.update_checksum();
239 }
240
241 pub fn health(&self) -> bool {
242 self.remote_state[2] >> 6 & 1 != 0
243 }
244
245 pub fn set_health(&mut self, health: bool) {
246 self.remote_state[2] = self.remote_state[2] & 0b1011_1111 | (health as u8) << 6;
247 self.update_checksum();
248 }
249
250 pub fn dry(&self) -> bool {
251 self.remote_state[2] >> 7 & 1 != 0
252 }
253
254 pub fn set_dry(&mut self, dry: bool) {
255 self.remote_state[2] = self.remote_state[2] & 0b0111_1111 | (dry as u8) << 7;
256 self.update_checksum();
257 }
258
259 pub fn ventilate(&self) -> bool {
260 self.remote_state[3] & 1 != 0
261 }
262
263 pub fn set_ventilateo(&mut self, ventilate: bool) {
264 self.remote_state[3] = self.remote_state[3] & 0b1111_1110 | ventilate as u8;
265 self.update_checksum();
266 }
267
268 pub fn v_swing(&self) -> SwingMode {
269 match self.remote_state[4] & 0xF {
270 0 => SwingMode::Off,
271 1 => SwingMode::On,
272 2 => SwingMode::Unknown2,
273 3 => SwingMode::Unknown3,
274 4 => SwingMode::Unknown4,
275 5 => SwingMode::Unknown5,
276 6 => SwingMode::Unknown6,
277 7 => SwingMode::Unknown7,
278 8 => SwingMode::Unknown8,
279 9 => SwingMode::Unknown9,
280 10 => SwingMode::Unknown10,
281 11 => SwingMode::Unknown11,
282 12 => SwingMode::Unknown12,
283 13 => SwingMode::Unknown13,
284 14 => SwingMode::Unknown14,
285 15 => SwingMode::Unknown15,
286 _ => unsafe { unreachable_unchecked() },
287 }
288 }
289
290 pub fn set_v_swing(&mut self, mode: SwingMode) {
291 self.remote_state[4] = self.remote_state[4] & 0xF0 | mode as u8;
292 self.update_checksum();
293 }
294
295 pub fn h_swing(&self) -> SwingMode {
296 match self.remote_state[4] >> 4 {
297 0 => SwingMode::Off,
298 1 => SwingMode::On,
299 2 => SwingMode::Unknown2,
300 3 => SwingMode::Unknown3,
301 4 => SwingMode::Unknown4,
302 5 => SwingMode::Unknown5,
303 6 => SwingMode::Unknown6,
304 7 => SwingMode::Unknown7,
305 8 => SwingMode::Unknown8,
306 9 => SwingMode::Unknown9,
307 10 => SwingMode::Unknown10,
308 11 => SwingMode::Unknown11,
309 12 => SwingMode::Unknown12,
310 13 => SwingMode::Unknown13,
311 14 => SwingMode::Unknown14,
312 15 => SwingMode::Unknown15,
313 _ => unsafe { unreachable_unchecked() },
314 }
315 }
316
317 pub fn set_h_swing(&mut self, mode: SwingMode) {
318 self.remote_state[4] = self.remote_state[4] & 0x0F | (mode as u8) << 4;
319 self.update_checksum();
320 }
321
322 pub fn temperature_display(&self) -> TemperatureDisplay {
323 match self.remote_state[5] & 0b11 {
324 0 => TemperatureDisplay::Setting,
325 1 => TemperatureDisplay::Room,
326 2 => TemperatureDisplay::Indoor,
327 3 => TemperatureDisplay::Outdoor,
328 _ => unsafe { unreachable_unchecked() },
329 }
330 }
331
332 pub fn set_temperature_display(&mut self, temp_display: TemperatureDisplay) {
333 self.remote_state[5] = self.remote_state[5] & 0b1111_1100 | temp_display as u8;
334 self.update_checksum();
335 }
336
337 pub fn i_feel(&self) -> bool {
338 self.remote_state[5] >> 2 & 1 != 0
339 }
340
341 pub fn set_i_feel(&mut self, i_feel: bool) {
342 self.remote_state[5] = self.remote_state[5] & 0b1111_1011 | (i_feel as u8) << 2;
343 self.update_checksum();
344 }
345
346 pub fn wifi(&self) -> bool {
347 self.remote_state[5] >> 6 & 1 != 0
348 }
349
350 pub fn set_wifi(&mut self, wifi: bool) {
351 self.remote_state[5] = self.remote_state[5] & 0b1011_1111 | (wifi as u8) << 6;
352 self.update_checksum();
353 }
354
355 pub fn econo(&self) -> bool {
356 self.remote_state[7] >> 2 & 1 != 0
357 }
358
359 pub fn set_econo(&mut self, econo: bool) {
360 self.remote_state[7] = self.remote_state[7] & 0b1111_1011 | (econo as u8) << 2;
361 self.update_checksum();
362 }
363}
364
365impl Debug for Message {
366 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
367 f.debug_struct("Message")
368 .field("mode", &self.mode())
369 .field("on", &self.is_on())
370 .field("fan", &self.fan())
371 .field("swing", &self.swing())
372 .field("sleep", &self.sleep())
373 .field("temperature", &self.temperature())
374 .field("timer", &self.timer())
375 .field("turbo", &self.turbo())
376 .field("light", &self.light())
377 .field("health", &self.health())
378 .field("dry", &self.dry())
379 .field("ventilate", &self.ventilate())
380 .field("v_swing", &self.v_swing())
381 .field("h_swing", &self.h_swing())
382 .field("temperature_display", &self.temperature_display())
383 .field("i_feel", &self.i_feel())
384 .field("wifi", &self.wifi())
385 .field("econo", &self.econo())
386 .finish()
387 }
388}
389
390#[derive(Clone, Debug)]
391pub enum DecodeError {
392 InvalidMarker,
393 UnexpectedMarker,
394 InvalidMode,
395 InvalidTimerSetting,
396 InvalidFan,
397 InvalidTemperature,
398 InvalidSwingMode,
399 InvalidMagic,
400 Eof,
401 Checksum,
402}
403
404#[derive(Clone, Copy, Debug)]
405pub enum Mode {
406 Auto,
407 Cold,
408 Dry,
409 Wind,
410 Hot,
411}
412
413#[derive(Clone, Copy, Debug)]
414pub enum Fan {
415 Auto,
416 Level1,
417 Level2,
418 Level3,
419}
420
421#[derive(Clone, Copy)]
422pub enum Temperature {
423 Centigrade(u8),
424}
425
426impl Debug for Temperature {
427 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
428 match self {
429 Temperature::Centigrade(degree) => f.write_fmt(format_args!("{} ℃", degree)),
430 }
431 }
432}
433
434#[derive(Clone, Copy, Debug)]
435pub struct TimerSetting {
436 pub enabled: bool,
437 pub half_hours: u8,
438}
439
440impl TryFrom<u8> for TimerSetting {
441 type Error = DecodeError;
442
443 fn try_from(value: u8) -> Result<Self, Self::Error> {
444 let half = value & 1;
445 let tens = value >> 1 & 0b11;
446 let enabled = value >> 3 & 1 != 0;
447 let units = value >> 4;
448 if tens > 2 || units > 9 {
449 Err(DecodeError::InvalidTimerSetting)
450 } else {
451 Ok(Self {
452 enabled,
453 half_hours: (tens * 10 + units) * 2 + half,
454 })
455 }
456 }
457}
458
459impl Into<u8> for &TimerSetting {
460 fn into(self) -> u8 {
461 let hours = self.half_hours / 2;
462 let half = self.half_hours % 2;
463 let tens = hours / 10;
464 let units = hours % 10;
465 half | tens << 1 | (self.enabled as u8) << 3 | units << 4
466 }
467}
468
469#[derive(Clone, Copy, Debug)]
470pub enum SwingMode {
471 Off,
472 On,
473 Unknown2,
474 Unknown3,
475 Unknown4,
476 Unknown5,
477 Unknown6,
478 Unknown7,
479 Unknown8,
480 Unknown9,
481 Unknown10,
482 Unknown11,
483 Unknown12,
484 Unknown13,
485 Unknown14,
486 Unknown15,
487}
488
489#[derive(Clone, Copy, Debug)]
490pub enum TemperatureDisplay {
491 Setting,
492 Room,
493 Indoor,
494 Outdoor,
495}
496
497const MAGIC_3: [Code; 3] = [Code::Short, Code::Long, Code::Short];
498
499fn check_magic_code3<'a>(iter: &mut impl Iterator<Item = &'a Code>) -> Result<(), DecodeError> {
500 let mut codes = [Code::Short; 3];
501 for v in codes.iter_mut() {
502 *v = *iter.next().ok_or(DecodeError::Eof)?;
503 }
504 match codes {
505 MAGIC_3 => Ok(()),
506 _ => Err(DecodeError::InvalidMagic),
507 }
508}