From 52e13164b43f7241326f0a9e3004c5a2a6dee822 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 15 Jan 2021 09:29:55 +1300 Subject: [PATCH] Add NDEF reading and writing to PN532 (#1351) --- CODEOWNERS | 1 + esphome/components/nfc/__init__.py | 7 + esphome/components/nfc/ndef_message.cpp | 106 ++++++++ esphome/components/nfc/ndef_message.h | 31 +++ esphome/components/nfc/ndef_record.cpp | 85 ++++++ esphome/components/nfc/ndef_record.h | 101 +++++++ esphome/components/nfc/nfc.cpp | 107 ++++++++ esphome/components/nfc/nfc.h | 57 ++++ esphome/components/nfc/nfc_tag.cpp | 9 + esphome/components/nfc/nfc_tag.h | 47 ++++ esphome/components/pn532/__init__.py | 34 ++- esphome/components/pn532/pn532.cpp | 179 +++++++++---- esphome/components/pn532/pn532.h | 66 ++++- .../components/pn532/pn532_mifare_classic.cpp | 249 ++++++++++++++++++ .../pn532/pn532_mifare_ultralight.cpp | 180 +++++++++++++ esphome/components/pn532_i2c/pn532_i2c.cpp | 2 +- 16 files changed, 1206 insertions(+), 55 deletions(-) create mode 100644 esphome/components/nfc/__init__.py create mode 100644 esphome/components/nfc/ndef_message.cpp create mode 100644 esphome/components/nfc/ndef_message.h create mode 100644 esphome/components/nfc/ndef_record.cpp create mode 100644 esphome/components/nfc/ndef_record.h create mode 100644 esphome/components/nfc/nfc.cpp create mode 100644 esphome/components/nfc/nfc.h create mode 100644 esphome/components/nfc/nfc_tag.cpp create mode 100644 esphome/components/nfc/nfc_tag.h create mode 100644 esphome/components/pn532/pn532_mifare_classic.cpp create mode 100644 esphome/components/pn532/pn532_mifare_ultralight.cpp diff --git a/CODEOWNERS b/CODEOWNERS index fb287b1cc9..5cf0f591d2 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -48,6 +48,7 @@ esphome/components/mcp23s17/* @SenexCrenshaw esphome/components/mcp2515/* @danielschramm @mvturnho esphome/components/mcp9808/* @k7hpn esphome/components/network/* @esphome/core +esphome/components/nfc/* @jesserockz esphome/components/ota/* @esphome/core esphome/components/output/* @esphome/core esphome/components/pid/* @OttoWinter diff --git a/esphome/components/nfc/__init__.py b/esphome/components/nfc/__init__.py new file mode 100644 index 0000000000..ae3c9a4c0a --- /dev/null +++ b/esphome/components/nfc/__init__.py @@ -0,0 +1,7 @@ +import esphome.codegen as cg + +CODEOWNERS = ['@jesserockz'] + +nfc_ns = cg.esphome_ns.namespace('nfc') + +NfcTag = nfc_ns.class_('NfcTag') diff --git a/esphome/components/nfc/ndef_message.cpp b/esphome/components/nfc/ndef_message.cpp new file mode 100644 index 0000000000..caba833932 --- /dev/null +++ b/esphome/components/nfc/ndef_message.cpp @@ -0,0 +1,106 @@ +#include "ndef_message.h" + +namespace esphome { +namespace nfc { + +static const char *TAG = "nfc.ndef_message"; + +NdefMessage::NdefMessage(std::vector &data) { + ESP_LOGV(TAG, "Building NdefMessage with %zu bytes", data.size()); + uint8_t index = 0; + while (index <= data.size()) { + uint8_t tnf_byte = data[index++]; + bool me = tnf_byte & 0x40; + bool sr = tnf_byte & 0x10; + bool il = tnf_byte & 0x08; + uint8_t tnf = tnf_byte & 0x07; + + ESP_LOGVV(TAG, "me=%s, sr=%s, il=%s, tnf=%d", YESNO(me), YESNO(sr), YESNO(il), tnf); + + auto record = new NdefRecord(); + record->set_tnf(tnf); + + uint8_t type_length = data[index++]; + uint32_t payload_length = 0; + if (sr) { + payload_length = data[index++]; + } else { + payload_length = (static_cast(data[index]) << 24) | (static_cast(data[index + 1]) << 16) | + (static_cast(data[index + 2]) << 8) | static_cast(data[index + 3]); + index += 4; + } + + uint8_t id_length = 0; + if (il) { + id_length = data[index++]; + } + + ESP_LOGVV(TAG, "Lengths: type=%d, payload=%d, id=%d", type_length, payload_length, id_length); + + std::string type_str(data.begin() + index, data.begin() + index + type_length); + record->set_type(type_str); + index += type_length; + + if (il) { + std::string id_str(data.begin() + index, data.begin() + index + id_length); + record->set_id(id_str); + index += id_length; + } + + uint8_t payload_identifier = 0x00; + if (type_str == "U") { + payload_identifier = data[index++]; + payload_length -= 1; + } + + std::string payload_str(data.begin() + index, data.begin() + index + payload_length); + + if (payload_identifier > 0x00 && payload_identifier <= PAYLOAD_IDENTIFIERS_COUNT) { + payload_str.insert(0, PAYLOAD_IDENTIFIERS[payload_identifier]); + } + + record->set_payload(payload_str); + index += payload_length; + + this->add_record(record); + ESP_LOGV(TAG, "Adding record type %s = %s", record->get_type().c_str(), record->get_payload().c_str()); + + if (me) + break; + } +} + +bool NdefMessage::add_record(NdefRecord *record) { + if (this->records_.size() >= MAX_NDEF_RECORDS) { + ESP_LOGE(TAG, "Too many records. Max: %d", MAX_NDEF_RECORDS); + return false; + } + this->records_.push_back(record); + return true; +} + +bool NdefMessage::add_text_record(const std::string &text) { return this->add_text_record(text, "en"); }; + +bool NdefMessage::add_text_record(const std::string &text, const std::string &encoding) { + std::string payload = to_string(text.length()) + encoding + text; + auto r = new NdefRecord(TNF_WELL_KNOWN, "T", payload); + return this->add_record(r); +} + +bool NdefMessage::add_uri_record(const std::string &uri) { + auto r = new NdefRecord(TNF_WELL_KNOWN, "U", uri); + return this->add_record(r); +} + +std::vector NdefMessage::encode() { + std::vector data; + + for (uint8_t i = 0; i < this->records_.size(); i++) { + auto encoded_record = this->records_[i]->encode(i == 0, (i + 1) == this->records_.size()); + data.insert(data.end(), encoded_record.begin(), encoded_record.end()); + } + return data; +} + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/ndef_message.h b/esphome/components/nfc/ndef_message.h new file mode 100644 index 0000000000..e47a7b992a --- /dev/null +++ b/esphome/components/nfc/ndef_message.h @@ -0,0 +1,31 @@ +#pragma once + +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "ndef_record.h" + +namespace esphome { +namespace nfc { + +static const uint8_t MAX_NDEF_RECORDS = 4; + +class NdefMessage { + public: + NdefMessage(){}; + NdefMessage(std::vector &data); + + std::vector get_records() { return this->records_; }; + + bool add_record(NdefRecord *record); + bool add_text_record(const std::string &text); + bool add_text_record(const std::string &text, const std::string &encoding); + bool add_uri_record(const std::string &uri); + + std::vector encode(); + + protected: + std::vector records_; +}; + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/ndef_record.cpp b/esphome/components/nfc/ndef_record.cpp new file mode 100644 index 0000000000..53fa221e41 --- /dev/null +++ b/esphome/components/nfc/ndef_record.cpp @@ -0,0 +1,85 @@ +#include "ndef_record.h" + +namespace esphome { +namespace nfc { + +static const char* TAG = "nfc.ndef_record"; + +uint32_t NdefRecord::get_encoded_size() { + uint32_t size = 2; + if (this->payload_.length() > 255) { + size += 4; + } else { + size += 1; + } + if (this->id_.length()) { + size += 1; + } + size += (this->type_.length() + this->payload_.length() + this->id_.length()); + return size; +} + +std::vector NdefRecord::encode(bool first, bool last) { + std::vector data; + + data.push_back(this->get_tnf_byte(first, last)); + + data.push_back(this->type_.length()); + + uint8_t payload_prefix = 0x00; + uint8_t payload_prefix_length = 0x00; + for (uint8_t i = 1; i < PAYLOAD_IDENTIFIERS_COUNT; i++) { + std::string prefix = PAYLOAD_IDENTIFIERS[i]; + if (this->payload_.substr(0, prefix.length()).find(prefix) != std::string::npos) { + payload_prefix = i; + payload_prefix_length = prefix.length(); + break; + } + } + + uint32_t payload_length = this->payload_.length() - payload_prefix_length + 1; + + if (payload_length <= 255) { + data.push_back(payload_length); + } else { + data.push_back(0); + data.push_back(0); + data.push_back((payload_length >> 8) & 0xFF); + data.push_back(payload_length & 0xFF); + } + + if (this->id_.length()) { + data.push_back(this->id_.length()); + } + + data.insert(data.end(), this->type_.begin(), this->type_.end()); + + if (this->id_.length()) { + data.insert(data.end(), this->id_.begin(), this->id_.end()); + } + + data.push_back(payload_prefix); + + data.insert(data.end(), this->payload_.begin() + payload_prefix_length, this->payload_.end()); + return data; +} + +uint8_t NdefRecord::get_tnf_byte(bool first, bool last) { + uint8_t value = this->tnf_; + if (first) { + value = value | 0x80; + } + if (last) { + value = value | 0x40; + } + if (this->payload_.length() <= 255) { + value = value | 0x10; + } + if (this->id_.length()) { + value = value | 0x08; + } + return value; +}; + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/ndef_record.h b/esphome/components/nfc/ndef_record.h new file mode 100644 index 0000000000..bc03ccf093 --- /dev/null +++ b/esphome/components/nfc/ndef_record.h @@ -0,0 +1,101 @@ +#pragma once + +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace nfc { + +static const uint8_t TNF_EMPTY = 0x00; +static const uint8_t TNF_WELL_KNOWN = 0x01; +static const uint8_t TNF_MIME_MEDIA = 0x02; +static const uint8_t TNF_ABSOLUTE_URI = 0x03; +static const uint8_t TNF_EXTERNAL_TYPE = 0x04; +static const uint8_t TNF_UNKNOWN = 0x05; +static const uint8_t TNF_UNCHANGED = 0x06; +static const uint8_t TNF_RESERVED = 0x07; + +static const uint8_t PAYLOAD_IDENTIFIERS_COUNT = 0x23; +static const char *PAYLOAD_IDENTIFIERS[] = {"", + "http://www.", + "https://www.", + "http://", + "https://", + "tel:", + "mailto:", + "ftp://anonymous:anonymous@", + "ftp://ftp.", + "ftps://", + "sftp://", + "smb://", + "nfs://", + "ftp://", + "dav://", + "news:", + "telnet://", + "imap:", + "rtsp://", + "urn:", + "pop:", + "sip:", + "sips:", + "tftp:", + "btspp://", + "btl2cap://", + "btgoep://", + "tcpobex://", + "irdaobex://", + "file://", + "urn:epc:id:", + "urn:epc:tag:", + "urn:epc:pat:", + "urn:epc:raw:", + "urn:epc:", + "urn:nfc:"}; + +class NdefRecord { + public: + NdefRecord(){}; + NdefRecord(uint8_t tnf, const std::string &type, const std::string &payload) { + this->tnf_ = tnf; + this->type_ = type; + this->set_payload(payload); + }; + NdefRecord(uint8_t tnf, const std::string &type, const std::string &payload, const std::string &id) { + this->tnf_ = tnf; + this->type_ = type; + this->set_payload(payload); + this->id_ = id; + }; + NdefRecord(const NdefRecord &rhs) { + this->tnf_ = rhs.tnf_; + this->type_ = rhs.type_; + this->payload_ = rhs.payload_; + this->payload_identifier_ = rhs.payload_identifier_; + this->id_ = rhs.id_; + }; + void set_tnf(uint8_t tnf) { this->tnf_ = tnf; }; + void set_type(const std::string &type) { this->type_ = type; }; + void set_payload_identifier(uint8_t payload_identifier) { this->payload_identifier_ = payload_identifier; }; + void set_payload(const std::string &payload) { this->payload_ = payload; }; + void set_id(const std::string &id) { this->id_ = id; }; + + uint32_t get_encoded_size(); + + std::vector encode(bool first, bool last); + uint8_t get_tnf_byte(bool first, bool last); + + const std::string &get_type() { return this->type_; }; + const std::string &get_id() { return this->id_; }; + const std::string &get_payload() { return this->payload_; }; + + protected: + uint8_t tnf_; + std::string type_; + uint8_t payload_identifier_; + std::string payload_; + std::string id_; +}; + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/nfc.cpp b/esphome/components/nfc/nfc.cpp new file mode 100644 index 0000000000..d1f7cbe15f --- /dev/null +++ b/esphome/components/nfc/nfc.cpp @@ -0,0 +1,107 @@ +#include "nfc.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nfc { + +static const char *TAG = "nfc"; + +std::string format_uid(std::vector &uid) { + char buf[(uid.size() * 2) + uid.size() - 1]; + int offset = 0; + for (uint8_t i = 0; i < uid.size(); i++) { + const char *format = "%02X"; + if (i + 1 < uid.size()) + format = "%02X-"; + offset += sprintf(buf + offset, format, uid[i]); + } + return std::string(buf); +} + +std::string format_bytes(std::vector &bytes) { + char buf[(bytes.size() * 2) + bytes.size() - 1]; + int offset = 0; + for (uint8_t i = 0; i < bytes.size(); i++) { + const char *format = "%02X"; + if (i + 1 < bytes.size()) + format = "%02X "; + offset += sprintf(buf + offset, format, bytes[i]); + } + return std::string(buf); +} + +uint8_t guess_tag_type(uint8_t uid_length) { + if (uid_length == 4) { + return TAG_TYPE_MIFARE_CLASSIC; + } else { + return TAG_TYPE_2; + } +} + +uint8_t get_mifare_classic_ndef_start_index(std::vector &data) { + for (uint8_t i = 0; i < MIFARE_CLASSIC_BLOCK_SIZE; i++) { + if (data[i] == 0x00) { + // Do nothing, skip + } else if (data[i] == 0x03) { + return i; + } else { + return -2; + } + } + return -1; +} + +bool decode_mifare_classic_tlv(std::vector &data, uint32_t &message_length, uint8_t &message_start_index) { + uint8_t i = get_mifare_classic_ndef_start_index(data); + if (i < 0 || data[i] != 0x03) { + ESP_LOGE(TAG, "Error, Can't decode message length."); + return false; + } + if (data[i + 1] == 0xFF) { + message_length = ((0xFF & data[i + 2]) << 8) | (0xFF & data[i + 3]); + message_start_index = i + MIFARE_CLASSIC_LONG_TLV_SIZE; + } else { + message_length = data[i + 1]; + message_start_index = i + MIFARE_CLASSIC_SHORT_TLV_SIZE; + } + return true; +} + +uint32_t get_mifare_ultralight_buffer_size(uint32_t message_length) { + uint32_t buffer_size = message_length + 2 + 1; + if (buffer_size % MIFARE_ULTRALIGHT_READ_SIZE != 0) + buffer_size = ((buffer_size / MIFARE_ULTRALIGHT_READ_SIZE) + 1) * MIFARE_ULTRALIGHT_READ_SIZE; + return buffer_size; +} + +uint32_t get_mifare_classic_buffer_size(uint32_t message_length) { + uint32_t buffer_size = message_length; + if (message_length < 255) { + buffer_size += MIFARE_CLASSIC_SHORT_TLV_SIZE + 1; + } else { + buffer_size += MIFARE_CLASSIC_LONG_TLV_SIZE + 1; + } + if (buffer_size % MIFARE_CLASSIC_BLOCK_SIZE != 0) { + buffer_size = ((buffer_size / MIFARE_CLASSIC_BLOCK_SIZE) + 1) * MIFARE_CLASSIC_BLOCK_SIZE; + } + return buffer_size; +} + +bool mifare_classic_is_first_block(uint8_t block_num) { + if (block_num < 128) { + return (block_num % 4 == 0); + } else { + return (block_num % 16 == 0); + } +} + +bool mifare_classic_is_trailer_block(uint8_t block_num) { + if (block_num < 128) { + return ((block_num + 1) % 4 == 0); + } else { + return ((block_num + 1) % 16 == 0); + } +} + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/nfc.h b/esphome/components/nfc/nfc.h new file mode 100644 index 0000000000..cf56f9f4de --- /dev/null +++ b/esphome/components/nfc/nfc.h @@ -0,0 +1,57 @@ +#pragma once + +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" +#include "ndef_record.h" +#include "ndef_message.h" +#include "nfc_tag.h" + +namespace esphome { +namespace nfc { + +static const uint8_t MIFARE_CLASSIC_BLOCK_SIZE = 16; +static const uint8_t MIFARE_CLASSIC_LONG_TLV_SIZE = 4; +static const uint8_t MIFARE_CLASSIC_SHORT_TLV_SIZE = 2; + +static const uint8_t MIFARE_ULTRALIGHT_PAGE_SIZE = 4; +static const uint8_t MIFARE_ULTRALIGHT_READ_SIZE = 4; +static const uint8_t MIFARE_ULTRALIGHT_DATA_START_PAGE = 4; +static const uint8_t MIFARE_ULTRALIGHT_MAX_PAGE = 63; + +static const uint8_t TAG_TYPE_MIFARE_CLASSIC = 0; +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_UNKNOWN = 99; + +// Mifare Commands +static const uint8_t MIFARE_CMD_AUTH_A = 0x60; +static const uint8_t MIFARE_CMD_AUTH_B = 0x61; +static const uint8_t MIFARE_CMD_READ = 0x30; +static const uint8_t MIFARE_CMD_WRITE = 0xA0; +static const uint8_t MIFARE_CMD_WRITE_ULTRALIGHT = 0xA2; + +static const char *MIFARE_CLASSIC = "Mifare Classic"; +static const char *NFC_FORUM_TYPE_2 = "NFC Forum Type 2"; +static const char *ERROR = "Error"; + +static const uint8_t DEFAULT_KEY[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; +static const uint8_t NDEF_KEY[6] = {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7}; +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 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); + +bool mifare_classic_is_first_block(uint8_t block_num); +bool mifare_classic_is_trailer_block(uint8_t block_num); + +uint32_t get_mifare_ultralight_buffer_size(uint32_t message_length); + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/nfc_tag.cpp b/esphome/components/nfc/nfc_tag.cpp new file mode 100644 index 0000000000..39e84e2032 --- /dev/null +++ b/esphome/components/nfc/nfc_tag.cpp @@ -0,0 +1,9 @@ +#include "nfc_tag.h" + +namespace esphome { +namespace nfc { + +static const char *TAG = "nfc.tag"; + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/nfc_tag.h b/esphome/components/nfc/nfc_tag.h new file mode 100644 index 0000000000..ff0d1c39b4 --- /dev/null +++ b/esphome/components/nfc/nfc_tag.h @@ -0,0 +1,47 @@ +#pragma once + +#include "esphome/core/log.h" +#include "ndef_message.h" + +namespace esphome { +namespace nfc { + +class NfcTag { + public: + NfcTag() { + this->uid_ = {}; + this->tag_type_ = "Unknown"; + }; + NfcTag(std::vector &uid) { + this->uid_ = uid; + this->tag_type_ = "Unknown"; + }; + NfcTag(std::vector &uid, const std::string &tag_type) { + this->uid_ = uid; + this->tag_type_ = tag_type; + }; + NfcTag(std::vector &uid, const std::string &tag_type, nfc::NdefMessage *ndef_message) { + this->uid_ = uid; + this->tag_type_ = tag_type; + this->ndef_message_ = ndef_message; + }; + NfcTag(std::vector &uid, const std::string &tag_type, std::vector &ndef_data) { + this->uid_ = uid; + this->tag_type_ = tag_type; + this->ndef_message_ = new NdefMessage(ndef_data); + }; + + std::vector &get_uid() { return this->uid_; }; + const std::string &get_tag_type() { return this->tag_type_; }; + bool has_ndef_message() { return this->ndef_message_ != nullptr; }; + NdefMessage *get_ndef_message() { return this->ndef_message_; }; + void set_ndef_message(NdefMessage *ndef_message) { this->ndef_message_ = ndef_message; }; + + protected: + std::vector uid_; + std::string tag_type_; + NdefMessage *ndef_message_{nullptr}; +}; + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/pn532/__init__.py b/esphome/components/pn532/__init__.py index 02215e2f5a..c96ebe2b4d 100644 --- a/esphome/components/pn532/__init__.py +++ b/esphome/components/pn532/__init__.py @@ -1,24 +1,34 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation -from esphome.const import CONF_ON_TAG, CONF_TRIGGER_ID +from esphome.components import nfc +from esphome.const import CONF_ID, CONF_ON_TAG, CONF_TRIGGER_ID from esphome.core import coroutine CODEOWNERS = ['@OttoWinter', '@jesserockz'] -AUTO_LOAD = ['binary_sensor'] +AUTO_LOAD = ['binary_sensor', 'nfc'] MULTI_CONF = True CONF_PN532_ID = 'pn532_id' +CONF_ON_FINISHED_WRITE = 'on_finished_write' pn532_ns = cg.esphome_ns.namespace('pn532') PN532 = pn532_ns.class_('PN532', cg.PollingComponent) -PN532Trigger = pn532_ns.class_('PN532Trigger', automation.Trigger.template(cg.std_string)) +PN532OnTagTrigger = pn532_ns.class_('PN532OnTagTrigger', + automation.Trigger.template(cg.std_string, nfc.NfcTag)) +PN532OnFinishedWriteTrigger = pn532_ns.class_('PN532OnFinishedWriteTrigger', + automation.Trigger.template()) + +PN532IsWritingCondition = pn532_ns.class_('PN532IsWritingCondition', automation.Condition) PN532_SCHEMA = cv.Schema({ cv.GenerateID(): cv.declare_id(PN532), cv.Optional(CONF_ON_TAG): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PN532Trigger), + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PN532OnTagTrigger), + }), + cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation({ + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PN532OnFinishedWriteTrigger), }), }).extend(cv.polling_component_schema('1s')) @@ -36,4 +46,18 @@ def setup_pn532(var, config): for conf in config.get(CONF_ON_TAG, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) cg.add(var.register_trigger(trigger)) - yield automation.build_automation(trigger, [(cg.std_string, 'x')], conf) + yield automation.build_automation(trigger, [(cg.std_string, 'x'), (nfc.NfcTag, 'tag')], + conf) + + for conf in config.get(CONF_ON_FINISHED_WRITE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + yield automation.build_automation(trigger, [], conf) + + +@automation.register_condition('pn532.is_writing', PN532IsWritingCondition, cv.Schema({ + cv.GenerateID(): cv.use_id(PN532), +})) +def pn532_is_writing_to_code(config, condition_id, template_arg, args): + var = cg.new_Pvariable(condition_id, template_arg) + yield cg.register_parented(var, config[CONF_ID]) + yield var diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index a9601185bb..e2511648b6 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -11,18 +11,6 @@ namespace pn532 { static const char *TAG = "pn532"; -std::string format_uid(std::vector &uid) { - char buf[32]; - int offset = 0; - for (uint8_t i = 0; i < uid.size(); i++) { - const char *format = "%02X"; - if (i + 1 < uid.size()) - format = "%02X-"; - offset += sprintf(buf + offset, format, uid[i]); - } - return std::string(buf); -} - void PN532::setup() { ESP_LOGCONFIG(TAG, "Setting up PN532..."); @@ -152,23 +140,56 @@ void PN532::loop() { this->current_uid_ = nfcid; - for (auto *trigger : this->triggers_) - trigger->process(nfcid); + if (next_task_ == READ) { + auto tag = this->read_tag_(nfcid); + for (auto *trigger : this->triggers_) + trigger->process(tag); - if (report) { - ESP_LOGD(TAG, "Found new tag '%s'", format_uid(nfcid).c_str()); + if (report) { + ESP_LOGD(TAG, "Found new tag '%s'", nfc::format_uid(nfcid).c_str()); + if (tag->has_ndef_message()) { + auto message = tag->get_ndef_message(); + auto records = message->get_records(); + ESP_LOGD(TAG, " NDEF formatted records:"); + for (auto &record : records) { + ESP_LOGD(TAG, " %s - %s", record->get_type().c_str(), record->get_payload().c_str()); + } + } + } + } else if (next_task_ == CLEAN) { + ESP_LOGD(TAG, " Tag cleaning..."); + if (!this->clean_tag_(nfcid)) { + ESP_LOGE(TAG, " Tag was not fully cleaned successfully"); + } + ESP_LOGD(TAG, " Tag cleaned!"); + } else if (next_task_ == FORMAT) { + ESP_LOGD(TAG, " Tag formatting..."); + if (!this->format_tag_(nfcid)) { + ESP_LOGE(TAG, "Error formatting tag as NDEF"); + } + ESP_LOGD(TAG, " Tag formatted!"); + } else if (next_task_ == WRITE) { + if (this->next_task_message_to_write_ != nullptr) { + ESP_LOGD(TAG, " Tag writing..."); + ESP_LOGD(TAG, " Tag formatting..."); + if (!this->format_tag_(nfcid)) { + ESP_LOGE(TAG, " Tag could not be formatted for writing"); + } else { + ESP_LOGD(TAG, " Writing NDEF data"); + if (!this->write_tag_(nfcid, this->next_task_message_to_write_)) { + ESP_LOGE(TAG, " Failed to write message to tag"); + } + ESP_LOGD(TAG, " Finished writing NDEF data"); + delete this->next_task_message_to_write_; + this->next_task_message_to_write_ = nullptr; + this->on_finished_write_callback_.call(); + } + } } - this->turn_off_rf_(); -} + this->read_mode(); -void PN532::turn_off_rf_() { - ESP_LOGVV(TAG, "Turning RF field OFF"); - this->write_command_({ - PN532_COMMAND_RFCONFIGURATION, - 0x1, // RF Field - 0x0 // Off - }); + this->turn_off_rf_(); } bool PN532::write_command_(const std::vector &data) { @@ -208,6 +229,22 @@ bool PN532::write_command_(const std::vector &data) { return this->read_ack_(); } +bool PN532::read_ack_() { + ESP_LOGVV(TAG, "Reading ACK..."); + + std::vector data; + if (!this->read_data(data, 6)) { + return false; + } + + bool matches = (data[1] == 0x00 && // preamble + data[2] == 0x00 && // start of packet + data[3] == 0xFF && data[4] == 0x00 && // ACK packet code + data[5] == 0xFF && data[6] == 0x00); // postamble + ESP_LOGVV(TAG, "ACK valid: %s", YESNO(matches)); + return matches; +} + bool PN532::read_response_(uint8_t command, std::vector &data) { ESP_LOGV(TAG, "Reading response"); uint8_t len = this->read_response_length_(); @@ -258,13 +295,6 @@ bool PN532::read_response_(uint8_t command, std::vector &data) { data.erase(data.begin(), data.begin() + 2); // Remove TFI and command code data.erase(data.end() - 2, data.end()); // Remove checksum and postamble -#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE - ESP_LOGD(TAG, "PN532 Data Frame: (%u)", data.size()); // NOLINT - for (uint8_t dat : data) { - ESP_LOGD(TAG, " 0x%02X", dat); - } -#endif - return true; } @@ -299,20 +329,81 @@ uint8_t PN532::read_response_length_() { return len; } -bool PN532::read_ack_() { - ESP_LOGVV(TAG, "Reading ACK..."); +void PN532::turn_off_rf_() { + ESP_LOGVV(TAG, "Turning RF field OFF"); + this->write_command_({ + PN532_COMMAND_RFCONFIGURATION, + 0x01, // RF Field + 0x00, // Off + }); +} - std::vector data; - if (!this->read_data(data, 6)) { - return false; +nfc::NfcTag *PN532::read_tag_(std::vector &uid) { + uint8_t type = nfc::guess_tag_type(uid.size()); + + 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 new nfc::NfcTag(uid); + } else { + return new nfc::NfcTag(uid); } +} - bool matches = (data[1] == 0x00 && // preamble - data[2] == 0x00 && // start of packet - data[3] == 0xFF && data[4] == 0x00 && // ACK packet code - data[5] == 0xFF && data[6] == 0x00); // postamble - ESP_LOGVV(TAG, "ACK valid: %s", YESNO(matches)); - return matches; +void PN532::read_mode() { + this->next_task_ = READ; + ESP_LOGD(TAG, "Waiting to read next tag"); +} +void PN532::clean_mode() { + this->next_task_ = CLEAN; + ESP_LOGD(TAG, "Waiting to clean next tag"); +} +void PN532::format_mode() { + this->next_task_ = FORMAT; + ESP_LOGD(TAG, "Waiting to format next tag"); +} +void PN532::write_mode(nfc::NdefMessage *message) { + this->next_task_ = WRITE; + this->next_task_message_to_write_ = message; + ESP_LOGD(TAG, "Waiting to write next tag"); +} + +bool PN532::clean_tag_(std::vector &uid) { + uint8_t type = nfc::guess_tag_type(uid.size()); + if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) { + return this->format_mifare_classic_mifare_(uid); + } else if (type == nfc::TAG_TYPE_2) { + return this->clean_mifare_ultralight_(); + } + ESP_LOGE(TAG, "Unsupported Tag for formatting"); + return false; +} + +bool PN532::format_tag_(std::vector &uid) { + uint8_t type = nfc::guess_tag_type(uid.size()); + if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) { + return this->format_mifare_classic_ndef_(uid); + } else if (type == nfc::TAG_TYPE_2) { + return this->clean_mifare_ultralight_(); + } + ESP_LOGE(TAG, "Unsupported Tag for formatting"); + return false; +} + +bool PN532::write_tag_(std::vector &uid, nfc::NdefMessage *message) { + uint8_t type = nfc::guess_tag_type(uid.size()); + if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) { + return this->write_mifare_classic_tag_(uid, message); + } else if (type == nfc::TAG_TYPE_2) { + return this->write_mifare_ultralight_tag_(uid, message); + } + ESP_LOGE(TAG, "Unsupported Tag for formatting"); + return false; } float PN532::get_setup_priority() const { return setup_priority::DATA; } @@ -350,7 +441,7 @@ bool PN532BinarySensor::process(std::vector &data) { this->found_ = true; return true; } -void PN532Trigger::process(std::vector &data) { this->trigger(format_uid(data)); } +void PN532OnTagTrigger::process(nfc::NfcTag *tag) { this->trigger(nfc::format_uid(tag->get_uid()), *tag); } } // namespace pn532 } // namespace esphome diff --git a/esphome/components/pn532/pn532.h b/esphome/components/pn532/pn532.h index af49a02400..6b3bef2918 100644 --- a/esphome/components/pn532/pn532.h +++ b/esphome/components/pn532/pn532.h @@ -3,6 +3,8 @@ #include "esphome/core/component.h" #include "esphome/core/automation.h" #include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/nfc/nfc_tag.h" +#include "esphome/components/nfc/nfc.h" namespace esphome { namespace pn532 { @@ -14,7 +16,7 @@ static const uint8_t PN532_COMMAND_INDATAEXCHANGE = 0x40; static const uint8_t PN532_COMMAND_INLISTPASSIVETARGET = 0x4A; class PN532BinarySensor; -class PN532Trigger; +class PN532OnTagTrigger; class PN532 : public PollingComponent { public: @@ -28,7 +30,18 @@ class PN532 : public PollingComponent { void loop() override; void register_tag(PN532BinarySensor *tag) { this->binary_sensors_.push_back(tag); } - void register_trigger(PN532Trigger *trig) { this->triggers_.push_back(trig); } + void register_trigger(PN532OnTagTrigger *trig) { this->triggers_.push_back(trig); } + + void add_on_finished_write_callback(std::function callback) { + this->on_finished_write_callback_.add(std::move(callback)); + } + + bool is_writing() { return this->next_task_ != READ; }; + + void read_mode(); + void clean_mode(); + void format_mode(); + void write_mode(nfc::NdefMessage *message); protected: void turn_off_rf_(); @@ -40,15 +53,46 @@ class PN532 : public PollingComponent { virtual bool write_data(const std::vector &data) = 0; virtual bool read_data(std::vector &data, uint8_t len) = 0; + nfc::NfcTag *read_tag_(std::vector &uid); + + bool format_tag_(std::vector &uid); + bool clean_tag_(std::vector &uid); + bool write_tag_(std::vector &uid, nfc::NdefMessage *message); + + nfc::NfcTag *read_mifare_classic_tag_(std::vector &uid); + bool read_mifare_classic_block_(uint8_t block_num, std::vector &data); + bool write_mifare_classic_block_(uint8_t block_num, std::vector &data); + bool auth_mifare_classic_block_(std::vector &uid, uint8_t block_num, uint8_t key_num, const uint8_t *key); + bool format_mifare_classic_mifare_(std::vector &uid); + bool format_mifare_classic_ndef_(std::vector &uid); + bool write_mifare_classic_tag_(std::vector &uid, nfc::NdefMessage *message); + + nfc::NfcTag *read_mifare_ultralight_tag_(std::vector &uid); + bool read_mifare_ultralight_page_(uint8_t page_num, std::vector &data); + bool is_mifare_ultralight_formatted_(); + uint16_t read_mifare_ultralight_capacity_(); + bool find_mifare_ultralight_ndef_(uint8_t &message_length, uint8_t &message_start_index); + bool write_mifare_ultralight_page_(uint8_t page_num, std::vector &write_data); + bool write_mifare_ultralight_tag_(std::vector &uid, nfc::NdefMessage *message); + bool clean_mifare_ultralight_(); + bool requested_read_{false}; std::vector binary_sensors_; - std::vector triggers_; + std::vector triggers_; std::vector current_uid_; + nfc::NdefMessage *next_task_message_to_write_; + enum NfcTask { + READ = 0, + CLEAN, + FORMAT, + WRITE, + } next_task_{READ}; enum PN532Error { NONE = 0, WAKEUP_FAILED, SAM_COMMAND_FAILED, } error_code_{NONE}; + CallbackManager on_finished_write_callback_; }; class PN532BinarySensor : public binary_sensor::BinarySensor { @@ -69,9 +113,21 @@ class PN532BinarySensor : public binary_sensor::BinarySensor { bool found_{false}; }; -class PN532Trigger : public Trigger { +class PN532OnTagTrigger : public Trigger { public: - void process(std::vector &data); + void process(nfc::NfcTag *tag); +}; + +class PN532OnFinishedWriteTrigger : public Trigger<> { + public: + explicit PN532OnFinishedWriteTrigger(PN532 *parent) { + parent->add_on_finished_write_callback([this]() { this->trigger(); }); + } +}; + +template class PN532IsWritingCondition : public Condition, public Parented { + public: + bool check(Ts... x) override { return this->parent_->is_writing(); } }; } // namespace pn532 diff --git a/esphome/components/pn532/pn532_mifare_classic.cpp b/esphome/components/pn532/pn532_mifare_classic.cpp new file mode 100644 index 0000000000..4e8c255755 --- /dev/null +++ b/esphome/components/pn532/pn532_mifare_classic.cpp @@ -0,0 +1,249 @@ +#include "pn532.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace pn532 { + +static const char *TAG = "pn532.mifare_classic"; + +nfc::NfcTag *PN532::read_mifare_classic_tag_(std::vector &uid) { + uint8_t current_block = 4; + uint8_t message_start_index = 0; + uint32_t message_length = 0; + + if (this->auth_mifare_classic_block_(uid, current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY)) { + std::vector data; + if (this->read_mifare_classic_block_(current_block, data)) { + if (!nfc::decode_mifare_classic_tlv(data, message_length, message_start_index)) { + return new nfc::NfcTag(uid, nfc::ERROR); + } + } else { + ESP_LOGE(TAG, "Failed to read block %d", current_block); + return new nfc::NfcTag(uid, nfc::MIFARE_CLASSIC); + } + } else { + ESP_LOGV(TAG, "Tag is not NDEF formatted"); + return new nfc::NfcTag(uid, nfc::MIFARE_CLASSIC); + } + + uint32_t index = 0; + uint32_t buffer_size = nfc::get_mifare_classic_buffer_size(message_length); + std::vector buffer; + + while (index < buffer_size) { + if (nfc::mifare_classic_is_first_block(current_block)) { + if (!this->auth_mifare_classic_block_(uid, current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY)) { + ESP_LOGE(TAG, "Error, Block authentication failed for %d", current_block); + } + } + std::vector block_data; + if (this->read_mifare_classic_block_(current_block, block_data)) { + buffer.insert(buffer.end(), block_data.begin(), block_data.end()); + } else { + ESP_LOGE(TAG, "Error reading block %d", current_block); + } + + index += nfc::MIFARE_CLASSIC_BLOCK_SIZE; + current_block++; + + if (nfc::mifare_classic_is_trailer_block(current_block)) { + current_block++; + } + } + buffer.erase(buffer.begin(), buffer.begin() + message_start_index); + return new nfc::NfcTag(uid, nfc::MIFARE_CLASSIC, buffer); +} + +bool PN532::read_mifare_classic_block_(uint8_t block_num, std::vector &data) { + if (!this->write_command_({ + PN532_COMMAND_INDATAEXCHANGE, + 0x01, // One card + nfc::MIFARE_CMD_READ, + block_num, + })) { + return false; + } + + if (!this->read_response_(PN532_COMMAND_INDATAEXCHANGE, data) || data[0] != 0x00) { + return false; + } + data.erase(data.begin()); + + ESP_LOGVV(TAG, " Block %d: %s", block_num, nfc::format_bytes(data).c_str()); + return true; +} + +bool PN532::auth_mifare_classic_block_(std::vector &uid, uint8_t block_num, uint8_t key_num, + const uint8_t *key) { + std::vector data({ + PN532_COMMAND_INDATAEXCHANGE, + 0x01, // One card + key_num, // Mifare Key slot + block_num, // Block number + }); + data.insert(data.end(), key, key + 6); + data.insert(data.end(), uid.begin(), uid.end()); + if (!this->write_command_(data)) { + ESP_LOGE(TAG, "Authentication failed - Block %d", block_num); + return false; + } + + std::vector response; + if (!this->read_response_(PN532_COMMAND_INDATAEXCHANGE, response) || response[0] != 0x00) { + ESP_LOGE(TAG, "Authentication failed - Block 0x%02x", block_num); + return false; + } + + return true; +} + +bool PN532::format_mifare_classic_mifare_(std::vector &uid) { + std::vector blank_buffer( + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + std::vector trailer_buffer( + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + + bool error = false; + + for (int block = 0; block < 64; block += 4) { + if (!this->auth_mifare_classic_block_(uid, block + 3, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY)) { + continue; + } + if (block != 0) { + if (!this->write_mifare_classic_block_(block, blank_buffer)) { + ESP_LOGE(TAG, "Unable to write block %d", block); + error = true; + } + } + if (!this->write_mifare_classic_block_(block + 1, blank_buffer)) { + ESP_LOGE(TAG, "Unable to write block %d", block + 1); + error = true; + } + if (!this->write_mifare_classic_block_(block + 2, blank_buffer)) { + ESP_LOGE(TAG, "Unable to write block %d", block + 2); + error = true; + } + if (!this->write_mifare_classic_block_(block + 3, trailer_buffer)) { + ESP_LOGE(TAG, "Unable to write block %d", block + 3); + error = true; + } + } + + return !error; +} + +bool PN532::format_mifare_classic_ndef_(std::vector &uid) { + std::vector empty_ndef_message( + {0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + std::vector blank_block( + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + std::vector block_1_data( + {0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}); + std::vector block_2_data( + {0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}); + std::vector block_3_trailer( + {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + std::vector ndef_trailer( + {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + + if (!this->auth_mifare_classic_block_(uid, 0, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY)) { + ESP_LOGE(TAG, "Unable to authenticate block 0 for formatting!"); + return false; + } + if (!this->write_mifare_classic_block_(1, block_1_data)) + return false; + if (!this->write_mifare_classic_block_(2, block_2_data)) + return false; + if (!this->write_mifare_classic_block_(3, block_3_trailer)) + return false; + + ESP_LOGD(TAG, "Sector 0 formatted to NDEF"); + + for (int block = 4; block < 64; block += 4) { + if (!this->auth_mifare_classic_block_(uid, block + 3, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY)) { + return false; + } + if (block == 4) { + if (!this->write_mifare_classic_block_(block, empty_ndef_message)) + ESP_LOGE(TAG, "Unable to write block %d", block); + } else { + if (!this->write_mifare_classic_block_(block, blank_block)) + ESP_LOGE(TAG, "Unable to write block %d", block); + } + if (!this->write_mifare_classic_block_(block + 1, blank_block)) + ESP_LOGE(TAG, "Unable to write block %d", block + 1); + if (!this->write_mifare_classic_block_(block + 2, blank_block)) + ESP_LOGE(TAG, "Unable to write block %d", block + 2); + if (!this->write_mifare_classic_block_(block + 3, ndef_trailer)) + ESP_LOGE(TAG, "Unable to write trailer block %d", block + 3); + } + return true; +} + +bool PN532::write_mifare_classic_block_(uint8_t block_num, std::vector &write_data) { + std::vector data({ + PN532_COMMAND_INDATAEXCHANGE, + 0x01, // One card + nfc::MIFARE_CMD_WRITE, + block_num, + }); + data.insert(data.end(), write_data.begin(), write_data.end()); + if (!this->write_command_(data)) { + ESP_LOGE(TAG, "Error writing block %d", block_num); + return false; + } + + std::vector response; + if (!this->read_response_(PN532_COMMAND_INDATAEXCHANGE, response)) { + ESP_LOGE(TAG, "Error writing block %d", block_num); + return false; + } + + return true; +} + +bool PN532::write_mifare_classic_tag_(std::vector &uid, nfc::NdefMessage *message) { + auto encoded = message->encode(); + + uint32_t message_length = encoded.size(); + uint32_t buffer_length = nfc::get_mifare_classic_buffer_size(message_length); + + encoded.insert(encoded.begin(), 0x03); + if (message_length < 255) { + encoded.insert(encoded.begin() + 1, message_length); + } else { + encoded.insert(encoded.begin() + 1, 0xFF); + encoded.insert(encoded.begin() + 2, (message_length >> 8) & 0xFF); + encoded.insert(encoded.begin() + 3, message_length & 0xFF); + } + encoded.push_back(0xFE); + + encoded.resize(buffer_length, 0); + + uint32_t index = 0; + uint8_t current_block = 4; + + while (index < buffer_length) { + if (nfc::mifare_classic_is_first_block(current_block)) { + if (!this->auth_mifare_classic_block_(uid, current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY)) { + return false; + } + } + + std::vector data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_CLASSIC_BLOCK_SIZE); + if (!this->write_mifare_classic_block_(current_block, data)) { + return false; + } + index += nfc::MIFARE_CLASSIC_BLOCK_SIZE; + current_block++; + + if (nfc::mifare_classic_is_trailer_block(current_block)) { + // Skipping as cannot write to trailer + current_block++; + } + } + return true; +} + +} // namespace pn532 +} // namespace esphome diff --git a/esphome/components/pn532/pn532_mifare_ultralight.cpp b/esphome/components/pn532/pn532_mifare_ultralight.cpp new file mode 100644 index 0000000000..00cb18aacd --- /dev/null +++ b/esphome/components/pn532/pn532_mifare_ultralight.cpp @@ -0,0 +1,180 @@ +#include "pn532.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace pn532 { + +static const char *TAG = "pn532.mifare_ultralight"; + +nfc::NfcTag *PN532::read_mifare_ultralight_tag_(std::vector &uid) { + if (!this->is_mifare_ultralight_formatted_()) { + ESP_LOGD(TAG, "Not NDEF formatted"); + return new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2); + } + + uint8_t message_length; + uint8_t message_start_index; + if (!this->find_mifare_ultralight_ndef_(message_length, message_start_index)) { + return new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2); + } + + if (message_length == 0) { + return new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2); + } + std::vector data; + uint8_t index = 0; + for (uint8_t page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; page < nfc::MIFARE_ULTRALIGHT_MAX_PAGE; page++) { + std::vector page_data; + if (!this->read_mifare_ultralight_page_(page, page_data)) { + ESP_LOGE(TAG, "Error reading page %d", page); + return new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2); + } + data.insert(data.end(), page_data.begin(), page_data.end()); + + if (index >= (message_length + message_start_index)) + break; + + index += page_data.size(); + } + + data.erase(data.begin(), data.begin() + message_start_index); + + return new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2, data); +} + +bool PN532::read_mifare_ultralight_page_(uint8_t page_num, std::vector &data) { + if (!this->write_command_({ + PN532_COMMAND_INDATAEXCHANGE, + 0x01, // One card + nfc::MIFARE_CMD_READ, + page_num, + })) { + return false; + } + + if (!this->read_response_(PN532_COMMAND_INDATAEXCHANGE, data) || data[0] != 0x00) { + return false; + } + data.erase(data.begin()); + // We only want 1 page of data but the PN532 returns 4 at once. + data.erase(data.begin() + 4, data.end()); + + ESP_LOGVV(TAG, "Pages %d-%d: %s", page_num, page_num + 4, nfc::format_bytes(data).c_str()); + + return true; +} + +bool PN532::is_mifare_ultralight_formatted_() { + std::vector data; + if (this->read_mifare_ultralight_page_(4, data)) { + return !(data[0] == 0xFF && data[1] == 0xFF && data[2] == 0xFF && data[3] == 0xFF); + } + return true; +} + +uint16_t PN532::read_mifare_ultralight_capacity_() { + std::vector data; + if (this->read_mifare_ultralight_page_(3, data)) { + return data[2] * 8U; + } + return 0; +} + +bool PN532::find_mifare_ultralight_ndef_(uint8_t &message_length, uint8_t &message_start_index) { + std::vector data; + for (int page = 4; page < 6; page++) { + std::vector page_data; + if (!this->read_mifare_ultralight_page_(page, page_data)) { + return false; + } + data.insert(data.end(), page_data.begin(), page_data.end()); + } + if (data[0] == 0x03) { + message_length = data[1]; + message_start_index = 2; + return true; + } else if (data[5] == 0x03) { + message_length = data[6]; + message_start_index = 7; + return true; + } + return false; +} + +bool PN532::write_mifare_ultralight_tag_(std::vector &uid, nfc::NdefMessage *message) { + uint32_t capacity = this->read_mifare_ultralight_capacity_(); + + auto encoded = message->encode(); + + uint32_t message_length = encoded.size(); + uint32_t buffer_length = nfc::get_mifare_ultralight_buffer_size(message_length); + + if (buffer_length > capacity) { + ESP_LOGE(TAG, "Message length exceeds tag capacity %d > %d", buffer_length, capacity); + return false; + } + + encoded.insert(encoded.begin(), 0x03); + if (message_length < 255) { + encoded.insert(encoded.begin() + 1, message_length); + } else { + encoded.insert(encoded.begin() + 1, 0xFF); + encoded.insert(encoded.begin() + 2, (message_length >> 8) & 0xFF); + encoded.insert(encoded.begin() + 2, message_length & 0xFF); + } + encoded.push_back(0xFE); + + encoded.resize(buffer_length, 0); + + uint32_t index = 0; + uint8_t current_page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; + + while (index < buffer_length) { + std::vector data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE); + if (!this->write_mifare_ultralight_page_(current_page, data)) { + return false; + } + index += nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; + current_page++; + } + return true; +} + +bool PN532::clean_mifare_ultralight_() { + uint32_t capacity = this->read_mifare_ultralight_capacity_(); + uint8_t pages = (capacity / nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) + nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; + + std::vector blank_data = {0x00, 0x00, 0x00, 0x00}; + + for (int i = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; i < pages; i++) { + if (!this->write_mifare_ultralight_page_(i, blank_data)) { + return false; + } + } + return true; +} + +bool PN532::write_mifare_ultralight_page_(uint8_t page_num, std::vector &write_data) { + std::vector data({ + PN532_COMMAND_INDATAEXCHANGE, + 0x01, // One card + nfc::MIFARE_CMD_WRITE_ULTRALIGHT, + page_num, + }); + data.insert(data.end(), write_data.begin(), write_data.end()); + if (!this->write_command_(data)) { + ESP_LOGE(TAG, "Error writing page %d", page_num); + return false; + } + + std::vector response; + if (!this->read_response_(PN532_COMMAND_INDATAEXCHANGE, response)) { + ESP_LOGE(TAG, "Error writing page %d", page_num); + return false; + } + + return true; +} + +} // namespace pn532 +} // namespace esphome diff --git a/esphome/components/pn532_i2c/pn532_i2c.cpp b/esphome/components/pn532_i2c/pn532_i2c.cpp index b959e764e7..162487de58 100644 --- a/esphome/components/pn532_i2c/pn532_i2c.cpp +++ b/esphome/components/pn532_i2c/pn532_i2c.cpp @@ -14,7 +14,7 @@ static const char *TAG = "pn532_i2c"; bool PN532I2C::write_data(const std::vector &data) { return this->write_bytes_raw(data.data(), data.size()); } bool PN532I2C::read_data(std::vector &data, uint8_t len) { - delay(5); + delay(1); std::vector ready; ready.resize(1);