mirror of
https://github.com/esphome/esphome.git
synced 2026-09-28 15:30:21 +00:00
[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:
co-authored by
Claude Fable 5.1
parent
b619df886a
commit
514ee178f9
@@ -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()) {
|
||||
|
||||
@@ -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_) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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_();
|
||||
};
|
||||
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user