Skip to main content

device_envoy/ir/
mapping.rs

1//! A device abstraction for mapping IR remote buttons to application-specific actions.
2//!
3//! See [`IrMapping`] for usage examples.
4
5use embassy_executor::Spawner;
6use embassy_rp::Peri;
7use embassy_rp::gpio::Pin;
8use embassy_rp::pio::PioPin;
9use heapless::LinearMap;
10
11use crate::Result;
12use crate::ir::{Ir, IrEvent, IrPioPeripheral, IrStatic};
13
14/// Static channel for IR mapping events.
15///
16/// See [`IrMapping`] for usage examples.
17pub struct IrMappingStatic(IrStatic);
18
19impl IrMappingStatic {
20    /// Create static mapping resources.
21    #[must_use]
22    pub(crate) const fn new() -> Self {
23        Self(Ir::new_static())
24    }
25
26    /// Get a reference to the inner channel resources.
27    #[must_use]
28    pub(crate) const fn inner(&self) -> &IrStatic {
29        &self.0
30    }
31}
32
33/// A generic device abstraction that maps IR remote button presses to user-defined button types.
34///
35/// # Examples
36/// ```rust,no_run
37/// # #![no_std]
38/// # #![no_main]
39/// use device_envoy::ir::{IrMapping, IrMappingStatic};
40/// # #[panic_handler]
41/// # fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} }
42/// #[derive(Debug, Clone, Copy)]
43/// enum RemoteButton { Power, Play, Stop }
44/// async fn example(
45///     p: embassy_rp::Peripherals,
46///     spawner: embassy_executor::Spawner,
47/// ) -> device_envoy::Result<()> {
48///     let button_map = [
49///         (0x0000, 0x45, RemoteButton::Power),
50///         (0x0000, 0x0C, RemoteButton::Play),
51///         (0x0000, 0x08, RemoteButton::Stop),
52///     ];
53///
54///     static IR_MAPPING_STATIC: IrMappingStatic = IrMapping::<RemoteButton, 3>::new_static();
55///     let ir_mapping: IrMapping<RemoteButton, 3> = IrMapping::new(&IR_MAPPING_STATIC, p.PIN_15, p.PIO0, &button_map, spawner)?;
56///
57///     loop {
58///         let button = ir_mapping.wait_for_press().await;
59///         // Use button...
60///     }
61/// }
62/// ```
63pub struct IrMapping<'a, B, const N: usize> {
64    ir: Ir<'a>,
65    button_map: LinearMap<(u16, u8), B, N>,
66}
67
68impl<'a, B, const N: usize> IrMapping<'a, B, N>
69where
70    B: Copy,
71{
72    /// Create static channel resources for IR mapping events.
73    ///
74    /// See [`IrMapping`] for usage examples.
75    #[must_use]
76    pub const fn new_static() -> IrMappingStatic {
77        IrMappingStatic::new()
78    }
79
80    /// Create a new IR remote button mapper.
81    ///
82    /// # Parameters
83    /// - `ir_mapping_static`: Static reference to the channel resources
84    /// - `pin`: GPIO pin connected to the IR receiver
85    /// - `pio`: PIO peripheral to use (PIO0, PIO1, or PIO2)
86    /// - `button_map`: Array mapping (address, command) pairs to button types
87    /// - `spawner`: Embassy spawner for background task
88    ///
89    /// See [`IrMapping`] for usage examples.
90    ///
91    /// # Errors
92    /// Returns an error if the background task cannot be spawned.
93    pub fn new<P, PIO>(
94        ir_mapping_static: &'static IrMappingStatic,
95        pin: Peri<'static, P>,
96        pio: Peri<'static, PIO>,
97        button_map: &[(u16, u8, B)],
98        spawner: Spawner,
99    ) -> Result<Self>
100    where
101        P: Pin + PioPin,
102        PIO: IrPioPeripheral,
103    {
104        let ir = Ir::new(ir_mapping_static.inner(), pin, pio, spawner)?;
105
106        // Convert the flat array to a LinearMap
107        let mut map = LinearMap::new();
108        for &(addr, cmd, button) in button_map {
109            map.insert((addr, cmd), button).ok();
110        }
111
112        Ok(Self {
113            ir,
114            button_map: map,
115        })
116    }
117
118    /// Wait for the next recognized button press.
119    ///
120    /// Ignores button presses that are not in the button map.
121    ///
122    /// See [`IrMapping`] for usage examples.
123    pub async fn wait_for_press(&self) -> B {
124        loop {
125            let IrEvent::Press { addr, cmd } = self.ir.wait_for_press().await;
126            #[cfg(feature = "defmt")]
127            defmt::info!("IR received - addr=0x{:04X} cmd=0x{:02X}", addr, cmd);
128            if let Some(&button) = self.button_map.get(&(addr, cmd)) {
129                return button;
130            }
131            #[cfg(feature = "defmt")]
132            defmt::info!("  (unrecognized - ignoring)");
133        }
134    }
135}