diff --git a/src/bin/main.rs b/src/bin/main.rs index b0ae8c7..f1296b5 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -166,8 +166,8 @@ async fn nfc_driver_task( card_data.len() ); } - Err(_) => { - log::info!("No NFC card found"); + Err(e) => { + log::error!("NFC read failed: {e}"); } } } diff --git a/src/drivers/nfc_pn532.rs b/src/drivers/nfc_pn532.rs index 9297bd1..805d918 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,12 @@ 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) { + return Err("Failed to parse NFC page"); + } + } } }; let bytes = if i == 0x0B { &bytes[1..] } else { &bytes };