use alloc::vec::Vec; use core::error::Error; use embedded_hal::i2c::I2c; use log::{error, info}; use pn532::i2c::I2CInterface; use pn532::requests::SAMMode; use pn532::{Pn532, Request}; pub type Pn532Device = Pn532, (), 34>; pub const PAGES_PER_READ: usize = 4; pub const BYTES_PER_READ: usize = PAGES_PER_READ * 4; /// PN532 NFC reader driver. pub struct NfcPn532Driver where I2C: I2c, { pn532: Pn532Device, } #[derive(Debug, Clone)] struct PageParseError { iteration: u8, bytes: [u8; N], 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 { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let PageParseError { iteration, bytes, status_code, } = self; match status_code { Some(code) => write!( f, "NFC pages parse error in iteration {iteration} with {code}: {bytes:02x?}" ), None => write!( f, "No data available for NFC page in iteration {iteration}: {bytes:02x?}" ), } } } impl NfcPn532Driver where I2C: I2c, { pub fn new(i2c: I2C) -> Self { let pn532 = Pn532::new_async(I2CInterface { i2c }); Self { pn532 } } pub async fn configure_sam(&mut self) -> Result<(), ()> { if let Err(e) = self .pn532 .process_async(&Request::sam_configuration(SAMMode::Normal, false), 0) .await { error!("PN532 SAM configuration failed over I2C: {e:?}"); Err(()) } else { info!("PN532 SAM configuration successful on shared I2C bus"); Ok(()) } } pub async fn poll_target(&mut self) -> Result<(), &'static str> { match self .pn532 .process_async(&Request::INLIST_ONE_ISO_A_TARGET, 23) .await { Ok(uid) => { info!("PN532 detected NFC target UID: {uid:?}"); Ok(()) } Err(e) => { info!("Failed to poll NFC target: {e:?}"); Err("Failed to poll NFC target") } } } fn parse_nfc_page( iteration: u8, bytes: &[u8], ) -> Result<[u8; BYTES_PER_READ], PageParseError> { let slice_len = bytes.len().min(BYTES_PER_READ + 1); let mut arr = [0; BYTES_PER_READ]; arr[..slice_len - 1].copy_from_slice(&bytes[1..slice_len]); match bytes.first() { Some(0x00) => Ok(arr), error => Err(PageParseError { iteration, bytes: arr, status_code: error.copied().map(Into::into), }), } } 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); } let mut data: Vec = Vec::with_capacity(858); for i in (0x0B..=230).step_by(PAGES_PER_READ) { let bytes = loop { let read = self .pn532 .process_async(&Request::ntag_read(i), BYTES_PER_READ + 1) .await; let bytes = match read { Ok(bytes) => bytes, Err(e) => { error!("Reading page {i} failed: {e:?}"); continue; } }; match Self::parse_nfc_page(i, bytes) { Ok(data) => break data, 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 }; data.extend(bytes); } Ok(data) } pub fn device_mut(&mut self) -> &mut Pn532Device { &mut self.pn532 } }