From 93ec8d9d95e9d8c894855a5f70ef0de08d953f47 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Sat, 26 Sep 2026 14:24:45 -0500 Subject: [PATCH] [pn71xx] Fix NCI transport and card emulation bugs; share code between PN7150 and PN7160 (#19589) Co-authored-by: Claude Opus 5.5 (1M context) --- CODEOWNERS | 1 + esphome/components/nfc/nci_message.cpp | 2 + esphome/components/nfc/nfc.cpp | 3 +- esphome/components/pn7150/__init__.py | 236 +--- esphome/components/pn7150/automation.h | 66 - esphome/components/pn7150/pn7150.cpp | 1134 +--------------- esphome/components/pn7150/pn7150.h | 271 +--- .../pn7150/pn7150_mifare_classic.cpp | 326 ----- esphome/components/pn7150_i2c/pn7150_i2c.cpp | 8 +- esphome/components/pn7160/__init__.py | 240 +--- esphome/components/pn7160/automation.h | 66 - esphome/components/pn7160/pn7160.cpp | 1161 +--------------- esphome/components/pn7160/pn7160.h | 269 +--- .../pn7160/pn7160_mifare_ultralight.cpp | 185 --- esphome/components/pn7160_i2c/pn7160_i2c.cpp | 8 +- esphome/components/pn7160_spi/pn7160_spi.cpp | 23 +- esphome/components/pn71xx/__init__.py | 150 +++ esphome/components/pn71xx/pn71xx.cpp | 1184 +++++++++++++++++ esphome/components/pn71xx/pn71xx.h | 306 +++++ .../pn71xx_mifare_classic.cpp} | 26 +- .../pn71xx_mifare_ultralight.cpp} | 32 +- tests/components/pn71xx/pn71xx_test.cpp | 194 +++ 22 files changed, 1963 insertions(+), 3928 deletions(-) delete mode 100644 esphome/components/pn7150/automation.h delete mode 100644 esphome/components/pn7150/pn7150_mifare_classic.cpp delete mode 100644 esphome/components/pn7160/automation.h delete mode 100644 esphome/components/pn7160/pn7160_mifare_ultralight.cpp create mode 100644 esphome/components/pn71xx/__init__.py create mode 100644 esphome/components/pn71xx/pn71xx.cpp create mode 100644 esphome/components/pn71xx/pn71xx.h rename esphome/components/{pn7160/pn7160_mifare_classic.cpp => pn71xx/pn71xx_mifare_classic.cpp} (94%) rename esphome/components/{pn7150/pn7150_mifare_ultralight.cpp => pn71xx/pn71xx_mifare_ultralight.cpp} (86%) create mode 100644 tests/components/pn71xx/pn71xx_test.cpp diff --git a/CODEOWNERS b/CODEOWNERS index bc601f0533..eaf8c79576 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -422,6 +422,7 @@ esphome/components/pn7150_i2c/* @jesserockz @kbx81 esphome/components/pn7160/* @jesserockz @kbx81 esphome/components/pn7160_i2c/* @jesserockz @kbx81 esphome/components/pn7160_spi/* @jesserockz @kbx81 +esphome/components/pn71xx/* @jesserockz @kbx81 esphome/components/power_supply/* @esphome/core esphome/components/preferences/* @esphome/core esphome/components/provisioning/* @esphome/core diff --git a/esphome/components/nfc/nci_message.cpp b/esphome/components/nfc/nci_message.cpp index 0b60fd0ade..a144b8a86d 100644 --- a/esphome/components/nfc/nci_message.cpp +++ b/esphome/components/nfc/nci_message.cpp @@ -116,6 +116,8 @@ void NciMessage::set_header(const uint8_t message_type, const uint8_t gid, const void NciMessage::set_message(const uint8_t message_type, const std::vector &payload) { this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE); + this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] = message_type & nfc::NCI_PKT_MT_MASK; + this->nci_message_[nfc::NCI_PKT_OID_OFFSET] = 0; this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = payload.size(); this->nci_message_.insert(this->nci_message_.end(), payload.begin(), payload.end()); } diff --git a/esphome/components/nfc/nfc.cpp b/esphome/components/nfc/nfc.cpp index 76a391f1de..64f3228b3c 100644 --- a/esphome/components/nfc/nfc.cpp +++ b/esphome/components/nfc/nfc.cpp @@ -61,7 +61,8 @@ bool decode_mifare_classic_tlv(std::vector &data, uint32_t &message_len } uint32_t get_mifare_ultralight_buffer_size(uint32_t message_length) { - uint32_t buffer_size = message_length + 2 + 1; + // TLV header (2 bytes, or 4 for messages of 255 bytes or more) plus the terminator TLV + uint32_t buffer_size = message_length + (message_length < 255 ? 2 : 4) + 1; if (buffer_size % MIFARE_ULTRALIGHT_READ_SIZE != 0) buffer_size = ((buffer_size / MIFARE_ULTRALIGHT_READ_SIZE) + 1) * MIFARE_ULTRALIGHT_READ_SIZE; return buffer_size; diff --git a/esphome/components/pn7150/__init__.py b/esphome/components/pn7150/__init__.py index da5f482464..14299ee310 100644 --- a/esphome/components/pn7150/__init__.py +++ b/esphome/components/pn7150/__init__.py @@ -1,245 +1,23 @@ -from esphome import automation, pins -from esphome.automation import maybe_simple_id import esphome.codegen as cg -from esphome.components import nfc +from esphome.components import pn71xx import esphome.config_validation as cv -from esphome.const import ( - CONF_ID, - CONF_IRQ_PIN, - CONF_MESSAGE, - CONF_ON_FINISHED_WRITE, - CONF_ON_TAG, - CONF_ON_TAG_REMOVED, - CONF_TRIGGER_ID, -) -from esphome.core import ID -from esphome.cpp_generator import MockObj, TemplateArgsType +from esphome.cpp_generator import MockObj from esphome.types import ConfigType -AUTO_LOAD = ["binary_sensor", "nfc"] +AUTO_LOAD = ["pn71xx"] 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_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", nfc.Nfcc, cg.Component) +PN7150 = pn7150_ns.class_("PN7150", pn71xx.PN71xx) -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) - - -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( +PN7150_SCHEMA = pn71xx.PN71XX_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(PN7150), - cv.Optional(CONF_ON_EMULATED_TAG_SCAN): automation.validate_automation({}), - cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation({}), - 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, - synchronous=True, -) -@automation.register_action( - "tag.set_write_message", - SetWriteMessageAction, - SET_MESSAGE_ACTION_SCHEMA, - synchronous=True, -) -async def pn7150_set_message_to_code( - config: ConfigType, - action_id: ID, - template_arg: cg.TemplateArguments, - args: TemplateArgsType, -) -> MockObj: - 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_parented_action( - "tag.emulation_off", - EmulationOffAction, - SIMPLE_ACTION_SCHEMA, - synchronous=True, ) - -automation.register_parented_action( - "tag.emulation_on", - EmulationOnAction, - SIMPLE_ACTION_SCHEMA, - synchronous=True, -) - - -automation.register_parented_action( - "tag.polling_off", - PollingOffAction, - SIMPLE_ACTION_SCHEMA, - synchronous=True, -) - - -automation.register_parented_action( - "tag.polling_on", - PollingOnAction, - SIMPLE_ACTION_SCHEMA, - synchronous=True, -) - - -automation.register_parented_action( - "tag.set_clean_mode", - SetCleanModeAction, - SIMPLE_ACTION_SCHEMA, - synchronous=True, -) - - -automation.register_parented_action( - "tag.set_format_mode", - SetFormatModeAction, - SIMPLE_ACTION_SCHEMA, - synchronous=True, -) - - -automation.register_parented_action( - "tag.set_read_mode", - SetReadModeAction, - SIMPLE_ACTION_SCHEMA, - synchronous=True, -) - - -automation.register_parented_action( - "tag.set_write_mode", - SetWriteModeAction, - SIMPLE_ACTION_SCHEMA, - synchronous=True, -) - - -_CALLBACK_AUTOMATIONS = ( - automation.CallbackAutomation( - CONF_ON_EMULATED_TAG_SCAN, "add_on_emulated_tag_scan_callback" - ), - automation.CallbackAutomation( - CONF_ON_FINISHED_WRITE, "add_on_finished_write_callback" - ), -) +pn71xx.register_is_writing_condition("pn7150.is_writing", PN7150) async def setup_pn7150(var: MockObj, config: ConfigType) -> None: - 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 - ) - - await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS) - - -automation.register_parented_condition( - "pn7150.is_writing", - PN7150IsWritingCondition, - cv.Schema( - { - cv.GenerateID(): cv.use_id(PN7150), - } - ), -) + await pn71xx.setup_pn71xx(var, config) diff --git a/esphome/components/pn7150/automation.h b/esphome/components/pn7150/automation.h deleted file mode 100644 index c3f8d3e5d3..0000000000 --- a/esphome/components/pn7150/automation.h +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include "esphome/core/automation.h" -#include "esphome/core/component.h" -#include "esphome/components/pn7150/pn7150.h" - -namespace esphome::pn7150 { - -template class PN7150IsWritingCondition final : public Condition, public Parented { - public: - bool check(const Ts &...x) override { return this->parent_->is_writing(); } -}; - -template class EmulationOffAction final : public Action, public Parented { - void play(const Ts &...x) override { this->parent_->set_tag_emulation_off(); } -}; - -template class EmulationOnAction final : public Action, public Parented { - void play(const Ts &...x) override { this->parent_->set_tag_emulation_on(); } -}; - -template class PollingOffAction final : public Action, public Parented { - void play(const Ts &...x) override { this->parent_->set_polling_off(); } -}; - -template class PollingOnAction final : public Action, public Parented { - void play(const Ts &...x) override { this->parent_->set_polling_on(); } -}; - -template class SetCleanModeAction final : public Action, public Parented { - void play(const Ts &...x) override { this->parent_->clean_mode(); } -}; - -template class SetFormatModeAction final : public Action, public Parented { - void play(const Ts &...x) override { this->parent_->format_mode(); } -}; - -template class SetReadModeAction final : public Action, public Parented { - void play(const Ts &...x) override { this->parent_->read_mode(); } -}; - -template class SetEmulationMessageAction final : public Action, public Parented { - TEMPLATABLE_VALUE(std::string, message) - TEMPLATABLE_VALUE(bool, include_android_app_record) - - void play(const Ts &...x) override { - this->parent_->set_tag_emulation_message(this->message_.optional_value(x...), - this->include_android_app_record_.optional_value(x...)); - } -}; - -template class SetWriteMessageAction final : public Action, public Parented { - TEMPLATABLE_VALUE(std::string, message) - TEMPLATABLE_VALUE(bool, include_android_app_record) - - void play(const Ts &...x) override { - this->parent_->set_tag_write_message(this->message_.optional_value(x...), - this->include_android_app_record_.optional_value(x...)); - } -}; - -template class SetWriteModeAction final : public Action, public Parented { - void play(const Ts &...x) override { this->parent_->write_mode(); } -}; - -} // namespace esphome::pn7150 diff --git a/esphome/components/pn7150/pn7150.cpp b/esphome/components/pn7150/pn7150.cpp index 4e679c664a..25bb7a0c24 100644 --- a/esphome/components/pn7150/pn7150.cpp +++ b/esphome/components/pn7150/pn7150.cpp @@ -1,239 +1,18 @@ -#include "automation.h" #include "pn7150.h" -#include - -#include "esphome/core/hal.h" -#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome::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_); + PN71xx::dump_config(); } -void PN7150::loop() { - this->nci_fsm_transition_(); - this->purge_old_tags_(); -} - -void PN7150::set_tag_emulation_message(std::shared_ptr message) { - this->card_emulation_message_ = std::move(message); - ESP_LOGD(TAG, "Tag emulation message set"); -} - -void PN7150::set_tag_emulation_message(const optional &message, - const optional include_android_app_record) { - if (!message.has_value()) { - return; - } - - auto ndef_message = make_unique(); - - ndef_message->add_uri_record(message.value()); - - if (!include_android_app_record.has_value() || include_android_app_record.value()) { - auto ext_record = make_unique(); - 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 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 message, optional include_android_app_record) { - if (!message.has_value()) { - return; - } - - auto ndef_message = make_unique(); - - ndef_message->add_uri_record(message.value()); - - if (!include_android_app_record.has_value() || include_android_app_record.value()) { - auto ext_record = make_unique(); - 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 &data, - std::vector &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()) { - char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGW(TAG, "Test results: %s", nfc::format_bytes_to(buf, result)); - } - } - 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)) { - char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGE(TAG, "Invalid reset response: %s", nfc::format_bytes_to(buf, rx.get_message())); - return rx.get_simple_status_response(); - } - // verify reset response +uint8_t PN7150::verify_reset(nfc::NciMessage &rx, const bool reset_config) { + // CORE_RESET_RSP payload: status, NCI version (reported as 1.1, see UM10936 4.1), configuration status 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)) { @@ -242,33 +21,23 @@ uint8_t PN7150::reset_core_(const bool reset_config, const bool power) { return nfc::STATUS_FAILED; } - ESP_LOGD(TAG, "Configuration %s, NCI version: %s", + ESP_LOGD(TAG, "Configuration %s, NCI version: 0x%02X", rx.get_message()[nfc::NCI_PKT_PAYLOAD_OFFSET + 2] ? LOG_STR_LITERAL("reset") : LOG_STR_LITERAL("retained"), - rx.get_message()[nfc::NCI_PKT_PAYLOAD_OFFSET + 1] == 0x20 ? LOG_STR_LITERAL("2.0") : LOG_STR_LITERAL("1.0")); + rx.get_message()[nfc::NCI_PKT_PAYLOAD_OFFSET + 1]); 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)) { +uint8_t PN7150::process_init_response(nfc::NciMessage &rx) { + // NCI 1.0 CORE_INIT_RSP: the manufacturer ID and 4 bytes of manufacturer specific information follow the list of + // supported RF interfaces, whose length is at offset 8 (UM10936, 5.2) + const auto &msg = rx.get_message(); + if (msg.size() < 9u || msg.size() < 20u + msg[8]) { char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGE(TAG, "Invalid initialise response: %s", nfc::format_bytes_to(buf, rx.get_message())); + ESP_LOGE(TAG, "Initialise response too short: %s", nfc::format_bytes_to(buf, msg)); 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]]; + const uint8_t n = msg[8]; ESP_LOGD(TAG, "PN7150 chip info:\n" @@ -277,886 +46,9 @@ uint8_t PN7150::init_core_() { " ROM code version: 0x%02X\n" " FLASH major version: 0x%02X\n" " FLASH minor version: 0x%02X", - manf_id, hw_version, rom_code_version, flash_major_version, flash_minor_version); + msg[15 + n], msg[16 + n], msg[17 + n], msg[18 + n], msg[19 + n]); 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(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(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 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(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 discover_config = std::vector((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 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_(nfc::NfcTagUid &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_(nfc::NfcTagUid &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_(nfc::NfcTagUid &uid, std::shared_ptr &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 PN7150::build_tag_(const uint8_t mode_tech, const std::vector &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; - } - nfc::NfcTagUid 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(uid, tag_type_str); - } - } - return nullptr; -} - -optional PN7150::find_tag_uid_(const nfc::NfcTagUid &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 = this->discovered_endpoint_.size(); i > 0; i--) { - if (millis() - this->discovered_endpoint_[i - 1].last_seen > this->tag_ttl_) { - this->erase_tag_(i - 1); - } - } -} - -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); - } - for (auto *listener : this->tag_listeners_) { - listener->tag_off(*this->discovered_endpoint_[tag_index].tag); - } - char uid_buf[nfc::FORMAT_UID_BUFFER_SIZE]; - ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid_to(uid_buf, this->discovered_endpoint_[tag_index].tag->get_uid())); - 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); - } - [[fallthrough]]; - - 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); - } - [[fallthrough]]; - - 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); - } - [[fallthrough]]; - - 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); - } - [[fallthrough]]; - - 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); - } - [[fallthrough]]; - - 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_(); - [[fallthrough]]; - - // All cases below are waiting for NOTIFICATION messages - case NCIState::RFST_DISCOVERY: - if (this->config_refresh_pending_) { - this->refresh_core_config_(); - } - [[fallthrough]]; - - 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 { - char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGV(TAG, "Unimplemented notification: %s", nfc::format_bytes_to(buf, rx.get_message())); - } - break; - - case nfc::NCI_PKT_MT_CTRL_RESPONSE: { - char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGV(TAG, "Unimplemented GID: 0x%02X OID: 0x%02X Full response: %s", rx.get_gid(), rx.get_oid(), - nfc::format_bytes_to(buf, rx.get_message())); - break; - } - - case nfc::NCI_PKT_MT_CTRL_COMMAND: { - char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGV(TAG, "Unimplemented command: %s", nfc::format_bytes_to(buf, rx.get_message())); - break; - } - - case nfc::NCI_PKT_MT_DATA: - this->process_data_message_(rx); - break; - - default: { - char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGV(TAG, "Unimplemented message type: %s", nfc::format_bytes_to(buf, rx.get_message())); - 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(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\n" - " 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) { - char uid_buf[nfc::FORMAT_UID_BUFFER_SIZE]; - ESP_LOGI(TAG, "Read tag type %s with UID %s", working_endpoint.tag->get_tag_type().c_str(), - nfc::format_uid_to(uid_buf, working_endpoint.tag->get_uid())); - 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); - } - for (auto *listener : this->tag_listeners_) { - listener->tag_on(*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(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) { - char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGVV(TAG, "Received data message: %s", nfc::format_bytes_to(buf, rx.get_message())); - - std::vector 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 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_to(buf, tx.get_message())); - 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 &response, std::vector &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]; - - char ndef_buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGVV(TAG, "Encoded NDEF message: %s", nfc::format_bytes_to(ndef_buf, ndef_message)); - - 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 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); - char ndef_buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGD(TAG, "Received %u-byte NDEF message: %s", length, nfc::format_bytes_to(ndef_buf, ndef_msg_written)); - 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; - char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - - 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_to(buf, tx.get_message())); - // 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_to(buf, rx.get_message())); - // 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_to(buf, rx.get_message())); - 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_to(buf, rx.get_message())); - } - 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_to(buf, rx.get_message())); - 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_to(buf, rx.get_message())); - } - - 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 esphome::pn7150 diff --git a/esphome/components/pn7150/pn7150.h b/esphome/components/pn7150/pn7150.h index fa38c5c313..7740954050 100644 --- a/esphome/components/pn7150/pn7150.h +++ b/esphome/components/pn7150/pn7150.h @@ -1,59 +1,9 @@ #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 +#include "esphome/components/pn71xx/pn71xx.h" namespace esphome::pn7150 { -static constexpr uint16_t NFCC_DEFAULT_TIMEOUT = 10; -static constexpr uint16_t NFCC_INIT_TIMEOUT = 50; -static constexpr uint16_t NFCC_TAG_WRITE_TIMEOUT = 15; - -static constexpr uint8_t NFCC_MAX_COMM_FAILS = 3; -static constexpr uint8_t NFCC_MAX_ERROR_COUNT = 10; - -static constexpr uint8_t XCHG_DATA_OID = 0x10; -static constexpr uint8_t MF_SECTORSEL_OID = 0x32; -static constexpr uint8_t MFC_AUTHENTICATE_OID = 0x40; -static constexpr uint8_t TEST_PRBS_OID = 0x30; -static constexpr uint8_t TEST_ANTENNA_OID = 0x3D; -static constexpr uint8_t TEST_GET_REGISTER_OID = 0x33; - -static constexpr uint8_t MFC_AUTHENTICATE_PARAM_KS_A = 0x00; // key select A -static constexpr uint8_t MFC_AUTHENTICATE_PARAM_KS_B = 0x80; // key select B -static constexpr uint8_t MFC_AUTHENTICATE_PARAM_EMBED_KEY = 0x10; - -static constexpr uint8_t CARD_EMU_T4T_APP_SELECT[] = {0x00, 0xA4, 0x04, 0x00, 0x07, 0xD2, 0x76, - 0x00, 0x00, 0x85, 0x01, 0x01, 0x00}; -static constexpr uint8_t CARD_EMU_T4T_CC[] = {0x00, 0x0F, 0x20, 0x00, 0xFF, 0x00, 0xFF, 0x04, - 0x06, 0xE1, 0x04, 0x00, 0xFF, 0x00, 0x00}; -static constexpr uint8_t CARD_EMU_T4T_CC_SELECT[] = {0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x03}; -static constexpr uint8_t CARD_EMU_T4T_NDEF_SELECT[] = {0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x04}; -static constexpr uint8_t CARD_EMU_T4T_READ[] = {0x00, 0xB0}; -static constexpr uint8_t CARD_EMU_T4T_WRITE[] = {0x00, 0xD6}; -static constexpr uint8_t CARD_EMU_T4T_OK[] = {0x90, 0x00}; -static constexpr uint8_t CARD_EMU_T4T_NOK[] = {0x6A, 0x82}; - -static constexpr 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 constexpr 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 constexpr uint8_t PMU_CFG[] = { 0x01, // Number of parameters 0xA0, 0x0E, // ext. tag @@ -63,34 +13,6 @@ static constexpr uint8_t PMU_CFG[] = { 0x01, // RFU; must be 0x00 for CFG1 and 0x01 for CFG2 }; -static constexpr 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 constexpr 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 constexpr 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 constexpr 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 constexpr uint8_t RF_LISTEN_MODE_ROUTING_CONFIG[] = {0x00, // "more" (another message is coming) 1, // number of table entries 0x01, // type = protocol-based @@ -99,196 +21,15 @@ static constexpr uint8_t RF_LISTEN_MODE_ROUTING_CONFIG[] = {0x00, // "more" (an 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 tag; - bool trig_called; -}; - -class PN7150 : public nfc::Nfcc, public Component { +class PN7150 : public pn71xx::PN71xx { public: - void setup() override; void dump_config() override; - 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 message); - void set_tag_emulation_message(const optional &message, optional 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); } - - template void add_on_emulated_tag_scan_callback(F &&callback) { - this->on_emulated_tag_scan_callback_.add(std::forward(callback)); - } - - template void add_on_finished_write_callback(F &&callback) { - this->on_finished_write_callback_.add(std::forward(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 message); - void set_tag_write_message(optional message, optional include_android_app_record); - - uint8_t set_test_mode(TestMode test_mode, const std::vector &data, std::vector &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_(nfc::NfcTagUid &uid); - uint8_t format_endpoint_(nfc::NfcTagUid &uid); - uint8_t write_endpoint_(nfc::NfcTagUid &uid, std::shared_ptr &message); - - std::unique_ptr build_tag_(uint8_t mode_tech, const std::vector &data); - optional find_tag_uid_(const nfc::NfcTagUid &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 &response, std::vector &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 &data); - uint8_t write_mifare_classic_block_(uint8_t block_num, const uint8_t *data, size_t len); - 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 &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 &data); - bool is_mifare_ultralight_formatted_(const std::vector &page_3_to_6); - uint16_t read_mifare_ultralight_capacity_(); - uint8_t find_mifare_ultralight_ndef_(const std::vector &page_3_to_6, uint8_t &message_length, - uint8_t &message_start_index); - uint8_t write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len); - uint8_t write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr &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 on_emulated_tag_scan_callback_; - CallbackManager on_finished_write_callback_; - - std::vector discovered_endpoint_; - - CardEmulationState ce_state_{CardEmulationState::CARD_EMU_IDLE}; - NCIState nci_state_{NCIState::NFCC_RESET}; - NCIState nci_state_error_{NCIState::NONE}; - - std::shared_ptr card_emulation_message_; - std::shared_ptr next_task_message_to_write_; - - std::vector triggers_ontag_; - std::vector triggers_ontagremoved_; + uint8_t verify_reset(nfc::NciMessage &rx, bool reset_config) override; + uint8_t process_init_response(nfc::NciMessage &rx) override; + std::span pmu_config() const override { return PMU_CFG; } + std::span listen_mode_routing_config() const override { return RF_LISTEN_MODE_ROUTING_CONFIG; } }; } // namespace esphome::pn7150 diff --git a/esphome/components/pn7150/pn7150_mifare_classic.cpp b/esphome/components/pn7150/pn7150_mifare_classic.cpp deleted file mode 100644 index f1832d95f1..0000000000 --- a/esphome/components/pn7150/pn7150_mifare_classic.cpp +++ /dev/null @@ -1,326 +0,0 @@ -#include -#include - -#include "pn7150.h" -#include "esphome/core/log.h" - -namespace esphome::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 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 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 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(buffer)); - - return nfc::STATUS_OK; -} - -uint8_t PN7150::read_mifare_classic_block_(uint8_t block_num, std::vector &data) { - nfc::NciMessage rx; - nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_READ, block_num}); - - char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGVV(TAG, "Read XCHG_DATA_REQ: %s", nfc::format_bytes_to(buf, tx.get_message())); - 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_to(buf, rx.get_message())); - 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_to(buf, data)); - 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); - } - - char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGVV(TAG, "MFC_AUTHENTICATE_REQ: %s", nfc::format_bytes_to(buf, tx.get_message())); - 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_to(buf, rx.get_message())); - 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_() { - static constexpr std::array BLANK_BUFFER = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - static constexpr std::array 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.data(), BLANK_BUFFER.size()) != 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.data(), BLANK_BUFFER.size()) != 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.data(), BLANK_BUFFER.size()) != 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.data(), TRAILER_BUFFER.size()) != 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_() { - static constexpr std::array EMPTY_NDEF_MESSAGE = { - 0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - static constexpr std::array BLANK_BLOCK = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - static constexpr std::array BLOCK_1_DATA = { - 0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}; - static constexpr std::array BLOCK_2_DATA = { - 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}; - static constexpr std::array BLOCK_3_TRAILER = { - 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - static constexpr std::array 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.data(), BLOCK_1_DATA.size()) != nfc::STATUS_OK) { - return nfc::STATUS_FAILED; - } - if (this->write_mifare_classic_block_(2, BLOCK_2_DATA.data(), BLOCK_2_DATA.size()) != nfc::STATUS_OK) { - return nfc::STATUS_FAILED; - } - if (this->write_mifare_classic_block_(3, BLOCK_3_TRAILER.data(), BLOCK_3_TRAILER.size()) != 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.data(), EMPTY_NDEF_MESSAGE.size()) != - 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.data(), BLANK_BLOCK.size()) != 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.data(), BLANK_BLOCK.size()) != 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.data(), BLANK_BLOCK.size()) != 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.data(), NDEF_TRAILER.size()) != 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, const uint8_t *data, size_t len) { - nfc::NciMessage rx; - nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_WRITE, block_num}); - - char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 1: %s", nfc::format_bytes_to(buf, tx.get_message())); - 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(), data, data + len); - - ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 2: %s", nfc::format_bytes_to(buf, tx.get_message())); - 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_to(buf, rx.get_message())); - return nfc::STATUS_FAILED; - } - - return nfc::STATUS_OK; -} - -uint8_t PN7150::write_mifare_classic_tag_(const std::shared_ptr &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; - } - } - - if (this->write_mifare_classic_block_(current_block, encoded.data() + index, nfc::MIFARE_CLASSIC_BLOCK_SIZE) != - 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}); - - char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGVV(TAG, "Halt XCHG_DATA_REQ: %s", nfc::format_bytes_to(buf, tx.get_message())); - 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 esphome::pn7150 diff --git a/esphome/components/pn7150_i2c/pn7150_i2c.cpp b/esphome/components/pn7150_i2c/pn7150_i2c.cpp index a61bd27c64..a4e45b8584 100644 --- a/esphome/components/pn7150_i2c/pn7150_i2c.cpp +++ b/esphome/components/pn7150_i2c/pn7150_i2c.cpp @@ -24,10 +24,10 @@ uint8_t PN7150I2C::read_nfcc(nfc::NciMessage &rx, const uint16_t timeout) { 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; + // IRQ normally drops at the end of the read. If another message is queued it rises again at once, and the short + // low pulse may be missed; that means more data is waiting, not that this read failed (UM10936, 3.4). + if (this->wait_for_irq_(pn71xx::NFCC_IRQ_CLEAR_TIMEOUT, false) != nfc::STATUS_OK) { + ESP_LOGVV(TAG, "IRQ still active after read; another message is pending"); } return nfc::STATUS_OK; } diff --git a/esphome/components/pn7160/__init__.py b/esphome/components/pn7160/__init__.py index 5ce0dd1286..4cde36e9e7 100644 --- a/esphome/components/pn7160/__init__.py +++ b/esphome/components/pn7160/__init__.py @@ -1,257 +1,37 @@ -from esphome import automation, pins -from esphome.automation import maybe_simple_id +from esphome import pins import esphome.codegen as cg -from esphome.components import nfc +from esphome.components import pn71xx import esphome.config_validation as cv -from esphome.const import ( - CONF_ID, - CONF_IRQ_PIN, - CONF_MESSAGE, - CONF_ON_FINISHED_WRITE, - CONF_ON_TAG, - CONF_ON_TAG_REMOVED, - CONF_TRIGGER_ID, -) -from esphome.core import ID -from esphome.cpp_generator import MockObj, TemplateArgsType +from esphome.cpp_generator import MockObj from esphome.types import ConfigType -AUTO_LOAD = ["binary_sensor", "nfc"] +AUTO_LOAD = ["pn71xx"] CODEOWNERS = ["@kbx81", "@jesserockz"] CONF_DWL_REQ_PIN = "dwl_req_pin" -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_ON_EMULATED_TAG_SCAN = "on_emulated_tag_scan" -CONF_PN7160_ID = "pn7160_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" CONF_WKUP_REQ_PIN = "wkup_req_pin" pn7160_ns = cg.esphome_ns.namespace("pn7160") -PN7160 = pn7160_ns.class_("PN7160", nfc.Nfcc, cg.Component) +PN7160 = pn7160_ns.class_("PN7160", pn71xx.PN71xx) -EmulationOffAction = pn7160_ns.class_("EmulationOffAction", automation.Action) -EmulationOnAction = pn7160_ns.class_("EmulationOnAction", automation.Action) -PollingOffAction = pn7160_ns.class_("PollingOffAction", automation.Action) -PollingOnAction = pn7160_ns.class_("PollingOnAction", automation.Action) -SetCleanModeAction = pn7160_ns.class_("SetCleanModeAction", automation.Action) -SetEmulationMessageAction = pn7160_ns.class_( - "SetEmulationMessageAction", automation.Action -) -SetFormatModeAction = pn7160_ns.class_("SetFormatModeAction", automation.Action) -SetReadModeAction = pn7160_ns.class_("SetReadModeAction", automation.Action) -SetWriteMessageAction = pn7160_ns.class_("SetWriteMessageAction", automation.Action) -SetWriteModeAction = pn7160_ns.class_("SetWriteModeAction", automation.Action) - - -PN7160IsWritingCondition = pn7160_ns.class_( - "PN7160IsWritingCondition", automation.Condition -) - - -IsWritingCondition = nfc.nfc_ns.class_("IsWritingCondition", automation.Condition) - - -SIMPLE_ACTION_SCHEMA = maybe_simple_id( - { - cv.Required(CONF_ID): cv.use_id(PN7160), - } -) - -SET_MESSAGE_ACTION_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.use_id(PN7160), - cv.Required(CONF_MESSAGE): cv.templatable(cv.string), - cv.Optional(CONF_INCLUDE_ANDROID_APP_RECORD, default=True): cv.boolean, - } -) - -PN7160_SCHEMA = cv.Schema( +PN7160_SCHEMA = pn71xx.PN71XX_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(PN7160), - cv.Optional(CONF_ON_EMULATED_TAG_SCAN): automation.validate_automation({}), - cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation({}), - 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.Optional(CONF_DWL_REQ_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_IRQ_PIN): pins.gpio_input_pin_schema, - cv.Required(CONF_VEN_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_WKUP_REQ_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, - synchronous=True, -) -@automation.register_action( - "tag.set_write_message", - SetWriteMessageAction, - SET_MESSAGE_ACTION_SCHEMA, - synchronous=True, -) -async def pn7160_set_message_to_code( - config: ConfigType, - action_id: ID, - template_arg: cg.TemplateArguments, - args: TemplateArgsType, -) -> MockObj: - 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_parented_action( - "tag.emulation_off", - EmulationOffAction, - SIMPLE_ACTION_SCHEMA, - synchronous=True, ) - -automation.register_parented_action( - "tag.emulation_on", - EmulationOnAction, - SIMPLE_ACTION_SCHEMA, - synchronous=True, -) - - -automation.register_parented_action( - "tag.polling_off", - PollingOffAction, - SIMPLE_ACTION_SCHEMA, - synchronous=True, -) - - -automation.register_parented_action( - "tag.polling_on", - PollingOnAction, - SIMPLE_ACTION_SCHEMA, - synchronous=True, -) - - -automation.register_parented_action( - "tag.set_clean_mode", - SetCleanModeAction, - SIMPLE_ACTION_SCHEMA, - synchronous=True, -) - - -automation.register_parented_action( - "tag.set_format_mode", - SetFormatModeAction, - SIMPLE_ACTION_SCHEMA, - synchronous=True, -) - - -automation.register_parented_action( - "tag.set_read_mode", - SetReadModeAction, - SIMPLE_ACTION_SCHEMA, - synchronous=True, -) - - -automation.register_parented_action( - "tag.set_write_mode", - SetWriteModeAction, - SIMPLE_ACTION_SCHEMA, - synchronous=True, -) - - -_CALLBACK_AUTOMATIONS = ( - automation.CallbackAutomation( - CONF_ON_EMULATED_TAG_SCAN, "add_on_emulated_tag_scan_callback" - ), - automation.CallbackAutomation( - CONF_ON_FINISHED_WRITE, "add_on_finished_write_callback" - ), -) +pn71xx.register_is_writing_condition("pn7160.is_writing", PN7160) async def setup_pn7160(var: MockObj, config: ConfigType) -> None: - await cg.register_component(var, config) + await pn71xx.setup_pn71xx(var, config) if dwl_req_pin_config := config.get(CONF_DWL_REQ_PIN): pin = await cg.gpio_pin_expression(dwl_req_pin_config) cg.add(var.set_dwl_req_pin(pin)) - 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 wakeup_req_pin_config := config.get(CONF_WKUP_REQ_PIN): - pin = await cg.gpio_pin_expression(wakeup_req_pin_config) + if wkup_req_pin_config := config.get(CONF_WKUP_REQ_PIN): + pin = await cg.gpio_pin_expression(wkup_req_pin_config) cg.add(var.set_wkup_req_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 - ) - - await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS) - - -automation.register_parented_condition( - "pn7160.is_writing", - PN7160IsWritingCondition, - cv.Schema( - { - cv.GenerateID(): cv.use_id(PN7160), - } - ), -) diff --git a/esphome/components/pn7160/automation.h b/esphome/components/pn7160/automation.h deleted file mode 100644 index 9f03a5a3d6..0000000000 --- a/esphome/components/pn7160/automation.h +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include "esphome/core/automation.h" -#include "esphome/core/component.h" -#include "esphome/components/pn7160/pn7160.h" - -namespace esphome::pn7160 { - -template class PN7160IsWritingCondition final : public Condition, public Parented { - public: - bool check(const Ts &...x) override { return this->parent_->is_writing(); } -}; - -template class EmulationOffAction final : public Action, public Parented { - void play(const Ts &...x) override { this->parent_->set_tag_emulation_off(); } -}; - -template class EmulationOnAction final : public Action, public Parented { - void play(const Ts &...x) override { this->parent_->set_tag_emulation_on(); } -}; - -template class PollingOffAction final : public Action, public Parented { - void play(const Ts &...x) override { this->parent_->set_polling_off(); } -}; - -template class PollingOnAction final : public Action, public Parented { - void play(const Ts &...x) override { this->parent_->set_polling_on(); } -}; - -template class SetCleanModeAction final : public Action, public Parented { - void play(const Ts &...x) override { this->parent_->clean_mode(); } -}; - -template class SetFormatModeAction final : public Action, public Parented { - void play(const Ts &...x) override { this->parent_->format_mode(); } -}; - -template class SetReadModeAction final : public Action, public Parented { - void play(const Ts &...x) override { this->parent_->read_mode(); } -}; - -template class SetEmulationMessageAction final : public Action, public Parented { - TEMPLATABLE_VALUE(std::string, message) - TEMPLATABLE_VALUE(bool, include_android_app_record) - - void play(const Ts &...x) override { - this->parent_->set_tag_emulation_message(this->message_.optional_value(x...), - this->include_android_app_record_.optional_value(x...)); - } -}; - -template class SetWriteMessageAction final : public Action, public Parented { - TEMPLATABLE_VALUE(std::string, message) - TEMPLATABLE_VALUE(bool, include_android_app_record) - - void play(const Ts &...x) override { - this->parent_->set_tag_write_message(this->message_.optional_value(x...), - this->include_android_app_record_.optional_value(x...)); - } -}; - -template class SetWriteModeAction final : public Action, public Parented { - void play(const Ts &...x) override { this->parent_->write_mode(); } -}; - -} // namespace esphome::pn7160 diff --git a/esphome/components/pn7160/pn7160.cpp b/esphome/components/pn7160/pn7160.cpp index f2cbfa6bcf..6dcb73bee1 100644 --- a/esphome/components/pn7160/pn7160.cpp +++ b/esphome/components/pn7160/pn7160.cpp @@ -1,10 +1,6 @@ -#include - -#include "automation.h" #include "pn7160.h" #include "esphome/core/hal.h" -#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome::pn7160 { @@ -12,250 +8,40 @@ namespace esphome::pn7160 { static const char *const TAG = "pn7160"; void PN7160::setup() { - this->irq_pin_->setup(); - this->ven_pin_->setup(); if (this->dwl_req_pin_ != nullptr) { this->dwl_req_pin_->setup(); } if (this->wkup_req_pin_ != nullptr) { this->wkup_req_pin_->setup(); } - - this->nci_fsm_transition_(); // kick off reset & init processes + PN71xx::setup(); } void PN7160::dump_config() { ESP_LOGCONFIG(TAG, "PN7160:"); + PN71xx::dump_config(); if (this->dwl_req_pin_ != nullptr) { LOG_PIN(" DWL_REQ pin: ", this->dwl_req_pin_); } - LOG_PIN(" IRQ pin: ", this->irq_pin_); - LOG_PIN(" VEN pin: ", this->ven_pin_); if (this->wkup_req_pin_ != nullptr) { LOG_PIN(" WKUP_REQ pin: ", this->wkup_req_pin_); } } -void PN7160::loop() { - this->nci_fsm_transition_(); - this->purge_old_tags_(); -} - -void PN7160::set_tag_emulation_message(std::shared_ptr message) { - this->card_emulation_message_ = std::move(message); - ESP_LOGD(TAG, "Tag emulation message set"); -} - -void PN7160::set_tag_emulation_message(const optional &message, - const optional include_android_app_record) { - if (!message.has_value()) { - return; - } - - auto ndef_message = make_unique(); - - ndef_message->add_uri_record(message.value()); - - if (!include_android_app_record.has_value() || include_android_app_record.value()) { - auto ext_record = make_unique(); - 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 PN7160::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 PN7160::set_tag_emulation_off() { - if (this->listening_enabled_) { - this->listening_enabled_ = false; - this->config_refresh_pending_ = true; - } - ESP_LOGD(TAG, "Tag emulation disabled"); -} - -void PN7160::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 PN7160::set_polling_off() { - if (this->polling_enabled_) { - this->polling_enabled_ = false; - this->config_refresh_pending_ = true; - } - ESP_LOGD(TAG, "Tag polling disabled"); -} - -void PN7160::set_polling_on() { - if (!this->polling_enabled_) { - this->polling_enabled_ = true; - this->config_refresh_pending_ = true; - } - ESP_LOGD(TAG, "Tag polling enabled"); -} - -void PN7160::read_mode() { - this->next_task_ = EP_READ; - ESP_LOGD(TAG, "Waiting to read next tag"); -} - -void PN7160::clean_mode() { - this->next_task_ = EP_CLEAN; - ESP_LOGD(TAG, "Waiting to clean next tag"); -} - -void PN7160::format_mode() { - this->next_task_ = EP_FORMAT; - ESP_LOGD(TAG, "Waiting to format next tag"); -} - -void PN7160::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 PN7160::set_tag_write_message(std::shared_ptr message) { - this->next_task_message_to_write_ = std::move(message); - ESP_LOGD(TAG, "Message to write has been set"); -} - -void PN7160::set_tag_write_message(optional message, optional include_android_app_record) { - if (!message.has_value()) { - return; - } - - auto ndef_message = make_unique(); - - ndef_message->add_uri_record(message.value()); - - if (!include_android_app_record.has_value() || include_android_app_record.value()) { - auto ext_record = make_unique(); - 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 PN7160::set_test_mode(const TestMode test_mode, const std::vector &data, - std::vector &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()) { - char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGW(TAG, "Test results: %s", nfc::format_bytes_to(buf, result)); - } - } - return status; -} - -uint8_t PN7160::reset_core_(const bool reset_config, const bool power) { +void PN7160::prepare_reset() { + // DWL_REQ must be low when VEN rises, or the chip starts in firmware download mode (UM11495) if (this->dwl_req_pin_ != nullptr) { this->dwl_req_pin_->digital_write(false); - delay(NFCC_DEFAULT_TIMEOUT); + delay(pn71xx::NFCC_RESET_DELAY); } +} - 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)) { - char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGE(TAG, "Invalid reset response: %s", nfc::format_bytes_to(buf, rx.get_message())); - return rx.get_simple_status_response(); - } - // read reset notification - if (this->read_nfcc(rx, NFCC_INIT_TIMEOUT) != nfc::STATUS_OK) { +uint8_t PN7160::verify_reset(nfc::NciMessage &rx, const bool reset_config) { + // PN7160 always sends CORE_RESET_NTF after CORE_RESET_RSP (UM11495, 8.2) + if (this->read_nfcc(rx, pn71xx::NFCC_INIT_TIMEOUT) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Reset notification was not received"); return nfc::STATUS_FAILED; } - // verify reset notification if ((!rx.message_type_is(nfc::NCI_PKT_MT_CTRL_NOTIFICATION)) || (!rx.message_length_is(9)) || (rx.get_message()[nfc::NCI_PKT_PAYLOAD_OFFSET] != 0x02) || (rx.get_message()[nfc::NCI_PKT_PAYLOAD_OFFSET + 1] != (uint8_t) reset_config)) { @@ -264,925 +50,30 @@ uint8_t PN7160::reset_core_(const bool reset_config, const bool power) { return nfc::STATUS_FAILED; } - ESP_LOGD(TAG, "Configuration %s, NCI version: %s, Manufacturer ID: 0x%02X", - rx.get_message()[4] ? LOG_STR_LITERAL("reset") : LOG_STR_LITERAL("retained"), - rx.get_message()[5] == 0x20 ? LOG_STR_LITERAL("2.0") : LOG_STR_LITERAL("1.0"), rx.get_message()[6]); - rx.get_message().erase(rx.get_message().begin(), rx.get_message().begin() + 8); - char mfr_buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGD(TAG, "Manufacturer info: %s", nfc::format_bytes_to(mfr_buf, rx.get_message())); + // payload: trigger, config status, NCI version, manufacturer ID, info length (4), then the manufacturer specific + // information: hardware, ROM code, FLASH major and FLASH minor versions (UM11495, 8.2 and 8.3) + const auto &ntf = rx.get_message(); + ESP_LOGD(TAG, + "Configuration %s, NCI version: 0x%02X, Manufacturer ID: 0x%02X\n" + " Hardware version: %u\n" + " ROM code version: %u\n" + " FLASH major version: %u\n" + " FLASH minor version: %u", + ntf[4] ? LOG_STR_LITERAL("reset") : LOG_STR_LITERAL("retained"), ntf[5], ntf[6], ntf[8], ntf[9], ntf[10], + ntf[11]); return nfc::STATUS_OK; } -uint8_t PN7160::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; +uint8_t PN7160::process_init_response(nfc::NciMessage &rx) { + // the chip's version information is logged from CORE_RESET_NTF in verify_reset() + if (rx.get_message().size() >= 8) { + std::vector features(rx.get_message().begin() + 4, rx.get_message().begin() + 8); + char feat_buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGD(TAG, "NFCC features: %s", nfc::format_bytes_to(feat_buf, features)); } - if (!rx.simple_status_response_is(nfc::STATUS_OK)) { - char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGE(TAG, "Invalid initialise response: %s", nfc::format_bytes_to(buf, rx.get_message())); - return nfc::STATUS_FAILED; - } - - uint8_t hw_version = rx.get_message()[17 + rx.get_message()[8]]; - uint8_t rom_code_version = rx.get_message()[18 + rx.get_message()[8]]; - uint8_t flash_major_version = rx.get_message()[19 + rx.get_message()[8]]; - uint8_t flash_minor_version = rx.get_message()[20 + rx.get_message()[8]]; - std::vector features(rx.get_message().begin() + 4, rx.get_message().begin() + 8); - - char feat_buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGD(TAG, - "PN7160 chip info:\n" - " Hardware version: %u\n" - " ROM code version: %u\n" - " FLASH major version: %u\n" - " FLASH minor version: %u\n" - " Features: %s", - hw_version, rom_code_version, flash_major_version, flash_minor_version, - nfc::format_bytes_to(feat_buf, features)); - return rx.get_simple_status_response(); } -uint8_t PN7160::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(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 PN7160::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(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 PN7160::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 PN7160::set_discover_map_() { - std::vector 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) != nfc::STATUS_OK) { - ESP_LOGE(TAG, "Error sending discover map poll config"); - return nfc::STATUS_FAILED; - } - return nfc::STATUS_OK; -} - -uint8_t PN7160::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(std::begin(RF_LISTEN_MODE_ROUTING_CONFIG), std::end(RF_LISTEN_MODE_ROUTING_CONFIG))); - - if (this->transceive_(tx, rx) != nfc::STATUS_OK) { - ESP_LOGE(TAG, "Error setting listen mode routing config"); - return nfc::STATUS_FAILED; - } - return nfc::STATUS_OK; -} - -uint8_t PN7160::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 discover_config = std::vector((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 PN7160::stop_discovery_() { return this->deactivate_(nfc::DEACTIVATION_TYPE_IDLE, NFCC_TAG_WRITE_TIMEOUT); } - -uint8_t PN7160::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 PN7160::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 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 PN7160::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 PN7160::clean_endpoint_(nfc::NfcTagUid &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 PN7160::format_endpoint_(nfc::NfcTagUid &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 PN7160::write_endpoint_(nfc::NfcTagUid &uid, std::shared_ptr &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 PN7160::build_tag_(const uint8_t mode_tech, const std::vector &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; - } - nfc::NfcTagUid 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(uid, tag_type_str); - } - } - return nullptr; -} - -optional PN7160::find_tag_uid_(const nfc::NfcTagUid &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 PN7160::purge_old_tags_() { - for (size_t i = this->discovered_endpoint_.size(); i > 0; i--) { - if (millis() - this->discovered_endpoint_[i - 1].last_seen > this->tag_ttl_) { - this->erase_tag_(i - 1); - } - } -} - -void PN7160::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); - } - for (auto *listener : this->tag_listeners_) { - listener->tag_off(*this->discovered_endpoint_[tag_index].tag); - } - char uid_buf[nfc::FORMAT_UID_BUFFER_SIZE]; - ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid_to(uid_buf, this->discovered_endpoint_[tag_index].tag->get_uid())); - this->discovered_endpoint_.erase(this->discovered_endpoint_.begin() + tag_index); - } -} - -void PN7160::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); - } - [[fallthrough]]; - - 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); - } - [[fallthrough]]; - - 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); - } - [[fallthrough]]; - - 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); - } - [[fallthrough]]; - - 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); - } - [[fallthrough]]; - - 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_(); - [[fallthrough]]; - - // All cases below are waiting for NOTIFICATION messages - case NCIState::RFST_DISCOVERY: - if (this->config_refresh_pending_) { - this->refresh_core_config_(); - } - [[fallthrough]]; - - 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::FAILED: - case NCIState::NONE: - default: - return; - } -} - -void PN7160::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 PN7160::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 PN7160::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 { - char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGV(TAG, "Unimplemented notification: %s", nfc::format_bytes_to(buf, rx.get_message())); - } - break; - - case nfc::NCI_PKT_MT_CTRL_RESPONSE: { - char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGV(TAG, "Unimplemented GID: 0x%02X OID: 0x%02X Full response: %s", rx.get_gid(), rx.get_oid(), - nfc::format_bytes_to(buf, rx.get_message())); - break; - } - - case nfc::NCI_PKT_MT_CTRL_COMMAND: { - char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGV(TAG, "Unimplemented command: %s", nfc::format_bytes_to(buf, rx.get_message())); - break; - } - - case nfc::NCI_PKT_MT_DATA: - this->process_data_message_(rx); - break; - - default: { - char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGV(TAG, "Unimplemented message type: %s", nfc::format_bytes_to(buf, rx.get_message())); - break; - } - } -} - -void PN7160::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(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\n" - " 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) { - char uid_buf[nfc::FORMAT_UID_BUFFER_SIZE]; - ESP_LOGI(TAG, "Read tag type %s with UID %s", working_endpoint.tag->get_tag_type().c_str(), - nfc::format_uid_to(uid_buf, working_endpoint.tag->get_uid())); - 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); - } - for (auto *listener : this->tag_listeners_) { - listener->tag_on(*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 PN7160::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(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 PN7160::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 PN7160::process_data_message_(nfc::NciMessage &rx) { - char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGVV(TAG, "Received data message: %s", nfc::format_bytes_to(buf, rx.get_message())); - - std::vector 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 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_to(buf, tx.get_message())); - if (this->transceive_(tx, rx, NFCC_DEFAULT_TIMEOUT, false) != nfc::STATUS_OK) { - ESP_LOGE(TAG, "Sending reply for card emulation failed"); - } -} - -void PN7160::card_emu_t4t_get_response_(std::vector &response, std::vector &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]; - - char ndef_buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGVV(TAG, "Encoded NDEF message: %s", nfc::format_bytes_to(ndef_buf, ndef_message)); - - 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 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); - char write_buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGD(TAG, "Received %u-byte NDEF message: %s", length, nfc::format_bytes_to(write_buf, ndef_msg_written)); - ndef_response.insert(ndef_response.end(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK)); - } - } -} - -uint8_t PN7160::transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, const uint16_t timeout, - const bool expect_notification) { - uint8_t retries = NFCC_MAX_COMM_FAILS; - char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - - 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_to(buf, tx.get_message())); - // 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_to(buf, rx.get_message())); - // 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_to(buf, rx.get_message())); - 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_to(buf, rx.get_message())); - } - 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_to(buf, rx.get_message())); - 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_to(buf, rx.get_message())); - } - - return nfc::STATUS_OK; - } -} - -uint8_t PN7160::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 esphome::pn7160 diff --git a/esphome/components/pn7160/pn7160.h b/esphome/components/pn7160/pn7160.h index da4577874c..b44a595cce 100644 --- a/esphome/components/pn7160/pn7160.h +++ b/esphome/components/pn7160/pn7160.h @@ -1,59 +1,9 @@ #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 +#include "esphome/components/pn71xx/pn71xx.h" namespace esphome::pn7160 { -static constexpr uint16_t NFCC_DEFAULT_TIMEOUT = 10; -static constexpr uint16_t NFCC_INIT_TIMEOUT = 50; -static constexpr uint16_t NFCC_TAG_WRITE_TIMEOUT = 15; - -static constexpr uint8_t NFCC_MAX_COMM_FAILS = 3; -static constexpr uint8_t NFCC_MAX_ERROR_COUNT = 10; - -static constexpr uint8_t XCHG_DATA_OID = 0x10; -static constexpr uint8_t MF_SECTORSEL_OID = 0x32; -static constexpr uint8_t MFC_AUTHENTICATE_OID = 0x40; -static constexpr uint8_t TEST_PRBS_OID = 0x30; -static constexpr uint8_t TEST_ANTENNA_OID = 0x3D; -static constexpr uint8_t TEST_GET_REGISTER_OID = 0x33; - -static constexpr uint8_t MFC_AUTHENTICATE_PARAM_KS_A = 0x00; // key select A -static constexpr uint8_t MFC_AUTHENTICATE_PARAM_KS_B = 0x80; // key select B -static constexpr uint8_t MFC_AUTHENTICATE_PARAM_EMBED_KEY = 0x10; - -static constexpr uint8_t CARD_EMU_T4T_APP_SELECT[] = {0x00, 0xA4, 0x04, 0x00, 0x07, 0xD2, 0x76, - 0x00, 0x00, 0x85, 0x01, 0x01, 0x00}; -static constexpr uint8_t CARD_EMU_T4T_CC[] = {0x00, 0x0F, 0x20, 0x00, 0xFF, 0x00, 0xFF, 0x04, - 0x06, 0xE1, 0x04, 0x00, 0xFF, 0x00, 0x00}; -static constexpr uint8_t CARD_EMU_T4T_CC_SELECT[] = {0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x03}; -static constexpr uint8_t CARD_EMU_T4T_NDEF_SELECT[] = {0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x04}; -static constexpr uint8_t CARD_EMU_T4T_READ[] = {0x00, 0xB0}; -static constexpr uint8_t CARD_EMU_T4T_WRITE[] = {0x00, 0xD6}; -static constexpr uint8_t CARD_EMU_T4T_OK[] = {0x90, 0x00}; -static constexpr uint8_t CARD_EMU_T4T_NOK[] = {0x6A, 0x82}; - -static constexpr 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 constexpr 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 constexpr uint8_t PMU_CFG[] = { 0x01, // Number of parameters 0xA0, 0x0E, // ext. tag @@ -73,34 +23,6 @@ static constexpr uint8_t PMU_CFG[] = { 0x0C, // RFU }; -static constexpr 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 constexpr 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 constexpr 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 constexpr 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 constexpr uint8_t RF_LISTEN_MODE_ROUTING_CONFIG[] = {0x00, // "more" (another message is coming) 2, // number of table entries 0x01, // type = protocol-based @@ -114,200 +36,23 @@ static constexpr uint8_t RF_LISTEN_MODE_ROUTING_CONFIG[] = {0x00, // "more" (an 0x07, // power state nfc::TECH_PASSIVE_NFCA}; // technology -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 tag; - bool trig_called; -}; - -class PN7160 : public nfc::Nfcc, public Component { +class PN7160 : public pn71xx::PN71xx { public: void setup() override; void dump_config() override; - void loop() override; void set_dwl_req_pin(GPIOPin *dwl_req_pin) { this->dwl_req_pin_ = dwl_req_pin; } - 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_wkup_req_pin(GPIOPin *wkup_req_pin) { this->wkup_req_pin_ = wkup_req_pin; } - void set_tag_ttl(uint32_t ttl) { this->tag_ttl_ = ttl; } - void set_tag_emulation_message(std::shared_ptr message); - void set_tag_emulation_message(const optional &message, optional 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); } - - template void add_on_emulated_tag_scan_callback(F &&callback) { - this->on_emulated_tag_scan_callback_.add(std::forward(callback)); - } - - template void add_on_finished_write_callback(F &&callback) { - this->on_finished_write_callback_.add(std::forward(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 message); - void set_tag_write_message(optional message, optional include_android_app_record); - - uint8_t set_test_mode(TestMode test_mode, const std::vector &data, std::vector &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_(nfc::NfcTagUid &uid); - uint8_t format_endpoint_(nfc::NfcTagUid &uid); - uint8_t write_endpoint_(nfc::NfcTagUid &uid, std::shared_ptr &message); - - std::unique_ptr build_tag_(uint8_t mode_tech, const std::vector &data); - optional find_tag_uid_(const nfc::NfcTagUid &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 &response, std::vector &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 &data); - uint8_t write_mifare_classic_block_(uint8_t block_num, const uint8_t *data, size_t len); - 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 &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 &data); - bool is_mifare_ultralight_formatted_(const std::vector &page_3_to_6); - uint16_t read_mifare_ultralight_capacity_(); - uint8_t find_mifare_ultralight_ndef_(const std::vector &page_3_to_6, uint8_t &message_length, - uint8_t &message_start_index); - uint8_t write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len); - uint8_t write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr &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}; + void prepare_reset() override; + uint8_t verify_reset(nfc::NciMessage &rx, bool reset_config) override; + uint8_t process_init_response(nfc::NciMessage &rx) override; + std::span pmu_config() const override { return PMU_CFG; } + std::span listen_mode_routing_config() const override { return RF_LISTEN_MODE_ROUTING_CONFIG; } GPIOPin *dwl_req_pin_{nullptr}; - GPIOPin *irq_pin_{nullptr}; - GPIOPin *ven_pin_{nullptr}; GPIOPin *wkup_req_pin_{nullptr}; - - CallbackManager on_emulated_tag_scan_callback_; - CallbackManager on_finished_write_callback_; - - std::vector discovered_endpoint_; - - CardEmulationState ce_state_{CardEmulationState::CARD_EMU_IDLE}; - NCIState nci_state_{NCIState::NFCC_RESET}; - NCIState nci_state_error_{NCIState::NONE}; - - std::shared_ptr card_emulation_message_; - std::shared_ptr next_task_message_to_write_; - - std::vector triggers_ontag_; - std::vector triggers_ontagremoved_; }; } // namespace esphome::pn7160 diff --git a/esphome/components/pn7160/pn7160_mifare_ultralight.cpp b/esphome/components/pn7160/pn7160_mifare_ultralight.cpp deleted file mode 100644 index e319a4cb2e..0000000000 --- a/esphome/components/pn7160/pn7160_mifare_ultralight.cpp +++ /dev/null @@ -1,185 +0,0 @@ -#include -#include -#include - -#include "pn7160.h" -#include "esphome/core/log.h" - -namespace esphome::pn7160 { - -static const char *const TAG = "pn7160.mifare_ultralight"; - -uint8_t PN7160::read_mifare_ultralight_tag_(nfc::NfcTag &tag) { - std::vector 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(data)); - - return nfc::STATUS_OK; -} - -uint8_t PN7160::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector &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); - } - } - - char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; - ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes_to(buf, data)); - - return nfc::STATUS_OK; -} - -bool PN7160::is_mifare_ultralight_formatted_(const std::vector &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 PN7160::read_mifare_ultralight_capacity_() { - std::vector 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 PN7160::find_mifare_ultralight_ndef_(const std::vector &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 + 6)) { - 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 PN7160::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr &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() + 3, 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) { - if (this->write_mifare_ultralight_page_(current_page, encoded.data() + index, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) != - nfc::STATUS_OK) { - return nfc::STATUS_FAILED; - } - index += nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; - current_page++; - } - return nfc::STATUS_OK; -} - -uint8_t PN7160::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; - - static constexpr std::array 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.data(), BLANK_DATA.size()) != nfc::STATUS_OK) { - return nfc::STATUS_FAILED; - } - } - return nfc::STATUS_OK; -} - -uint8_t PN7160::write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len) { - std::vector payload = {nfc::MIFARE_CMD_WRITE_ULTRALIGHT, page_num}; - payload.insert(payload.end(), write_data, write_data + len); - - 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 esphome::pn7160 diff --git a/esphome/components/pn7160_i2c/pn7160_i2c.cpp b/esphome/components/pn7160_i2c/pn7160_i2c.cpp index c34cf90e68..799a258b98 100644 --- a/esphome/components/pn7160_i2c/pn7160_i2c.cpp +++ b/esphome/components/pn7160_i2c/pn7160_i2c.cpp @@ -24,10 +24,10 @@ uint8_t PN7160I2C::read_nfcc(nfc::NciMessage &rx, const uint16_t timeout) { return nfc::STATUS_FAILED; } } - // semaphore to ensure transaction is complete before returning - if (this->wait_for_irq_(pn7160::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; + // IRQ normally drops at the end of the read. If another message is queued it rises again at once, and the short + // low pulse may be missed; that means more data is waiting, not that this read failed (UM11495, 6.2.4). + if (this->wait_for_irq_(pn71xx::NFCC_IRQ_CLEAR_TIMEOUT, false) != nfc::STATUS_OK) { + ESP_LOGVV(TAG, "IRQ still active after read; another message is pending"); } return nfc::STATUS_OK; } diff --git a/esphome/components/pn7160_spi/pn7160_spi.cpp b/esphome/components/pn7160_spi/pn7160_spi.cpp index f3c413e952..43420f9efd 100644 --- a/esphome/components/pn7160_spi/pn7160_spi.cpp +++ b/esphome/components/pn7160_spi/pn7160_spi.cpp @@ -7,7 +7,6 @@ static const char *const TAG = "pn7160_spi"; void PN7160Spi::setup() { this->spi_setup(); - this->cs_->digital_write(false); PN7160::setup(); } @@ -28,20 +27,28 @@ uint8_t PN7160Spi::read_nfcc(nfc::NciMessage &rx, const uint16_t timeout) { this->read_array(rx.get_message().data() + nfc::NCI_PKT_HEADER_SIZE, length); } this->disable(); - // semaphore to ensure transaction is complete before returning - if (this->wait_for_irq_(pn7160::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; + // IRQ normally drops at the end of the read. If another message is queued it rises again at once, and the short + // low pulse may be missed; that means more data is waiting, not that this read failed (UM11495, 6.3.4). + if (this->wait_for_irq_(pn71xx::NFCC_IRQ_CLEAR_TIMEOUT, false) != nfc::STATUS_OK) { + ESP_LOGVV(TAG, "IRQ still active after read; another message is pending"); } return nfc::STATUS_OK; } uint8_t PN7160Spi::write_nfcc(nfc::NciMessage &tx) { + auto encoded = tx.encode(); this->enable(); - this->write_byte(TDD_SPI_WRITE); // send "transfer direction detector" - this->write_array(tx.encode().data(), tx.encode().size()); + // send "transfer direction detector"; the NFCC answers 0xFF when it is ready to receive (UM11495, 6.3.3) + const uint8_t status = this->transfer_byte(TDD_SPI_WRITE); + const bool ready = status == 0xFF; + if (ready) { + this->write_array(encoded.data(), encoded.size()); + } this->disable(); - return nfc::STATUS_OK; + if (!ready) { + ESP_LOGV(TAG, "NFCC not ready for write (0x%02X)", status); + } + return ready ? nfc::STATUS_OK : nfc::STATUS_FAILED; } void PN7160Spi::dump_config() { diff --git a/esphome/components/pn71xx/__init__.py b/esphome/components/pn71xx/__init__.py new file mode 100644 index 0000000000..8cda915feb --- /dev/null +++ b/esphome/components/pn71xx/__init__.py @@ -0,0 +1,150 @@ +from esphome import automation, pins +from esphome.automation import maybe_simple_id +import esphome.codegen as cg +from esphome.components import nfc +import esphome.config_validation as cv +from esphome.const import ( + CONF_ID, + CONF_IRQ_PIN, + CONF_MESSAGE, + CONF_ON_FINISHED_WRITE, + CONF_ON_TAG, + CONF_ON_TAG_REMOVED, + CONF_TRIGGER_ID, +) +from esphome.cpp_generator import MockObj +from esphome.types import ConfigType + +AUTO_LOAD = ["binary_sensor", "nfc"] +CODEOWNERS = ["@kbx81", "@jesserockz"] + +CONF_EMULATION_MESSAGE = "emulation_message" +CONF_INCLUDE_ANDROID_APP_RECORD = "include_android_app_record" +CONF_ON_EMULATED_TAG_SCAN = "on_emulated_tag_scan" +CONF_TAG_TTL = "tag_ttl" +CONF_VEN_PIN = "ven_pin" + +pn71xx_ns = cg.esphome_ns.namespace("pn71xx") +PN71xx = pn71xx_ns.class_("PN71xx", nfc.Nfcc, cg.Component) + +SIMPLE_ACTION_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(PN71xx), + } +) + +SET_MESSAGE_ACTION_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(PN71xx), + cv.Required(CONF_MESSAGE): cv.templatable(cv.string), + cv.Optional(CONF_INCLUDE_ANDROID_APP_RECORD, default=True): cv.boolean, + } +) + +PN71XX_SCHEMA = cv.Schema( + { + cv.Optional(CONF_ON_EMULATED_TAG_SCAN): automation.validate_automation({}), + cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation({}), + 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) + + +for _name, _method in ( + ("tag.set_emulation_message", "set_tag_emulation_message"), + ("tag.set_write_message", "set_tag_write_message"), +): + automation.register_apply_action( + _name, + SET_MESSAGE_ACTION_SCHEMA, + automation.ApplyCall( + f"{_method}({{}}, {{}})", + ( + (CONF_MESSAGE, cg.std_string), + (CONF_INCLUDE_ANDROID_APP_RECORD, cg.bool_), + ), + ), + ) + +for _name, _call in ( + ("tag.emulation_off", "set_tag_emulation_off()"), + ("tag.emulation_on", "set_tag_emulation_on()"), + ("tag.polling_off", "set_polling_off()"), + ("tag.polling_on", "set_polling_on()"), + ("tag.set_clean_mode", "clean_mode()"), + ("tag.set_format_mode", "format_mode()"), + ("tag.set_read_mode", "read_mode()"), + ("tag.set_write_mode", "write_mode()"), +): + automation.register_apply_action( + _name, SIMPLE_ACTION_SCHEMA, automation.ApplyCall(_call) + ) + + +def register_is_writing_condition(name: str, chip_class: MockObj) -> None: + """Register the chip-specific ``.is_writing`` condition.""" + automation.register_apply_condition( + name, + cv.Schema( + { + cv.GenerateID(): cv.use_id(chip_class), + } + ), + "is_writing()", + ) + + +_CALLBACK_AUTOMATIONS = ( + automation.CallbackAutomation( + CONF_ON_EMULATED_TAG_SCAN, "add_on_emulated_tag_scan_callback" + ), + automation.CallbackAutomation( + CONF_ON_FINISHED_WRITE, "add_on_finished_write_callback" + ), +) + + +async def setup_pn71xx(var: MockObj, config: ConfigType) -> None: + 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 (tag_ttl := config.get(CONF_TAG_TTL)) is not None: + cg.add(var.set_tag_ttl(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 + ) + + await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS) diff --git a/esphome/components/pn71xx/pn71xx.cpp b/esphome/components/pn71xx/pn71xx.cpp new file mode 100644 index 0000000000..f13e2de702 --- /dev/null +++ b/esphome/components/pn71xx/pn71xx.cpp @@ -0,0 +1,1184 @@ +#include +#include + +#include "pn71xx.h" + +#include "esphome/core/application.h" +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome::pn71xx { + +static const char *const TAG = "pn71xx"; + +// Builds a message with a URI record and, optionally, the Home Assistant Android app record +static std::unique_ptr build_uri_message(const std::string &uri, + const bool include_android_app_record) { + auto ndef_message = make_unique(); + + ndef_message->add_uri_record(uri); + + if (include_android_app_record) { + auto ext_record = make_unique(); + 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)); + } + return ndef_message; +} + +void PN71xx::setup() { + this->irq_pin_->setup(); + this->ven_pin_->setup(); + + this->nci_fsm_transition_(); // kick off reset & init processes +} + +void PN71xx::dump_config() { + LOG_PIN(" IRQ pin: ", this->irq_pin_); + LOG_PIN(" VEN pin: ", this->ven_pin_); +} + +void PN71xx::loop() { + this->nci_fsm_transition_(); + this->purge_old_tags_(); +} + +void PN71xx::set_tag_emulation_message(const std::shared_ptr &message) { + if (message == nullptr) { + return; + } + // encoded once here so it is validated up front and not re-encoded for every read from the reader + auto encoded = message->encode(); + if (encoded.size() > CARD_EMU_T4T_MAX_NDEF_SIZE) { + ESP_LOGE(TAG, "Tag emulation message too long: %zu > %u bytes", encoded.size(), CARD_EMU_T4T_MAX_NDEF_SIZE); + return; + } + this->card_emulation_ndef_ = std::move(encoded); + ESP_LOGD(TAG, "Tag emulation message set"); +} + +void PN71xx::set_tag_emulation_message(const std::string &message, const bool include_android_app_record) { + this->set_tag_emulation_message(build_uri_message(message, include_android_app_record)); +} + +void PN71xx::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 PN71xx::set_tag_emulation_off() { + if (this->listening_enabled_) { + this->listening_enabled_ = false; + this->config_refresh_pending_ = true; + } + ESP_LOGD(TAG, "Tag emulation disabled"); +} + +void PN71xx::set_tag_emulation_on() { + if (this->card_emulation_ndef_.empty()) { + 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 PN71xx::set_polling_off() { + if (this->polling_enabled_) { + this->polling_enabled_ = false; + this->config_refresh_pending_ = true; + } + ESP_LOGD(TAG, "Tag polling disabled"); +} + +void PN71xx::set_polling_on() { + if (!this->polling_enabled_) { + this->polling_enabled_ = true; + this->config_refresh_pending_ = true; + } + ESP_LOGD(TAG, "Tag polling enabled"); +} + +void PN71xx::read_mode() { + this->next_task_ = EP_READ; + ESP_LOGD(TAG, "Waiting to read next tag"); +} + +void PN71xx::clean_mode() { + this->next_task_ = EP_CLEAN; + ESP_LOGD(TAG, "Waiting to clean next tag"); +} + +void PN71xx::format_mode() { + this->next_task_ = EP_FORMAT; + ESP_LOGD(TAG, "Waiting to format next tag"); +} + +void PN71xx::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 PN71xx::set_tag_write_message(std::shared_ptr message) { + this->next_task_message_to_write_ = std::move(message); + ESP_LOGD(TAG, "Message to write has been set"); +} + +void PN71xx::set_tag_write_message(const std::string &message, const bool include_android_app_record) { + this->set_tag_write_message(build_uri_message(message, include_android_app_record)); +} + +uint8_t PN71xx::set_test_mode(const TestMode test_mode, const std::vector &data, + std::vector &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()) { + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGW(TAG, "Test results: %s", nfc::format_bytes_to(buf, result)); + } + } + return status; +} + +uint8_t PN71xx::reset_core_(const bool reset_config, const bool power) { + this->prepare_reset(); + + if (power) { + this->ven_pin_->digital_write(true); + delay(NFCC_RESET_DELAY); + this->ven_pin_->digital_write(false); + delay(NFCC_RESET_DELAY); + 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)) { + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGE(TAG, "Invalid reset response: %s", nfc::format_bytes_to(buf, rx.get_message())); + return rx.get_simple_status_response(); + } + return this->verify_reset(rx, reset_config); +} + +uint8_t PN71xx::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)) { + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGE(TAG, "Invalid initialise response: %s", nfc::format_bytes_to(buf, rx.get_message())); + return nfc::STATUS_FAILED; + } + + return this->process_init_response(rx); +} + +uint8_t PN71xx::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; + } + + const auto pmu_config = this->pmu_config(); + tx.set_message(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::NCI_CORE_GID, nfc::NCI_CORE_SET_CONFIG_OID, + std::vector(pmu_config.begin(), pmu_config.end())); + + 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 PN71xx::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(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 PN71xx::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 PN71xx::set_discover_map_() { + std::vector 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 PN71xx::set_listen_mode_routing_() { + const auto routing_config = this->listen_mode_routing_config(); + nfc::NciMessage rx; + nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_SET_LISTEN_MODE_ROUTING_OID, + std::vector(routing_config.begin(), routing_config.end())); + + 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 PN71xx::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 discover_config = std::vector((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 PN71xx::stop_discovery_() { return this->deactivate_(nfc::DEACTIVATION_TYPE_IDLE, NFCC_TAG_WRITE_TIMEOUT); } + +uint8_t PN71xx::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 PN71xx::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; + } + this->selecting_endpoint_ = 0; + for (size_t i = 0; i < this->discovered_endpoint_.size(); i++) { + if (!this->discovered_endpoint_[i].trig_called) { + this->selecting_endpoint_ = i; + break; + } + } + const auto &endpoint = this->discovered_endpoint_[this->selecting_endpoint_]; + // the RF interface must match the one set for this protocol in RF_DISCOVER_MAP_CONFIG + uint8_t interface = nfc::INTF_FRAME; + if (endpoint.protocol == nfc::PROT_ISODEP) { + interface = nfc::INTF_ISODEP; + } else if (endpoint.protocol == nfc::PROT_MIFARE) { + interface = nfc::INTF_TAGCMD; + } + std::vector endpoint_data = {endpoint.id, endpoint.protocol, interface}; + + 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 PN71xx::read_endpoint_data_(const uint8_t protocol, nfc::NfcTag &tag) { + switch (protocol) { + case nfc::PROT_MIFARE: + ESP_LOGV(TAG, "Reading Mifare classic"); + return this->read_mifare_classic_tag_(tag); + + case nfc::PROT_T2T: + ESP_LOGV(TAG, "Reading Mifare ultralight"); + return this->read_mifare_ultralight_tag_(tag); + + default: + ESP_LOGV(TAG, "Reading protocol 0x%02X is not supported", protocol); + break; + } + return nfc::STATUS_FAILED; +} + +uint8_t PN71xx::clean_endpoint_(const uint8_t protocol) { + switch (protocol) { + case nfc::PROT_MIFARE: + return this->format_mifare_classic_mifare_(); + + case nfc::PROT_T2T: + return this->clean_mifare_ultralight_(); + + default: + ESP_LOGE(TAG, "Unsupported tag for cleaning"); + break; + } + return nfc::STATUS_FAILED; +} + +uint8_t PN71xx::format_endpoint_(const uint8_t protocol) { + switch (protocol) { + case nfc::PROT_MIFARE: + return this->format_mifare_classic_ndef_(); + + case nfc::PROT_T2T: + return this->clean_mifare_ultralight_(); + + default: + ESP_LOGE(TAG, "Unsupported tag for formatting"); + break; + } + return nfc::STATUS_FAILED; +} + +uint8_t PN71xx::write_endpoint_(const uint8_t protocol, nfc::NfcTagUid &uid, + std::shared_ptr &message) { + switch (protocol) { + case nfc::PROT_MIFARE: + return this->write_mifare_classic_tag_(message); + + case nfc::PROT_T2T: + return this->write_mifare_ultralight_tag_(uid, message); + + default: + ESP_LOGE(TAG, "Unsupported tag for writing"); + break; + } + return nfc::STATUS_FAILED; +} + +std::unique_ptr PN71xx::build_tag_(const uint8_t mode_tech, const uint8_t protocol, + const std::vector &data) { + switch (mode_tech) { + case (nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCA): { + // RF technology parameters: SENS_RES (2 bytes), NFCID1 length, NFCID1, ... + if (data.size() < 3) { + ESP_LOGE(TAG, "NFC-A parameters too short"); + return nullptr; + } + uint8_t uid_length = data[2]; + if (uid_length == 0 || uid_length > nfc::NFC_UID_MAX_LENGTH || data.size() < 3u + uid_length) { + ESP_LOGE(TAG, "Invalid UID length: %u", uid_length); + return nullptr; + } + nfc::NfcTagUid uid(data.begin() + 3, data.begin() + 3 + uid_length); + if (protocol == nfc::PROT_MIFARE) { + return make_unique(uid, nfc::MIFARE_CLASSIC); + } + if (protocol == nfc::PROT_T2T) { + return make_unique(uid, nfc::NFC_FORUM_TYPE_2); + } + return make_unique(uid); + } + } + return nullptr; +} + +optional PN71xx::find_tag_uid_(const nfc::NfcTagUid &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 PN71xx::purge_old_tags_() { + // millis(), not the loop start time: last_seen is stamped after tag operations that may block for seconds + const uint32_t now = millis(); + for (size_t i = this->discovered_endpoint_.size(); i > 0; i--) { + if (now - this->discovered_endpoint_[i - 1].last_seen > this->tag_ttl_) { + this->erase_tag_(i - 1); + } + } +} + +void PN71xx::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); + } + for (auto *listener : this->tag_listeners_) { + listener->tag_off(*this->discovered_endpoint_[tag_index].tag); + } + char uid_buf[nfc::FORMAT_UID_BUFFER_SIZE]; + ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid_to(uid_buf, this->discovered_endpoint_[tag_index].tag->get_uid())); + this->discovered_endpoint_.erase(this->discovered_endpoint_.begin() + tag_index); + } +} + +void PN71xx::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); + } + [[fallthrough]]; + + 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); + } + [[fallthrough]]; + + 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); + } + [[fallthrough]]; + + 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_DISCOVER_MAP); + return; + } else { + this->nci_fsm_set_state_(NCIState::NFCC_SET_LISTEN_MODE_ROUTING); + } + [[fallthrough]]; + + 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::NFCC_SET_LISTEN_MODE_ROUTING); + return; + } else { + this->nci_fsm_set_state_(NCIState::RFST_IDLE); + } + [[fallthrough]]; + + 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_(); + [[fallthrough]]; + + // All cases below are waiting for NOTIFICATION messages + case NCIState::RFST_DISCOVERY: + if (this->config_refresh_pending_) { + this->refresh_core_config_(); + } + [[fallthrough]]; + + case NCIState::RFST_LISTEN_ACTIVE: + case NCIState::RFST_LISTEN_SLEEP: + case NCIState::RFST_POLL_ACTIVE: + case NCIState::EP_SELECTING: + case NCIState::EP_DEACTIVATING: + // only a notification from the NFCC ends the EP_ states; if it was lost, recover rather than wait forever. + // millis(), not the loop start time: the state is stamped after tag operations that may block for seconds. + if ((this->nci_state_ == NCIState::EP_SELECTING || this->nci_state_ == NCIState::EP_DEACTIVATING) && + !this->irq_pin_->digital_read() && millis() - this->last_nci_state_change_ > NFCC_STATE_TIMEOUT) { + ESP_LOGW(TAG, "Timed out waiting for notification in state %u; resetting NFCC", (uint8_t) this->nci_state_); + this->nci_fsm_set_state_(NCIState::NFCC_RESET); + return; + } + if (this->irq_pin_->digital_read()) { + this->process_message_(); + } + break; + + case NCIState::TEST: + case NCIState::FAILED: + case NCIState::NONE: + default: + return; + } +} + +void PN71xx::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 PN71xx::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 PN71xx::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 { + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGV(TAG, "Unimplemented notification: %s", nfc::format_bytes_to(buf, rx.get_message())); + } + break; + + case nfc::NCI_PKT_MT_CTRL_RESPONSE: { + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGV(TAG, "Unimplemented GID: 0x%02X OID: 0x%02X Full response: %s", rx.get_gid(), rx.get_oid(), + nfc::format_bytes_to(buf, rx.get_message())); + break; + } + + case nfc::NCI_PKT_MT_CTRL_COMMAND: { + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGV(TAG, "Unimplemented command: %s", nfc::format_bytes_to(buf, rx.get_message())); + break; + } + + case nfc::NCI_PKT_MT_DATA: + this->process_data_message_(rx); + break; + + default: { + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGV(TAG, "Unimplemented message type: %s", nfc::format_bytes_to(buf, rx.get_message())); + break; + } + } +} + +void PN71xx::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); + if (rx.get_message().size() < nfc::RF_INTF_ACTIVATED_NTF_RF_TECH_PARAMS) { + ESP_LOGE(TAG, "RF_INTF_ACTIVATED_NTF too short"); + this->stop_discovery_(); + this->nci_fsm_set_state_(NCIState::EP_DEACTIVATING); + return; + } + auto incoming_tag = + this->build_tag_(mode_tech, protocol, + std::vector(rx.get_message().begin() + nfc::RF_INTF_ACTIVATED_NTF_RF_TECH_PARAMS, + 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 = App.get_loop_component_start_time(); + ESP_LOGVV(TAG, "Tag cache updated"); + } else { + this->discovered_endpoint_.emplace_back(DiscoveredEndpoint{.last_seen = App.get_loop_component_start_time(), + .tag = std::move(incoming_tag), + .id = discovery_id, + .protocol = protocol, + .trig_called = 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.protocol) != 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.protocol) != 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\n" + " Tag formatting"); + if (this->format_endpoint_(working_endpoint.protocol) != 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.protocol, 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) { + char uid_buf[nfc::FORMAT_UID_BUFFER_SIZE]; + ESP_LOGI(TAG, "Read tag type %s with UID %s", working_endpoint.tag->get_tag_type().c_str(), + nfc::format_uid_to(uid_buf, working_endpoint.tag->get_uid())); + if (this->read_endpoint_data_(working_endpoint.protocol, *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); + } + for (auto *listener : this->tag_listeners_) { + listener->tag_on(*working_endpoint.tag); + } + working_endpoint.trig_called = true; + break; + } + } + // the tag was present for the whole operation, which may have taken longer than tag_ttl + working_endpoint.last_seen = millis(); + if (working_endpoint.protocol == nfc::PROT_MIFARE) { + 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 PN71xx::process_rf_discover_oid_(nfc::NciMessage &rx) { + if (rx.get_message().size() < nfc::RF_DISCOVER_NTF_RF_TECH_PARAMS) { + ESP_LOGE(TAG, "RF_DISCOVER_NTF too short"); + return; + } + auto incoming_tag = this->build_tag_( + rx.get_message_byte(nfc::RF_DISCOVER_NTF_MODE_TECH), rx.get_message_byte(nfc::RF_DISCOVER_NTF_PROTOCOL), + std::vector(rx.get_message().begin() + nfc::RF_DISCOVER_NTF_RF_TECH_PARAMS, 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 = App.get_loop_component_start_time(); + ESP_LOGVV(TAG, "Tag found & updated"); + } else { + this->discovered_endpoint_.emplace_back( + DiscoveredEndpoint{.last_seen = App.get_loop_component_start_time(), + .tag = std::move(incoming_tag), + .id = rx.get_message_byte(nfc::RF_DISCOVER_NTF_DISCOVERY_ID), + .protocol = rx.get_message_byte(nfc::RF_DISCOVER_NTF_PROTOCOL), + .trig_called = 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 %zu endpoints", this->discovered_endpoint_.size()); + } +} + +void PN71xx::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 PN71xx::process_data_message_(nfc::NciMessage &rx) { + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGVV(TAG, "Received data message: %s", nfc::format_bytes_to(buf, rx.get_message())); + + std::vector ndef_response; + this->card_emu_t4t_get_response_(rx.get_message(), ndef_response); + + if (ndef_response.empty()) { + return; // no message returned, we cannot respond + } + // the CC limits MLe so every response fits in a single data packet (payload length is one byte) + if (ndef_response.size() > UINT8_MAX) { + ESP_LOGE(TAG, "Card emulation response too long: %zu bytes", ndef_response.size()); + return; + } + + nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, ndef_response); + ESP_LOGVV(TAG, "Sending data message: %s", nfc::format_bytes_to(buf, tx.get_message())); + if (this->transceive_(tx, rx, NFCC_DEFAULT_TIMEOUT, false) != nfc::STATUS_OK) { + ESP_LOGE(TAG, "Sending reply for card emulation failed"); + } +} + +bool PN71xx::card_emu_t4t_read_ndef_(const uint16_t offset, const uint8_t length, std::vector &ndef_response) { + const auto &ndef_message = this->card_emulation_ndef_; + // the NDEF file is a two-byte big-endian length (NLEN) followed by the message + const uint16_t ndef_msg_size = ndef_message.size(); + const uint32_t file_size = ndef_msg_size + 2; + if (offset + static_cast(length) > file_size) { + return false; + } + for (uint32_t i = offset; i < offset + static_cast(length); i++) { + if (i == 0) { + ndef_response.push_back(ndef_msg_size >> 8); + } else if (i == 1) { + ndef_response.push_back(ndef_msg_size & 0xFF); + } else { + ndef_response.push_back(ndef_message[i - 2]); + } + } + if (offset + static_cast(length) == file_size) { + ESP_LOGD(TAG, "NDEF message sent"); + this->on_emulated_tag_scan_callback_.call(); + } + return true; +} + +void PN71xx::card_emu_t4t_get_response_(const std::vector &response, std::vector &ndef_response) { + ndef_response.clear(); + if (this->card_emulation_ndef_.empty()) { + ESP_LOGE(TAG, "No NDEF message is set; tag emulation not possible"); + return; + } + if (response.size() < nfc::NCI_PKT_HEADER_SIZE) { + return; + } + + const auto apdu_begin = response.begin() + nfc::NCI_PKT_HEADER_SIZE; + const size_t apdu_size = response.size() - nfc::NCI_PKT_HEADER_SIZE; + auto apdu_is = [&](const uint8_t *cmd, size_t cmd_size) { + return apdu_size == cmd_size && std::equal(apdu_begin, response.end(), cmd); + }; + auto apdu_starts_with = [&](const uint8_t *cmd, size_t cmd_size) { + return apdu_size >= cmd_size && std::equal(cmd, cmd + cmd_size, apdu_begin); + }; + bool ok = false; + + if (apdu_is(CARD_EMU_T4T_APP_SELECT, sizeof(CARD_EMU_T4T_APP_SELECT)) || + apdu_is(CARD_EMU_T4T_APP_SELECT, sizeof(CARD_EMU_T4T_APP_SELECT) - 1)) { // Le is optional + ESP_LOGVV(TAG, "CARD_EMU_NDEF_APP_SELECTED"); + this->ce_state_ = CardEmulationState::CARD_EMU_NDEF_APP_SELECTED; + ok = true; + } else if (apdu_is(CARD_EMU_T4T_CC_SELECT, sizeof(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; + ok = true; + } + } else if (apdu_is(CARD_EMU_T4T_NDEF_SELECT, sizeof(CARD_EMU_T4T_NDEF_SELECT))) { + ESP_LOGVV(TAG, "CARD_EMU_NDEF_SELECTED"); + this->ce_state_ = CardEmulationState::CARD_EMU_NDEF_SELECTED; + ok = true; + } else if (apdu_starts_with(CARD_EMU_T4T_READ, sizeof(CARD_EMU_T4T_READ)) && apdu_size == 5) { + // READ BINARY: CLA INS P1 P2 Le, where P1-P2 is the offset + const uint16_t offset = (apdu_begin[2] << 8) | apdu_begin[3]; + const uint8_t length = apdu_begin[4]; + if (this->ce_state_ == CardEmulationState::CARD_EMU_CC_SELECTED) { + ESP_LOGVV(TAG, "CARD_EMU_T4T_READ with CARD_EMU_CC_SELECTED"); + if (offset + static_cast(length) <= sizeof(CARD_EMU_T4T_CC)) { + ndef_response.insert(ndef_response.end(), std::begin(CARD_EMU_T4T_CC) + offset, + std::begin(CARD_EMU_T4T_CC) + offset + length); + ok = true; + } + } else if (this->ce_state_ == CardEmulationState::CARD_EMU_NDEF_SELECTED) { + ESP_LOGVV(TAG, "CARD_EMU_T4T_READ with CARD_EMU_NDEF_SELECTED"); + ok = this->card_emu_t4t_read_ndef_(offset, length, ndef_response); + } + } else if (apdu_starts_with(CARD_EMU_T4T_WRITE, sizeof(CARD_EMU_T4T_WRITE)) && apdu_size >= 5) { + // UPDATE BINARY: CLA INS P1 P2 Lc data + const uint8_t length = apdu_begin[4]; + if (this->ce_state_ == CardEmulationState::CARD_EMU_NDEF_SELECTED && apdu_size >= 5u + length) { + ESP_LOGVV(TAG, "CARD_EMU_T4T_WRITE"); + std::vector ndef_msg_written(apdu_begin + 5, apdu_begin + 5 + length); + char write_buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + ESP_LOGD(TAG, "Received %u-byte NDEF message: %s", length, nfc::format_bytes_to(write_buf, ndef_msg_written)); + ok = true; + } + } + + if (ok) { + ndef_response.insert(ndef_response.end(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK)); + } else { + ndef_response.clear(); + ndef_response.insert(ndef_response.end(), std::begin(CARD_EMU_T4T_NOK), std::end(CARD_EMU_T4T_NOK)); + this->ce_state_ = CardEmulationState::CARD_EMU_IDLE; + } +} + +uint8_t PN71xx::transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, const uint16_t timeout, + const bool expect_notification) { + char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; + + // The NFCC may refuse a frame while it wakes from standby; the whole frame must then be sent again. + // A command must never be re-sent once it was accepted, as the NFCC will answer it (NCI 1.0, 3.2.1). + uint8_t attempts = 0; + while (this->write_nfcc(tx) != nfc::STATUS_OK) { + if (++attempts >= NFCC_MAX_COMM_FAILS) { + ESP_LOGE(TAG, "Error sending message"); + return nfc::STATUS_FAILED; + } + delay(NFCC_WRITE_RETRY_DELAY); + } + ESP_LOGVV(TAG, "Wrote: %s", nfc::format_bytes_to(buf, tx.get_message())); + + if (!tx.message_type_is(nfc::NCI_PKT_MT_DATA)) { + // Notifications may already be queued ahead of the response; skip them. They carry the same GID and OID + // as some responses (e.g. RF_DEACTIVATE_NTF), so the message type must be checked. A response to an earlier + // command whose read timed out (a tag leaving the field delays RF_DEACTIVATE_RSP) is skipped the same way. + for (uint8_t i = 0; i < NFCC_MAX_COMM_FAILS; i++) { + if (this->read_nfcc(rx, timeout) != nfc::STATUS_OK) { + ESP_LOGW(TAG, "Error receiving response"); + return nfc::STATUS_FAILED; + } + ESP_LOGVV(TAG, "Read: %s", nfc::format_bytes_to(buf, rx.get_message())); + if (rx.message_type_is(nfc::NCI_PKT_MT_CTRL_RESPONSE) && rx.get_gid() == tx.get_gid() && + rx.get_oid() == tx.get_oid()) { + break; + } + ESP_LOGW(TAG, "Discarding message received while waiting for response: %s", + nfc::format_bytes_to(buf, rx.get_message())); + } + // for commands, the GID and OID should match and the status should be OK + if (!rx.message_type_is(nfc::NCI_PKT_MT_CTRL_RESPONSE) || (rx.get_gid() != tx.get_gid()) || + (rx.get_oid() != tx.get_oid())) { + ESP_LOGE(TAG, "Incorrect response to command: %s", nfc::format_bytes_to(buf, rx.get_message())); + 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_to(buf, rx.get_message())); + } + return rx.get_simple_status_response(); + } + + // when sending data to the endpoint, the first message is the credit notification from the NFCC + if (this->read_nfcc(rx, timeout) != nfc::STATUS_OK) { + ESP_LOGW(TAG, "Error receiving credit notification"); + return nfc::STATUS_FAILED; + } + ESP_LOGVV(TAG, "Read: %s", nfc::format_bytes_to(buf, rx.get_message())); + 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_to(buf, rx.get_message())); + return nfc::STATUS_FAILED; + } + + if (expect_notification) { + // the endpoint's answer follows in a data 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_to(buf, rx.get_message())); + } + + return nfc::STATUS_OK; +} + +uint8_t PN71xx::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; + } + } + return nfc::STATUS_FAILED; +} + +} // namespace esphome::pn71xx diff --git a/esphome/components/pn71xx/pn71xx.h b/esphome/components/pn71xx/pn71xx.h new file mode 100644 index 0000000000..f9cfddc2e9 --- /dev/null +++ b/esphome/components/pn71xx/pn71xx.h @@ -0,0 +1,306 @@ +#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 +#include + +namespace esphome::pn71xx { + +// Time to wait for the NFCC to answer. NXP's reference stack waits 1 s for a response; 10 ms was short enough +// that a slow RF_DEACTIVATE_RSP, for example when a tag leaves the field, caused a full NFCC reset. +static constexpr uint16_t NFCC_DEFAULT_TIMEOUT = 100; +static constexpr uint16_t NFCC_INIT_TIMEOUT = 50; +static constexpr uint16_t NFCC_TAG_WRITE_TIMEOUT = 100; +// Time to wait for IRQ to drop after a read; it drops within microseconds unless another message is queued +static constexpr uint16_t NFCC_IRQ_CLEAR_TIMEOUT = 5; +// Length of the VEN and DWL_REQ pulses when resetting the NFCC +static constexpr uint16_t NFCC_RESET_DELAY = 10; +// Time to wait before resending a frame the NFCC refused, e.g. while waking from standby +static constexpr uint16_t NFCC_WRITE_RETRY_DELAY = 5; +// Longest time the FSM may wait for a notification that ends a transitional state before resetting the NFCC +static constexpr uint32_t NFCC_STATE_TIMEOUT = 1000; + +static constexpr uint8_t NFCC_MAX_COMM_FAILS = 3; +static constexpr uint8_t NFCC_MAX_ERROR_COUNT = 10; + +static constexpr uint8_t XCHG_DATA_OID = 0x10; +static constexpr uint8_t MF_SECTORSEL_OID = 0x32; +static constexpr uint8_t MFC_AUTHENTICATE_OID = 0x40; +static constexpr uint8_t TEST_PRBS_OID = 0x30; +static constexpr uint8_t TEST_ANTENNA_OID = 0x3D; +static constexpr uint8_t TEST_GET_REGISTER_OID = 0x33; + +static constexpr uint8_t MFC_AUTHENTICATE_PARAM_KS_A = 0x00; // key select A +static constexpr uint8_t MFC_AUTHENTICATE_PARAM_KS_B = 0x80; // key select B +static constexpr uint8_t MFC_AUTHENTICATE_PARAM_EMBED_KEY = 0x10; + +static constexpr uint8_t CARD_EMU_T4T_APP_SELECT[] = {0x00, 0xA4, 0x04, 0x00, 0x07, 0xD2, 0x76, + 0x00, 0x00, 0x85, 0x01, 0x01, 0x00}; +// MLe is 0xFD so a full-length READ BINARY response plus the two status bytes fits in one NCI data packet +static constexpr uint8_t CARD_EMU_T4T_CC[] = {0x00, 0x0F, 0x20, 0x00, 0xFD, 0x00, 0xFF, 0x04, + 0x06, 0xE1, 0x04, 0x00, 0xFF, 0x00, 0x00}; +// Largest NDEF message that fits in the emulated NDEF file (max file size in the CC, less the 2-byte length) +static constexpr uint16_t CARD_EMU_T4T_MAX_NDEF_SIZE = 0xFF - 2; +static constexpr uint8_t CARD_EMU_T4T_CC_SELECT[] = {0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x03}; +static constexpr uint8_t CARD_EMU_T4T_NDEF_SELECT[] = {0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x04}; +static constexpr uint8_t CARD_EMU_T4T_READ[] = {0x00, 0xB0}; +static constexpr uint8_t CARD_EMU_T4T_WRITE[] = {0x00, 0xD6}; +static constexpr uint8_t CARD_EMU_T4T_OK[] = {0x90, 0x00}; +static constexpr uint8_t CARD_EMU_T4T_NOK[] = {0x6A, 0x82}; + +static constexpr 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 constexpr 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 constexpr 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 constexpr 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 constexpr 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 constexpr 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 + +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 { + uint32_t last_seen; + std::unique_ptr tag; + uint8_t id; + uint8_t protocol; + bool trig_called; +}; + +/// Common driver for the NXP PN71xx family of NCI NFC controllers. The chip classes (PN7150, PN7160) supply the parts +/// that differ between chips; the bus classes supply read_nfcc() and write_nfcc(). +class PN71xx : public nfc::Nfcc, public Component { + public: + void setup() override; + void dump_config() override; + 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(const std::shared_ptr &message); + void set_tag_emulation_message(const std::string &message, bool include_android_app_record = true); + 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); } + + template void add_on_emulated_tag_scan_callback(F &&callback) { + this->on_emulated_tag_scan_callback_.add(std::forward(callback)); + } + + template void add_on_finished_write_callback(F &&callback) { + this->on_finished_write_callback_.add(std::forward(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 message); + void set_tag_write_message(const std::string &message, bool include_android_app_record = true); + + uint8_t set_test_mode(TestMode test_mode, const std::vector &data, std::vector &result); + + protected: + uint8_t reset_core_(bool reset_config, bool power); + uint8_t init_core_(); + + /// Chip hooks + /// Called before the NFCC is reset, e.g. to make sure it will not start in firmware download mode + virtual void prepare_reset() {} + /// Validates what follows a successful CORE_RESET_RSP (in `rx`); may read more messages from the NFCC + virtual uint8_t verify_reset(nfc::NciMessage &rx, bool reset_config) = 0; + /// Logs chip information from a successful CORE_INIT_RSP + virtual uint8_t process_init_response(nfc::NciMessage &rx) = 0; + /// Parameters for the CORE_SET_CONFIG_CMD that configures the power management unit + virtual std::span pmu_config() const = 0; + /// Payload of the RF_SET_LISTEN_MODE_ROUTING_CMD + virtual std::span listen_mode_routing_config() const = 0; + + 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_(uint8_t protocol, nfc::NfcTag &tag); + uint8_t clean_endpoint_(uint8_t protocol); + uint8_t format_endpoint_(uint8_t protocol); + uint8_t write_endpoint_(uint8_t protocol, nfc::NfcTagUid &uid, std::shared_ptr &message); + + std::unique_ptr build_tag_(uint8_t mode_tech, uint8_t protocol, const std::vector &data); + optional find_tag_uid_(const nfc::NfcTagUid &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_(const std::vector &response, std::vector &ndef_response); + bool card_emu_t4t_read_ndef_(uint16_t offset, uint8_t length, std::vector &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 &data); + uint8_t write_mifare_classic_block_(uint8_t block_num, const uint8_t *data, size_t len); + 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 &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 &data); + bool is_mifare_ultralight_formatted_(const std::vector &page_3_to_6); + uint16_t read_mifare_ultralight_capacity_(); + uint8_t find_mifare_ultralight_ndef_(const std::vector &page_3_to_6, uint8_t &message_length, + uint8_t &message_start_index); + uint8_t write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len); + uint8_t write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr &message); + uint8_t clean_mifare_ultralight_(); + + enum NfcTask : uint8_t { + EP_READ = 0, + EP_CLEAN, + EP_FORMAT, + EP_WRITE, + }; + + // members are ordered by alignment, widest first, to minimize padding + CallbackManager on_emulated_tag_scan_callback_; + CallbackManager on_finished_write_callback_; + + std::vector discovered_endpoint_; + std::vector card_emulation_ndef_; // encoded emulation message; empty when none is set + std::vector triggers_ontag_; + std::vector triggers_ontagremoved_; + std::shared_ptr next_task_message_to_write_; + + GPIOPin *irq_pin_{nullptr}; + GPIOPin *ven_pin_{nullptr}; + + uint32_t last_nci_state_change_{0}; + uint32_t tag_ttl_{250}; + + NfcTask next_task_{EP_READ}; + CardEmulationState ce_state_{CardEmulationState::CARD_EMU_IDLE}; + NCIState nci_state_{NCIState::NFCC_RESET}; + NCIState nci_state_error_{NCIState::NONE}; + uint8_t error_count_{0}; + uint8_t selecting_endpoint_{0}; + + bool config_refresh_pending_{false}; + bool core_config_is_solo_{false}; + bool listening_enabled_{false}; + bool polling_enabled_{true}; +}; + +} // namespace esphome::pn71xx diff --git a/esphome/components/pn7160/pn7160_mifare_classic.cpp b/esphome/components/pn71xx/pn71xx_mifare_classic.cpp similarity index 94% rename from esphome/components/pn7160/pn7160_mifare_classic.cpp rename to esphome/components/pn71xx/pn71xx_mifare_classic.cpp index 0dc8bbdfe4..0bd6148e8a 100644 --- a/esphome/components/pn7160/pn7160_mifare_classic.cpp +++ b/esphome/components/pn71xx/pn71xx_mifare_classic.cpp @@ -1,14 +1,14 @@ #include #include -#include "pn7160.h" +#include "pn71xx.h" #include "esphome/core/log.h" -namespace esphome::pn7160 { +namespace esphome::pn71xx { -static const char *const TAG = "pn7160.mifare_classic"; +static const char *const TAG = "pn71xx.mifare_classic"; -uint8_t PN7160::read_mifare_classic_tag_(nfc::NfcTag &tag) { +uint8_t PN71xx::read_mifare_classic_tag_(nfc::NfcTag &tag) { uint8_t current_block = 4; uint8_t message_start_index = 0; uint32_t message_length = 0; @@ -66,7 +66,7 @@ uint8_t PN7160::read_mifare_classic_tag_(nfc::NfcTag &tag) { return nfc::STATUS_OK; } -uint8_t PN7160::read_mifare_classic_block_(uint8_t block_num, std::vector &data) { +uint8_t PN71xx::read_mifare_classic_block_(uint8_t block_num, std::vector &data) { nfc::NciMessage rx; nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_READ, block_num}); char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; @@ -90,7 +90,7 @@ uint8_t PN7160::read_mifare_classic_block_(uint8_t block_num, std::vectorsect_to_auth_(block_num), key_num}); @@ -129,7 +129,7 @@ uint8_t PN7160::auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, c return nfc::STATUS_OK; } -uint8_t PN7160::sect_to_auth_(const uint8_t block_num) { +uint8_t PN71xx::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) + @@ -138,7 +138,7 @@ uint8_t PN7160::sect_to_auth_(const uint8_t block_num) { return block_num / nfc::MIFARE_CLASSIC_BLOCKS_PER_SECT_LOW; } -uint8_t PN7160::format_mifare_classic_mifare_() { +uint8_t PN71xx::format_mifare_classic_mifare_() { static constexpr std::array BLANK_BUFFER = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; static constexpr std::array TRAILER_BUFFER = { @@ -173,7 +173,7 @@ uint8_t PN7160::format_mifare_classic_mifare_() { return status; } -uint8_t PN7160::format_mifare_classic_ndef_() { +uint8_t PN71xx::format_mifare_classic_ndef_() { static constexpr std::array EMPTY_NDEF_MESSAGE = { 0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; static constexpr std::array BLANK_BLOCK = { @@ -237,7 +237,7 @@ uint8_t PN7160::format_mifare_classic_ndef_() { return status; } -uint8_t PN7160::write_mifare_classic_block_(uint8_t block_num, const uint8_t *data, size_t len) { +uint8_t PN71xx::write_mifare_classic_block_(uint8_t block_num, const uint8_t *data, size_t len) { nfc::NciMessage rx; nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_WRITE, block_num}); char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; @@ -267,7 +267,7 @@ uint8_t PN7160::write_mifare_classic_block_(uint8_t block_num, const uint8_t *da return nfc::STATUS_OK; } -uint8_t PN7160::write_mifare_classic_tag_(const std::shared_ptr &message) { +uint8_t PN71xx::write_mifare_classic_tag_(const std::shared_ptr &message) { auto encoded = message->encode(); uint32_t message_length = encoded.size(); @@ -310,7 +310,7 @@ uint8_t PN7160::write_mifare_classic_tag_(const std::shared_ptr #include -#include "pn7150.h" +#include "pn71xx.h" #include "esphome/core/log.h" -namespace esphome::pn7150 { +namespace esphome::pn71xx { -static const char *const TAG = "pn7150.mifare_ultralight"; +static const char *const TAG = "pn71xx.mifare_ultralight"; -uint8_t PN7150::read_mifare_ultralight_tag_(nfc::NfcTag &tag) { +uint8_t PN71xx::read_mifare_ultralight_tag_(nfc::NfcTag &tag) { std::vector 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, @@ -50,18 +50,24 @@ uint8_t PN7150::read_mifare_ultralight_tag_(nfc::NfcTag &tag) { return nfc::STATUS_OK; } -uint8_t PN7150::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector &data) { +uint8_t PN71xx::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector &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...???... + // a short answer (e.g. a NAK for a page beyond the end of the tag) is retried a limited number of times + uint8_t attempts = 0; + do { if (this->transceive_(tx, rx) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Error reading tag data"); return nfc::STATUS_FAILED; } + if (++attempts > NFCC_MAX_COMM_FAILS && rx.get_payload_size() < read_increment) { + ESP_LOGE(TAG, "Short read from page %u", tx.get_message().back()); + 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 @@ -78,7 +84,7 @@ uint8_t PN7150::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_b return nfc::STATUS_OK; } -bool PN7150::is_mifare_ultralight_formatted_(const std::vector &page_3_to_6) { +bool PN71xx::is_mifare_ultralight_formatted_(const std::vector &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) && @@ -86,7 +92,7 @@ bool PN7150::is_mifare_ultralight_formatted_(const std::vector &page_3_ (page_3_to_6[p4_offset + 2] != 0xFF) || (page_3_to_6[p4_offset + 3] != 0xFF)); } -uint16_t PN7150::read_mifare_ultralight_capacity_() { +uint16_t PN71xx::read_mifare_ultralight_capacity_() { std::vector 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); @@ -95,7 +101,7 @@ uint16_t PN7150::read_mifare_ultralight_capacity_() { return 0; } -uint8_t PN7150::find_mifare_ultralight_ndef_(const std::vector &page_3_to_6, uint8_t &message_length, +uint8_t PN71xx::find_mifare_ultralight_ndef_(const std::vector &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 @@ -115,7 +121,7 @@ uint8_t PN7150::find_mifare_ultralight_ndef_(const std::vector &page_3_ return nfc::STATUS_FAILED; } -uint8_t PN7150::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr &message) { +uint8_t PN71xx::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr &message) { uint32_t capacity = this->read_mifare_ultralight_capacity_(); auto encoded = message->encode(); @@ -154,7 +160,7 @@ uint8_t PN7150::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::sha return nfc::STATUS_OK; } -uint8_t PN7150::clean_mifare_ultralight_() { +uint8_t PN71xx::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; @@ -168,7 +174,7 @@ uint8_t PN7150::clean_mifare_ultralight_() { return nfc::STATUS_OK; } -uint8_t PN7150::write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len) { +uint8_t PN71xx::write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len) { std::vector payload = {nfc::MIFARE_CMD_WRITE_ULTRALIGHT, page_num}; payload.insert(payload.end(), write_data, write_data + len); @@ -182,4 +188,4 @@ uint8_t PN7150::write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *w return nfc::STATUS_OK; } -} // namespace esphome::pn7150 +} // namespace esphome::pn71xx diff --git a/tests/components/pn71xx/pn71xx_test.cpp b/tests/components/pn71xx/pn71xx_test.cpp new file mode 100644 index 0000000000..ceb2d7305c --- /dev/null +++ b/tests/components/pn71xx/pn71xx_test.cpp @@ -0,0 +1,194 @@ +#include + +#include + +#include "esphome/components/pn71xx/pn71xx.h" + +namespace esphome::pn71xx { + +namespace { + +// Stands in for the bus: records every frame written and replays queued frames on read. +class FakePN71xx : public PN71xx { + public: + using PN71xx::card_emu_t4t_get_response_; + using PN71xx::transceive_; + + std::deque> to_read; + std::vector> written; + uint8_t write_failures{0}; + + protected: + uint8_t verify_reset(nfc::NciMessage &rx, bool reset_config) override { return nfc::STATUS_OK; } + uint8_t process_init_response(nfc::NciMessage &rx) override { return nfc::STATUS_OK; } + std::span pmu_config() const override { return {}; } + std::span listen_mode_routing_config() const override { return {}; } + + uint8_t read_nfcc(nfc::NciMessage &rx, uint16_t timeout) override { + if (this->to_read.empty()) + return nfc::STATUS_FAILED; + rx = nfc::NciMessage(this->to_read.front()); + this->to_read.pop_front(); + return nfc::STATUS_OK; + } + uint8_t write_nfcc(nfc::NciMessage &tx) override { + if (this->write_failures > 0) { + this->write_failures--; + return nfc::STATUS_FAILED; + } + this->written.push_back(tx.encode()); + return nfc::STATUS_OK; + } +}; + +std::vector apdu(std::initializer_list bytes) { + std::vector msg = {nfc::NCI_PKT_MT_DATA, 0x00, static_cast(bytes.size())}; + msg.insert(msg.end(), bytes); + return msg; +} + +std::vector respond(FakePN71xx &nfcc, std::initializer_list bytes) { + std::vector response; + nfcc.card_emu_t4t_get_response_(apdu(bytes), response); + return response; +} + +void select_ndef_file(FakePN71xx &nfcc) { + respond(nfcc, {0x00, 0xA4, 0x04, 0x00, 0x07, 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01, 0x00}); + respond(nfcc, {0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x04}); +} + +const std::vector SW_OK = {0x90, 0x00}; +const std::vector SW_NOT_FOUND = {0x6A, 0x82}; + +} // namespace + +// A timed-out read must not cause the command to be sent again (NCI forbids a second command before the response). +TEST(PN71xxTransceive, ReadTimeoutDoesNotResend) { + FakePN71xx nfcc; + nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DEACTIVATE_OID, {0x00}); + nfc::NciMessage rx; + EXPECT_NE(nfcc.transceive_(tx, rx), nfc::STATUS_OK); + EXPECT_EQ(nfcc.written.size(), 1u); +} + +// A notification with the same GID/OID as the response (RF_DEACTIVATE_NTF) is not mistaken for it. +TEST(PN71xxTransceive, SkipsNotificationAheadOfResponse) { + FakePN71xx nfcc; + nfcc.to_read.push_back({0x61, 0x06, 0x02, 0x00, 0x00}); + nfcc.to_read.push_back({0x41, 0x06, 0x01, 0x00}); + nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DEACTIVATE_OID, {0x00}); + nfc::NciMessage rx; + EXPECT_EQ(nfcc.transceive_(tx, rx), nfc::STATUS_OK); + EXPECT_EQ(rx.get_message(), (std::vector{0x41, 0x06, 0x01, 0x00})); + EXPECT_EQ(nfcc.written.size(), 1u); +} + +TEST(PN71xxTransceive, NotificationAloneIsNotAResponse) { + FakePN71xx nfcc; + nfcc.to_read.push_back({0x61, 0x06, 0x02, 0x00, 0x00}); + nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DEACTIVATE_OID, {0x00}); + nfc::NciMessage rx; + EXPECT_NE(nfcc.transceive_(tx, rx), nfc::STATUS_OK); +} + +// A late response to an earlier, timed-out command must not be taken as the response to this one. +TEST(PN71xxTransceive, SkipsStaleResponseFromEarlierCommand) { + FakePN71xx nfcc; + nfcc.to_read.push_back({0x41, 0x06, 0x01, 0x00}); // RF_DEACTIVATE_RSP, arriving late + nfcc.to_read.push_back({0x41, 0x03, 0x01, 0x00}); // RF_DISCOVER_RSP + nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DISCOVER_OID, {0x00}); + nfc::NciMessage rx; + EXPECT_EQ(nfcc.transceive_(tx, rx), nfc::STATUS_OK); + EXPECT_EQ(rx.get_message(), (std::vector{0x41, 0x03, 0x01, 0x00})); + EXPECT_EQ(nfcc.written.size(), 1u); +} + +// A refused write (e.g. NFCC in standby) is sent again. +TEST(PN71xxTransceive, RefusedWriteIsRetried) { + FakePN71xx nfcc; + nfcc.write_failures = 1; + nfcc.to_read.push_back({0x41, 0x06, 0x01, 0x00}); + nfc::NciMessage tx(nfc::NCI_PKT_MT_CTRL_COMMAND, nfc::RF_GID, nfc::RF_DEACTIVATE_OID, {0x00}); + nfc::NciMessage rx; + EXPECT_EQ(nfcc.transceive_(tx, rx), nfc::STATUS_OK); + EXPECT_EQ(nfcc.written.size(), 1u); +} + +TEST(PN71xxCardEmulation, CcReadOutOfRangeIsRejected) { + FakePN71xx nfcc; + nfcc.set_tag_emulation_message("https://www.home-assistant.io/tag/test", false); + respond(nfcc, {0x00, 0xA4, 0x04, 0x00, 0x07, 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01, 0x00}); + respond(nfcc, {0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x03}); + // the CC file is 15 bytes; reading 17 or reading far past its end must not return memory beyond it + EXPECT_EQ(respond(nfcc, {0x00, 0xB0, 0x00, 0x00, 0x11}), SW_NOT_FOUND); + respond(nfcc, {0x00, 0xA4, 0x04, 0x00, 0x07, 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01, 0x00}); + respond(nfcc, {0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x03}); + EXPECT_EQ(respond(nfcc, {0x00, 0xB0, 0x01, 0x00, 0x0F}), SW_NOT_FOUND); +} + +TEST(PN71xxCardEmulation, CcReadInRange) { + FakePN71xx nfcc; + nfcc.set_tag_emulation_message("https://www.home-assistant.io/tag/test", false); + respond(nfcc, {0x00, 0xA4, 0x04, 0x00, 0x07, 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01, 0x00}); + respond(nfcc, {0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x03}); + auto response = respond(nfcc, {0x00, 0xB0, 0x00, 0x00, 0x0F}); + ASSERT_EQ(response.size(), sizeof(CARD_EMU_T4T_CC) + 2); + EXPECT_TRUE(std::equal(std::begin(CARD_EMU_T4T_CC), std::end(CARD_EMU_T4T_CC), response.begin())); +} + +// Reading the NDEF file in small chunks returns NLEN followed by the message, in order. +TEST(PN71xxCardEmulation, ChunkedNdefReadMatchesFile) { + FakePN71xx nfcc; + auto message = std::make_shared(); + message->add_uri_record("https://www.home-assistant.io/tag/0123456789abcdef"); + const auto encoded = message->encode(); + nfcc.set_tag_emulation_message(message); + select_ndef_file(nfcc); + + std::vector expected = {static_cast(encoded.size() >> 8), + static_cast(encoded.size() & 0xFF)}; + expected.insert(expected.end(), encoded.begin(), encoded.end()); + + std::vector file; + for (size_t offset = 0; offset < expected.size(); offset += 5) { + const uint8_t length = std::min(5, expected.size() - offset); + auto response = + respond(nfcc, {0x00, 0xB0, static_cast(offset >> 8), static_cast(offset), length}); + ASSERT_EQ(response.size(), length + 2u); + EXPECT_EQ(std::vector(response.end() - 2, response.end()), SW_OK); + file.insert(file.end(), response.begin(), response.end() - 2); + } + EXPECT_EQ(file, expected); +} + +TEST(PN71xxCardEmulation, NdefReadPastEndIsRejected) { + FakePN71xx nfcc; + nfcc.set_tag_emulation_message("https://www.home-assistant.io/tag/test", false); + select_ndef_file(nfcc); + EXPECT_EQ(respond(nfcc, {0x00, 0xB0, 0x00, 0x02, 0xFD}), SW_NOT_FOUND); +} + +TEST(PN71xxCardEmulation, TruncatedApdusAreRejected) { + FakePN71xx nfcc; + nfcc.set_tag_emulation_message("https://www.home-assistant.io/tag/test", false); + select_ndef_file(nfcc); + EXPECT_EQ(respond(nfcc, {0x00, 0xB0, 0x00}), SW_NOT_FOUND); + select_ndef_file(nfcc); + // UPDATE BINARY claiming 16 bytes of data but carrying only 2 + EXPECT_EQ(respond(nfcc, {0x00, 0xD6, 0x00, 0x00, 0x10, 0x00, 0x00}), SW_NOT_FOUND); +} + +// A message too large for the emulated NDEF file is refused when it is set, keeping the previous one. +TEST(PN71xxCardEmulation, OversizedMessageRejectedWhenSet) { + FakePN71xx nfcc; + nfcc.set_tag_emulation_message("https://www.home-assistant.io/tag/test", false); + nfcc.set_tag_emulation_message("https://www.home-assistant.io/tag/" + std::string(300, 'x'), false); + select_ndef_file(nfcc); + auto response = respond(nfcc, {0x00, 0xB0, 0x00, 0x00, 0x02}); + ASSERT_EQ(response.size(), 4u); + EXPECT_LT((response[0] << 8) | response[1], 0xFF - 2); + EXPECT_EQ(std::vector(response.end() - 2, response.end()), SW_OK); +} + +} // namespace esphome::pn71xx