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/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 | ||||
|   | ||||
							
								
								
									
										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.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 | ||||
|   | ||||
| @@ -11,18 +11,6 @@ namespace 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() { | ||||
|   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<uint8_t> &data) { | ||||
| @@ -208,6 +229,22 @@ bool PN532::write_command_(const std::vector<uint8_t> &data) { | ||||
|   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) { | ||||
|   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<uint8_t> &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<uint8_t> data; | ||||
|   if (!this->read_data(data, 6)) { | ||||
|     return false; | ||||
| nfc::NfcTag *PN532::read_tag_(std::vector<uint8_t> &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<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; } | ||||
| @@ -350,7 +441,7 @@ bool PN532BinarySensor::process(std::vector<uint8_t> &data) { | ||||
|   this->found_ = 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 esphome | ||||
|   | ||||
| @@ -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<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: | ||||
|   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 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}; | ||||
|   std::vector<PN532BinarySensor *> binary_sensors_; | ||||
|   std::vector<PN532Trigger *> triggers_; | ||||
|   std::vector<PN532OnTagTrigger *> triggers_; | ||||
|   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 { | ||||
|     NONE = 0, | ||||
|     WAKEUP_FAILED, | ||||
|     SAM_COMMAND_FAILED, | ||||
|   } error_code_{NONE}; | ||||
|   CallbackManager<void()> 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<std::string> { | ||||
| class PN532OnTagTrigger : public Trigger<std::string, nfc::NfcTag> { | ||||
|  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 | ||||
|   | ||||
							
								
								
									
										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::read_data(std::vector<uint8_t> &data, uint8_t len) { | ||||
|   delay(5); | ||||
|   delay(1); | ||||
|  | ||||
|   std::vector<uint8_t> ready; | ||||
|   ready.resize(1); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user