[pn532] Remove runtime heap allocation from the frame and tag paths (#19744)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Keith Burzinski
2026-09-27 05:55:38 +01:00
committed by GitHub
co-authored by Claude Fable 5.1
parent b619df886a
commit 514ee178f9
17 changed files with 356 additions and 200 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ namespace esphome::nfc {
static const char *const TAG = "nfc.ndef_message";
NdefMessage::NdefMessage(std::vector<uint8_t> &data) {
NdefMessage::NdefMessage(const std::span<const uint8_t> data) {
ESP_LOGV(TAG, "Building NdefMessage with %zu bytes", data.size());
size_t index = 0;
while (index < data.size()) {
+3 -1
View File
@@ -1,6 +1,7 @@
#pragma once
#include <memory>
#include <span>
#include <vector>
#include "esphome/core/helpers.h"
@@ -16,7 +17,8 @@ static constexpr uint8_t MAX_NDEF_RECORDS = 4;
class NdefMessage {
public:
NdefMessage() = default;
NdefMessage(std::vector<uint8_t> &data);
NdefMessage(std::span<const uint8_t> data);
NdefMessage(std::vector<uint8_t> &data) : NdefMessage(std::span<const uint8_t>(data)) {}
NdefMessage(const NdefMessage &msg) {
records_.reserve(msg.records_.size());
for (const auto &r : msg.records_) {
+22 -2
View File
@@ -23,7 +23,7 @@ uint8_t guess_tag_type(uint8_t uid_length) {
}
}
int8_t get_mifare_classic_ndef_start_index(std::vector<uint8_t> &data) {
int8_t get_mifare_classic_ndef_start_index(const std::span<const uint8_t> data) {
for (uint8_t i = 0; i < MIFARE_CLASSIC_BLOCK_SIZE; i++) {
if (data[i] == 0x00) {
// Do nothing, skip
@@ -36,7 +36,8 @@ int8_t get_mifare_classic_ndef_start_index(std::vector<uint8_t> &data) {
return -1;
}
bool decode_mifare_classic_tlv(std::vector<uint8_t> &data, uint32_t &message_length, uint8_t &message_start_index) {
bool decode_mifare_classic_tlv(const std::span<const uint8_t> data, uint32_t &message_length,
uint8_t &message_start_index) {
if (data.size() < MIFARE_CLASSIC_BLOCK_SIZE) {
ESP_LOGE(TAG, "Error, data too short for NDEF detection.");
return false;
@@ -68,6 +69,25 @@ uint32_t get_mifare_ultralight_buffer_size(uint32_t message_length) {
return buffer_size;
}
void fill_ndef_tlv(const std::span<const uint8_t> message, const uint32_t buffer_length, FixedVector<uint8_t> &buffer) {
buffer.init(buffer_length);
buffer.push_back(0x03);
if (message.size() < 255) {
buffer.push_back(message.size());
} else {
buffer.push_back(0xFF);
buffer.push_back((message.size() >> 8) & 0xFF);
buffer.push_back(message.size() & 0xFF);
}
for (const uint8_t byte : message) {
buffer.push_back(byte);
}
buffer.push_back(0xFE);
while (buffer.size() < buffer_length) {
buffer.push_back(0x00);
}
}
uint32_t get_mifare_classic_buffer_size(uint32_t message_length) {
uint32_t buffer_size = message_length;
if (message_length < 255) {
+4 -2
View File
@@ -64,9 +64,11 @@ static constexpr size_t FORMAT_BYTES_BUFFER_SIZE = 192;
char *format_bytes_to(char *buffer, std::span<const uint8_t> bytes);
uint8_t guess_tag_type(uint8_t uid_length);
int8_t get_mifare_classic_ndef_start_index(std::vector<uint8_t> &data);
bool decode_mifare_classic_tlv(std::vector<uint8_t> &data, uint32_t &message_length, uint8_t &message_start_index);
int8_t get_mifare_classic_ndef_start_index(std::span<const uint8_t> data);
bool decode_mifare_classic_tlv(std::span<const uint8_t> data, uint32_t &message_length, uint8_t &message_start_index);
uint32_t get_mifare_classic_buffer_size(uint32_t message_length);
/// Fills `buffer` with the NDEF TLV (type, length, message, terminator) padded with zeros to `buffer_length`
void fill_ndef_tlv(std::span<const uint8_t> message, uint32_t buffer_length, FixedVector<uint8_t> &buffer);
bool mifare_classic_is_first_block(uint8_t block_num);
bool mifare_classic_is_trailer_block(uint8_t block_num);
+7
View File
@@ -46,6 +46,11 @@ def CONFIG_SCHEMA(conf: ConfigType) -> None:
)
_request_ontag_trigger_slot = cg.slot_counter("PN532_ON_TAG_TRIGGER_COUNT")
_request_ontagremoved_trigger_slot = cg.slot_counter(
"PN532_ON_TAG_REMOVED_TRIGGER_COUNT"
)
_CALLBACK_AUTOMATIONS = (
automation.CallbackAutomation(
CONF_ON_FINISHED_WRITE, "add_on_finished_write_callback"
@@ -58,6 +63,7 @@ async def setup_pn532(var: MockObj, config: ConfigType) -> None:
for conf in config.get(CONF_ON_TAG, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
_request_ontag_trigger_slot(str(var))
cg.add(var.register_ontag_trigger(trigger))
await automation.build_automation(
trigger, [(cg.std_string, "x"), (nfc.NfcTag, "tag")], conf
@@ -65,6 +71,7 @@ async def setup_pn532(var: MockObj, config: ConfigType) -> None:
for conf in config.get(CONF_ON_TAG_REMOVED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
_request_ontagremoved_trigger_slot(str(var))
cg.add(var.register_ontagremoved_trigger(trigger))
await automation.build_automation(
trigger, [(cg.std_string, "x"), (nfc.NfcTag, "tag")], conf
@@ -34,6 +34,8 @@ def validate_uid(value: Any) -> str:
PN532BinarySensor = pn532_ns.class_("PN532BinarySensor", binary_sensor.BinarySensor)
_request_binary_sensor_slot = cg.slot_counter("PN532_BINARY_SENSOR_COUNT")
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(PN532BinarySensor).extend(
{
cv.GenerateID(CONF_PN532_ID): cv.use_id(PN532),
@@ -46,6 +48,7 @@ async def to_code(config: ConfigType) -> None:
var = await binary_sensor.new_binary_sensor(config)
hub = await cg.get_variable(config[CONF_PN532_ID])
_request_binary_sensor_slot(str(hub))
cg.add(hub.register_tag(var))
addr = [HexInt(int(x, 16)) for x in config[CONF_UID].split("-")]
cg.add(var.set_uid(addr))
+60 -23
View File
@@ -1,5 +1,7 @@
#include "pn532.h"
#include <algorithm>
#include <array>
#include <memory>
#include "esphome/core/application.h"
#include "esphome/core/log.h"
@@ -25,7 +27,7 @@ void PN532::setup() {
}
}
std::vector<uint8_t> version_data;
PN532Frame 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");
@@ -45,7 +47,7 @@ void PN532::setup() {
return;
}
std::vector<uint8_t> wakeup_result;
PN532Frame wakeup_result;
if (!this->read_response(PN532_COMMAND_SAMCONFIGURATION, wakeup_result)) {
this->error_code_ = WAKEUP_FAILED;
this->mark_failed();
@@ -65,7 +67,7 @@ void PN532::setup() {
return;
}
std::vector<uint8_t> sam_result;
PN532Frame sam_result;
if (!this->read_response(PN532_COMMAND_SAMCONFIGURATION, sam_result)) {
ESP_LOGV(TAG, "Invalid SAM result: (%u)", sam_result.size()); // NOLINT
for (uint8_t dat : sam_result) {
@@ -87,7 +89,7 @@ bool PN532::powerdown() {
ESP_LOGE(TAG, "Error writing powerdown command to PN532");
return false;
}
std::vector<uint8_t> response;
PN532Frame response;
if (!this->read_response(PN532_COMMAND_POWERDOWN, response)) {
ESP_LOGE(TAG, "Error reading PN532 powerdown response");
return false;
@@ -105,8 +107,10 @@ void PN532::update() {
if (!updates_enabled_)
return;
#ifdef PN532_BINARY_SENSOR_COUNT
for (auto *obj : this->binary_sensors_)
obj->on_scan_end();
#endif
if (!this->write_command_({
PN532_COMMAND_INLISTPASSIVETARGET,
@@ -130,7 +134,7 @@ void PN532::loop() {
return;
bool success = false;
std::vector<uint8_t> read;
PN532Frame read;
if (ready == READY) {
success = this->read_response(PN532_COMMAND_INLISTPASSIVETARGET, read);
@@ -142,11 +146,13 @@ void PN532::loop() {
if (!success) {
// Something failed
#ifdef PN532_ON_TAG_REMOVED_TRIGGER_COUNT
if (!this->current_uid_.empty()) {
auto tag = make_unique<nfc::NfcTag>(this->current_uid_);
for (auto *trigger : this->triggers_ontagremoved_)
trigger->process(tag);
}
#endif
this->current_uid_ = {};
this->turn_off_rf_();
return;
@@ -155,11 +161,13 @@ void PN532::loop() {
uint8_t num_targets = read.empty() ? 0 : read[0];
if (num_targets != 1) {
// no tags found or too many
#ifdef PN532_ON_TAG_REMOVED_TRIGGER_COUNT
if (!this->current_uid_.empty()) {
auto tag = make_unique<nfc::NfcTag>(this->current_uid_);
for (auto *trigger : this->triggers_ontagremoved_)
trigger->process(tag);
}
#endif
this->current_uid_ = {};
this->turn_off_rf_();
return;
@@ -181,11 +189,13 @@ void PN532::loop() {
const uint8_t tag_type = tag_type_from_sel_res(sel_res);
bool report = true;
#ifdef PN532_BINARY_SENSOR_COUNT
for (auto *bin_sens : this->binary_sensors_) {
if (bin_sens->process(nfcid)) {
report = false;
}
}
#endif
if (nfcid.size() == this->current_uid_.size()) {
bool same_uid = true;
@@ -199,8 +209,10 @@ void PN532::loop() {
if (next_task_ == READ) {
auto tag = this->read_tag_(nfcid, tag_type);
#ifdef PN532_ON_TAG_TRIGGER_COUNT
for (auto *trigger : this->triggers_ontag_)
trigger->process(tag);
#endif
if (report) {
char uid_buf[nfc::FORMAT_UID_BUFFER_SIZE];
@@ -249,39 +261,42 @@ void PN532::loop() {
this->turn_off_rf_();
}
bool PN532::write_command_(const std::vector<uint8_t> &data) {
std::vector<uint8_t> write_data;
bool PN532::write_command_(const std::span<const uint8_t> data) {
if (data.size() > PN532_FRAME_MAX_DATA_SIZE) {
return false;
}
PN532Frame frame;
// Preamble
write_data.push_back(0x00);
frame.push_back(0x00);
// Start code
write_data.push_back(0x00);
write_data.push_back(0xFF);
frame.push_back(0x00);
frame.push_back(0xFF);
// Length of message, TFI + data bytes
const uint8_t real_length = data.size() + 1;
// LEN
write_data.push_back(real_length);
frame.push_back(real_length);
// LCS (Length checksum)
write_data.push_back(~real_length + 1);
frame.push_back(~real_length + 1);
// TFI (Frame Identifier, 0xD4 means to PN532, 0xD5 means from PN532)
write_data.push_back(0xD4);
frame.push_back(0xD4);
// calculate checksum, TFI is part of checksum
uint8_t checksum = 0xD4;
// DATA
for (uint8_t dat : data) {
write_data.push_back(dat);
frame.push_back(dat);
checksum += dat;
}
// DCS (Data checksum)
write_data.push_back(~checksum + 1);
frame.push_back(~checksum + 1);
// Postamble
write_data.push_back(0x00);
frame.push_back(0x00);
this->write_data(write_data);
this->write_data(frame);
return this->read_ack_();
}
@@ -289,8 +304,8 @@ bool PN532::write_command_(const std::vector<uint8_t> &data) {
bool PN532::read_ack_() {
ESP_LOGV(TAG, "Reading ACK");
std::vector<uint8_t> data;
if (!this->read_data(data, 6)) {
PN532Frame data;
if (!this->read_data(data, 6) || data.size() < 7) {
return false;
}
@@ -303,13 +318,15 @@ bool PN532::read_ack_() {
}
void PN532::send_ack_() {
static constexpr std::array<uint8_t, 6> ACK_FRAME = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00};
ESP_LOGV(TAG, "Sending ACK for abort");
this->write_data({0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00});
this->write_data(ACK_FRAME);
delay(10);
}
void PN532::send_nack_() {
static constexpr std::array<uint8_t, 6> NACK_FRAME = {0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00};
ESP_LOGV(TAG, "Sending NACK for retransmit");
this->write_data({0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00});
this->write_data(NACK_FRAME);
delay(10);
}
@@ -425,7 +442,7 @@ bool PN532::write_tag_(nfc::NfcTagUid &uid, const uint8_t tag_type, nfc::NdefMes
return false;
}
bool PN532::in_data_exchange_(const std::vector<uint8_t> &command, std::vector<uint8_t> &response) {
bool PN532::in_data_exchange_(const std::span<const uint8_t> command, PN532Frame &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)) {
@@ -439,7 +456,25 @@ bool PN532::in_data_exchange_(const std::vector<uint8_t> &command, std::vector<u
ESP_LOGV(TAG, "InDataExchange failed, status 0x%02X", response[0]);
return false;
}
response.erase(response.begin());
std::copy(response.begin() + 1, response.end(), response.begin());
response.resize(response.size() - 1);
return true;
}
bool PN532::mifare_read_(uint8_t address, MifareReadData &data) {
PN532Frame response;
if (!this->in_data_exchange_(
{
PN532_COMMAND_INDATAEXCHANGE,
0x01, // One card
nfc::MIFARE_CMD_READ,
address,
},
response) ||
response.size() != data.size()) {
return false;
}
std::copy(response.begin(), response.end(), data.begin());
return true;
}
@@ -458,9 +493,11 @@ void PN532::dump_config() {
LOG_UPDATE_INTERVAL(this);
#ifdef PN532_BINARY_SENSOR_COUNT
for (auto *child : this->binary_sensors_) {
LOG_BINARY_SENSOR(" ", "Tag", child);
}
#endif
}
bool PN532BinarySensor::process(const nfc::NfcTagUid &data) {
+56 -16
View File
@@ -1,13 +1,16 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/nfc/nfc_tag.h"
#include "esphome/components/nfc/nfc.h"
#include "esphome/components/nfc/automation.h"
#include <array>
#include <cinttypes>
#include <vector>
#include <initializer_list>
#include <span>
namespace esphome::pn532 {
@@ -40,6 +43,21 @@ inline uint8_t tag_type_from_sel_res(uint8_t sel_res) {
return nfc::TAG_TYPE_UNKNOWN;
}
/// Most data bytes a normal information frame carries: LEN is one byte and counts the TFI byte plus the data
static constexpr size_t PN532_FRAME_MAX_DATA_SIZE = 254;
/// A received frame at its longest: the status byte the I2C bus prepends, preamble, start code (2), LEN, LCS, TFI,
/// the data, DCS and postamble
static constexpr size_t PN532_FRAME_MAX_SIZE = 1 + 6 + PN532_FRAME_MAX_DATA_SIZE + 2;
/// Holds one frame in either direction so bus traffic never allocates
using PN532Frame = StaticVector<uint8_t, PN532_FRAME_MAX_SIZE>;
/// Holds pages 3 to 6 (16 bytes) plus an NDEF message of up to 255 bytes and its TLV header, rounded up to whole reads
using UltralightReadBuffer = StaticVector<uint8_t, 272>;
/// Longest NDEF message accepted from a MIFARE Classic tag (the capacity of a 4K tag)
static constexpr uint32_t MIFARE_CLASSIC_MAX_NDEF_SIZE = 3440;
/// A MIFARE READ answers with 16 bytes: one Classic block or four Ultralight pages
static constexpr size_t MIFARE_READ_SIZE = 16;
using MifareReadData = std::array<uint8_t, MIFARE_READ_SIZE>;
class PN532BinarySensor;
class PN532 : public PollingComponent {
@@ -53,9 +71,15 @@ class PN532 : public PollingComponent {
void loop() override;
void on_powerdown() override { powerdown(); }
#ifdef PN532_BINARY_SENSOR_COUNT
void register_tag(PN532BinarySensor *tag) { this->binary_sensors_.push_back(tag); }
#endif
#ifdef PN532_ON_TAG_TRIGGER_COUNT
void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); }
#endif
#ifdef PN532_ON_TAG_REMOVED_TRIGGER_COUNT
void register_ontagremoved_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontagremoved_.push_back(trig); }
#endif
template<typename F> void add_on_finished_write_callback(F &&callback) {
this->on_finished_write_callback_.add(std::forward<F>(callback));
@@ -71,16 +95,21 @@ class PN532 : public PollingComponent {
protected:
void turn_off_rf_();
bool write_command_(const std::vector<uint8_t> &data);
bool write_command_(std::span<const uint8_t> data);
bool write_command_(std::initializer_list<uint8_t> data) {
return this->write_command_(std::span<const uint8_t>(data.begin(), data.size()));
}
bool read_ack_();
void send_ack_();
void send_nack_();
enum PN532ReadReady read_ready_(bool block);
virtual bool is_read_ready() = 0;
virtual bool write_data(const std::vector<uint8_t> &data) = 0;
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;
virtual bool write_data(std::span<const uint8_t> data) = 0;
/// Reads `len` frame bytes into `data` behind a leading status byte, so every bus presents the I2C layout
virtual bool read_data(PN532Frame &data, size_t len) = 0;
/// Reads the response to `command`; on success `data` holds only the bytes that follow the response code
virtual bool read_response(uint8_t command, PN532Frame &data) = 0;
std::unique_ptr<nfc::NfcTag> read_tag_(nfc::NfcTagUid &uid, uint8_t tag_type);
@@ -89,23 +118,28 @@ class PN532 : public PollingComponent {
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);
bool in_data_exchange_(std::span<const uint8_t> command, PN532Frame &response);
bool in_data_exchange_(std::initializer_list<uint8_t> command, PN532Frame &response) {
return this->in_data_exchange_(std::span<const uint8_t>(command.begin(), command.size()), response);
}
/// Sends MIFARE READ for `address` and returns the 16 bytes the tag answers with
bool mifare_read_(uint8_t address, MifareReadData &data);
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);
bool write_mifare_classic_block_(uint8_t block_num, const uint8_t *data, size_t len);
bool read_mifare_classic_block_(uint8_t block_num, MifareReadData &data);
bool write_mifare_classic_block_(uint8_t block_num, std::span<const uint8_t> data);
bool auth_mifare_classic_block_(nfc::NfcTagUid &uid, uint8_t block_num, uint8_t key_num, const uint8_t *key);
bool format_mifare_classic_mifare_(nfc::NfcTagUid &uid);
bool format_mifare_classic_ndef_(nfc::NfcTagUid &uid);
bool write_mifare_classic_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message);
std::unique_ptr<nfc::NfcTag> read_mifare_ultralight_tag_(nfc::NfcTagUid &uid);
bool read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data);
bool is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6);
bool read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, UltralightReadBuffer &data);
bool is_mifare_ultralight_formatted_(std::span<const uint8_t> page_3_to_6);
uint16_t read_mifare_ultralight_capacity_();
bool find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
bool find_mifare_ultralight_ndef_(std::span<const uint8_t> page_3_to_6, uint8_t &message_length,
uint8_t &message_start_index);
bool write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len);
bool write_mifare_ultralight_page_(uint8_t page_num, std::span<const uint8_t> write_data);
bool write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message);
bool clean_mifare_ultralight_();
@@ -122,10 +156,16 @@ class PN532 : public PollingComponent {
};
// 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_;
LazyCallbackManager<void()> on_finished_write_callback_;
#ifdef PN532_BINARY_SENSOR_COUNT
StaticVector<PN532BinarySensor *, PN532_BINARY_SENSOR_COUNT> binary_sensors_;
#endif
#ifdef PN532_ON_TAG_TRIGGER_COUNT
StaticVector<nfc::NfcOnTagTrigger *, PN532_ON_TAG_TRIGGER_COUNT> triggers_ontag_;
#endif
#ifdef PN532_ON_TAG_REMOVED_TRIGGER_COUNT
StaticVector<nfc::NfcOnTagTrigger *, PN532_ON_TAG_REMOVED_TRIGGER_COUNT> triggers_ontagremoved_;
#endif
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
@@ -1,4 +1,6 @@
#include <algorithm>
#include <array>
#include <cinttypes>
#include <memory>
#include "pn532.h"
@@ -13,10 +15,10 @@ std::unique_ptr<nfc::NfcTag> PN532::read_mifare_classic_tag_(nfc::NfcTagUid &uid
uint8_t message_start_index = 0;
uint32_t message_length = 0;
MifareReadData block_data;
if (this->auth_mifare_classic_block_(uid, current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY)) {
std::vector<uint8_t> data;
if (this->read_mifare_classic_block_(current_block, data)) {
if (!nfc::decode_mifare_classic_tlv(data, message_length, message_start_index)) {
if (this->read_mifare_classic_block_(current_block, block_data)) {
if (!nfc::decode_mifare_classic_tlv(block_data, message_length, message_start_index)) {
return make_unique<nfc::NfcTag>(uid, nfc::ERROR);
}
} else {
@@ -27,52 +29,50 @@ std::unique_ptr<nfc::NfcTag> PN532::read_mifare_classic_tag_(nfc::NfcTagUid &uid
ESP_LOGV(TAG, "Tag is not NDEF formatted");
return make_unique<nfc::NfcTag>(uid, nfc::MIFARE_CLASSIC);
}
if (message_length > MIFARE_CLASSIC_MAX_NDEF_SIZE) {
ESP_LOGE(TAG, "NDEF message too long: %" PRIu32 " bytes", message_length);
return make_unique<nfc::NfcTag>(uid, nfc::MIFARE_CLASSIC);
}
uint32_t index = 0;
uint32_t buffer_size = nfc::get_mifare_classic_buffer_size(message_length);
std::vector<uint8_t> buffer;
const uint32_t buffer_size = nfc::get_mifare_classic_buffer_size(message_length);
FixedVector<uint8_t> buffer;
if (!buffer.try_init(buffer_size)) {
ESP_LOGE(TAG, "Out of memory reading NDEF message of %" PRIu32 " bytes", buffer_size);
return make_unique<nfc::NfcTag>(uid, nfc::MIFARE_CLASSIC);
}
while (index < buffer_size) {
while (buffer.size() < buffer_size) {
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)) {
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());
for (const uint8_t byte : block_data) {
buffer.push_back(byte);
}
index += nfc::MIFARE_CLASSIC_BLOCK_SIZE;
current_block++;
if (nfc::mifare_classic_is_trailer_block(current_block)) {
current_block++;
}
}
if (buffer.begin() + message_start_index < buffer.end()) {
buffer.erase(buffer.begin(), buffer.begin() + message_start_index);
} else {
if (message_start_index >= buffer.size()) {
return make_unique<nfc::NfcTag>(uid, nfc::MIFARE_CLASSIC);
}
return make_unique<nfc::NfcTag>(uid, nfc::MIFARE_CLASSIC, buffer);
return make_unique<nfc::NfcTag>(
uid, nfc::MIFARE_CLASSIC,
make_unique<nfc::NdefMessage>(std::span<const uint8_t>(buffer).subspan(message_start_index)));
}
bool PN532::read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data) {
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) {
bool PN532::read_mifare_classic_block_(uint8_t block_num, MifareReadData &data) {
if (!this->mifare_read_(block_num, data)) {
return false;
}
@@ -82,20 +82,25 @@ bool PN532::read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &
}
bool PN532::auth_mifare_classic_block_(nfc::NfcTagUid &uid, uint8_t block_num, uint8_t key_num, const uint8_t *key) {
std::vector<uint8_t> data({
// InDataExchange, Tg, key slot, block, key (6), UID (4)
StaticVector<uint8_t, 14> data = {
PN532_COMMAND_INDATAEXCHANGE,
0x01, // One card
key_num, // Mifare Key slot
block_num, // Block number
});
data.insert(data.end(), key, key + 6);
};
for (size_t i = 0; i < 6; i++) {
data.push_back(key[i]);
}
// 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());
for (size_t i = uid.size() - 4; i < uid.size(); i++) {
data.push_back(uid[i]);
}
std::vector<uint8_t> response;
PN532Frame response;
if (!this->in_data_exchange_(data, response)) {
ESP_LOGE(TAG, "Authentication failed - Block 0x%02x", block_num);
return false;
@@ -117,20 +122,20 @@ bool PN532::format_mifare_classic_mifare_(nfc::NfcTagUid &uid) {
continue;
}
if (block != 0) {
if (!this->write_mifare_classic_block_(block, BLANK_BUFFER.data(), BLANK_BUFFER.size())) {
if (!this->write_mifare_classic_block_(block, BLANK_BUFFER)) {
ESP_LOGE(TAG, "Unable to write block %d", block);
error = true;
}
}
if (!this->write_mifare_classic_block_(block + 1, BLANK_BUFFER.data(), BLANK_BUFFER.size())) {
if (!this->write_mifare_classic_block_(block + 1, BLANK_BUFFER)) {
ESP_LOGE(TAG, "Unable to write block %d", block + 1);
error = true;
}
if (!this->write_mifare_classic_block_(block + 2, BLANK_BUFFER.data(), BLANK_BUFFER.size())) {
if (!this->write_mifare_classic_block_(block + 2, BLANK_BUFFER)) {
ESP_LOGE(TAG, "Unable to write block %d", block + 2);
error = true;
}
if (!this->write_mifare_classic_block_(block + 3, TRAILER_BUFFER.data(), TRAILER_BUFFER.size())) {
if (!this->write_mifare_classic_block_(block + 3, TRAILER_BUFFER)) {
ESP_LOGE(TAG, "Unable to write block %d", block + 3);
error = true;
}
@@ -157,11 +162,11 @@ bool PN532::format_mifare_classic_ndef_(nfc::NfcTagUid &uid) {
ESP_LOGE(TAG, "Unable to authenticate block 0 for formatting!");
return false;
}
if (!this->write_mifare_classic_block_(1, BLOCK_1_DATA.data(), BLOCK_1_DATA.size()))
if (!this->write_mifare_classic_block_(1, BLOCK_1_DATA))
return false;
if (!this->write_mifare_classic_block_(2, BLOCK_2_DATA.data(), BLOCK_2_DATA.size()))
if (!this->write_mifare_classic_block_(2, BLOCK_2_DATA))
return false;
if (!this->write_mifare_classic_block_(3, BLOCK_3_TRAILER.data(), BLOCK_3_TRAILER.size()))
if (!this->write_mifare_classic_block_(3, BLOCK_3_TRAILER))
return false;
ESP_LOGD(TAG, "Sector 0 formatted to NDEF");
@@ -173,25 +178,25 @@ bool PN532::format_mifare_classic_ndef_(nfc::NfcTagUid &uid) {
return false;
}
if (block == 4) {
if (!this->write_mifare_classic_block_(block, EMPTY_NDEF_MESSAGE.data(), EMPTY_NDEF_MESSAGE.size())) {
if (!this->write_mifare_classic_block_(block, EMPTY_NDEF_MESSAGE)) {
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())) {
if (!this->write_mifare_classic_block_(block, BLANK_BLOCK)) {
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())) {
if (!this->write_mifare_classic_block_(block + 1, BLANK_BLOCK)) {
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())) {
if (!this->write_mifare_classic_block_(block + 2, BLANK_BLOCK)) {
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())) {
if (!this->write_mifare_classic_block_(block + 3, NDEF_TRAILER)) {
ESP_LOGE(TAG, "Unable to write trailer block %d", block + 3);
error = true;
}
@@ -199,16 +204,18 @@ bool PN532::format_mifare_classic_ndef_(nfc::NfcTagUid &uid) {
return !error;
}
bool PN532::write_mifare_classic_block_(uint8_t block_num, const uint8_t *data, size_t len) {
std::vector<uint8_t> cmd({
bool PN532::write_mifare_classic_block_(uint8_t block_num, const std::span<const uint8_t> data) {
StaticVector<uint8_t, 4 + nfc::MIFARE_CLASSIC_BLOCK_SIZE> cmd = {
PN532_COMMAND_INDATAEXCHANGE,
0x01, // One card
nfc::MIFARE_CMD_WRITE,
block_num,
});
cmd.insert(cmd.end(), data, data + len);
};
for (const uint8_t byte : data) {
cmd.push_back(byte);
}
std::vector<uint8_t> response;
PN532Frame response;
if (!this->in_data_exchange_(cmd, response)) {
ESP_LOGE(TAG, "Error writing block %d", block_num);
return false;
@@ -218,22 +225,10 @@ bool PN532::write_mifare_classic_block_(uint8_t block_num, const uint8_t *data,
}
bool PN532::write_mifare_classic_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message) {
auto encoded = message->encode();
uint32_t message_length = encoded.size();
uint32_t buffer_length = nfc::get_mifare_classic_buffer_size(message_length);
encoded.insert(encoded.begin(), 0x03);
if (message_length < 255) {
encoded.insert(encoded.begin() + 1, message_length);
} else {
encoded.insert(encoded.begin() + 1, 0xFF);
encoded.insert(encoded.begin() + 2, (message_length >> 8) & 0xFF);
encoded.insert(encoded.begin() + 3, message_length & 0xFF);
}
encoded.push_back(0xFE);
encoded.resize(buffer_length, 0);
const auto encoded = message->encode();
const uint32_t buffer_length = nfc::get_mifare_classic_buffer_size(encoded.size());
FixedVector<uint8_t> buffer;
nfc::fill_ndef_tlv(encoded, buffer_length, buffer);
uint32_t index = 0;
uint8_t current_block = 4;
@@ -245,7 +240,8 @@ bool PN532::write_mifare_classic_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *mes
}
}
if (!this->write_mifare_classic_block_(current_block, encoded.data() + index, nfc::MIFARE_CLASSIC_BLOCK_SIZE)) {
if (!this->write_mifare_classic_block_(current_block,
std::span<const uint8_t>(&buffer[index], nfc::MIFARE_CLASSIC_BLOCK_SIZE))) {
return false;
}
index += nfc::MIFARE_CLASSIC_BLOCK_SIZE;
@@ -10,7 +10,7 @@ namespace esphome::pn532 {
static const char *const TAG = "pn532.mifare_ultralight";
std::unique_ptr<nfc::NfcTag> PN532::read_mifare_ultralight_tag_(nfc::NfcTagUid &uid) {
std::vector<uint8_t> data;
UltralightReadBuffer data;
// pages 3 to 6 contain various info we are interested in -- do one read to grab it all
if (!this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE * nfc::MIFARE_ULTRALIGHT_READ_SIZE,
data)) {
@@ -41,31 +41,30 @@ std::unique_ptr<nfc::NfcTag> PN532::read_mifare_ultralight_tag_(nfc::NfcTagUid &
return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
}
}
// we need to trim off page 3 as well as any bytes ahead of message_start_index
data.erase(data.begin(), data.begin() + message_start_index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE);
// skip page 3 as well as any bytes ahead of message_start_index
const size_t skip = message_start_index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
if (skip >= data.size()) {
return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
}
return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2, data);
return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2,
make_unique<nfc::NdefMessage>(std::span<const uint8_t>(data).subspan(skip)));
}
bool PN532::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data) {
const uint8_t read_increment = nfc::MIFARE_ULTRALIGHT_READ_SIZE * nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
std::vector<uint8_t> response;
bool PN532::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, UltralightReadBuffer &data) {
MifareReadData chunk;
for (uint8_t i = 0; i * read_increment < num_bytes; i++) {
for (uint8_t i = 0; i * MIFARE_READ_SIZE < num_bytes; i++) {
// 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) {
if (!this->mifare_read_(uint8_t(i * nfc::MIFARE_ULTRALIGHT_READ_SIZE + start_page), chunk)) {
return false;
}
const uint16_t remaining = num_bytes - i * read_increment;
data.insert(data.end(), response.begin(), response.begin() + std::min<uint16_t>(read_increment, remaining));
// keep only the bytes still wanted from this read
const uint16_t remaining = num_bytes - i * MIFARE_READ_SIZE;
const size_t count = std::min<size_t>(MIFARE_READ_SIZE, remaining);
for (const uint8_t byte : std::span<const uint8_t>(chunk).subspan(0, count)) {
data.push_back(byte);
}
}
char data_buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
@@ -74,7 +73,7 @@ bool PN532::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes
return true;
}
bool PN532::is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6) {
bool PN532::is_mifare_ultralight_formatted_(const std::span<const uint8_t> page_3_to_6) {
const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector
return (page_3_to_6.size() > p4_offset + 3) &&
@@ -83,7 +82,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;
UltralightReadBuffer 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;
@@ -91,7 +90,7 @@ uint16_t PN532::read_mifare_ultralight_capacity_() {
return 0;
}
bool PN532::find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
bool PN532::find_mifare_ultralight_ndef_(const std::span<const uint8_t> page_3_to_6, uint8_t &message_length,
uint8_t &message_start_index) {
const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector
@@ -114,33 +113,23 @@ bool PN532::find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6
bool PN532::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message) {
uint32_t capacity = this->read_mifare_ultralight_capacity_();
auto encoded = message->encode();
uint32_t message_length = encoded.size();
uint32_t buffer_length = nfc::get_mifare_ultralight_buffer_size(message_length);
const auto encoded = message->encode();
const uint32_t buffer_length = nfc::get_mifare_ultralight_buffer_size(encoded.size());
if (buffer_length > capacity) {
ESP_LOGE(TAG, "Message length exceeds tag capacity %" PRIu32 " > %" PRIu32, buffer_length, capacity);
return false;
}
encoded.insert(encoded.begin(), 0x03);
if (message_length < 255) {
encoded.insert(encoded.begin() + 1, message_length);
} else {
encoded.insert(encoded.begin() + 1, 0xFF);
encoded.insert(encoded.begin() + 2, (message_length >> 8) & 0xFF);
encoded.insert(encoded.begin() + 3, message_length & 0xFF);
}
encoded.push_back(0xFE);
encoded.resize(buffer_length, 0);
FixedVector<uint8_t> buffer;
nfc::fill_ndef_tlv(encoded, buffer_length, buffer);
uint32_t index = 0;
uint8_t current_page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE;
while (index < buffer_length) {
if (!this->write_mifare_ultralight_page_(current_page, encoded.data() + index, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE)) {
if (!this->write_mifare_ultralight_page_(
current_page, std::span<const uint8_t>(&buffer[index], nfc::MIFARE_ULTRALIGHT_PAGE_SIZE))) {
return false;
}
index += nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
@@ -156,23 +145,25 @@ bool PN532::clean_mifare_ultralight_() {
static constexpr std::array<uint8_t, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE> BLANK_DATA = {0x00, 0x00, 0x00, 0x00};
for (int i = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; i < pages; i++) {
if (!this->write_mifare_ultralight_page_(i, BLANK_DATA.data(), BLANK_DATA.size())) {
if (!this->write_mifare_ultralight_page_(i, BLANK_DATA)) {
return false;
}
}
return true;
}
bool PN532::write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len) {
std::vector<uint8_t> cmd({
bool PN532::write_mifare_ultralight_page_(uint8_t page_num, const std::span<const uint8_t> write_data) {
StaticVector<uint8_t, 4 + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE> cmd = {
PN532_COMMAND_INDATAEXCHANGE,
0x01, // One card
nfc::MIFARE_CMD_WRITE_ULTRALIGHT,
page_num,
});
cmd.insert(cmd.end(), write_data, write_data + len);
};
for (const uint8_t byte : write_data) {
cmd.push_back(byte);
}
std::vector<uint8_t> response;
PN532Frame response;
if (!this->in_data_exchange_(cmd, response)) {
ESP_LOGE(TAG, "Error writing page %u", page_num);
return false;
+19 -14
View File
@@ -2,6 +2,8 @@
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include <algorithm>
// Based on:
// - https://cdn-shop.adafruit.com/datasheets/PN532C106_Application+Note_v1.2.pdf
// - https://www.nxp.com/docs/en/nxp/application-notes/AN133910.pdf
@@ -20,11 +22,14 @@ bool PN532I2C::is_read_ready() {
return status & 0x01;
}
bool PN532I2C::write_data(const std::vector<uint8_t> &data) {
bool PN532I2C::write_data(const std::span<const uint8_t> data) {
return this->write(data.data(), data.size()) == i2c::ERROR_OK;
}
bool PN532I2C::read_data(std::vector<uint8_t> &data, uint8_t len) {
bool PN532I2C::read_data(pn532::PN532Frame &data, size_t len) {
if (len + 1 > pn532::PN532_FRAME_MAX_SIZE) {
return false;
}
delay(1);
if (this->read_ready_(true) != pn532::PN532ReadReady::READY) {
@@ -36,7 +41,7 @@ bool PN532I2C::read_data(std::vector<uint8_t> &data, uint8_t len) {
return this->read_bytes_raw(data.data(), len + 1);
}
bool PN532I2C::read_response(uint8_t command, std::vector<uint8_t> &data) {
bool PN532I2C::read_response(uint8_t command, pn532::PN532Frame &data) {
ESP_LOGV(TAG, "Reading response");
uint8_t len = this->read_response_length_();
if (len == 0) {
@@ -64,33 +69,33 @@ bool PN532I2C::read_response(uint8_t command, std::vector<uint8_t> &data) {
return false;
}
data.erase(data.begin(), data.begin() + 6); // Remove headers
// frame: status, preamble, start code (2), LEN, LCS, TFI, command response code, data, DCS, postamble
constexpr size_t tfi_offset = 6;
uint8_t checksum = 0;
for (int i = 0; i < len + 1; i++) {
uint8_t dat = data[i];
checksum += dat;
for (size_t i = 0; i < len + 1U; i++) {
checksum += data[tfi_offset + i];
}
checksum = ~checksum + 1;
if (data[len + 1] != checksum) {
ESP_LOGV(TAG, "read data invalid checksum! %02X != %02X", data[len + 1], checksum);
if (data[tfi_offset + len + 1] != checksum) {
ESP_LOGV(TAG, "read data invalid checksum! %02X != %02X", data[tfi_offset + len + 1], checksum);
return false;
}
if (data[len + 2] != 0x00) {
if (data[tfi_offset + len + 2] != 0x00) {
ESP_LOGV(TAG, "read data invalid postamble!");
return false;
}
data.erase(data.begin(), data.begin() + 2); // Remove TFI and command code
data.erase(data.end() - 2, data.end()); // Remove checksum and postamble
// keep only the data bytes that follow the command response code
std::copy(data.begin() + tfi_offset + 2, data.begin() + tfi_offset + len + 1, data.begin());
data.resize(len - 1);
return true;
}
uint8_t PN532I2C::read_response_length_() {
std::vector<uint8_t> data;
pn532::PN532Frame data;
if (!this->read_data(data, 6)) {
return 0;
}
+4 -4
View File
@@ -4,7 +4,7 @@
#include "esphome/components/pn532/pn532.h"
#include "esphome/components/i2c/i2c.h"
#include <vector>
#include <span>
namespace esphome::pn532_i2c {
@@ -14,9 +14,9 @@ class PN532I2C final : public pn532::PN532, public i2c::I2CDevice {
protected:
bool is_read_ready() override;
bool write_data(const std::vector<uint8_t> &data) override;
bool read_data(std::vector<uint8_t> &data, uint8_t len) override;
bool read_response(uint8_t command, std::vector<uint8_t> &data) override;
bool write_data(std::span<const uint8_t> data) override;
bool read_data(pn532::PN532Frame &data, size_t len) override;
bool read_response(uint8_t command, pn532::PN532Frame &data) override;
uint8_t read_response_length_();
};
+13 -10
View File
@@ -2,6 +2,8 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <array>
// Based on:
// - https://cdn-shop.adafruit.com/datasheets/PN532C106_Application+Note_v1.2.pdf
// - https://www.nxp.com/docs/en/nxp/application-notes/AN133910.pdf
@@ -31,7 +33,7 @@ bool PN532Spi::is_read_ready() {
return ready;
}
bool PN532Spi::write_data(const std::vector<uint8_t> &data) {
bool PN532Spi::write_data(const std::span<const uint8_t> data) {
this->enable();
delay(2);
// First byte, communication mode: Write data
@@ -46,8 +48,8 @@ bool PN532Spi::write_data(const std::vector<uint8_t> &data) {
return true;
}
bool PN532Spi::read_data(std::vector<uint8_t> &data, uint8_t len) {
if (this->read_ready_(true) != pn532::PN532ReadReady::READY) {
bool PN532Spi::read_data(pn532::PN532Frame &data, size_t len) {
if (len + 1 > pn532::PN532_FRAME_MAX_SIZE || this->read_ready_(true) != pn532::PN532ReadReady::READY) {
return false;
}
@@ -58,10 +60,11 @@ bool PN532Spi::read_data(std::vector<uint8_t> &data, uint8_t len) {
ESP_LOGV(TAG, "Reading data");
data.resize(len);
this->read_array(data.data(), len);
// lead with a status byte so callers see the same layout as on the I2C bus
data.resize(len + 1);
data[0] = 0x01;
this->read_array(data.data() + 1, len);
this->disable();
data.insert(data.begin(), 0x01);
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(PN532_MAX_LOG_BYTES)];
#endif
@@ -69,7 +72,7 @@ bool PN532Spi::read_data(std::vector<uint8_t> &data, uint8_t len) {
return true;
}
bool PN532Spi::read_response(uint8_t command, std::vector<uint8_t> &data) {
bool PN532Spi::read_response(uint8_t command, pn532::PN532Frame &data) {
ESP_LOGV(TAG, "Reading response");
if (this->read_ready_(true) != pn532::PN532ReadReady::READY) {
@@ -80,8 +83,8 @@ bool PN532Spi::read_response(uint8_t command, std::vector<uint8_t> &data) {
delay(2);
this->write_byte(0x03);
std::vector<uint8_t> header(7);
this->read_array(header.data(), 7);
std::array<uint8_t, 7> header;
this->read_array(header.data(), header.size());
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(PN532_MAX_LOG_BYTES)];
@@ -141,7 +144,7 @@ bool PN532Spi::read_response(uint8_t command, std::vector<uint8_t> &data) {
return false;
}
data.erase(data.end() - 2, data.end()); // Remove checksum and postamble
data.resize(len - 1); // Remove checksum and postamble
return true;
}
+4 -4
View File
@@ -4,7 +4,7 @@
#include "esphome/components/pn532/pn532.h"
#include "esphome/components/spi/spi.h"
#include <vector>
#include <span>
namespace esphome::pn532_spi {
@@ -18,9 +18,9 @@ class PN532Spi final : public pn532::PN532,
protected:
bool is_read_ready() override;
bool write_data(const std::vector<uint8_t> &data) override;
bool read_data(std::vector<uint8_t> &data, uint8_t len) override;
bool read_response(uint8_t command, std::vector<uint8_t> &data) override;
bool write_data(std::span<const uint8_t> data) override;
bool read_data(pn532::PN532Frame &data, size_t len) override;
bool read_response(uint8_t command, pn532::PN532Frame &data) override;
};
} // namespace esphome::pn532_spi
+3
View File
@@ -139,6 +139,9 @@
#define MICRONOVA_LISTENER_COUNT 1
#define USE_MICRONOVA_WRITER
#define MK2PVROUTER_LISTENER_COUNT 1
#define PN532_BINARY_SENSOR_COUNT 1
#define PN532_ON_TAG_REMOVED_TRIGGER_COUNT 1
#define PN532_ON_TAG_TRIGGER_COUNT 1
#define REMOTE_BASE_DUMPER_COUNT 1
#define REMOTE_BASE_LISTENER_COUNT 1
#define USE_REMOTE_PROTOCOL_ABBWELCOME
+3
View File
@@ -276,6 +276,9 @@ template<typename T, size_t N> class StaticVector {
// Clear all elements
void clear() { count_ = 0; }
// Set the element count, capped at N. Elements are neither initialized when growing nor destroyed when
// shrinking; release owning elements before shrinking past them.
void resize(size_t n) { count_ = n < N ? n : N; }
// Assign from iterator range
template<typename InputIt> void assign(InputIt first, InputIt last) {
+59 -15
View File
@@ -1,6 +1,8 @@
#include <gtest/gtest.h>
#include <array>
#include <deque>
#include <span>
#include "esphome/components/pn532/pn532.h"
@@ -14,6 +16,7 @@ class FakePN532 : public PN532 {
using PN532::auth_mifare_classic_block_;
using PN532::read_mifare_ultralight_bytes_;
using PN532::read_mifare_classic_block_;
using PN532::write_command_;
using PN532::write_mifare_classic_block_;
using PN532::write_mifare_ultralight_page_;
@@ -22,24 +25,26 @@ class FakePN532 : public PN532 {
protected:
bool is_read_ready() override { return true; }
bool write_data(const std::vector<uint8_t> &data) override {
this->written.push_back(data);
bool write_data(std::span<const uint8_t> data) override {
this->written.emplace_back(data.begin(), data.end());
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 {
bool read_data(PN532Frame &data, size_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 {
bool read_response(uint8_t command, PN532Frame &data) override {
if (this->responses.empty())
return false;
data = this->responses.front();
data.assign(this->responses.front().begin(), this->responses.front().end());
this->responses.pop_front();
return true;
}
};
std::vector<uint8_t> bytes_of(std::span<const uint8_t> bytes) { return {bytes.begin(), bytes.end()}; }
// 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
@@ -58,40 +63,79 @@ TEST(PN532TagType, FromSelRes) {
EXPECT_EQ(tag_type_from_sel_res(0x40), nfc::TAG_TYPE_UNKNOWN);
}
// The frame wraps the command in preamble, start code, LEN, LCS, TFI, DCS and postamble (UM0701-02, 6.2.1.1).
TEST(PN532Frame, WrapsCommand) {
FakePN532 pn532;
ASSERT_TRUE(pn532.write_command_({0x4A, 0x01, 0x00}));
ASSERT_EQ(pn532.written.size(), 1u);
EXPECT_EQ(pn532.written[0], (std::vector<uint8_t>{0x00, 0x00, 0xFF, 0x04, 0xFC, 0xD4, 0x4A, 0x01, 0x00, 0xE1, 0x00}));
}
// A command that cannot fit a normal information frame is refused rather than truncated.
TEST(PN532Frame, RejectsOversizedCommand) {
FakePN532 pn532;
std::array<uint8_t, PN532_FRAME_MAX_DATA_SIZE + 1> too_long{};
EXPECT_FALSE(pn532.write_command_(too_long));
EXPECT_TRUE(pn532.written.empty());
EXPECT_TRUE(pn532.write_command_(std::span<const uint8_t>(too_long).first(PN532_FRAME_MAX_DATA_SIZE)));
ASSERT_EQ(pn532.written.size(), 1u);
EXPECT_EQ(pn532.written[0].size(), PN532_FRAME_MAX_DATA_SIZE + 8);
}
// 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)));
EXPECT_FALSE(pn532.write_mifare_classic_block_(4, block));
pn532.responses.push_back({0x00});
EXPECT_TRUE(pn532.write_mifare_classic_block_(4, block, sizeof(block)));
EXPECT_TRUE(pn532.write_mifare_classic_block_(4, 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)));
EXPECT_FALSE(pn532.write_mifare_ultralight_page_(4, page));
pn532.responses.push_back({0x00});
EXPECT_TRUE(pn532.write_mifare_ultralight_page_(4, page, sizeof(page)));
EXPECT_TRUE(pn532.write_mifare_ultralight_page_(4, page));
}
TEST(PN532Mifare, ClassicReadRejectsBadResponses) {
FakePN532 pn532;
std::vector<uint8_t> data;
MifareReadData 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));
EXPECT_EQ(bytes_of(data), std::vector<uint8_t>(16, 0xAB));
}
// The NDEF TLV is type 0x03, a one-byte length below 255 (three bytes otherwise), the message, terminator 0xFE,
// then zero padding out to the requested length.
TEST(PN532Ndef, FillsTlv) {
FixedVector<uint8_t> buffer;
const std::array<uint8_t, 3> message = {0xD1, 0x01, 0x02};
nfc::fill_ndef_tlv(message, 8, buffer);
EXPECT_EQ(bytes_of(std::span<const uint8_t>(buffer)),
(std::vector<uint8_t>{0x03, 0x03, 0xD1, 0x01, 0x02, 0xFE, 0x00, 0x00}));
std::vector<uint8_t> long_message(300, 0xAA);
nfc::fill_ndef_tlv(long_message, 320, buffer);
ASSERT_EQ(buffer.size(), 320u);
EXPECT_EQ(buffer[0], 0x03);
EXPECT_EQ(buffer[1], 0xFF);
EXPECT_EQ(buffer[2], 0x01); // 300 = 0x012C
EXPECT_EQ(buffer[3], 0x2C);
EXPECT_EQ(buffer[4], 0xAA);
EXPECT_EQ(buffer[303], 0xAA);
EXPECT_EQ(buffer[304], 0xFE);
EXPECT_EQ(buffer[319], 0x00);
}
// Authentication carries exactly 4 UID bytes: the last 4 of a 7-byte UID.
@@ -119,7 +163,7 @@ TEST(PN532Mifare, UltralightReadTrimsLastChunk) {
pn532.responses.push_back(first);
pn532.responses.push_back(second);
std::vector<uint8_t> data;
UltralightReadBuffer data;
ASSERT_TRUE(pn532.read_mifare_ultralight_bytes_(4, 20, data));
ASSERT_EQ(data.size(), 20u);
EXPECT_EQ(data[15], 15);
@@ -133,7 +177,7 @@ TEST(PN532Mifare, UltralightReadTrimsLastChunk) {
TEST(PN532Mifare, UltralightReadRejectsBadResponses) {
FakePN532 pn532;
std::vector<uint8_t> data;
UltralightReadBuffer data;
pn532.responses.push_back({0x00, 0x01, 0x02}); // short response
EXPECT_FALSE(pn532.read_mifare_ultralight_bytes_(4, 16, data));