Skip to main content

oxstack_ir/
nec.rs

1//! NEC protocol (the most common consumer IR format): 38 kHz carrier,
2//! 9 ms + 4.5 ms leader, 32 data bits LSB-first, pulse-distance coded.
3
4use crate::Pulse;
5
6/// Carrier frequency in hertz.
7pub const CARRIER_HZ: u32 = 38_000;
8/// Pulses in one frame: leader, 32 bits, trailing mark.
9pub const FRAME_PULSES: usize = 34;
10
11const LEADER: Pulse = Pulse {
12    mark_us: 9000,
13    space_us: 4500,
14};
15const ZERO: Pulse = Pulse {
16    mark_us: 562,
17    space_us: 562,
18};
19const ONE: Pulse = Pulse {
20    mark_us: 562,
21    space_us: 1687,
22};
23const STOP: Pulse = Pulse {
24    mark_us: 562,
25    space_us: 0,
26};
27/// The "button held" repeat frame, sent every ~108 ms.
28pub const REPEAT: [Pulse; 2] = [
29    Pulse {
30        mark_us: 9000,
31        space_us: 2250,
32    },
33    STOP,
34];
35
36/// Encodes a standard NEC frame: address, !address, command, !command.
37pub fn encode(address: u8, command: u8) -> [Pulse; FRAME_PULSES] {
38    encode_raw(u32::from_le_bytes([address, !address, command, !command]))
39}
40
41/// Encodes an extended-NEC frame (16-bit address, no address inverse).
42pub fn encode_extended(address: u16, command: u8) -> [Pulse; FRAME_PULSES] {
43    let [lo, hi] = address.to_le_bytes();
44    encode_raw(u32::from_le_bytes([lo, hi, command, !command]))
45}
46
47fn encode_raw(word: u32) -> [Pulse; FRAME_PULSES] {
48    let mut out = [STOP; FRAME_PULSES];
49    out[0] = LEADER;
50    for bit in 0..32 {
51        out[1 + bit] = if word >> bit & 1 == 1 { ONE } else { ZERO };
52    }
53    out
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    /// Decodes bits back from pulse spaces, as a receiver would.
61    fn decode(p: &[Pulse; FRAME_PULSES]) -> u32 {
62        p[1..33]
63            .iter()
64            .enumerate()
65            .fold(0, |w, (i, b)| w | (u32::from(b.space_us > 1000) << i))
66    }
67
68    #[test]
69    fn standard_frame() {
70        let f = encode(0x04, 0x08);
71        let us = |p: &Pulse| (p.mark_us, p.space_us);
72        assert_eq!(us(&f[0]), (9000, 4500), "leader");
73        assert_eq!(us(&f[33]), (562, 0), "trailing mark");
74        for (i, p) in f[1..33].iter().enumerate() {
75            assert!(matches!(us(p), (562, 562 | 1687)), "bit {i}: {p:?}");
76        }
77        assert_eq!(decode(&f).to_le_bytes(), [0x04, 0xFB, 0x08, 0xF7]);
78    }
79
80    #[test]
81    fn extended_frame_has_16_bit_address() {
82        let w = decode(&encode_extended(0xBEEF, 0x12));
83        assert_eq!(w.to_le_bytes(), [0xEF, 0xBE, 0x12, 0xED]);
84    }
85}