Add card detection
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "pn532"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
description = "A no_std, embedded-hal driver for the NXP PN532 NFC controller (I2C interface)"
|
||||
license = "BSD-3-Clause"
|
||||
keywords = ["nfc", "pn532", "embedded-hal", "no-std", "i2c"]
|
||||
categories = ["embedded", "no-std", "hardware-support"]
|
||||
|
||||
[lib]
|
||||
name = "pn532"
|
||||
path = "src/lib.rs"
|
||||
test = false
|
||||
bench = false
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
embedded-hal = "~1"
|
||||
defmt = { version = "~1.1", optional = true }
|
||||
hex = { version = "~0.4", default-features = false }
|
||||
des = { version = "~0.9", default-features = false }
|
||||
cbc = { version = "~0.2", default-features = false, features = ["block-padding"] }
|
||||
strum = { version = "0.28.0", default-features = false, features = ["derive", "strum_macros"] }
|
||||
heapless = "~0.9"
|
||||
thiserror = { version = "2.0.20", default-features = false }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
defmt = ["dep:defmt", "heapless/defmt"]
|
||||
alloc = ["defmt/alloc", "hex/alloc", "heapless/alloc"]
|
||||
@@ -0,0 +1,146 @@
|
||||
//! Command and protocol constants for the PN532.
|
||||
|
||||
// PN532 commands.
|
||||
pub const PN532_COMMAND_DIAGNOSE: u8 = 0x00;
|
||||
pub const PN532_COMMAND_GETFIRMWAREVERSION: u8 = 0x02;
|
||||
pub const PN532_COMMAND_GETGENERALSTATUS: u8 = 0x04;
|
||||
pub const PN532_COMMAND_READREGISTER: u8 = 0x06;
|
||||
pub const PN532_COMMAND_WRITEREGISTER: u8 = 0x08;
|
||||
pub const PN532_COMMAND_READGPIO: u8 = 0x0C;
|
||||
pub const PN532_COMMAND_WRITEGPIO: u8 = 0x0E;
|
||||
pub const PN532_COMMAND_SETSERIALBAUDRATE: u8 = 0x10;
|
||||
pub const PN532_COMMAND_SETPARAMETERS: u8 = 0x12;
|
||||
pub const PN532_COMMAND_SAMCONFIGURATION: u8 = 0x14;
|
||||
pub const PN532_COMMAND_POWERDOWN: u8 = 0x16;
|
||||
pub const PN532_COMMAND_RFCONFIGURATION: u8 = 0x32;
|
||||
pub const PN532_COMMAND_RFREGULATIONTEST: u8 = 0x58;
|
||||
pub const PN532_COMMAND_INJUMPFORDEP: u8 = 0x56;
|
||||
pub const PN532_COMMAND_INJUMPFORPSL: u8 = 0x46;
|
||||
pub const PN532_COMMAND_INLISTPASSIVETARGET: u8 = 0x4A;
|
||||
pub const PN532_COMMAND_INATR: u8 = 0x50;
|
||||
pub const PN532_COMMAND_INPSL: u8 = 0x4E;
|
||||
pub const PN532_COMMAND_INDATAEXCHANGE: u8 = 0x40;
|
||||
pub const PN532_COMMAND_INCOMMUNICATETHRU: u8 = 0x42;
|
||||
pub const PN532_COMMAND_INDESELECT: u8 = 0x44;
|
||||
pub const PN532_COMMAND_INRELEASE: u8 = 0x52;
|
||||
pub const PN532_COMMAND_INSELECT: u8 = 0x54;
|
||||
pub const PN532_COMMAND_INAUTOPOLL: u8 = 0x60;
|
||||
pub const PN532_COMMAND_TGINITASTARGET: u8 = 0x8C;
|
||||
pub const PN532_COMMAND_TGSETGENERALBYTES: u8 = 0x92;
|
||||
pub const PN532_COMMAND_TGGETDATA: u8 = 0x86;
|
||||
pub const PN532_COMMAND_TGSETDATA: u8 = 0x8E;
|
||||
pub const PN532_COMMAND_TGSETMETADATA: u8 = 0x94;
|
||||
pub const PN532_COMMAND_TGGETINITIATORCOMMAND: u8 = 0x88;
|
||||
pub const PN532_COMMAND_TGRESPONSETOINITIATOR: u8 = 0x90;
|
||||
pub const PN532_COMMAND_TGGETTARGETSTATUS: u8 = 0x8A;
|
||||
|
||||
pub const PN532_RESPONSE_INDATAEXCHANGE: u8 = 0x41;
|
||||
pub const PN532_RESPONSE_INLISTPASSIVETARGET: u8 = 0x4B;
|
||||
|
||||
pub const PN532_MIFARE_ISO14443A: u8 = 0x00;
|
||||
|
||||
// Mifare commands.
|
||||
pub const MIFARE_CMD_AUTH_A: u8 = 0x60;
|
||||
pub const MIFARE_CMD_AUTH_B: u8 = 0x61;
|
||||
pub const MIFARE_CMD_READ: u8 = 0x30;
|
||||
pub const MIFARE_CMD_WRITE: u8 = 0xA0;
|
||||
pub const MIFARE_CMD_WRITE_ULTRALIGHT: u8 = 0xA2;
|
||||
pub const MIFARE_CMD_TRANSFER: u8 = 0xB0;
|
||||
pub const MIFARE_CMD_DECREMENT: u8 = 0xC0;
|
||||
pub const MIFARE_CMD_INCREMENT: u8 = 0xC1;
|
||||
pub const MIFARE_CMD_STORE: u8 = 0xC2;
|
||||
|
||||
// FeliCa commands.
|
||||
pub const FELICA_CMD_POLLING: u8 = 0x00;
|
||||
pub const FELICA_CMD_REQUEST_SERVICE: u8 = 0x02;
|
||||
pub const FELICA_CMD_REQUEST_RESPONSE: u8 = 0x04;
|
||||
pub const FELICA_CMD_READ_WITHOUT_ENCRYPTION: u8 = 0x06;
|
||||
pub const FELICA_CMD_WRITE_WITHOUT_ENCRYPTION: u8 = 0x08;
|
||||
pub const FELICA_CMD_REQUEST_SYSTEM_CODE: u8 = 0x0C;
|
||||
|
||||
/// NDEF URI record identifier codes (see NFC Forum "URI Record Type Definition").
|
||||
pub mod ndef {
|
||||
pub const NONE: u8 = 0x00;
|
||||
pub const HTTP_WWWDOT: u8 = 0x01;
|
||||
pub const HTTPS_WWWDOT: u8 = 0x02;
|
||||
pub const HTTP: u8 = 0x03;
|
||||
pub const HTTPS: u8 = 0x04;
|
||||
pub const TEL: u8 = 0x05;
|
||||
pub const MAILTO: u8 = 0x06;
|
||||
pub const FTP_ANONAT: u8 = 0x07;
|
||||
pub const FTP_FTPDOT: u8 = 0x08;
|
||||
pub const FTPS: u8 = 0x09;
|
||||
pub const SFTP: u8 = 0x0A;
|
||||
pub const SMB: u8 = 0x0B;
|
||||
pub const NFS: u8 = 0x0C;
|
||||
pub const FTP: u8 = 0x0D;
|
||||
pub const DAV: u8 = 0x0E;
|
||||
pub const NEWS: u8 = 0x0F;
|
||||
pub const TELNET: u8 = 0x10;
|
||||
pub const IMAP: u8 = 0x11;
|
||||
pub const RTSP: u8 = 0x12;
|
||||
pub const URN: u8 = 0x13;
|
||||
pub const POP: u8 = 0x14;
|
||||
pub const SIP: u8 = 0x15;
|
||||
pub const SIPS: u8 = 0x16;
|
||||
pub const TFTP: u8 = 0x17;
|
||||
pub const BTSPP: u8 = 0x18;
|
||||
pub const BTL2CAP: u8 = 0x19;
|
||||
pub const BTGOEP: u8 = 0x1A;
|
||||
pub const TCPOBEX: u8 = 0x1B;
|
||||
pub const IRDAOBEX: u8 = 0x1C;
|
||||
pub const FILE: u8 = 0x1D;
|
||||
pub const URN_EPC_ID: u8 = 0x1E;
|
||||
pub const URN_EPC_TAG: u8 = 0x1F;
|
||||
pub const URN_EPC_PAT: u8 = 0x20;
|
||||
pub const URN_EPC_RAW: u8 = 0x21;
|
||||
pub const URN_EPC: u8 = 0x22;
|
||||
pub const URN_NFC: u8 = 0x23;
|
||||
}
|
||||
|
||||
// GPIO.
|
||||
pub const PN532_GPIO_VALIDATIONBIT: u8 = 0x80;
|
||||
pub const PN532_GPIO_P30: u8 = 0;
|
||||
pub const PN532_GPIO_P31: u8 = 1;
|
||||
pub const PN532_GPIO_P32: u8 = 2;
|
||||
pub const PN532_GPIO_P33: u8 = 3;
|
||||
pub const PN532_GPIO_P34: u8 = 4;
|
||||
pub const PN532_GPIO_P35: u8 = 5;
|
||||
|
||||
// FeliCa limits.
|
||||
pub const FELICA_READ_MAX_SERVICE_NUM: usize = 16;
|
||||
pub const FELICA_READ_MAX_BLOCK_NUM: usize = 12;
|
||||
pub const FELICA_WRITE_MAX_SERVICE_NUM: usize = 16;
|
||||
pub const FELICA_WRITE_MAX_BLOCK_NUM: usize = 10;
|
||||
pub const FELICA_REQ_SERVICE_MAX_NODE_NUM: usize = 32;
|
||||
|
||||
// Frame protocol constants.
|
||||
pub const PN532_PREAMBLE: u8 = 0x00;
|
||||
pub const PN532_STARTCODE1: u8 = 0x00;
|
||||
pub const PN532_STARTCODE2: u8 = 0xFF;
|
||||
pub const PN532_POSTAMBLE: u8 = 0x00;
|
||||
|
||||
pub const PN532_HOST_TO_PN532: u8 = 0xD4;
|
||||
pub const PN532_PN532_TO_HOST: u8 = 0xD5;
|
||||
|
||||
pub const PN532_ACK_WAIT_TIME_MS: u16 = 10;
|
||||
|
||||
/// The fixed 7-bit I2C address of the PN532 (0x48 >> 1).
|
||||
pub const PN532_I2C_ADDRESS: u8 = 0x48 >> 1;
|
||||
|
||||
/// Passive target baud rates supported by [`crate::Pn532::read_passive_target_id`].
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum BaudRate {
|
||||
ISO14443A106kbps = 0x00,
|
||||
Felica212kbps = 0x01,
|
||||
Felica424kbps = 0x02,
|
||||
ISO14443B106kbps = 0x03,
|
||||
InnovisionJewel106kbps = 0x04,
|
||||
}
|
||||
|
||||
impl From<BaudRate> for u8 {
|
||||
fn from(value: BaudRate) -> Self {
|
||||
value as u8
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
//! High-level PN532 command driver.
|
||||
|
||||
use crate::commands::*;
|
||||
use crate::error::{Error};
|
||||
use crate::interface::Interface;
|
||||
|
||||
pub(crate) const PACKET_BUFFER_SIZE: usize = 64;
|
||||
|
||||
/// A card UID read by [`Pn532::read_passive_target_id`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Uid {
|
||||
pub bytes: heapless::Vec<u8, 7>,
|
||||
}
|
||||
|
||||
impl core::fmt::Display for Uid {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
#[cfg(feature = "alloc")]
|
||||
return write!(f, "{}", hex::encode(&self.bytes));
|
||||
#[cfg(not(feature = "alloc"))]
|
||||
{
|
||||
let mut buf: heapless::Vec<u8, 14> = heapless::Vec::new();
|
||||
hex::encode_to_slice(&self.bytes, &mut buf)
|
||||
.map_err(|_| core::fmt::Error)?;
|
||||
let s = heapless::String::from_utf8(buf)
|
||||
.map_err(|_| core::fmt::Error)?;
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for Uid {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(fmt, "{}", defmt::Display2Format(self));
|
||||
}
|
||||
}
|
||||
|
||||
/// Outcome of [`Pn532::tg_init_as_target`] / [`Pn532::tg_init_as_target_default`].
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TargetInitStatus {
|
||||
/// The PN532 entered target mode.
|
||||
Success,
|
||||
/// No initiator appeared within the timeout.
|
||||
Timeout,
|
||||
/// The operation failed.
|
||||
Failed,
|
||||
}
|
||||
|
||||
/// Result of a successful FeliCa polling request.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct FelicaPollingResponse {
|
||||
/// The card's IDm (NFCID2).
|
||||
pub idm: [u8; 8],
|
||||
/// The card's PMm (PAD).
|
||||
pub pmm: [u8; 8],
|
||||
/// The card's system code, when returned.
|
||||
pub system_code_response: Option<u16>,
|
||||
}
|
||||
|
||||
/// Driver for the NXP PN532 NFC controller.
|
||||
pub struct Pn532<B: Interface> {
|
||||
pub(crate) interface: B,
|
||||
pub(crate) in_listed_tag: u8,
|
||||
pub(crate) felica_idm: [u8; 8],
|
||||
pub(crate) felica_pmm: [u8; 8],
|
||||
pub(crate) buffer: [u8; PACKET_BUFFER_SIZE],
|
||||
}
|
||||
|
||||
impl<B: Interface> Pn532<B> {
|
||||
/// Create a driver over the given transport interface.
|
||||
pub fn new(interface: B) -> Self {
|
||||
Self {
|
||||
interface,
|
||||
in_listed_tag: 0,
|
||||
felica_idm: [0; 8],
|
||||
felica_pmm: [0; 8],
|
||||
buffer: [0; PACKET_BUFFER_SIZE],
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialise the PN532: pulse the reset pin (if provided) and wait for it
|
||||
/// to become ready.
|
||||
pub fn begin(&mut self) -> Result<(), Error<B::TransportError>> {
|
||||
self.interface.begin()
|
||||
}
|
||||
|
||||
// -- internal helpers ---------------------------------------------------
|
||||
|
||||
pub(crate) fn send(&mut self, header_len: usize) -> Result<(), Error<B::TransportError>> {
|
||||
self.interface.write_command(&self.buffer[..header_len], &[])
|
||||
}
|
||||
|
||||
pub(crate) fn send_with_body(
|
||||
&mut self,
|
||||
header_len: usize,
|
||||
body: &[u8],
|
||||
) -> Result<(), Error<B::TransportError>> {
|
||||
self.interface
|
||||
.write_command(&self.buffer[..header_len], body)
|
||||
}
|
||||
|
||||
pub(crate) fn read(&mut self) -> Result<usize, Error<B::TransportError>> {
|
||||
self.interface.read_response(&mut self.buffer, 1000)
|
||||
}
|
||||
|
||||
pub(crate) fn read_timeout(&mut self, timeout_ms: u16) -> Result<usize, Error<B::TransportError>> {
|
||||
self.interface.read_response(&mut self.buffer, timeout_ms)
|
||||
}
|
||||
|
||||
// -- generic PN532 functions -------------------------------------------
|
||||
|
||||
/// Read the PN532 firmware version and ID.
|
||||
pub fn get_firmware_version(&mut self) -> Result<u32, Error<B::TransportError>> {
|
||||
self.buffer[0] = PN532_COMMAND_GETFIRMWAREVERSION;
|
||||
self.send(1)?;
|
||||
let len = self.read()?;
|
||||
if len < 4 {
|
||||
return Err(Error::InvalidFrame);
|
||||
}
|
||||
Ok(u32::from_be_bytes([
|
||||
self.buffer[0],
|
||||
self.buffer[1],
|
||||
self.buffer[2],
|
||||
self.buffer[3],
|
||||
]))
|
||||
}
|
||||
|
||||
/// Read a 16-bit PN532 register.
|
||||
pub fn read_register(&mut self, reg: u16) -> Result<u8, Error<B::TransportError>> {
|
||||
self.buffer[0] = PN532_COMMAND_READREGISTER;
|
||||
self.buffer[1] = (reg >> 8) as u8;
|
||||
self.buffer[2] = reg as u8;
|
||||
self.send(3)?;
|
||||
let len = self.read()?;
|
||||
if len < 1 {
|
||||
return Err(Error::InvalidFrame);
|
||||
}
|
||||
Ok(self.buffer[0])
|
||||
}
|
||||
|
||||
/// Write to a 16-bit PN532 register.
|
||||
pub fn write_register(&mut self, reg: u16, val: u8) -> Result<(), Error<B::TransportError>> {
|
||||
self.buffer[0] = PN532_COMMAND_WRITEREGISTER;
|
||||
self.buffer[1] = (reg >> 8) as u8;
|
||||
self.buffer[2] = reg as u8;
|
||||
self.buffer[3] = val;
|
||||
self.send(4)?;
|
||||
self.read()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the PN532's GPIO pins (see the PN532 user manual for valid pins).
|
||||
pub fn write_gpio(&mut self, pinstate: u8) -> Result<(), Error<B::TransportError>> {
|
||||
let pinstate = pinstate | (1 << PN532_GPIO_P32) | (1 << PN532_GPIO_P34);
|
||||
self.buffer[0] = PN532_COMMAND_WRITEGPIO;
|
||||
self.buffer[1] = PN532_GPIO_VALIDATIONBIT | pinstate;
|
||||
self.buffer[2] = 0x00;
|
||||
self.send(3)?;
|
||||
let len = self.read()?;
|
||||
if len == 0 {
|
||||
return Err(Error::InvalidFrame);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read the state of the PN532's GPIO pins.
|
||||
pub fn read_gpio(&mut self) -> Result<u8, Error<B::TransportError>> {
|
||||
self.buffer[0] = PN532_COMMAND_READGPIO;
|
||||
self.send(1)?;
|
||||
self.read()?;
|
||||
Ok(self.buffer[0])
|
||||
}
|
||||
|
||||
/// Configure the SAM (Secure Access Module) in normal mode.
|
||||
pub fn sam_config(&mut self) -> Result<(), Error<B::TransportError>> {
|
||||
self.buffer[0] = PN532_COMMAND_SAMCONFIGURATION;
|
||||
self.buffer[1] = 0x01; // normal mode
|
||||
self.buffer[2] = 0x14; // timeout 50 ms * 20 = 1 second
|
||||
self.buffer[3] = 0x01; // use IRQ pin
|
||||
self.send(4)?;
|
||||
self.read()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the `MxRtyPassiveActivation` RF configuration item.
|
||||
pub fn set_passive_activation_retries(
|
||||
&mut self,
|
||||
max_retries: u8,
|
||||
) -> Result<(), Error<B::TransportError>> {
|
||||
self.buffer[0] = PN532_COMMAND_RFCONFIGURATION;
|
||||
self.buffer[1] = 5;
|
||||
self.buffer[2] = 0xFF;
|
||||
self.buffer[3] = 0x01;
|
||||
self.buffer[4] = max_retries;
|
||||
self.send(5)?;
|
||||
self.read()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Switch the RF field on/off.
|
||||
pub fn set_rf_field(
|
||||
&mut self,
|
||||
auto_rfca: u8,
|
||||
rf_on_off: u8,
|
||||
) -> Result<(), Error<B::TransportError>> {
|
||||
self.buffer[0] = PN532_COMMAND_RFCONFIGURATION;
|
||||
self.buffer[1] = 1;
|
||||
self.buffer[2] = 0x00 | auto_rfca | rf_on_off;
|
||||
self.send(3)?;
|
||||
self.read()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
use defmt::Debug2Format;
|
||||
use strum::FromRepr;
|
||||
|
||||
/// Driver-level error type, generic over the transport (I2C) error.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
||||
pub enum Error<E> {
|
||||
/// The underlying transport (I2C) reported an error.
|
||||
#[error("transport error: {0:?}")]
|
||||
Transport(E),
|
||||
/// Timed out waiting for a response or acknowledgement.
|
||||
#[error("Timed out waiting for a response or acknowledgement")]
|
||||
Timeout,
|
||||
/// The acknowledgement frame was malformed.
|
||||
#[error("The acknowledgement frame was malformed")]
|
||||
InvalidAck,
|
||||
/// The response frame was malformed (bad preamble, length or checksum).
|
||||
#[error("The response frame was malformed")]
|
||||
InvalidFrame,
|
||||
/// The response did not fit in the provided buffer.
|
||||
#[error("The response did not fit in the provided buffer")]
|
||||
NoSpace,
|
||||
/// A parameter passed to a command was out of range.
|
||||
#[error("A parameter passed to a command was out of range")]
|
||||
InvalidParam,
|
||||
/// The device returned a non-zero status code.
|
||||
#[error("Received non zero status code: {0:?}")]
|
||||
Status(StatusCode),
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl<E: defmt::Format> defmt::Format for Error<E> {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
match self {
|
||||
Error::Transport(e) => defmt::write!(fmt, "transport error: {}", e),
|
||||
Error::Timeout => defmt::write!(fmt, "timeout"),
|
||||
Error::InvalidAck => defmt::write!(fmt, "invalid ACK frame"),
|
||||
Error::InvalidFrame => defmt::write!(fmt, "invalid frame"),
|
||||
Error::NoSpace => defmt::write!(fmt, "not enough space in buffer"),
|
||||
Error::InvalidParam => defmt::write!(fmt, "invalid parameter"),
|
||||
Error::Status(code) => defmt::write!(fmt, "status error {:?}", code),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(FromRepr, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum StatusCode {
|
||||
TimeOut = 0x01,
|
||||
CRC = 0x02,
|
||||
Parity = 0x03,
|
||||
BitCount = 0x04,
|
||||
Framing = 0x05,
|
||||
Collision = 0x06,
|
||||
Space = 0x07,
|
||||
Overflow = 0x09,
|
||||
NoField = 0x0A,
|
||||
Protocol = 0x0B,
|
||||
Temperature = 0x0D,
|
||||
IntOverflow = 0x0E,
|
||||
Parameter = 0x10,
|
||||
DepProtocol = 0x12,
|
||||
DepFormat = 0x13,
|
||||
MifareAuth = 0x14,
|
||||
UIDCheck = 0x23,
|
||||
DepState = 0x25,
|
||||
NotAllowed = 0x26,
|
||||
NotAcceptable = 0x27,
|
||||
Released = 0x29,
|
||||
CardSwapped = 0x2A,
|
||||
Disappeared = 0x2B,
|
||||
MismatchUd = 0x2C,
|
||||
OverCurrent = 0x2D,
|
||||
NADMissing = 0x2E,
|
||||
Max = 0x2F
|
||||
}
|
||||
|
||||
impl defmt::Format for StatusCode {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(fmt, "{}", Debug2Format(self))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
mod types;
|
||||
|
||||
use crate::driver::Pn532;
|
||||
use crate::error::{Error, StatusCode};
|
||||
use crate::interface::Interface;
|
||||
|
||||
use types::*;
|
||||
pub use types::Block as FelicaBlock;
|
||||
pub(crate) use types::AsBlock;
|
||||
use crate::commands::{FELICA_CMD_POLLING, FELICA_CMD_READ_WITHOUT_ENCRYPTION, FELICA_CMD_REQUEST_RESPONSE, FELICA_CMD_REQUEST_SERVICE, FELICA_CMD_REQUEST_SYSTEM_CODE, FELICA_CMD_WRITE_WITHOUT_ENCRYPTION, FELICA_READ_MAX_BLOCK_NUM, FELICA_READ_MAX_SERVICE_NUM, FELICA_REQ_SERVICE_MAX_NODE_NUM, FELICA_WRITE_MAX_BLOCK_NUM, FELICA_WRITE_MAX_SERVICE_NUM, PN532_COMMAND_INDATAEXCHANGE, PN532_COMMAND_INLISTPASSIVETARGET, PN532_COMMAND_INRELEASE};
|
||||
use crate::{BaudRate, FelicaPollingResponse};
|
||||
|
||||
impl<B: Interface> Pn532<B> {
|
||||
/// Poll for a FeliCa card.
|
||||
pub fn felica_polling(
|
||||
&mut self,
|
||||
system_code: u16,
|
||||
request_code: u8,
|
||||
timeout_ms: u16,
|
||||
) -> Result<Option<FelicaPollingResponse>, Error<B::TransportError>> {
|
||||
self.buffer[0] = PN532_COMMAND_INLISTPASSIVETARGET;
|
||||
self.buffer[1] = 1;
|
||||
self.buffer[2] = BaudRate::Felica212kbps.into();
|
||||
self.buffer[3] = FELICA_CMD_POLLING;
|
||||
self.buffer[4] = (system_code >> 8) as u8;
|
||||
self.buffer[5] = system_code as u8;
|
||||
self.buffer[6] = request_code;
|
||||
self.buffer[7] = 0;
|
||||
self.send(8)?;
|
||||
|
||||
match self.read_timeout(timeout_ms) {
|
||||
Err(Error::Timeout) => return Ok(None),
|
||||
Err(e) => return Err(e),
|
||||
Ok(_) => {}
|
||||
}
|
||||
|
||||
if self.buffer[0] == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
if self.buffer[0] != 1 {
|
||||
return Err(Error::InvalidFrame);
|
||||
}
|
||||
|
||||
self.in_listed_tag = self.buffer[1];
|
||||
let response_length = self.buffer[2];
|
||||
if response_length != 18 && response_length != 20 {
|
||||
return Err(Error::InvalidFrame);
|
||||
}
|
||||
|
||||
let mut idm = [0u8; 8];
|
||||
idm.copy_from_slice(&self.buffer[4..12]);
|
||||
self.felica_idm = idm;
|
||||
|
||||
let mut pmm = [0u8; 8];
|
||||
pmm.copy_from_slice(&self.buffer[12..20]);
|
||||
self.felica_pmm = pmm;
|
||||
|
||||
let system_code_response = if response_length == 20 {
|
||||
Some(u16::from_be_bytes([self.buffer[20], self.buffer[21]]))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Some(FelicaPollingResponse {
|
||||
idm,
|
||||
pmm,
|
||||
system_code_response,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Send a raw FeliCa command to the currently in-listed card.
|
||||
///
|
||||
/// Returns the number of bytes written to `response`.
|
||||
pub fn felica_send_command(
|
||||
&mut self,
|
||||
command: &[u8],
|
||||
response: &mut [u8],
|
||||
) -> Result<usize, Error<B::TransportError>> {
|
||||
if command.len() > 0xFE {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
self.buffer[0] = PN532_COMMAND_INDATAEXCHANGE;
|
||||
self.buffer[1] = self.in_listed_tag;
|
||||
self.buffer[2] = (command.len() + 1) as u8;
|
||||
self.send_with_body(3, command)?;
|
||||
|
||||
let len = self.read_timeout(200)?;
|
||||
if len == 0 || (self.buffer[0] & 0x3F) != 0 {
|
||||
return Err(Error::Status(StatusCode::from_repr(self.buffer[0] & 0x3F).unwrap()));
|
||||
}
|
||||
|
||||
let response_len = self.buffer[1] as usize - 1;
|
||||
if len - 2 != response_len {
|
||||
return Err(Error::InvalidFrame);
|
||||
}
|
||||
if response_len > response.len() {
|
||||
return Err(Error::NoSpace);
|
||||
}
|
||||
response[..response_len].copy_from_slice(&self.buffer[2..2 + response_len]);
|
||||
Ok(response_len)
|
||||
}
|
||||
|
||||
/// Send a FeliCa "Request Service" command.
|
||||
pub fn felica_request_service(
|
||||
&mut self,
|
||||
node_code_list: &[u16],
|
||||
key_versions: &mut [u16],
|
||||
) -> Result<(), Error<B::TransportError>> {
|
||||
let num_node = node_code_list.len();
|
||||
if num_node > FELICA_REQ_SERVICE_MAX_NODE_NUM || num_node > key_versions.len() {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
|
||||
let mut cmd = [0u8; 1 + 8 + 1 + 2 * FELICA_REQ_SERVICE_MAX_NODE_NUM];
|
||||
let mut j = 0;
|
||||
cmd[j] = FELICA_CMD_REQUEST_SERVICE;
|
||||
j += 1;
|
||||
cmd[j..j + 8].copy_from_slice(&self.felica_idm);
|
||||
j += 8;
|
||||
cmd[j] = num_node as u8;
|
||||
j += 1;
|
||||
for &code in node_code_list {
|
||||
cmd[j] = code as u8;
|
||||
cmd[j + 1] = (code >> 8) as u8;
|
||||
j += 2;
|
||||
}
|
||||
|
||||
let mut response = [0u8; 10 + 2 * FELICA_REQ_SERVICE_MAX_NODE_NUM];
|
||||
let response_len = self.felica_send_command(&cmd[..j], &mut response)?;
|
||||
if response_len != 10 + 2 * num_node {
|
||||
return Err(Error::InvalidFrame);
|
||||
}
|
||||
for (i, kv) in key_versions.iter_mut().enumerate().take(num_node) {
|
||||
*kv = u16::from_le_bytes([response[10 + i * 2], response[10 + i * 2 + 1]]);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a FeliCa "Request Response" command, returning the card's mode.
|
||||
pub fn felica_request_response(&mut self) -> Result<u8, Error<B::TransportError>> {
|
||||
let mut cmd = [0u8; 9];
|
||||
cmd[0] = FELICA_CMD_REQUEST_RESPONSE;
|
||||
cmd[1..9].copy_from_slice(&self.felica_idm);
|
||||
|
||||
let mut response = [0u8; 10];
|
||||
let response_len = self.felica_send_command(&cmd, &mut response)?;
|
||||
if response_len != 10 {
|
||||
return Err(Error::InvalidFrame);
|
||||
}
|
||||
Ok(response[9])
|
||||
}
|
||||
|
||||
/// Send a FeliCa "Read Without Encryption" command.
|
||||
///
|
||||
/// Note: the number of blocks is limited by the 64-byte response buffer.
|
||||
pub fn felica_read_without_encryption(
|
||||
&mut self,
|
||||
service_code_list: &[u16],
|
||||
block_list: &[u16],
|
||||
block_data: &mut [[u8; 16]],
|
||||
) -> Result<(), Error<B::TransportError>> {
|
||||
let num_service = service_code_list.len();
|
||||
let num_block = block_list.len();
|
||||
if num_service > FELICA_READ_MAX_SERVICE_NUM
|
||||
|| num_block > FELICA_READ_MAX_BLOCK_NUM
|
||||
|| num_block > block_data.len()
|
||||
{
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
|
||||
let mut cmd = [0u8; 1 + 8 + 1 + 2 * FELICA_READ_MAX_SERVICE_NUM + 1 + 2 * FELICA_READ_MAX_BLOCK_NUM];
|
||||
let mut j = 0;
|
||||
cmd[j] = FELICA_CMD_READ_WITHOUT_ENCRYPTION;
|
||||
j += 1;
|
||||
cmd[j..j + 8].copy_from_slice(&self.felica_idm);
|
||||
j += 8;
|
||||
cmd[j] = num_service as u8;
|
||||
j += 1;
|
||||
for &sc in service_code_list {
|
||||
cmd[j] = sc as u8;
|
||||
cmd[j + 1] = (sc >> 8) as u8;
|
||||
j += 2;
|
||||
}
|
||||
cmd[j] = num_block as u8;
|
||||
j += 1;
|
||||
for &bl in block_list {
|
||||
cmd[j] = (bl >> 8) as u8;
|
||||
cmd[j + 1] = bl as u8;
|
||||
j += 2;
|
||||
}
|
||||
|
||||
let mut response = [0u8; 12 + 16 * FELICA_READ_MAX_BLOCK_NUM];
|
||||
let response_len = self.felica_send_command(&cmd[..j], &mut response)?;
|
||||
if response_len != 12 + 16 * num_block {
|
||||
return Err(Error::InvalidFrame);
|
||||
}
|
||||
if response[9] != 0 || response[10] != 0 {
|
||||
return Err(Error::Status(StatusCode::from_repr(response[9]).unwrap()));
|
||||
}
|
||||
|
||||
let mut k = 12;
|
||||
for block in block_data.iter_mut().take(num_block) {
|
||||
block.copy_from_slice(&response[k..k + 16]);
|
||||
k += 16;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a FeliCa "Write Without Encryption" command.
|
||||
pub fn felica_write_without_encryption(
|
||||
&mut self,
|
||||
service_code_list: &[u16],
|
||||
block_list: &[impl AsBlock],
|
||||
block_data: &[[u8; 16]],
|
||||
) -> Result<(), Error<B::TransportError>> {
|
||||
let num_service = service_code_list.len();
|
||||
let num_block = block_list.len();
|
||||
if num_service > FELICA_WRITE_MAX_SERVICE_NUM
|
||||
|| num_block > FELICA_WRITE_MAX_BLOCK_NUM
|
||||
|| num_block > block_data.len()
|
||||
{
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
|
||||
let mut cmd = [0u8; 1 + 8 + 1 + 2 * FELICA_WRITE_MAX_SERVICE_NUM + 1 + 2 * FELICA_WRITE_MAX_BLOCK_NUM
|
||||
+ 16 * FELICA_WRITE_MAX_BLOCK_NUM];
|
||||
let mut j = 0;
|
||||
cmd[j] = FELICA_CMD_WRITE_WITHOUT_ENCRYPTION;
|
||||
j += 1;
|
||||
cmd[j..j + 8].copy_from_slice(&self.felica_idm);
|
||||
j += 8;
|
||||
cmd[j] = num_service as u8;
|
||||
j += 1;
|
||||
for &sc in service_code_list {
|
||||
cmd[j] = sc as u8;
|
||||
cmd[j + 1] = (sc >> 8) as u8;
|
||||
j += 2;
|
||||
}
|
||||
cmd[j] = num_block as u8;
|
||||
j += 1;
|
||||
for block in block_list {
|
||||
let block = block.as_block();
|
||||
cmd[j] = (block >> 8) as u8;
|
||||
cmd[j + 1] = block as u8;
|
||||
j += 2;
|
||||
}
|
||||
for block in block_data.iter().take(num_block) {
|
||||
cmd[j..j + 16].copy_from_slice(block);
|
||||
j += 16;
|
||||
}
|
||||
|
||||
let mut response = [0u8; 11];
|
||||
let response_len = self.felica_send_command(&cmd[..j], &mut response)?;
|
||||
if response_len != 11 {
|
||||
return Err(Error::InvalidFrame);
|
||||
}
|
||||
if response[9] != 0 || response[10] != 0 {
|
||||
return Err(Error::Status(StatusCode::from_repr(response[9]).unwrap()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a FeliCa "Request System Code" command.
|
||||
///
|
||||
/// Returns the number of system codes written to `system_code_list`.
|
||||
pub fn felica_request_system_code(
|
||||
&mut self,
|
||||
system_code_list: &mut [impl AsServiceCode],
|
||||
) -> Result<usize, Error<B::TransportError>> {
|
||||
let mut cmd = [0u8; 9];
|
||||
cmd[0] = FELICA_CMD_REQUEST_SYSTEM_CODE;
|
||||
cmd[1..9].copy_from_slice(&self.felica_idm);
|
||||
|
||||
let mut response = [0u8; 10 + 2 * 16];
|
||||
let response_len = self.felica_send_command(&cmd, &mut response)?;
|
||||
if response_len < 10 {
|
||||
return Err(Error::InvalidFrame);
|
||||
}
|
||||
let num_system_code = response[9] as usize;
|
||||
if response_len < 10 + 2 * num_system_code {
|
||||
return Err(Error::InvalidFrame);
|
||||
}
|
||||
if num_system_code > system_code_list.len() {
|
||||
return Err(Error::NoSpace);
|
||||
}
|
||||
for (i, out) in system_code_list.iter_mut().enumerate().take(num_system_code) {
|
||||
*out = AsServiceCode::from_service_code(
|
||||
u16::from_be_bytes([response[10 + i * 2], response[10 + i * 2 + 1]])
|
||||
);
|
||||
}
|
||||
Ok(num_system_code)
|
||||
}
|
||||
|
||||
/// Release the FeliCa card.
|
||||
pub fn felica_release(&mut self) -> Result<(), Error<B::TransportError>> {
|
||||
self.buffer[0] = PN532_COMMAND_INRELEASE;
|
||||
self.buffer[1] = 0x00;
|
||||
self.send(2)?;
|
||||
|
||||
let len = self.read_timeout(1000)?;
|
||||
if len == 0 || (self.buffer[0] & 0x3F) != 0 {
|
||||
return Err(Error::Status(StatusCode::from_repr(self.buffer[0] & 0x3F).unwrap()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
use strum::FromRepr;
|
||||
|
||||
pub trait AsServiceCode {
|
||||
fn as_service_code(&self) -> u16;
|
||||
fn from_service_code(val: u16) -> Self;
|
||||
}
|
||||
|
||||
#[derive(FromRepr, Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum ServiceCode {
|
||||
/// FeliCa Lite read service code (read without key).
|
||||
Read = 0x000B,
|
||||
/// FeliCa Lite write service code (write without key).
|
||||
Write = 0x0009
|
||||
}
|
||||
|
||||
impl AsServiceCode for ServiceCode {
|
||||
fn as_service_code(&self) -> u16 {
|
||||
*self as u16
|
||||
}
|
||||
|
||||
fn from_service_code(val: u16) -> Self {
|
||||
Self::from_repr(val).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsServiceCode for u16 {
|
||||
fn as_service_code(&self) -> u16 {
|
||||
*self
|
||||
}
|
||||
|
||||
fn from_service_code(val: u16) -> Self {
|
||||
val
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AsBlock {
|
||||
fn as_block(&self) -> u16;
|
||||
}
|
||||
|
||||
/// FeliCa Lite system block numbers.
|
||||
#[derive(FromRepr, Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum Block {
|
||||
/// Random challenge block.
|
||||
RC = 0x8080,
|
||||
/// MAC block.
|
||||
MAC = 0x8081,
|
||||
/// ID block.
|
||||
ID = 0x8082,
|
||||
/// Card key version block.
|
||||
CKV = 0x8086,
|
||||
/// Card key block.
|
||||
CK= 0x8087,
|
||||
/// Memory configuration block.
|
||||
MemoryConfig = 0x8088,
|
||||
/// Write counter block.
|
||||
WriteCounter = 0x8090,
|
||||
/// MAC_A block.
|
||||
MACA = 0x8091
|
||||
}
|
||||
|
||||
impl AsBlock for Block {
|
||||
fn as_block(&self) -> u16 {
|
||||
*self as u16
|
||||
}
|
||||
}
|
||||
|
||||
impl AsBlock for u16 {
|
||||
fn as_block(&self) -> u16 {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
//! Transport abstraction and the I2C implementation.
|
||||
//!
|
||||
//! The PN532 communicates over a framed protocol regardless of the physical
|
||||
//! transport (I2C, SPI or HSU). The [`Interface`] trait mirrors that boundary:
|
||||
//! the driver builds logical commands, the interface handles framing and the
|
||||
//! acknowledgement/response handshake.
|
||||
|
||||
use core::convert::Infallible;
|
||||
use embedded_hal::delay::DelayNs;
|
||||
use embedded_hal::digital::{ErrorType, InputPin, OutputPin};
|
||||
use embedded_hal::i2c::{I2c, Operation};
|
||||
|
||||
use crate::commands::{
|
||||
PN532_ACK_WAIT_TIME_MS, PN532_HOST_TO_PN532, PN532_I2C_ADDRESS, PN532_PN532_TO_HOST,
|
||||
PN532_POSTAMBLE, PN532_PREAMBLE, PN532_STARTCODE1, PN532_STARTCODE2,
|
||||
};
|
||||
use crate::error::Error;
|
||||
|
||||
const ACK: [u8; 6] = [0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00];
|
||||
|
||||
/// Maximum response data length supported (matches the PN532's 64-byte packet buffer).
|
||||
const MAX_DATA_LEN: usize = 64;
|
||||
/// Maximum length of an outbound command frame.
|
||||
const WRITE_FRAME_CAPACITY: usize = 64;
|
||||
|
||||
/// Marker type for when no reset pin is connected.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct NoReset;
|
||||
|
||||
/// A PN532 reset pin (`RSTPD_N`, active-low).
|
||||
///
|
||||
/// Implemented for any [`OutputPin`] as well as for [`NoReset`] (which does
|
||||
/// nothing). A reset pin is optional but strongly recommended: without a reset
|
||||
/// pulse the PN532 can end up in an uninitialised state and NACK subsequent
|
||||
/// commands (surfacing as `Error::Transport(AcknowledgeCheckFailed(Data))`).
|
||||
pub trait ResetPin {
|
||||
/// Assert reset (drive the pin low).
|
||||
fn assert(&mut self);
|
||||
/// Deassert reset (drive the pin high).
|
||||
fn deassert(&mut self);
|
||||
}
|
||||
|
||||
impl ResetPin for NoReset {
|
||||
fn assert(&mut self) {}
|
||||
fn deassert(&mut self) {}
|
||||
}
|
||||
|
||||
impl<P: OutputPin> ResetPin for P {
|
||||
fn assert(&mut self) {
|
||||
let _ = self.set_low();
|
||||
}
|
||||
|
||||
fn deassert(&mut self) {
|
||||
let _ = self.set_high();
|
||||
}
|
||||
}
|
||||
|
||||
/// Marker type for when no IRQ pin is connected.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct NoIrq;
|
||||
|
||||
impl ErrorType for NoIrq { type Error = Infallible; }
|
||||
|
||||
impl InputPin for NoIrq {
|
||||
fn is_high(&mut self) -> Result<bool, Self::Error> {
|
||||
panic!("NoIrq should not be used");
|
||||
}
|
||||
|
||||
fn is_low(&mut self) -> Result<bool, Self::Error> {
|
||||
panic!("NoIrq should not be used");
|
||||
}
|
||||
}
|
||||
|
||||
/// A framed transport to the PN532.
|
||||
///
|
||||
/// Implementations are responsible for the frame/ack handshake described in
|
||||
/// the PN532 user manual (UM0701-02).
|
||||
pub trait Interface {
|
||||
/// The error type produced by the underlying physical transport.
|
||||
type TransportError;
|
||||
|
||||
/// Initialise the hardware: pulse the reset pin (if provided) and wait for
|
||||
/// the PN532 to become ready.
|
||||
fn begin(&mut self) -> Result<(), Error<Self::TransportError>>;
|
||||
|
||||
/// Write a command frame (`header` + optional `body`) and wait for the ACK.
|
||||
fn write_command(
|
||||
&mut self,
|
||||
header: &[u8],
|
||||
body: &[u8],
|
||||
) -> Result<(), Error<Self::TransportError>>;
|
||||
|
||||
/// Read a response frame, returning the length of the payload (excluding
|
||||
/// the `TFI` and command bytes) placed in `buf`.
|
||||
fn read_response(
|
||||
&mut self,
|
||||
buf: &mut [u8],
|
||||
timeout_ms: u16,
|
||||
) -> Result<usize, Error<Self::TransportError>>;
|
||||
}
|
||||
|
||||
/// [`Interface`] implementation over a blocking `embedded_hal::i2c::I2c` bus.
|
||||
///
|
||||
/// `RST` and `IRQ` are the optional reset and IRQ pins; use [`NoReset`] /
|
||||
/// [`NoIrq`] (the defaults) or the relevant `embedded_hal` pins via the
|
||||
/// [`I2cInterface::with_reset`] / [`I2cInterface::with_reset_irq`] constructors.
|
||||
pub struct I2cInterface<I2C, D, RST = NoReset, IRQ = NoIrq> {
|
||||
i2c: I2C,
|
||||
delay: D,
|
||||
reset: RST,
|
||||
irq: IRQ,
|
||||
command: u8,
|
||||
}
|
||||
|
||||
impl<I2C, D> I2cInterface<I2C, D, NoReset, NoIrq> {
|
||||
/// Create an interface from an already-configured I2C bus and a delay
|
||||
/// source, without a reset or IRQ pin.
|
||||
pub fn new(i2c: I2C, delay: D) -> Self {
|
||||
Self {
|
||||
i2c,
|
||||
delay,
|
||||
reset: NoReset,
|
||||
irq: NoIrq,
|
||||
command: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I2C, D, RST: ResetPin> I2cInterface<I2C, D, RST, NoIrq> {
|
||||
/// Create an interface with a reset pin (`RSTPD_N`).
|
||||
pub fn with_reset(i2c: I2C, delay: D, reset: RST) -> Self {
|
||||
Self {
|
||||
i2c,
|
||||
delay,
|
||||
reset,
|
||||
irq: NoIrq,
|
||||
command: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I2C, D, IRQ: InputPin> I2cInterface<I2C, D, NoReset, IRQ> {
|
||||
/// Create an interface with an IRQ pin but no reset pin.
|
||||
pub fn with_irq(i2c: I2C, delay: D, irq: IRQ) -> Self {
|
||||
Self {
|
||||
i2c,
|
||||
delay,
|
||||
reset: NoReset,
|
||||
irq,
|
||||
command: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I2C, D, RST: ResetPin, IRQ: InputPin> I2cInterface<I2C, D, RST, IRQ> {
|
||||
/// Create an interface with both a reset pin and an IRQ pin.
|
||||
pub fn with_reset_irq(i2c: I2C, delay: D, reset: RST, irq: IRQ) -> Self {
|
||||
Self {
|
||||
i2c,
|
||||
delay,
|
||||
reset,
|
||||
irq,
|
||||
command: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I2C, D, RST, IRQ> I2cInterface<I2C, D, RST, IRQ> {
|
||||
/// Consume the interface and return the underlying I2C bus.
|
||||
pub fn release(self) -> I2C {
|
||||
self.i2c
|
||||
}
|
||||
}
|
||||
|
||||
impl<I2C, D, RST, IRQ> Interface for I2cInterface<I2C, D, RST, IRQ>
|
||||
where
|
||||
I2C: I2c,
|
||||
D: DelayNs,
|
||||
RST: ResetPin,
|
||||
IRQ: InputPin,
|
||||
{
|
||||
type TransportError = I2C::Error;
|
||||
|
||||
fn begin(&mut self) -> Result<(), Error<Self::TransportError>> {
|
||||
// Pulse RSTPD_N: high -> low -> wait -> high -> wait. This mirrors the
|
||||
// Adafruit library's begin() reset sequence.
|
||||
self.reset.deassert();
|
||||
self.reset.assert();
|
||||
self.delay.delay_ms(400);
|
||||
self.reset.deassert();
|
||||
// Let the PN532 boot after the reset is released. The Adafruit library
|
||||
// waits ~10 ms + a 500 ms wakeup here; give it a full 500 ms.
|
||||
self.delay.delay_ms(500);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_command(
|
||||
&mut self,
|
||||
header: &[u8],
|
||||
body: &[u8],
|
||||
) -> Result<(), Error<Self::TransportError>> {
|
||||
self.command = header[0];
|
||||
|
||||
let data_len = header.len() + body.len() + 1; // TFI + payload
|
||||
if data_len > 0xFF {
|
||||
return Err(Error::NoSpace);
|
||||
}
|
||||
|
||||
// PREAMBLE + STARTCODE1 + STARTCODE2 + LEN + LCS + TFI + payload + DCS + POSTAMBLE
|
||||
let frame_len = 6 + header.len() + body.len() + 2;
|
||||
let mut frame = [0u8; WRITE_FRAME_CAPACITY];
|
||||
if frame_len > frame.len() {
|
||||
return Err(Error::NoSpace);
|
||||
}
|
||||
|
||||
frame[0] = PN532_PREAMBLE;
|
||||
frame[1] = PN532_STARTCODE1;
|
||||
frame[2] = PN532_STARTCODE2;
|
||||
frame[3] = data_len as u8;
|
||||
frame[4] = (!(data_len as u8)).wrapping_add(1);
|
||||
frame[5] = PN532_HOST_TO_PN532;
|
||||
|
||||
let mut sum = PN532_HOST_TO_PN532;
|
||||
let mut idx = 6;
|
||||
for &b in header.iter().chain(body.iter()) {
|
||||
frame[idx] = b;
|
||||
idx += 1;
|
||||
sum = sum.wrapping_add(b);
|
||||
}
|
||||
frame[idx] = (!sum).wrapping_add(1);
|
||||
frame[idx + 1] = PN532_POSTAMBLE;
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
defmt::debug!("pn532: write cmd=0x{:02X} len={}", header[0], frame_len);
|
||||
self.i2c
|
||||
.write(PN532_I2C_ADDRESS, &frame[..frame_len])
|
||||
.map_err(Error::Transport)?;
|
||||
#[cfg(feature = "defmt")]
|
||||
defmt::debug!("pn532: write ACKed, reading ACK frame");
|
||||
self.read_ack_frame()
|
||||
}
|
||||
|
||||
fn read_response(
|
||||
&mut self,
|
||||
buf: &mut [u8],
|
||||
timeout_ms: u16,
|
||||
) -> Result<usize, Error<Self::TransportError>> {
|
||||
// Wait for the PN532 to signal data is ready.
|
||||
self.wait_ready(timeout_ms)?;
|
||||
|
||||
// Read the full response frame in a single transaction, stripping the
|
||||
// RDY byte. Frame layout: PREAMBLE(3) + LEN + LCS + TFI + CMD + payload + DCS + POSTAMBLE.
|
||||
let frame_len = buf.len() + 9;
|
||||
let mut frame = [0u8; MAX_DATA_LEN + 10];
|
||||
if frame_len > frame.len() {
|
||||
return Err(Error::NoSpace);
|
||||
}
|
||||
self.i2c
|
||||
.transaction(
|
||||
PN532_I2C_ADDRESS,
|
||||
&mut [
|
||||
Operation::Read(&mut [0]),
|
||||
Operation::Read(&mut frame[..frame_len]),
|
||||
],
|
||||
)
|
||||
.map_err(Error::Transport)?;
|
||||
|
||||
if frame[0] != PN532_PREAMBLE
|
||||
|| frame[1] != PN532_STARTCODE1
|
||||
|| frame[2] != PN532_STARTCODE2
|
||||
{
|
||||
return Err(Error::InvalidFrame);
|
||||
}
|
||||
let length = frame[3] as usize;
|
||||
if frame[3].wrapping_add(frame[4]) != 0 {
|
||||
return Err(Error::InvalidFrame);
|
||||
}
|
||||
|
||||
let cmd = self.command.wrapping_add(1);
|
||||
if frame[5] != PN532_PN532_TO_HOST || frame[6] != cmd {
|
||||
return Err(Error::InvalidFrame);
|
||||
}
|
||||
|
||||
let data_len = length.saturating_sub(2);
|
||||
if data_len > buf.len() {
|
||||
return Err(Error::NoSpace);
|
||||
}
|
||||
let mut sum = frame[5].wrapping_add(frame[6]);
|
||||
for i in 0..data_len {
|
||||
buf[i] = frame[7 + i];
|
||||
sum = sum.wrapping_add(frame[7 + i]);
|
||||
}
|
||||
if sum.wrapping_add(frame[7 + data_len]) != 0 {
|
||||
return Err(Error::InvalidFrame);
|
||||
}
|
||||
|
||||
Ok(data_len)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I2C, D, RST, IRQ> I2cInterface<I2C, D, RST, IRQ>
|
||||
where
|
||||
I2C: I2c,
|
||||
D: DelayNs,
|
||||
IRQ: InputPin,
|
||||
{
|
||||
/// Wait until the PN532 signals data is ready.
|
||||
///
|
||||
/// With an IRQ pin this blocks until the pin goes low; without one it polls
|
||||
/// the I2C status byte. Either way the status byte is consumed, so the next
|
||||
/// read returns the frame itself.
|
||||
fn wait_ready(&mut self, timeout_ms: u16) -> Result<(), Error<I2C::Error>> {
|
||||
let mut elapsed = 0u16;
|
||||
loop {
|
||||
match self.irq.is_low() {
|
||||
Ok(true) => {
|
||||
// IRQ asserted (active-low): data is ready. Poll the status
|
||||
// byte to confirm and consume it so the next read gets the
|
||||
// frame itself.
|
||||
let mut status = [0u8; 1];
|
||||
match self.i2c.read(PN532_I2C_ADDRESS, &mut status) {
|
||||
Ok(()) if status[0] & 1 == 1 => return Ok(()),
|
||||
_ => {
|
||||
self.delay.delay_ms(1);
|
||||
elapsed += 1;
|
||||
if timeout_ms != 0 && elapsed >= timeout_ms {
|
||||
return Err(Error::Timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(false) | Err(_) => {
|
||||
// IRQ connected but not asserted yet (or a read error):
|
||||
// keep waiting.
|
||||
self.delay.delay_ms(1);
|
||||
elapsed += 1;
|
||||
if timeout_ms != 0 && elapsed >= timeout_ms {
|
||||
return Err(Error::Timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_ack_frame(&mut self) -> Result<(), Error<I2C::Error>> {
|
||||
self.wait_ready(PN532_ACK_WAIT_TIME_MS)?;
|
||||
let mut ack = [0u8; 6];
|
||||
self.i2c
|
||||
.transaction(
|
||||
PN532_I2C_ADDRESS,
|
||||
&mut [
|
||||
Operation::Read(&mut [0]),
|
||||
Operation::Read(&mut ack),
|
||||
],
|
||||
)
|
||||
.map_err(Error::Transport)?;
|
||||
if ack == ACK {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::InvalidAck)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
use crate::{BaudRate, Error, Interface, Pn532, Uid};
|
||||
use crate::commands::{PN532_COMMAND_INDATAEXCHANGE, PN532_COMMAND_INLISTPASSIVETARGET, PN532_COMMAND_INRELEASE};
|
||||
use crate::error::StatusCode;
|
||||
|
||||
impl<B: Interface> Pn532<B> {
|
||||
/// Wait for an ISO14443A target and read its UID.
|
||||
///
|
||||
/// Returns `Ok(None)` when no card appears within `timeout_ms` milliseconds.
|
||||
pub fn read_passive_target_id(
|
||||
&mut self,
|
||||
card_baudrate: BaudRate,
|
||||
timeout_ms: u16,
|
||||
) -> Result<Option<Uid>, Error<B::TransportError>> {
|
||||
self.buffer[0] = PN532_COMMAND_INLISTPASSIVETARGET;
|
||||
self.buffer[1] = 1;
|
||||
self.buffer[2] = card_baudrate.into();
|
||||
self.send(3)?;
|
||||
|
||||
match self.read_timeout(timeout_ms) {
|
||||
Err(Error::Timeout) => return Ok(None),
|
||||
Err(e) => return Err(e),
|
||||
Ok(_) => {}
|
||||
}
|
||||
|
||||
if self.buffer[0] != 1 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let uid_len = self.buffer[5];
|
||||
if uid_len > 7 {
|
||||
return Err(Error::InvalidFrame);
|
||||
}
|
||||
|
||||
let mut bytes = [0u8; 7];
|
||||
bytes[..uid_len as usize].copy_from_slice(&self.buffer[6..6 + uid_len as usize]);
|
||||
|
||||
Ok(Some(Uid { bytes: heapless::Vec::from_array(bytes) }))
|
||||
}
|
||||
|
||||
/// In-list a passive target (used before [`Self::in_data_exchange`]).
|
||||
pub fn in_list_passive_target(&mut self) -> Result<bool, Error<B::TransportError>> {
|
||||
self.buffer[0] = PN532_COMMAND_INLISTPASSIVETARGET;
|
||||
self.buffer[1] = 1;
|
||||
self.buffer[2] = 0;
|
||||
self.send(3)?;
|
||||
|
||||
match self.read_timeout(30_000) {
|
||||
Err(Error::Timeout) => return Ok(false),
|
||||
Err(e) => return Err(e),
|
||||
Ok(_) => {}
|
||||
}
|
||||
|
||||
if self.buffer[0] != 1 {
|
||||
return Ok(false);
|
||||
}
|
||||
self.in_listed_tag = self.buffer[1];
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Exchange data with the currently in-listed peer.
|
||||
///
|
||||
/// Returns the number of bytes written to `response`. The response buffer
|
||||
/// is limited to 64 bytes.
|
||||
pub fn in_data_exchange(
|
||||
&mut self,
|
||||
send: &[u8],
|
||||
response: &mut [u8],
|
||||
) -> Result<usize, Error<B::TransportError>> {
|
||||
self.buffer[0] = PN532_COMMAND_INDATAEXCHANGE;
|
||||
self.buffer[1] = self.in_listed_tag;
|
||||
self.send_with_body(2, send)?;
|
||||
|
||||
let len = self.read_timeout(1000)?;
|
||||
if len == 0 || (self.buffer[0] & 0x3F) != 0 {
|
||||
return Err(Error::Status(StatusCode::from_repr(self.buffer[0] & 0x3F).unwrap()));
|
||||
}
|
||||
|
||||
let data_len = len - 1;
|
||||
let copy_len = data_len.min(response.len());
|
||||
response[..copy_len].copy_from_slice(&self.buffer[1..1 + copy_len]);
|
||||
Ok(copy_len)
|
||||
}
|
||||
|
||||
/// Release an in-listed target.
|
||||
pub fn in_release(&mut self, relevant_target: u8) -> Result<usize, Error<B::TransportError>> {
|
||||
self.buffer[0] = PN532_COMMAND_INRELEASE;
|
||||
self.buffer[1] = relevant_target;
|
||||
self.send(2)?;
|
||||
self.read()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#![no_std]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
//! A `no_std` [`embedded-hal`] driver for the NXP PN532 NFC controller over I2C.
|
||||
//!
|
||||
//! This crate is a Rust port of the well-known
|
||||
//! [Adafruit/Seeed PN532 Arduino library](https://github.com/elechouse/PN532),
|
||||
//! targeting the I2C interface (the most common breakout-board wiring).
|
||||
//!
|
||||
//! The driver is transport-agnostic via the [`Interface`] trait; [`I2cInterface`]
|
||||
//! provides the I2C implementation on top of a blocking `embedded_hal::i2c::I2c`
|
||||
//! bus plus a `embedded_hal::delay::DelayNs` delay source.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```ignore
|
||||
//! use embedded_hal::delay::DelayNs;
|
||||
//! use pn532::{BaudRate, I2cInterface, Pn532};
|
||||
//!
|
||||
//! # struct Delay;
|
||||
//! # impl DelayNs for Delay { fn delay_ns(&mut self, _: u32) {} }
|
||||
//! #
|
||||
//! # fn demo<I2C>(i2c: I2C, delay: Delay)
|
||||
//! # where I2C: embedded_hal::i2c::I2c
|
||||
//! # {
|
||||
//! let mut nfc = Pn532::new(I2cInterface::new(i2c, delay));
|
||||
//! nfc.begin().unwrap();
|
||||
//! nfc.sam_config().unwrap();
|
||||
//!
|
||||
//! let uid = nfc
|
||||
//! .read_passive_target_id(BaudRate::ISO14443A106kbps, 1000)
|
||||
//! .unwrap();
|
||||
//! if let Some(uid) = uid {
|
||||
//! // uid.bytes[..uid.len as usize] contains the card UID
|
||||
//! }
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! # Limitations
|
||||
//!
|
||||
//! * Responses are limited to 64 bytes, matching the PN532's internal packet
|
||||
//! buffer (this also constrains multi-block FeliCa reads).
|
||||
//! * The I2C interface is blocking and uses busy-wait polling for the PN532's
|
||||
//! "ready" flag, mirroring the original Arduino implementation.
|
||||
|
||||
pub mod commands;
|
||||
pub mod error;
|
||||
pub mod felica;
|
||||
pub mod interface;
|
||||
|
||||
pub(crate) mod driver;
|
||||
pub mod tg;
|
||||
pub mod mifare;
|
||||
pub mod iso14443a;
|
||||
|
||||
pub use commands::BaudRate;
|
||||
pub use driver::{FelicaPollingResponse, Pn532, TargetInitStatus, Uid};
|
||||
pub use error::Error;
|
||||
pub use interface::{I2cInterface, Interface, NoIrq, NoReset};
|
||||
@@ -0,0 +1,218 @@
|
||||
use crate::{Error, Interface, Pn532};
|
||||
use crate::commands::{MIFARE_CMD_AUTH_A, MIFARE_CMD_AUTH_B, MIFARE_CMD_READ, MIFARE_CMD_WRITE, MIFARE_CMD_WRITE_ULTRALIGHT, PN532_COMMAND_INDATAEXCHANGE};
|
||||
use crate::error::StatusCode;
|
||||
|
||||
impl<B: Interface> Pn532<B> {
|
||||
/// Whether the block number is the first block of a sector.
|
||||
pub fn mifare_classic_is_first_block(block: u32) -> bool {
|
||||
if block < 128 {
|
||||
block % 4 == 0
|
||||
} else {
|
||||
block % 16 == 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the block number is a sector trailer.
|
||||
pub fn mifare_classic_is_trailer_block(block: u32) -> bool {
|
||||
if block < 128 {
|
||||
(block + 1) % 4 == 0
|
||||
} else {
|
||||
(block + 1) % 16 == 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Authenticate a Mifare Classic block.
|
||||
pub fn mifare_classic_authenticate_block(
|
||||
&mut self,
|
||||
uid: &[u8],
|
||||
block_number: u8,
|
||||
key_number: u8,
|
||||
key_data: &[u8; 6],
|
||||
) -> Result<(), Error<B::TransportError>> {
|
||||
self.buffer[0] = PN532_COMMAND_INDATAEXCHANGE;
|
||||
self.buffer[1] = 1;
|
||||
self.buffer[2] = if key_number != 0 {
|
||||
MIFARE_CMD_AUTH_B
|
||||
} else {
|
||||
MIFARE_CMD_AUTH_A
|
||||
};
|
||||
self.buffer[3] = block_number;
|
||||
self.buffer[4..10].copy_from_slice(key_data);
|
||||
|
||||
let uid_len = uid.len().min(7);
|
||||
self.buffer[10..10 + uid_len].copy_from_slice(&uid[..uid_len]);
|
||||
|
||||
self.send(10 + uid_len)?;
|
||||
let len = self.read()?;
|
||||
if len == 0 || self.buffer[0] != 0x00 {
|
||||
return Err(Error::Status(StatusCode::from_repr(self.buffer[0]).unwrap()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read a 16-byte Mifare Classic data block.
|
||||
pub fn mifare_classic_read_data_block(
|
||||
&mut self,
|
||||
block_number: u8,
|
||||
data: &mut [u8; 16],
|
||||
) -> Result<(), Error<B::TransportError>> {
|
||||
self.buffer[0] = PN532_COMMAND_INDATAEXCHANGE;
|
||||
self.buffer[1] = 1;
|
||||
self.buffer[2] = MIFARE_CMD_READ;
|
||||
self.buffer[3] = block_number;
|
||||
self.send(4)?;
|
||||
|
||||
let len = self.read()?;
|
||||
if len < 17 || self.buffer[0] != 0x00 {
|
||||
return Err(Error::Status(StatusCode::from_repr(self.buffer[0]).unwrap()));
|
||||
}
|
||||
data.copy_from_slice(&self.buffer[1..17]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write a 16-byte Mifare Classic data block.
|
||||
pub fn mifare_classic_write_data_block(
|
||||
&mut self,
|
||||
block_number: u8,
|
||||
data: &[u8; 16],
|
||||
) -> Result<(), Error<B::TransportError>> {
|
||||
self.buffer[0] = PN532_COMMAND_INDATAEXCHANGE;
|
||||
self.buffer[1] = 1;
|
||||
self.buffer[2] = MIFARE_CMD_WRITE;
|
||||
self.buffer[3] = block_number;
|
||||
self.buffer[4..20].copy_from_slice(data);
|
||||
self.send(20)?;
|
||||
|
||||
let len = self.read()?;
|
||||
if len == 0 {
|
||||
return Err(Error::InvalidFrame);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Format a Mifare Classic card to store NDEF records.
|
||||
pub fn mifare_classic_format_ndef(&mut self) -> Result<(), Error<B::TransportError>> {
|
||||
let sector1 = [
|
||||
0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1,
|
||||
0x03, 0xE1,
|
||||
];
|
||||
let sector2 = [
|
||||
0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1,
|
||||
0x03, 0xE1,
|
||||
];
|
||||
let sector3 = [
|
||||
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF,
|
||||
];
|
||||
self.mifare_classic_write_data_block(1, §or1)?;
|
||||
self.mifare_classic_write_data_block(2, §or2)?;
|
||||
self.mifare_classic_write_data_block(3, §or3)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write an NDEF URI record to the given sector (1..15).
|
||||
pub fn mifare_classic_write_ndef_uri(
|
||||
&mut self,
|
||||
sector_number: u8,
|
||||
uri_identifier: u8,
|
||||
url: &str,
|
||||
) -> Result<(), Error<B::TransportError>> {
|
||||
let len = url.len();
|
||||
if !(1..=15).contains(§or_number) {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
if !(1..=38).contains(&len) {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
|
||||
let url = url.as_bytes();
|
||||
let mut s1 = [0u8; 16];
|
||||
let mut s2 = [0u8; 16];
|
||||
let mut s3 = [0u8; 16];
|
||||
let s4 = [
|
||||
0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF,
|
||||
];
|
||||
|
||||
s1[0] = 0x00;
|
||||
s1[1] = 0x00;
|
||||
s1[2] = 0x03;
|
||||
s1[3] = (len + 5) as u8;
|
||||
s1[4] = 0xD1;
|
||||
s1[5] = 0x01;
|
||||
s1[6] = (len + 1) as u8;
|
||||
s1[7] = 0x55;
|
||||
s1[8] = uri_identifier;
|
||||
|
||||
if len <= 6 {
|
||||
s1[9..9 + len].copy_from_slice(&url[..len]);
|
||||
s1[9 + len] = 0xFE;
|
||||
} else if len == 7 {
|
||||
s1[9..16].copy_from_slice(&url[..7]);
|
||||
s2[0] = 0xFE;
|
||||
} else if len <= 22 {
|
||||
s1[9..16].copy_from_slice(&url[..7]);
|
||||
s2[..len - 7].copy_from_slice(&url[7..len]);
|
||||
s2[len - 7] = 0xFE;
|
||||
} else if len == 23 {
|
||||
s1[9..16].copy_from_slice(&url[..7]);
|
||||
s2.copy_from_slice(&url[7..23]);
|
||||
s3[0] = 0xFE;
|
||||
} else {
|
||||
s1[9..16].copy_from_slice(&url[..7]);
|
||||
s2.copy_from_slice(&url[7..23]);
|
||||
s3[..len - 23].copy_from_slice(&url[23..len]);
|
||||
s3[len - 23] = 0xFE;
|
||||
}
|
||||
|
||||
self.mifare_classic_write_data_block(sector_number * 4, &s1)?;
|
||||
self.mifare_classic_write_data_block(sector_number * 4 + 1, &s2)?;
|
||||
self.mifare_classic_write_data_block(sector_number * 4 + 2, &s3)?;
|
||||
self.mifare_classic_write_data_block(sector_number * 4 + 3, &s4)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// -- Mifare Ultralight / NTAG ------------------------------------------
|
||||
|
||||
/// Read a 4-byte Mifare Ultralight / NTAG page.
|
||||
pub fn mifare_ultralight_read_page(
|
||||
&mut self,
|
||||
page: u8,
|
||||
buffer: &mut [u8; 4],
|
||||
) -> Result<(), Error<B::TransportError>> {
|
||||
if page >= 64 {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
self.buffer[0] = PN532_COMMAND_INDATAEXCHANGE;
|
||||
self.buffer[1] = 1;
|
||||
self.buffer[2] = MIFARE_CMD_READ;
|
||||
self.buffer[3] = page;
|
||||
self.send(4)?;
|
||||
|
||||
let len = self.read()?;
|
||||
if len < 5 || self.buffer[0] != 0x00 {
|
||||
return Err(Error::Status(StatusCode::from_repr(self.buffer[0]).unwrap()));
|
||||
}
|
||||
buffer.copy_from_slice(&self.buffer[1..5]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write a 4-byte Mifare Ultralight / NTAG page.
|
||||
pub fn mifare_ultralight_write_page(
|
||||
&mut self,
|
||||
page: u8,
|
||||
buffer: &[u8; 4],
|
||||
) -> Result<(), Error<B::TransportError>> {
|
||||
if page >= 64 {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
self.buffer[0] = PN532_COMMAND_INDATAEXCHANGE;
|
||||
self.buffer[1] = 1;
|
||||
self.buffer[2] = MIFARE_CMD_WRITE_ULTRALIGHT;
|
||||
self.buffer[3] = page;
|
||||
self.buffer[4..8].copy_from_slice(buffer);
|
||||
self.send(8)?;
|
||||
self.read()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
use crate::{Error, Interface, Pn532, TargetInitStatus};
|
||||
use crate::commands::{PN532_COMMAND_TGGETDATA, PN532_COMMAND_TGINITASTARGET, PN532_COMMAND_TGSETDATA};
|
||||
use crate::error::StatusCode;
|
||||
|
||||
impl<B: Interface> Pn532<B> {
|
||||
/// Initialize the PN532 as a target using a raw command frame.
|
||||
pub fn tg_init_as_target(
|
||||
&mut self,
|
||||
command: &[u8],
|
||||
timeout_ms: u16,
|
||||
) -> Result<TargetInitStatus, Error<B::TransportError>> {
|
||||
self.interface.write_command(command, &[])?;
|
||||
match self.interface.read_response(&mut self.buffer, timeout_ms) {
|
||||
Ok(_) => Ok(TargetInitStatus::Success),
|
||||
Err(Error::Timeout) => Ok(TargetInitStatus::Timeout),
|
||||
Err(_) => Ok(TargetInitStatus::Failed),
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the PN532 as a target with the default LLCP parameters.
|
||||
pub fn tg_init_as_target_default(
|
||||
&mut self,
|
||||
timeout_ms: u16,
|
||||
) -> Result<TargetInitStatus, Error<B::TransportError>> {
|
||||
const COMMAND: [u8; 44] = [
|
||||
PN532_COMMAND_TGINITASTARGET, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0xFE,
|
||||
0x0F, 0xBB, 0xBA, 0xA6, 0xC9, 0x89, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xFF, 0xFF, 0x01, 0xFE, 0x0F, 0xBB, 0xBA, 0xA6, 0xC9, 0x89, 0x00, 0x00, 0x06, 0x46,
|
||||
0x66, 0x6D, 0x01, 0x01, 0x10, 0x00,
|
||||
];
|
||||
self.tg_init_as_target(&COMMAND, timeout_ms)
|
||||
}
|
||||
|
||||
/// Retrieve data received from the initiator.
|
||||
pub fn tg_get_data(&mut self, buf: &mut [u8]) -> Result<usize, Error<B::TransportError>> {
|
||||
self.buffer[0] = PN532_COMMAND_TGGETDATA;
|
||||
self.send(1)?;
|
||||
|
||||
let len = self.read_timeout(3000)?;
|
||||
if len == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
if self.buffer[0] != 0 {
|
||||
return Err(Error::Status(StatusCode::from_repr(self.buffer[0]).unwrap()));
|
||||
}
|
||||
let data_len = len - 1;
|
||||
let copy_len = data_len.min(buf.len());
|
||||
buf[..copy_len].copy_from_slice(&self.buffer[1..1 + copy_len]);
|
||||
Ok(copy_len)
|
||||
}
|
||||
|
||||
/// Send data to the initiator.
|
||||
pub fn tg_set_data(
|
||||
&mut self,
|
||||
header: &[u8],
|
||||
body: &[u8],
|
||||
) -> Result<(), Error<B::TransportError>> {
|
||||
if header.len() > crate::driver::PACKET_BUFFER_SIZE - 1 {
|
||||
self.buffer[0] = PN532_COMMAND_TGSETDATA;
|
||||
self.interface.write_command(&self.buffer[..1], header)?;
|
||||
} else {
|
||||
self.buffer[0] = PN532_COMMAND_TGSETDATA;
|
||||
self.buffer[1..1 + header.len()].copy_from_slice(header);
|
||||
self.interface
|
||||
.write_command(&self.buffer[..1 + header.len()], body)?;
|
||||
}
|
||||
|
||||
let len = self.read_timeout(3000)?;
|
||||
if len == 0 || self.buffer[0] != 0 {
|
||||
return Err(Error::Status(StatusCode::from_repr(self.buffer[0]).unwrap()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user