Add NDEF reading and writing to PN532 (#1351)

This commit is contained in:
Jesse Hills
2021-01-15 09:29:55 +13:00
parent 28f2582256
commit 52e13164b4
16 changed files with 1206 additions and 55 deletions
+29 -5
View File
@@ -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
+135 -44
View File
@@ -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
+61 -5
View File
@@ -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
@@ -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
@@ -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