ESPHome  2024.3.1
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 
12 namespace esphome {
13 namespace pn532 {
14 
15 static const char *const TAG = "pn532";
16 
17 void PN532::setup() {
18  ESP_LOGCONFIG(TAG, "Setting up PN532...");
19 
20  // Get version data
21  if (!this->write_command_({PN532_COMMAND_VERSION_DATA})) {
22  ESP_LOGW(TAG, "Error sending version command, trying again...");
23  if (!this->write_command_({PN532_COMMAND_VERSION_DATA})) {
24  ESP_LOGE(TAG, "Error sending version command");
25  this->mark_failed();
26  return;
27  }
28  }
29 
30  std::vector<uint8_t> version_data;
31  if (!this->read_response(PN532_COMMAND_VERSION_DATA, version_data)) {
32  ESP_LOGE(TAG, "Error getting version");
33  this->mark_failed();
34  return;
35  }
36  ESP_LOGD(TAG, "Found chip PN5%02X", version_data[0]);
37  ESP_LOGD(TAG, "Firmware ver. %d.%d", 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 
126 void PN532::loop() {
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  ESP_LOGD(TAG, "Found new tag '%s'", nfc::format_uid(nfcid).c_str());
201  if (tag->has_ndef_message()) {
202  const auto &message = tag->get_ndef_message();
203  const auto &records = message->get_records();
204  ESP_LOGD(TAG, " NDEF formatted records:");
205  for (const auto &record : records) {
206  ESP_LOGD(TAG, " %s - %s", record->get_type().c_str(), record->get_payload().c_str());
207  }
208  }
209  }
210  } else if (next_task_ == CLEAN) {
211  ESP_LOGD(TAG, " Tag cleaning...");
212  if (!this->clean_tag_(nfcid)) {
213  ESP_LOGE(TAG, " Tag was not fully cleaned successfully");
214  }
215  ESP_LOGD(TAG, " Tag cleaned!");
216  } else if (next_task_ == FORMAT) {
217  ESP_LOGD(TAG, " Tag formatting...");
218  if (!this->format_tag_(nfcid)) {
219  ESP_LOGE(TAG, "Error formatting tag as NDEF");
220  }
221  ESP_LOGD(TAG, " Tag formatted!");
222  } else if (next_task_ == WRITE) {
223  if (this->next_task_message_to_write_ != nullptr) {
224  ESP_LOGD(TAG, " Tag writing...");
225  ESP_LOGD(TAG, " Tag formatting...");
226  if (!this->format_tag_(nfcid)) {
227  ESP_LOGE(TAG, " Tag could not be formatted for writing");
228  } else {
229  ESP_LOGD(TAG, " Writing NDEF data");
230  if (!this->write_tag_(nfcid, this->next_task_message_to_write_)) {
231  ESP_LOGE(TAG, " Failed to write message to tag");
232  }
233  ESP_LOGD(TAG, " Finished writing NDEF data");
234  delete this->next_task_message_to_write_;
235  this->next_task_message_to_write_ = nullptr;
236  this->on_finished_write_callback_.call();
237  }
238  }
239  }
240 
241  this->read_mode();
242 
243  this->turn_off_rf_();
244 }
245 
246 bool PN532::write_command_(const std::vector<uint8_t> &data) {
247  std::vector<uint8_t> write_data;
248  // Preamble
249  write_data.push_back(0x00);
250 
251  // Start code
252  write_data.push_back(0x00);
253  write_data.push_back(0xFF);
254 
255  // Length of message, TFI + data bytes
256  const uint8_t real_length = data.size() + 1;
257  // LEN
258  write_data.push_back(real_length);
259  // LCS (Length checksum)
260  write_data.push_back(~real_length + 1);
261 
262  // TFI (Frame Identifier, 0xD4 means to PN532, 0xD5 means from PN532)
263  write_data.push_back(0xD4);
264  // calculate checksum, TFI is part of checksum
265  uint8_t checksum = 0xD4;
266 
267  // DATA
268  for (uint8_t dat : data) {
269  write_data.push_back(dat);
270  checksum += dat;
271  }
272 
273  // DCS (Data checksum)
274  write_data.push_back(~checksum + 1);
275  // Postamble
276  write_data.push_back(0x00);
277 
278  this->write_data(write_data);
279 
280  return this->read_ack_();
281 }
282 
284  ESP_LOGV(TAG, "Reading ACK...");
285 
286  std::vector<uint8_t> data;
287  if (!this->read_data(data, 6)) {
288  return false;
289  }
290 
291  bool matches = (data[1] == 0x00 && // preamble
292  data[2] == 0x00 && // start of packet
293  data[3] == 0xFF && data[4] == 0x00 && // ACK packet code
294  data[5] == 0xFF && data[6] == 0x00); // postamble
295  ESP_LOGV(TAG, "ACK valid: %s", YESNO(matches));
296  return matches;
297 }
298 
300  ESP_LOGV(TAG, "Sending ACK for abort");
301  this->write_data({0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00});
302  delay(10);
303 }
305  ESP_LOGV(TAG, "Sending NACK for retransmit");
306  this->write_data({0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00});
307  delay(10);
308 }
309 
311  if (this->rd_ready_ == READY) {
312  if (block) {
313  this->rd_start_time_ = 0;
314  this->rd_ready_ = WOULDBLOCK;
315  }
316  return READY;
317  }
318 
319  if (!this->rd_start_time_) {
320  this->rd_start_time_ = millis();
321  }
322 
323  while (true) {
324  if (this->is_read_ready()) {
325  this->rd_ready_ = READY;
326  break;
327  }
328 
329  if (millis() - this->rd_start_time_ > 100) {
330  ESP_LOGV(TAG, "Timed out waiting for readiness from PN532!");
331  this->rd_ready_ = TIMEOUT;
332  break;
333  }
334 
335  if (!block) {
336  this->rd_ready_ = WOULDBLOCK;
337  break;
338  }
339 
340  yield();
341  }
342 
343  auto rdy = this->rd_ready_;
344  if (block || rdy == TIMEOUT) {
345  this->rd_start_time_ = 0;
346  this->rd_ready_ = WOULDBLOCK;
347  }
348  return rdy;
349 }
350 
352  ESP_LOGV(TAG, "Turning RF field OFF");
353  this->write_command_({
354  PN532_COMMAND_RFCONFIGURATION,
355  0x01, // RF Field
356  0x00, // Off
357  });
358 }
359 
360 std::unique_ptr<nfc::NfcTag> PN532::read_tag_(std::vector<uint8_t> &uid) {
361  uint8_t type = nfc::guess_tag_type(uid.size());
362 
363  if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
364  ESP_LOGD(TAG, "Mifare classic");
365  return this->read_mifare_classic_tag_(uid);
366  } else if (type == nfc::TAG_TYPE_2) {
367  ESP_LOGD(TAG, "Mifare ultralight");
368  return this->read_mifare_ultralight_tag_(uid);
369  } else if (type == nfc::TAG_TYPE_UNKNOWN) {
370  ESP_LOGV(TAG, "Cannot determine tag type");
371  return make_unique<nfc::NfcTag>(uid);
372  } else {
373  return make_unique<nfc::NfcTag>(uid);
374  }
375 }
376 
378  this->next_task_ = READ;
379  ESP_LOGD(TAG, "Waiting to read next tag");
380 }
382  this->next_task_ = CLEAN;
383  ESP_LOGD(TAG, "Waiting to clean next tag");
384 }
386  this->next_task_ = FORMAT;
387  ESP_LOGD(TAG, "Waiting to format next tag");
388 }
390  this->next_task_ = WRITE;
391  this->next_task_message_to_write_ = message;
392  ESP_LOGD(TAG, "Waiting to write next tag");
393 }
394 
395 bool PN532::clean_tag_(std::vector<uint8_t> &uid) {
396  uint8_t type = nfc::guess_tag_type(uid.size());
397  if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
398  return this->format_mifare_classic_mifare_(uid);
399  } else if (type == nfc::TAG_TYPE_2) {
400  return this->clean_mifare_ultralight_();
401  }
402  ESP_LOGE(TAG, "Unsupported Tag for formatting");
403  return false;
404 }
405 
406 bool PN532::format_tag_(std::vector<uint8_t> &uid) {
407  uint8_t type = nfc::guess_tag_type(uid.size());
408  if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
409  return this->format_mifare_classic_ndef_(uid);
410  } else if (type == nfc::TAG_TYPE_2) {
411  return this->clean_mifare_ultralight_();
412  }
413  ESP_LOGE(TAG, "Unsupported Tag for formatting");
414  return false;
415 }
416 
417 bool PN532::write_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message) {
418  uint8_t type = nfc::guess_tag_type(uid.size());
419  if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
420  return this->write_mifare_classic_tag_(uid, message);
421  } else if (type == nfc::TAG_TYPE_2) {
422  return this->write_mifare_ultralight_tag_(uid, message);
423  }
424  ESP_LOGE(TAG, "Unsupported Tag for formatting");
425  return false;
426 }
427 
429 
431  ESP_LOGCONFIG(TAG, "PN532:");
432  switch (this->error_code_) {
433  case NONE:
434  break;
435  case WAKEUP_FAILED:
436  ESP_LOGE(TAG, "Wake Up command failed!");
437  break;
438  case SAM_COMMAND_FAILED:
439  ESP_LOGE(TAG, "SAM command failed!");
440  break;
441  }
442 
443  LOG_UPDATE_INTERVAL(this);
444 
445  for (auto *child : this->binary_sensors_) {
446  LOG_BINARY_SENSOR(" ", "Tag", child);
447  }
448 }
449 
450 bool PN532BinarySensor::process(std::vector<uint8_t> &data) {
451  if (data.size() != this->uid_.size())
452  return false;
453 
454  for (size_t i = 0; i < data.size(); i++) {
455  if (data[i] != this->uid_[i])
456  return false;
457  }
458 
459  this->publish_state(true);
460  this->found_ = true;
461  return true;
462 }
463 
464 } // namespace pn532
465 } // namespace esphome
bool format_mifare_classic_ndef_(std::vector< uint8_t > &uid)
enum esphome::pn532::PN532::NfcTask READ
virtual bool write_data(const std::vector< uint8_t > &data)=0
const float DATA
For components that import data from directly connected sensors like DHT.
Definition: component.cpp:19
void update() override
Definition: pn532.cpp:106
std::vector< PN532BinarySensor * > binary_sensors_
Definition: pn532.h:98
bool updates_enabled_
Definition: pn532.h:96
virtual bool read_response(uint8_t command, std::vector< uint8_t > &data)=0
void status_set_warning(const char *message="unspecified")
Definition: component.cpp:146
void dump_config() override
Definition: pn532.cpp:430
virtual bool is_read_ready()=0
std::string format_uid(std::vector< uint8_t > &uid)
Definition: nfc.cpp:10
std::unique_ptr< nfc::NfcTag > read_tag_(std::vector< uint8_t > &uid)
Definition: pn532.cpp:360
enum PN532ReadReady read_ready_(bool block)
Definition: pn532.cpp:310
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
nfc::NdefMessage * next_task_message_to_write_
Definition: pn532.h:102
bool clean_tag_(std::vector< uint8_t > &uid)
Definition: pn532.cpp:395
std::vector< uint8_t > current_uid_
Definition: pn532.h:101
bool requested_read_
Definition: pn532.h:97
bool format_mifare_classic_mifare_(std::vector< uint8_t > &uid)
bool process(std::vector< uint8_t > &data)
Definition: pn532.cpp:450
uint8_t guess_tag_type(uint8_t uid_length)
Definition: nfc.cpp:34
void status_clear_warning()
Definition: component.cpp:161
uint8_t type
CallbackManager< void()> on_finished_write_callback_
Definition: pn532.h:116
std::unique_ptr< nfc::NfcTag > read_mifare_ultralight_tag_(std::vector< uint8_t > &uid)
uint8_t checksum
Definition: bl0939.h:35
enum esphome::pn532::PN532::PN532Error NONE
void setup() override
Definition: pn532.cpp:17
virtual bool read_data(std::vector< uint8_t > &data, uint8_t len)=0
bool write_mifare_classic_tag_(std::vector< uint8_t > &uid, nfc::NdefMessage *message)
void IRAM_ATTR HOT yield()
Definition: core.cpp:24
virtual void mark_failed()
Mark this component as failed.
Definition: component.cpp:113
float get_setup_priority() const override
Definition: pn532.cpp:428
void loop() override
Definition: pn532.cpp:126
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7
uint32_t rd_start_time_
Definition: pn532.h:103
bool write_mifare_ultralight_tag_(std::vector< uint8_t > &uid, nfc::NdefMessage *message)
std::unique_ptr< nfc::NfcTag > read_mifare_classic_tag_(std::vector< uint8_t > &uid)
bool write_tag_(std::vector< uint8_t > &uid, nfc::NdefMessage *message)
Definition: pn532.cpp:417
std::vector< nfc::NfcOnTagTrigger * > triggers_ontagremoved_
Definition: pn532.h:100
bool format_tag_(std::vector< uint8_t > &uid)
Definition: pn532.cpp:406
bool write_command_(const std::vector< uint8_t > &data)
Definition: pn532.cpp:246
void write_mode(nfc::NdefMessage *message)
Definition: pn532.cpp:389
std::vector< nfc::NfcOnTagTrigger * > triggers_ontag_
Definition: pn532.h:99
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26