gree_ir/
lib.rs

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, // 0
12    Long,  // 1
13}
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        // Start
83        let Code::Start = iter.next().ok_or(DecodeError::Eof)? else {
84            return Err(DecodeError::InvalidMarker);
85        };
86        // Code 1
87        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        // Continue
95        let Code::Continue = iter.next().ok_or(DecodeError::Eof)? else {
96            return Err(DecodeError::InvalidMarker);
97        };
98        // Code 2
99        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        // End
106        let Code::End = iter.next().ok_or(DecodeError::Eof)? else {
107            return Err(DecodeError::InvalidMarker);
108        };
109        // Checksum
110        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        // Sum the lower half of the first 4 bytes of this block.
119        for v in self.remote_state.iter().take(4) {
120            sum += *v & 0xF;
121        }
122        // then sum the upper half of the next 3 bytes.
123        for v in self.remote_state[4..].iter().take(3) {
124            sum += *v >> 4;
125        }
126        // Trim it down to fit into the 4 bits allowed. i.e. Mod 16.
127        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        // TODO: support fahrenheit
195        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}