Pn532 upgrades (#1302)

* Move pn532 -> pn532_spi
Add pn532_i2c

* Update i2c address

* Always wait for ready byte before reading

* Generalise the pn532 a bit more so less code in i2c and spi implementations

* clang

* Add pn532_i2c to test1

* Try to get setup working

* Fixes

* More updates

* Command consts

* A few upgrades

* Change text back to include 'new'

* Fix data reading
This commit is contained in:
Jesse Hills 2020-11-01 11:55:48 +13:00 committed by GitHub
parent 22e1758d5b
commit 0059a6de46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 415 additions and 261 deletions

View File

@ -43,7 +43,9 @@ esphome/components/network/* @esphome/core
esphome/components/ota/* @esphome/core esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core esphome/components/output/* @esphome/core
esphome/components/pid/* @OttoWinter esphome/components/pid/* @OttoWinter
esphome/components/pn532/* @OttoWinter esphome/components/pn532/* @OttoWinter @jesserockz
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
esphome/components/pn532_spi/* @OttoWinter @jesserockz
esphome/components/power_supply/* @esphome/core esphome/components/power_supply/* @esphome/core
esphome/components/rc522_spi/* @glmnet esphome/components/rc522_spi/* @glmnet
esphome/components/restart/* @esphome/core esphome/components/restart/* @esphome/core

View File

@ -1,30 +1,37 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import automation from esphome import automation
from esphome.components import spi from esphome.const import CONF_ON_TAG, CONF_TRIGGER_ID
from esphome.const import CONF_ID, CONF_ON_TAG, CONF_TRIGGER_ID from esphome.core import coroutine
CODEOWNERS = ['@OttoWinter'] CODEOWNERS = ['@OttoWinter', '@jesserockz']
DEPENDENCIES = ['spi']
AUTO_LOAD = ['binary_sensor'] AUTO_LOAD = ['binary_sensor']
MULTI_CONF = True MULTI_CONF = True
CONF_PN532_ID = 'pn532_id'
pn532_ns = cg.esphome_ns.namespace('pn532') pn532_ns = cg.esphome_ns.namespace('pn532')
PN532 = pn532_ns.class_('PN532', cg.PollingComponent, spi.SPIDevice) PN532 = pn532_ns.class_('PN532', cg.PollingComponent)
PN532Trigger = pn532_ns.class_('PN532Trigger', automation.Trigger.template(cg.std_string)) PN532Trigger = pn532_ns.class_('PN532Trigger', automation.Trigger.template(cg.std_string))
CONFIG_SCHEMA = cv.Schema({ PN532_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(PN532), cv.GenerateID(): cv.declare_id(PN532),
cv.Optional(CONF_ON_TAG): automation.validate_automation({ cv.Optional(CONF_ON_TAG): automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PN532Trigger), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PN532Trigger),
}), }),
}).extend(cv.polling_component_schema('1s')).extend(spi.spi_device_schema()) }).extend(cv.polling_component_schema('1s'))
def to_code(config): def CONFIG_SCHEMA(conf):
var = cg.new_Pvariable(config[CONF_ID]) if conf:
raise cv.Invalid("This component has been moved in 1.16, please see the docs for updated "
"instructions. https://esphome.io/components/binary_sensor/pn532.html")
@coroutine
def setup_pn532(var, config):
yield cg.register_component(var, config) yield cg.register_component(var, config)
yield spi.register_spi_device(var, config)
for conf in config.get(CONF_ON_TAG, []): for conf in config.get(CONF_ON_TAG, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])

View File

@ -3,12 +3,10 @@ import esphome.config_validation as cv
from esphome.components import binary_sensor from esphome.components import binary_sensor
from esphome.const import CONF_UID, CONF_ID from esphome.const import CONF_UID, CONF_ID
from esphome.core import HexInt from esphome.core import HexInt
from . import pn532_ns, PN532 from . import pn532_ns, PN532, CONF_PN532_ID
DEPENDENCIES = ['pn532'] DEPENDENCIES = ['pn532']
CONF_PN532_ID = 'pn532_id'
def validate_uid(value): def validate_uid(value):
value = cv.string_strict(value) value = cv.string_strict(value)

View File

@ -11,51 +11,50 @@ namespace pn532 {
static const char *TAG = "pn532"; static const char *TAG = "pn532";
void format_uid(char *buf, const uint8_t *uid, uint8_t uid_length) { std::string format_uid(std::vector<uint8_t> &uid) {
char buf[32];
int offset = 0; int offset = 0;
for (uint8_t i = 0; i < uid_length; i++) { for (uint8_t i = 0; i < uid.size(); i++) {
const char *format = "%02X"; const char *format = "%02X";
if (i + 1 < uid_length) if (i + 1 < uid.size())
format = "%02X-"; format = "%02X-";
offset += sprintf(buf + offset, format, uid[i]); offset += sprintf(buf + offset, format, uid[i]);
} }
return std::string(buf);
} }
void PN532::setup() { void PN532::setup() {
ESP_LOGCONFIG(TAG, "Setting up PN532..."); ESP_LOGCONFIG(TAG, "Setting up PN532...");
this->spi_setup();
// Wake the chip up from power down // Get version data
// 1. Enable the SS line for at least 2ms if (!this->write_command_({PN532_COMMAND_VERSION_DATA})) {
// 2. Send a dummy command to get the protocol synced up ESP_LOGE(TAG, "Error sending version command");
// (this may time out, but that's ok) this->mark_failed();
// 3. Send SAM config command with normal mode without waiting for ready bit (IRQ not initialized yet) return;
// 4. Probably optional, send SAM config again, this time checking ACK and return value }
this->cs_->digital_write(false);
delay(10);
// send dummy firmware version command to get synced up std::vector<uint8_t> version_data;
this->pn532_write_command_check_ack_({0x02}); // get firmware version command if (!this->read_response_(PN532_COMMAND_VERSION_DATA, version_data)) {
// do not actually read any data, this should be OK according to datasheet ESP_LOGE(TAG, "Error getting version");
this->mark_failed();
return;
}
ESP_LOGD(TAG, "Found chip PN5%02X", version_data[0]);
ESP_LOGD(TAG, "Firmware ver. %d.%d", version_data[1], version_data[2]);
this->pn532_write_command_({ if (!this->write_command_({
0x14, // SAM config command PN532_COMMAND_SAMCONFIGURATION,
0x01, // normal mode 0x01, // normal mode
0x14, // zero timeout (not in virtual card mode) 0x14, // zero timeout (not in virtual card mode)
0x01, 0x01,
}); })) {
ESP_LOGE(TAG, "No wakeup ack");
this->mark_failed();
return;
}
// do not wait for ready bit, this is a dummy command std::vector<uint8_t> wakeup_result;
delay(2); if (!this->read_response_(PN532_COMMAND_SAMCONFIGURATION, wakeup_result)) {
// Try to read ACK, if it fails it might be because there's data from a previous power cycle left
this->read_ack_();
// do not wait for ready bit for return data
delay(5);
// read data packet for wakeup result
auto wakeup_result = this->pn532_read_data_();
if (wakeup_result.size() != 1) {
this->error_code_ = WAKEUP_FAILED; this->error_code_ = WAKEUP_FAILED;
this->mark_failed(); this->mark_failed();
return; return;
@ -63,23 +62,21 @@ void PN532::setup() {
// Set up SAM (secure access module) // Set up SAM (secure access module)
uint8_t sam_timeout = std::min(255u, this->update_interval_ / 50); uint8_t sam_timeout = std::min(255u, this->update_interval_ / 50);
bool ret = this->pn532_write_command_check_ack_({ if (!this->write_command_({
0x14, // SAM config command PN532_COMMAND_SAMCONFIGURATION,
0x01, // normal mode 0x01, // normal mode
sam_timeout, // timeout as multiple of 50ms (actually only for virtual card mode, but shouldn't matter) sam_timeout, // timeout as multiple of 50ms (actually only for virtual card mode, but shouldn't matter)
0x01, // Enable IRQ 0x01, // Enable IRQ
}); })) {
if (!ret) {
this->error_code_ = SAM_COMMAND_FAILED; this->error_code_ = SAM_COMMAND_FAILED;
this->mark_failed(); this->mark_failed();
return; return;
} }
auto sam_result = this->pn532_read_data_(); std::vector<uint8_t> sam_result;
if (sam_result.size() != 1) { if (!this->read_response_(PN532_COMMAND_SAMCONFIGURATION, sam_result)) {
ESP_LOGV(TAG, "Invalid SAM result: (%u)", sam_result.size()); // NOLINT ESP_LOGV(TAG, "Invalid SAM result: (%u)", sam_result.size()); // NOLINT
for (auto dat : sam_result) { for (uint8_t dat : sam_result) {
ESP_LOGV(TAG, " 0x%02X", dat); ESP_LOGV(TAG, " 0x%02X", dat);
} }
this->error_code_ = SAM_COMMAND_FAILED; this->error_code_ = SAM_COMMAND_FAILED;
@ -94,12 +91,11 @@ void PN532::update() {
for (auto *obj : this->binary_sensors_) for (auto *obj : this->binary_sensors_)
obj->on_scan_end(); obj->on_scan_end();
bool success = this->pn532_write_command_check_ack_({ if (!this->write_command_({
0x4A, // INLISTPASSIVETARGET PN532_COMMAND_INLISTPASSIVETARGET,
0x01, // max 1 card 0x01, // max 1 card
0x00, // baud rate ISO14443A (106 kbit/s) 0x00, // baud rate ISO14443A (106 kbit/s)
}); })) {
if (!success) {
ESP_LOGW(TAG, "Requesting tag read failed!"); ESP_LOGW(TAG, "Requesting tag read failed!");
this->status_set_warning(); this->status_set_warning();
return; return;
@ -107,53 +103,60 @@ void PN532::update() {
this->status_clear_warning(); this->status_clear_warning();
this->requested_read_ = true; this->requested_read_ = true;
} }
void PN532::loop() { void PN532::loop() {
if (!this->requested_read_ || !this->is_ready_()) if (!this->requested_read_)
return; return;
auto read = this->pn532_read_data_(); std::vector<uint8_t> read;
bool success = this->read_response_(PN532_COMMAND_INLISTPASSIVETARGET, read);
this->requested_read_ = false; this->requested_read_ = false;
if (read.size() <= 2 || read[0] != 0x4B) { if (!success) {
// Something failed // Something failed
this->current_uid_ = {};
this->turn_off_rf_(); this->turn_off_rf_();
return; return;
} }
uint8_t num_targets = read[1]; uint8_t num_targets = read[0];
if (num_targets != 1) { if (num_targets != 1) {
// no tags found or too many // no tags found or too many
this->current_uid_ = {};
this->turn_off_rf_(); this->turn_off_rf_();
return; return;
} }
// const uint8_t target_number = read[2]; uint8_t nfcid_length = read[5];
// const uint16_t sens_res = uint16_t(read[3] << 8) | read[4]; std::vector<uint8_t> nfcid(read.begin() + 6, read.begin() + 6 + nfcid_length);
// const uint8_t sel_res = read[5]; if (read.size() < 6U + nfcid_length) {
const uint8_t nfcid_length = read[6];
const uint8_t *nfcid = &read[7];
if (read.size() < 7U + nfcid_length) {
// oops, pn532 returned invalid data // oops, pn532 returned invalid data
return; return;
} }
bool report = true; bool report = true;
// 1. Go through all triggers for (auto *bin_sens : this->binary_sensors_) {
for (auto *trigger : this->triggers_) if (bin_sens->process(nfcid)) {
trigger->process(nfcid, nfcid_length);
// 2. Find a binary sensor
for (auto *tag : this->binary_sensors_) {
if (tag->process(nfcid, nfcid_length)) {
// 2.1 if found, do not dump
report = false; report = false;
} }
} }
if (nfcid.size() == this->current_uid_.size()) {
bool same_uid = false;
for (uint8_t i = 0; i < nfcid.size(); i++)
same_uid |= nfcid[i] == this->current_uid_[i];
if (same_uid)
return;
}
this->current_uid_ = nfcid;
for (auto *trigger : this->triggers_)
trigger->process(nfcid);
if (report) { if (report) {
char buf[32]; ESP_LOGD(TAG, "Found new tag '%s'", format_uid(nfcid).c_str());
format_uid(buf, nfcid, nfcid_length);
ESP_LOGD(TAG, "Found new tag '%s'", buf);
} }
this->turn_off_rf_(); this->turn_off_rf_();
@ -161,195 +164,158 @@ void PN532::loop() {
void PN532::turn_off_rf_() { void PN532::turn_off_rf_() {
ESP_LOGVV(TAG, "Turning RF field OFF"); ESP_LOGVV(TAG, "Turning RF field OFF");
this->pn532_write_command_check_ack_({ this->write_command_({
0x32, // RFConfiguration PN532_COMMAND_RFCONFIGURATION,
0x1, // RF Field 0x1, // RF Field
0x0 // Off 0x0 // Off
}); });
} }
float PN532::get_setup_priority() const { return setup_priority::DATA; } bool PN532::write_command_(const std::vector<uint8_t> &data) {
std::vector<uint8_t> write_data;
void PN532::pn532_write_command_(const std::vector<uint8_t> &data) {
this->enable();
delay(2);
// First byte, communication mode: Write data
this->write_byte(0x01);
// Preamble // Preamble
this->write_byte(0x00); write_data.push_back(0x00);
// Start code // Start code
this->write_byte(0x00); write_data.push_back(0x00);
this->write_byte(0xFF); write_data.push_back(0xFF);
// Length of message, TFI + data bytes // Length of message, TFI + data bytes
const uint8_t real_length = data.size() + 1; const uint8_t real_length = data.size() + 1;
// LEN // LEN
this->write_byte(real_length); write_data.push_back(real_length);
// LCS (Length checksum) // LCS (Length checksum)
this->write_byte(~real_length + 1); write_data.push_back(~real_length + 1);
// TFI (Frame Identifier, 0xD4 means to PN532, 0xD5 means from PN532) // TFI (Frame Identifier, 0xD4 means to PN532, 0xD5 means from PN532)
this->write_byte(0xD4); write_data.push_back(0xD4);
// calculate checksum, TFI is part of checksum // calculate checksum, TFI is part of checksum
uint8_t checksum = 0xD4; uint8_t checksum = 0xD4;
// DATA // DATA
for (uint8_t dat : data) { for (uint8_t dat : data) {
this->write_byte(dat); write_data.push_back(dat);
checksum += dat; checksum += dat;
} }
// DCS (Data checksum) // DCS (Data checksum)
this->write_byte(~checksum + 1); write_data.push_back(~checksum + 1);
// Postamble // Postamble
this->write_byte(0x00); write_data.push_back(0x00);
this->disable(); this->write_data(write_data);
return this->read_ack_();
} }
bool PN532::pn532_write_command_check_ack_(const std::vector<uint8_t> &data) { bool PN532::read_response_(uint8_t command, std::vector<uint8_t> &data) {
// 1. write command ESP_LOGV(TAG, "Reading response");
this->pn532_write_command_(data); uint8_t len = this->read_response_length_();
if (len == 0) {
// 2. wait for readiness
if (!this->wait_ready_())
return false;
// 3. read ack
if (!this->read_ack_()) {
ESP_LOGV(TAG, "Invalid ACK frame received from PN532!");
return false; return false;
} }
ESP_LOGV(TAG, "Reading response of length %d", len);
if (!this->read_data(data, 6 + len + 2)) {
ESP_LOGD(TAG, "No response data");
return false;
}
if (data[1] != 0x00 && data[2] != 0x00 && data[3] != 0xFF) {
// invalid packet
ESP_LOGV(TAG, "read data invalid preamble!");
return false;
}
bool valid_header = (static_cast<uint8_t>(data[4] + data[5]) == 0 && // LCS, len + lcs = 0
data[6] == 0xD5 && // TFI - frame from PN532 to system controller
data[7] == command + 1); // Correct command response
if (!valid_header) {
ESP_LOGV(TAG, "read data invalid header!");
return false;
}
data.erase(data.begin(), data.begin() + 6); // Remove headers
uint8_t checksum = 0;
for (int i = 0; i < len + 1; i++) {
uint8_t dat = data[i];
checksum += dat;
}
checksum = ~checksum + 1;
if (data[len + 1] != checksum) {
ESP_LOGV(TAG, "read data invalid checksum! %02X != %02X", data[len], checksum);
return false;
}
if (data[len + 2] != 0x00) {
ESP_LOGV(TAG, "read data invalid postamble!");
return false;
}
data.erase(data.begin(), data.begin() + 2); // Remove TFI and command code
data.erase(data.end() - 2, data.end()); // Remove checksum and postamble
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
ESP_LOGD(TAG, "PN532 Data Frame: (%u)", data.size()); // NOLINT
for (uint8_t dat : data) {
ESP_LOGD(TAG, " 0x%02X", dat);
}
#endif
return true; return true;
} }
std::vector<uint8_t> PN532::pn532_read_data_() { uint8_t PN532::read_response_length_() {
this->enable(); std::vector<uint8_t> data;
delay(2); if (!this->read_data(data, 6)) {
// Read data (transmission from the PN532 to the host) return 0;
this->write_byte(0x03); }
// sometimes preamble is not transmitted for whatever reason if (data[1] != 0x00 && data[2] != 0x00 && data[3] != 0xFF) {
// mostly happens during startup.
// just read the first two bytes and check if that is the case
uint8_t header[6];
this->read_array(header, 2);
if (header[0] == 0x00 && header[1] == 0x00) {
// normal packet, preamble included
this->read_array(header + 2, 4);
} else if (header[0] == 0x00 && header[1] == 0xFF) {
// weird packet, preamble skipped; make it look like a normal packet
header[0] = 0x00;
header[1] = 0x00;
header[2] = 0xFF;
this->read_array(header + 3, 3);
} else {
// invalid packet // invalid packet
this->disable();
ESP_LOGV(TAG, "read data invalid preamble!"); ESP_LOGV(TAG, "read data invalid preamble!");
return {}; return 0;
} }
bool valid_header = (header[0] == 0x00 && // preamble bool valid_header = (static_cast<uint8_t>(data[4] + data[5]) == 0 && // LCS, len + lcs = 0
header[1] == 0x00 && // start code data[6] == 0xD5); // TFI - frame from PN532 to system controller
header[2] == 0xFF && static_cast<uint8_t>(header[3] + header[4]) == 0 && // LCS, len + lcs = 0
header[5] == 0xD5 // TFI - frame from PN532 to system controller
);
if (!valid_header) { if (!valid_header) {
this->disable();
ESP_LOGV(TAG, "read data invalid header!"); ESP_LOGV(TAG, "read data invalid header!");
return {}; return 0;
} }
std::vector<uint8_t> ret; this->write_data({0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00}); // NACK - Retransmit last message
// full length of message, including TFI // full length of message, including TFI
const uint8_t full_len = header[3]; uint8_t full_len = data[4];
// length of data, excluding TFI // length of data, excluding TFI
uint8_t len = full_len - 1; uint8_t len = full_len - 1;
if (full_len == 0) if (full_len == 0)
len = 0; len = 0;
return len;
ret.resize(len);
this->read_array(ret.data(), len);
uint8_t checksum = 0xD5;
for (uint8_t dat : ret)
checksum += dat;
checksum = ~checksum + 1;
uint8_t dcs = this->read_byte();
if (dcs != checksum) {
this->disable();
ESP_LOGV(TAG, "read data invalid checksum! %02X != %02X", dcs, checksum);
return {};
}
if (this->read_byte() != 0x00) {
this->disable();
ESP_LOGV(TAG, "read data invalid postamble!");
return {};
}
this->disable();
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
ESP_LOGVV(TAG, "PN532 Data Frame: (%u)", ret.size()); // NOLINT
for (uint8_t dat : ret) {
ESP_LOGVV(TAG, " 0x%02X", dat);
}
#endif
return ret;
} }
bool PN532::is_ready_() {
this->enable();
// First byte, communication mode: Read state
this->write_byte(0x02);
// PN532 returns a single data byte,
// "After having sent a command, the host controller must wait for bit 0 of Status byte equals 1
// before reading the data from the PN532."
bool ret = this->read_byte() == 0x01;
this->disable();
if (ret) {
ESP_LOGVV(TAG, "Chip is ready!");
}
return ret;
}
bool PN532::read_ack_() { bool PN532::read_ack_() {
ESP_LOGVV(TAG, "Reading ACK..."); ESP_LOGVV(TAG, "Reading ACK...");
this->enable();
delay(2);
// "Read data (transmission from the PN532 to the host) "
this->write_byte(0x03);
uint8_t ack[6]; std::vector<uint8_t> data;
memset(ack, 0, sizeof(ack)); if (!this->read_data(data, 6)) {
return false;
}
this->read_array(ack, 6); bool matches = (data[1] == 0x00 && // preamble
this->disable(); data[2] == 0x00 && // start of packet
data[3] == 0xFF && data[4] == 0x00 && // ACK packet code
bool matches = (ack[0] == 0x00 && // preamble data[5] == 0xFF && data[6] == 0x00); // postamble
ack[1] == 0x00 && // start of packet
ack[2] == 0xFF && ack[3] == 0x00 && // ACK packet code
ack[4] == 0xFF && ack[5] == 0x00 // postamble
);
ESP_LOGVV(TAG, "ACK valid: %s", YESNO(matches)); ESP_LOGVV(TAG, "ACK valid: %s", YESNO(matches));
return matches; return matches;
} }
bool PN532::wait_ready_() {
uint32_t start_time = millis(); float PN532::get_setup_priority() const { return setup_priority::DATA; }
while (!this->is_ready_()) {
if (millis() - start_time > 100) {
ESP_LOGE(TAG, "Timed out waiting for readiness from PN532!");
return false;
}
yield();
}
return true;
}
void PN532::dump_config() { void PN532::dump_config() {
ESP_LOGCONFIG(TAG, "PN532:"); ESP_LOGCONFIG(TAG, "PN532:");
@ -364,7 +330,6 @@ void PN532::dump_config() {
break; break;
} }
LOG_PIN(" CS Pin: ", this->cs_);
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
for (auto *child : this->binary_sensors_) { for (auto *child : this->binary_sensors_) {
@ -372,11 +337,11 @@ void PN532::dump_config() {
} }
} }
bool PN532BinarySensor::process(const uint8_t *data, uint8_t len) { bool PN532BinarySensor::process(std::vector<uint8_t> &data) {
if (len != this->uid_.size()) if (data.size() != this->uid_.size())
return false; return false;
for (uint8_t i = 0; i < len; i++) { for (uint8_t i = 0; i < data.size(); i++) {
if (data[i] != this->uid_[i]) if (data[i] != this->uid_[i])
return false; return false;
} }
@ -385,11 +350,7 @@ bool PN532BinarySensor::process(const uint8_t *data, uint8_t len) {
this->found_ = true; this->found_ = true;
return true; return true;
} }
void PN532Trigger::process(const uint8_t *uid, uint8_t uid_length) { void PN532Trigger::process(std::vector<uint8_t> &data) { this->trigger(format_uid(data)); }
char buf[32];
format_uid(buf, uid, uid_length);
this->trigger(std::string(buf));
}
} // namespace pn532 } // namespace pn532
} // namespace esphome } // namespace esphome

View File

@ -3,17 +3,20 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/components/binary_sensor/binary_sensor.h" #include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/spi/spi.h"
namespace esphome { namespace esphome {
namespace pn532 { namespace pn532 {
static const uint8_t PN532_COMMAND_VERSION_DATA = 0x02;
static const uint8_t PN532_COMMAND_SAMCONFIGURATION = 0x14;
static const uint8_t PN532_COMMAND_RFCONFIGURATION = 0x32;
static const uint8_t PN532_COMMAND_INDATAEXCHANGE = 0x40;
static const uint8_t PN532_COMMAND_INLISTPASSIVETARGET = 0x4A;
class PN532BinarySensor; class PN532BinarySensor;
class PN532Trigger; class PN532Trigger;
class PN532 : public PollingComponent, class PN532 : public PollingComponent {
public spi::SPIDevice<spi::BIT_ORDER_LSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
spi::DATA_RATE_1MHZ> {
public: public:
void setup() override; void setup() override;
@ -28,38 +31,19 @@ class PN532 : public PollingComponent,
void register_trigger(PN532Trigger *trig) { this->triggers_.push_back(trig); } void register_trigger(PN532Trigger *trig) { this->triggers_.push_back(trig); }
protected: protected:
/// Write the full command given in data to the PN532
void pn532_write_command_(const std::vector<uint8_t> &data);
bool pn532_write_command_check_ack_(const std::vector<uint8_t> &data);
/** Read a data frame from the PN532 and return the result as a vector.
*
* Note that is_ready needs to be checked first before requesting this method.
*
* On failure, an empty vector is returned.
*/
std::vector<uint8_t> pn532_read_data_();
/** Checks if the PN532 has set its ready status flag.
*
* Procedure goes as follows:
* - Host sends command to PN532 "write data"
* - Wait for readiness (until PN532 has processed command) by polling "read status"/is_ready_
* - Parse ACK/NACK frame with "read data" byte
*
* - If data required, wait until device reports readiness again
* - Then call "read data" and read certain number of bytes (length is given at offset 4 of frame)
*/
bool is_ready_();
bool wait_ready_();
bool read_ack_();
void turn_off_rf_(); void turn_off_rf_();
bool write_command_(const std::vector<uint8_t> &data);
bool read_response_(uint8_t command, std::vector<uint8_t> &data);
bool read_ack_();
uint8_t read_response_length_();
virtual bool write_data(const std::vector<uint8_t> &data) = 0;
virtual bool read_data(std::vector<uint8_t> &data, uint8_t len) = 0;
bool requested_read_{false}; bool requested_read_{false};
std::vector<PN532BinarySensor *> binary_sensors_; std::vector<PN532BinarySensor *> binary_sensors_;
std::vector<PN532Trigger *> triggers_; std::vector<PN532Trigger *> triggers_;
std::vector<uint8_t> current_uid_;
enum PN532Error { enum PN532Error {
NONE = 0, NONE = 0,
WAKEUP_FAILED, WAKEUP_FAILED,
@ -71,7 +55,7 @@ class PN532BinarySensor : public binary_sensor::BinarySensor {
public: public:
void set_uid(const std::vector<uint8_t> &uid) { uid_ = uid; } void set_uid(const std::vector<uint8_t> &uid) { uid_ = uid; }
bool process(const uint8_t *data, uint8_t len); bool process(std::vector<uint8_t> &data);
void on_scan_end() { void on_scan_end() {
if (!this->found_) { if (!this->found_) {
@ -87,7 +71,7 @@ class PN532BinarySensor : public binary_sensor::BinarySensor {
class PN532Trigger : public Trigger<std::string> { class PN532Trigger : public Trigger<std::string> {
public: public:
void process(const uint8_t *uid, uint8_t uid_length); void process(std::vector<uint8_t> &data);
}; };
} // namespace pn532 } // namespace pn532

View File

@ -0,0 +1,21 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, pn532
from esphome.const import CONF_ID
AUTO_LOAD = ['pn532']
CODEOWNERS = ['@OttoWinter', '@jesserockz']
DEPENDENCIES = ['i2c']
pn532_i2c_ns = cg.esphome_ns.namespace('pn532_i2c')
PN532I2C = pn532_i2c_ns.class_('PN532I2C', pn532.PN532, i2c.I2CDevice)
CONFIG_SCHEMA = cv.All(pn532.PN532_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(PN532I2C),
}).extend(i2c.i2c_device_schema(0x24)))
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield pn532.setup_pn532(var, config)
yield i2c.register_i2c_device(var, config)

View File

@ -0,0 +1,45 @@
#include "pn532_i2c.h"
#include "esphome/core/log.h"
// Based on:
// - https://cdn-shop.adafruit.com/datasheets/PN532C106_Application+Note_v1.2.pdf
// - https://www.nxp.com/docs/en/nxp/application-notes/AN133910.pdf
// - https://www.nxp.com/docs/en/nxp/application-notes/153710.pdf
namespace esphome {
namespace pn532_i2c {
static const char *TAG = "pn532_i2c";
bool PN532I2C::write_data(const std::vector<uint8_t> &data) { return this->write_bytes_raw(data.data(), data.size()); }
bool PN532I2C::read_data(std::vector<uint8_t> &data, uint8_t len) {
delay(5);
std::vector<uint8_t> ready;
ready.resize(1);
uint32_t start_time = millis();
while (true) {
if (this->read_bytes_raw(ready.data(), 1)) {
if (ready[0] == 0x01)
break;
}
if (millis() - start_time > 100) {
ESP_LOGV(TAG, "Timed out waiting for readiness from PN532!");
return false;
}
}
data.resize(len + 1);
this->read_bytes_raw(data.data(), len + 1);
return true;
}
void PN532I2C::dump_config() {
PN532::dump_config();
LOG_I2C_DEVICE(this);
}
} // namespace pn532_i2c
} // namespace esphome

View File

@ -0,0 +1,20 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/pn532/pn532.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace pn532_i2c {
class PN532I2C : public pn532::PN532, public i2c::I2CDevice {
public:
void dump_config() override;
protected:
bool write_data(const std::vector<uint8_t> &data) override;
bool read_data(std::vector<uint8_t> &data, uint8_t len) override;
};
} // namespace pn532_i2c
} // namespace esphome

View File

@ -0,0 +1,21 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import spi, pn532
from esphome.const import CONF_ID
AUTO_LOAD = ['pn532']
CODEOWNERS = ['@OttoWinter', '@jesserockz']
DEPENDENCIES = ['spi']
pn532_spi_ns = cg.esphome_ns.namespace('pn532_spi')
PN532Spi = pn532_spi_ns.class_('PN532Spi', pn532.PN532, spi.SPIDevice)
CONFIG_SCHEMA = cv.All(pn532.PN532_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(PN532Spi),
}).extend(spi.spi_device_schema(cs_pin_required=True)))
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield pn532.setup_pn532(var, config)
yield spi.register_spi_device(var, config)

View File

@ -0,0 +1,69 @@
#include "pn532_spi.h"
#include "esphome/core/log.h"
// Based on:
// - https://cdn-shop.adafruit.com/datasheets/PN532C106_Application+Note_v1.2.pdf
// - https://www.nxp.com/docs/en/nxp/application-notes/AN133910.pdf
// - https://www.nxp.com/docs/en/nxp/application-notes/153710.pdf
namespace esphome {
namespace pn532_spi {
static const char *TAG = "pn532_spi";
void PN532Spi::setup() {
ESP_LOGI(TAG, "PN532Spi setup started!");
this->spi_setup();
this->cs_->digital_write(false);
delay(10);
ESP_LOGI(TAG, "SPI setup finished!");
PN532::setup();
}
bool PN532Spi::write_data(const std::vector<uint8_t> &data) {
this->enable();
delay(2);
// First byte, communication mode: Write data
this->write_byte(0x01);
this->write_array(data.data(), data.size());
this->disable();
return true;
}
bool PN532Spi::read_data(std::vector<uint8_t> &data, uint8_t len) {
this->enable();
// First byte, communication mode: Read state
this->write_byte(0x02);
uint32_t start_time = millis();
while (true) {
if (this->read_byte() & 0x01)
break;
if (millis() - start_time > 100) {
this->disable();
ESP_LOGV(TAG, "Timed out waiting for readiness from PN532!");
return false;
}
}
// Read data (transmission from the PN532 to the host)
this->write_byte(0x03);
data.resize(len);
this->read_array(data.data(), len);
this->disable();
data.insert(data.begin(), 0x01);
return true;
};
void PN532Spi::dump_config() {
PN532::dump_config();
LOG_PIN(" CS Pin: ", this->cs_);
}
} // namespace pn532_spi
} // namespace esphome

View File

@ -0,0 +1,24 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/pn532/pn532.h"
#include "esphome/components/spi/spi.h"
namespace esphome {
namespace pn532_spi {
class PN532Spi : public pn532::PN532,
public spi::SPIDevice<spi::BIT_ORDER_LSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
spi::DATA_RATE_1MHZ> {
public:
void setup() override;
void dump_config() override;
protected:
bool write_data(const std::vector<uint8_t> &data) override;
bool read_data(std::vector<uint8_t> &data, uint8_t len) override;
};
} // namespace pn532_spi
} // namespace esphome

View File

@ -1645,7 +1645,7 @@ remote_receiver:
status_led: status_led:
pin: GPIO2 pin: GPIO2
pn532: pn532_spi:
cs_pin: GPIO23 cs_pin: GPIO23
update_interval: 1s update_interval: 1s
on_tag: on_tag:
@ -1655,6 +1655,8 @@ pn532:
topic: the/topic topic: the/topic
payload: !lambda 'return x;' payload: !lambda 'return x;'
pn532_i2c:
rdm6300: rdm6300:
rc522_spi: rc522_spi: