ESPHome 2026.10.2
Loading...
Searching...
No Matches
pn532_mifare_ultralight.cpp
Go to the documentation of this file.
1#include <algorithm>
2#include <array>
3#include <memory>
4
5#include "pn532.h"
6#include "esphome/core/log.h"
7
8namespace esphome::pn532 {
9
10static const char *const TAG = "pn532.mifare_ultralight";
11
12std::unique_ptr<nfc::NfcTag> PN532::read_mifare_ultralight_tag_(nfc::NfcTagUid &uid) {
14 // pages 3 to 6 contain various info we are interested in -- do one read to grab it all
15 if (!this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE * nfc::MIFARE_ULTRALIGHT_READ_SIZE,
16 data)) {
17 return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
18 }
19
20 if (!this->is_mifare_ultralight_formatted_(data)) {
21 ESP_LOGW(TAG, "Not NDEF formatted");
22 return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
23 }
24
25 uint8_t message_length;
26 uint8_t message_start_index;
27 if (!this->find_mifare_ultralight_ndef_(data, message_length, message_start_index)) {
28 ESP_LOGW(TAG, "Couldn't find NDEF message");
29 return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
30 }
31 ESP_LOGVV(TAG, "NDEF message length: %u, start: %u", message_length, message_start_index);
32
33 if (message_length == 0) {
34 return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
35 }
36 // we already read pages 3-6 earlier -- pick up where we left off so we're not re-reading pages
37 const uint8_t read_length = message_length + message_start_index > 12 ? message_length + message_start_index - 12 : 0;
38 if (read_length) {
39 if (!read_mifare_ultralight_bytes_(nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE + 3, read_length, data)) {
40 ESP_LOGE(TAG, "Error reading tag data");
41 return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
42 }
43 }
44 // skip page 3 as well as any bytes ahead of message_start_index
45 const size_t skip = message_start_index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
46 if (skip >= data.size()) {
47 return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2);
48 }
49
50 return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2,
51 make_unique<nfc::NdefMessage>(std::span<const uint8_t>(data).subspan(skip)));
52}
53
54bool PN532::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, UltralightReadBuffer &data) {
55 MifareReadData chunk;
56
57 for (uint8_t i = 0; i * MIFARE_READ_SIZE < num_bytes; i++) {
58 // a READ returns 4 pages (16 bytes)
59 if (!this->mifare_read_(uint8_t(i * nfc::MIFARE_ULTRALIGHT_READ_SIZE + start_page), chunk)) {
60 return false;
61 }
62 // keep only the bytes still wanted from this read
63 const uint16_t remaining = num_bytes - i * MIFARE_READ_SIZE;
64 const size_t count = std::min<size_t>(MIFARE_READ_SIZE, remaining);
65 for (const uint8_t byte : std::span<const uint8_t>(chunk).subspan(0, count)) {
66 data.push_back(byte);
67 }
68 }
69
70 char data_buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
71 ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes_to(data_buf, data));
72
73 return true;
74}
75
76bool PN532::is_mifare_ultralight_formatted_(const std::span<const uint8_t> page_3_to_6) {
77 const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector
78
79 return (page_3_to_6.size() > p4_offset + 3) &&
80 ((page_3_to_6[p4_offset + 0] != 0xFF) || (page_3_to_6[p4_offset + 1] != 0xFF) ||
81 (page_3_to_6[p4_offset + 2] != 0xFF) || (page_3_to_6[p4_offset + 3] != 0xFF));
82}
83
86 if (this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE, data) && data.size() > 2) {
87 ESP_LOGV(TAG, "Tag capacity is %u bytes", data[2] * 8U);
88 return data[2] * 8U;
89 }
90 return 0;
91}
92
93bool PN532::find_mifare_ultralight_ndef_(const std::span<const uint8_t> page_3_to_6, uint8_t &message_length,
94 uint8_t &message_start_index) {
95 const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector
96
97 if (!(page_3_to_6.size() > p4_offset + 6)) {
98 return false;
99 }
100
101 if (page_3_to_6[p4_offset + 0] == 0x03) {
102 message_length = page_3_to_6[p4_offset + 1];
103 message_start_index = 2;
104 return true;
105 } else if (page_3_to_6[p4_offset + 5] == 0x03) {
106 message_length = page_3_to_6[p4_offset + 6];
107 message_start_index = 7;
108 return true;
109 }
110 return false;
111}
112
115
116 const auto encoded = message->encode();
117 const uint32_t buffer_length = nfc::get_mifare_ultralight_buffer_size(encoded.size());
118
119 if (buffer_length > capacity) {
120 ESP_LOGE(TAG, "Message length exceeds tag capacity %" PRIu32 " > %" PRIu32, buffer_length, capacity);
121 return false;
122 }
123
125 nfc::fill_ndef_tlv(encoded, buffer_length, buffer);
126
127 uint32_t index = 0;
128 uint8_t current_page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE;
129
130 while (index < buffer_length) {
132 current_page, std::span<const uint8_t>(&buffer[index], nfc::MIFARE_ULTRALIGHT_PAGE_SIZE))) {
133 return false;
134 }
135 index += nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
136 current_page++;
137 }
138 return true;
139}
140
143 uint8_t pages = (capacity / nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) + nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE;
144
145 static constexpr std::array<uint8_t, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE> BLANK_DATA = {0x00, 0x00, 0x00, 0x00};
146
147 for (int i = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; i < pages; i++) {
148 if (!this->write_mifare_ultralight_page_(i, BLANK_DATA)) {
149 return false;
150 }
151 }
152 return true;
153}
154
155bool PN532::write_mifare_ultralight_page_(uint8_t page_num, const std::span<const uint8_t> write_data) {
157 PN532_COMMAND_INDATAEXCHANGE,
158 0x01, // One card
159 nfc::MIFARE_CMD_WRITE_ULTRALIGHT,
160 page_num,
161 };
162 for (const uint8_t byte : write_data) {
163 cmd.push_back(byte);
164 }
165
166 PN532Frame response;
167 if (!this->in_data_exchange_(cmd, response)) {
168 ESP_LOGE(TAG, "Error writing page %u", page_num);
169 return false;
170 }
171
172 return true;
173}
174
175} // namespace esphome::pn532
Fixed-capacity vector - sized once through init() or try_init(); push_back never reallocates This avo...
Definition helpers.h:621
void push_back(const T &value)
Definition helpers.h:330
bool mifare_read_(uint8_t address, MifareReadData &data)
Sends MIFARE READ for address and returns the 16 bytes the tag answers with.
Definition pn532.cpp:466
bool read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, UltralightReadBuffer &data)
std::unique_ptr< nfc::NfcTag > read_mifare_ultralight_tag_(nfc::NfcTagUid &uid)
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, std::span< const uint8_t > write_data)
bool write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message)
virtual bool write_data(std::span< const uint8_t > data)=0
bool in_data_exchange_(std::span< const uint8_t > command, PN532Frame &response)
Sends an InDataExchange command and reads the response; returns false unless the status byte reports ...
Definition pn532.cpp:447
bool is_mifare_ultralight_formatted_(std::span< const uint8_t > page_3_to_6)
const LogString * message
Definition component.cpp:35
unsigned count
Definition gif.cpp:51
uint32_t buffer
Definition gif.cpp:50
char * format_bytes_to(char *buffer, std::span< const uint8_t > bytes)
Format bytes to buffer with ' ' separator (e.g., "04 11 22 33"). Returns buffer for inline use.
Definition nfc.cpp:14
uint32_t get_mifare_ultralight_buffer_size(uint32_t message_length)
Definition nfc.cpp:64
void fill_ndef_tlv(const std::span< const uint8_t > message, const uint32_t buffer_length, FixedVector< uint8_t > &buffer)
Fills buffer with the NDEF TLV (type, length, message, terminator) padded with zeros to buffer_length
Definition nfc.cpp:72
std::array< uint8_t, MIFARE_READ_SIZE > MifareReadData
Definition pn532.h:59
uint16_t uint16_t & capacity
Definition helpers.cpp:33
static void uint32_t