mirror of
https://github.com/esphome/esphome.git
synced 2024-11-25 08:28:12 +01:00
Add support for PN7160 (#5486)
This commit is contained in:
parent
81aa48a5f3
commit
6fd239362d
20 changed files with 2964 additions and 1 deletions
|
@ -240,6 +240,9 @@ esphome/components/pmwcs3/* @SeByDocKy
|
||||||
esphome/components/pn532/* @OttoWinter @jesserockz
|
esphome/components/pn532/* @OttoWinter @jesserockz
|
||||||
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
|
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
|
||||||
esphome/components/pn532_spi/* @OttoWinter @jesserockz
|
esphome/components/pn532_spi/* @OttoWinter @jesserockz
|
||||||
|
esphome/components/pn7160/* @jesserockz @kbx81
|
||||||
|
esphome/components/pn7160_i2c/* @jesserockz @kbx81
|
||||||
|
esphome/components/pn7160_spi/* @jesserockz @kbx81
|
||||||
esphome/components/power_supply/* @esphome/core
|
esphome/components/power_supply/* @esphome/core
|
||||||
esphome/components/preferences/* @esphome/core
|
esphome/components/preferences/* @esphome/core
|
||||||
esphome/components/psram/* @esphome/core
|
esphome/components/psram/* @esphome/core
|
||||||
|
|
144
esphome/components/nfc/nci_core.h
Normal file
144
esphome/components/nfc/nci_core.h
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace nfc {
|
||||||
|
|
||||||
|
// Header info
|
||||||
|
static const uint8_t NCI_PKT_HEADER_SIZE = 3; // NCI packet (pkt) headers are always three bytes
|
||||||
|
static const uint8_t NCI_PKT_MT_GID_OFFSET = 0; // NCI packet (pkt) MT and GID offsets
|
||||||
|
static const uint8_t NCI_PKT_OID_OFFSET = 1; // NCI packet (pkt) OID offset
|
||||||
|
static const uint8_t NCI_PKT_LENGTH_OFFSET = 2; // NCI packet (pkt) message length (size) offset
|
||||||
|
static const uint8_t NCI_PKT_PAYLOAD_OFFSET = 3; // NCI packet (pkt) payload offset
|
||||||
|
// Important masks
|
||||||
|
static const uint8_t NCI_PKT_MT_MASK = 0xE0; // NCI packet (pkt) message type mask
|
||||||
|
static const uint8_t NCI_PKT_PBF_MASK = 0x10; // packet boundary flag bit
|
||||||
|
static const uint8_t NCI_PKT_GID_MASK = 0x0F;
|
||||||
|
static const uint8_t NCI_PKT_OID_MASK = 0x3F;
|
||||||
|
// Message types
|
||||||
|
static const uint8_t NCI_PKT_MT_DATA = 0x00; // For sending commands to NFC endpoint (card/tag)
|
||||||
|
static const uint8_t NCI_PKT_MT_CTRL_COMMAND = 0x20; // For sending commands to NFCC
|
||||||
|
static const uint8_t NCI_PKT_MT_CTRL_RESPONSE = 0x40; // Response from NFCC to commands
|
||||||
|
static const uint8_t NCI_PKT_MT_CTRL_NOTIFICATION = 0x60; // Notification from NFCC
|
||||||
|
// GIDs
|
||||||
|
static const uint8_t NCI_CORE_GID = 0x0;
|
||||||
|
static const uint8_t RF_GID = 0x1;
|
||||||
|
static const uint8_t NFCEE_GID = 0x1;
|
||||||
|
static const uint8_t NCI_PROPRIETARY_GID = 0xF;
|
||||||
|
// OIDs
|
||||||
|
static const uint8_t NCI_CORE_RESET_OID = 0x00;
|
||||||
|
static const uint8_t NCI_CORE_INIT_OID = 0x01;
|
||||||
|
static const uint8_t NCI_CORE_SET_CONFIG_OID = 0x02;
|
||||||
|
static const uint8_t NCI_CORE_GET_CONFIG_OID = 0x03;
|
||||||
|
static const uint8_t NCI_CORE_CONN_CREATE_OID = 0x04;
|
||||||
|
static const uint8_t NCI_CORE_CONN_CLOSE_OID = 0x05;
|
||||||
|
static const uint8_t NCI_CORE_CONN_CREDITS_OID = 0x06;
|
||||||
|
static const uint8_t NCI_CORE_GENERIC_ERROR_OID = 0x07;
|
||||||
|
static const uint8_t NCI_CORE_INTERFACE_ERROR_OID = 0x08;
|
||||||
|
|
||||||
|
static const uint8_t RF_DISCOVER_MAP_OID = 0x00;
|
||||||
|
static const uint8_t RF_SET_LISTEN_MODE_ROUTING_OID = 0x01;
|
||||||
|
static const uint8_t RF_GET_LISTEN_MODE_ROUTING_OID = 0x02;
|
||||||
|
static const uint8_t RF_DISCOVER_OID = 0x03;
|
||||||
|
static const uint8_t RF_DISCOVER_SELECT_OID = 0x04;
|
||||||
|
static const uint8_t RF_INTF_ACTIVATED_OID = 0x05;
|
||||||
|
static const uint8_t RF_DEACTIVATE_OID = 0x06;
|
||||||
|
static const uint8_t RF_FIELD_INFO_OID = 0x07;
|
||||||
|
static const uint8_t RF_T3T_POLLING_OID = 0x08;
|
||||||
|
static const uint8_t RF_NFCEE_ACTION_OID = 0x09;
|
||||||
|
static const uint8_t RF_NFCEE_DISCOVERY_REQ_OID = 0x0A;
|
||||||
|
static const uint8_t RF_PARAMETER_UPDATE_OID = 0x0B;
|
||||||
|
|
||||||
|
static const uint8_t NFCEE_DISCOVER_OID = 0x00;
|
||||||
|
static const uint8_t NFCEE_MODE_SET_OID = 0x01;
|
||||||
|
// Interfaces
|
||||||
|
static const uint8_t INTF_NFCEE_DIRECT = 0x00;
|
||||||
|
static const uint8_t INTF_FRAME = 0x01;
|
||||||
|
static const uint8_t INTF_ISODEP = 0x02;
|
||||||
|
static const uint8_t INTF_NFCDEP = 0x03;
|
||||||
|
static const uint8_t INTF_TAGCMD = 0x80; // NXP proprietary
|
||||||
|
// Bit rates
|
||||||
|
static const uint8_t NFC_BIT_RATE_106 = 0x00;
|
||||||
|
static const uint8_t NFC_BIT_RATE_212 = 0x01;
|
||||||
|
static const uint8_t NFC_BIT_RATE_424 = 0x02;
|
||||||
|
static const uint8_t NFC_BIT_RATE_848 = 0x03;
|
||||||
|
static const uint8_t NFC_BIT_RATE_1695 = 0x04;
|
||||||
|
static const uint8_t NFC_BIT_RATE_3390 = 0x05;
|
||||||
|
static const uint8_t NFC_BIT_RATE_6780 = 0x06;
|
||||||
|
// Protocols
|
||||||
|
static const uint8_t PROT_UNDETERMINED = 0x00;
|
||||||
|
static const uint8_t PROT_T1T = 0x01;
|
||||||
|
static const uint8_t PROT_T2T = 0x02;
|
||||||
|
static const uint8_t PROT_T3T = 0x03;
|
||||||
|
static const uint8_t PROT_ISODEP = 0x04;
|
||||||
|
static const uint8_t PROT_NFCDEP = 0x05;
|
||||||
|
static const uint8_t PROT_T5T = 0x06;
|
||||||
|
static const uint8_t PROT_MIFARE = 0x80;
|
||||||
|
// RF Technologies
|
||||||
|
static const uint8_t NFC_RF_TECH_A = 0x00;
|
||||||
|
static const uint8_t NFC_RF_TECH_B = 0x01;
|
||||||
|
static const uint8_t NFC_RF_TECH_F = 0x02;
|
||||||
|
static const uint8_t NFC_RF_TECH_15693 = 0x03;
|
||||||
|
// RF Technology & Modes
|
||||||
|
static const uint8_t MODE_MASK = 0xF0;
|
||||||
|
static const uint8_t MODE_LISTEN_MASK = 0x80;
|
||||||
|
static const uint8_t MODE_POLL = 0x00;
|
||||||
|
|
||||||
|
static const uint8_t TECH_PASSIVE_NFCA = 0x00;
|
||||||
|
static const uint8_t TECH_PASSIVE_NFCB = 0x01;
|
||||||
|
static const uint8_t TECH_PASSIVE_NFCF = 0x02;
|
||||||
|
static const uint8_t TECH_ACTIVE_NFCA = 0x03;
|
||||||
|
static const uint8_t TECH_ACTIVE_NFCF = 0x05;
|
||||||
|
static const uint8_t TECH_PASSIVE_15693 = 0x06;
|
||||||
|
// Status codes
|
||||||
|
static const uint8_t STATUS_OK = 0x00;
|
||||||
|
static const uint8_t STATUS_REJECTED = 0x01;
|
||||||
|
static const uint8_t STATUS_RF_FRAME_CORRUPTED = 0x02;
|
||||||
|
static const uint8_t STATUS_FAILED = 0x03;
|
||||||
|
static const uint8_t STATUS_NOT_INITIALIZED = 0x04;
|
||||||
|
static const uint8_t STATUS_SYNTAX_ERROR = 0x05;
|
||||||
|
static const uint8_t STATUS_SEMANTIC_ERROR = 0x06;
|
||||||
|
static const uint8_t STATUS_INVALID_PARAM = 0x09;
|
||||||
|
static const uint8_t STATUS_MESSAGE_SIZE_EXCEEDED = 0x0A;
|
||||||
|
static const uint8_t DISCOVERY_ALREADY_STARTED = 0xA0;
|
||||||
|
static const uint8_t DISCOVERY_TARGET_ACTIVATION_FAILED = 0xA1;
|
||||||
|
static const uint8_t DISCOVERY_TEAR_DOWN = 0xA2;
|
||||||
|
static const uint8_t RF_TRANSMISSION_ERROR = 0xB0;
|
||||||
|
static const uint8_t RF_PROTOCOL_ERROR = 0xB1;
|
||||||
|
static const uint8_t RF_TIMEOUT_ERROR = 0xB2;
|
||||||
|
static const uint8_t NFCEE_INTERFACE_ACTIVATION_FAILED = 0xC0;
|
||||||
|
static const uint8_t NFCEE_TRANSMISSION_ERROR = 0xC1;
|
||||||
|
static const uint8_t NFCEE_PROTOCOL_ERROR = 0xC2;
|
||||||
|
static const uint8_t NFCEE_TIMEOUT_ERROR = 0xC3;
|
||||||
|
// Deactivation types/reasons
|
||||||
|
static const uint8_t DEACTIVATION_TYPE_IDLE = 0x00;
|
||||||
|
static const uint8_t DEACTIVATION_TYPE_SLEEP = 0x01;
|
||||||
|
static const uint8_t DEACTIVATION_TYPE_SLEEP_AF = 0x02;
|
||||||
|
static const uint8_t DEACTIVATION_TYPE_DISCOVERY = 0x03;
|
||||||
|
// RF discover map modes
|
||||||
|
static const uint8_t RF_DISCOVER_MAP_MODE_POLL = 0x1;
|
||||||
|
static const uint8_t RF_DISCOVER_MAP_MODE_LISTEN = 0x2;
|
||||||
|
// RF discover notification types
|
||||||
|
static const uint8_t RF_DISCOVER_NTF_NT_LAST = 0x00;
|
||||||
|
static const uint8_t RF_DISCOVER_NTF_NT_LAST_RL = 0x01;
|
||||||
|
static const uint8_t RF_DISCOVER_NTF_NT_MORE = 0x02;
|
||||||
|
// Important message offsets
|
||||||
|
static const uint8_t RF_DISCOVER_NTF_DISCOVERY_ID = 0 + NCI_PKT_HEADER_SIZE;
|
||||||
|
static const uint8_t RF_DISCOVER_NTF_PROTOCOL = 1 + NCI_PKT_HEADER_SIZE;
|
||||||
|
static const uint8_t RF_DISCOVER_NTF_MODE_TECH = 2 + NCI_PKT_HEADER_SIZE;
|
||||||
|
static const uint8_t RF_DISCOVER_NTF_RF_TECH_LENGTH = 3 + NCI_PKT_HEADER_SIZE;
|
||||||
|
static const uint8_t RF_DISCOVER_NTF_RF_TECH_PARAMS = 4 + NCI_PKT_HEADER_SIZE;
|
||||||
|
static const uint8_t RF_INTF_ACTIVATED_NTF_DISCOVERY_ID = 0 + NCI_PKT_HEADER_SIZE;
|
||||||
|
static const uint8_t RF_INTF_ACTIVATED_NTF_INTERFACE = 1 + NCI_PKT_HEADER_SIZE;
|
||||||
|
static const uint8_t RF_INTF_ACTIVATED_NTF_PROTOCOL = 2 + NCI_PKT_HEADER_SIZE;
|
||||||
|
static const uint8_t RF_INTF_ACTIVATED_NTF_MODE_TECH = 3 + NCI_PKT_HEADER_SIZE;
|
||||||
|
static const uint8_t RF_INTF_ACTIVATED_NTF_MAX_SIZE = 4 + NCI_PKT_HEADER_SIZE;
|
||||||
|
static const uint8_t RF_INTF_ACTIVATED_NTF_INIT_CRED = 5 + NCI_PKT_HEADER_SIZE;
|
||||||
|
static const uint8_t RF_INTF_ACTIVATED_NTF_RF_TECH_LENGTH = 6 + NCI_PKT_HEADER_SIZE;
|
||||||
|
static const uint8_t RF_INTF_ACTIVATED_NTF_RF_TECH_PARAMS = 7 + NCI_PKT_HEADER_SIZE;
|
||||||
|
|
||||||
|
} // namespace nfc
|
||||||
|
} // namespace esphome
|
166
esphome/components/nfc/nci_message.cpp
Normal file
166
esphome/components/nfc/nci_message.cpp
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
#include "nci_core.h"
|
||||||
|
#include "nci_message.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace nfc {
|
||||||
|
|
||||||
|
static const char *const TAG = "NciMessage";
|
||||||
|
|
||||||
|
NciMessage::NciMessage(const uint8_t message_type, const std::vector<uint8_t> &payload) {
|
||||||
|
this->set_message(message_type, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
NciMessage::NciMessage(const uint8_t message_type, const uint8_t gid, const uint8_t oid) {
|
||||||
|
this->set_header(message_type, gid, oid);
|
||||||
|
}
|
||||||
|
|
||||||
|
NciMessage::NciMessage(const uint8_t message_type, const uint8_t gid, const uint8_t oid,
|
||||||
|
const std::vector<uint8_t> &payload) {
|
||||||
|
this->set_message(message_type, gid, oid, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
NciMessage::NciMessage(const std::vector<uint8_t> &raw_packet) { this->nci_message_ = raw_packet; };
|
||||||
|
|
||||||
|
std::vector<uint8_t> NciMessage::encode() {
|
||||||
|
this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - nfc::NCI_PKT_HEADER_SIZE;
|
||||||
|
std::vector<uint8_t> message = this->nci_message_;
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NciMessage::reset() { this->nci_message_ = {0, 0, 0}; }
|
||||||
|
|
||||||
|
uint8_t NciMessage::get_message_type() const {
|
||||||
|
return this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & nfc::NCI_PKT_MT_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t NciMessage::get_gid() const { return this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & nfc::NCI_PKT_GID_MASK; }
|
||||||
|
|
||||||
|
uint8_t NciMessage::get_oid() const { return this->nci_message_[nfc::NCI_PKT_OID_OFFSET] & nfc::NCI_PKT_OID_MASK; }
|
||||||
|
|
||||||
|
uint8_t NciMessage::get_payload_size(const bool recompute) {
|
||||||
|
if (!this->nci_message_.empty()) {
|
||||||
|
if (recompute) {
|
||||||
|
this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - nfc::NCI_PKT_HEADER_SIZE;
|
||||||
|
}
|
||||||
|
return this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET];
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t NciMessage::get_simple_status_response() const {
|
||||||
|
if (this->nci_message_.size() > nfc::NCI_PKT_PAYLOAD_OFFSET) {
|
||||||
|
return this->nci_message_[nfc::NCI_PKT_PAYLOAD_OFFSET];
|
||||||
|
}
|
||||||
|
return STATUS_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t NciMessage::get_message_byte(const uint8_t offset) const {
|
||||||
|
if (this->nci_message_.size() > offset) {
|
||||||
|
return this->nci_message_[offset];
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> &NciMessage::get_message() { return this->nci_message_; }
|
||||||
|
|
||||||
|
bool NciMessage::has_payload() const { return this->nci_message_.size() > nfc::NCI_PKT_HEADER_SIZE; }
|
||||||
|
|
||||||
|
bool NciMessage::message_type_is(const uint8_t message_type) const {
|
||||||
|
if (!this->nci_message_.empty()) {
|
||||||
|
return message_type == (this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & nfc::NCI_PKT_MT_MASK);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NciMessage::message_length_is(const uint8_t message_length, const bool recompute) {
|
||||||
|
if (this->nci_message_.size() > nfc::NCI_PKT_LENGTH_OFFSET) {
|
||||||
|
if (recompute) {
|
||||||
|
this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = this->nci_message_.size() - nfc::NCI_PKT_HEADER_SIZE;
|
||||||
|
}
|
||||||
|
return message_length == this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET];
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NciMessage::gid_is(const uint8_t gid) const {
|
||||||
|
if (this->nci_message_.size() > nfc::NCI_PKT_MT_GID_OFFSET) {
|
||||||
|
return gid == (this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & nfc::NCI_PKT_GID_MASK);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NciMessage::oid_is(const uint8_t oid) const {
|
||||||
|
if (this->nci_message_.size() > nfc::NCI_PKT_OID_OFFSET) {
|
||||||
|
return oid == (this->nci_message_[nfc::NCI_PKT_OID_OFFSET] & nfc::NCI_PKT_OID_MASK);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NciMessage::simple_status_response_is(const uint8_t response) const {
|
||||||
|
if (this->nci_message_.size() > nfc::NCI_PKT_PAYLOAD_OFFSET) {
|
||||||
|
return response == this->nci_message_[nfc::NCI_PKT_PAYLOAD_OFFSET];
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NciMessage::set_header(const uint8_t message_type, const uint8_t gid, const uint8_t oid) {
|
||||||
|
if (this->nci_message_.size() < nfc::NCI_PKT_HEADER_SIZE) {
|
||||||
|
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) | (gid & nfc::NCI_PKT_GID_MASK);
|
||||||
|
this->nci_message_[nfc::NCI_PKT_OID_OFFSET] = oid & nfc::NCI_PKT_OID_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_LENGTH_OFFSET] = payload.size();
|
||||||
|
this->nci_message_.insert(this->nci_message_.end(), payload.begin(), payload.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
void NciMessage::set_message(const uint8_t message_type, const uint8_t gid, const uint8_t oid,
|
||||||
|
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) | (gid & nfc::NCI_PKT_GID_MASK);
|
||||||
|
this->nci_message_[nfc::NCI_PKT_OID_OFFSET] = oid & nfc::NCI_PKT_OID_MASK;
|
||||||
|
this->nci_message_[nfc::NCI_PKT_LENGTH_OFFSET] = payload.size();
|
||||||
|
this->nci_message_.insert(this->nci_message_.end(), payload.begin(), payload.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
void NciMessage::set_message_type(const uint8_t message_type) {
|
||||||
|
if (this->nci_message_.size() < nfc::NCI_PKT_HEADER_SIZE) {
|
||||||
|
this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE);
|
||||||
|
}
|
||||||
|
auto mt_masked = this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & ~nfc::NCI_PKT_MT_MASK;
|
||||||
|
this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] = mt_masked | (message_type & nfc::NCI_PKT_MT_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NciMessage::set_gid(const uint8_t gid) {
|
||||||
|
if (this->nci_message_.size() < nfc::NCI_PKT_HEADER_SIZE) {
|
||||||
|
this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE);
|
||||||
|
}
|
||||||
|
auto gid_masked = this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] & ~nfc::NCI_PKT_GID_MASK;
|
||||||
|
this->nci_message_[nfc::NCI_PKT_MT_GID_OFFSET] = gid_masked | (gid & nfc::NCI_PKT_GID_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NciMessage::set_oid(const uint8_t oid) {
|
||||||
|
if (this->nci_message_.size() < nfc::NCI_PKT_HEADER_SIZE) {
|
||||||
|
this->nci_message_.resize(nfc::NCI_PKT_HEADER_SIZE);
|
||||||
|
}
|
||||||
|
this->nci_message_[nfc::NCI_PKT_OID_OFFSET] = oid & nfc::NCI_PKT_OID_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NciMessage::set_payload(const std::vector<uint8_t> &payload) {
|
||||||
|
std::vector<uint8_t> message(this->nci_message_.begin(), this->nci_message_.begin() + nfc::NCI_PKT_HEADER_SIZE);
|
||||||
|
|
||||||
|
message.insert(message.end(), payload.begin(), payload.end());
|
||||||
|
message[nfc::NCI_PKT_LENGTH_OFFSET] = payload.size();
|
||||||
|
this->nci_message_ = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace nfc
|
||||||
|
} // namespace esphome
|
50
esphome/components/nfc/nci_message.h
Normal file
50
esphome/components/nfc/nci_message.h
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace nfc {
|
||||||
|
|
||||||
|
class NciMessage {
|
||||||
|
public:
|
||||||
|
NciMessage() {}
|
||||||
|
NciMessage(uint8_t message_type, const std::vector<uint8_t> &payload);
|
||||||
|
NciMessage(uint8_t message_type, uint8_t gid, uint8_t oid);
|
||||||
|
NciMessage(uint8_t message_type, uint8_t gid, uint8_t oid, const std::vector<uint8_t> &payload);
|
||||||
|
NciMessage(const std::vector<uint8_t> &raw_packet);
|
||||||
|
|
||||||
|
std::vector<uint8_t> encode();
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
uint8_t get_message_type() const;
|
||||||
|
uint8_t get_gid() const;
|
||||||
|
uint8_t get_oid() const;
|
||||||
|
uint8_t get_payload_size(bool recompute = false);
|
||||||
|
uint8_t get_simple_status_response() const;
|
||||||
|
uint8_t get_message_byte(uint8_t offset) const;
|
||||||
|
std::vector<uint8_t> &get_message();
|
||||||
|
|
||||||
|
bool has_payload() const;
|
||||||
|
bool message_type_is(uint8_t message_type) const;
|
||||||
|
bool message_length_is(uint8_t message_length, bool recompute = false);
|
||||||
|
bool gid_is(uint8_t gid) const;
|
||||||
|
bool oid_is(uint8_t oid) const;
|
||||||
|
bool simple_status_response_is(uint8_t response) const;
|
||||||
|
|
||||||
|
void set_header(uint8_t message_type, uint8_t gid, uint8_t oid);
|
||||||
|
void set_message(uint8_t message_type, const std::vector<uint8_t> &payload);
|
||||||
|
void set_message(uint8_t message_type, uint8_t gid, uint8_t oid, const std::vector<uint8_t> &payload);
|
||||||
|
void set_message_type(uint8_t message_type);
|
||||||
|
void set_gid(uint8_t gid);
|
||||||
|
void set_oid(uint8_t oid);
|
||||||
|
void set_payload(const std::vector<uint8_t> &payload);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<uint8_t> nci_message_{0, 0, 0}; // three bytes, MT/PBF/GID, OID, payload length/size
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace nfc
|
||||||
|
} // namespace esphome
|
|
@ -53,7 +53,7 @@ uint8_t get_mifare_classic_ndef_start_index(std::vector<uint8_t> &data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool decode_mifare_classic_tlv(std::vector<uint8_t> &data, uint32_t &message_length, uint8_t &message_start_index) {
|
bool decode_mifare_classic_tlv(std::vector<uint8_t> &data, uint32_t &message_length, uint8_t &message_start_index) {
|
||||||
uint8_t i = get_mifare_classic_ndef_start_index(data);
|
auto i = get_mifare_classic_ndef_start_index(data);
|
||||||
if (data[i] != 0x03) {
|
if (data[i] != 0x03) {
|
||||||
ESP_LOGE(TAG, "Error, Can't decode message length.");
|
ESP_LOGE(TAG, "Error, Can't decode message length.");
|
||||||
return false;
|
return false;
|
||||||
|
|
47
esphome/components/nfc/nfc_helpers.cpp
Normal file
47
esphome/components/nfc/nfc_helpers.cpp
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
#include "nfc_helpers.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace nfc {
|
||||||
|
|
||||||
|
static const char *const TAG = "nfc.helpers";
|
||||||
|
|
||||||
|
bool has_ha_tag_ndef(NfcTag &tag) { return !get_ha_tag_ndef(tag).empty(); }
|
||||||
|
|
||||||
|
std::string get_ha_tag_ndef(NfcTag &tag) {
|
||||||
|
if (!tag.has_ndef_message()) {
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
auto message = tag.get_ndef_message();
|
||||||
|
auto records = message->get_records();
|
||||||
|
for (const auto &record : records) {
|
||||||
|
std::string payload = record->get_payload();
|
||||||
|
size_t pos = payload.find(HA_TAG_ID_PREFIX);
|
||||||
|
if (pos != std::string::npos) {
|
||||||
|
return payload.substr(pos + sizeof(HA_TAG_ID_PREFIX) - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_random_ha_tag_ndef() {
|
||||||
|
static const char ALPHANUM[] = "0123456789abcdef";
|
||||||
|
std::string uri = HA_TAG_ID_PREFIX;
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
uri += ALPHANUM[random_uint32() % (sizeof(ALPHANUM) - 1)];
|
||||||
|
}
|
||||||
|
uri += "-";
|
||||||
|
for (int j = 0; j < 3; j++) {
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
uri += ALPHANUM[random_uint32() % (sizeof(ALPHANUM) - 1)];
|
||||||
|
}
|
||||||
|
uri += "-";
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
uri += ALPHANUM[random_uint32() % (sizeof(ALPHANUM) - 1)];
|
||||||
|
}
|
||||||
|
ESP_LOGD("pn7160", "Payload to be written: %s", uri.c_str());
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace nfc
|
||||||
|
} // namespace esphome
|
17
esphome/components/nfc/nfc_helpers.h
Normal file
17
esphome/components/nfc/nfc_helpers.h
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "nfc_tag.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace nfc {
|
||||||
|
|
||||||
|
static const char HA_TAG_ID_EXT_RECORD_TYPE[] = "android.com:pkg";
|
||||||
|
static const char HA_TAG_ID_EXT_RECORD_PAYLOAD[] = "io.homeassistant.companion.android";
|
||||||
|
static const char HA_TAG_ID_PREFIX[] = "https://www.home-assistant.io/tag/";
|
||||||
|
|
||||||
|
std::string get_ha_tag_ndef(NfcTag &tag);
|
||||||
|
std::string get_random_ha_tag_ndef();
|
||||||
|
bool has_ha_tag_ndef(NfcTag &tag);
|
||||||
|
|
||||||
|
} // namespace nfc
|
||||||
|
} // namespace esphome
|
227
esphome/components/pn7160/__init__.py
Normal file
227
esphome/components/pn7160/__init__.py
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
from esphome import automation, pins
|
||||||
|
from esphome.automation import maybe_simple_id
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import nfc
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ID,
|
||||||
|
CONF_IRQ_PIN,
|
||||||
|
CONF_ON_TAG_REMOVED,
|
||||||
|
CONF_ON_TAG,
|
||||||
|
CONF_TRIGGER_ID,
|
||||||
|
)
|
||||||
|
|
||||||
|
AUTO_LOAD = ["binary_sensor", "nfc"]
|
||||||
|
CODEOWNERS = ["@kbx81", "@jesserockz"]
|
||||||
|
|
||||||
|
CONF_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_MESSAGE = "message"
|
||||||
|
CONF_ON_FINISHED_WRITE = "on_finished_write"
|
||||||
|
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", cg.Component)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
PN7160OnEmulatedTagScanTrigger = pn7160_ns.class_(
|
||||||
|
"PN7160OnEmulatedTagScanTrigger", automation.Trigger.template()
|
||||||
|
)
|
||||||
|
|
||||||
|
PN7160OnFinishedWriteTrigger = pn7160_ns.class_(
|
||||||
|
"PN7160OnFinishedWriteTrigger", automation.Trigger.template()
|
||||||
|
)
|
||||||
|
|
||||||
|
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(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(PN7160),
|
||||||
|
cv.Optional(CONF_ON_EMULATED_TAG_SCAN): automation.validate_automation(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||||
|
PN7160OnEmulatedTagScanTrigger
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||||
|
PN7160OnFinishedWriteTrigger
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
@automation.register_action(
|
||||||
|
"tag.set_write_message",
|
||||||
|
SetWriteMessageAction,
|
||||||
|
SET_MESSAGE_ACTION_SCHEMA,
|
||||||
|
)
|
||||||
|
async def pn7160_set_message_to_code(config, action_id, template_arg, args):
|
||||||
|
var = cg.new_Pvariable(action_id, template_arg)
|
||||||
|
await cg.register_parented(var, config[CONF_ID])
|
||||||
|
template_ = await cg.templatable(config[CONF_MESSAGE], args, cg.std_string)
|
||||||
|
cg.add(var.set_message(template_))
|
||||||
|
template_ = await cg.templatable(
|
||||||
|
config[CONF_INCLUDE_ANDROID_APP_RECORD], args, cg.bool_
|
||||||
|
)
|
||||||
|
cg.add(var.set_include_android_app_record(template_))
|
||||||
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action(
|
||||||
|
"tag.emulation_off", EmulationOffAction, SIMPLE_ACTION_SCHEMA
|
||||||
|
)
|
||||||
|
@automation.register_action("tag.emulation_on", EmulationOnAction, SIMPLE_ACTION_SCHEMA)
|
||||||
|
@automation.register_action("tag.polling_off", PollingOffAction, SIMPLE_ACTION_SCHEMA)
|
||||||
|
@automation.register_action("tag.polling_on", PollingOnAction, SIMPLE_ACTION_SCHEMA)
|
||||||
|
@automation.register_action(
|
||||||
|
"tag.set_clean_mode", SetCleanModeAction, SIMPLE_ACTION_SCHEMA
|
||||||
|
)
|
||||||
|
@automation.register_action(
|
||||||
|
"tag.set_format_mode", SetFormatModeAction, SIMPLE_ACTION_SCHEMA
|
||||||
|
)
|
||||||
|
@automation.register_action(
|
||||||
|
"tag.set_read_mode", SetReadModeAction, SIMPLE_ACTION_SCHEMA
|
||||||
|
)
|
||||||
|
@automation.register_action(
|
||||||
|
"tag.set_write_mode", SetWriteModeAction, SIMPLE_ACTION_SCHEMA
|
||||||
|
)
|
||||||
|
async def pn7160_simple_action_to_code(config, action_id, template_arg, args):
|
||||||
|
var = cg.new_Pvariable(action_id, template_arg)
|
||||||
|
await cg.register_parented(var, config[CONF_ID])
|
||||||
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
async def setup_pn7160(var, config):
|
||||||
|
await cg.register_component(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)
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
for conf in config.get(CONF_ON_EMULATED_TAG_SCAN, []):
|
||||||
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
|
await automation.build_automation(trigger, [], conf)
|
||||||
|
|
||||||
|
for conf in config.get(CONF_ON_FINISHED_WRITE, []):
|
||||||
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
|
await automation.build_automation(trigger, [], conf)
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_condition(
|
||||||
|
"pn7160.is_writing",
|
||||||
|
PN7160IsWritingCondition,
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.use_id(PN7160),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
async def pn7160_is_writing_to_code(config, condition_id, template_arg, args):
|
||||||
|
var = cg.new_Pvariable(condition_id, template_arg)
|
||||||
|
await cg.register_parented(var, config[CONF_ID])
|
||||||
|
return var
|
82
esphome/components/pn7160/automation.h
Normal file
82
esphome/components/pn7160/automation.h
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/pn7160/pn7160.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace pn7160 {
|
||||||
|
|
||||||
|
class PN7160OnEmulatedTagScanTrigger : public Trigger<> {
|
||||||
|
public:
|
||||||
|
explicit PN7160OnEmulatedTagScanTrigger(PN7160 *parent) {
|
||||||
|
parent->add_on_emulated_tag_scan_callback([this]() { this->trigger(); });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class PN7160OnFinishedWriteTrigger : public Trigger<> {
|
||||||
|
public:
|
||||||
|
explicit PN7160OnFinishedWriteTrigger(PN7160 *parent) {
|
||||||
|
parent->add_on_finished_write_callback([this]() { this->trigger(); });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class PN7160IsWritingCondition : public Condition<Ts...>, public Parented<PN7160> {
|
||||||
|
public:
|
||||||
|
bool check(Ts... x) override { return this->parent_->is_writing(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class EmulationOffAction : public Action<Ts...>, public Parented<PN7160> {
|
||||||
|
void play(Ts... x) override { this->parent_->set_tag_emulation_off(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class EmulationOnAction : public Action<Ts...>, public Parented<PN7160> {
|
||||||
|
void play(Ts... x) override { this->parent_->set_tag_emulation_on(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class PollingOffAction : public Action<Ts...>, public Parented<PN7160> {
|
||||||
|
void play(Ts... x) override { this->parent_->set_polling_off(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class PollingOnAction : public Action<Ts...>, public Parented<PN7160> {
|
||||||
|
void play(Ts... x) override { this->parent_->set_polling_on(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class SetCleanModeAction : public Action<Ts...>, public Parented<PN7160> {
|
||||||
|
void play(Ts... x) override { this->parent_->clean_mode(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class SetFormatModeAction : public Action<Ts...>, public Parented<PN7160> {
|
||||||
|
void play(Ts... x) override { this->parent_->format_mode(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class SetReadModeAction : public Action<Ts...>, public Parented<PN7160> {
|
||||||
|
void play(Ts... x) override { this->parent_->read_mode(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class SetEmulationMessageAction : public Action<Ts...>, public Parented<PN7160> {
|
||||||
|
TEMPLATABLE_VALUE(std::string, message)
|
||||||
|
TEMPLATABLE_VALUE(bool, include_android_app_record)
|
||||||
|
|
||||||
|
void play(Ts... x) override {
|
||||||
|
this->parent_->set_tag_emulation_message(this->message_.optional_value(x...),
|
||||||
|
this->include_android_app_record_.optional_value(x...));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class SetWriteMessageAction : public Action<Ts...>, public Parented<PN7160> {
|
||||||
|
TEMPLATABLE_VALUE(std::string, message)
|
||||||
|
TEMPLATABLE_VALUE(bool, include_android_app_record)
|
||||||
|
|
||||||
|
void play(Ts... x) override {
|
||||||
|
this->parent_->set_tag_write_message(this->message_.optional_value(x...),
|
||||||
|
this->include_android_app_record_.optional_value(x...));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class SetWriteModeAction : public Action<Ts...>, public Parented<PN7160> {
|
||||||
|
void play(Ts... x) override { this->parent_->write_mode(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pn7160
|
||||||
|
} // namespace esphome
|
1161
esphome/components/pn7160/pn7160.cpp
Normal file
1161
esphome/components/pn7160/pn7160.cpp
Normal file
File diff suppressed because it is too large
Load diff
315
esphome/components/pn7160/pn7160.h
Normal file
315
esphome/components/pn7160/pn7160.h
Normal file
|
@ -0,0 +1,315 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/nfc/automation.h"
|
||||||
|
#include "esphome/components/nfc/nci_core.h"
|
||||||
|
#include "esphome/components/nfc/nci_message.h"
|
||||||
|
#include "esphome/components/nfc/nfc.h"
|
||||||
|
#include "esphome/components/nfc/nfc_helpers.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/gpio.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace pn7160 {
|
||||||
|
|
||||||
|
static const uint16_t NFCC_DEFAULT_TIMEOUT = 10;
|
||||||
|
static const uint16_t NFCC_INIT_TIMEOUT = 50;
|
||||||
|
static const uint16_t NFCC_TAG_WRITE_TIMEOUT = 15;
|
||||||
|
|
||||||
|
static const uint8_t NFCC_MAX_COMM_FAILS = 3;
|
||||||
|
static const uint8_t NFCC_MAX_ERROR_COUNT = 10;
|
||||||
|
|
||||||
|
static const uint8_t XCHG_DATA_OID = 0x10;
|
||||||
|
static const uint8_t MF_SECTORSEL_OID = 0x32;
|
||||||
|
static const uint8_t MFC_AUTHENTICATE_OID = 0x40;
|
||||||
|
static const uint8_t TEST_PRBS_OID = 0x30;
|
||||||
|
static const uint8_t TEST_ANTENNA_OID = 0x3D;
|
||||||
|
static const uint8_t TEST_GET_REGISTER_OID = 0x33;
|
||||||
|
|
||||||
|
static const uint8_t MFC_AUTHENTICATE_PARAM_KS_A = 0x00; // key select A
|
||||||
|
static const uint8_t MFC_AUTHENTICATE_PARAM_KS_B = 0x80; // key select B
|
||||||
|
static const uint8_t MFC_AUTHENTICATE_PARAM_EMBED_KEY = 0x10;
|
||||||
|
|
||||||
|
static const uint8_t CARD_EMU_T4T_APP_SELECT[] = {0x00, 0xA4, 0x04, 0x00, 0x07, 0xD2, 0x76,
|
||||||
|
0x00, 0x00, 0x85, 0x01, 0x01, 0x00};
|
||||||
|
static const uint8_t CARD_EMU_T4T_CC[] = {0x00, 0x0F, 0x20, 0x00, 0xFF, 0x00, 0xFF, 0x04,
|
||||||
|
0x06, 0xE1, 0x04, 0x00, 0xFF, 0x00, 0x00};
|
||||||
|
static const uint8_t CARD_EMU_T4T_CC_SELECT[] = {0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x03};
|
||||||
|
static const uint8_t CARD_EMU_T4T_NDEF_SELECT[] = {0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x04};
|
||||||
|
static const uint8_t CARD_EMU_T4T_READ[] = {0x00, 0xB0};
|
||||||
|
static const uint8_t CARD_EMU_T4T_WRITE[] = {0x00, 0xD6};
|
||||||
|
static const uint8_t CARD_EMU_T4T_OK[] = {0x90, 0x00};
|
||||||
|
static const uint8_t CARD_EMU_T4T_NOK[] = {0x6A, 0x82};
|
||||||
|
|
||||||
|
static const uint8_t CORE_CONFIG_SOLO[] = {0x01, // Number of parameter fields
|
||||||
|
0x00, // config param identifier (TOTAL_DURATION)
|
||||||
|
0x02, // length of value
|
||||||
|
0x01, // TOTAL_DURATION (low)...
|
||||||
|
0x00}; // TOTAL_DURATION (high): 1 ms
|
||||||
|
|
||||||
|
static const uint8_t CORE_CONFIG_RW_CE[] = {0x01, // Number of parameter fields
|
||||||
|
0x00, // config param identifier (TOTAL_DURATION)
|
||||||
|
0x02, // length of value
|
||||||
|
0xF8, // TOTAL_DURATION (low)...
|
||||||
|
0x02}; // TOTAL_DURATION (high): 760 ms
|
||||||
|
|
||||||
|
static const uint8_t PMU_CFG[] = {
|
||||||
|
0x01, // Number of parameters
|
||||||
|
0xA0, 0x0E, // ext. tag
|
||||||
|
11, // length
|
||||||
|
0x11, // IRQ Enable: PVDD + temp sensor IRQs
|
||||||
|
0x01, // RFU
|
||||||
|
0x01, // Power and Clock Configuration, device on (CFG1)
|
||||||
|
0x01, // Power and Clock Configuration, device off (CFG1)
|
||||||
|
0x00, // RFU
|
||||||
|
0x00, // DC-DC 0
|
||||||
|
0x00, // DC-DC 1
|
||||||
|
// 0x14, // TXLDO (3.3V / 4.75V)
|
||||||
|
// 0xBB, // TXLDO (4.7V / 4.7V)
|
||||||
|
0xFF, // TXLDO (5.0V / 5.0V)
|
||||||
|
0x00, // RFU
|
||||||
|
0xD0, // TXLDO check
|
||||||
|
0x0C, // RFU
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint8_t RF_DISCOVER_MAP_CONFIG[] = { // poll modes
|
||||||
|
nfc::PROT_T1T, nfc::RF_DISCOVER_MAP_MODE_POLL,
|
||||||
|
nfc::INTF_FRAME, // poll mode
|
||||||
|
nfc::PROT_T2T, nfc::RF_DISCOVER_MAP_MODE_POLL,
|
||||||
|
nfc::INTF_FRAME, // poll mode
|
||||||
|
nfc::PROT_T3T, nfc::RF_DISCOVER_MAP_MODE_POLL,
|
||||||
|
nfc::INTF_FRAME, // poll mode
|
||||||
|
nfc::PROT_ISODEP, nfc::RF_DISCOVER_MAP_MODE_POLL | nfc::RF_DISCOVER_MAP_MODE_LISTEN,
|
||||||
|
nfc::INTF_ISODEP, // poll & listen mode
|
||||||
|
nfc::PROT_MIFARE, nfc::RF_DISCOVER_MAP_MODE_POLL,
|
||||||
|
nfc::INTF_TAGCMD}; // poll mode
|
||||||
|
|
||||||
|
static const uint8_t RF_DISCOVERY_LISTEN_CONFIG[] = {nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCA, // listen mode
|
||||||
|
nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCB, // listen mode
|
||||||
|
nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCF}; // listen mode
|
||||||
|
|
||||||
|
static const uint8_t RF_DISCOVERY_POLL_CONFIG[] = {nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCA, // poll mode
|
||||||
|
nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCB, // poll mode
|
||||||
|
nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCF}; // poll mode
|
||||||
|
|
||||||
|
static const uint8_t RF_DISCOVERY_CONFIG[] = {nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCA, // poll mode
|
||||||
|
nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCB, // poll mode
|
||||||
|
nfc::MODE_POLL | nfc::TECH_PASSIVE_NFCF, // poll mode
|
||||||
|
nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCA, // listen mode
|
||||||
|
nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCB, // listen mode
|
||||||
|
nfc::MODE_LISTEN_MASK | nfc::TECH_PASSIVE_NFCF}; // listen mode
|
||||||
|
|
||||||
|
static const uint8_t RF_LISTEN_MODE_ROUTING_CONFIG[] = {0x00, // "more" (another message is coming)
|
||||||
|
2, // number of table entries
|
||||||
|
0x01, // type = protocol-based
|
||||||
|
3, // length
|
||||||
|
0, // DH NFCEE ID, a static ID representing the DH-NFCEE
|
||||||
|
0x07, // power state
|
||||||
|
nfc::PROT_ISODEP, // protocol
|
||||||
|
0x00, // type = technology-based
|
||||||
|
3, // length
|
||||||
|
0, // DH NFCEE ID, a static ID representing the DH-NFCEE
|
||||||
|
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 Component {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
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); }
|
||||||
|
|
||||||
|
void add_on_emulated_tag_scan_callback(std::function<void()> callback) {
|
||||||
|
this->on_emulated_tag_scan_callback_.add(std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_on_finished_write_callback(std::function<void()> callback) {
|
||||||
|
this->on_finished_write_callback_.add(std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_writing() { return this->next_task_ != EP_READ; };
|
||||||
|
|
||||||
|
void read_mode();
|
||||||
|
void clean_mode();
|
||||||
|
void format_mode();
|
||||||
|
void write_mode();
|
||||||
|
void set_tag_write_message(std::shared_ptr<nfc::NdefMessage> message);
|
||||||
|
void set_tag_write_message(optional<std::string> message, optional<bool> include_android_app_record);
|
||||||
|
|
||||||
|
uint8_t set_test_mode(TestMode test_mode, const std::vector<uint8_t> &data, std::vector<uint8_t> &result);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint8_t reset_core_(bool reset_config, bool power);
|
||||||
|
uint8_t init_core_();
|
||||||
|
uint8_t send_init_config_();
|
||||||
|
uint8_t send_core_config_();
|
||||||
|
uint8_t refresh_core_config_();
|
||||||
|
|
||||||
|
uint8_t set_discover_map_();
|
||||||
|
|
||||||
|
uint8_t set_listen_mode_routing_();
|
||||||
|
|
||||||
|
uint8_t start_discovery_();
|
||||||
|
uint8_t stop_discovery_();
|
||||||
|
uint8_t deactivate_(uint8_t type, uint16_t timeout = NFCC_DEFAULT_TIMEOUT);
|
||||||
|
|
||||||
|
void select_endpoint_();
|
||||||
|
|
||||||
|
uint8_t read_endpoint_data_(nfc::NfcTag &tag);
|
||||||
|
uint8_t clean_endpoint_(std::vector<uint8_t> &uid);
|
||||||
|
uint8_t format_endpoint_(std::vector<uint8_t> &uid);
|
||||||
|
uint8_t write_endpoint_(std::vector<uint8_t> &uid, std::shared_ptr<nfc::NdefMessage> &message);
|
||||||
|
|
||||||
|
std::unique_ptr<nfc::NfcTag> build_tag_(uint8_t mode_tech, const std::vector<uint8_t> &data);
|
||||||
|
optional<size_t> find_tag_uid_(const std::vector<uint8_t> &uid);
|
||||||
|
void purge_old_tags_();
|
||||||
|
void erase_tag_(uint8_t tag_index);
|
||||||
|
|
||||||
|
/// advance controller state as required
|
||||||
|
void nci_fsm_transition_();
|
||||||
|
/// set new controller state
|
||||||
|
void nci_fsm_set_state_(NCIState new_state);
|
||||||
|
/// setting controller to this state caused an error; returns true if too many errors/failures
|
||||||
|
bool nci_fsm_set_error_state_(NCIState new_state);
|
||||||
|
/// parse & process incoming messages from the NFCC
|
||||||
|
void process_message_();
|
||||||
|
void process_rf_intf_activated_oid_(nfc::NciMessage &rx);
|
||||||
|
void process_rf_discover_oid_(nfc::NciMessage &rx);
|
||||||
|
void process_rf_deactivate_oid_(nfc::NciMessage &rx);
|
||||||
|
void process_data_message_(nfc::NciMessage &rx);
|
||||||
|
|
||||||
|
void card_emu_t4t_get_response_(std::vector<uint8_t> &response, std::vector<uint8_t> &ndef_response);
|
||||||
|
|
||||||
|
uint8_t transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, uint16_t timeout = NFCC_DEFAULT_TIMEOUT,
|
||||||
|
bool expect_notification = true);
|
||||||
|
virtual uint8_t read_nfcc(nfc::NciMessage &rx, uint16_t timeout) = 0;
|
||||||
|
virtual uint8_t write_nfcc(nfc::NciMessage &tx) = 0;
|
||||||
|
|
||||||
|
uint8_t wait_for_irq_(uint16_t timeout = NFCC_DEFAULT_TIMEOUT, bool pin_state = true);
|
||||||
|
|
||||||
|
uint8_t read_mifare_classic_tag_(nfc::NfcTag &tag);
|
||||||
|
uint8_t read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
|
||||||
|
uint8_t write_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
|
||||||
|
uint8_t auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, const uint8_t *key);
|
||||||
|
uint8_t sect_to_auth_(uint8_t block_num);
|
||||||
|
uint8_t format_mifare_classic_mifare_();
|
||||||
|
uint8_t format_mifare_classic_ndef_();
|
||||||
|
uint8_t write_mifare_classic_tag_(const std::shared_ptr<nfc::NdefMessage> &message);
|
||||||
|
uint8_t halt_mifare_classic_tag_();
|
||||||
|
|
||||||
|
uint8_t read_mifare_ultralight_tag_(nfc::NfcTag &tag);
|
||||||
|
uint8_t read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data);
|
||||||
|
bool is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6);
|
||||||
|
uint16_t read_mifare_ultralight_capacity_();
|
||||||
|
uint8_t find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
|
||||||
|
uint8_t &message_start_index);
|
||||||
|
uint8_t write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data);
|
||||||
|
uint8_t write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, const std::shared_ptr<nfc::NdefMessage> &message);
|
||||||
|
uint8_t clean_mifare_ultralight_();
|
||||||
|
|
||||||
|
enum NfcTask : uint8_t {
|
||||||
|
EP_READ = 0,
|
||||||
|
EP_CLEAN,
|
||||||
|
EP_FORMAT,
|
||||||
|
EP_WRITE,
|
||||||
|
} next_task_{EP_READ};
|
||||||
|
|
||||||
|
bool config_refresh_pending_{false};
|
||||||
|
bool core_config_is_solo_{false};
|
||||||
|
bool listening_enabled_{false};
|
||||||
|
bool polling_enabled_{true};
|
||||||
|
|
||||||
|
uint8_t error_count_{0};
|
||||||
|
uint8_t fail_count_{0};
|
||||||
|
uint32_t last_nci_state_change_{0};
|
||||||
|
uint8_t selecting_endpoint_{0};
|
||||||
|
uint32_t tag_ttl_{250};
|
||||||
|
|
||||||
|
GPIOPin *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 pn7160
|
||||||
|
} // namespace esphome
|
322
esphome/components/pn7160/pn7160_mifare_classic.cpp
Normal file
322
esphome/components/pn7160/pn7160_mifare_classic.cpp
Normal file
|
@ -0,0 +1,322 @@
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "pn7160.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace pn7160 {
|
||||||
|
|
||||||
|
static const char *const TAG = "pn7160.mifare_classic";
|
||||||
|
|
||||||
|
uint8_t PN7160::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 PN7160::read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data) {
|
||||||
|
nfc::NciMessage rx;
|
||||||
|
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_READ, block_num});
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "Read XCHG_DATA_REQ: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||||
|
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||||
|
ESP_LOGE(TAG, "Timeout reading tag data");
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(XCHG_DATA_OID)) ||
|
||||||
|
(!rx.message_length_is(18))) {
|
||||||
|
ESP_LOGE(TAG, "MFC read block failed - block 0x%02x", block_num);
|
||||||
|
ESP_LOGV(TAG, "Read response: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.insert(data.begin(), rx.get_message().begin() + 4, rx.get_message().end() - 1);
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, " Block %u: %s", block_num, nfc::format_bytes(data).c_str());
|
||||||
|
return nfc::STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t PN7160::auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, const uint8_t *key) {
|
||||||
|
nfc::NciMessage rx;
|
||||||
|
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {MFC_AUTHENTICATE_OID, this->sect_to_auth_(block_num), key_num});
|
||||||
|
|
||||||
|
switch (key_num) {
|
||||||
|
case nfc::MIFARE_CMD_AUTH_A:
|
||||||
|
tx.get_message().back() = MFC_AUTHENTICATE_PARAM_KS_A;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nfc::MIFARE_CMD_AUTH_B:
|
||||||
|
tx.get_message().back() = MFC_AUTHENTICATE_PARAM_KS_B;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key != nullptr) {
|
||||||
|
tx.get_message().back() |= MFC_AUTHENTICATE_PARAM_EMBED_KEY;
|
||||||
|
tx.get_message().insert(tx.get_message().end(), key, key + 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "MFC_AUTHENTICATE_REQ: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||||
|
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||||
|
ESP_LOGE(TAG, "Sending MFC_AUTHENTICATE_REQ failed");
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(MFC_AUTHENTICATE_OID)) ||
|
||||||
|
(rx.get_message()[4] != nfc::STATUS_OK)) {
|
||||||
|
ESP_LOGE(TAG, "MFC authentication failed - block 0x%02x", block_num);
|
||||||
|
ESP_LOGVV(TAG, "MFC_AUTHENTICATE_RSP: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGV(TAG, "MFC block %u authentication succeeded", block_num);
|
||||||
|
return nfc::STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t PN7160::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 PN7160::format_mifare_classic_mifare_() {
|
||||||
|
std::vector<uint8_t> blank_buffer(
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
|
std::vector<uint8_t> trailer_buffer(
|
||||||
|
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
|
||||||
|
|
||||||
|
auto status = nfc::STATUS_OK;
|
||||||
|
|
||||||
|
for (int block = 0; block < 64; block += 4) {
|
||||||
|
if (this->auth_mifare_classic_block_(block + 3, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (block != 0) {
|
||||||
|
if (this->write_mifare_classic_block_(block, blank_buffer) != nfc::STATUS_OK) {
|
||||||
|
ESP_LOGE(TAG, "Unable to write block %u", block);
|
||||||
|
status = nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this->write_mifare_classic_block_(block + 1, blank_buffer) != nfc::STATUS_OK) {
|
||||||
|
ESP_LOGE(TAG, "Unable to write block %u", block + 1);
|
||||||
|
status = nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
if (this->write_mifare_classic_block_(block + 2, blank_buffer) != nfc::STATUS_OK) {
|
||||||
|
ESP_LOGE(TAG, "Unable to write block %u", block + 2);
|
||||||
|
status = nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
if (this->write_mifare_classic_block_(block + 3, trailer_buffer) != nfc::STATUS_OK) {
|
||||||
|
ESP_LOGE(TAG, "Unable to write block %u", block + 3);
|
||||||
|
status = nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t PN7160::format_mifare_classic_ndef_() {
|
||||||
|
std::vector<uint8_t> empty_ndef_message(
|
||||||
|
{0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
|
std::vector<uint8_t> blank_block(
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
|
std::vector<uint8_t> block_1_data(
|
||||||
|
{0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1});
|
||||||
|
std::vector<uint8_t> block_2_data(
|
||||||
|
{0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1});
|
||||||
|
std::vector<uint8_t> block_3_trailer(
|
||||||
|
{0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
|
||||||
|
std::vector<uint8_t> ndef_trailer(
|
||||||
|
{0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
|
||||||
|
|
||||||
|
if (this->auth_mifare_classic_block_(0, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) {
|
||||||
|
ESP_LOGE(TAG, "Unable to authenticate block 0 for formatting");
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
if (this->write_mifare_classic_block_(1, block_1_data) != nfc::STATUS_OK) {
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
if (this->write_mifare_classic_block_(2, block_2_data) != nfc::STATUS_OK) {
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
if (this->write_mifare_classic_block_(3, block_3_trailer) != nfc::STATUS_OK) {
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Sector 0 formatted with NDEF");
|
||||||
|
|
||||||
|
auto status = nfc::STATUS_OK;
|
||||||
|
|
||||||
|
for (int block = 4; block < 64; block += 4) {
|
||||||
|
if (this->auth_mifare_classic_block_(block + 3, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) {
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
if (block == 4) {
|
||||||
|
if (this->write_mifare_classic_block_(block, empty_ndef_message) != nfc::STATUS_OK) {
|
||||||
|
ESP_LOGE(TAG, "Unable to write block %u", block);
|
||||||
|
status = nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this->write_mifare_classic_block_(block, blank_block) != nfc::STATUS_OK) {
|
||||||
|
ESP_LOGE(TAG, "Unable to write block %u", block);
|
||||||
|
status = nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this->write_mifare_classic_block_(block + 1, blank_block) != nfc::STATUS_OK) {
|
||||||
|
ESP_LOGE(TAG, "Unable to write block %u", block + 1);
|
||||||
|
status = nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
if (this->write_mifare_classic_block_(block + 2, blank_block) != nfc::STATUS_OK) {
|
||||||
|
ESP_LOGE(TAG, "Unable to write block %u", block + 2);
|
||||||
|
status = nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
if (this->write_mifare_classic_block_(block + 3, ndef_trailer) != nfc::STATUS_OK) {
|
||||||
|
ESP_LOGE(TAG, "Unable to write trailer block %u", block + 3);
|
||||||
|
status = nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t PN7160::write_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &write_data) {
|
||||||
|
nfc::NciMessage rx;
|
||||||
|
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_WRITE, block_num});
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 1: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||||
|
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||||
|
ESP_LOGE(TAG, "Sending XCHG_DATA_REQ failed");
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
// write command part two
|
||||||
|
tx.set_payload({XCHG_DATA_OID});
|
||||||
|
tx.get_message().insert(tx.get_message().end(), write_data.begin(), write_data.end());
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 2: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||||
|
if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) {
|
||||||
|
ESP_LOGE(TAG, "MFC XCHG_DATA timed out waiting for XCHG_DATA_RSP during block write");
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(XCHG_DATA_OID)) ||
|
||||||
|
(rx.get_message()[4] != nfc::MIFARE_CMD_ACK)) {
|
||||||
|
ESP_LOGE(TAG, "MFC write block failed - block 0x%02x", block_num);
|
||||||
|
ESP_LOGV(TAG, "Write response: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nfc::STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t PN7160::write_mifare_classic_tag_(const std::shared_ptr<nfc::NdefMessage> &message) {
|
||||||
|
auto encoded = message->encode();
|
||||||
|
|
||||||
|
uint32_t message_length = encoded.size();
|
||||||
|
uint32_t buffer_length = nfc::get_mifare_classic_buffer_size(message_length);
|
||||||
|
|
||||||
|
encoded.insert(encoded.begin(), 0x03);
|
||||||
|
if (message_length < 255) {
|
||||||
|
encoded.insert(encoded.begin() + 1, message_length);
|
||||||
|
} else {
|
||||||
|
encoded.insert(encoded.begin() + 1, 0xFF);
|
||||||
|
encoded.insert(encoded.begin() + 2, (message_length >> 8) & 0xFF);
|
||||||
|
encoded.insert(encoded.begin() + 3, message_length & 0xFF);
|
||||||
|
}
|
||||||
|
encoded.push_back(0xFE);
|
||||||
|
|
||||||
|
encoded.resize(buffer_length, 0);
|
||||||
|
|
||||||
|
uint32_t index = 0;
|
||||||
|
uint8_t current_block = 4;
|
||||||
|
|
||||||
|
while (index < buffer_length) {
|
||||||
|
if (nfc::mifare_classic_is_first_block(current_block)) {
|
||||||
|
if (this->auth_mifare_classic_block_(current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY) != nfc::STATUS_OK) {
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_CLASSIC_BLOCK_SIZE);
|
||||||
|
if (this->write_mifare_classic_block_(current_block, data) != nfc::STATUS_OK) {
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
index += nfc::MIFARE_CLASSIC_BLOCK_SIZE;
|
||||||
|
current_block++;
|
||||||
|
|
||||||
|
if (nfc::mifare_classic_is_trailer_block(current_block)) {
|
||||||
|
// Skipping as cannot write to trailer
|
||||||
|
current_block++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nfc::STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t PN7160::halt_mifare_classic_tag_() {
|
||||||
|
nfc::NciMessage rx;
|
||||||
|
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_HALT, 0});
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "Halt XCHG_DATA_REQ: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||||
|
if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) {
|
||||||
|
ESP_LOGE(TAG, "Sending halt XCHG_DATA_REQ failed");
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
return nfc::STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pn7160
|
||||||
|
} // namespace esphome
|
186
esphome/components/pn7160/pn7160_mifare_ultralight.cpp
Normal file
186
esphome/components/pn7160/pn7160_mifare_ultralight.cpp
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
#include <cinttypes>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "pn7160.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes(data).c_str());
|
||||||
|
|
||||||
|
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 + 5)) {
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page_3_to_6[p4_offset + 0] == 0x03) {
|
||||||
|
message_length = page_3_to_6[p4_offset + 1];
|
||||||
|
message_start_index = 2;
|
||||||
|
return nfc::STATUS_OK;
|
||||||
|
} else if (page_3_to_6[p4_offset + 5] == 0x03) {
|
||||||
|
message_length = page_3_to_6[p4_offset + 6];
|
||||||
|
message_start_index = 7;
|
||||||
|
return nfc::STATUS_OK;
|
||||||
|
}
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t PN7160::write_mifare_ultralight_tag_(std::vector<uint8_t> &uid,
|
||||||
|
const std::shared_ptr<nfc::NdefMessage> &message) {
|
||||||
|
uint32_t capacity = this->read_mifare_ultralight_capacity_();
|
||||||
|
|
||||||
|
auto encoded = message->encode();
|
||||||
|
|
||||||
|
uint32_t message_length = encoded.size();
|
||||||
|
uint32_t buffer_length = nfc::get_mifare_ultralight_buffer_size(message_length);
|
||||||
|
|
||||||
|
if (buffer_length > capacity) {
|
||||||
|
ESP_LOGE(TAG, "Message length exceeds tag capacity %" PRIu32 " > %" PRIu32, buffer_length, capacity);
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded.insert(encoded.begin(), 0x03);
|
||||||
|
if (message_length < 255) {
|
||||||
|
encoded.insert(encoded.begin() + 1, message_length);
|
||||||
|
} else {
|
||||||
|
encoded.insert(encoded.begin() + 1, 0xFF);
|
||||||
|
encoded.insert(encoded.begin() + 2, (message_length >> 8) & 0xFF);
|
||||||
|
encoded.insert(encoded.begin() + 2, message_length & 0xFF);
|
||||||
|
}
|
||||||
|
encoded.push_back(0xFE);
|
||||||
|
|
||||||
|
encoded.resize(buffer_length, 0);
|
||||||
|
|
||||||
|
uint32_t index = 0;
|
||||||
|
uint8_t current_page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE;
|
||||||
|
|
||||||
|
while (index < buffer_length) {
|
||||||
|
std::vector<uint8_t> data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE);
|
||||||
|
if (this->write_mifare_ultralight_page_(current_page, data) != nfc::STATUS_OK) {
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
index += nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
|
||||||
|
current_page++;
|
||||||
|
}
|
||||||
|
return nfc::STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t 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;
|
||||||
|
|
||||||
|
std::vector<uint8_t> blank_data = {0x00, 0x00, 0x00, 0x00};
|
||||||
|
|
||||||
|
for (int i = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; i < pages; i++) {
|
||||||
|
if (this->write_mifare_ultralight_page_(i, blank_data) != nfc::STATUS_OK) {
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nfc::STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t PN7160::write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data) {
|
||||||
|
std::vector<uint8_t> payload = {nfc::MIFARE_CMD_WRITE_ULTRALIGHT, page_num};
|
||||||
|
payload.insert(payload.end(), write_data.begin(), write_data.end());
|
||||||
|
|
||||||
|
nfc::NciMessage rx;
|
||||||
|
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, payload);
|
||||||
|
|
||||||
|
if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) {
|
||||||
|
ESP_LOGE(TAG, "Error writing page %u", page_num);
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
return nfc::STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pn7160
|
||||||
|
} // namespace esphome
|
25
esphome/components/pn7160_i2c/__init__.py
Normal file
25
esphome/components/pn7160_i2c/__init__.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import i2c, pn7160
|
||||||
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
|
AUTO_LOAD = ["pn7160"]
|
||||||
|
CODEOWNERS = ["@kbx81", "@jesserockz"]
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
|
pn7160_i2c_ns = cg.esphome_ns.namespace("pn7160_i2c")
|
||||||
|
PN7160I2C = pn7160_i2c_ns.class_("PN7160I2C", pn7160.PN7160, i2c.I2CDevice)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
pn7160.PN7160_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(PN7160I2C),
|
||||||
|
}
|
||||||
|
).extend(i2c.i2c_device_schema(0x28))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await pn7160.setup_pn7160(var, config)
|
||||||
|
await i2c.register_i2c_device(var, config)
|
49
esphome/components/pn7160_i2c/pn7160_i2c.cpp
Normal file
49
esphome/components/pn7160_i2c/pn7160_i2c.cpp
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
#include "pn7160_i2c.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace pn7160_i2c {
|
||||||
|
|
||||||
|
static const char *const TAG = "pn7160_i2c";
|
||||||
|
|
||||||
|
uint8_t PN7160I2C::read_nfcc(nfc::NciMessage &rx, const uint16_t timeout) {
|
||||||
|
if (this->wait_for_irq_(timeout) != nfc::STATUS_OK) {
|
||||||
|
ESP_LOGW(TAG, "read_nfcc_() timeout waiting for IRQ");
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
rx.get_message().resize(nfc::NCI_PKT_HEADER_SIZE);
|
||||||
|
if (!this->read_bytes_raw(rx.get_message().data(), nfc::NCI_PKT_HEADER_SIZE)) {
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t length = rx.get_payload_size();
|
||||||
|
if (length > 0) {
|
||||||
|
rx.get_message().resize(length + nfc::NCI_PKT_HEADER_SIZE);
|
||||||
|
if (!this->read_bytes_raw(rx.get_message().data() + nfc::NCI_PKT_HEADER_SIZE, length)) {
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// semaphore to ensure transaction is complete before returning
|
||||||
|
if (this->wait_for_irq_(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;
|
||||||
|
}
|
||||||
|
return nfc::STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t PN7160I2C::write_nfcc(nfc::NciMessage &tx) {
|
||||||
|
if (this->write(tx.encode().data(), tx.encode().size()) == i2c::ERROR_OK) {
|
||||||
|
return nfc::STATUS_OK;
|
||||||
|
}
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PN7160I2C::dump_config() {
|
||||||
|
PN7160::dump_config();
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pn7160_i2c
|
||||||
|
} // namespace esphome
|
22
esphome/components/pn7160_i2c/pn7160_i2c.h
Normal file
22
esphome/components/pn7160_i2c/pn7160_i2c.h
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/pn7160/pn7160.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace pn7160_i2c {
|
||||||
|
|
||||||
|
class PN7160I2C : public pn7160::PN7160, public i2c::I2CDevice {
|
||||||
|
public:
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint8_t read_nfcc(nfc::NciMessage &rx, uint16_t timeout) override;
|
||||||
|
uint8_t write_nfcc(nfc::NciMessage &tx) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pn7160_i2c
|
||||||
|
} // namespace esphome
|
26
esphome/components/pn7160_spi/__init__.py
Normal file
26
esphome/components/pn7160_spi/__init__.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import spi, pn7160
|
||||||
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
|
AUTO_LOAD = ["pn7160"]
|
||||||
|
CODEOWNERS = ["@kbx81", "@jesserockz"]
|
||||||
|
DEPENDENCIES = ["spi"]
|
||||||
|
MULTI_CONF = True
|
||||||
|
|
||||||
|
pn7160_spi_ns = cg.esphome_ns.namespace("pn7160_spi")
|
||||||
|
PN7160Spi = pn7160_spi_ns.class_("PN7160Spi", pn7160.PN7160, spi.SPIDevice)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
pn7160.PN7160_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(PN7160Spi),
|
||||||
|
}
|
||||||
|
).extend(spi.spi_device_schema(cs_pin_required=True))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await pn7160.setup_pn7160(var, config)
|
||||||
|
await spi.register_spi_device(var, config)
|
54
esphome/components/pn7160_spi/pn7160_spi.cpp
Normal file
54
esphome/components/pn7160_spi/pn7160_spi.cpp
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
#include "pn7160_spi.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace pn7160_spi {
|
||||||
|
|
||||||
|
static const char *const TAG = "pn7160_spi";
|
||||||
|
|
||||||
|
void PN7160Spi::setup() {
|
||||||
|
this->spi_setup();
|
||||||
|
this->cs_->digital_write(false);
|
||||||
|
PN7160::setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t PN7160Spi::read_nfcc(nfc::NciMessage &rx, const uint16_t timeout) {
|
||||||
|
if (this->wait_for_irq_(timeout) != nfc::STATUS_OK) {
|
||||||
|
ESP_LOGW(TAG, "read_nfcc_() timeout waiting for IRQ");
|
||||||
|
return nfc::STATUS_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
rx.get_message().resize(nfc::NCI_PKT_HEADER_SIZE);
|
||||||
|
this->enable();
|
||||||
|
this->write_byte(TDD_SPI_READ); // send "transfer direction detector"
|
||||||
|
this->read_array(rx.get_message().data(), nfc::NCI_PKT_HEADER_SIZE);
|
||||||
|
|
||||||
|
uint8_t length = rx.get_payload_size();
|
||||||
|
if (length > 0) {
|
||||||
|
rx.get_message().resize(length + nfc::NCI_PKT_HEADER_SIZE);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return nfc::STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t PN7160Spi::write_nfcc(nfc::NciMessage &tx) {
|
||||||
|
this->enable();
|
||||||
|
this->write_byte(TDD_SPI_WRITE); // send "transfer direction detector"
|
||||||
|
this->write_array(tx.encode().data(), tx.encode().size());
|
||||||
|
this->disable();
|
||||||
|
return nfc::STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PN7160Spi::dump_config() {
|
||||||
|
PN7160::dump_config();
|
||||||
|
LOG_PIN(" CS Pin: ", this->cs_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pn7160_spi
|
||||||
|
} // namespace esphome
|
30
esphome/components/pn7160_spi/pn7160_spi.h
Normal file
30
esphome/components/pn7160_spi/pn7160_spi.h
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/nfc/nci_core.h"
|
||||||
|
#include "esphome/components/pn7160/pn7160.h"
|
||||||
|
#include "esphome/components/spi/spi.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace pn7160_spi {
|
||||||
|
|
||||||
|
static const uint8_t TDD_SPI_READ = 0xFF;
|
||||||
|
static const uint8_t TDD_SPI_WRITE = 0x0A;
|
||||||
|
|
||||||
|
class PN7160Spi : public pn7160::PN7160,
|
||||||
|
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||||
|
spi::DATA_RATE_4MHZ> {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint8_t read_nfcc(nfc::NciMessage &rx, uint16_t timeout) override;
|
||||||
|
uint8_t write_nfcc(nfc::NciMessage &tx) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pn7160_spi
|
||||||
|
} // namespace esphome
|
|
@ -3388,6 +3388,43 @@ pn532_spi:
|
||||||
pn532_i2c:
|
pn532_i2c:
|
||||||
i2c_id: i2c_bus
|
i2c_id: i2c_bus
|
||||||
|
|
||||||
|
pn7160_i2c:
|
||||||
|
id: nfcc_pn7160_i2c
|
||||||
|
i2c_id: i2c_bus
|
||||||
|
dwl_req_pin:
|
||||||
|
allow_other_uses: true
|
||||||
|
number: GPIO17
|
||||||
|
irq_pin:
|
||||||
|
allow_other_uses: true
|
||||||
|
number: GPIO35
|
||||||
|
ven_pin:
|
||||||
|
allow_other_uses: true
|
||||||
|
number: GPIO16
|
||||||
|
wkup_req_pin:
|
||||||
|
allow_other_uses: true
|
||||||
|
number: GPIO21
|
||||||
|
emulation_message: https://www.home-assistant.io/tag/pulse_ce
|
||||||
|
tag_ttl: 1000ms
|
||||||
|
|
||||||
|
pn7160_spi:
|
||||||
|
id: nfcc_pn7160_spi
|
||||||
|
cs_pin:
|
||||||
|
number: GPIO15
|
||||||
|
dwl_req_pin:
|
||||||
|
allow_other_uses: true
|
||||||
|
number: GPIO17
|
||||||
|
irq_pin:
|
||||||
|
allow_other_uses: true
|
||||||
|
number: GPIO35
|
||||||
|
ven_pin:
|
||||||
|
allow_other_uses: true
|
||||||
|
number: GPIO16
|
||||||
|
wkup_req_pin:
|
||||||
|
allow_other_uses: true
|
||||||
|
number: GPIO21
|
||||||
|
emulation_message: https://www.home-assistant.io/tag/pulse_ce
|
||||||
|
tag_ttl: 1000ms
|
||||||
|
|
||||||
rdm6300:
|
rdm6300:
|
||||||
uart_id: uart_0
|
uart_id: uart_0
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue