mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 13:34:54 +01:00
Add NDEF reading and writing to PN532 (#1351)
This commit is contained in:
parent
28f2582256
commit
52e13164b4
16 changed files with 1206 additions and 55 deletions
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue