[pn532] Check InDataExchange status and fix tag handling (#19591)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Keith Burzinski
2026-09-26 14:44:48 -05:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 746691d5c2
commit 6c5010032b
7 changed files with 301 additions and 113 deletions
+62 -42
View File
@@ -1,6 +1,7 @@
#include "pn532.h"
#include <memory>
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
@@ -25,7 +26,8 @@ void PN532::setup() {
}
std::vector<uint8_t> version_data;
if (!this->read_response(PN532_COMMAND_VERSION_DATA, version_data)) {
// GetFirmwareVersion returns IC, Ver, Rev and Support
if (!this->read_response(PN532_COMMAND_VERSION_DATA, version_data) || version_data.size() < 3) {
ESP_LOGE(TAG, "Error getting version");
this->mark_failed();
return;
@@ -35,8 +37,8 @@ void PN532::setup() {
if (!this->write_command_({
PN532_COMMAND_SAMCONFIGURATION,
0x01, // normal mode
0x14, // zero timeout (not in virtual card mode)
0x01,
0x14, // timeout: 20 x 50 ms (only used in virtual card mode)
0x01, // use IRQ
})) {
ESP_LOGE(TAG, "No wakeup ack");
this->mark_failed();
@@ -90,8 +92,8 @@ bool PN532::powerdown() {
ESP_LOGE(TAG, "Error reading PN532 powerdown response");
return false;
}
if (response[0] != 0x00) {
ESP_LOGE(TAG, "Error on PN532 powerdown: %02x", response[0]);
if (response.empty() || response[0] != 0x00) {
ESP_LOGE(TAG, "Powerdown error: %02x", response.empty() ? 0xFF : response[0]);
return false;
}
ESP_LOGV(TAG, "Powerdown successful");
@@ -150,7 +152,7 @@ void PN532::loop() {
return;
}
uint8_t num_targets = read[0];
uint8_t num_targets = read.empty() ? 0 : read[0];
if (num_targets != 1) {
// no tags found or too many
if (!this->current_uid_.empty()) {
@@ -163,12 +165,20 @@ void PN532::loop() {
return;
}
// target data for 106 kbps type A: NbTg, Tg, SENS_RES (2 bytes), SEL_RES, NFCIDLength, NFCID1 (UM0701-02, 7.3.5)
if (read.size() < 6) {
this->turn_off_rf_();
return;
}
const uint8_t sel_res = read[4];
uint8_t nfcid_length = read[5];
if (nfcid_length > nfc::NFC_UID_MAX_LENGTH || read.size() < 6U + nfcid_length) {
if (nfcid_length == 0 || nfcid_length > nfc::NFC_UID_MAX_LENGTH || read.size() < 6U + nfcid_length) {
// oops, pn532 returned invalid data
this->turn_off_rf_();
return;
}
nfc::NfcTagUid nfcid(read.begin() + 6, read.begin() + 6 + nfcid_length);
const uint8_t tag_type = tag_type_from_sel_res(sel_res);
bool report = true;
for (auto *bin_sens : this->binary_sensors_) {
@@ -188,7 +198,7 @@ void PN532::loop() {
this->current_uid_ = nfcid;
if (next_task_ == READ) {
auto tag = this->read_tag_(nfcid);
auto tag = this->read_tag_(nfcid, tag_type);
for (auto *trigger : this->triggers_ontag_)
trigger->process(tag);
@@ -206,13 +216,13 @@ void PN532::loop() {
}
} else if (next_task_ == CLEAN) {
ESP_LOGD(TAG, " Tag cleaning");
if (!this->clean_tag_(nfcid)) {
if (!this->clean_tag_(nfcid, tag_type)) {
ESP_LOGE(TAG, " Tag was not fully cleaned successfully");
}
ESP_LOGD(TAG, " Tag cleaned!");
} else if (next_task_ == FORMAT) {
ESP_LOGD(TAG, " Tag formatting");
if (!this->format_tag_(nfcid)) {
if (!this->format_tag_(nfcid, tag_type)) {
ESP_LOGE(TAG, "Error formatting tag as NDEF");
}
ESP_LOGD(TAG, " Tag formatted!");
@@ -220,16 +230,15 @@ void PN532::loop() {
if (this->next_task_message_to_write_ != nullptr) {
ESP_LOGD(TAG, " Tag writing");
ESP_LOGD(TAG, " Tag formatting");
if (!this->format_tag_(nfcid)) {
if (!this->format_tag_(nfcid, tag_type)) {
ESP_LOGE(TAG, " Tag could not be formatted for writing");
} else {
ESP_LOGD(TAG, " Writing NDEF data");
if (!this->write_tag_(nfcid, this->next_task_message_to_write_)) {
if (!this->write_tag_(nfcid, tag_type, this->next_task_message_to_write_.get())) {
ESP_LOGE(TAG, " Failed to write message to tag");
}
ESP_LOGD(TAG, " Finished writing NDEF data");
delete this->next_task_message_to_write_;
this->next_task_message_to_write_ = nullptr;
this->next_task_message_to_write_.reset();
this->on_finished_write_callback_.call();
}
}
@@ -307,16 +316,17 @@ void PN532::send_nack_() {
enum PN532ReadReady PN532::read_ready_(bool block) {
if (this->rd_ready_ == READY) {
if (block) {
this->rd_start_time_.reset();
this->rd_started_ = false;
this->rd_ready_ = WOULDBLOCK;
}
return READY;
}
if (!this->rd_start_time_.has_value()) {
if (!this->rd_started_) {
this->rd_start_time_ = millis();
this->rd_started_ = true;
}
const uint32_t rd_start_time = *this->rd_start_time_;
const uint32_t rd_start_time = this->rd_start_time_;
while (true) {
if (this->is_read_ready()) {
@@ -340,7 +350,7 @@ enum PN532ReadReady PN532::read_ready_(bool block) {
auto rdy = this->rd_ready_;
if (block || rdy == TIMEOUT) {
this->rd_start_time_.reset();
this->rd_started_ = false;
this->rd_ready_ = WOULDBLOCK;
}
return rdy;
@@ -355,21 +365,16 @@ void PN532::turn_off_rf_() {
});
}
std::unique_ptr<nfc::NfcTag> PN532::read_tag_(nfc::NfcTagUid &uid) {
uint8_t type = nfc::guess_tag_type(uid.size());
if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
std::unique_ptr<nfc::NfcTag> PN532::read_tag_(nfc::NfcTagUid &uid, const uint8_t tag_type) {
if (tag_type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
ESP_LOGD(TAG, "Mifare classic");
return this->read_mifare_classic_tag_(uid);
} else if (type == nfc::TAG_TYPE_2) {
} else if (tag_type == nfc::TAG_TYPE_2) {
ESP_LOGD(TAG, "Mifare ultralight");
return this->read_mifare_ultralight_tag_(uid);
} else if (type == nfc::TAG_TYPE_UNKNOWN) {
ESP_LOGV(TAG, "Cannot determine tag type");
return make_unique<nfc::NfcTag>(uid);
} else {
return make_unique<nfc::NfcTag>(uid);
}
ESP_LOGV(TAG, "Reading tag type %u is not supported", tag_type);
return make_unique<nfc::NfcTag>(uid);
}
void PN532::read_mode() {
@@ -386,43 +391,58 @@ void PN532::format_mode() {
}
void PN532::write_mode(nfc::NdefMessage *message) {
this->next_task_ = WRITE;
this->next_task_message_to_write_ = message;
this->next_task_message_to_write_.reset(message);
ESP_LOGD(TAG, "Waiting to write next tag");
}
bool PN532::clean_tag_(nfc::NfcTagUid &uid) {
uint8_t type = nfc::guess_tag_type(uid.size());
if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
bool PN532::clean_tag_(nfc::NfcTagUid &uid, const uint8_t tag_type) {
if (tag_type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
return this->format_mifare_classic_mifare_(uid);
} else if (type == nfc::TAG_TYPE_2) {
} else if (tag_type == nfc::TAG_TYPE_2) {
return this->clean_mifare_ultralight_();
}
ESP_LOGE(TAG, "Unsupported Tag for formatting");
return false;
}
bool PN532::format_tag_(nfc::NfcTagUid &uid) {
uint8_t type = nfc::guess_tag_type(uid.size());
if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
bool PN532::format_tag_(nfc::NfcTagUid &uid, const uint8_t tag_type) {
if (tag_type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
return this->format_mifare_classic_ndef_(uid);
} else if (type == nfc::TAG_TYPE_2) {
} else if (tag_type == nfc::TAG_TYPE_2) {
return this->clean_mifare_ultralight_();
}
ESP_LOGE(TAG, "Unsupported Tag for formatting");
return false;
}
bool PN532::write_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message) {
uint8_t type = nfc::guess_tag_type(uid.size());
if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
bool PN532::write_tag_(nfc::NfcTagUid &uid, const uint8_t tag_type, nfc::NdefMessage *message) {
if (tag_type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
return this->write_mifare_classic_tag_(uid, message);
} else if (type == nfc::TAG_TYPE_2) {
} else if (tag_type == nfc::TAG_TYPE_2) {
return this->write_mifare_ultralight_tag_(uid, message);
}
ESP_LOGE(TAG, "Unsupported Tag for formatting");
ESP_LOGE(TAG, "Unsupported Tag for writing");
return false;
}
bool PN532::in_data_exchange_(const std::vector<uint8_t> &command, std::vector<uint8_t> &response) {
// formatting a tag takes seconds of back-to-back exchanges inside loop(), longer than the task watchdog allows
App.feed_wdt();
if (!this->write_command_(command)) {
return false;
}
// output: Status, DataIn; a status of 0x00 means the exchange with the target succeeded (UM0701-02, 7.3.8)
if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, response) || response.empty()) {
return false;
}
if (response[0] != 0x00) {
ESP_LOGV(TAG, "InDataExchange failed, status 0x%02X", response[0]);
return false;
}
response.erase(response.begin());
return true;
}
void PN532::dump_config() {
ESP_LOGCONFIG(TAG, "PN532:");
switch (this->error_code_) {
+42 -18
View File
@@ -19,12 +19,28 @@ static const uint8_t PN532_COMMAND_INDATAEXCHANGE = 0x40;
static const uint8_t PN532_COMMAND_INLISTPASSIVETARGET = 0x4A;
static const uint8_t PN532_COMMAND_POWERDOWN = 0x16;
enum PN532ReadReady {
enum PN532ReadReady : uint8_t {
WOULDBLOCK = 0,
TIMEOUT,
READY,
};
// SEL_RES (SAK) bits, as reported by InListPassiveTarget for ISO/IEC 14443 type A targets (NXP AN10833)
static constexpr uint8_t SEL_RES_MIFARE_CLASSIC = 0x08;
static constexpr uint8_t SEL_RES_ISO_DEP = 0x20;
static constexpr uint8_t SEL_RES_TNP3XXX = 0x01; // MIFARE Classic 1K compatible
/// Tag type (nfc::TAG_TYPE_*) from a type A target's SEL_RES byte
inline uint8_t tag_type_from_sel_res(uint8_t sel_res) {
if ((sel_res & SEL_RES_MIFARE_CLASSIC) || sel_res == SEL_RES_TNP3XXX)
return nfc::TAG_TYPE_MIFARE_CLASSIC;
if (sel_res & SEL_RES_ISO_DEP)
return nfc::TAG_TYPE_4;
if (sel_res == 0x00)
return nfc::TAG_TYPE_2;
return nfc::TAG_TYPE_UNKNOWN;
}
class PN532BinarySensor;
class PN532 : public PollingComponent {
@@ -67,11 +83,14 @@ class PN532 : public PollingComponent {
virtual bool read_data(std::vector<uint8_t> &data, uint8_t len) = 0;
virtual bool read_response(uint8_t command, std::vector<uint8_t> &data) = 0;
std::unique_ptr<nfc::NfcTag> read_tag_(nfc::NfcTagUid &uid);
std::unique_ptr<nfc::NfcTag> read_tag_(nfc::NfcTagUid &uid, uint8_t tag_type);
bool format_tag_(nfc::NfcTagUid &uid);
bool clean_tag_(nfc::NfcTagUid &uid);
bool write_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message);
bool format_tag_(nfc::NfcTagUid &uid, uint8_t tag_type);
bool clean_tag_(nfc::NfcTagUid &uid, uint8_t tag_type);
bool write_tag_(nfc::NfcTagUid &uid, uint8_t tag_type, nfc::NdefMessage *message);
/// Sends an InDataExchange command and reads the response; returns false unless the status byte reports success.
/// On success, `response` holds the data returned by the target, without the status byte.
bool in_data_exchange_(const std::vector<uint8_t> &command, std::vector<uint8_t> &response);
std::unique_ptr<nfc::NfcTag> read_mifare_classic_tag_(nfc::NfcTagUid &uid);
bool read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
@@ -91,27 +110,32 @@ class PN532 : public PollingComponent {
bool write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message);
bool clean_mifare_ultralight_();
bool updates_enabled_{true};
bool requested_read_{false};
std::vector<PN532BinarySensor *> binary_sensors_;
std::vector<nfc::NfcOnTagTrigger *> triggers_ontag_;
std::vector<nfc::NfcOnTagTrigger *> triggers_ontagremoved_;
nfc::NfcTagUid current_uid_;
nfc::NdefMessage *next_task_message_to_write_;
optional<uint32_t> rd_start_time_{};
enum PN532ReadReady rd_ready_ { WOULDBLOCK };
enum NfcTask {
enum NfcTask : uint8_t {
READ = 0,
CLEAN,
FORMAT,
WRITE,
} next_task_{READ};
enum PN532Error {
};
enum PN532Error : uint8_t {
NONE = 0,
WAKEUP_FAILED,
SAM_COMMAND_FAILED,
} error_code_{NONE};
};
// members are ordered by alignment, widest first, to minimize padding
CallbackManager<void()> on_finished_write_callback_;
std::vector<PN532BinarySensor *> binary_sensors_;
std::vector<nfc::NfcOnTagTrigger *> triggers_ontag_;
std::vector<nfc::NfcOnTagTrigger *> triggers_ontagremoved_;
std::unique_ptr<nfc::NdefMessage> next_task_message_to_write_;
nfc::NfcTagUid current_uid_;
uint32_t rd_start_time_{0}; // valid only while rd_started_ is set
PN532ReadReady rd_ready_{WOULDBLOCK};
NfcTask next_task_{READ};
PN532Error error_code_{NONE};
bool rd_started_{false};
bool updates_enabled_{true};
bool requested_read_{false};
};
class PN532BinarySensor final : public binary_sensor::BinarySensor {
@@ -36,14 +36,15 @@ std::unique_ptr<nfc::NfcTag> PN532::read_mifare_classic_tag_(nfc::NfcTagUid &uid
if (nfc::mifare_classic_is_first_block(current_block)) {
if (!this->auth_mifare_classic_block_(uid, current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY)) {
ESP_LOGE(TAG, "Error, Block authentication failed for %d", current_block);
return make_unique<nfc::NfcTag>(uid, nfc::MIFARE_CLASSIC);
}
}
std::vector<uint8_t> block_data;
if (this->read_mifare_classic_block_(current_block, block_data)) {
buffer.insert(buffer.end(), block_data.begin(), block_data.end());
} else {
if (!this->read_mifare_classic_block_(current_block, block_data)) {
ESP_LOGE(TAG, "Error reading block %d", current_block);
return make_unique<nfc::NfcTag>(uid, nfc::MIFARE_CLASSIC);
}
buffer.insert(buffer.end(), block_data.begin(), block_data.end());
index += nfc::MIFARE_CLASSIC_BLOCK_SIZE;
current_block++;
@@ -63,20 +64,18 @@ std::unique_ptr<nfc::NfcTag> PN532::read_mifare_classic_tag_(nfc::NfcTagUid &uid
}
bool PN532::read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data) {
if (!this->write_command_({
PN532_COMMAND_INDATAEXCHANGE,
0x01, // One card
nfc::MIFARE_CMD_READ,
block_num,
})) {
if (!this->in_data_exchange_(
{
PN532_COMMAND_INDATAEXCHANGE,
0x01, // One card
nfc::MIFARE_CMD_READ,
block_num,
},
data) ||
data.size() != nfc::MIFARE_CLASSIC_BLOCK_SIZE) {
return false;
}
if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, data) || data[0] != 0x00) {
return false;
}
data.erase(data.begin());
char data_buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
ESP_LOGVV(TAG, " Block %d: %s", block_num, nfc::format_bytes_to(data_buf, data));
return true;
@@ -90,14 +89,14 @@ bool PN532::auth_mifare_classic_block_(nfc::NfcTagUid &uid, uint8_t block_num, u
block_num, // Block number
});
data.insert(data.end(), key, key + 6);
data.insert(data.end(), uid.begin(), uid.end());
if (!this->write_command_(data)) {
ESP_LOGE(TAG, "Authentication failed - Block %d", block_num);
// the command takes exactly 4 UID bytes (UM0701-02, 7.3.8); for 7-byte UIDs these are the last 4, as in libnfc
if (uid.size() < 4) {
return false;
}
data.insert(data.end(), uid.end() - 4, uid.end());
std::vector<uint8_t> response;
if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, response) || response[0] != 0x00) {
if (!this->in_data_exchange_(data, response)) {
ESP_LOGE(TAG, "Authentication failed - Block 0x%02x", block_num);
return false;
}
@@ -167,6 +166,8 @@ bool PN532::format_mifare_classic_ndef_(nfc::NfcTagUid &uid) {
ESP_LOGD(TAG, "Sector 0 formatted to NDEF");
bool error = false;
for (int block = 4; block < 64; block += 4) {
if (!this->auth_mifare_classic_block_(uid, block + 3, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY)) {
return false;
@@ -174,23 +175,28 @@ bool PN532::format_mifare_classic_ndef_(nfc::NfcTagUid &uid) {
if (block == 4) {
if (!this->write_mifare_classic_block_(block, EMPTY_NDEF_MESSAGE.data(), EMPTY_NDEF_MESSAGE.size())) {
ESP_LOGE(TAG, "Unable to write block %d", block);
error = true;
}
} else {
if (!this->write_mifare_classic_block_(block, BLANK_BLOCK.data(), BLANK_BLOCK.size())) {
ESP_LOGE(TAG, "Unable to write block %d", block);
error = true;
}
}
if (!this->write_mifare_classic_block_(block + 1, BLANK_BLOCK.data(), BLANK_BLOCK.size())) {
ESP_LOGE(TAG, "Unable to write block %d", block + 1);
error = true;
}
if (!this->write_mifare_classic_block_(block + 2, BLANK_BLOCK.data(), BLANK_BLOCK.size())) {
ESP_LOGE(TAG, "Unable to write block %d", block + 2);
error = true;
}
if (!this->write_mifare_classic_block_(block + 3, NDEF_TRAILER.data(), NDEF_TRAILER.size())) {
ESP_LOGE(TAG, "Unable to write trailer block %d", block + 3);
error = true;
}
}
return true;
return !error;
}
bool PN532::write_mifare_classic_block_(uint8_t block_num, const uint8_t *data, size_t len) {
@@ -201,13 +207,9 @@ bool PN532::write_mifare_classic_block_(uint8_t block_num, const uint8_t *data,
block_num,
});
cmd.insert(cmd.end(), data, data + len);
if (!this->write_command_(cmd)) {
ESP_LOGE(TAG, "Error writing block %d", block_num);
return false;
}
std::vector<uint8_t> response;
if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, response)) {
if (!this->in_data_exchange_(cmd, response)) {
ESP_LOGE(TAG, "Error writing block %d", block_num);
return false;
}
@@ -1,3 +1,4 @@
#include <algorithm>
#include <array>
#include <memory>
@@ -51,24 +52,20 @@ bool PN532::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes
std::vector<uint8_t> response;
for (uint8_t i = 0; i * read_increment < num_bytes; i++) {
if (!this->write_command_({
PN532_COMMAND_INDATAEXCHANGE,
0x01, // One card
nfc::MIFARE_CMD_READ,
uint8_t(i * nfc::MIFARE_ULTRALIGHT_READ_SIZE + start_page),
})) {
// a READ returns 4 pages (16 bytes)
if (!this->in_data_exchange_(
{
PN532_COMMAND_INDATAEXCHANGE,
0x01, // One card
nfc::MIFARE_CMD_READ,
uint8_t(i * nfc::MIFARE_ULTRALIGHT_READ_SIZE + start_page),
},
response) ||
response.size() != read_increment) {
return false;
}
if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, response) || response[0] != 0x00) {
return false;
}
uint16_t bytes_offset = (i + 1) * read_increment;
auto pages_in_end_itr = bytes_offset <= num_bytes ? response.end() : response.end() - (bytes_offset - num_bytes);
if ((pages_in_end_itr > response.begin()) && (pages_in_end_itr <= response.end())) {
data.insert(data.end(), response.begin() + 1, pages_in_end_itr);
}
const uint16_t remaining = num_bytes - i * read_increment;
data.insert(data.end(), response.begin(), response.begin() + std::min<uint16_t>(read_increment, remaining));
}
char data_buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
@@ -87,7 +84,7 @@ bool PN532::is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_t
uint16_t PN532::read_mifare_ultralight_capacity_() {
std::vector<uint8_t> data;
if (this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE, data)) {
if (this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE, data) && data.size() > 2) {
ESP_LOGV(TAG, "Tag capacity is %u bytes", data[2] * 8U);
return data[2] * 8U;
}
@@ -174,13 +171,9 @@ bool PN532::write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write
page_num,
});
cmd.insert(cmd.end(), write_data, write_data + len);
if (!this->write_command_(cmd)) {
ESP_LOGE(TAG, "Error writing page %u", page_num);
return false;
}
std::vector<uint8_t> response;
if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, response)) {
if (!this->in_data_exchange_(cmd, response)) {
ESP_LOGE(TAG, "Error writing page %u", page_num);
return false;
}
+7 -6
View File
@@ -12,11 +12,12 @@ namespace esphome::pn532_i2c {
static const char *const TAG = "pn532_i2c";
bool PN532I2C::is_read_ready() {
uint8_t ready;
if (!this->read_bytes_raw(&ready, 1)) {
uint8_t status;
if (!this->read_bytes_raw(&status, 1)) {
return false;
}
return ready == 0x01;
// only bit 0 (RDY) of the status byte is defined (UM0701-02, 6.2.4)
return status & 0x01;
}
bool PN532I2C::write_data(const std::vector<uint8_t> &data) {
@@ -30,9 +31,9 @@ bool PN532I2C::read_data(std::vector<uint8_t> &data, uint8_t len) {
return false;
}
// the PN532 prefixes every frame with a status byte
data.resize(len + 1);
this->read_bytes_raw(data.data(), len + 1);
return true;
return this->read_bytes_raw(data.data(), len + 1);
}
bool PN532I2C::read_response(uint8_t command, std::vector<uint8_t> &data) {
@@ -73,7 +74,7 @@ bool PN532I2C::read_response(uint8_t command, std::vector<uint8_t> &data) {
checksum = ~checksum + 1;
if (data[len + 1] != checksum) {
ESP_LOGV(TAG, "read data invalid checksum! %02X != %02X", data[len], checksum);
ESP_LOGV(TAG, "read data invalid checksum! %02X != %02X", data[len + 1], checksum);
return false;
}
+2 -1
View File
@@ -25,7 +25,8 @@ void PN532Spi::setup() {
bool PN532Spi::is_read_ready() {
this->enable();
this->write_byte(0x02);
bool ready = this->read_byte() == 0x01;
// only bit 0 (RDY) of the status byte is defined (UM0701-02, 6.2.5)
const bool ready = this->read_byte() & 0x01;
this->disable();
return ready;
}
+147
View File
@@ -0,0 +1,147 @@
#include <gtest/gtest.h>
#include <deque>
#include "esphome/components/pn532/pn532.h"
namespace esphome::pn532 {
namespace {
// Stands in for the bus: acknowledges every command and answers with queued response payloads.
class FakePN532 : public PN532 {
public:
using PN532::auth_mifare_classic_block_;
using PN532::read_mifare_ultralight_bytes_;
using PN532::read_mifare_classic_block_;
using PN532::write_mifare_classic_block_;
using PN532::write_mifare_ultralight_page_;
std::deque<std::vector<uint8_t>> responses;
std::vector<std::vector<uint8_t>> written;
protected:
bool is_read_ready() override { return true; }
bool write_data(const std::vector<uint8_t> &data) override {
this->written.push_back(data);
return true;
}
// only used for ACK frames; index 0 is the I2C status byte
bool read_data(std::vector<uint8_t> &data, uint8_t len) override {
data = {0x01, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00};
return true;
}
bool read_response(uint8_t command, std::vector<uint8_t> &data) override {
if (this->responses.empty())
return false;
data = this->responses.front();
this->responses.pop_front();
return true;
}
};
// Extracts the command bytes (after TFI) from a normal information frame
std::vector<uint8_t> frame_data(const std::vector<uint8_t> &frame) {
// preamble, start code (2), LEN, LCS, TFI, data..., DCS, postamble
return std::vector<uint8_t>(frame.begin() + 6, frame.end() - 2);
}
} // namespace
TEST(PN532TagType, FromSelRes) {
EXPECT_EQ(tag_type_from_sel_res(0x08), nfc::TAG_TYPE_MIFARE_CLASSIC); // Classic 1K
EXPECT_EQ(tag_type_from_sel_res(0x18), nfc::TAG_TYPE_MIFARE_CLASSIC); // Classic 4K
EXPECT_EQ(tag_type_from_sel_res(0x09), nfc::TAG_TYPE_MIFARE_CLASSIC); // Mini
EXPECT_EQ(tag_type_from_sel_res(0x01), nfc::TAG_TYPE_MIFARE_CLASSIC); // TNP3xxx
EXPECT_EQ(tag_type_from_sel_res(0x00), nfc::TAG_TYPE_2); // Ultralight / NTAG
EXPECT_EQ(tag_type_from_sel_res(0x20), nfc::TAG_TYPE_4); // ISO-DEP (phones, DESFire)
EXPECT_EQ(tag_type_from_sel_res(0x40), nfc::TAG_TYPE_UNKNOWN);
}
// A failed write (status byte other than 0x00) must be reported as a failure.
TEST(PN532Mifare, ClassicWriteChecksStatus) {
FakePN532 pn532;
const uint8_t block[16] = {};
pn532.responses.push_back({0x14}); // authentication error
EXPECT_FALSE(pn532.write_mifare_classic_block_(4, block, sizeof(block)));
pn532.responses.push_back({0x00});
EXPECT_TRUE(pn532.write_mifare_classic_block_(4, block, sizeof(block)));
}
TEST(PN532Mifare, UltralightWriteChecksStatus) {
FakePN532 pn532;
const uint8_t page[4] = {};
pn532.responses.push_back({0x01}); // timeout
EXPECT_FALSE(pn532.write_mifare_ultralight_page_(4, page, sizeof(page)));
pn532.responses.push_back({0x00});
EXPECT_TRUE(pn532.write_mifare_ultralight_page_(4, page, sizeof(page)));
}
TEST(PN532Mifare, ClassicReadRejectsBadResponses) {
FakePN532 pn532;
std::vector<uint8_t> data;
pn532.responses.emplace_back(); // empty response
EXPECT_FALSE(pn532.read_mifare_classic_block_(4, data));
data.clear();
pn532.responses.push_back({0x00, 0x01, 0x02}); // short block
EXPECT_FALSE(pn532.read_mifare_classic_block_(4, data));
std::vector<uint8_t> good(17, 0xAB);
good[0] = 0x00;
pn532.responses.push_back(good);
data.clear();
EXPECT_TRUE(pn532.read_mifare_classic_block_(4, data));
EXPECT_EQ(data, std::vector<uint8_t>(16, 0xAB));
}
// Authentication carries exactly 4 UID bytes: the last 4 of a 7-byte UID.
TEST(PN532Mifare, AuthSendsFourUidBytes) {
FakePN532 pn532;
nfc::NfcTagUid uid = {0x04, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66};
pn532.responses.push_back({0x00});
EXPECT_TRUE(pn532.auth_mifare_classic_block_(uid, 4, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY));
ASSERT_EQ(pn532.written.size(), 1u);
const auto cmd = frame_data(pn532.written[0]);
// InDataExchange, Tg, Cmd, Addr, key (6), UID (4)
ASSERT_EQ(cmd.size(), 14u);
EXPECT_EQ(std::vector<uint8_t>(cmd.end() - 4, cmd.end()), (std::vector<uint8_t>{0x33, 0x44, 0x55, 0x66}));
}
// Reads in 16-byte chunks, keeps only the bytes asked for, and advances 4 pages per READ.
TEST(PN532Mifare, UltralightReadTrimsLastChunk) {
FakePN532 pn532;
std::vector<uint8_t> first(17), second(17);
first[0] = second[0] = 0x00; // status
for (uint8_t i = 0; i < 16; i++) {
first[i + 1] = i;
second[i + 1] = 0x10 + i;
}
pn532.responses.push_back(first);
pn532.responses.push_back(second);
std::vector<uint8_t> data;
ASSERT_TRUE(pn532.read_mifare_ultralight_bytes_(4, 20, data));
ASSERT_EQ(data.size(), 20u);
EXPECT_EQ(data[15], 15);
EXPECT_EQ(data[16], 0x10);
EXPECT_EQ(data[19], 0x13);
ASSERT_EQ(pn532.written.size(), 2u);
EXPECT_EQ(frame_data(pn532.written[0]).back(), 4); // READ page 4
EXPECT_EQ(frame_data(pn532.written[1]).back(), 8); // then page 8
}
TEST(PN532Mifare, UltralightReadRejectsBadResponses) {
FakePN532 pn532;
std::vector<uint8_t> data;
pn532.responses.push_back({0x00, 0x01, 0x02}); // short response
EXPECT_FALSE(pn532.read_mifare_ultralight_bytes_(4, 16, data));
std::vector<uint8_t> failed(17, 0x00);
failed[0] = 0x01; // timeout status
pn532.responses.push_back(failed);
data.clear();
EXPECT_FALSE(pn532.read_mifare_ultralight_bytes_(4, 16, data));
}
} // namespace esphome::pn532