diff --git a/src/background_tasks.rs b/src/background_tasks.rs new file mode 100644 index 0000000..de7035f --- /dev/null +++ b/src/background_tasks.rs @@ -0,0 +1,3 @@ +mod nfc; + +pub use nfc::nfc_scanner; diff --git a/src/background_tasks/nfc.rs b/src/background_tasks/nfc.rs new file mode 100644 index 0000000..1e50d02 --- /dev/null +++ b/src/background_tasks/nfc.rs @@ -0,0 +1,45 @@ +use embedded_hal_bus::i2c::AtomicDevice; +use esp_hal::{Blocking, i2c::master::I2c, time::Instant}; + +use crate::{ + card::{decoder::split_nfc_hex, model::Card}, + drivers::nfc_pn532::NfcPn532Driver, + navigation::navigation::CARD_DATA, +}; + +#[embassy_executor::task] +#[allow( + clippy::large_stack_frames, + reason = "ignoring this for now because it still works" +)] +pub async fn nfc_scanner( + mut nfc_driver: NfcPn532Driver>>, +) { + loop { + while CARD_DATA.lock().await.is_none() { + let start = Instant::now(); + match nfc_driver.read_card_data().await { + Ok(card_data) => { + let Some(split_data) = split_nfc_hex(&card_data) else { + log::error!("Failed to split NFC card data"); + continue; + }; + CARD_DATA + .lock() + .await + .replace(Card::try_from(split_data).unwrap()); + let elapsed_ms = start.elapsed().as_millis(); + log::info!( + "NFC read duration: {} ms, card data length: {}", + elapsed_ms, + card_data.len() + ); + } + Err(e) => { + log::error!("NFC read failed: {e}"); + } + } + } + embassy_time::Timer::after(embassy_time::Duration::from_millis(1000)).await; + } +} diff --git a/src/bin/main.rs b/src/bin/main.rs index b0ae8c7..6a54a85 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -7,19 +7,17 @@ )] #![deny(clippy::large_stack_frames)] -use creaturedex::card::decoder::split_nfc_hex; -use creaturedex::card::model::Card; +use creaturedex::background_tasks; use creaturedex::drivers::i2c_bus::I2cBusPinConfiguration; use creaturedex::drivers::nfc_pn532::NfcPn532Driver; use creaturedex::drivers::secondary_oled::SecondaryDisplay; use creaturedex::drivers::spi_bus; use creaturedex::drivers::tertiary_lcd::{TertiaryDisplay, init_tertiary_lcd, write_wrapped}; use creaturedex::navigation::inputs::Inputs; -use creaturedex::navigation::navigation::{self, CARD_DATA}; +use creaturedex::navigation::navigation::{self}; use creaturedex::navigation::outputs::Outputs; use creaturedex::network::wifi::setup_wifi; use embassy_executor::Spawner; -use embassy_time::Instant; use embedded_graphics::pixelcolor::BinaryColor; use embedded_graphics::{ mono_font::{MonoTextStyle, ascii::FONT_6X10}, @@ -27,10 +25,8 @@ use embedded_graphics::{ text::Text, }; use embedded_hal_bus::i2c::AtomicDevice; -use esp_hal::Blocking; use esp_hal::clock::CpuClock; use esp_hal::gpio::{Input, InputConfig, Pull}; -use esp_hal::i2c::master::I2c; use esp_hal::timer::timg::TimerGroup; use log::error; @@ -135,44 +131,7 @@ async fn main(spawner: Spawner) { log::info!("Setup complete, entering main loop"); spawner.spawn(navigation::run(inputs, outputs).expect("run task failed")); - spawner.spawn(nfc_driver_task(nfc_driver).expect("nfc driver task failed")); -} - -#[embassy_executor::task] -#[allow( - clippy::large_stack_frames, - reason = "ignoring this for now because it still works" -)] -async fn nfc_driver_task( - mut nfc_driver: NfcPn532Driver>>, -) { - loop { - while CARD_DATA.lock().await.is_none() { - let start = Instant::now(); - match nfc_driver.read_card_data().await { - Ok(card_data) => { - let Some(split_data) = split_nfc_hex(&card_data) else { - log::error!("Failed to split NFC card data"); - continue; - }; - CARD_DATA - .lock() - .await - .replace(Card::try_from(split_data).unwrap()); - let elapsed_ms = start.elapsed().as_millis(); - log::info!( - "NFC read duration: {} ms, card data length: {}", - elapsed_ms, - card_data.len() - ); - } - Err(_) => { - log::info!("No NFC card found"); - } - } - } - embassy_time::Timer::after(embassy_time::Duration::from_millis(1000)).await; - } + spawner.spawn(background_tasks::nfc_scanner(nfc_driver).expect("nfc scanner task failed")); } fn display_shit(oled: &mut SecondaryDisplay<'static>, text: &str) { diff --git a/src/drivers/nfc_pn532.rs b/src/drivers/nfc_pn532.rs index 9297bd1..6fa8227 100644 --- a/src/drivers/nfc_pn532.rs +++ b/src/drivers/nfc_pn532.rs @@ -23,9 +23,118 @@ where struct PageParseError { iteration: u8, bytes: [u8; N], - status_code: Option, + status_code: Option, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct CommandResult { + /// # WrErr + /// Set to logic 1, when data is written into the FIFO by the 80C51 during the AutoColl command or MFAuthent command or + /// if data is written into the FIFO by the 80C51 during the time between sending the last bit on the RF interface and + /// receiving the last bit on the RF interface. + write_error: bool, + /// # TempErr + /// Set to logic 1, if the internal temperature sensor detects overheating. In this case the antenna drivers are switched off automatically. + overheating: bool, + /// # RFErr + /// Set to logic 1, if in active communication mode the counterpart does not switch on the RF field in time as defined in NFCIP-1 standard. + /// Note: RFErr is only used in active communication mode. The bit RxFraming or the bit TxFraming has to be set to 01h to enable + /// this functionality. + rf_timeout: bool, + /// # BufferOvfl + /// Set to logic 1, if the 80C51 or if the internal state machine (e.g. receiver) tries to write data into the FIFO buffer + /// although the FIFO buffer is already full. + buffer_overflow: bool, + /// # CollErr + /// Set to logic 1, if a bit-collision is detected. It is set to logic 0 automatically at receiver start phase. + /// This flag is only valid during the bitwise anticollision at 106 kbit/s. During communication schemes at 212 and 424 kbit/s + /// this flag is always set to logic 0. + bit_collision: bool, + /// # CRCErr + /// Set to logic 1, if RxCRCEn in CIU_RxMode register is set to logic 1 and the CRC calculation fails. It is set to logic 0 automatically + /// at receiver start-up phase. + crc_error: bool, + /// # ParityErr + /// Set to logic 1, if the parity check has failed. It is set to logic 0 automatically at receiver start-up phase. + /// Only valid for ISO/IEC 14443A/MIFARE or NFCIP-1 communication at 106 kbit/s. + parity_error: bool, + /// # ProtocollErr + /// Set to logic 1, if one out of the following cases occurs: + /// - Set to logic 1 if the SOF is incorrect. It is set to logic 0 automatically at receiver start-up phase. + /// The bit is only valid for 106 kbit in Active and Passive Communication mode. + /// - If bit DetectSync in CIU_Mode register is set to logic 1 during FeliCa communication or Active Communication + /// with transfer speeds higher than 106 kbit, ProtocolErr is set to logic 1 in case of a byte length violation. + /// - During the AutoColl command, ProtocolErr is set to logic 1, if the Initiator bit in CIU_Control register is set to logic 1. + /// - During the MFAuthent Command, ProtocolErr is set to logic 1, if the number of bytes received in one data stream is incorrect. + /// - Set to logic 1, if the Miller Decoder detects 2 pauses below the minimum time according to the ISO/IEC 14443A definitions. + protocol_error: bool, +} + +impl CommandResult { + pub fn is_error(&self) -> bool { + self.write_error + || self.overheating + || self.rf_timeout + || self.buffer_overflow + || self.bit_collision + || self.crc_error + || self.parity_error + || self.protocol_error + } +} + +impl From for CommandResult { + fn from(value: u8) -> Self { + let mask = |bit| 1u8 << bit; + let take_bit = |bit| (value & mask(bit)) != 0u8; + + Self { + write_error: take_bit(7), + overheating: take_bit(6), + rf_timeout: take_bit(5), + buffer_overflow: take_bit(4), + bit_collision: take_bit(3), + crc_error: take_bit(2), + parity_error: take_bit(1), + protocol_error: take_bit(0), + } + } +} + +impl core::fmt::Display for CommandResult { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + if !self.is_error() { + return write!(f, "ok"); + } + + write!(f, "error: ")?; + + let errors = [ + (self.write_error, "write_error(7)"), + (self.overheating, "overheating(6)"), + (self.rf_timeout, "rf_timeout(5)"), + (self.buffer_overflow, "buffer_overflow(4)"), + (self.bit_collision, "bit_collision(3)"), + (self.crc_error, "crc_error(2)"), + (self.parity_error, "parity_error(1)"), + (self.protocol_error, "protocol_error(0)"), + ] + .into_iter() + .filter_map(|(active, name)| active.then_some(name)); + + let mut first = true; + for error in errors { + let separator = if !first { ", " } else { "" }; + write!(f, "{separator}{error}")?; + first = false; + } + + Ok(()) + } +} + +impl Error for CommandResult {} + impl Error for PageParseError {} impl core::fmt::Display for PageParseError { @@ -38,7 +147,7 @@ impl core::fmt::Display for PageParseError { match status_code { Some(code) => write!( f, - "NFC pages parse error in iteration {iteration} with error code 0x{code:02x}: {bytes:02x?}" + "NFC pages parse error in iteration {iteration} with {code}: {bytes:02x?}" ), None => write!( f, @@ -71,7 +180,7 @@ where } } - pub async fn poll_target(&mut self) -> Result<(), ()> { + pub async fn poll_target(&mut self) -> Result<(), &'static str> { match self .pn532 .process_async(&Request::INLIST_ONE_ISO_A_TARGET, 23) @@ -82,8 +191,8 @@ where Ok(()) } Err(e) => { - info!("No NFC card found on shared I2C bus: {e:?}"); - Err(()) + info!("Failed to poll NFC target: {e:?}"); + Err("Failed to poll NFC target") } } } @@ -101,12 +210,12 @@ where error => Err(PageParseError { iteration, bytes: arr, - status_code: error.copied(), + status_code: error.copied().map(Into::into), }), } } - pub async fn read_card_data(&mut self) -> Result, ()> { + pub async fn read_card_data(&mut self) -> Result, &'static str> { if let Err(e) = self.poll_target().await { error!("PN532 I2C target poll failed before reading card data: {e:?}"); return Err(e); @@ -131,7 +240,13 @@ where match Self::parse_nfc_page(i, bytes) { Ok(data) => break data, - Err(e) => error!("Parsing page failed: {e}"), + Err(e) => { + error!("Parsing page failed: {e}"); + if e.status_code.is_some_and(|e| e.protocol_error) { + // After protocol error, all subsequent reads will fail, so we can stop trying to read more pages. + return Err("Failed to parse NFC page"); + } + } } }; let bytes = if i == 0x0B { &bytes[1..] } else { &bytes }; diff --git a/src/lib.rs b/src/lib.rs index 501ca20..857c224 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ extern crate alloc; +pub mod background_tasks; pub mod card; pub mod display; pub mod drivers;