Skip to main content

device_envoy_esp/
rfid.rs

1//! A device abstraction for RFID readers using the MFRC522 chip.
2//!
3//! See [`RfidEsp`] for the primary example.
4//!
5//! You can create up to two concurrent `RfidEsp` instances per program; a third is expected to fail at runtime because the `rfid` task pool uses `pool_size = 2`.
6
7pub use device_envoy_core::rfid::{Rfid, RfidEvent};
8use embassy_executor::Spawner;
9use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
10use embassy_sync::channel::Channel as EmbassyChannel;
11use embassy_time::{Instant, Timer};
12use embedded_hal_bus::spi::{ExclusiveDevice, NoDelay};
13use esp_hal::gpio::interconnect::{PeripheralInput, PeripheralOutput};
14use esp_hal::gpio::{Level, Output, OutputConfig, OutputPin};
15use esp_hal::spi::master::{Config as SpiConfig, Instance, Spi};
16use esp_hal_mfrc522::consts::UidSize;
17use esp_hal_mfrc522::drivers::SpiDriver;
18use esp_hal_mfrc522::MFRC522;
19
20use crate::{Error, Result};
21
22type Mfrc522Device =
23    MFRC522<SpiDriver<ExclusiveDevice<Spi<'static, esp_hal::Async>, Output<'static>, NoDelay>>>;
24
25/// Static resources for the [`RfidEsp`] device abstraction.
26pub struct RfidStatic(EmbassyChannel<CriticalSectionRawMutex, RfidEvent, 4>);
27
28impl RfidStatic {
29    /// Create static resources for an RFID reader.
30    #[must_use]
31    pub const fn new() -> Self {
32        Self(EmbassyChannel::new())
33    }
34
35    async fn send(&self, event: RfidEvent) {
36        self.0.send(event).await;
37    }
38
39    async fn receive(&self) -> RfidEvent {
40        self.0.receive().await
41    }
42}
43
44/// A device abstraction for an RFID reader using the MFRC522 chip.
45///
46/// # Example
47///
48/// ```rust,no_run
49/// # #![no_std]
50/// # #![no_main]
51/// use device_envoy_esp::{
52///     Result, init_and_start,
53///     rfid::{Rfid, RfidEsp, RfidEvent, RfidStatic},
54/// };
55/// # use esp_backtrace as _;
56/// # use log::info;
57///
58/// #[esp_rtos::main]
59/// async fn main(spawner: embassy_executor::Spawner) -> ! {
60///     match example(spawner).await {
61///         Ok(infallible) => match infallible {},
62///         Err(error) => panic!("{error:?}"),
63///     }
64/// }
65///
66/// async fn example(spawner: embassy_executor::Spawner) -> Result<core::convert::Infallible> {
67///     init_and_start!(p);
68///     static RFID_STATIC: RfidStatic = RfidEsp::new_static();
69///
70///     let rfid = RfidEsp::new(
71///         &RFID_STATIC,
72///         p.SPI2,
73///         p.GPIO6,
74///         p.GPIO7,
75///         p.GPIO2,
76///         p.GPIO10,
77///         p.GPIO5,
78///         spawner,
79///     )
80///     .await?;
81///
82///     loop {
83///         let RfidEvent::CardDetected { uid } = rfid.wait_for_tap().await;
84///         info!("RFID uid: {:?}", uid);
85///     }
86/// }
87/// ```
88pub struct RfidEsp<'a> {
89    rfid_static: &'a RfidStatic,
90}
91
92impl RfidEsp<'_> {
93    /// Create static channel resources for an RFID reader.
94    #[must_use]
95    pub const fn new_static() -> RfidStatic {
96        RfidStatic::new()
97    }
98
99    /// Create a new RFID reader device abstraction.
100    ///
101    /// See the [Rfid struct example](Self) for usage.
102    pub async fn new(
103        rfid_static: &'static RfidStatic,
104        spi: impl Instance + 'static,
105        sck: impl PeripheralOutput<'static>,
106        mosi: impl PeripheralOutput<'static>,
107        miso: impl PeripheralInput<'static>,
108        cs: impl OutputPin + 'static,
109        rst: impl OutputPin + 'static,
110        spawner: Spawner,
111    ) -> Result<Self> {
112        let mfrc522 = init_mfrc522_hardware(spi, sck, mosi, miso, cs, rst).await?;
113        let token = rfid_polling_task(mfrc522, rfid_static);
114        spawner.spawn(token).map_err(Error::TaskSpawn)?;
115        Ok(Self { rfid_static })
116    }
117}
118
119impl Rfid for RfidEsp<'_> {
120    async fn wait_for_tap(&self) -> RfidEvent {
121        self.rfid_static.receive().await
122    }
123}
124
125fn uid_to_fixed_array(uid_bytes: &[u8]) -> [u8; 10] {
126    let mut uid_key = [0u8; 10];
127    for (uid_index, &uid_byte) in uid_bytes.iter().enumerate() {
128        if uid_index < uid_key.len() {
129            uid_key[uid_index] = uid_byte;
130        }
131    }
132    uid_key
133}
134
135#[embassy_executor::task(pool_size = 2)]
136async fn rfid_polling_task(mut mfrc522: Mfrc522Device, rfid_static: &'static RfidStatic) -> ! {
137    loop {
138        let Ok(()) = mfrc522.picc_is_new_card_present().await else {
139            Timer::after_millis(500).await;
140            continue;
141        };
142
143        let Ok(uid) = mfrc522.get_card(UidSize::Four).await else {
144            Timer::after_millis(500).await;
145            continue;
146        };
147
148        let uid_key = uid_to_fixed_array(&uid.uid_bytes);
149        rfid_static
150            .send(RfidEvent::CardDetected { uid: uid_key })
151            .await;
152        Timer::after_millis(50).await;
153    }
154}
155
156async fn init_mfrc522_hardware(
157    spi: impl Instance + 'static,
158    sck: impl PeripheralOutput<'static>,
159    mosi: impl PeripheralOutput<'static>,
160    miso: impl PeripheralInput<'static>,
161    cs: impl OutputPin + 'static,
162    rst: impl OutputPin + 'static,
163) -> Result<Mfrc522Device> {
164    let spi_config = SpiConfig::default()
165        .with_frequency(esp_hal::time::Rate::from_hz(1_000_000))
166        .with_mode(esp_hal::spi::Mode::_0);
167    let spi = Spi::new(spi, spi_config)
168        .map_err(Error::SpiConfig)?
169        .with_sck(sck)
170        .with_mosi(mosi)
171        .with_miso(miso)
172        .into_async();
173
174    let cs = Output::new(cs, Level::High, OutputConfig::default());
175
176    let mut rst = Output::new(rst, Level::High, OutputConfig::default());
177    rst.set_low();
178    Timer::after_millis(10).await;
179    rst.set_high();
180    Timer::after_millis(50).await;
181
182    let spi_device = ExclusiveDevice::new_no_delay(spi, cs).expect("CS pin is infallible");
183    let spi_driver = SpiDriver::new(spi_device);
184    let mut mfrc522 = MFRC522::new(spi_driver, || Instant::now().as_millis());
185
186    mfrc522.pcd_init().await.map_err(Error::Mfrc522Init)?;
187    let _version = mfrc522
188        .pcd_get_version()
189        .await
190        .map_err(Error::Mfrc522Version)?;
191
192    Ok(mfrc522)
193}