ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
pn71xx_mifare_ultralight.cpp
Go to the documentation of this file.
1#include <algorithm>
2#include <array>
3#include <cinttypes>
4#include <memory>
5
6#include "pn71xx.h"
7#include "esphome/core/log.h"
8
9namespace esphome::pn71xx {
10
11ESPHOME_LOG_TAG(TAG, "pn71xx.mifare_ultralight");
12
15 // pages 3 to 6 contain various info we are interested in -- do one read to grab it all
16 if (this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE * nfc::MIFARE_ULTRALIGHT_READ_SIZE,
17 data) != nfc::STATUS_OK) {
18 return nfc::STATUS_FAILED;
19 }
20
21 if (!this->is_mifare_ultralight_formatted_(data)) {
22 ESP_LOGW(TAG, "Not NDEF formatted");
23 return nfc::STATUS_FAILED;
24 }
25
26 uint8_t message_length;
27 uint8_t message_start_index;
28 if (this->find_mifare_ultralight_ndef_(data, message_length, message_start_index) != nfc::STATUS_OK) {
29 ESP_LOGW(TAG, "Couldn't find NDEF message");
30 return nfc::STATUS_FAILED;
31 }
32 ESP_LOGVV(TAG, "NDEF message length: %u, start: %u", message_length, message_start_index);
33
34 if (message_length == 0) {
35 return nfc::STATUS_FAILED;
36 }
37 // we already read pages 3-6 earlier -- pick up where we left off so we're not re-reading pages
38 const uint8_t read_length = message_length + message_start_index > 12 ? message_length + message_start_index - 12 : 0;
39 if (read_length) {
40 if (read_mifare_ultralight_bytes_(nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE + 3, read_length, data) !=
41 nfc::STATUS_OK) {
42 ESP_LOGE(TAG, "Error reading tag data");
43 return nfc::STATUS_FAILED;
44 }
45 }
46 // skip page 3 as well as any bytes ahead of message_start_index
47 const size_t skip = message_start_index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
48 if (skip >= data.size()) {
49 return nfc::STATUS_FAILED;
50 }
51 tag.set_ndef_message(make_unique<nfc::NdefMessage>(std::span<const uint8_t>(data).subspan(skip)));
52
53 return nfc::STATUS_OK;
54}
55
56uint8_t PN71xx::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, UltralightReadBuffer &data) {
57 const uint8_t read_increment = nfc::MIFARE_ULTRALIGHT_READ_SIZE * nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
59 nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {nfc::MIFARE_CMD_READ, start_page});
60
61 for (size_t i = 0; i * read_increment < num_bytes; i++) {
62 const uint8_t page = i * nfc::MIFARE_ULTRALIGHT_READ_SIZE + start_page;
63 tx.set_payload({nfc::MIFARE_CMD_READ, page});
64 // a short answer (e.g. a NAK for a page beyond the end of the tag) is retried a limited number of times
65 uint8_t attempts = 0;
66 do {
67 if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
68 ESP_LOGE(TAG, "Error reading tag data");
69 return nfc::STATUS_FAILED;
70 }
71 if (++attempts > NFCC_MAX_COMM_FAILS && rx.get_payload_size() < read_increment) {
72 ESP_LOGE(TAG, "Short read from page %u", page);
73 return nfc::STATUS_FAILED;
74 }
75 } while (rx.get_payload_size() < read_increment);
76 // the payload ends with a status byte; keep only the bytes still wanted from this read
77 const uint16_t wanted = num_bytes - i * read_increment;
78 const size_t count = std::min<size_t>(read_increment, wanted);
79 for (const uint8_t byte : rx.get_payload().subspan(0, count)) {
80 data.push_back(byte);
81 }
82 }
83
84 char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
85 ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes_to(buf, data));
86
87 return nfc::STATUS_OK;
88}
89
90bool PN71xx::is_mifare_ultralight_formatted_(const std::span<const uint8_t> page_3_to_6) {
91 const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector
92
93 return (page_3_to_6.size() > p4_offset + 3) &&
94 ((page_3_to_6[p4_offset + 0] != 0xFF) || (page_3_to_6[p4_offset + 1] != 0xFF) ||
95 (page_3_to_6[p4_offset + 2] != 0xFF) || (page_3_to_6[p4_offset + 3] != 0xFF));
96}
97
100 if (this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE, data) == nfc::STATUS_OK) {
101 ESP_LOGV(TAG, "Tag capacity is %u bytes", data[2] * 8U);
102 return data[2] * 8U;
103 }
104 return 0;
105}
106
107uint8_t PN71xx::find_mifare_ultralight_ndef_(const std::span<const uint8_t> page_3_to_6, uint8_t &message_length,
108 uint8_t &message_start_index) {
109 const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector
110
111 if (!(page_3_to_6.size() > p4_offset + 6)) {
112 return nfc::STATUS_FAILED;
113 }
114
115 if (page_3_to_6[p4_offset + 0] == 0x03) {
116 message_length = page_3_to_6[p4_offset + 1];
117 message_start_index = 2;
118 return nfc::STATUS_OK;
119 } else if (page_3_to_6[p4_offset + 5] == 0x03) {
120 message_length = page_3_to_6[p4_offset + 6];
121 message_start_index = 7;
122 return nfc::STATUS_OK;
123 }
124 return nfc::STATUS_FAILED;
125}
126
127uint8_t PN71xx::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr<nfc::NdefMessage> &message) {
129
130 const auto encoded = message->encode();
131 const uint32_t buffer_length = nfc::get_mifare_ultralight_buffer_size(encoded.size());
132
133 if (buffer_length > capacity) {
134 ESP_LOGE(TAG, "Message length exceeds tag capacity %" PRIu32 " > %" PRIu32, buffer_length, capacity);
135 return nfc::STATUS_FAILED;
136 }
137
139 nfc::fill_ndef_tlv(encoded, buffer_length, buffer);
140
141 uint32_t index = 0;
142 uint8_t current_page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE;
143
144 while (index < buffer_length) {
145 if (this->write_mifare_ultralight_page_(current_page, &buffer[index], nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) !=
146 nfc::STATUS_OK) {
147 return nfc::STATUS_FAILED;
148 }
149 index += nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
150 current_page++;
151 }
152 return nfc::STATUS_OK;
153}
154
157 uint8_t pages = (capacity / nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) + nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE;
158
159 static constexpr std::array<uint8_t, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE> BLANK_DATA = {0x00, 0x00, 0x00, 0x00};
160
161 for (int i = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; i < pages; i++) {
162 if (this->write_mifare_ultralight_page_(i, BLANK_DATA.data(), BLANK_DATA.size()) != nfc::STATUS_OK) {
163 return nfc::STATUS_FAILED;
164 }
165 }
166 return nfc::STATUS_OK;
167}
168
169uint8_t PN71xx::write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len) {
171 nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {nfc::MIFARE_CMD_WRITE_ULTRALIGHT, page_num});
172 tx.append(std::span<const uint8_t>(write_data, len));
173
174 if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) {
175 ESP_LOGE(TAG, "Error writing page %u", page_num);
176 return nfc::STATUS_FAILED;
177 }
178 return nfc::STATUS_OK;
179}
180
181} // namespace esphome::pn71xx
Fixed-capacity vector - sized once through init() or try_init(); push_back never reallocates This avo...
Definition helpers.h:616
Minimal static vector - saves memory by avoiding std::vector overhead.
Definition helpers.h:286
One NCI packet, held in a fixed buffer so building and reading messages never allocates.
Definition nci_message.h:16
uint8_t get_payload_size(bool recompute=false)
std::span< const uint8_t > get_payload() const
The payload bytes that follow the header.
void append(std::span< const uint8_t > data)
Appends bytes to the payload; bytes that do not fit are dropped.
void set_ndef_message(std::unique_ptr< NdefMessage > ndef_message)
Definition nfc_tag.h:49
uint8_t find_mifare_ultralight_ndef_(std::span< const uint8_t > page_3_to_6, uint8_t &message_length, uint8_t &message_start_index)
uint8_t write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr< nfc::NdefMessage > &message)
uint8_t transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, uint16_t timeout=NFCC_DEFAULT_TIMEOUT, bool expect_notification=true)
Definition pn71xx.cpp:1107
bool is_mifare_ultralight_formatted_(std::span< const uint8_t > page_3_to_6)
uint8_t read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, UltralightReadBuffer &data)
uint8_t read_mifare_ultralight_tag_(nfc::NfcTag &tag)
uint8_t write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len)
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
ESPHOME_LOG_TAG(TAG, "pn71xx")
const void size_t len
Definition hal.h:68
uint16_t uint16_t & capacity
Definition helpers.cpp:34
static void uint32_t