mirror of
https://github.com/esphome/esphome.git
synced 2026-09-28 23:40:25 +00:00
[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:
co-authored by
Claude Opus 5.5
parent
746691d5c2
commit
6c5010032b
@@ -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_) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user