mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Add NDEF reading and writing to PN532 (#1351)
This commit is contained in:
		| @@ -48,6 +48,7 @@ esphome/components/mcp23s17/* @SenexCrenshaw | |||||||
| esphome/components/mcp2515/* @danielschramm @mvturnho | esphome/components/mcp2515/* @danielschramm @mvturnho | ||||||
| esphome/components/mcp9808/* @k7hpn | esphome/components/mcp9808/* @k7hpn | ||||||
| esphome/components/network/* @esphome/core | esphome/components/network/* @esphome/core | ||||||
|  | esphome/components/nfc/* @jesserockz | ||||||
| esphome/components/ota/* @esphome/core | esphome/components/ota/* @esphome/core | ||||||
| esphome/components/output/* @esphome/core | esphome/components/output/* @esphome/core | ||||||
| esphome/components/pid/* @OttoWinter | esphome/components/pid/* @OttoWinter | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								esphome/components/nfc/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								esphome/components/nfc/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  |  | ||||||
|  | CODEOWNERS = ['@jesserockz'] | ||||||
|  |  | ||||||
|  | nfc_ns = cg.esphome_ns.namespace('nfc') | ||||||
|  |  | ||||||
|  | NfcTag = nfc_ns.class_('NfcTag') | ||||||
							
								
								
									
										106
									
								
								esphome/components/nfc/ndef_message.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								esphome/components/nfc/ndef_message.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,106 @@ | |||||||
|  | #include "ndef_message.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace nfc { | ||||||
|  |  | ||||||
|  | static const char *TAG = "nfc.ndef_message"; | ||||||
|  |  | ||||||
|  | NdefMessage::NdefMessage(std::vector<uint8_t> &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<uint32_t>(data[index]) << 24) | (static_cast<uint32_t>(data[index + 1]) << 16) | | ||||||
|  |                        (static_cast<uint32_t>(data[index + 2]) << 8) | static_cast<uint32_t>(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<uint8_t> NdefMessage::encode() { | ||||||
|  |   std::vector<uint8_t> 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 | ||||||
							
								
								
									
										31
									
								
								esphome/components/nfc/ndef_message.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								esphome/components/nfc/ndef_message.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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<uint8_t> &data); | ||||||
|  |  | ||||||
|  |   std::vector<NdefRecord *> 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<uint8_t> encode(); | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   std::vector<NdefRecord *> records_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace nfc | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										85
									
								
								esphome/components/nfc/ndef_record.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								esphome/components/nfc/ndef_record.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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<uint8_t> NdefRecord::encode(bool first, bool last) { | ||||||
|  |   std::vector<uint8_t> 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 | ||||||
							
								
								
									
										101
									
								
								esphome/components/nfc/ndef_record.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								esphome/components/nfc/ndef_record.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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<uint8_t> 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 | ||||||
							
								
								
									
										107
									
								
								esphome/components/nfc/nfc.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								esphome/components/nfc/nfc.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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<uint8_t> &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<uint8_t> &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<uint8_t> &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<uint8_t> &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 | ||||||
							
								
								
									
										57
									
								
								esphome/components/nfc/nfc.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								esphome/components/nfc/nfc.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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<uint8_t> &uid); | ||||||
|  | std::string format_bytes(std::vector<uint8_t> &bytes); | ||||||
|  |  | ||||||
|  | uint8_t guess_tag_type(uint8_t uid_length); | ||||||
|  | uint8_t get_mifare_classic_ndef_start_index(std::vector<uint8_t> &data); | ||||||
|  | bool decode_mifare_classic_tlv(std::vector<uint8_t> &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 | ||||||
							
								
								
									
										9
									
								
								esphome/components/nfc/nfc_tag.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								esphome/components/nfc/nfc_tag.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | #include "nfc_tag.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace nfc { | ||||||
|  |  | ||||||
|  | static const char *TAG = "nfc.tag"; | ||||||
|  |  | ||||||
|  | }  // namespace nfc | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										47
									
								
								esphome/components/nfc/nfc_tag.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								esphome/components/nfc/nfc_tag.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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<uint8_t> &uid) { | ||||||
|  |     this->uid_ = uid; | ||||||
|  |     this->tag_type_ = "Unknown"; | ||||||
|  |   }; | ||||||
|  |   NfcTag(std::vector<uint8_t> &uid, const std::string &tag_type) { | ||||||
|  |     this->uid_ = uid; | ||||||
|  |     this->tag_type_ = tag_type; | ||||||
|  |   }; | ||||||
|  |   NfcTag(std::vector<uint8_t> &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<uint8_t> &uid, const std::string &tag_type, std::vector<uint8_t> &ndef_data) { | ||||||
|  |     this->uid_ = uid; | ||||||
|  |     this->tag_type_ = tag_type; | ||||||
|  |     this->ndef_message_ = new NdefMessage(ndef_data); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   std::vector<uint8_t> &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<uint8_t> uid_; | ||||||
|  |   std::string tag_type_; | ||||||
|  |   NdefMessage *ndef_message_{nullptr}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace nfc | ||||||
|  | }  // namespace esphome | ||||||
| @@ -1,24 +1,34 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome import automation | 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 | from esphome.core import coroutine | ||||||
|  |  | ||||||
| CODEOWNERS = ['@OttoWinter', '@jesserockz'] | CODEOWNERS = ['@OttoWinter', '@jesserockz'] | ||||||
| AUTO_LOAD = ['binary_sensor'] | AUTO_LOAD = ['binary_sensor', 'nfc'] | ||||||
| MULTI_CONF = True | MULTI_CONF = True | ||||||
|  |  | ||||||
| CONF_PN532_ID = 'pn532_id' | CONF_PN532_ID = 'pn532_id' | ||||||
|  | CONF_ON_FINISHED_WRITE = 'on_finished_write' | ||||||
|  |  | ||||||
| pn532_ns = cg.esphome_ns.namespace('pn532') | pn532_ns = cg.esphome_ns.namespace('pn532') | ||||||
| PN532 = pn532_ns.class_('PN532', cg.PollingComponent) | 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({ | PN532_SCHEMA = cv.Schema({ | ||||||
|     cv.GenerateID(): cv.declare_id(PN532), |     cv.GenerateID(): cv.declare_id(PN532), | ||||||
|     cv.Optional(CONF_ON_TAG): automation.validate_automation({ |     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')) | }).extend(cv.polling_component_schema('1s')) | ||||||
|  |  | ||||||
| @@ -36,4 +46,18 @@ def setup_pn532(var, config): | |||||||
|     for conf in config.get(CONF_ON_TAG, []): |     for conf in config.get(CONF_ON_TAG, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) | ||||||
|         cg.add(var.register_trigger(trigger)) |         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 | ||||||
|   | |||||||
| @@ -11,18 +11,6 @@ namespace pn532 { | |||||||
|  |  | ||||||
| static const char *TAG = "pn532"; | static const char *TAG = "pn532"; | ||||||
|  |  | ||||||
| std::string format_uid(std::vector<uint8_t> &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() { | void PN532::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up PN532..."); |   ESP_LOGCONFIG(TAG, "Setting up PN532..."); | ||||||
|  |  | ||||||
| @@ -152,23 +140,56 @@ void PN532::loop() { | |||||||
|  |  | ||||||
|   this->current_uid_ = nfcid; |   this->current_uid_ = nfcid; | ||||||
|  |  | ||||||
|   for (auto *trigger : this->triggers_) |   if (next_task_ == READ) { | ||||||
|     trigger->process(nfcid); |     auto tag = this->read_tag_(nfcid); | ||||||
|  |     for (auto *trigger : this->triggers_) | ||||||
|  |       trigger->process(tag); | ||||||
|  |  | ||||||
|   if (report) { |     if (report) { | ||||||
|     ESP_LOGD(TAG, "Found new tag '%s'", format_uid(nfcid).c_str()); |       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_() { |   this->turn_off_rf_(); | ||||||
|   ESP_LOGVV(TAG, "Turning RF field OFF"); |  | ||||||
|   this->write_command_({ |  | ||||||
|       PN532_COMMAND_RFCONFIGURATION, |  | ||||||
|       0x1,  // RF Field |  | ||||||
|       0x0   // Off |  | ||||||
|   }); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| bool PN532::write_command_(const std::vector<uint8_t> &data) { | bool PN532::write_command_(const std::vector<uint8_t> &data) { | ||||||
| @@ -208,6 +229,22 @@ bool PN532::write_command_(const std::vector<uint8_t> &data) { | |||||||
|   return this->read_ack_(); |   return this->read_ack_(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | bool PN532::read_ack_() { | ||||||
|  |   ESP_LOGVV(TAG, "Reading ACK..."); | ||||||
|  |  | ||||||
|  |   std::vector<uint8_t> 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<uint8_t> &data) { | bool PN532::read_response_(uint8_t command, std::vector<uint8_t> &data) { | ||||||
|   ESP_LOGV(TAG, "Reading response"); |   ESP_LOGV(TAG, "Reading response"); | ||||||
|   uint8_t len = this->read_response_length_(); |   uint8_t len = this->read_response_length_(); | ||||||
| @@ -258,13 +295,6 @@ bool PN532::read_response_(uint8_t command, std::vector<uint8_t> &data) { | |||||||
|   data.erase(data.begin(), data.begin() + 2);  // Remove TFI and command code |   data.erase(data.begin(), data.begin() + 2);  // Remove TFI and command code | ||||||
|   data.erase(data.end() - 2, data.end());      // Remove checksum and postamble |   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; |   return true; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -299,20 +329,81 @@ uint8_t PN532::read_response_length_() { | |||||||
|   return len; |   return len; | ||||||
| } | } | ||||||
|  |  | ||||||
| bool PN532::read_ack_() { | void PN532::turn_off_rf_() { | ||||||
|   ESP_LOGVV(TAG, "Reading ACK..."); |   ESP_LOGVV(TAG, "Turning RF field OFF"); | ||||||
|  |   this->write_command_({ | ||||||
|  |       PN532_COMMAND_RFCONFIGURATION, | ||||||
|  |       0x01,  // RF Field | ||||||
|  |       0x00,  // Off | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|   std::vector<uint8_t> data; | nfc::NfcTag *PN532::read_tag_(std::vector<uint8_t> &uid) { | ||||||
|   if (!this->read_data(data, 6)) { |   uint8_t type = nfc::guess_tag_type(uid.size()); | ||||||
|     return false; |  | ||||||
|  |   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 | void PN532::read_mode() { | ||||||
|                   data[2] == 0x00 &&                     // start of packet |   this->next_task_ = READ; | ||||||
|                   data[3] == 0xFF && data[4] == 0x00 &&  // ACK packet code |   ESP_LOGD(TAG, "Waiting to read next tag"); | ||||||
|                   data[5] == 0xFF && data[6] == 0x00);   // postamble | } | ||||||
|   ESP_LOGVV(TAG, "ACK valid: %s", YESNO(matches)); | void PN532::clean_mode() { | ||||||
|   return matches; |   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<uint8_t> &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<uint8_t> &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<uint8_t> &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; } | float PN532::get_setup_priority() const { return setup_priority::DATA; } | ||||||
| @@ -350,7 +441,7 @@ bool PN532BinarySensor::process(std::vector<uint8_t> &data) { | |||||||
|   this->found_ = true; |   this->found_ = true; | ||||||
|   return true; |   return true; | ||||||
| } | } | ||||||
| void PN532Trigger::process(std::vector<uint8_t> &data) { this->trigger(format_uid(data)); } | void PN532OnTagTrigger::process(nfc::NfcTag *tag) { this->trigger(nfc::format_uid(tag->get_uid()), *tag); } | ||||||
|  |  | ||||||
| }  // namespace pn532 | }  // namespace pn532 | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -3,6 +3,8 @@ | |||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
| #include "esphome/core/automation.h" | #include "esphome/core/automation.h" | ||||||
| #include "esphome/components/binary_sensor/binary_sensor.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 esphome { | ||||||
| namespace pn532 { | namespace pn532 { | ||||||
| @@ -14,7 +16,7 @@ static const uint8_t PN532_COMMAND_INDATAEXCHANGE = 0x40; | |||||||
| static const uint8_t PN532_COMMAND_INLISTPASSIVETARGET = 0x4A; | static const uint8_t PN532_COMMAND_INLISTPASSIVETARGET = 0x4A; | ||||||
|  |  | ||||||
| class PN532BinarySensor; | class PN532BinarySensor; | ||||||
| class PN532Trigger; | class PN532OnTagTrigger; | ||||||
|  |  | ||||||
| class PN532 : public PollingComponent { | class PN532 : public PollingComponent { | ||||||
|  public: |  public: | ||||||
| @@ -28,7 +30,18 @@ class PN532 : public PollingComponent { | |||||||
|   void loop() override; |   void loop() override; | ||||||
|  |  | ||||||
|   void register_tag(PN532BinarySensor *tag) { this->binary_sensors_.push_back(tag); } |   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<void()> 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: |  protected: | ||||||
|   void turn_off_rf_(); |   void turn_off_rf_(); | ||||||
| @@ -40,15 +53,46 @@ class PN532 : public PollingComponent { | |||||||
|   virtual bool write_data(const std::vector<uint8_t> &data) = 0; |   virtual bool write_data(const std::vector<uint8_t> &data) = 0; | ||||||
|   virtual bool read_data(std::vector<uint8_t> &data, uint8_t len) = 0; |   virtual bool read_data(std::vector<uint8_t> &data, uint8_t len) = 0; | ||||||
|  |  | ||||||
|  |   nfc::NfcTag *read_tag_(std::vector<uint8_t> &uid); | ||||||
|  |  | ||||||
|  |   bool format_tag_(std::vector<uint8_t> &uid); | ||||||
|  |   bool clean_tag_(std::vector<uint8_t> &uid); | ||||||
|  |   bool write_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message); | ||||||
|  |  | ||||||
|  |   nfc::NfcTag *read_mifare_classic_tag_(std::vector<uint8_t> &uid); | ||||||
|  |   bool read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data); | ||||||
|  |   bool write_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data); | ||||||
|  |   bool auth_mifare_classic_block_(std::vector<uint8_t> &uid, uint8_t block_num, uint8_t key_num, const uint8_t *key); | ||||||
|  |   bool format_mifare_classic_mifare_(std::vector<uint8_t> &uid); | ||||||
|  |   bool format_mifare_classic_ndef_(std::vector<uint8_t> &uid); | ||||||
|  |   bool write_mifare_classic_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message); | ||||||
|  |  | ||||||
|  |   nfc::NfcTag *read_mifare_ultralight_tag_(std::vector<uint8_t> &uid); | ||||||
|  |   bool read_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &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<uint8_t> &write_data); | ||||||
|  |   bool write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message); | ||||||
|  |   bool clean_mifare_ultralight_(); | ||||||
|  |  | ||||||
|   bool requested_read_{false}; |   bool requested_read_{false}; | ||||||
|   std::vector<PN532BinarySensor *> binary_sensors_; |   std::vector<PN532BinarySensor *> binary_sensors_; | ||||||
|   std::vector<PN532Trigger *> triggers_; |   std::vector<PN532OnTagTrigger *> triggers_; | ||||||
|   std::vector<uint8_t> current_uid_; |   std::vector<uint8_t> current_uid_; | ||||||
|  |   nfc::NdefMessage *next_task_message_to_write_; | ||||||
|  |   enum NfcTask { | ||||||
|  |     READ = 0, | ||||||
|  |     CLEAN, | ||||||
|  |     FORMAT, | ||||||
|  |     WRITE, | ||||||
|  |   } next_task_{READ}; | ||||||
|   enum PN532Error { |   enum PN532Error { | ||||||
|     NONE = 0, |     NONE = 0, | ||||||
|     WAKEUP_FAILED, |     WAKEUP_FAILED, | ||||||
|     SAM_COMMAND_FAILED, |     SAM_COMMAND_FAILED, | ||||||
|   } error_code_{NONE}; |   } error_code_{NONE}; | ||||||
|  |   CallbackManager<void()> on_finished_write_callback_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class PN532BinarySensor : public binary_sensor::BinarySensor { | class PN532BinarySensor : public binary_sensor::BinarySensor { | ||||||
| @@ -69,9 +113,21 @@ class PN532BinarySensor : public binary_sensor::BinarySensor { | |||||||
|   bool found_{false}; |   bool found_{false}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class PN532Trigger : public Trigger<std::string> { | class PN532OnTagTrigger : public Trigger<std::string, nfc::NfcTag> { | ||||||
|  public: |  public: | ||||||
|   void process(std::vector<uint8_t> &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<typename... Ts> class PN532IsWritingCondition : public Condition<Ts...>, public Parented<PN532> { | ||||||
|  |  public: | ||||||
|  |   bool check(Ts... x) override { return this->parent_->is_writing(); } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace pn532 | }  // namespace pn532 | ||||||
|   | |||||||
							
								
								
									
										249
									
								
								esphome/components/pn532/pn532_mifare_classic.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										249
									
								
								esphome/components/pn532/pn532_mifare_classic.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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<uint8_t> &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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> &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<uint8_t> &uid, uint8_t block_num, uint8_t key_num, | ||||||
|  |                                        const uint8_t *key) { | ||||||
|  |   std::vector<uint8_t> 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<uint8_t> 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<uint8_t> &uid) { | ||||||
|  |   std::vector<uint8_t> blank_buffer( | ||||||
|  |       {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); | ||||||
|  |   std::vector<uint8_t> 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<uint8_t> &uid) { | ||||||
|  |   std::vector<uint8_t> empty_ndef_message( | ||||||
|  |       {0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); | ||||||
|  |   std::vector<uint8_t> blank_block( | ||||||
|  |       {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); | ||||||
|  |   std::vector<uint8_t> block_1_data( | ||||||
|  |       {0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}); | ||||||
|  |   std::vector<uint8_t> block_2_data( | ||||||
|  |       {0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}); | ||||||
|  |   std::vector<uint8_t> block_3_trailer( | ||||||
|  |       {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); | ||||||
|  |   std::vector<uint8_t> 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<uint8_t> &write_data) { | ||||||
|  |   std::vector<uint8_t> 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<uint8_t> 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<uint8_t> &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<uint8_t> 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 | ||||||
							
								
								
									
										180
									
								
								esphome/components/pn532/pn532_mifare_ultralight.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								esphome/components/pn532/pn532_mifare_ultralight.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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<uint8_t> &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<uint8_t> data; | ||||||
|  |   uint8_t index = 0; | ||||||
|  |   for (uint8_t page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; page < nfc::MIFARE_ULTRALIGHT_MAX_PAGE; page++) { | ||||||
|  |     std::vector<uint8_t> 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<uint8_t> &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<uint8_t> 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<uint8_t> 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<uint8_t> data; | ||||||
|  |   for (int page = 4; page < 6; page++) { | ||||||
|  |     std::vector<uint8_t> 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<uint8_t> &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<uint8_t> 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<uint8_t> 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<uint8_t> &write_data) { | ||||||
|  |   std::vector<uint8_t> 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<uint8_t> 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 | ||||||
| @@ -14,7 +14,7 @@ static const char *TAG = "pn532_i2c"; | |||||||
| bool PN532I2C::write_data(const std::vector<uint8_t> &data) { return this->write_bytes_raw(data.data(), data.size()); } | bool PN532I2C::write_data(const std::vector<uint8_t> &data) { return this->write_bytes_raw(data.data(), data.size()); } | ||||||
|  |  | ||||||
| bool PN532I2C::read_data(std::vector<uint8_t> &data, uint8_t len) { | bool PN532I2C::read_data(std::vector<uint8_t> &data, uint8_t len) { | ||||||
|   delay(5); |   delay(1); | ||||||
|  |  | ||||||
|   std::vector<uint8_t> ready; |   std::vector<uint8_t> ready; | ||||||
|   ready.resize(1); |   ready.resize(1); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user