device_envoy_core/ir.rs
1//! A device abstraction for infrared receivers using the NEC protocol.
2//!
3//! This module provides platform-independent types and helpers for NEC IR decoding.
4//! See the platform-specific crate (for example `device_envoy_rp::ir` or
5//! `device_envoy_esp::ir`) for the primary documentation and examples.
6
7pub mod kepler;
8pub mod mapping;
9
10pub use kepler::{IrKepler, IrKeplerStatic, KEPLER_MAPPING, KeplerKeys};
11pub use mapping::{IrMapping, IrMappingStatic};
12
13use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
14use embassy_sync::channel::Channel as EmbassyChannel;
15
16/// Events received from the infrared receiver.
17///
18/// See the platform-specific crate for usage examples.
19#[derive(Copy, Clone, Debug, PartialEq)]
20pub enum IrEvent {
21 /// Button press with 16-bit address and 8-bit command.
22 /// Supports both standard NEC (8-bit address) and extended NEC (16-bit address).
23 Press {
24 /// 16-bit device address (or 8-bit address in low byte for standard NEC).
25 addr: u16,
26 /// 8-bit command code.
27 cmd: u8,
28 },
29}
30
31/// Platform-agnostic infrared receiver device contract.
32///
33/// Platform crates implement this for their concrete `Ir` types so shared logic can wait for
34/// NEC button press events without depending on platform-specific modules.
35///
36/// This trait is the core `wait_for_press` surface used by platform IR receiver wrappers.
37///
38/// # Example
39///
40/// ```rust,no_run
41/// use device_envoy_core::ir::{Ir, IrEvent};
42///
43/// async fn handle_ir_presses(ir: &impl Ir) -> ! {
44/// loop {
45/// let ir_event = ir.wait_for_press().await;
46/// match ir_event {
47/// IrEvent::Press { addr, cmd } => {
48/// // Handle decoded NEC press event.
49/// let _ = (addr, cmd);
50/// }
51/// }
52/// }
53/// }
54///
55/// # struct DemoIr;
56/// # impl Ir for DemoIr {
57/// # async fn wait_for_press(&self) -> IrEvent {
58/// # IrEvent::Press { addr: 0, cmd: 0 }
59/// # }
60/// # }
61/// # fn main() {
62/// # let ir = DemoIr;
63/// # let _future = handle_ir_presses(&ir);
64/// # }
65/// ```
66#[allow(async_fn_in_trait)]
67pub trait Ir {
68 /// Wait for the next IR event.
69 ///
70 /// See the [Ir trait documentation](Self) for usage examples.
71 async fn wait_for_press(&self) -> IrEvent;
72}
73
74/// Static resources for the [`Ir`] device abstraction.
75///
76/// See the platform-specific crate for usage examples.
77// Public for cross-crate platform plumbing; hidden from end-user docs.
78#[doc(hidden)]
79pub struct IrStatic(EmbassyChannel<CriticalSectionRawMutex, IrEvent, 8>);
80
81impl IrStatic {
82 /// Creates static resources for the infrared receiver device.
83 #[must_use]
84 pub const fn new() -> Self {
85 Self(EmbassyChannel::new())
86 }
87
88 /// Send an IR event to waiting receivers.
89 ///
90 /// Called by the platform's hardware receiver task.
91 pub async fn send(&self, event: IrEvent) {
92 self.0.send(event).await;
93 }
94
95 /// Wait for the next IR event.
96 pub async fn receive(&self) -> IrEvent {
97 self.0.receive().await
98 }
99}
100
101/// Decode and validate a 32-bit NEC frame.
102///
103/// NEC protocol structure (32 bits, LSB first):
104/// - Byte 0: Address (8 bits)
105/// - Byte 1: Address inverse (~Address)
106/// - Byte 2: Command (8 bits)
107/// - Byte 3: Command inverse (~Command)
108///
109/// Extended NEC uses 16-bit address (bytes 0-1) without inversion check.
110///
111/// Returns `Some((address, command))` if valid, `None` if checksum fails.
112// Public for cross-crate platform plumbing; hidden from end-user docs.
113#[doc(hidden)]
114pub fn decode_nec_frame(frame: u32) -> Option<(u16, u8)> {
115 let byte0 = (frame & 0xFF) as u8;
116 let byte1 = ((frame >> 8) & 0xFF) as u8;
117 let byte2 = ((frame >> 16) & 0xFF) as u8;
118 let byte3 = ((frame >> 24) & 0xFF) as u8;
119
120 // Validate command bytes (required in both standard and extended NEC)
121 if (byte2 ^ byte3) != 0xFF {
122 return None;
123 }
124
125 // Standard NEC: 8-bit address with inverse validation
126 if (byte0 ^ byte1) == 0xFF {
127 return Some((u16::from(byte0), byte2));
128 }
129
130 // Extended NEC: 16-bit address (no inversion check on address)
131 let addr16 = ((u16::from(byte1)) << 8) | u16::from(byte0);
132 Some((addr16, byte2))
133}