From 4ed1e44081f633194633b045d9771f0549e6153a Mon Sep 17 00:00:00 2001 From: Spencer Owen Date: Mon, 9 Dec 2024 19:45:15 -0700 Subject: [PATCH 1/5] Initial Draft NTAG215 support --- esphome/components/nfc/nfc.cpp | 29 +++++++++++++++++++++++++++-- esphome/components/nfc/nfc.h | 15 ++++++++++++++- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/esphome/components/nfc/nfc.cpp b/esphome/components/nfc/nfc.cpp index cf5a7f5ef1..71ef842b37 100644 --- a/esphome/components/nfc/nfc.cpp +++ b/esphome/components/nfc/nfc.cpp @@ -31,11 +31,36 @@ std::string format_bytes(std::vector &bytes) { return std::string(buf); } -uint8_t guess_tag_type(uint8_t uid_length) { +uint8_t is_ntag(const std::vector &first_page) { + // NTAG typically has a capability container (CC) starting at byte 3 + // The CC for NTAG is usually 0xE1 0x10 followed by a type-specific byte + if (first_page.size() >= 16 && first_page[3] == 0xE1 && first_page[4] == 0x10) { + switch (first_page[5]) { + case 0x11: + return TAG_TYPE_NTAG_213; + case 0x12: + return TAG_TYPE_NTAG_215; + case 0x13: + return TAG_TYPE_NTAG_216; + default: + return TAG_TYPE_UNKNOWN; + } + } + return TAG_TYPE_UNKNOWN; +} + +uint8_t guess_tag_type(uint8_t uid_length, const std::vector &first_page) { if (uid_length == 4) { return TAG_TYPE_MIFARE_CLASSIC; + } else if (uid_length == 7) { + uint8_t ntag_type = is_ntag(first_page); + if (ntag_type != TAG_TYPE_UNKNOWN) { + return ntag_type; + } else { + return TAG_TYPE_2; // Could be MIFARE Ultralight or other Type 2 tag + } } else { - return TAG_TYPE_2; + return TAG_TYPE_UNKNOWN; } } diff --git a/esphome/components/nfc/nfc.h b/esphome/components/nfc/nfc.h index 23bfdd8ef0..78422f7e3c 100644 --- a/esphome/components/nfc/nfc.h +++ b/esphome/components/nfc/nfc.h @@ -28,8 +28,16 @@ static const uint8_t TAG_TYPE_1 = 1; static const uint8_t TAG_TYPE_2 = 2; static const uint8_t TAG_TYPE_3 = 3; static const uint8_t TAG_TYPE_4 = 4; +static const uint8_t TAG_TYPE_NTAG_213 = 5; +static const uint8_t TAG_TYPE_NTAG_215 = 6; +static const uint8_t TAG_TYPE_NTAG_216 = 7; static const uint8_t TAG_TYPE_UNKNOWN = 99; +static const uint8_t NTAG_PAGE_SIZE = 4; +static const uint8_t NTAG_213_MAX_PAGE = 45; // NTAG213 has 180 bytes of memory (45 pages * 4 bytes per page) +static const uint8_t NTAG_215_MAX_PAGE = 135; // NTAG215 has 540 bytes of memory (135 pages * 4 bytes per page) +static const uint8_t NTAG_216_MAX_PAGE = 231; // NTAG216 has 924 bytes of memory (231 pages * 4 bytes per page) + // Mifare Commands static const uint8_t MIFARE_CMD_AUTH_A = 0x60; static const uint8_t MIFARE_CMD_AUTH_B = 0x61; @@ -56,7 +64,8 @@ static const uint8_t MAD_KEY[6] = {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5}; std::string format_uid(std::vector &uid); std::string format_bytes(std::vector &bytes); -uint8_t guess_tag_type(uint8_t uid_length); +// uint8_t guess_tag_type(uint8_t uid_length);//TODO: deprecate guess_tag_type +uint8_t guess_tag_type(uint8_t uid_length, const std::vector &first_page); uint8_t get_mifare_classic_ndef_start_index(std::vector &data); bool decode_mifare_classic_tlv(std::vector &data, uint32_t &message_length, uint8_t &message_start_index); uint32_t get_mifare_classic_buffer_size(uint32_t message_length); @@ -66,6 +75,10 @@ bool mifare_classic_is_trailer_block(uint8_t block_num); uint32_t get_mifare_ultralight_buffer_size(uint32_t message_length); +bool is_ntag(const std::vector &first_page); +uint32_t get_ntag_buffer_size(uint32_t message_length); +bool decode_ntag_tlv(const std::vector &data, uint32_t &message_length, uint8_t &message_start_index); + class NfcTagListener { public: virtual void tag_off(NfcTag &tag) {} From 2d95a58b3764e39de5e3440e6576effa55514f69 Mon Sep 17 00:00:00 2001 From: Spencer Owen Date: Mon, 9 Dec 2024 21:12:48 -0700 Subject: [PATCH 2/5] fix function def --- esphome/components/nfc/nfc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/nfc/nfc.cpp b/esphome/components/nfc/nfc.cpp index 71ef842b37..f94cdc79c2 100644 --- a/esphome/components/nfc/nfc.cpp +++ b/esphome/components/nfc/nfc.cpp @@ -31,7 +31,7 @@ std::string format_bytes(std::vector &bytes) { return std::string(buf); } -uint8_t is_ntag(const std::vector &first_page) { +bool is_ntag(const std::vector &first_page) { // NTAG typically has a capability container (CC) starting at byte 3 // The CC for NTAG is usually 0xE1 0x10 followed by a type-specific byte if (first_page.size() >= 16 && first_page[3] == 0xE1 && first_page[4] == 0x10) { From 5b31c0404f3c4e803213d3c8938dd5477e2adab2 Mon Sep 17 00:00:00 2001 From: Spencer Owen Date: Mon, 9 Dec 2024 21:27:15 -0700 Subject: [PATCH 3/5] Add read_page to get NTAG cc data --- esphome/components/pn532/pn532.cpp | 70 ++++++++++++++++++++++++------ esphome/components/pn532/pn532.h | 2 + 2 files changed, 59 insertions(+), 13 deletions(-) diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index 8088e6c022..1d306c9b22 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -357,20 +357,64 @@ void PN532::turn_off_rf_() { }); } -std::unique_ptr PN532::read_tag_(std::vector &uid) { - uint8_t type = nfc::guess_tag_type(uid.size()); +bool PN532::ntag2xx_read_page(uint8_t page_number, std::vector &data) { + std::vector command = { + PN532_COMMAND_INDATAEXCHANGE, + 0x01, // Card number + 0x30, // MIFARE Read command + page_number // Page to read + }; - if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) { - ESP_LOGD(TAG, "Mifare classic"); - return this->read_mifare_classic_tag_(uid); - } else if (type == nfc::TAG_TYPE_2) { - ESP_LOGD(TAG, "Mifare ultralight"); - return this->read_mifare_ultralight_tag_(uid); - } else if (type == nfc::TAG_TYPE_UNKNOWN) { - ESP_LOGV(TAG, "Cannot determine tag type"); - return make_unique(uid); - } else { - return make_unique(uid); + if (!this->write_command_(command)) { + ESP_LOGE(TAG, "Error writing read command"); + return false; + } + + if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, data)) { + ESP_LOGE(TAG, "Error reading page data"); + return false; + } + + // Remove status byte + data.erase(data.begin()); + + return true; +} + +std::unique_ptr PN532::read_tag_(std::vector &uid) { + std::vector first_page(16); // 4 pages, 4 bytes each + for (uint8_t i = 0; i < 4; i++) { + uint8_t page_data[4]; + if (this->ntag2xx_read_page(i, page_data)) { + std::copy(page_data, page_data + 4, first_page.begin() + i * 4); + } else { + ESP_LOGE(TAG, "Failed to read page %d", i); + return make_unique(uid); + } + } + + uint8_t type = nfc::guess_tag_type(uid.size(), first_page); + + switch (type) { + case nfc::TAG_TYPE_MIFARE_CLASSIC: + ESP_LOGD(TAG, "Mifare classic"); + return this->read_mifare_classic_tag_(uid); + case nfc::TAG_TYPE_2: + ESP_LOGD(TAG, "Mifare ultralight"); + return this->read_mifare_ultralight_tag_(uid); + case nfc::TAG_TYPE_NTAG_213: + ESP_LOGD(TAG, "NTAG213"); + return this->read_ntag_tag_(uid, nfc::TAG_TYPE_NTAG_213); + case nfc::TAG_TYPE_NTAG_215: + ESP_LOGD(TAG, "NTAG215"); + return this->read_ntag_tag_(uid, nfc::TAG_TYPE_NTAG_215); + case nfc::TAG_TYPE_NTAG_216: + ESP_LOGD(TAG, "NTAG216"); + return this->read_ntag_tag_(uid, nfc::TAG_TYPE_NTAG_216); + case nfc::TAG_TYPE_UNKNOWN: + default: + ESP_LOGV(TAG, "Cannot determine tag type"); + return make_unique(uid); } } diff --git a/esphome/components/pn532/pn532.h b/esphome/components/pn532/pn532.h index 8194d86477..fdafd4f686 100644 --- a/esphome/components/pn532/pn532.h +++ b/esphome/components/pn532/pn532.h @@ -93,6 +93,8 @@ class PN532 : public PollingComponent { bool write_mifare_ultralight_tag_(std::vector &uid, nfc::NdefMessage *message); bool clean_mifare_ultralight_(); + bool ntag2xx_read_page(uint8_t page_number, std::vector &data); + bool updates_enabled_{true}; bool requested_read_{false}; std::vector binary_sensors_; From 59004bf569acb214c2e7b51eb15b47d86c9cd5b4 Mon Sep 17 00:00:00 2001 From: Spencer Owen Date: Mon, 9 Dec 2024 21:49:07 -0700 Subject: [PATCH 4/5] Add missing function read_ntag_tag_ --- esphome/components/pn532/pn532.cpp | 61 +++++++++++++++++++++++++++++- esphome/components/pn532/pn532.h | 1 + 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index 1d306c9b22..860657329c 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -384,9 +384,9 @@ bool PN532::ntag2xx_read_page(uint8_t page_number, std::vector &data) { std::unique_ptr PN532::read_tag_(std::vector &uid) { std::vector first_page(16); // 4 pages, 4 bytes each for (uint8_t i = 0; i < 4; i++) { - uint8_t page_data[4]; + std::vector page_data(4); if (this->ntag2xx_read_page(i, page_data)) { - std::copy(page_data, page_data + 4, first_page.begin() + i * 4); + first_page.insert(first_page.begin() + i * 4, page_data.begin(), page_data.end()); } else { ESP_LOGE(TAG, "Failed to read page %d", i); return make_unique(uid); @@ -418,6 +418,63 @@ std::unique_ptr PN532::read_tag_(std::vector &uid) { } } +std::unique_ptr PN532::read_ntag_tag_(std::vector &uid, uint8_t tag_type) { + std::vector data(4); + uint8_t max_page; + + switch (tag_type) { + case nfc::TAG_TYPE_NTAG_213: + max_page = 39; + break; + case nfc::TAG_TYPE_NTAG_215: + max_page = 129; + break; + case nfc::TAG_TYPE_NTAG_216: + max_page = 225; + break; + default: + ESP_LOGE(TAG, "Unknown NTAG type"); + return nullptr; + } + + std::vector tag_data; + for (uint8_t page = 4; page <= max_page; page++) { + if (!this->ntag2xx_read_page(page, data)) { + ESP_LOGE(TAG, "Failed to read page %d", page); + return nullptr; + } + tag_data.insert(tag_data.end(), data.begin(), data.end()); + } + + return make_unique(uid, tag_data, tag_type); +} + +bool PN532::ntag2xx_read_page(uint8_t page, std::vector &data) { + std::vector command = { + PN532_COMMAND_INDATAEXCHANGE, + 0x01, // Card number + 0x30, // MIFARE Read command + page // Page to read + }; + + if (!this->write_command_(command)) { + ESP_LOGE(TAG, "Error writing read command"); + return false; + } + + std::vector response; + if (!this->read_response(PN532_COMMAND_INDATAEXCHANGE, response)) { + ESP_LOGE(TAG, "Error reading page data"); + return false; + } + + // Remove status byte + response.erase(response.begin()); + data = response; + + return true; +} + void PN532::read_mode() { this->next_task_ = READ; ESP_LOGD(TAG, "Waiting to read next tag"); diff --git a/esphome/components/pn532/pn532.h b/esphome/components/pn532/pn532.h index fdafd4f686..b503f8baa2 100644 --- a/esphome/components/pn532/pn532.h +++ b/esphome/components/pn532/pn532.h @@ -70,6 +70,7 @@ class PN532 : public PollingComponent { virtual bool read_response(uint8_t command, std::vector &data) = 0; std::unique_ptr read_tag_(std::vector &uid); + std::unique_ptr PN532::read_ntag_tag_(std::vector &uid, uint8_t tag_type); bool format_tag_(std::vector &uid); bool clean_tag_(std::vector &uid); From 422e699aa69284416b650fca3e5295144c39c7ac Mon Sep 17 00:00:00 2001 From: Spencer Owen Date: Mon, 9 Dec 2024 22:08:09 -0700 Subject: [PATCH 5/5] Add first page to clean_tag --- esphome/components/pn532/pn532.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index 860657329c..a20f94f162 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -494,7 +494,13 @@ void PN532::write_mode(nfc::NdefMessage *message) { } bool PN532::clean_tag_(std::vector &uid) { - uint8_t type = nfc::guess_tag_type(uid.size()); + std::vector first_page(16); + if (!this->ntag2xx_read_page(4, first_page)) { + ESP_LOGE(TAG, "Failed to read first page"); + return false; + } + + uint8_t type = nfc::guess_tag_type(uid.size(), first_page); if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) { return this->format_mifare_classic_mifare_(uid); } else if (type == nfc::TAG_TYPE_2) {