mirror of
https://github.com/esphome/esphome.git
synced 2026-09-28 15:30:21 +00:00
[pn71xx] Fix NCI transport and card emulation bugs; share code between PN7150 and PN7160 (#19589)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
dea1200c35
commit
93ec8d9d95
@@ -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
|
||||
|
||||
@@ -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<uint8_t> &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());
|
||||
}
|
||||
|
||||
@@ -61,7 +61,8 @@ bool decode_mifare_classic_tlv(std::vector<uint8_t> &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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<typename... Ts> class PN7150IsWritingCondition final : public Condition<Ts...>, public Parented<PN7150> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return this->parent_->is_writing(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class EmulationOffAction final : public Action<Ts...>, public Parented<PN7150> {
|
||||
void play(const Ts &...x) override { this->parent_->set_tag_emulation_off(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class EmulationOnAction final : public Action<Ts...>, public Parented<PN7150> {
|
||||
void play(const Ts &...x) override { this->parent_->set_tag_emulation_on(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class PollingOffAction final : public Action<Ts...>, public Parented<PN7150> {
|
||||
void play(const Ts &...x) override { this->parent_->set_polling_off(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class PollingOnAction final : public Action<Ts...>, public Parented<PN7150> {
|
||||
void play(const Ts &...x) override { this->parent_->set_polling_on(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetCleanModeAction final : public Action<Ts...>, public Parented<PN7150> {
|
||||
void play(const Ts &...x) override { this->parent_->clean_mode(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetFormatModeAction final : public Action<Ts...>, public Parented<PN7150> {
|
||||
void play(const Ts &...x) override { this->parent_->format_mode(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetReadModeAction final : public Action<Ts...>, public Parented<PN7150> {
|
||||
void play(const Ts &...x) override { this->parent_->read_mode(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetEmulationMessageAction final : public Action<Ts...>, public Parented<PN7150> {
|
||||
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<typename... Ts> class SetWriteMessageAction final : public Action<Ts...>, public Parented<PN7150> {
|
||||
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<typename... Ts> class SetWriteModeAction final : public Action<Ts...>, public Parented<PN7150> {
|
||||
void play(const Ts &...x) override { this->parent_->write_mode(); }
|
||||
};
|
||||
|
||||
} // namespace esphome::pn7150
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 <functional>
|
||||
#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<nfc::NfcTag> 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<nfc::NdefMessage> message);
|
||||
void set_tag_emulation_message(const optional<std::string> &message, optional<bool> include_android_app_record);
|
||||
void set_tag_emulation_message(const char *message, bool include_android_app_record = true);
|
||||
void set_tag_emulation_off();
|
||||
void set_tag_emulation_on();
|
||||
bool tag_emulation_enabled() { return this->listening_enabled_; }
|
||||
|
||||
void set_polling_off();
|
||||
void set_polling_on();
|
||||
bool polling_enabled() { return this->polling_enabled_; }
|
||||
|
||||
void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); }
|
||||
void register_ontagremoved_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontagremoved_.push_back(trig); }
|
||||
|
||||
template<typename F> void add_on_emulated_tag_scan_callback(F &&callback) {
|
||||
this->on_emulated_tag_scan_callback_.add(std::forward<F>(callback));
|
||||
}
|
||||
|
||||
template<typename F> void add_on_finished_write_callback(F &&callback) {
|
||||
this->on_finished_write_callback_.add(std::forward<F>(callback));
|
||||
}
|
||||
|
||||
bool is_writing() { return this->next_task_ != EP_READ; };
|
||||
|
||||
void read_mode();
|
||||
void clean_mode();
|
||||
void format_mode();
|
||||
void write_mode();
|
||||
void set_tag_write_message(std::shared_ptr<nfc::NdefMessage> message);
|
||||
void set_tag_write_message(optional<std::string> message, optional<bool> include_android_app_record);
|
||||
|
||||
uint8_t set_test_mode(TestMode test_mode, const std::vector<uint8_t> &data, std::vector<uint8_t> &result);
|
||||
|
||||
protected:
|
||||
uint8_t reset_core_(bool reset_config, bool power);
|
||||
uint8_t init_core_();
|
||||
uint8_t send_init_config_();
|
||||
uint8_t send_core_config_();
|
||||
uint8_t refresh_core_config_();
|
||||
|
||||
uint8_t set_discover_map_();
|
||||
|
||||
uint8_t set_listen_mode_routing_();
|
||||
|
||||
uint8_t start_discovery_();
|
||||
uint8_t stop_discovery_();
|
||||
uint8_t deactivate_(uint8_t type, uint16_t timeout = NFCC_DEFAULT_TIMEOUT);
|
||||
|
||||
void select_endpoint_();
|
||||
|
||||
uint8_t read_endpoint_data_(nfc::NfcTag &tag);
|
||||
uint8_t clean_endpoint_(nfc::NfcTagUid &uid);
|
||||
uint8_t format_endpoint_(nfc::NfcTagUid &uid);
|
||||
uint8_t write_endpoint_(nfc::NfcTagUid &uid, std::shared_ptr<nfc::NdefMessage> &message);
|
||||
|
||||
std::unique_ptr<nfc::NfcTag> build_tag_(uint8_t mode_tech, const std::vector<uint8_t> &data);
|
||||
optional<size_t> find_tag_uid_(const 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<uint8_t> &response, std::vector<uint8_t> &ndef_response);
|
||||
|
||||
uint8_t transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, uint16_t timeout = NFCC_DEFAULT_TIMEOUT,
|
||||
bool expect_notification = true);
|
||||
virtual uint8_t read_nfcc(nfc::NciMessage &rx, uint16_t timeout) = 0;
|
||||
virtual uint8_t write_nfcc(nfc::NciMessage &tx) = 0;
|
||||
|
||||
uint8_t wait_for_irq_(uint16_t timeout = NFCC_DEFAULT_TIMEOUT, bool pin_state = true);
|
||||
|
||||
uint8_t read_mifare_classic_tag_(nfc::NfcTag &tag);
|
||||
uint8_t read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
|
||||
uint8_t write_mifare_classic_block_(uint8_t block_num, 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<nfc::NdefMessage> &message);
|
||||
uint8_t halt_mifare_classic_tag_();
|
||||
|
||||
uint8_t read_mifare_ultralight_tag_(nfc::NfcTag &tag);
|
||||
uint8_t read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data);
|
||||
bool is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6);
|
||||
uint16_t read_mifare_ultralight_capacity_();
|
||||
uint8_t find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
|
||||
uint8_t &message_start_index);
|
||||
uint8_t write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len);
|
||||
uint8_t write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr<nfc::NdefMessage> &message);
|
||||
uint8_t clean_mifare_ultralight_();
|
||||
|
||||
enum NfcTask : uint8_t {
|
||||
EP_READ = 0,
|
||||
EP_CLEAN,
|
||||
EP_FORMAT,
|
||||
EP_WRITE,
|
||||
} next_task_{EP_READ};
|
||||
|
||||
bool config_refresh_pending_{false};
|
||||
bool core_config_is_solo_{false};
|
||||
bool listening_enabled_{false};
|
||||
bool polling_enabled_{true};
|
||||
|
||||
uint8_t error_count_{0};
|
||||
uint8_t fail_count_{0};
|
||||
uint32_t last_nci_state_change_{0};
|
||||
uint8_t selecting_endpoint_{0};
|
||||
uint32_t tag_ttl_{250};
|
||||
|
||||
GPIOPin *irq_pin_{nullptr};
|
||||
GPIOPin *ven_pin_{nullptr};
|
||||
|
||||
CallbackManager<void()> on_emulated_tag_scan_callback_;
|
||||
CallbackManager<void()> on_finished_write_callback_;
|
||||
|
||||
std::vector<DiscoveredEndpoint> discovered_endpoint_;
|
||||
|
||||
CardEmulationState ce_state_{CardEmulationState::CARD_EMU_IDLE};
|
||||
NCIState nci_state_{NCIState::NFCC_RESET};
|
||||
NCIState nci_state_error_{NCIState::NONE};
|
||||
|
||||
std::shared_ptr<nfc::NdefMessage> card_emulation_message_;
|
||||
std::shared_ptr<nfc::NdefMessage> next_task_message_to_write_;
|
||||
|
||||
std::vector<nfc::NfcOnTagTrigger *> triggers_ontag_;
|
||||
std::vector<nfc::NfcOnTagTrigger *> triggers_ontagremoved_;
|
||||
uint8_t verify_reset(nfc::NciMessage &rx, bool reset_config) override;
|
||||
uint8_t process_init_response(nfc::NciMessage &rx) override;
|
||||
std::span<const uint8_t> pmu_config() const override { return PMU_CFG; }
|
||||
std::span<const uint8_t> listen_mode_routing_config() const override { return RF_LISTEN_MODE_ROUTING_CONFIG; }
|
||||
};
|
||||
|
||||
} // namespace esphome::pn7150
|
||||
|
||||
@@ -1,326 +0,0 @@
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#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<uint8_t> data;
|
||||
|
||||
if (this->read_mifare_classic_block_(current_block, data) == nfc::STATUS_OK) {
|
||||
if (!nfc::decode_mifare_classic_tlv(data, message_length, message_start_index)) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to read block %u", current_block);
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
uint32_t index = 0;
|
||||
uint32_t buffer_size = nfc::get_mifare_classic_buffer_size(message_length);
|
||||
std::vector<uint8_t> buffer;
|
||||
|
||||
while (index < buffer_size) {
|
||||
if (nfc::mifare_classic_is_first_block(current_block)) {
|
||||
if (this->auth_mifare_classic_block_(current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Block authentication failed for %u", current_block);
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
std::vector<uint8_t> block_data;
|
||||
if (this->read_mifare_classic_block_(current_block, block_data) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Error reading block %u", current_block);
|
||||
return nfc::STATUS_FAILED;
|
||||
} else {
|
||||
buffer.insert(buffer.end(), block_data.begin(), block_data.end());
|
||||
}
|
||||
|
||||
index += nfc::MIFARE_CLASSIC_BLOCK_SIZE;
|
||||
current_block++;
|
||||
|
||||
if (nfc::mifare_classic_is_trailer_block(current_block)) {
|
||||
current_block++;
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer.begin() + message_start_index < buffer.end()) {
|
||||
buffer.erase(buffer.begin(), buffer.begin() + message_start_index);
|
||||
} else {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
tag.set_ndef_message(make_unique<nfc::NdefMessage>(buffer));
|
||||
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7150::read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data) {
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_READ, block_num});
|
||||
|
||||
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<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLANK_BUFFER = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> 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<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> EMPTY_NDEF_MESSAGE = {
|
||||
0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLANK_BLOCK = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLOCK_1_DATA = {
|
||||
0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1};
|
||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLOCK_2_DATA = {
|
||||
0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1};
|
||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLOCK_3_TRAILER = {
|
||||
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> 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<nfc::NdefMessage> &message) {
|
||||
auto encoded = message->encode();
|
||||
|
||||
uint32_t message_length = encoded.size();
|
||||
uint32_t buffer_length = nfc::get_mifare_classic_buffer_size(message_length);
|
||||
|
||||
encoded.insert(encoded.begin(), 0x03);
|
||||
if (message_length < 255) {
|
||||
encoded.insert(encoded.begin() + 1, message_length);
|
||||
} else {
|
||||
encoded.insert(encoded.begin() + 1, 0xFF);
|
||||
encoded.insert(encoded.begin() + 2, (message_length >> 8) & 0xFF);
|
||||
encoded.insert(encoded.begin() + 3, message_length & 0xFF);
|
||||
}
|
||||
encoded.push_back(0xFE);
|
||||
|
||||
encoded.resize(buffer_length, 0);
|
||||
|
||||
uint32_t index = 0;
|
||||
uint8_t current_block = 4;
|
||||
|
||||
while (index < buffer_length) {
|
||||
if (nfc::mifare_classic_is_first_block(current_block)) {
|
||||
if (this->auth_mifare_classic_block_(current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
@@ -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<typename... Ts> class PN7160IsWritingCondition final : public Condition<Ts...>, public Parented<PN7160> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return this->parent_->is_writing(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class EmulationOffAction final : public Action<Ts...>, public Parented<PN7160> {
|
||||
void play(const Ts &...x) override { this->parent_->set_tag_emulation_off(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class EmulationOnAction final : public Action<Ts...>, public Parented<PN7160> {
|
||||
void play(const Ts &...x) override { this->parent_->set_tag_emulation_on(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class PollingOffAction final : public Action<Ts...>, public Parented<PN7160> {
|
||||
void play(const Ts &...x) override { this->parent_->set_polling_off(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class PollingOnAction final : public Action<Ts...>, public Parented<PN7160> {
|
||||
void play(const Ts &...x) override { this->parent_->set_polling_on(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetCleanModeAction final : public Action<Ts...>, public Parented<PN7160> {
|
||||
void play(const Ts &...x) override { this->parent_->clean_mode(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetFormatModeAction final : public Action<Ts...>, public Parented<PN7160> {
|
||||
void play(const Ts &...x) override { this->parent_->format_mode(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetReadModeAction final : public Action<Ts...>, public Parented<PN7160> {
|
||||
void play(const Ts &...x) override { this->parent_->read_mode(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetEmulationMessageAction final : public Action<Ts...>, public Parented<PN7160> {
|
||||
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<typename... Ts> class SetWriteMessageAction final : public Action<Ts...>, public Parented<PN7160> {
|
||||
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<typename... Ts> class SetWriteModeAction final : public Action<Ts...>, public Parented<PN7160> {
|
||||
void play(const Ts &...x) override { this->parent_->write_mode(); }
|
||||
};
|
||||
|
||||
} // namespace esphome::pn7160
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 <functional>
|
||||
#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<nfc::NfcTag> 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<nfc::NdefMessage> message);
|
||||
void set_tag_emulation_message(const optional<std::string> &message, optional<bool> include_android_app_record);
|
||||
void set_tag_emulation_message(const char *message, bool include_android_app_record = true);
|
||||
void set_tag_emulation_off();
|
||||
void set_tag_emulation_on();
|
||||
bool tag_emulation_enabled() { return this->listening_enabled_; }
|
||||
|
||||
void set_polling_off();
|
||||
void set_polling_on();
|
||||
bool polling_enabled() { return this->polling_enabled_; }
|
||||
|
||||
void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); }
|
||||
void register_ontagremoved_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontagremoved_.push_back(trig); }
|
||||
|
||||
template<typename F> void add_on_emulated_tag_scan_callback(F &&callback) {
|
||||
this->on_emulated_tag_scan_callback_.add(std::forward<F>(callback));
|
||||
}
|
||||
|
||||
template<typename F> void add_on_finished_write_callback(F &&callback) {
|
||||
this->on_finished_write_callback_.add(std::forward<F>(callback));
|
||||
}
|
||||
|
||||
bool is_writing() { return this->next_task_ != EP_READ; };
|
||||
|
||||
void read_mode();
|
||||
void clean_mode();
|
||||
void format_mode();
|
||||
void write_mode();
|
||||
void set_tag_write_message(std::shared_ptr<nfc::NdefMessage> message);
|
||||
void set_tag_write_message(optional<std::string> message, optional<bool> include_android_app_record);
|
||||
|
||||
uint8_t set_test_mode(TestMode test_mode, const std::vector<uint8_t> &data, std::vector<uint8_t> &result);
|
||||
|
||||
protected:
|
||||
uint8_t reset_core_(bool reset_config, bool power);
|
||||
uint8_t init_core_();
|
||||
uint8_t send_init_config_();
|
||||
uint8_t send_core_config_();
|
||||
uint8_t refresh_core_config_();
|
||||
|
||||
uint8_t set_discover_map_();
|
||||
|
||||
uint8_t set_listen_mode_routing_();
|
||||
|
||||
uint8_t start_discovery_();
|
||||
uint8_t stop_discovery_();
|
||||
uint8_t deactivate_(uint8_t type, uint16_t timeout = NFCC_DEFAULT_TIMEOUT);
|
||||
|
||||
void select_endpoint_();
|
||||
|
||||
uint8_t read_endpoint_data_(nfc::NfcTag &tag);
|
||||
uint8_t clean_endpoint_(nfc::NfcTagUid &uid);
|
||||
uint8_t format_endpoint_(nfc::NfcTagUid &uid);
|
||||
uint8_t write_endpoint_(nfc::NfcTagUid &uid, std::shared_ptr<nfc::NdefMessage> &message);
|
||||
|
||||
std::unique_ptr<nfc::NfcTag> build_tag_(uint8_t mode_tech, const std::vector<uint8_t> &data);
|
||||
optional<size_t> find_tag_uid_(const 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<uint8_t> &response, std::vector<uint8_t> &ndef_response);
|
||||
|
||||
uint8_t transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, uint16_t timeout = NFCC_DEFAULT_TIMEOUT,
|
||||
bool expect_notification = true);
|
||||
virtual uint8_t read_nfcc(nfc::NciMessage &rx, uint16_t timeout) = 0;
|
||||
virtual uint8_t write_nfcc(nfc::NciMessage &tx) = 0;
|
||||
|
||||
uint8_t wait_for_irq_(uint16_t timeout = NFCC_DEFAULT_TIMEOUT, bool pin_state = true);
|
||||
|
||||
uint8_t read_mifare_classic_tag_(nfc::NfcTag &tag);
|
||||
uint8_t read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
|
||||
uint8_t write_mifare_classic_block_(uint8_t block_num, 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<nfc::NdefMessage> &message);
|
||||
uint8_t halt_mifare_classic_tag_();
|
||||
|
||||
uint8_t read_mifare_ultralight_tag_(nfc::NfcTag &tag);
|
||||
uint8_t read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data);
|
||||
bool is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6);
|
||||
uint16_t read_mifare_ultralight_capacity_();
|
||||
uint8_t find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
|
||||
uint8_t &message_start_index);
|
||||
uint8_t write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len);
|
||||
uint8_t write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr<nfc::NdefMessage> &message);
|
||||
uint8_t clean_mifare_ultralight_();
|
||||
|
||||
enum NfcTask : uint8_t {
|
||||
EP_READ = 0,
|
||||
EP_CLEAN,
|
||||
EP_FORMAT,
|
||||
EP_WRITE,
|
||||
} next_task_{EP_READ};
|
||||
|
||||
bool config_refresh_pending_{false};
|
||||
bool core_config_is_solo_{false};
|
||||
bool listening_enabled_{false};
|
||||
bool polling_enabled_{true};
|
||||
|
||||
uint8_t error_count_{0};
|
||||
uint8_t fail_count_{0};
|
||||
uint32_t last_nci_state_change_{0};
|
||||
uint8_t selecting_endpoint_{0};
|
||||
uint32_t tag_ttl_{250};
|
||||
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<const uint8_t> pmu_config() const override { return PMU_CFG; }
|
||||
std::span<const uint8_t> 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<void()> on_emulated_tag_scan_callback_;
|
||||
CallbackManager<void()> on_finished_write_callback_;
|
||||
|
||||
std::vector<DiscoveredEndpoint> discovered_endpoint_;
|
||||
|
||||
CardEmulationState ce_state_{CardEmulationState::CARD_EMU_IDLE};
|
||||
NCIState nci_state_{NCIState::NFCC_RESET};
|
||||
NCIState nci_state_error_{NCIState::NONE};
|
||||
|
||||
std::shared_ptr<nfc::NdefMessage> card_emulation_message_;
|
||||
std::shared_ptr<nfc::NdefMessage> next_task_message_to_write_;
|
||||
|
||||
std::vector<nfc::NfcOnTagTrigger *> triggers_ontag_;
|
||||
std::vector<nfc::NfcOnTagTrigger *> triggers_ontagremoved_;
|
||||
};
|
||||
|
||||
} // namespace esphome::pn7160
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <memory>
|
||||
|
||||
#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<uint8_t> data;
|
||||
// pages 3 to 6 contain various info we are interested in -- do one read to grab it all
|
||||
if (this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE * nfc::MIFARE_ULTRALIGHT_READ_SIZE,
|
||||
data) != nfc::STATUS_OK) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
if (!this->is_mifare_ultralight_formatted_(data)) {
|
||||
ESP_LOGW(TAG, "Not NDEF formatted");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
uint8_t message_length;
|
||||
uint8_t message_start_index;
|
||||
if (this->find_mifare_ultralight_ndef_(data, message_length, message_start_index) != nfc::STATUS_OK) {
|
||||
ESP_LOGW(TAG, "Couldn't find NDEF message");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
ESP_LOGVV(TAG, "NDEF message length: %u, start: %u", message_length, message_start_index);
|
||||
|
||||
if (message_length == 0) {
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
// we already read pages 3-6 earlier -- pick up where we left off so we're not re-reading pages
|
||||
const uint8_t read_length = message_length + message_start_index > 12 ? message_length + message_start_index - 12 : 0;
|
||||
if (read_length) {
|
||||
if (read_mifare_ultralight_bytes_(nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE + 3, read_length, data) !=
|
||||
nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Error reading tag data");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
// we need to trim off page 3 as well as any bytes ahead of message_start_index
|
||||
data.erase(data.begin(), data.begin() + message_start_index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE);
|
||||
|
||||
tag.set_ndef_message(make_unique<nfc::NdefMessage>(data));
|
||||
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7160::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data) {
|
||||
const uint8_t read_increment = nfc::MIFARE_ULTRALIGHT_READ_SIZE * nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {nfc::MIFARE_CMD_READ, start_page});
|
||||
|
||||
for (size_t i = 0; i * read_increment < num_bytes; i++) {
|
||||
tx.get_message().back() = i * nfc::MIFARE_ULTRALIGHT_READ_SIZE + start_page;
|
||||
do { // loop because sometimes we struggle here...???...
|
||||
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Error reading tag data");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
} while (rx.get_payload_size() < read_increment);
|
||||
uint16_t bytes_offset = (i + 1) * read_increment;
|
||||
auto pages_in_end_itr = bytes_offset <= num_bytes ? rx.get_message().end() - 1
|
||||
: rx.get_message().end() - (bytes_offset - num_bytes + 1);
|
||||
|
||||
if ((pages_in_end_itr > rx.get_message().begin()) && (pages_in_end_itr < rx.get_message().end())) {
|
||||
data.insert(data.end(), rx.get_message().begin() + nfc::NCI_PKT_HEADER_SIZE, pages_in_end_itr);
|
||||
}
|
||||
}
|
||||
|
||||
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<uint8_t> &page_3_to_6) {
|
||||
const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector
|
||||
|
||||
return (page_3_to_6.size() > p4_offset + 3) &&
|
||||
((page_3_to_6[p4_offset + 0] != 0xFF) || (page_3_to_6[p4_offset + 1] != 0xFF) ||
|
||||
(page_3_to_6[p4_offset + 2] != 0xFF) || (page_3_to_6[p4_offset + 3] != 0xFF));
|
||||
}
|
||||
|
||||
uint16_t PN7160::read_mifare_ultralight_capacity_() {
|
||||
std::vector<uint8_t> data;
|
||||
if (this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE, data) == nfc::STATUS_OK) {
|
||||
ESP_LOGV(TAG, "Tag capacity is %u bytes", data[2] * 8U);
|
||||
return data[2] * 8U;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t PN7160::find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
|
||||
uint8_t &message_start_index) {
|
||||
const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector
|
||||
|
||||
if (!(page_3_to_6.size() > p4_offset + 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<nfc::NdefMessage> &message) {
|
||||
uint32_t capacity = this->read_mifare_ultralight_capacity_();
|
||||
|
||||
auto encoded = message->encode();
|
||||
|
||||
uint32_t message_length = encoded.size();
|
||||
uint32_t buffer_length = nfc::get_mifare_ultralight_buffer_size(message_length);
|
||||
|
||||
if (buffer_length > capacity) {
|
||||
ESP_LOGE(TAG, "Message length exceeds tag capacity %" PRIu32 " > %" PRIu32, buffer_length, capacity);
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
encoded.insert(encoded.begin(), 0x03);
|
||||
if (message_length < 255) {
|
||||
encoded.insert(encoded.begin() + 1, message_length);
|
||||
} else {
|
||||
encoded.insert(encoded.begin() + 1, 0xFF);
|
||||
encoded.insert(encoded.begin() + 2, (message_length >> 8) & 0xFF);
|
||||
encoded.insert(encoded.begin() + 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<uint8_t, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE> 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<uint8_t> 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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 ``<chip>.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)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 <functional>
|
||||
#include <span>
|
||||
|
||||
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<nfc::NfcTag> 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<nfc::NdefMessage> &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<typename F> void add_on_emulated_tag_scan_callback(F &&callback) {
|
||||
this->on_emulated_tag_scan_callback_.add(std::forward<F>(callback));
|
||||
}
|
||||
|
||||
template<typename F> void add_on_finished_write_callback(F &&callback) {
|
||||
this->on_finished_write_callback_.add(std::forward<F>(callback));
|
||||
}
|
||||
|
||||
bool is_writing() { return this->next_task_ != EP_READ; };
|
||||
|
||||
void read_mode();
|
||||
void clean_mode();
|
||||
void format_mode();
|
||||
void write_mode();
|
||||
void set_tag_write_message(std::shared_ptr<nfc::NdefMessage> message);
|
||||
void set_tag_write_message(const std::string &message, bool include_android_app_record = true);
|
||||
|
||||
uint8_t set_test_mode(TestMode test_mode, const std::vector<uint8_t> &data, std::vector<uint8_t> &result);
|
||||
|
||||
protected:
|
||||
uint8_t reset_core_(bool reset_config, bool power);
|
||||
uint8_t init_core_();
|
||||
|
||||
/// 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<const uint8_t> pmu_config() const = 0;
|
||||
/// Payload of the RF_SET_LISTEN_MODE_ROUTING_CMD
|
||||
virtual std::span<const uint8_t> 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<nfc::NdefMessage> &message);
|
||||
|
||||
std::unique_ptr<nfc::NfcTag> build_tag_(uint8_t mode_tech, uint8_t protocol, const std::vector<uint8_t> &data);
|
||||
optional<size_t> 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<uint8_t> &response, std::vector<uint8_t> &ndef_response);
|
||||
bool card_emu_t4t_read_ndef_(uint16_t offset, uint8_t length, std::vector<uint8_t> &ndef_response);
|
||||
|
||||
uint8_t transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, uint16_t timeout = NFCC_DEFAULT_TIMEOUT,
|
||||
bool expect_notification = true);
|
||||
virtual uint8_t read_nfcc(nfc::NciMessage &rx, uint16_t timeout) = 0;
|
||||
virtual uint8_t write_nfcc(nfc::NciMessage &tx) = 0;
|
||||
|
||||
uint8_t wait_for_irq_(uint16_t timeout = NFCC_DEFAULT_TIMEOUT, bool pin_state = true);
|
||||
|
||||
uint8_t read_mifare_classic_tag_(nfc::NfcTag &tag);
|
||||
uint8_t read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
|
||||
uint8_t write_mifare_classic_block_(uint8_t block_num, 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<nfc::NdefMessage> &message);
|
||||
uint8_t halt_mifare_classic_tag_();
|
||||
|
||||
uint8_t read_mifare_ultralight_tag_(nfc::NfcTag &tag);
|
||||
uint8_t read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data);
|
||||
bool is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6);
|
||||
uint16_t read_mifare_ultralight_capacity_();
|
||||
uint8_t find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
|
||||
uint8_t &message_start_index);
|
||||
uint8_t write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len);
|
||||
uint8_t write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr<nfc::NdefMessage> &message);
|
||||
uint8_t clean_mifare_ultralight_();
|
||||
|
||||
enum NfcTask : uint8_t {
|
||||
EP_READ = 0,
|
||||
EP_CLEAN,
|
||||
EP_FORMAT,
|
||||
EP_WRITE,
|
||||
};
|
||||
|
||||
// members are ordered by alignment, widest first, to minimize padding
|
||||
CallbackManager<void()> on_emulated_tag_scan_callback_;
|
||||
CallbackManager<void()> on_finished_write_callback_;
|
||||
|
||||
std::vector<DiscoveredEndpoint> discovered_endpoint_;
|
||||
std::vector<uint8_t> card_emulation_ndef_; // encoded emulation message; empty when none is set
|
||||
std::vector<nfc::NfcOnTagTrigger *> triggers_ontag_;
|
||||
std::vector<nfc::NfcOnTagTrigger *> triggers_ontagremoved_;
|
||||
std::shared_ptr<nfc::NdefMessage> 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
|
||||
+13
-13
@@ -1,14 +1,14 @@
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#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<uint8_t> &data) {
|
||||
uint8_t PN71xx::read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data) {
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_READ, block_num});
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
@@ -90,7 +90,7 @@ uint8_t PN7160::read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7160::auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, const uint8_t *key) {
|
||||
uint8_t PN71xx::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});
|
||||
|
||||
@@ -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<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLANK_BUFFER = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> 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<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> EMPTY_NDEF_MESSAGE = {
|
||||
0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> 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<nfc::NdefMessage> &message) {
|
||||
uint8_t PN71xx::write_mifare_classic_tag_(const std::shared_ptr<nfc::NdefMessage> &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<nfc::NdefMessage
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
uint8_t PN7160::halt_mifare_classic_tag_() {
|
||||
uint8_t PN71xx::halt_mifare_classic_tag_() {
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_HALT, 0});
|
||||
|
||||
@@ -323,4 +323,4 @@ uint8_t PN7160::halt_mifare_classic_tag_() {
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
} // namespace esphome::pn7160
|
||||
} // namespace esphome::pn71xx
|
||||
+19
-13
@@ -2,14 +2,14 @@
|
||||
#include <cinttypes>
|
||||
#include <memory>
|
||||
|
||||
#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<uint8_t> data;
|
||||
// pages 3 to 6 contain various info we are interested in -- do one read to grab it all
|
||||
if (this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE * nfc::MIFARE_ULTRALIGHT_READ_SIZE,
|
||||
@@ -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<uint8_t> &data) {
|
||||
uint8_t PN71xx::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data) {
|
||||
const uint8_t read_increment = nfc::MIFARE_ULTRALIGHT_READ_SIZE * nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {nfc::MIFARE_CMD_READ, start_page});
|
||||
|
||||
for (size_t i = 0; i * read_increment < num_bytes; i++) {
|
||||
tx.get_message().back() = i * nfc::MIFARE_ULTRALIGHT_READ_SIZE + start_page;
|
||||
do { // loop because sometimes we struggle here...???...
|
||||
// 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<uint8_t> &page_3_to_6) {
|
||||
bool PN71xx::is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6) {
|
||||
const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector
|
||||
|
||||
return (page_3_to_6.size() > p4_offset + 3) &&
|
||||
@@ -86,7 +92,7 @@ bool PN7150::is_mifare_ultralight_formatted_(const std::vector<uint8_t> &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<uint8_t> data;
|
||||
if (this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE, data) == nfc::STATUS_OK) {
|
||||
ESP_LOGV(TAG, "Tag capacity is %u bytes", data[2] * 8U);
|
||||
@@ -95,7 +101,7 @@ uint16_t PN7150::read_mifare_ultralight_capacity_() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t PN7150::find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
|
||||
uint8_t PN71xx::find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
|
||||
uint8_t &message_start_index) {
|
||||
const uint8_t p4_offset = nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; // page 4 will begin 4 bytes into the vector
|
||||
|
||||
@@ -115,7 +121,7 @@ uint8_t PN7150::find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
uint8_t PN7150::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr<nfc::NdefMessage> &message) {
|
||||
uint8_t PN71xx::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr<nfc::NdefMessage> &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<uint8_t> 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
|
||||
@@ -0,0 +1,194 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <deque>
|
||||
|
||||
#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<std::vector<uint8_t>> to_read;
|
||||
std::vector<std::vector<uint8_t>> 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<const uint8_t> pmu_config() const override { return {}; }
|
||||
std::span<const uint8_t> 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<uint8_t> apdu(std::initializer_list<uint8_t> bytes) {
|
||||
std::vector<uint8_t> msg = {nfc::NCI_PKT_MT_DATA, 0x00, static_cast<uint8_t>(bytes.size())};
|
||||
msg.insert(msg.end(), bytes);
|
||||
return msg;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> respond(FakePN71xx &nfcc, std::initializer_list<uint8_t> bytes) {
|
||||
std::vector<uint8_t> 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<uint8_t> SW_OK = {0x90, 0x00};
|
||||
const std::vector<uint8_t> 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<uint8_t>{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<uint8_t>{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<nfc::NdefMessage>();
|
||||
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<uint8_t> expected = {static_cast<uint8_t>(encoded.size() >> 8),
|
||||
static_cast<uint8_t>(encoded.size() & 0xFF)};
|
||||
expected.insert(expected.end(), encoded.begin(), encoded.end());
|
||||
|
||||
std::vector<uint8_t> file;
|
||||
for (size_t offset = 0; offset < expected.size(); offset += 5) {
|
||||
const uint8_t length = std::min<size_t>(5, expected.size() - offset);
|
||||
auto response =
|
||||
respond(nfcc, {0x00, 0xB0, static_cast<uint8_t>(offset >> 8), static_cast<uint8_t>(offset), length});
|
||||
ASSERT_EQ(response.size(), length + 2u);
|
||||
EXPECT_EQ(std::vector<uint8_t>(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<uint8_t>(response.end() - 2, response.end()), SW_OK);
|
||||
}
|
||||
|
||||
} // namespace esphome::pn71xx
|
||||
Reference in New Issue
Block a user