ESPHome 2026.3.0-dev
Loading...
Searching...
No Matches
pn532_mifare_ultralight.cpp
Go to the documentation of this file.
1#include <array>
2#include <memory>
3
4#include "pn532.h"
5#include "esphome/core/log.h"
6
7namespace esphome {
8namespace pn532 {
9
10static const char *const TAG = "pn532.mifare_ultralight";
11
12std::unique_ptr<nfc::NfcTag> PN532::read_mifare_ultralight_tag_(nfc::NfcTagUid &uid) {
13 std::vector<uint8_t> data;
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 // we need to trim off page 3 as well as any bytes ahead of message_start_index
45 data.erase(data.begin(), data.begin() + message_start_index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE);
46
47 return make_unique<nfc::NfcTag>(uid, nfc::NFC_FORUM_TYPE_2, data);
48}
49
50bool PN532::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data) {
51 const uint8_t read_increment = nfc::MIFARE_ULTRALIGHT_READ_SIZE * nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
52 std::vector<uint8_t> response;
53
54 for (uint8_t i = 0; i * read_increment < num_bytes; i++) {
55 if (!this->write_command_({
56 PN532_COMMAND_INDATAEXCHANGE,
57 0x01, // One card
58 nfc::MIFARE_CMD_READ,
59 uint8_t(i * nfc::MIFARE_ULTRALIGHT_READ_SIZE + start_page),
60 })) {
61 return false;
62 }
63
64 if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, response) || response[0] != 0x00) {
65 return false;
66 }
67 uint16_t bytes_offset = (i + 1) * read_increment;
68 auto pages_in_end_itr = bytes_offset <= num_bytes ? response.end() : response.end() - (bytes_offset - num_bytes);
69
70 if ((pages_in_end_itr > response.begin()) && (pages_in_end_itr <= response.end())) {
71 data.insert(data.end(), response.begin() + 1, pages_in_end_itr);
72 }
73 }
74
75 char data_buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
76 ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes_to(data_buf, data));
77
78 return true;
79}
80
81bool PN532::is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6) {
82 const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector
83
84 return (page_3_to_6.size() > p4_offset + 3) &&
85 ((page_3_to_6[p4_offset + 0] != 0xFF) || (page_3_to_6[p4_offset + 1] != 0xFF) ||
86 (page_3_to_6[p4_offset + 2] != 0xFF) || (page_3_to_6[p4_offset + 3] != 0xFF));
87}
88
90 std::vector<uint8_t> data;
91 if (this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE, data)) {
92 ESP_LOGV(TAG, "Tag capacity is %u bytes", data[2] * 8U);
93 return data[2] * 8U;
94 }
95 return 0;
96}
97
98bool PN532::find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
99 uint8_t &message_start_index) {
100 const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector
101
102 if (!(page_3_to_6.size() > p4_offset + 5)) {
103 return false;
104 }
105
106 if (page_3_to_6[p4_offset + 0] == 0x03) {
107 message_length = page_3_to_6[p4_offset + 1];
108 message_start_index = 2;
109 return true;
110 } else if (page_3_to_6[p4_offset + 5] == 0x03) {
111 message_length = page_3_to_6[p4_offset + 6];
112 message_start_index = 7;
113 return true;
114 }
115 return false;
116}
117
119 uint32_t capacity = this->read_mifare_ultralight_capacity_();
120
121 auto encoded = message->encode();
122
123 uint32_t message_length = encoded.size();
124 uint32_t buffer_length = nfc::get_mifare_ultralight_buffer_size(message_length);
125
126 if (buffer_length > capacity) {
127 ESP_LOGE(TAG, "Message length exceeds tag capacity %" PRIu32 " > %" PRIu32, buffer_length, capacity);
128 return false;
129 }
130
131 encoded.insert(encoded.begin(), 0x03);
132 if (message_length < 255) {
133 encoded.insert(encoded.begin() + 1, message_length);
134 } else {
135 encoded.insert(encoded.begin() + 1, 0xFF);
136 encoded.insert(encoded.begin() + 2, (message_length >> 8) & 0xFF);
137 encoded.insert(encoded.begin() + 2, message_length & 0xFF);
138 }
139 encoded.push_back(0xFE);
140
141 encoded.resize(buffer_length, 0);
142
143 uint32_t index = 0;
144 uint8_t current_page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE;
145
146 while (index < buffer_length) {
147 if (!this->write_mifare_ultralight_page_(current_page, encoded.data() + index, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE)) {
148 return false;
149 }
150 index += nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
151 current_page++;
152 }
153 return true;
154}
155
157 uint32_t capacity = this->read_mifare_ultralight_capacity_();
158 uint8_t pages = (capacity / nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) + nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE;
159
160 static constexpr std::array<uint8_t, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE> BLANK_DATA = {0x00, 0x00, 0x00, 0x00};
161
162 for (int i = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; i < pages; i++) {
163 if (!this->write_mifare_ultralight_page_(i, BLANK_DATA.data(), BLANK_DATA.size())) {
164 return false;
165 }
166 }
167 return true;
168}
169
170bool PN532::write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len) {
171 std::vector<uint8_t> cmd({
172 PN532_COMMAND_INDATAEXCHANGE,
173 0x01, // One card
174 nfc::MIFARE_CMD_WRITE_ULTRALIGHT,
175 page_num,
176 });
177 cmd.insert(cmd.end(), write_data, write_data + len);
178 if (!this->write_command_(cmd)) {
179 ESP_LOGE(TAG, "Error writing page %u", page_num);
180 return false;
181 }
182
183 std::vector<uint8_t> response;
184 if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, response)) {
185 ESP_LOGE(TAG, "Error writing page %u", page_num);
186 return false;
187 }
188
189 return true;
190}
191
192} // namespace pn532
193} // namespace esphome
virtual bool write_data(const std::vector< uint8_t > &data)=0
std::unique_ptr< nfc::NfcTag > read_mifare_ultralight_tag_(nfc::NfcTagUid &uid)
virtual bool read_response(uint8_t command, std::vector< uint8_t > &data)=0
bool write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len)
bool find_mifare_ultralight_ndef_(const std::vector< uint8_t > &page_3_to_6, uint8_t &message_length, uint8_t &message_start_index)
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, std::vector< uint8_t > &data)
bool write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message)
bool write_command_(const std::vector< uint8_t > &data)
Definition pn532.cpp:244
const char * message
Definition component.cpp:38
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:15
uint32_t get_mifare_ultralight_buffer_size(uint32_t message_length)
Definition nfc.cpp:67
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string size_t len
Definition helpers.h:817