mirror of
https://github.com/esphome/esphome.git
synced 2026-03-24 12:16:46 +01:00
Add support for PN7150 (#5487)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
6fd239362d
commit
76a6e288b6
@ -240,6 +240,8 @@ esphome/components/pmwcs3/* @SeByDocKy
|
||||
esphome/components/pn532/* @OttoWinter @jesserockz
|
||||
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
|
||||
esphome/components/pn532_spi/* @OttoWinter @jesserockz
|
||||
esphome/components/pn7150/* @jesserockz @kbx81
|
||||
esphome/components/pn7150_i2c/* @jesserockz @kbx81
|
||||
esphome/components/pn7160/* @jesserockz @kbx81
|
||||
esphome/components/pn7160_i2c/* @jesserockz @kbx81
|
||||
esphome/components/pn7160_spi/* @jesserockz @kbx81
|
||||
|
||||
215
esphome/components/pn7150/__init__.py
Normal file
215
esphome/components/pn7150/__init__.py
Normal file
@ -0,0 +1,215 @@
|
||||
from esphome import automation, pins
|
||||
from esphome.automation import maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import nfc
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_IRQ_PIN,
|
||||
CONF_ON_TAG_REMOVED,
|
||||
CONF_ON_TAG,
|
||||
CONF_TRIGGER_ID,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["binary_sensor", "nfc"]
|
||||
CODEOWNERS = ["@kbx81", "@jesserockz"]
|
||||
|
||||
CONF_EMULATION_MESSAGE = "emulation_message"
|
||||
CONF_EMULATION_OFF = "emulation_off"
|
||||
CONF_EMULATION_ON = "emulation_on"
|
||||
CONF_INCLUDE_ANDROID_APP_RECORD = "include_android_app_record"
|
||||
CONF_MESSAGE = "message"
|
||||
CONF_ON_FINISHED_WRITE = "on_finished_write"
|
||||
CONF_ON_EMULATED_TAG_SCAN = "on_emulated_tag_scan"
|
||||
CONF_PN7150_ID = "pn7150_id"
|
||||
CONF_POLLING_OFF = "polling_off"
|
||||
CONF_POLLING_ON = "polling_on"
|
||||
CONF_SET_CLEAN_MODE = "set_clean_mode"
|
||||
CONF_SET_EMULATION_MESSAGE = "set_emulation_message"
|
||||
CONF_SET_FORMAT_MODE = "set_format_mode"
|
||||
CONF_SET_READ_MODE = "set_read_mode"
|
||||
CONF_SET_WRITE_MESSAGE = "set_write_message"
|
||||
CONF_SET_WRITE_MODE = "set_write_mode"
|
||||
CONF_TAG_TTL = "tag_ttl"
|
||||
CONF_VEN_PIN = "ven_pin"
|
||||
|
||||
pn7150_ns = cg.esphome_ns.namespace("pn7150")
|
||||
PN7150 = pn7150_ns.class_("PN7150", cg.Component)
|
||||
|
||||
EmulationOffAction = pn7150_ns.class_("EmulationOffAction", automation.Action)
|
||||
EmulationOnAction = pn7150_ns.class_("EmulationOnAction", automation.Action)
|
||||
PollingOffAction = pn7150_ns.class_("PollingOffAction", automation.Action)
|
||||
PollingOnAction = pn7150_ns.class_("PollingOnAction", automation.Action)
|
||||
SetCleanModeAction = pn7150_ns.class_("SetCleanModeAction", automation.Action)
|
||||
SetEmulationMessageAction = pn7150_ns.class_(
|
||||
"SetEmulationMessageAction", automation.Action
|
||||
)
|
||||
SetFormatModeAction = pn7150_ns.class_("SetFormatModeAction", automation.Action)
|
||||
SetReadModeAction = pn7150_ns.class_("SetReadModeAction", automation.Action)
|
||||
SetWriteMessageAction = pn7150_ns.class_("SetWriteMessageAction", automation.Action)
|
||||
SetWriteModeAction = pn7150_ns.class_("SetWriteModeAction", automation.Action)
|
||||
|
||||
|
||||
PN7150OnEmulatedTagScanTrigger = pn7150_ns.class_(
|
||||
"PN7150OnEmulatedTagScanTrigger", automation.Trigger.template()
|
||||
)
|
||||
|
||||
PN7150OnFinishedWriteTrigger = pn7150_ns.class_(
|
||||
"PN7150OnFinishedWriteTrigger", automation.Trigger.template()
|
||||
)
|
||||
|
||||
PN7150IsWritingCondition = pn7150_ns.class_(
|
||||
"PN7150IsWritingCondition", automation.Condition
|
||||
)
|
||||
|
||||
|
||||
IsWritingCondition = nfc.nfc_ns.class_("IsWritingCondition", automation.Condition)
|
||||
|
||||
|
||||
SIMPLE_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(PN7150),
|
||||
}
|
||||
)
|
||||
|
||||
SET_MESSAGE_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(PN7150),
|
||||
cv.Required(CONF_MESSAGE): cv.templatable(cv.string),
|
||||
cv.Optional(CONF_INCLUDE_ANDROID_APP_RECORD, default=True): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
PN7150_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(PN7150),
|
||||
cv.Optional(CONF_ON_EMULATED_TAG_SCAN): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
PN7150OnEmulatedTagScanTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
PN7150OnFinishedWriteTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_TAG): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_TAG_REMOVED): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger),
|
||||
}
|
||||
),
|
||||
cv.Required(CONF_IRQ_PIN): pins.gpio_input_pin_schema,
|
||||
cv.Required(CONF_VEN_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_EMULATION_MESSAGE): cv.string,
|
||||
cv.Optional(CONF_TAG_TTL): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"tag.set_emulation_message",
|
||||
SetEmulationMessageAction,
|
||||
SET_MESSAGE_ACTION_SCHEMA,
|
||||
)
|
||||
@automation.register_action(
|
||||
"tag.set_write_message",
|
||||
SetWriteMessageAction,
|
||||
SET_MESSAGE_ACTION_SCHEMA,
|
||||
)
|
||||
async def pn7150_set_message_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_MESSAGE], args, cg.std_string)
|
||||
cg.add(var.set_message(template_))
|
||||
template_ = await cg.templatable(
|
||||
config[CONF_INCLUDE_ANDROID_APP_RECORD], args, cg.bool_
|
||||
)
|
||||
cg.add(var.set_include_android_app_record(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"tag.emulation_off", EmulationOffAction, SIMPLE_ACTION_SCHEMA
|
||||
)
|
||||
@automation.register_action("tag.emulation_on", EmulationOnAction, SIMPLE_ACTION_SCHEMA)
|
||||
@automation.register_action("tag.polling_off", PollingOffAction, SIMPLE_ACTION_SCHEMA)
|
||||
@automation.register_action("tag.polling_on", PollingOnAction, SIMPLE_ACTION_SCHEMA)
|
||||
@automation.register_action(
|
||||
"tag.set_clean_mode", SetCleanModeAction, SIMPLE_ACTION_SCHEMA
|
||||
)
|
||||
@automation.register_action(
|
||||
"tag.set_format_mode", SetFormatModeAction, SIMPLE_ACTION_SCHEMA
|
||||
)
|
||||
@automation.register_action(
|
||||
"tag.set_read_mode", SetReadModeAction, SIMPLE_ACTION_SCHEMA
|
||||
)
|
||||
@automation.register_action(
|
||||
"tag.set_write_mode", SetWriteModeAction, SIMPLE_ACTION_SCHEMA
|
||||
)
|
||||
async def pn7150_simple_action_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
async def setup_pn7150(var, config):
|
||||
await cg.register_component(var, config)
|
||||
|
||||
pin = await cg.gpio_pin_expression(config[CONF_IRQ_PIN])
|
||||
cg.add(var.set_irq_pin(pin))
|
||||
|
||||
pin = await cg.gpio_pin_expression(config[CONF_VEN_PIN])
|
||||
cg.add(var.set_ven_pin(pin))
|
||||
|
||||
if emulation_message_config := config.get(CONF_EMULATION_MESSAGE):
|
||||
cg.add(var.set_tag_emulation_message(emulation_message_config))
|
||||
cg.add(var.set_tag_emulation_on())
|
||||
|
||||
if CONF_TAG_TTL in config:
|
||||
cg.add(var.set_tag_ttl(config[CONF_TAG_TTL]))
|
||||
|
||||
for conf in config.get(CONF_ON_TAG, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
|
||||
cg.add(var.register_ontag_trigger(trigger))
|
||||
await automation.build_automation(
|
||||
trigger, [(cg.std_string, "x"), (nfc.NfcTag, "tag")], conf
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_TAG_REMOVED, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
|
||||
cg.add(var.register_ontagremoved_trigger(trigger))
|
||||
await automation.build_automation(
|
||||
trigger, [(cg.std_string, "x"), (nfc.NfcTag, "tag")], conf
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_EMULATED_TAG_SCAN, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_FINISHED_WRITE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
"pn7150.is_writing",
|
||||
PN7150IsWritingCondition,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(PN7150),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def pn7150_is_writing_to_code(config, condition_id, template_arg, args):
|
||||
var = cg.new_Pvariable(condition_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
82
esphome/components/pn7150/automation.h
Normal file
82
esphome/components/pn7150/automation.h
Normal file
@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/pn7150/pn7150.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pn7150 {
|
||||
|
||||
class PN7150OnEmulatedTagScanTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit PN7150OnEmulatedTagScanTrigger(PN7150 *parent) {
|
||||
parent->add_on_emulated_tag_scan_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
class PN7150OnFinishedWriteTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit PN7150OnFinishedWriteTrigger(PN7150 *parent) {
|
||||
parent->add_on_finished_write_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class PN7150IsWritingCondition : public Condition<Ts...>, public Parented<PN7150> {
|
||||
public:
|
||||
bool check(Ts... x) override { return this->parent_->is_writing(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class EmulationOffAction : public Action<Ts...>, public Parented<PN7150> {
|
||||
void play(Ts... x) override { this->parent_->set_tag_emulation_off(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class EmulationOnAction : public Action<Ts...>, public Parented<PN7150> {
|
||||
void play(Ts... x) override { this->parent_->set_tag_emulation_on(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class PollingOffAction : public Action<Ts...>, public Parented<PN7150> {
|
||||
void play(Ts... x) override { this->parent_->set_polling_off(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class PollingOnAction : public Action<Ts...>, public Parented<PN7150> {
|
||||
void play(Ts... x) override { this->parent_->set_polling_on(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetCleanModeAction : public Action<Ts...>, public Parented<PN7150> {
|
||||
void play(Ts... x) override { this->parent_->clean_mode(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetFormatModeAction : public Action<Ts...>, public Parented<PN7150> {
|
||||
void play(Ts... x) override { this->parent_->format_mode(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetReadModeAction : public Action<Ts...>, public Parented<PN7150> {
|
||||
void play(Ts... x) override { this->parent_->read_mode(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetEmulationMessageAction : public Action<Ts...>, public Parented<PN7150> {
|
||||
TEMPLATABLE_VALUE(std::string, message)
|
||||
TEMPLATABLE_VALUE(bool, include_android_app_record)
|
||||
|
||||
void play(Ts... x) override {
|
||||
this->parent_->set_tag_emulation_message(this->message_.optional_value(x...),
|
||||
this->include_android_app_record_.optional_value(x...));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetWriteMessageAction : public Action<Ts...>, public Parented<PN7150> {
|
||||
TEMPLATABLE_VALUE(std::string, message)
|
||||
TEMPLATABLE_VALUE(bool, include_android_app_record)
|
||||
|
||||
void play(Ts... x) override {
|
||||
this->parent_->set_tag_write_message(this->message_.optional_value(x...),
|
||||
this->include_android_app_record_.optional_value(x...));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetWriteModeAction : public Action<Ts...>, public Parented<PN7150> {
|
||||
void play(Ts... x) override { this->parent_->write_mode(); }
|
||||
};
|
||||
|
||||
} // namespace pn7150
|
||||
} // namespace esphome
|
||||
1137
esphome/components/pn7150/pn7150.cpp
Normal file
1137
esphome/components/pn7150/pn7150.cpp
Normal file
@ -0,0 +1,1137 @@
|
||||
#include "automation.h"
|
||||
#include "pn7150.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pn7150 {
|
||||
|
||||
static const char *const TAG = "pn7150";
|
||||
|
||||
void PN7150::setup() {
|
||||
this->irq_pin_->setup();
|
||||
this->ven_pin_->setup();
|
||||
|
||||
this->nci_fsm_transition_(); // kick off reset & init processes
|
||||
}
|
||||
|
||||
void PN7150::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "PN7150:");
|
||||
LOG_PIN(" IRQ pin: ", this->irq_pin_);
|
||||
LOG_PIN(" VEN pin: ", this->ven_pin_);
|
||||
}
|
||||
|
||||
void PN7150::loop() {
|
||||
this->nci_fsm_transition_();
|
||||
this->purge_old_tags_();
|
||||
}
|
||||
|
||||
void PN7150::set_tag_emulation_message(std::shared_ptr<nfc::NdefMessage> message) {
|
||||
this->card_emulation_message_ = std::move(message);
|
||||
ESP_LOGD(TAG, "Tag emulation message set");
|
||||
}
|
||||
|
||||
void PN7150::set_tag_emulation_message(const optional<std::string> &message,
|
||||
const optional<bool> include_android_app_record) {
|
||||
if (!message.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto ndef_message = make_unique<nfc::NdefMessage>();
|
||||
|
||||
ndef_message->add_uri_record(message.value());
|
||||
|
||||
if (!include_android_app_record.has_value() || include_android_app_record.value()) {
|
||||
auto ext_record = make_unique<nfc::NdefRecord>();
|
||||
ext_record->set_tnf(nfc::TNF_EXTERNAL_TYPE);
|
||||
ext_record->set_type(nfc::HA_TAG_ID_EXT_RECORD_TYPE);
|
||||
ext_record->set_payload(nfc::HA_TAG_ID_EXT_RECORD_PAYLOAD);
|
||||
ndef_message->add_record(std::move(ext_record));
|
||||
}
|
||||
|
||||
this->card_emulation_message_ = std::move(ndef_message);
|
||||
ESP_LOGD(TAG, "Tag emulation message set");
|
||||
}
|
||||
|
||||
void PN7150::set_tag_emulation_message(const char *message, const bool include_android_app_record) {
|
||||
this->set_tag_emulation_message(std::string(message), include_android_app_record);
|
||||
}
|
||||
|
||||
void PN7150::set_tag_emulation_off() {
|
||||
if (this->listening_enabled_) {
|
||||
this->listening_enabled_ = false;
|
||||
this->config_refresh_pending_ = true;
|
||||
}
|
||||
ESP_LOGD(TAG, "Tag emulation disabled");
|
||||
}
|
||||
|
||||
void PN7150::set_tag_emulation_on() {
|
||||
if (this->card_emulation_message_ == nullptr) {
|
||||
ESP_LOGE(TAG, "No NDEF message is set; tag emulation cannot be enabled");
|
||||
return;
|
||||
}
|
||||
if (!this->listening_enabled_) {
|
||||
this->listening_enabled_ = true;
|
||||
this->config_refresh_pending_ = true;
|
||||
}
|
||||
ESP_LOGD(TAG, "Tag emulation enabled");
|
||||
}
|
||||
|
||||
void PN7150::set_polling_off() {
|
||||
if (this->polling_enabled_) {
|
||||
this->polling_enabled_ = false;
|
||||
this->config_refresh_pending_ = true;
|
||||
}
|
||||
ESP_LOGD(TAG, "Tag polling disabled");
|
||||
}
|
||||
|
||||
void PN7150::set_polling_on() {
|
||||
if (!this->polling_enabled_) {
|
||||
this->polling_enabled_ = true;
|
||||
this->config_refresh_pending_ = true;
|
||||
}
|
||||
ESP_LOGD(TAG, "Tag polling enabled");
|
||||
}
|
||||
|
||||
void PN7150::read_mode() {
|
||||
this->next_task_ = EP_READ;
|
||||
ESP_LOGD(TAG, "Waiting to read next tag");
|
||||
}
|
||||
|
||||
void PN7150::clean_mode() {
|
||||
this->next_task_ = EP_CLEAN;
|
||||
ESP_LOGD(TAG, "Waiting to clean next tag");
|
||||
}
|
||||
|
||||
void PN7150::format_mode() {
|
||||
this->next_task_ = EP_FORMAT;
|
||||
ESP_LOGD(TAG, "Waiting to format next tag");
|
||||
}
|
||||
|
||||
void PN7150::write_mode() {
|
||||
if (this->next_task_message_to_write_ == nullptr) {
|
||||
ESP_LOGW(TAG, "Message to write must be set before setting write mode");
|
||||
return;
|
||||
}
|
||||
|
||||
this->next_task_ = EP_WRITE;
|
||||
ESP_LOGD(TAG, "Waiting to write next tag");
|
||||
}
|
||||
|
||||
void PN7150::set_tag_write_message(std::shared_ptr<nfc::NdefMessage> message) {
|
||||
this->next_task_message_to_write_ = std::move(message);
|
||||
ESP_LOGD(TAG, "Message to write has been set");
|
||||
}
|
||||
|
||||
void PN7150::set_tag_write_message(optional<std::string> message, optional<bool> include_android_app_record) {
|
||||
if (!message.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto ndef_message = make_unique<nfc::NdefMessage>();
|
||||
|
||||
ndef_message->add_uri_record(message.value());
|
||||
|
||||
if (!include_android_app_record.has_value() || include_android_app_record.value()) {
|
||||
auto ext_record = make_unique<nfc::NdefRecord>();
|
||||
ext_record->set_tnf(nfc::TNF_EXTERNAL_TYPE);
|
||||
ext_record->set_type(nfc::HA_TAG_ID_EXT_RECORD_TYPE);
|
||||
ext_record->set_payload(nfc::HA_TAG_ID_EXT_RECORD_PAYLOAD);
|
||||
ndef_message->add_record(std::move(ext_record));
|
||||
}
|
||||
|
||||
this->next_task_message_to_write_ = std::move(ndef_message);
|
||||
ESP_LOGD(TAG, "Message to write has been set");
|
||||
}
|
||||
|
||||
uint8_t PN7150::set_test_mode(const TestMode test_mode, const std::vector<uint8_t> &data,
|
||||
std::vector<uint8_t> &result) {
|
||||
auto test_oid = TEST_PRBS_OID;
|
||||
|
||||
switch (test_mode) {
|
||||
case TestMode::TEST_PRBS:
|
||||
// test_oid = TEST_PRBS_OID;
|
||||
break;
|
||||
|
||||
case TestMode::TEST_ANTENNA:
|
||||
test_oid = TEST_ANTENNA_OID;
|
||||
break;
|
||||
|
||||
case TestMode::TEST_GET_REGISTER:
|
||||
test_oid = TEST_GET_REGISTER_OID;
|
||||
break;
|
||||
|
||||
case TestMode::TEST_NONE:
|
||||
default:
|
||||
ESP_LOGD(TAG, "Exiting test mode");
|
||||
this->nci_fsm_set_state_(NCIState::NFCC_RESET);
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
if (this->reset_core_(true, true) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Failed to reset NCI core");
|
||||
this->nci_fsm_set_error_state_(NCIState::NFCC_RESET);
|
||||
result.clear();
|
||||
return nfc::STATUS_FAILED;
|
||||
} else {
|
||||
this->nci_fsm_set_state_(NCIState::NFCC_INIT);
|
||||
}
|
||||
if (this->init_core_() != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialise NCI core");
|
||||
this->nci_fsm_set_error_state_(NCIState::NFCC_INIT);
|
||||
result.clear();
|
||||
return nfc::STATUS_FAILED;
|
||||
} else {
|
||||
this->nci_fsm_set_state_(NCIState::TEST);
|
||||
}
|
||||
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::NCI_PROPRIETARY_GID, test_oid, data);
|
||||
|
||||
ESP_LOGW(TAG, "Starting test mode, OID 0x%02X", test_oid);
|
||||
auto status = this->transceive_(tx, rx, NFCC_INIT_TIMEOUT);
|
||||
|
||||
if (status != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Failed to start test mode, OID 0x%02X", test_oid);
|
||||
this->nci_fsm_set_state_(NCIState::NFCC_RESET);
|
||||
result.clear();
|
||||
} else {
|
||||
result = rx.get_message();
|
||||
result.erase(result.begin(), result.begin() + 4); // remove NCI header
|
||||
if (!result.empty()) {
|
||||
ESP_LOGW(TAG, "Test results: %s", nfc::format_bytes(result).c_str());
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t PN7150::reset_core_(const bool reset_config, const bool power) {
|
||||
if (power) {
|
||||
this->ven_pin_->digital_write(true);
|
||||
delay(NFCC_DEFAULT_TIMEOUT);
|
||||
this->ven_pin_->digital_write(false);
|
||||
delay(NFCC_DEFAULT_TIMEOUT);
|
||||
this->ven_pin_->digital_write(true);
|
||||
delay(NFCC_INIT_TIMEOUT);
|
||||
}
|
||||
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::NCI_CORE_GID, nfc::NCI_CORE_RESET_OID,
|
||||
{(uint8_t) reset_config});
|
||||
|
||||
if (this->transceive_(tx, rx, NFCC_INIT_TIMEOUT) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Error sending reset command");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
if (!rx.simple_status_response_is(nfc::STATUS_OK)) {
|
||||
ESP_LOGE(TAG, "Invalid reset response: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
return rx.get_simple_status_response();
|
||||
}
|
||||
// verify reset response
|
||||
if ((!rx.message_type_is(nfc::NCI_PKT_MT_CTRL_RESPONSE)) || (!rx.message_length_is(3)) ||
|
||||
(rx.get_message()[nfc::NCI_PKT_PAYLOAD_OFFSET + 1] != 0x11) ||
|
||||
(rx.get_message()[nfc::NCI_PKT_PAYLOAD_OFFSET + 2] != (uint8_t) reset_config)) {
|
||||
ESP_LOGE(TAG, "Reset response was malformed: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Configuration %s", rx.get_message()[nfc::NCI_PKT_PAYLOAD_OFFSET + 2] ? "reset" : "retained");
|
||||
ESP_LOGD(TAG, "NCI version: %s", rx.get_message()[nfc::NCI_PKT_PAYLOAD_OFFSET + 1] == 0x20 ? "2.0" : "1.0");
|
||||
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150::init_core_() {
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::NCI_CORE_GID, nfc::NCI_CORE_INIT_OID);
|
||||
|
||||
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Error sending initialise command");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
if (!rx.simple_status_response_is(nfc::STATUS_OK)) {
|
||||
ESP_LOGE(TAG, "Invalid initialise response: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
uint8_t manf_id = rx.get_message()[15 + rx.get_message()[8]];
|
||||
uint8_t hw_version = rx.get_message()[16 + rx.get_message()[8]];
|
||||
uint8_t rom_code_version = rx.get_message()[17 + rx.get_message()[8]];
|
||||
uint8_t flash_major_version = rx.get_message()[18 + rx.get_message()[8]];
|
||||
uint8_t flash_minor_version = rx.get_message()[19 + rx.get_message()[8]];
|
||||
|
||||
ESP_LOGD(TAG, "Manufacturer ID: 0x%02X", manf_id);
|
||||
ESP_LOGD(TAG, "Hardware version: 0x%02X", hw_version);
|
||||
ESP_LOGD(TAG, "ROM code version: 0x%02X", rom_code_version);
|
||||
ESP_LOGD(TAG, "FLASH major version: 0x%02X", flash_major_version);
|
||||
ESP_LOGD(TAG, "FLASH minor version: 0x%02X", flash_minor_version);
|
||||
|
||||
return rx.get_simple_status_response();
|
||||
}
|
||||
|
||||
uint8_t PN7150::send_init_config_() {
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::NCI_PROPRIETARY_GID, nfc::NCI_CORE_SET_CONFIG_OID);
|
||||
|
||||
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Error enabling proprietary extensions");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
tx.set_message(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::NCI_CORE_GID, nfc::NCI_CORE_SET_CONFIG_OID,
|
||||
std::vector<uint8_t>(std::begin(PMU_CFG), std::end(PMU_CFG)));
|
||||
|
||||
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Error sending PMU config");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
return this->send_core_config_();
|
||||
}
|
||||
|
||||
uint8_t PN7150::send_core_config_() {
|
||||
const auto *core_config_begin = std::begin(CORE_CONFIG_SOLO);
|
||||
const auto *core_config_end = std::end(CORE_CONFIG_SOLO);
|
||||
this->core_config_is_solo_ = true;
|
||||
|
||||
if (this->listening_enabled_ && this->polling_enabled_) {
|
||||
core_config_begin = std::begin(CORE_CONFIG_RW_CE);
|
||||
core_config_end = std::end(CORE_CONFIG_RW_CE);
|
||||
this->core_config_is_solo_ = false;
|
||||
}
|
||||
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::NCI_CORE_GID, nfc::NCI_CORE_SET_CONFIG_OID,
|
||||
std::vector<uint8_t>(core_config_begin, core_config_end));
|
||||
|
||||
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||
ESP_LOGW(TAG, "Error sending core config");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150::refresh_core_config_() {
|
||||
bool core_config_should_be_solo = !(this->listening_enabled_ && this->polling_enabled_);
|
||||
|
||||
if (this->nci_state_ == NCIState::RFST_DISCOVERY) {
|
||||
if (this->stop_discovery_() != nfc::STATUS_OK) {
|
||||
this->nci_fsm_set_state_(NCIState::NFCC_RESET);
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
this->nci_fsm_set_state_(NCIState::RFST_IDLE);
|
||||
}
|
||||
|
||||
if (this->core_config_is_solo_ != core_config_should_be_solo) {
|
||||
if (this->send_core_config_() != nfc::STATUS_OK) {
|
||||
ESP_LOGV(TAG, "Failed to refresh core config");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
this->config_refresh_pending_ = false;
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150::set_discover_map_() {
|
||||
std::vector<uint8_t> discover_map = {sizeof(RF_DISCOVER_MAP_CONFIG) / 3};
|
||||
discover_map.insert(discover_map.end(), std::begin(RF_DISCOVER_MAP_CONFIG), std::end(RF_DISCOVER_MAP_CONFIG));
|
||||
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DISCOVER_MAP_OID, discover_map);
|
||||
|
||||
if (this->transceive_(tx, rx, NFCC_INIT_TIMEOUT) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Error sending discover map poll config");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150::set_listen_mode_routing_() {
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(
|
||||
nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_SET_LISTEN_MODE_ROUTING_OID,
|
||||
std::vector<uint8_t>(std::begin(RF_LISTEN_MODE_ROUTING_CONFIG), std::end(RF_LISTEN_MODE_ROUTING_CONFIG)));
|
||||
|
||||
if (this->transceive_(tx, rx, NFCC_INIT_TIMEOUT) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Error setting listen mode routing config");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150::start_discovery_() {
|
||||
const uint8_t *rf_discovery_config = RF_DISCOVERY_CONFIG;
|
||||
uint8_t length = sizeof(RF_DISCOVERY_CONFIG);
|
||||
|
||||
if (!this->listening_enabled_) {
|
||||
length = sizeof(RF_DISCOVERY_POLL_CONFIG);
|
||||
rf_discovery_config = RF_DISCOVERY_POLL_CONFIG;
|
||||
} else if (!this->polling_enabled_) {
|
||||
length = sizeof(RF_DISCOVERY_LISTEN_CONFIG);
|
||||
rf_discovery_config = RF_DISCOVERY_LISTEN_CONFIG;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> discover_config = std::vector<uint8_t>((length * 2) + 1);
|
||||
|
||||
discover_config[0] = length;
|
||||
for (uint8_t i = 0; i < length; i++) {
|
||||
discover_config[(i * 2) + 1] = rf_discovery_config[i];
|
||||
discover_config[(i * 2) + 2] = 0x01; // RF Technology and Mode will be executed in every discovery period
|
||||
}
|
||||
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DISCOVER_OID, discover_config);
|
||||
|
||||
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||
switch (rx.get_simple_status_response()) {
|
||||
// in any of these cases, we are either already in or will remain in discovery, which satisfies the function call
|
||||
case nfc::STATUS_OK:
|
||||
case nfc::DISCOVERY_ALREADY_STARTED:
|
||||
case nfc::DISCOVERY_TARGET_ACTIVATION_FAILED:
|
||||
case nfc::DISCOVERY_TEAR_DOWN:
|
||||
return nfc::STATUS_OK;
|
||||
|
||||
default:
|
||||
ESP_LOGE(TAG, "Error starting discovery");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150::stop_discovery_() { return this->deactivate_(nfc::DEACTIVATION_TYPE_IDLE, NFCC_TAG_WRITE_TIMEOUT); }
|
||||
|
||||
uint8_t PN7150::deactivate_(const uint8_t type, const uint16_t timeout) {
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DEACTIVATE_OID, {type});
|
||||
|
||||
auto status = this->transceive_(tx, rx, timeout);
|
||||
// if (status != nfc::STATUS_OK) {
|
||||
// ESP_LOGE(TAG, "Error sending deactivate type %u", type);
|
||||
// return nfc::STATUS_FAILED;
|
||||
// }
|
||||
return status;
|
||||
}
|
||||
|
||||
void PN7150::select_endpoint_() {
|
||||
if (this->discovered_endpoint_.empty()) {
|
||||
ESP_LOGW(TAG, "No cached tags to select");
|
||||
this->stop_discovery_();
|
||||
this->nci_fsm_set_state_(NCIState::RFST_IDLE);
|
||||
return;
|
||||
}
|
||||
std::vector<uint8_t> endpoint_data = {this->discovered_endpoint_[0].id, this->discovered_endpoint_[0].protocol,
|
||||
0x01}; // that last byte is the interface ID
|
||||
for (size_t i = 0; i < this->discovered_endpoint_.size(); i++) {
|
||||
if (!this->discovered_endpoint_[i].trig_called) {
|
||||
endpoint_data = {this->discovered_endpoint_[i].id, this->discovered_endpoint_[i].protocol,
|
||||
0x01}; // that last byte is the interface ID
|
||||
this->selecting_endpoint_ = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DISCOVER_SELECT_OID, endpoint_data);
|
||||
|
||||
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Error selecting endpoint");
|
||||
} else {
|
||||
this->nci_fsm_set_state_(NCIState::EP_SELECTING);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t PN7150::read_endpoint_data_(nfc::NfcTag &tag) {
|
||||
uint8_t type = nfc::guess_tag_type(tag.get_uid().size());
|
||||
|
||||
switch (type) {
|
||||
case nfc::TAG_TYPE_MIFARE_CLASSIC:
|
||||
ESP_LOGV(TAG, "Reading Mifare classic");
|
||||
return this->read_mifare_classic_tag_(tag);
|
||||
|
||||
case nfc::TAG_TYPE_2:
|
||||
ESP_LOGV(TAG, "Reading Mifare ultralight");
|
||||
return this->read_mifare_ultralight_tag_(tag);
|
||||
|
||||
case nfc::TAG_TYPE_UNKNOWN:
|
||||
default:
|
||||
ESP_LOGV(TAG, "Cannot determine tag type");
|
||||
break;
|
||||
}
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
uint8_t PN7150::clean_endpoint_(std::vector<uint8_t> &uid) {
|
||||
uint8_t type = nfc::guess_tag_type(uid.size());
|
||||
switch (type) {
|
||||
case nfc::TAG_TYPE_MIFARE_CLASSIC:
|
||||
return this->format_mifare_classic_mifare_();
|
||||
|
||||
case nfc::TAG_TYPE_2:
|
||||
return this->clean_mifare_ultralight_();
|
||||
|
||||
default:
|
||||
ESP_LOGE(TAG, "Unsupported tag for cleaning");
|
||||
break;
|
||||
}
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
uint8_t PN7150::format_endpoint_(std::vector<uint8_t> &uid) {
|
||||
uint8_t type = nfc::guess_tag_type(uid.size());
|
||||
switch (type) {
|
||||
case nfc::TAG_TYPE_MIFARE_CLASSIC:
|
||||
return this->format_mifare_classic_ndef_();
|
||||
|
||||
case nfc::TAG_TYPE_2:
|
||||
return this->clean_mifare_ultralight_();
|
||||
|
||||
default:
|
||||
ESP_LOGE(TAG, "Unsupported tag for formatting");
|
||||
break;
|
||||
}
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
uint8_t PN7150::write_endpoint_(std::vector<uint8_t> &uid, std::shared_ptr<nfc::NdefMessage> &message) {
|
||||
uint8_t type = nfc::guess_tag_type(uid.size());
|
||||
switch (type) {
|
||||
case nfc::TAG_TYPE_MIFARE_CLASSIC:
|
||||
return this->write_mifare_classic_tag_(message);
|
||||
|
||||
case nfc::TAG_TYPE_2:
|
||||
return this->write_mifare_ultralight_tag_(uid, message);
|
||||
|
||||
default:
|
||||
ESP_LOGE(TAG, "Unsupported tag for writing");
|
||||
break;
|
||||
}
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
std::unique_ptr<nfc::NfcTag> PN7150::build_tag_(const uint8_t mode_tech, const std::vector<uint8_t> &data) {
|
||||
switch (mode_tech) {
|
||||
case (nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCA): {
|
||||
uint8_t uid_length = data[2];
|
||||
if (!uid_length) {
|
||||
ESP_LOGE(TAG, "UID length cannot be zero");
|
||||
return nullptr;
|
||||
}
|
||||
std::vector<uint8_t> uid(data.begin() + 3, data.begin() + 3 + uid_length);
|
||||
const auto *tag_type_str =
|
||||
nfc::guess_tag_type(uid_length) == nfc::TAG_TYPE_MIFARE_CLASSIC ? nfc::MIFARE_CLASSIC : nfc::NFC_FORUM_TYPE_2;
|
||||
return make_unique<nfc::NfcTag>(uid, tag_type_str);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
optional<size_t> PN7150::find_tag_uid_(const std::vector<uint8_t> &uid) {
|
||||
if (!this->discovered_endpoint_.empty()) {
|
||||
for (size_t i = 0; i < this->discovered_endpoint_.size(); i++) {
|
||||
auto existing_tag_uid = this->discovered_endpoint_[i].tag->get_uid();
|
||||
bool uid_match = (uid.size() == existing_tag_uid.size());
|
||||
|
||||
if (uid_match) {
|
||||
for (size_t i = 0; i < uid.size(); i++) {
|
||||
uid_match &= (uid[i] == existing_tag_uid[i]);
|
||||
}
|
||||
if (uid_match) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
void PN7150::purge_old_tags_() {
|
||||
for (size_t i = 0; i < this->discovered_endpoint_.size(); i++) {
|
||||
if (millis() - this->discovered_endpoint_[i].last_seen > this->tag_ttl_) {
|
||||
this->erase_tag_(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PN7150::erase_tag_(const uint8_t tag_index) {
|
||||
if (tag_index < this->discovered_endpoint_.size()) {
|
||||
for (auto *trigger : this->triggers_ontagremoved_) {
|
||||
trigger->process(this->discovered_endpoint_[tag_index].tag);
|
||||
}
|
||||
ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid(this->discovered_endpoint_[tag_index].tag->get_uid()).c_str());
|
||||
this->discovered_endpoint_.erase(this->discovered_endpoint_.begin() + tag_index);
|
||||
}
|
||||
}
|
||||
|
||||
void PN7150::nci_fsm_transition_() {
|
||||
switch (this->nci_state_) {
|
||||
case NCIState::NFCC_RESET:
|
||||
if (this->reset_core_(true, true) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Failed to reset NCI core");
|
||||
this->nci_fsm_set_error_state_(NCIState::NFCC_RESET);
|
||||
return;
|
||||
} else {
|
||||
this->nci_fsm_set_state_(NCIState::NFCC_INIT);
|
||||
}
|
||||
// fall through
|
||||
|
||||
case NCIState::NFCC_INIT:
|
||||
if (this->init_core_() != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialise NCI core");
|
||||
this->nci_fsm_set_error_state_(NCIState::NFCC_INIT);
|
||||
return;
|
||||
} else {
|
||||
this->nci_fsm_set_state_(NCIState::NFCC_CONFIG);
|
||||
}
|
||||
// fall through
|
||||
|
||||
case NCIState::NFCC_CONFIG:
|
||||
if (this->send_init_config_() != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send initial config");
|
||||
this->nci_fsm_set_error_state_(NCIState::NFCC_CONFIG);
|
||||
return;
|
||||
} else {
|
||||
this->config_refresh_pending_ = false;
|
||||
this->nci_fsm_set_state_(NCIState::NFCC_SET_DISCOVER_MAP);
|
||||
}
|
||||
// fall through
|
||||
|
||||
case NCIState::NFCC_SET_DISCOVER_MAP:
|
||||
if (this->set_discover_map_() != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set discover map");
|
||||
this->nci_fsm_set_error_state_(NCIState::NFCC_SET_LISTEN_MODE_ROUTING);
|
||||
return;
|
||||
} else {
|
||||
this->nci_fsm_set_state_(NCIState::NFCC_SET_LISTEN_MODE_ROUTING);
|
||||
}
|
||||
// fall through
|
||||
|
||||
case NCIState::NFCC_SET_LISTEN_MODE_ROUTING:
|
||||
if (this->set_listen_mode_routing_() != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set listen mode routing");
|
||||
this->nci_fsm_set_error_state_(NCIState::RFST_IDLE);
|
||||
return;
|
||||
} else {
|
||||
this->nci_fsm_set_state_(NCIState::RFST_IDLE);
|
||||
}
|
||||
// fall through
|
||||
|
||||
case NCIState::RFST_IDLE:
|
||||
if (this->nci_state_error_ == NCIState::RFST_DISCOVERY) {
|
||||
this->stop_discovery_();
|
||||
}
|
||||
|
||||
if (this->config_refresh_pending_) {
|
||||
this->refresh_core_config_();
|
||||
}
|
||||
|
||||
if (!this->listening_enabled_ && !this->polling_enabled_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->start_discovery_() != nfc::STATUS_OK) {
|
||||
ESP_LOGV(TAG, "Failed to start discovery");
|
||||
this->nci_fsm_set_error_state_(NCIState::RFST_DISCOVERY);
|
||||
} else {
|
||||
this->nci_fsm_set_state_(NCIState::RFST_DISCOVERY);
|
||||
}
|
||||
return;
|
||||
|
||||
case NCIState::RFST_W4_HOST_SELECT:
|
||||
select_endpoint_();
|
||||
// fall through
|
||||
|
||||
// All cases below are waiting for NOTIFICATION messages
|
||||
case NCIState::RFST_DISCOVERY:
|
||||
if (this->config_refresh_pending_) {
|
||||
this->refresh_core_config_();
|
||||
}
|
||||
// fall through
|
||||
|
||||
case NCIState::RFST_LISTEN_ACTIVE:
|
||||
case NCIState::RFST_LISTEN_SLEEP:
|
||||
case NCIState::RFST_POLL_ACTIVE:
|
||||
case NCIState::EP_SELECTING:
|
||||
case NCIState::EP_DEACTIVATING:
|
||||
if (this->irq_pin_->digital_read()) {
|
||||
this->process_message_();
|
||||
}
|
||||
break;
|
||||
|
||||
case NCIState::TEST:
|
||||
case NCIState::FAILED:
|
||||
case NCIState::NONE:
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void PN7150::nci_fsm_set_state_(NCIState new_state) {
|
||||
ESP_LOGVV(TAG, "nci_fsm_set_state_(%u)", (uint8_t) new_state);
|
||||
this->nci_state_ = new_state;
|
||||
this->nci_state_error_ = NCIState::NONE;
|
||||
this->error_count_ = 0;
|
||||
this->last_nci_state_change_ = millis();
|
||||
}
|
||||
|
||||
bool PN7150::nci_fsm_set_error_state_(NCIState new_state) {
|
||||
ESP_LOGVV(TAG, "nci_fsm_set_error_state_(%u); error_count_ = %u", (uint8_t) new_state, this->error_count_);
|
||||
this->nci_state_error_ = new_state;
|
||||
if (this->error_count_++ > NFCC_MAX_ERROR_COUNT) {
|
||||
if ((this->nci_state_error_ == NCIState::NFCC_RESET) || (this->nci_state_error_ == NCIState::NFCC_INIT) ||
|
||||
(this->nci_state_error_ == NCIState::NFCC_CONFIG)) {
|
||||
ESP_LOGE(TAG, "Too many initialization failures -- check device connections");
|
||||
this->mark_failed();
|
||||
this->nci_fsm_set_state_(NCIState::FAILED);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Too many errors transitioning to state %u; resetting NFCC", (uint8_t) this->nci_state_error_);
|
||||
this->nci_fsm_set_state_(NCIState::NFCC_RESET);
|
||||
}
|
||||
}
|
||||
return this->error_count_ > NFCC_MAX_ERROR_COUNT;
|
||||
}
|
||||
|
||||
void PN7150::process_message_() {
|
||||
nfc::NciMessage rx;
|
||||
if (this->read_nfcc(rx, NFCC_DEFAULT_TIMEOUT) != nfc::STATUS_OK) {
|
||||
return; // No data
|
||||
}
|
||||
|
||||
switch (rx.get_message_type()) {
|
||||
case nfc::NCI_PKT_MT_CTRL_NOTIFICATION:
|
||||
if (rx.get_gid() == nfc::RF_GID) {
|
||||
switch (rx.get_oid()) {
|
||||
case nfc::RF_INTF_ACTIVATED_OID:
|
||||
ESP_LOGVV(TAG, "RF_INTF_ACTIVATED_OID");
|
||||
this->process_rf_intf_activated_oid_(rx);
|
||||
return;
|
||||
|
||||
case nfc::RF_DISCOVER_OID:
|
||||
ESP_LOGVV(TAG, "RF_DISCOVER_OID");
|
||||
this->process_rf_discover_oid_(rx);
|
||||
return;
|
||||
|
||||
case nfc::RF_DEACTIVATE_OID:
|
||||
ESP_LOGVV(TAG, "RF_DEACTIVATE_OID: type: 0x%02X, reason: 0x%02X", rx.get_message()[3], rx.get_message()[4]);
|
||||
this->process_rf_deactivate_oid_(rx);
|
||||
return;
|
||||
|
||||
default:
|
||||
ESP_LOGV(TAG, "Unimplemented RF OID received: 0x%02X", rx.get_oid());
|
||||
}
|
||||
} else if (rx.get_gid() == nfc::NCI_CORE_GID) {
|
||||
switch (rx.get_oid()) {
|
||||
case nfc::NCI_CORE_GENERIC_ERROR_OID:
|
||||
ESP_LOGV(TAG, "NCI_CORE_GENERIC_ERROR_OID:");
|
||||
switch (rx.get_simple_status_response()) {
|
||||
case nfc::DISCOVERY_ALREADY_STARTED:
|
||||
ESP_LOGV(TAG, " DISCOVERY_ALREADY_STARTED");
|
||||
break;
|
||||
|
||||
case nfc::DISCOVERY_TARGET_ACTIVATION_FAILED:
|
||||
// Tag removed too soon
|
||||
ESP_LOGV(TAG, " DISCOVERY_TARGET_ACTIVATION_FAILED");
|
||||
if (this->nci_state_ == NCIState::EP_SELECTING) {
|
||||
this->nci_fsm_set_state_(NCIState::RFST_W4_HOST_SELECT);
|
||||
if (!this->discovered_endpoint_.empty()) {
|
||||
this->erase_tag_(this->selecting_endpoint_);
|
||||
}
|
||||
} else {
|
||||
this->stop_discovery_();
|
||||
this->nci_fsm_set_state_(NCIState::RFST_IDLE);
|
||||
}
|
||||
break;
|
||||
|
||||
case nfc::DISCOVERY_TEAR_DOWN:
|
||||
ESP_LOGV(TAG, " DISCOVERY_TEAR_DOWN");
|
||||
break;
|
||||
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown error: 0x%02X", rx.get_simple_status_response());
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
ESP_LOGV(TAG, "Unimplemented NCI Core OID received: 0x%02X", rx.get_oid());
|
||||
}
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Unimplemented notification: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
}
|
||||
break;
|
||||
|
||||
case nfc::NCI_PKT_MT_CTRL_RESPONSE:
|
||||
ESP_LOGV(TAG, "Unimplemented GID: 0x%02X OID: 0x%02X Full response: %s", rx.get_gid(), rx.get_oid(),
|
||||
nfc::format_bytes(rx.get_message()).c_str());
|
||||
break;
|
||||
|
||||
case nfc::NCI_PKT_MT_CTRL_COMMAND:
|
||||
ESP_LOGV(TAG, "Unimplemented command: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
break;
|
||||
|
||||
case nfc::NCI_PKT_MT_DATA:
|
||||
this->process_data_message_(rx);
|
||||
break;
|
||||
|
||||
default:
|
||||
ESP_LOGV(TAG, "Unimplemented message type: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PN7150::process_rf_intf_activated_oid_(nfc::NciMessage &rx) { // an endpoint was activated
|
||||
uint8_t discovery_id = rx.get_message_byte(nfc::RF_INTF_ACTIVATED_NTF_DISCOVERY_ID);
|
||||
uint8_t interface = rx.get_message_byte(nfc::RF_INTF_ACTIVATED_NTF_INTERFACE);
|
||||
uint8_t protocol = rx.get_message_byte(nfc::RF_INTF_ACTIVATED_NTF_PROTOCOL);
|
||||
uint8_t mode_tech = rx.get_message_byte(nfc::RF_INTF_ACTIVATED_NTF_MODE_TECH);
|
||||
uint8_t max_size = rx.get_message_byte(nfc::RF_INTF_ACTIVATED_NTF_MAX_SIZE);
|
||||
|
||||
ESP_LOGVV(TAG, "Endpoint activated -- interface: 0x%02X, protocol: 0x%02X, mode&tech: 0x%02X, max payload: %u",
|
||||
interface, protocol, mode_tech, max_size);
|
||||
|
||||
if (mode_tech & nfc::MODE_LISTEN_MASK) {
|
||||
ESP_LOGVV(TAG, "Tag activated in listen mode");
|
||||
this->nci_fsm_set_state_(NCIState::RFST_LISTEN_ACTIVE);
|
||||
return;
|
||||
}
|
||||
|
||||
this->nci_fsm_set_state_(NCIState::RFST_POLL_ACTIVE);
|
||||
auto incoming_tag =
|
||||
this->build_tag_(mode_tech, std::vector<uint8_t>(rx.get_message().begin() + 10, rx.get_message().end()));
|
||||
|
||||
if (incoming_tag == nullptr) {
|
||||
ESP_LOGE(TAG, "Could not build tag");
|
||||
} else {
|
||||
auto tag_loc = this->find_tag_uid_(incoming_tag->get_uid());
|
||||
if (tag_loc.has_value()) {
|
||||
this->discovered_endpoint_[tag_loc.value()].id = discovery_id;
|
||||
this->discovered_endpoint_[tag_loc.value()].protocol = protocol;
|
||||
this->discovered_endpoint_[tag_loc.value()].last_seen = millis();
|
||||
ESP_LOGVV(TAG, "Tag cache updated");
|
||||
} else {
|
||||
this->discovered_endpoint_.emplace_back(
|
||||
DiscoveredEndpoint{discovery_id, protocol, millis(), std::move(incoming_tag), false});
|
||||
tag_loc = this->discovered_endpoint_.size() - 1;
|
||||
ESP_LOGVV(TAG, "Tag added to cache");
|
||||
}
|
||||
|
||||
auto &working_endpoint = this->discovered_endpoint_[tag_loc.value()];
|
||||
|
||||
switch (this->next_task_) {
|
||||
case EP_CLEAN:
|
||||
ESP_LOGD(TAG, " Tag cleaning...");
|
||||
if (this->clean_endpoint_(working_endpoint.tag->get_uid()) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, " Tag cleaning incomplete");
|
||||
}
|
||||
ESP_LOGD(TAG, " Tag cleaned!");
|
||||
break;
|
||||
|
||||
case EP_FORMAT:
|
||||
ESP_LOGD(TAG, " Tag formatting...");
|
||||
if (this->format_endpoint_(working_endpoint.tag->get_uid()) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Error formatting tag as NDEF");
|
||||
}
|
||||
ESP_LOGD(TAG, " Tag formatted!");
|
||||
break;
|
||||
|
||||
case EP_WRITE:
|
||||
if (this->next_task_message_to_write_ != nullptr) {
|
||||
ESP_LOGD(TAG, " Tag writing...");
|
||||
ESP_LOGD(TAG, " Tag formatting...");
|
||||
if (this->format_endpoint_(working_endpoint.tag->get_uid()) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, " Tag could not be formatted for writing");
|
||||
} else {
|
||||
ESP_LOGD(TAG, " Writing NDEF data");
|
||||
if (this->write_endpoint_(working_endpoint.tag->get_uid(), this->next_task_message_to_write_) !=
|
||||
nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, " Failed to write message to tag");
|
||||
}
|
||||
ESP_LOGD(TAG, " Finished writing NDEF data");
|
||||
this->next_task_message_to_write_ = nullptr;
|
||||
this->on_finished_write_callback_.call();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case EP_READ:
|
||||
default:
|
||||
if (!working_endpoint.trig_called) {
|
||||
ESP_LOGI(TAG, "Read tag type %s with UID %s", working_endpoint.tag->get_tag_type().c_str(),
|
||||
nfc::format_uid(working_endpoint.tag->get_uid()).c_str());
|
||||
if (this->read_endpoint_data_(*working_endpoint.tag) != nfc::STATUS_OK) {
|
||||
ESP_LOGW(TAG, " Unable to read NDEF record(s)");
|
||||
} else if (working_endpoint.tag->has_ndef_message()) {
|
||||
const auto message = working_endpoint.tag->get_ndef_message();
|
||||
const auto records = message->get_records();
|
||||
ESP_LOGD(TAG, " NDEF record(s):");
|
||||
for (const auto &record : records) {
|
||||
ESP_LOGD(TAG, " %s - %s", record->get_type().c_str(), record->get_payload().c_str());
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, " No NDEF records found");
|
||||
}
|
||||
for (auto *trigger : this->triggers_ontag_) {
|
||||
trigger->process(working_endpoint.tag);
|
||||
}
|
||||
working_endpoint.trig_called = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (working_endpoint.tag->get_tag_type() == nfc::MIFARE_CLASSIC) {
|
||||
this->halt_mifare_classic_tag_();
|
||||
}
|
||||
}
|
||||
if (this->next_task_ != EP_READ) {
|
||||
this->read_mode();
|
||||
}
|
||||
|
||||
this->stop_discovery_();
|
||||
this->nci_fsm_set_state_(NCIState::EP_DEACTIVATING);
|
||||
}
|
||||
|
||||
void PN7150::process_rf_discover_oid_(nfc::NciMessage &rx) {
|
||||
auto incoming_tag = this->build_tag_(rx.get_message_byte(nfc::RF_DISCOVER_NTF_MODE_TECH),
|
||||
std::vector<uint8_t>(rx.get_message().begin() + 7, rx.get_message().end()));
|
||||
|
||||
if (incoming_tag == nullptr) {
|
||||
ESP_LOGE(TAG, "Could not build tag!");
|
||||
} else {
|
||||
auto tag_loc = this->find_tag_uid_(incoming_tag->get_uid());
|
||||
if (tag_loc.has_value()) {
|
||||
this->discovered_endpoint_[tag_loc.value()].id = rx.get_message_byte(nfc::RF_DISCOVER_NTF_DISCOVERY_ID);
|
||||
this->discovered_endpoint_[tag_loc.value()].protocol = rx.get_message_byte(nfc::RF_DISCOVER_NTF_PROTOCOL);
|
||||
this->discovered_endpoint_[tag_loc.value()].last_seen = millis();
|
||||
ESP_LOGVV(TAG, "Tag found & updated");
|
||||
} else {
|
||||
this->discovered_endpoint_.emplace_back(DiscoveredEndpoint{rx.get_message_byte(nfc::RF_DISCOVER_NTF_DISCOVERY_ID),
|
||||
rx.get_message_byte(nfc::RF_DISCOVER_NTF_PROTOCOL),
|
||||
millis(), std::move(incoming_tag), false});
|
||||
ESP_LOGVV(TAG, "Tag saved");
|
||||
}
|
||||
}
|
||||
|
||||
if (rx.get_message().back() != nfc::RF_DISCOVER_NTF_NT_MORE) {
|
||||
this->nci_fsm_set_state_(NCIState::RFST_W4_HOST_SELECT);
|
||||
ESP_LOGVV(TAG, "Discovered %u endpoints", this->discovered_endpoint_.size());
|
||||
}
|
||||
}
|
||||
|
||||
void PN7150::process_rf_deactivate_oid_(nfc::NciMessage &rx) {
|
||||
this->ce_state_ = CardEmulationState::CARD_EMU_IDLE;
|
||||
|
||||
switch (rx.get_simple_status_response()) {
|
||||
case nfc::DEACTIVATION_TYPE_DISCOVERY:
|
||||
this->nci_fsm_set_state_(NCIState::RFST_DISCOVERY);
|
||||
break;
|
||||
|
||||
case nfc::DEACTIVATION_TYPE_IDLE:
|
||||
this->nci_fsm_set_state_(NCIState::RFST_IDLE);
|
||||
break;
|
||||
|
||||
case nfc::DEACTIVATION_TYPE_SLEEP:
|
||||
case nfc::DEACTIVATION_TYPE_SLEEP_AF:
|
||||
if (this->nci_state_ == NCIState::RFST_LISTEN_ACTIVE) {
|
||||
this->nci_fsm_set_state_(NCIState::RFST_LISTEN_SLEEP);
|
||||
} else if (this->nci_state_ == NCIState::RFST_POLL_ACTIVE) {
|
||||
this->nci_fsm_set_state_(NCIState::RFST_W4_HOST_SELECT);
|
||||
} else {
|
||||
this->nci_fsm_set_state_(NCIState::RFST_IDLE);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PN7150::process_data_message_(nfc::NciMessage &rx) {
|
||||
ESP_LOGVV(TAG, "Received data message: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
|
||||
std::vector<uint8_t> ndef_response;
|
||||
this->card_emu_t4t_get_response_(rx.get_message(), ndef_response);
|
||||
|
||||
uint16_t ndef_response_size = ndef_response.size();
|
||||
if (!ndef_response_size) {
|
||||
return; // no message returned, we cannot respond
|
||||
}
|
||||
|
||||
std::vector<uint8_t> tx_msg = {nfc::NCI_PKT_MT_DATA, uint8_t((ndef_response_size & 0xFF00) >> 8),
|
||||
uint8_t(ndef_response_size & 0x00FF)};
|
||||
tx_msg.insert(tx_msg.end(), ndef_response.begin(), ndef_response.end());
|
||||
nfc::NciMessage tx(tx_msg);
|
||||
ESP_LOGVV(TAG, "Sending data message: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
if (this->transceive_(tx, rx, NFCC_DEFAULT_TIMEOUT, false) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Sending reply for card emulation failed");
|
||||
}
|
||||
}
|
||||
|
||||
void PN7150::card_emu_t4t_get_response_(std::vector<uint8_t> &response, std::vector<uint8_t> &ndef_response) {
|
||||
if (this->card_emulation_message_ == nullptr) {
|
||||
ESP_LOGE(TAG, "No NDEF message is set; tag emulation not possible");
|
||||
ndef_response.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (equal(response.begin() + nfc::NCI_PKT_HEADER_SIZE, response.end(), std::begin(CARD_EMU_T4T_APP_SELECT))) {
|
||||
// CARD_EMU_T4T_APP_SELECT
|
||||
ESP_LOGVV(TAG, "CARD_EMU_NDEF_APP_SELECTED");
|
||||
this->ce_state_ = CardEmulationState::CARD_EMU_NDEF_APP_SELECTED;
|
||||
ndef_response.insert(ndef_response.begin(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK));
|
||||
} else if (equal(response.begin() + nfc::NCI_PKT_HEADER_SIZE, response.end(), std::begin(CARD_EMU_T4T_CC_SELECT))) {
|
||||
// CARD_EMU_T4T_CC_SELECT
|
||||
if (this->ce_state_ == CardEmulationState::CARD_EMU_NDEF_APP_SELECTED) {
|
||||
ESP_LOGVV(TAG, "CARD_EMU_CC_SELECTED");
|
||||
this->ce_state_ = CardEmulationState::CARD_EMU_CC_SELECTED;
|
||||
ndef_response.insert(ndef_response.begin(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK));
|
||||
}
|
||||
} else if (equal(response.begin() + nfc::NCI_PKT_HEADER_SIZE, response.end(), std::begin(CARD_EMU_T4T_NDEF_SELECT))) {
|
||||
// CARD_EMU_T4T_NDEF_SELECT
|
||||
ESP_LOGVV(TAG, "CARD_EMU_NDEF_SELECTED");
|
||||
this->ce_state_ = CardEmulationState::CARD_EMU_NDEF_SELECTED;
|
||||
ndef_response.insert(ndef_response.begin(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK));
|
||||
} else if (equal(response.begin() + nfc::NCI_PKT_HEADER_SIZE,
|
||||
response.begin() + nfc::NCI_PKT_HEADER_SIZE + sizeof(CARD_EMU_T4T_READ),
|
||||
std::begin(CARD_EMU_T4T_READ))) {
|
||||
// CARD_EMU_T4T_READ
|
||||
if (this->ce_state_ == CardEmulationState::CARD_EMU_CC_SELECTED) {
|
||||
// CARD_EMU_T4T_READ with CARD_EMU_CC_SELECTED
|
||||
ESP_LOGVV(TAG, "CARD_EMU_T4T_READ with CARD_EMU_CC_SELECTED");
|
||||
uint16_t offset = (response[nfc::NCI_PKT_HEADER_SIZE + 2] << 8) + response[nfc::NCI_PKT_HEADER_SIZE + 3];
|
||||
uint8_t length = response[nfc::NCI_PKT_HEADER_SIZE + 4];
|
||||
|
||||
if (length <= (sizeof(CARD_EMU_T4T_CC) + offset + 2)) {
|
||||
ndef_response.insert(ndef_response.begin(), std::begin(CARD_EMU_T4T_CC) + offset,
|
||||
std::begin(CARD_EMU_T4T_CC) + offset + length);
|
||||
ndef_response.insert(ndef_response.end(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK));
|
||||
}
|
||||
} else if (this->ce_state_ == CardEmulationState::CARD_EMU_NDEF_SELECTED) {
|
||||
// CARD_EMU_T4T_READ with CARD_EMU_NDEF_SELECTED
|
||||
ESP_LOGVV(TAG, "CARD_EMU_T4T_READ with CARD_EMU_NDEF_SELECTED");
|
||||
auto ndef_message = this->card_emulation_message_->encode();
|
||||
uint16_t ndef_msg_size = ndef_message.size();
|
||||
uint16_t offset = (response[nfc::NCI_PKT_HEADER_SIZE + 2] << 8) + response[nfc::NCI_PKT_HEADER_SIZE + 3];
|
||||
uint8_t length = response[nfc::NCI_PKT_HEADER_SIZE + 4];
|
||||
|
||||
ESP_LOGVV(TAG, "Encoded NDEF message: %s", nfc::format_bytes(ndef_message).c_str());
|
||||
|
||||
if (length <= (ndef_msg_size + offset + 2)) {
|
||||
if (offset == 0) {
|
||||
ndef_response.resize(2);
|
||||
ndef_response[0] = (ndef_msg_size & 0xFF00) >> 8;
|
||||
ndef_response[1] = (ndef_msg_size & 0x00FF);
|
||||
if (length > 2) {
|
||||
ndef_response.insert(ndef_response.end(), ndef_message.begin(), ndef_message.begin() + length - 2);
|
||||
}
|
||||
} else if (offset == 1) {
|
||||
ndef_response.resize(1);
|
||||
ndef_response[0] = (ndef_msg_size & 0x00FF);
|
||||
if (length > 1) {
|
||||
ndef_response.insert(ndef_response.end(), ndef_message.begin(), ndef_message.begin() + length - 1);
|
||||
}
|
||||
} else {
|
||||
ndef_response.insert(ndef_response.end(), ndef_message.begin(), ndef_message.begin() + length);
|
||||
}
|
||||
|
||||
ndef_response.insert(ndef_response.end(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK));
|
||||
|
||||
if ((offset + length) >= (ndef_msg_size + 2)) {
|
||||
ESP_LOGD(TAG, "NDEF message sent");
|
||||
this->on_emulated_tag_scan_callback_.call();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (equal(response.begin() + nfc::NCI_PKT_HEADER_SIZE,
|
||||
response.begin() + nfc::NCI_PKT_HEADER_SIZE + sizeof(CARD_EMU_T4T_WRITE),
|
||||
std::begin(CARD_EMU_T4T_WRITE))) {
|
||||
// CARD_EMU_T4T_WRITE
|
||||
if (this->ce_state_ == CardEmulationState::CARD_EMU_NDEF_SELECTED) {
|
||||
ESP_LOGVV(TAG, "CARD_EMU_T4T_WRITE");
|
||||
uint8_t length = response[nfc::NCI_PKT_HEADER_SIZE + 4];
|
||||
std::vector<uint8_t> ndef_msg_written;
|
||||
|
||||
ndef_msg_written.insert(ndef_msg_written.end(), response.begin() + nfc::NCI_PKT_HEADER_SIZE + 5,
|
||||
response.begin() + nfc::NCI_PKT_HEADER_SIZE + 5 + length);
|
||||
ESP_LOGD(TAG, "Received %u-byte NDEF message: %s", length, nfc::format_bytes(ndef_msg_written).c_str());
|
||||
ndef_response.insert(ndef_response.end(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t PN7150::transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, const uint16_t timeout,
|
||||
const bool expect_notification) {
|
||||
uint8_t retries = NFCC_MAX_COMM_FAILS;
|
||||
|
||||
while (retries) {
|
||||
// first, send the message we need to send
|
||||
if (this->write_nfcc(tx) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Error sending message");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
ESP_LOGVV(TAG, "Wrote: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
// next, the NFCC should send back a response
|
||||
if (this->read_nfcc(rx, timeout) != nfc::STATUS_OK) {
|
||||
ESP_LOGW(TAG, "Error receiving message");
|
||||
if (!retries--) {
|
||||
ESP_LOGE(TAG, " ...giving up");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ESP_LOGVV(TAG, "Read: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
// validate the response based on the message type that was sent (command vs. data)
|
||||
if (!tx.message_type_is(nfc::NCI_PKT_MT_DATA)) {
|
||||
// for commands, the GID and OID should match and the status should be OK
|
||||
if ((rx.get_gid() != tx.get_gid()) || (rx.get_oid()) != tx.get_oid()) {
|
||||
ESP_LOGE(TAG, "Incorrect response to command: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
if (!rx.simple_status_response_is(nfc::STATUS_OK)) {
|
||||
ESP_LOGE(TAG, "Error in response to command: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
}
|
||||
return rx.get_simple_status_response();
|
||||
} else {
|
||||
// when requesting data from the endpoint, the first response is from the NFCC; we must validate this, first
|
||||
if ((!rx.message_type_is(nfc::NCI_PKT_MT_CTRL_NOTIFICATION)) || (!rx.gid_is(nfc::NCI_CORE_GID)) ||
|
||||
(!rx.oid_is(nfc::NCI_CORE_CONN_CREDITS_OID)) || (!rx.message_length_is(3))) {
|
||||
ESP_LOGE(TAG, "Incorrect response to data message: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
if (expect_notification) {
|
||||
// if the NFCC said "OK", there will be additional data to read; this comes back in a notification message
|
||||
if (this->read_nfcc(rx, timeout) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Error receiving data from endpoint");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
ESP_LOGVV(TAG, "Read: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
}
|
||||
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t PN7150::wait_for_irq_(uint16_t timeout, bool pin_state) {
|
||||
auto start_time = millis();
|
||||
|
||||
while (millis() - start_time < timeout) {
|
||||
if (this->irq_pin_->digital_read() == pin_state) {
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
}
|
||||
ESP_LOGW(TAG, "Timed out waiting for IRQ state");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
} // namespace pn7150
|
||||
} // namespace esphome
|
||||
296
esphome/components/pn7150/pn7150.h
Normal file
296
esphome/components/pn7150/pn7150.h
Normal file
@ -0,0 +1,296 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/nfc/automation.h"
|
||||
#include "esphome/components/nfc/nci_core.h"
|
||||
#include "esphome/components/nfc/nci_message.h"
|
||||
#include "esphome/components/nfc/nfc.h"
|
||||
#include "esphome/components/nfc/nfc_helpers.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/gpio.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace esphome {
|
||||
namespace pn7150 {
|
||||
|
||||
static const uint16_t NFCC_DEFAULT_TIMEOUT = 10;
|
||||
static const uint16_t NFCC_INIT_TIMEOUT = 50;
|
||||
static const uint16_t NFCC_TAG_WRITE_TIMEOUT = 15;
|
||||
|
||||
static const uint8_t NFCC_MAX_COMM_FAILS = 3;
|
||||
static const uint8_t NFCC_MAX_ERROR_COUNT = 10;
|
||||
|
||||
static const uint8_t XCHG_DATA_OID = 0x10;
|
||||
static const uint8_t MF_SECTORSEL_OID = 0x32;
|
||||
static const uint8_t MFC_AUTHENTICATE_OID = 0x40;
|
||||
static const uint8_t TEST_PRBS_OID = 0x30;
|
||||
static const uint8_t TEST_ANTENNA_OID = 0x3D;
|
||||
static const uint8_t TEST_GET_REGISTER_OID = 0x33;
|
||||
|
||||
static const uint8_t MFC_AUTHENTICATE_PARAM_KS_A = 0x00; // key select A
|
||||
static const uint8_t MFC_AUTHENTICATE_PARAM_KS_B = 0x80; // key select B
|
||||
static const uint8_t MFC_AUTHENTICATE_PARAM_EMBED_KEY = 0x10;
|
||||
|
||||
static const uint8_t CARD_EMU_T4T_APP_SELECT[] = {0x00, 0xA4, 0x04, 0x00, 0x07, 0xD2, 0x76,
|
||||
0x00, 0x00, 0x85, 0x01, 0x01, 0x00};
|
||||
static const uint8_t CARD_EMU_T4T_CC[] = {0x00, 0x0F, 0x20, 0x00, 0xFF, 0x00, 0xFF, 0x04,
|
||||
0x06, 0xE1, 0x04, 0x00, 0xFF, 0x00, 0x00};
|
||||
static const uint8_t CARD_EMU_T4T_CC_SELECT[] = {0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x03};
|
||||
static const uint8_t CARD_EMU_T4T_NDEF_SELECT[] = {0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x04};
|
||||
static const uint8_t CARD_EMU_T4T_READ[] = {0x00, 0xB0};
|
||||
static const uint8_t CARD_EMU_T4T_WRITE[] = {0x00, 0xD6};
|
||||
static const uint8_t CARD_EMU_T4T_OK[] = {0x90, 0x00};
|
||||
static const uint8_t CARD_EMU_T4T_NOK[] = {0x6A, 0x82};
|
||||
|
||||
static const uint8_t CORE_CONFIG_SOLO[] = {0x01, // Number of parameter fields
|
||||
0x00, // config param identifier (TOTAL_DURATION)
|
||||
0x02, // length of value
|
||||
0x01, // TOTAL_DURATION (low)...
|
||||
0x00}; // TOTAL_DURATION (high): 1 ms
|
||||
|
||||
static const uint8_t CORE_CONFIG_RW_CE[] = {0x01, // Number of parameter fields
|
||||
0x00, // config param identifier (TOTAL_DURATION)
|
||||
0x02, // length of value
|
||||
0xF8, // TOTAL_DURATION (low)...
|
||||
0x02}; // TOTAL_DURATION (high): 760 ms
|
||||
|
||||
static const uint8_t PMU_CFG[] = {
|
||||
0x01, // Number of parameters
|
||||
0xA0, 0x0E, // ext. tag
|
||||
3, // length
|
||||
0x06, // VBAT1 connected to 5V (CFG2)
|
||||
0x64, // TVDD monitoring threshold = 5.0V; TxLDO voltage = 4.7V (in reader & card modes)
|
||||
0x01, // RFU; must be 0x00 for CFG1 and 0x01 for CFG2
|
||||
};
|
||||
|
||||
static const uint8_t RF_DISCOVER_MAP_CONFIG[] = { // poll modes
|
||||
nfc::PROT_T1T, nfc::RF_DISCOVER_MAP_MODE_POLL,
|
||||
nfc::INTF_FRAME, // poll mode
|
||||
nfc::PROT_T2T, nfc::RF_DISCOVER_MAP_MODE_POLL,
|
||||
nfc::INTF_FRAME, // poll mode
|
||||
nfc::PROT_T3T, nfc::RF_DISCOVER_MAP_MODE_POLL,
|
||||
nfc::INTF_FRAME, // poll mode
|
||||
nfc::PROT_ISODEP, nfc::RF_DISCOVER_MAP_MODE_POLL | nfc::RF_DISCOVER_MAP_MODE_LISTEN,
|
||||
nfc::INTF_ISODEP, // poll & listen mode
|
||||
nfc::PROT_MIFARE, nfc::RF_DISCOVER_MAP_MODE_POLL,
|
||||
nfc::INTF_TAGCMD}; // poll mode
|
||||
|
||||
static const uint8_t RF_DISCOVERY_LISTEN_CONFIG[] = {nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCA, // listen mode
|
||||
nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCB, // listen mode
|
||||
nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCF}; // listen mode
|
||||
|
||||
static const uint8_t RF_DISCOVERY_POLL_CONFIG[] = {nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCA, // poll mode
|
||||
nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCB, // poll mode
|
||||
nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCF}; // poll mode
|
||||
|
||||
static const uint8_t RF_DISCOVERY_CONFIG[] = {nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCA, // poll mode
|
||||
nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCB, // poll mode
|
||||
nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCF, // poll mode
|
||||
nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCA, // listen mode
|
||||
nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCB, // listen mode
|
||||
nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCF}; // listen mode
|
||||
|
||||
static const uint8_t RF_LISTEN_MODE_ROUTING_CONFIG[] = {0x00, // "more" (another message is coming)
|
||||
1, // number of table entries
|
||||
0x01, // type = protocol-based
|
||||
3, // length
|
||||
0, // DH NFCEE ID, a static ID representing the DH-NFCEE
|
||||
0x01, // power state
|
||||
nfc::PROT_ISODEP}; // protocol
|
||||
|
||||
enum class CardEmulationState : uint8_t {
|
||||
CARD_EMU_IDLE,
|
||||
CARD_EMU_NDEF_APP_SELECTED,
|
||||
CARD_EMU_CC_SELECTED,
|
||||
CARD_EMU_NDEF_SELECTED,
|
||||
CARD_EMU_DESFIRE_PROD,
|
||||
};
|
||||
|
||||
enum class NCIState : uint8_t {
|
||||
NONE = 0x00,
|
||||
NFCC_RESET,
|
||||
NFCC_INIT,
|
||||
NFCC_CONFIG,
|
||||
NFCC_SET_DISCOVER_MAP,
|
||||
NFCC_SET_LISTEN_MODE_ROUTING,
|
||||
RFST_IDLE,
|
||||
RFST_DISCOVERY,
|
||||
RFST_W4_ALL_DISCOVERIES,
|
||||
RFST_W4_HOST_SELECT,
|
||||
RFST_LISTEN_ACTIVE,
|
||||
RFST_LISTEN_SLEEP,
|
||||
RFST_POLL_ACTIVE,
|
||||
EP_DEACTIVATING,
|
||||
EP_SELECTING,
|
||||
TEST = 0XFE,
|
||||
FAILED = 0XFF,
|
||||
};
|
||||
|
||||
enum class TestMode : uint8_t {
|
||||
TEST_NONE = 0x00,
|
||||
TEST_PRBS,
|
||||
TEST_ANTENNA,
|
||||
TEST_GET_REGISTER,
|
||||
};
|
||||
|
||||
struct DiscoveredEndpoint {
|
||||
uint8_t id;
|
||||
uint8_t protocol;
|
||||
uint32_t last_seen;
|
||||
std::unique_ptr<nfc::NfcTag> tag;
|
||||
bool trig_called;
|
||||
};
|
||||
|
||||
class PN7150 : public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void loop() override;
|
||||
|
||||
void set_irq_pin(GPIOPin *irq_pin) { this->irq_pin_ = irq_pin; }
|
||||
void set_ven_pin(GPIOPin *ven_pin) { this->ven_pin_ = ven_pin; }
|
||||
|
||||
void set_tag_ttl(uint32_t ttl) { this->tag_ttl_ = ttl; }
|
||||
void set_tag_emulation_message(std::shared_ptr<nfc::NdefMessage> message);
|
||||
void set_tag_emulation_message(const optional<std::string> &message, optional<bool> include_android_app_record);
|
||||
void set_tag_emulation_message(const char *message, bool include_android_app_record = true);
|
||||
void set_tag_emulation_off();
|
||||
void set_tag_emulation_on();
|
||||
bool tag_emulation_enabled() { return this->listening_enabled_; }
|
||||
|
||||
void set_polling_off();
|
||||
void set_polling_on();
|
||||
bool polling_enabled() { return this->polling_enabled_; }
|
||||
|
||||
void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); }
|
||||
void register_ontagremoved_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontagremoved_.push_back(trig); }
|
||||
|
||||
void add_on_emulated_tag_scan_callback(std::function<void()> callback) {
|
||||
this->on_emulated_tag_scan_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
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_ != EP_READ; };
|
||||
|
||||
void read_mode();
|
||||
void clean_mode();
|
||||
void format_mode();
|
||||
void write_mode();
|
||||
void set_tag_write_message(std::shared_ptr<nfc::NdefMessage> message);
|
||||
void set_tag_write_message(optional<std::string> message, optional<bool> include_android_app_record);
|
||||
|
||||
uint8_t set_test_mode(TestMode test_mode, const std::vector<uint8_t> &data, std::vector<uint8_t> &result);
|
||||
|
||||
protected:
|
||||
uint8_t reset_core_(bool reset_config, bool power);
|
||||
uint8_t init_core_();
|
||||
uint8_t send_init_config_();
|
||||
uint8_t send_core_config_();
|
||||
uint8_t refresh_core_config_();
|
||||
|
||||
uint8_t set_discover_map_();
|
||||
|
||||
uint8_t set_listen_mode_routing_();
|
||||
|
||||
uint8_t start_discovery_();
|
||||
uint8_t stop_discovery_();
|
||||
uint8_t deactivate_(uint8_t type, uint16_t timeout = NFCC_DEFAULT_TIMEOUT);
|
||||
|
||||
void select_endpoint_();
|
||||
|
||||
uint8_t read_endpoint_data_(nfc::NfcTag &tag);
|
||||
uint8_t clean_endpoint_(std::vector<uint8_t> &uid);
|
||||
uint8_t format_endpoint_(std::vector<uint8_t> &uid);
|
||||
uint8_t write_endpoint_(std::vector<uint8_t> &uid, std::shared_ptr<nfc::NdefMessage> &message);
|
||||
|
||||
std::unique_ptr<nfc::NfcTag> build_tag_(uint8_t mode_tech, const std::vector<uint8_t> &data);
|
||||
optional<size_t> find_tag_uid_(const std::vector<uint8_t> &uid);
|
||||
void purge_old_tags_();
|
||||
void erase_tag_(uint8_t tag_index);
|
||||
|
||||
/// advance controller state as required
|
||||
void nci_fsm_transition_();
|
||||
/// set new controller state
|
||||
void nci_fsm_set_state_(NCIState new_state);
|
||||
/// setting controller to this state caused an error; returns true if too many errors/failures
|
||||
bool nci_fsm_set_error_state_(NCIState new_state);
|
||||
/// parse & process incoming messages from the NFCC
|
||||
void process_message_();
|
||||
void process_rf_intf_activated_oid_(nfc::NciMessage &rx);
|
||||
void process_rf_discover_oid_(nfc::NciMessage &rx);
|
||||
void process_rf_deactivate_oid_(nfc::NciMessage &rx);
|
||||
void process_data_message_(nfc::NciMessage &rx);
|
||||
|
||||
void card_emu_t4t_get_response_(std::vector<uint8_t> &response, std::vector<uint8_t> &ndef_response);
|
||||
|
||||
uint8_t transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, uint16_t timeout = NFCC_DEFAULT_TIMEOUT,
|
||||
bool expect_notification = true);
|
||||
virtual uint8_t read_nfcc(nfc::NciMessage &rx, uint16_t timeout) = 0;
|
||||
virtual uint8_t write_nfcc(nfc::NciMessage &tx) = 0;
|
||||
|
||||
uint8_t wait_for_irq_(uint16_t timeout = NFCC_DEFAULT_TIMEOUT, bool pin_state = true);
|
||||
|
||||
uint8_t read_mifare_classic_tag_(nfc::NfcTag &tag);
|
||||
uint8_t read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
|
||||
uint8_t write_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
|
||||
uint8_t auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, const uint8_t *key);
|
||||
uint8_t sect_to_auth_(uint8_t block_num);
|
||||
uint8_t format_mifare_classic_mifare_();
|
||||
uint8_t format_mifare_classic_ndef_();
|
||||
uint8_t write_mifare_classic_tag_(const std::shared_ptr<nfc::NdefMessage> &message);
|
||||
uint8_t halt_mifare_classic_tag_();
|
||||
|
||||
uint8_t read_mifare_ultralight_tag_(nfc::NfcTag &tag);
|
||||
uint8_t read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data);
|
||||
bool is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6);
|
||||
uint16_t read_mifare_ultralight_capacity_();
|
||||
uint8_t find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
|
||||
uint8_t &message_start_index);
|
||||
uint8_t write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data);
|
||||
uint8_t write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, const std::shared_ptr<nfc::NdefMessage> &message);
|
||||
uint8_t clean_mifare_ultralight_();
|
||||
|
||||
enum NfcTask : uint8_t {
|
||||
EP_READ = 0,
|
||||
EP_CLEAN,
|
||||
EP_FORMAT,
|
||||
EP_WRITE,
|
||||
} next_task_{EP_READ};
|
||||
|
||||
bool config_refresh_pending_{false};
|
||||
bool core_config_is_solo_{false};
|
||||
bool listening_enabled_{false};
|
||||
bool polling_enabled_{true};
|
||||
|
||||
uint8_t error_count_{0};
|
||||
uint8_t fail_count_{0};
|
||||
uint32_t last_nci_state_change_{0};
|
||||
uint8_t selecting_endpoint_{0};
|
||||
uint32_t tag_ttl_{250};
|
||||
|
||||
GPIOPin *irq_pin_{nullptr};
|
||||
GPIOPin *ven_pin_{nullptr};
|
||||
|
||||
CallbackManager<void()> on_emulated_tag_scan_callback_;
|
||||
CallbackManager<void()> on_finished_write_callback_;
|
||||
|
||||
std::vector<DiscoveredEndpoint> discovered_endpoint_;
|
||||
|
||||
CardEmulationState ce_state_{CardEmulationState::CARD_EMU_IDLE};
|
||||
NCIState nci_state_{NCIState::NFCC_RESET};
|
||||
NCIState nci_state_error_{NCIState::NONE};
|
||||
|
||||
std::shared_ptr<nfc::NdefMessage> card_emulation_message_;
|
||||
std::shared_ptr<nfc::NdefMessage> next_task_message_to_write_;
|
||||
|
||||
std::vector<nfc::NfcOnTagTrigger *> triggers_ontag_;
|
||||
std::vector<nfc::NfcOnTagTrigger *> triggers_ontagremoved_;
|
||||
};
|
||||
|
||||
} // namespace pn7150
|
||||
} // namespace esphome
|
||||
322
esphome/components/pn7150/pn7150_mifare_classic.cpp
Normal file
322
esphome/components/pn7150/pn7150_mifare_classic.cpp
Normal file
@ -0,0 +1,322 @@
|
||||
#include <memory>
|
||||
|
||||
#include "pn7150.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pn7150 {
|
||||
|
||||
static const char *const TAG = "pn7150.mifare_classic";
|
||||
|
||||
uint8_t PN7150::read_mifare_classic_tag_(nfc::NfcTag &tag) {
|
||||
uint8_t current_block = 4;
|
||||
uint8_t message_start_index = 0;
|
||||
uint32_t message_length = 0;
|
||||
|
||||
if (this->auth_mifare_classic_block_(current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Tag auth failed while attempting to read tag data");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
if (this->read_mifare_classic_block_(current_block, data) == nfc::STATUS_OK) {
|
||||
if (!nfc::decode_mifare_classic_tlv(data, message_length, message_start_index)) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to read block %u", current_block);
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
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_(current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Block authentication failed for %u", current_block);
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
std::vector<uint8_t> block_data;
|
||||
if (this->read_mifare_classic_block_(current_block, block_data) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Error reading block %u", current_block);
|
||||
return nfc::STATUS_FAILED;
|
||||
} else {
|
||||
buffer.insert(buffer.end(), block_data.begin(), block_data.end());
|
||||
}
|
||||
|
||||
index += nfc::MIFARE_CLASSIC_BLOCK_SIZE;
|
||||
current_block++;
|
||||
|
||||
if (nfc::mifare_classic_is_trailer_block(current_block)) {
|
||||
current_block++;
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer.begin() + message_start_index < buffer.end()) {
|
||||
buffer.erase(buffer.begin(), buffer.begin() + message_start_index);
|
||||
} else {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
tag.set_ndef_message(make_unique<nfc::NdefMessage>(buffer));
|
||||
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150::read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data) {
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_READ, block_num});
|
||||
|
||||
ESP_LOGVV(TAG, "Read XCHG_DATA_REQ: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Timeout reading tag data");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(XCHG_DATA_OID)) ||
|
||||
(!rx.message_length_is(18))) {
|
||||
ESP_LOGE(TAG, "MFC read block failed - block 0x%02x", block_num);
|
||||
ESP_LOGV(TAG, "Read response: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
data.insert(data.begin(), rx.get_message().begin() + 4, rx.get_message().end() - 1);
|
||||
|
||||
ESP_LOGVV(TAG, " Block %u: %s", block_num, nfc::format_bytes(data).c_str());
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150::auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, const uint8_t *key) {
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {MFC_AUTHENTICATE_OID, this->sect_to_auth_(block_num), key_num});
|
||||
|
||||
switch (key_num) {
|
||||
case nfc::MIFARE_CMD_AUTH_A:
|
||||
tx.get_message().back() = MFC_AUTHENTICATE_PARAM_KS_A;
|
||||
break;
|
||||
|
||||
case nfc::MIFARE_CMD_AUTH_B:
|
||||
tx.get_message().back() = MFC_AUTHENTICATE_PARAM_KS_B;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (key != nullptr) {
|
||||
tx.get_message().back() |= MFC_AUTHENTICATE_PARAM_EMBED_KEY;
|
||||
tx.get_message().insert(tx.get_message().end(), key, key + 6);
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "MFC_AUTHENTICATE_REQ: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Sending MFC_AUTHENTICATE_REQ failed");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(MFC_AUTHENTICATE_OID)) ||
|
||||
(rx.get_message()[4] != nfc::STATUS_OK)) {
|
||||
ESP_LOGE(TAG, "MFC authentication failed - block 0x%02x", block_num);
|
||||
ESP_LOGVV(TAG, "MFC_AUTHENTICATE_RSP: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "MFC block %u authentication succeeded", block_num);
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150::sect_to_auth_(const uint8_t block_num) {
|
||||
const uint8_t first_high_block = nfc::MIFARE_CLASSIC_BLOCKS_PER_SECT_LOW * nfc::MIFARE_CLASSIC_16BLOCK_SECT_START;
|
||||
if (block_num >= first_high_block) {
|
||||
return ((block_num - first_high_block) / nfc::MIFARE_CLASSIC_BLOCKS_PER_SECT_HIGH) +
|
||||
nfc::MIFARE_CLASSIC_16BLOCK_SECT_START;
|
||||
}
|
||||
return block_num / nfc::MIFARE_CLASSIC_BLOCKS_PER_SECT_LOW;
|
||||
}
|
||||
|
||||
uint8_t PN7150::format_mifare_classic_mifare_() {
|
||||
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});
|
||||
|
||||
auto status = nfc::STATUS_OK;
|
||||
|
||||
for (int block = 0; block < 64; block += 4) {
|
||||
if (this->auth_mifare_classic_block_(block + 3, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) {
|
||||
continue;
|
||||
}
|
||||
if (block != 0) {
|
||||
if (this->write_mifare_classic_block_(block, blank_buffer) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write block %u", block);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
if (this->write_mifare_classic_block_(block + 1, blank_buffer) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write block %u", block + 1);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
if (this->write_mifare_classic_block_(block + 2, blank_buffer) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write block %u", block + 2);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
if (this->write_mifare_classic_block_(block + 3, trailer_buffer) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write block %u", block + 3);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t PN7150::format_mifare_classic_ndef_() {
|
||||
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_(0, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to authenticate block 0 for formatting");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
if (this->write_mifare_classic_block_(1, block_1_data) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
if (this->write_mifare_classic_block_(2, block_2_data) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
if (this->write_mifare_classic_block_(3, block_3_trailer) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Sector 0 formatted with NDEF");
|
||||
|
||||
auto status = nfc::STATUS_OK;
|
||||
|
||||
for (int block = 4; block < 64; block += 4) {
|
||||
if (this->auth_mifare_classic_block_(block + 3, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
if (block == 4) {
|
||||
if (this->write_mifare_classic_block_(block, empty_ndef_message) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write block %u", block);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
} else {
|
||||
if (this->write_mifare_classic_block_(block, blank_block) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write block %u", block);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
if (this->write_mifare_classic_block_(block + 1, blank_block) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write block %u", block + 1);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
if (this->write_mifare_classic_block_(block + 2, blank_block) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write block %u", block + 2);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
if (this->write_mifare_classic_block_(block + 3, ndef_trailer) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Unable to write trailer block %u", block + 3);
|
||||
status = nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t PN7150::write_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &write_data) {
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_WRITE, block_num});
|
||||
|
||||
ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 1: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Sending XCHG_DATA_REQ failed");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
// write command part two
|
||||
tx.set_payload({XCHG_DATA_OID});
|
||||
tx.get_message().insert(tx.get_message().end(), write_data.begin(), write_data.end());
|
||||
|
||||
ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 2: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "MFC XCHG_DATA timed out waiting for XCHG_DATA_RSP during block write");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(XCHG_DATA_OID)) ||
|
||||
(rx.get_message()[4] != nfc::MIFARE_CMD_ACK)) {
|
||||
ESP_LOGE(TAG, "MFC write block failed - block 0x%02x", block_num);
|
||||
ESP_LOGV(TAG, "Write response: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150::write_mifare_classic_tag_(const std::shared_ptr<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_(current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
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) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
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 nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150::halt_mifare_classic_tag_() {
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_HALT, 0});
|
||||
|
||||
ESP_LOGVV(TAG, "Halt XCHG_DATA_REQ: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Sending halt XCHG_DATA_REQ failed");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
} // namespace pn7150
|
||||
} // namespace esphome
|
||||
186
esphome/components/pn7150/pn7150_mifare_ultralight.cpp
Normal file
186
esphome/components/pn7150/pn7150_mifare_ultralight.cpp
Normal file
@ -0,0 +1,186 @@
|
||||
#include <cinttypes>
|
||||
#include <memory>
|
||||
|
||||
#include "pn7150.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pn7150 {
|
||||
|
||||
static const char *const TAG = "pn7150.mifare_ultralight";
|
||||
|
||||
uint8_t PN7150::read_mifare_ultralight_tag_(nfc::NfcTag &tag) {
|
||||
std::vector<uint8_t> data;
|
||||
// pages 3 to 6 contain various info we are interested in -- do one read to grab it all
|
||||
if (this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE * nfc::MIFARE_ULTRALIGHT_READ_SIZE,
|
||||
data) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
if (!this->is_mifare_ultralight_formatted_(data)) {
|
||||
ESP_LOGW(TAG, "Not NDEF formatted");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
uint8_t message_length;
|
||||
uint8_t message_start_index;
|
||||
if (this->find_mifare_ultralight_ndef_(data, message_length, message_start_index) != nfc::STATUS_OK) {
|
||||
ESP_LOGW(TAG, "Couldn't find NDEF message");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
ESP_LOGVV(TAG, "NDEF message length: %u, start: %u", message_length, message_start_index);
|
||||
|
||||
if (message_length == 0) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
// we already read pages 3-6 earlier -- pick up where we left off so we're not re-reading pages
|
||||
const uint8_t read_length = message_length + message_start_index > 12 ? message_length + message_start_index - 12 : 0;
|
||||
if (read_length) {
|
||||
if (read_mifare_ultralight_bytes_(nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE + 3, read_length, data) !=
|
||||
nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Error reading tag data");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
// we need to trim off page 3 as well as any bytes ahead of message_start_index
|
||||
data.erase(data.begin(), data.begin() + message_start_index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE);
|
||||
|
||||
tag.set_ndef_message(make_unique<nfc::NdefMessage>(data));
|
||||
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data) {
|
||||
const uint8_t read_increment = nfc::MIFARE_ULTRALIGHT_READ_SIZE * nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {nfc::MIFARE_CMD_READ, start_page});
|
||||
|
||||
for (size_t i = 0; i * read_increment < num_bytes; i++) {
|
||||
tx.get_message().back() = i * nfc::MIFARE_ULTRALIGHT_READ_SIZE + start_page;
|
||||
do { // loop because sometimes we struggle here...???...
|
||||
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Error reading tag data");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
} while (rx.get_payload_size() < read_increment);
|
||||
uint16_t bytes_offset = (i + 1) * read_increment;
|
||||
auto pages_in_end_itr = bytes_offset <= num_bytes ? rx.get_message().end() - 1
|
||||
: rx.get_message().end() - (bytes_offset - num_bytes + 1);
|
||||
|
||||
if ((pages_in_end_itr > rx.get_message().begin()) && (pages_in_end_itr < rx.get_message().end())) {
|
||||
data.insert(data.end(), rx.get_message().begin() + nfc::NCI_PKT_HEADER_SIZE, pages_in_end_itr);
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes(data).c_str());
|
||||
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
bool PN7150::is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6) {
|
||||
const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector
|
||||
|
||||
return (page_3_to_6.size() > p4_offset + 3) &&
|
||||
!((page_3_to_6[p4_offset + 0] == 0xFF) && (page_3_to_6[p4_offset + 1] == 0xFF) &&
|
||||
(page_3_to_6[p4_offset + 2] == 0xFF) && (page_3_to_6[p4_offset + 3] == 0xFF));
|
||||
}
|
||||
|
||||
uint16_t PN7150::read_mifare_ultralight_capacity_() {
|
||||
std::vector<uint8_t> data;
|
||||
if (this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE, data) == nfc::STATUS_OK) {
|
||||
ESP_LOGV(TAG, "Tag capacity is %u bytes", data[2] * 8U);
|
||||
return data[2] * 8U;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t PN7150::find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
|
||||
uint8_t &message_start_index) {
|
||||
const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector
|
||||
|
||||
if (!(page_3_to_6.size() > p4_offset + 5)) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
if (page_3_to_6[p4_offset + 0] == 0x03) {
|
||||
message_length = page_3_to_6[p4_offset + 1];
|
||||
message_start_index = 2;
|
||||
return nfc::STATUS_OK;
|
||||
} else if (page_3_to_6[p4_offset + 5] == 0x03) {
|
||||
message_length = page_3_to_6[p4_offset + 6];
|
||||
message_start_index = 7;
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
uint8_t PN7150::write_mifare_ultralight_tag_(std::vector<uint8_t> &uid,
|
||||
const std::shared_ptr<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 %" PRIu32 " > %" PRIu32, buffer_length, capacity);
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
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) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
index += nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
|
||||
current_page++;
|
||||
}
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150::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) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150::write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data) {
|
||||
std::vector<uint8_t> payload = {nfc::MIFARE_CMD_WRITE_ULTRALIGHT, page_num};
|
||||
payload.insert(payload.end(), write_data.begin(), write_data.end());
|
||||
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, payload);
|
||||
|
||||
if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Error writing page %u", page_num);
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
} // namespace pn7150
|
||||
} // namespace esphome
|
||||
25
esphome/components/pn7150_i2c/__init__.py
Normal file
25
esphome/components/pn7150_i2c/__init__.py
Normal file
@ -0,0 +1,25 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, pn7150
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
AUTO_LOAD = ["pn7150"]
|
||||
CODEOWNERS = ["@kbx81", "@jesserockz"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
pn7150_i2c_ns = cg.esphome_ns.namespace("pn7150_i2c")
|
||||
PN7150I2C = pn7150_i2c_ns.class_("PN7150I2C", pn7150.PN7150, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
pn7150.PN7150_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(PN7150I2C),
|
||||
}
|
||||
).extend(i2c.i2c_device_schema(0x28))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await pn7150.setup_pn7150(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
49
esphome/components/pn7150_i2c/pn7150_i2c.cpp
Normal file
49
esphome/components/pn7150_i2c/pn7150_i2c.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
#include "pn7150_i2c.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pn7150_i2c {
|
||||
|
||||
static const char *const TAG = "pn7150_i2c";
|
||||
|
||||
uint8_t PN7150I2C::read_nfcc(nfc::NciMessage &rx, const uint16_t timeout) {
|
||||
if (this->wait_for_irq_(timeout) != nfc::STATUS_OK) {
|
||||
ESP_LOGW(TAG, "read_nfcc_() timeout waiting for IRQ");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
rx.get_message().resize(nfc::NCI_PKT_HEADER_SIZE);
|
||||
if (!this->read_bytes_raw(rx.get_message().data(), nfc::NCI_PKT_HEADER_SIZE)) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
uint8_t length = rx.get_payload_size();
|
||||
if (length > 0) {
|
||||
rx.get_message().resize(length + nfc::NCI_PKT_HEADER_SIZE);
|
||||
if (!this->read_bytes_raw(rx.get_message().data() + nfc::NCI_PKT_HEADER_SIZE, length)) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
// semaphore to ensure transaction is complete before returning
|
||||
if (this->wait_for_irq_(pn7150::NFCC_DEFAULT_TIMEOUT, false) != nfc::STATUS_OK) {
|
||||
ESP_LOGW(TAG, "read_nfcc_() post-read timeout waiting for IRQ line to clear");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150I2C::write_nfcc(nfc::NciMessage &tx) {
|
||||
if (this->write(tx.encode().data(), tx.encode().size()) == i2c::ERROR_OK) {
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
void PN7150I2C::dump_config() {
|
||||
PN7150::dump_config();
|
||||
LOG_I2C_DEVICE(this);
|
||||
}
|
||||
|
||||
} // namespace pn7150_i2c
|
||||
} // namespace esphome
|
||||
22
esphome/components/pn7150_i2c/pn7150_i2c.h
Normal file
22
esphome/components/pn7150_i2c/pn7150_i2c.h
Normal file
@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/pn7150/pn7150.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace pn7150_i2c {
|
||||
|
||||
class PN7150I2C : public pn7150::PN7150, public i2c::I2CDevice {
|
||||
public:
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
uint8_t read_nfcc(nfc::NciMessage &rx, uint16_t timeout) override;
|
||||
uint8_t write_nfcc(nfc::NciMessage &tx) override;
|
||||
};
|
||||
|
||||
} // namespace pn7150_i2c
|
||||
} // namespace esphome
|
||||
@ -3388,6 +3388,15 @@ pn532_spi:
|
||||
pn532_i2c:
|
||||
i2c_id: i2c_bus
|
||||
|
||||
pn7150_i2c:
|
||||
i2c_id: i2c_bus
|
||||
irq_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO32
|
||||
ven_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO16
|
||||
|
||||
pn7160_i2c:
|
||||
id: nfcc_pn7160_i2c
|
||||
i2c_id: i2c_bus
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user