ESPHome 2026.1.4
Loading...
Searching...
No Matches
pn532.cpp
Go to the documentation of this file.
1#include "pn532.h"
2
3#include <memory>
4#include "esphome/core/log.h"
5#include "esphome/core/hal.h"
6
7// Based on:
8// - https://cdn-shop.adafruit.com/datasheets/PN532C106_Application+Note_v1.2.pdf
9// - https://www.nxp.com/docs/en/nxp/application-notes/AN133910.pdf
10// - https://www.nxp.com/docs/en/nxp/application-notes/153710.pdf
11
12namespace esphome {
13namespace pn532 {
14
15static const char *const TAG = "pn532";
16
18 // Get version data
19 if (!this->write_command_({PN532_COMMAND_VERSION_DATA})) {
20 ESP_LOGW(TAG, "Error sending version command, trying again");
21 if (!this->write_command_({PN532_COMMAND_VERSION_DATA})) {
22 ESP_LOGE(TAG, "Error sending version command");
23 this->mark_failed();
24 return;
25 }
26 }
27
28 std::vector<uint8_t> version_data;
29 if (!this->read_response(PN532_COMMAND_VERSION_DATA, version_data)) {
30 ESP_LOGE(TAG, "Error getting version");
31 this->mark_failed();
32 return;
33 }
34 ESP_LOGD(TAG,
35 "Found chip PN5%02X\n"
36 "Firmware ver. %d.%d",
37 version_data[0], version_data[1], version_data[2]);
38
39 if (!this->write_command_({
40 PN532_COMMAND_SAMCONFIGURATION,
41 0x01, // normal mode
42 0x14, // zero timeout (not in virtual card mode)
43 0x01,
44 })) {
45 ESP_LOGE(TAG, "No wakeup ack");
46 this->mark_failed();
47 return;
48 }
49
50 std::vector<uint8_t> wakeup_result;
51 if (!this->read_response(PN532_COMMAND_SAMCONFIGURATION, wakeup_result)) {
52 this->error_code_ = WAKEUP_FAILED;
53 this->mark_failed();
54 return;
55 }
56
57 // Set up SAM (secure access module)
58 uint8_t sam_timeout = std::min<uint8_t>(255u, this->update_interval_ / 50);
59 if (!this->write_command_({
60 PN532_COMMAND_SAMCONFIGURATION,
61 0x01, // normal mode
62 sam_timeout, // timeout as multiple of 50ms (actually only for virtual card mode, but shouldn't matter)
63 0x01, // Enable IRQ
64 })) {
65 this->error_code_ = SAM_COMMAND_FAILED;
66 this->mark_failed();
67 return;
68 }
69
70 std::vector<uint8_t> sam_result;
71 if (!this->read_response(PN532_COMMAND_SAMCONFIGURATION, sam_result)) {
72 ESP_LOGV(TAG, "Invalid SAM result: (%u)", sam_result.size()); // NOLINT
73 for (uint8_t dat : sam_result) {
74 ESP_LOGV(TAG, " 0x%02X", dat);
75 }
76 this->error_code_ = SAM_COMMAND_FAILED;
77 this->mark_failed();
78 return;
79 }
80
81 this->turn_off_rf_();
82}
83
85 updates_enabled_ = false;
86 requested_read_ = false;
87 ESP_LOGI(TAG, "Powering down PN532");
88 if (!this->write_command_({PN532_COMMAND_POWERDOWN, 0b10100000})) { // enable i2c,spi wakeup
89 ESP_LOGE(TAG, "Error writing powerdown command to PN532");
90 return false;
91 }
92 std::vector<uint8_t> response;
93 if (!this->read_response(PN532_COMMAND_POWERDOWN, response)) {
94 ESP_LOGE(TAG, "Error reading PN532 powerdown response");
95 return false;
96 }
97 if (response[0] != 0x00) {
98 ESP_LOGE(TAG, "Error on PN532 powerdown: %02x", response[0]);
99 return false;
100 }
101 ESP_LOGV(TAG, "Powerdown successful");
102 delay(1);
103 return true;
104}
105
107 if (!updates_enabled_)
108 return;
109
110 for (auto *obj : this->binary_sensors_)
111 obj->on_scan_end();
112
113 if (!this->write_command_({
114 PN532_COMMAND_INLISTPASSIVETARGET,
115 0x01, // max 1 card
116 0x00, // baud rate ISO14443A (106 kbit/s)
117 })) {
118 ESP_LOGW(TAG, "Requesting tag read failed!");
119 this->status_set_warning();
120 return;
121 }
122 this->status_clear_warning();
123 this->requested_read_ = true;
124}
125
127 if (!this->requested_read_)
128 return;
129
130 auto ready = this->read_ready_(false);
131 if (ready == WOULDBLOCK)
132 return;
133
134 bool success = false;
135 std::vector<uint8_t> read;
136
137 if (ready == READY) {
138 success = this->read_response(PN532_COMMAND_INLISTPASSIVETARGET, read);
139 } else {
140 this->send_ack_(); // abort still running InListPassiveTarget
141 }
142
143 this->requested_read_ = false;
144
145 if (!success) {
146 // Something failed
147 if (!this->current_uid_.empty()) {
148 auto tag = make_unique<nfc::NfcTag>(this->current_uid_);
149 for (auto *trigger : this->triggers_ontagremoved_)
150 trigger->process(tag);
151 }
152 this->current_uid_ = {};
153 this->turn_off_rf_();
154 return;
155 }
156
157 uint8_t num_targets = read[0];
158 if (num_targets != 1) {
159 // no tags found or too many
160 if (!this->current_uid_.empty()) {
161 auto tag = make_unique<nfc::NfcTag>(this->current_uid_);
162 for (auto *trigger : this->triggers_ontagremoved_)
163 trigger->process(tag);
164 }
165 this->current_uid_ = {};
166 this->turn_off_rf_();
167 return;
168 }
169
170 uint8_t nfcid_length = read[5];
171 std::vector<uint8_t> nfcid(read.begin() + 6, read.begin() + 6 + nfcid_length);
172 if (read.size() < 6U + nfcid_length) {
173 // oops, pn532 returned invalid data
174 return;
175 }
176
177 bool report = true;
178 for (auto *bin_sens : this->binary_sensors_) {
179 if (bin_sens->process(nfcid)) {
180 report = false;
181 }
182 }
183
184 if (nfcid.size() == this->current_uid_.size()) {
185 bool same_uid = true;
186 for (size_t i = 0; i < nfcid.size(); i++)
187 same_uid &= nfcid[i] == this->current_uid_[i];
188 if (same_uid)
189 return;
190 }
191
192 this->current_uid_ = nfcid;
193
194 if (next_task_ == READ) {
195 auto tag = this->read_tag_(nfcid);
196 for (auto *trigger : this->triggers_ontag_)
197 trigger->process(tag);
198
199 if (report) {
200 char uid_buf[nfc::FORMAT_UID_BUFFER_SIZE];
201 ESP_LOGD(TAG, "Found new tag '%s'", nfc::format_uid_to(uid_buf, nfcid));
202 if (tag->has_ndef_message()) {
203 const auto &message = tag->get_ndef_message();
204 const auto &records = message->get_records();
205 ESP_LOGD(TAG, " NDEF formatted records:");
206 for (const auto &record : records) {
207 ESP_LOGD(TAG, " %s - %s", record->get_type().c_str(), record->get_payload().c_str());
208 }
209 }
210 }
211 } else if (next_task_ == CLEAN) {
212 ESP_LOGD(TAG, " Tag cleaning");
213 if (!this->clean_tag_(nfcid)) {
214 ESP_LOGE(TAG, " Tag was not fully cleaned successfully");
215 }
216 ESP_LOGD(TAG, " Tag cleaned!");
217 } else if (next_task_ == FORMAT) {
218 ESP_LOGD(TAG, " Tag formatting");
219 if (!this->format_tag_(nfcid)) {
220 ESP_LOGE(TAG, "Error formatting tag as NDEF");
221 }
222 ESP_LOGD(TAG, " Tag formatted!");
223 } else if (next_task_ == WRITE) {
224 if (this->next_task_message_to_write_ != nullptr) {
225 ESP_LOGD(TAG, " Tag writing");
226 ESP_LOGD(TAG, " Tag formatting");
227 if (!this->format_tag_(nfcid)) {
228 ESP_LOGE(TAG, " Tag could not be formatted for writing");
229 } else {
230 ESP_LOGD(TAG, " Writing NDEF data");
231 if (!this->write_tag_(nfcid, this->next_task_message_to_write_)) {
232 ESP_LOGE(TAG, " Failed to write message to tag");
233 }
234 ESP_LOGD(TAG, " Finished writing NDEF data");
235 delete this->next_task_message_to_write_;
236 this->next_task_message_to_write_ = nullptr;
237 this->on_finished_write_callback_.call();
238 }
239 }
240 }
241
242 this->read_mode();
243
244 this->turn_off_rf_();
245}
246
247bool PN532::write_command_(const std::vector<uint8_t> &data) {
248 std::vector<uint8_t> write_data;
249 // Preamble
250 write_data.push_back(0x00);
251
252 // Start code
253 write_data.push_back(0x00);
254 write_data.push_back(0xFF);
255
256 // Length of message, TFI + data bytes
257 const uint8_t real_length = data.size() + 1;
258 // LEN
259 write_data.push_back(real_length);
260 // LCS (Length checksum)
261 write_data.push_back(~real_length + 1);
262
263 // TFI (Frame Identifier, 0xD4 means to PN532, 0xD5 means from PN532)
264 write_data.push_back(0xD4);
265 // calculate checksum, TFI is part of checksum
266 uint8_t checksum = 0xD4;
267
268 // DATA
269 for (uint8_t dat : data) {
270 write_data.push_back(dat);
271 checksum += dat;
272 }
273
274 // DCS (Data checksum)
275 write_data.push_back(~checksum + 1);
276 // Postamble
277 write_data.push_back(0x00);
278
279 this->write_data(write_data);
280
281 return this->read_ack_();
282}
283
285 ESP_LOGV(TAG, "Reading ACK");
286
287 std::vector<uint8_t> data;
288 if (!this->read_data(data, 6)) {
289 return false;
290 }
291
292 bool matches = (data[1] == 0x00 && // preamble
293 data[2] == 0x00 && // start of packet
294 data[3] == 0xFF && data[4] == 0x00 && // ACK packet code
295 data[5] == 0xFF && data[6] == 0x00); // postamble
296 ESP_LOGV(TAG, "ACK valid: %s", YESNO(matches));
297 return matches;
298}
299
301 ESP_LOGV(TAG, "Sending ACK for abort");
302 this->write_data({0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00});
303 delay(10);
304}
306 ESP_LOGV(TAG, "Sending NACK for retransmit");
307 this->write_data({0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00});
308 delay(10);
309}
310
312 if (this->rd_ready_ == READY) {
313 if (block) {
314 this->rd_start_time_ = 0;
315 this->rd_ready_ = WOULDBLOCK;
316 }
317 return READY;
318 }
319
320 if (!this->rd_start_time_) {
321 this->rd_start_time_ = millis();
322 }
323
324 while (true) {
325 if (this->is_read_ready()) {
326 this->rd_ready_ = READY;
327 break;
328 }
329
330 if (millis() - this->rd_start_time_ > 100) {
331 ESP_LOGV(TAG, "Timed out waiting for readiness from PN532!");
332 this->rd_ready_ = TIMEOUT;
333 break;
334 }
335
336 if (!block) {
337 this->rd_ready_ = WOULDBLOCK;
338 break;
339 }
340
341 yield();
342 }
343
344 auto rdy = this->rd_ready_;
345 if (block || rdy == TIMEOUT) {
346 this->rd_start_time_ = 0;
347 this->rd_ready_ = WOULDBLOCK;
348 }
349 return rdy;
350}
351
353 ESP_LOGV(TAG, "Turning RF field OFF");
354 this->write_command_({
355 PN532_COMMAND_RFCONFIGURATION,
356 0x01, // RF Field
357 0x00, // Off
358 });
359}
360
361std::unique_ptr<nfc::NfcTag> PN532::read_tag_(std::vector<uint8_t> &uid) {
362 uint8_t type = nfc::guess_tag_type(uid.size());
363
364 if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
365 ESP_LOGD(TAG, "Mifare classic");
366 return this->read_mifare_classic_tag_(uid);
367 } else if (type == nfc::TAG_TYPE_2) {
368 ESP_LOGD(TAG, "Mifare ultralight");
369 return this->read_mifare_ultralight_tag_(uid);
370 } else if (type == nfc::TAG_TYPE_UNKNOWN) {
371 ESP_LOGV(TAG, "Cannot determine tag type");
372 return make_unique<nfc::NfcTag>(uid);
373 } else {
374 return make_unique<nfc::NfcTag>(uid);
375 }
376}
377
379 this->next_task_ = READ;
380 ESP_LOGD(TAG, "Waiting to read next tag");
381}
383 this->next_task_ = CLEAN;
384 ESP_LOGD(TAG, "Waiting to clean next tag");
385}
387 this->next_task_ = FORMAT;
388 ESP_LOGD(TAG, "Waiting to format next tag");
389}
391 this->next_task_ = WRITE;
393 ESP_LOGD(TAG, "Waiting to write next tag");
394}
395
396bool PN532::clean_tag_(std::vector<uint8_t> &uid) {
397 uint8_t type = nfc::guess_tag_type(uid.size());
398 if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
399 return this->format_mifare_classic_mifare_(uid);
400 } else if (type == nfc::TAG_TYPE_2) {
401 return this->clean_mifare_ultralight_();
402 }
403 ESP_LOGE(TAG, "Unsupported Tag for formatting");
404 return false;
405}
406
407bool PN532::format_tag_(std::vector<uint8_t> &uid) {
408 uint8_t type = nfc::guess_tag_type(uid.size());
409 if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
410 return this->format_mifare_classic_ndef_(uid);
411 } else if (type == nfc::TAG_TYPE_2) {
412 return this->clean_mifare_ultralight_();
413 }
414 ESP_LOGE(TAG, "Unsupported Tag for formatting");
415 return false;
416}
417
418bool PN532::write_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message) {
419 uint8_t type = nfc::guess_tag_type(uid.size());
420 if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
421 return this->write_mifare_classic_tag_(uid, message);
422 } else if (type == nfc::TAG_TYPE_2) {
423 return this->write_mifare_ultralight_tag_(uid, message);
424 }
425 ESP_LOGE(TAG, "Unsupported Tag for formatting");
426 return false;
427}
428
430
432 ESP_LOGCONFIG(TAG, "PN532:");
433 switch (this->error_code_) {
434 case NONE:
435 break;
436 case WAKEUP_FAILED:
437 ESP_LOGE(TAG, "Wake Up command failed!");
438 break;
440 ESP_LOGE(TAG, "SAM command failed!");
441 break;
442 }
443
444 LOG_UPDATE_INTERVAL(this);
445
446 for (auto *child : this->binary_sensors_) {
447 LOG_BINARY_SENSOR(" ", "Tag", child);
448 }
449}
450
451bool PN532BinarySensor::process(std::vector<uint8_t> &data) {
452 if (data.size() != this->uid_.size())
453 return false;
454
455 for (size_t i = 0; i < data.size(); i++) {
456 if (data[i] != this->uid_[i])
457 return false;
458 }
459
460 this->publish_state(true);
461 this->found_ = true;
462 return true;
463}
464
465} // namespace pn532
466} // namespace esphome
uint8_t checksum
Definition bl0906.h:3
virtual void mark_failed()
Mark this component as failed.
void status_set_warning(const char *message=nullptr)
void status_clear_warning()
void publish_state(bool new_state)
Publish a new state to the front-end.
std::vector< uint8_t > uid_
Definition pn532.h:133
bool process(std::vector< uint8_t > &data)
Definition pn532.cpp:451
virtual bool write_data(const std::vector< uint8_t > &data)=0
enum PN532ReadReady read_ready_(bool block)
Definition pn532.cpp:311
virtual bool is_read_ready()=0
bool format_mifare_classic_ndef_(std::vector< uint8_t > &uid)
bool format_mifare_classic_mifare_(std::vector< uint8_t > &uid)
bool write_tag_(std::vector< uint8_t > &uid, nfc::NdefMessage *message)
Definition pn532.cpp:418
virtual bool read_response(uint8_t command, std::vector< uint8_t > &data)=0
virtual bool read_data(std::vector< uint8_t > &data, uint8_t len)=0
std::vector< PN532BinarySensor * > binary_sensors_
Definition pn532.h:98
void dump_config() override
Definition pn532.cpp:431
void setup() override
Definition pn532.cpp:17
std::unique_ptr< nfc::NfcTag > read_mifare_classic_tag_(std::vector< uint8_t > &uid)
enum esphome::pn532::PN532::NfcTask READ
std::vector< nfc::NfcOnTagTrigger * > triggers_ontagremoved_
Definition pn532.h:100
bool format_tag_(std::vector< uint8_t > &uid)
Definition pn532.cpp:407
float get_setup_priority() const override
Definition pn532.cpp:429
std::unique_ptr< nfc::NfcTag > read_tag_(std::vector< uint8_t > &uid)
Definition pn532.cpp:361
bool write_mifare_ultralight_tag_(std::vector< uint8_t > &uid, nfc::NdefMessage *message)
CallbackManager< void()> on_finished_write_callback_
Definition pn532.h:116
void write_mode(nfc::NdefMessage *message)
Definition pn532.cpp:390
void update() override
Definition pn532.cpp:106
std::vector< nfc::NfcOnTagTrigger * > triggers_ontag_
Definition pn532.h:99
enum esphome::pn532::PN532::PN532Error NONE
bool clean_tag_(std::vector< uint8_t > &uid)
Definition pn532.cpp:396
std::unique_ptr< nfc::NfcTag > read_mifare_ultralight_tag_(std::vector< uint8_t > &uid)
bool write_mifare_classic_tag_(std::vector< uint8_t > &uid, nfc::NdefMessage *message)
bool write_command_(const std::vector< uint8_t > &data)
Definition pn532.cpp:247
nfc::NdefMessage * next_task_message_to_write_
Definition pn532.h:102
std::vector< uint8_t > current_uid_
Definition pn532.h:101
uint32_t rd_start_time_
Definition pn532.h:103
void loop() override
Definition pn532.cpp:126
const char * message
Definition component.cpp:38
uint16_t type
char * format_uid_to(char *buffer, const std::vector< uint8_t > &uid)
Format UID to buffer with '-' separator (e.g., "04-11-22-33"). Returns buffer for inline use.
Definition nfc.cpp:11
uint8_t guess_tag_type(uint8_t uid_length)
Definition nfc.cpp:26
const float DATA
For components that import data from directly connected sensors like DHT.
Definition component.cpp:81
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
void IRAM_ATTR HOT yield()
Definition core.cpp:24
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:26
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:25