From 0059a6de462dcbc0892efb0761cddbde8e08b7e6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sun, 1 Nov 2020 11:55:48 +1300 Subject: [PATCH] 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 --- CODEOWNERS | 4 +- esphome/components/pn532/__init__.py | 27 +- esphome/components/pn532/binary_sensor.py | 4 +- esphome/components/pn532/pn532.cpp | 387 +++++++++------------ esphome/components/pn532/pn532.h | 50 +-- esphome/components/pn532_i2c/__init__.py | 21 ++ esphome/components/pn532_i2c/pn532_i2c.cpp | 45 +++ esphome/components/pn532_i2c/pn532_i2c.h | 20 ++ esphome/components/pn532_spi/__init__.py | 21 ++ esphome/components/pn532_spi/pn532_spi.cpp | 69 ++++ esphome/components/pn532_spi/pn532_spi.h | 24 ++ tests/test1.yaml | 4 +- 12 files changed, 415 insertions(+), 261 deletions(-) create mode 100644 esphome/components/pn532_i2c/__init__.py create mode 100644 esphome/components/pn532_i2c/pn532_i2c.cpp create mode 100644 esphome/components/pn532_i2c/pn532_i2c.h create mode 100644 esphome/components/pn532_spi/__init__.py create mode 100644 esphome/components/pn532_spi/pn532_spi.cpp create mode 100644 esphome/components/pn532_spi/pn532_spi.h diff --git a/CODEOWNERS b/CODEOWNERS index 157e05a161..488753e1d0 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -43,7 +43,9 @@ esphome/components/network/* @esphome/core esphome/components/ota/* @esphome/core esphome/components/output/* @esphome/core 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/rc522_spi/* @glmnet esphome/components/restart/* @esphome/core diff --git a/esphome/components/pn532/__init__.py b/esphome/components/pn532/__init__.py index cbd41d11cc..02215e2f5a 100644 --- a/esphome/components/pn532/__init__.py +++ b/esphome/components/pn532/__init__.py @@ -1,30 +1,37 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation -from esphome.components import spi -from esphome.const import CONF_ID, CONF_ON_TAG, CONF_TRIGGER_ID +from esphome.const import CONF_ON_TAG, CONF_TRIGGER_ID +from esphome.core import coroutine -CODEOWNERS = ['@OttoWinter'] -DEPENDENCIES = ['spi'] +CODEOWNERS = ['@OttoWinter', '@jesserockz'] AUTO_LOAD = ['binary_sensor'] MULTI_CONF = True +CONF_PN532_ID = 'pn532_id' + 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)) -CONFIG_SCHEMA = cv.Schema({ +PN532_SCHEMA = cv.Schema({ cv.GenerateID(): cv.declare_id(PN532), cv.Optional(CONF_ON_TAG): automation.validate_automation({ 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): - var = cg.new_Pvariable(config[CONF_ID]) +def CONFIG_SCHEMA(conf): + 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 spi.register_spi_device(var, config) for conf in config.get(CONF_ON_TAG, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) diff --git a/esphome/components/pn532/binary_sensor.py b/esphome/components/pn532/binary_sensor.py index 46a7ac03c8..2404bc9e99 100644 --- a/esphome/components/pn532/binary_sensor.py +++ b/esphome/components/pn532/binary_sensor.py @@ -3,12 +3,10 @@ import esphome.config_validation as cv from esphome.components import binary_sensor from esphome.const import CONF_UID, CONF_ID from esphome.core import HexInt -from . import pn532_ns, PN532 +from . import pn532_ns, PN532, CONF_PN532_ID DEPENDENCIES = ['pn532'] -CONF_PN532_ID = 'pn532_id' - def validate_uid(value): value = cv.string_strict(value) diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index 792d92a6ac..a9601185bb 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -11,51 +11,50 @@ namespace 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 &uid) { + char buf[32]; 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"; - if (i + 1 < uid_length) + if (i + 1 < uid.size()) format = "%02X-"; offset += sprintf(buf + offset, format, uid[i]); } + return std::string(buf); } void PN532::setup() { ESP_LOGCONFIG(TAG, "Setting up PN532..."); - this->spi_setup(); - // Wake the chip up from power down - // 1. Enable the SS line for at least 2ms - // 2. Send a dummy command to get the protocol synced up - // (this may time out, but that's ok) - // 3. Send SAM config command with normal mode without waiting for ready bit (IRQ not initialized yet) - // 4. Probably optional, send SAM config again, this time checking ACK and return value - this->cs_->digital_write(false); - delay(10); + // Get version data + if (!this->write_command_({PN532_COMMAND_VERSION_DATA})) { + ESP_LOGE(TAG, "Error sending version command"); + this->mark_failed(); + return; + } - // send dummy firmware version command to get synced up - this->pn532_write_command_check_ack_({0x02}); // get firmware version command - // do not actually read any data, this should be OK according to datasheet + std::vector version_data; + if (!this->read_response_(PN532_COMMAND_VERSION_DATA, version_data)) { + 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_({ - 0x14, // SAM config command - 0x01, // normal mode - 0x14, // zero timeout (not in virtual card mode) - 0x01, - }); + if (!this->write_command_({ + PN532_COMMAND_SAMCONFIGURATION, + 0x01, // normal mode + 0x14, // zero timeout (not in virtual card mode) + 0x01, + })) { + ESP_LOGE(TAG, "No wakeup ack"); + this->mark_failed(); + return; + } - // do not wait for ready bit, this is a dummy command - delay(2); - - // 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) { + std::vector wakeup_result; + if (!this->read_response_(PN532_COMMAND_SAMCONFIGURATION, wakeup_result)) { this->error_code_ = WAKEUP_FAILED; this->mark_failed(); return; @@ -63,23 +62,21 @@ void PN532::setup() { // Set up SAM (secure access module) uint8_t sam_timeout = std::min(255u, this->update_interval_ / 50); - bool ret = this->pn532_write_command_check_ack_({ - 0x14, // SAM config command - 0x01, // normal mode - sam_timeout, // timeout as multiple of 50ms (actually only for virtual card mode, but shouldn't matter) - 0x01, // Enable IRQ - }); - - if (!ret) { + if (!this->write_command_({ + PN532_COMMAND_SAMCONFIGURATION, + 0x01, // normal mode + sam_timeout, // timeout as multiple of 50ms (actually only for virtual card mode, but shouldn't matter) + 0x01, // Enable IRQ + })) { this->error_code_ = SAM_COMMAND_FAILED; this->mark_failed(); return; } - auto sam_result = this->pn532_read_data_(); - if (sam_result.size() != 1) { + std::vector sam_result; + if (!this->read_response_(PN532_COMMAND_SAMCONFIGURATION, sam_result)) { 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); } this->error_code_ = SAM_COMMAND_FAILED; @@ -94,12 +91,11 @@ void PN532::update() { for (auto *obj : this->binary_sensors_) obj->on_scan_end(); - bool success = this->pn532_write_command_check_ack_({ - 0x4A, // INLISTPASSIVETARGET - 0x01, // max 1 card - 0x00, // baud rate ISO14443A (106 kbit/s) - }); - if (!success) { + if (!this->write_command_({ + PN532_COMMAND_INLISTPASSIVETARGET, + 0x01, // max 1 card + 0x00, // baud rate ISO14443A (106 kbit/s) + })) { ESP_LOGW(TAG, "Requesting tag read failed!"); this->status_set_warning(); return; @@ -107,53 +103,60 @@ void PN532::update() { this->status_clear_warning(); this->requested_read_ = true; } + void PN532::loop() { - if (!this->requested_read_ || !this->is_ready_()) + if (!this->requested_read_) return; - auto read = this->pn532_read_data_(); + std::vector read; + bool success = this->read_response_(PN532_COMMAND_INLISTPASSIVETARGET, read); + this->requested_read_ = false; - if (read.size() <= 2 || read[0] != 0x4B) { + if (!success) { // Something failed + this->current_uid_ = {}; this->turn_off_rf_(); return; } - uint8_t num_targets = read[1]; + uint8_t num_targets = read[0]; if (num_targets != 1) { // no tags found or too many + this->current_uid_ = {}; this->turn_off_rf_(); return; } - // const uint8_t target_number = read[2]; - // const uint16_t sens_res = uint16_t(read[3] << 8) | read[4]; - // const uint8_t sel_res = read[5]; - const uint8_t nfcid_length = read[6]; - const uint8_t *nfcid = &read[7]; - if (read.size() < 7U + nfcid_length) { + uint8_t nfcid_length = read[5]; + std::vector nfcid(read.begin() + 6, read.begin() + 6 + nfcid_length); + if (read.size() < 6U + nfcid_length) { // oops, pn532 returned invalid data return; } bool report = true; - // 1. Go through all triggers - for (auto *trigger : this->triggers_) - 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 + for (auto *bin_sens : this->binary_sensors_) { + if (bin_sens->process(nfcid)) { 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) { - char buf[32]; - format_uid(buf, nfcid, nfcid_length); - ESP_LOGD(TAG, "Found new tag '%s'", buf); + ESP_LOGD(TAG, "Found new tag '%s'", format_uid(nfcid).c_str()); } this->turn_off_rf_(); @@ -161,195 +164,158 @@ void PN532::loop() { void PN532::turn_off_rf_() { ESP_LOGVV(TAG, "Turning RF field OFF"); - this->pn532_write_command_check_ack_({ - 0x32, // RFConfiguration - 0x1, // RF Field - 0x0 // Off + this->write_command_({ + PN532_COMMAND_RFCONFIGURATION, + 0x1, // RF Field + 0x0 // Off }); } -float PN532::get_setup_priority() const { return setup_priority::DATA; } - -void PN532::pn532_write_command_(const std::vector &data) { - this->enable(); - delay(2); - // First byte, communication mode: Write data - this->write_byte(0x01); - +bool PN532::write_command_(const std::vector &data) { + std::vector write_data; // Preamble - this->write_byte(0x00); + write_data.push_back(0x00); // Start code - this->write_byte(0x00); - this->write_byte(0xFF); + write_data.push_back(0x00); + write_data.push_back(0xFF); // Length of message, TFI + data bytes const uint8_t real_length = data.size() + 1; // LEN - this->write_byte(real_length); + write_data.push_back(real_length); // 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) - this->write_byte(0xD4); + write_data.push_back(0xD4); // calculate checksum, TFI is part of checksum uint8_t checksum = 0xD4; // DATA for (uint8_t dat : data) { - this->write_byte(dat); + write_data.push_back(dat); checksum += dat; } // DCS (Data checksum) - this->write_byte(~checksum + 1); + write_data.push_back(~checksum + 1); // 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 &data) { - // 1. write command - this->pn532_write_command_(data); - - // 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!"); +bool PN532::read_response_(uint8_t command, std::vector &data) { + ESP_LOGV(TAG, "Reading response"); + uint8_t len = this->read_response_length_(); + if (len == 0) { 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(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; } -std::vector PN532::pn532_read_data_() { - this->enable(); - delay(2); - // Read data (transmission from the PN532 to the host) - this->write_byte(0x03); +uint8_t PN532::read_response_length_() { + std::vector data; + if (!this->read_data(data, 6)) { + return 0; + } - // sometimes preamble is not transmitted for whatever reason - // 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 { + if (data[1] != 0x00 && data[2] != 0x00 && data[3] != 0xFF) { // invalid packet - this->disable(); ESP_LOGV(TAG, "read data invalid preamble!"); - return {}; + return 0; } - bool valid_header = (header[0] == 0x00 && // preamble - header[1] == 0x00 && // start code - header[2] == 0xFF && static_cast(header[3] + header[4]) == 0 && // LCS, len + lcs = 0 - header[5] == 0xD5 // TFI - frame from PN532 to system controller - ); + bool valid_header = (static_cast(data[4] + data[5]) == 0 && // LCS, len + lcs = 0 + data[6] == 0xD5); // TFI - frame from PN532 to system controller + if (!valid_header) { - this->disable(); ESP_LOGV(TAG, "read data invalid header!"); - return {}; + return 0; } - std::vector ret; + this->write_data({0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00}); // NACK - Retransmit last message + // full length of message, including TFI - const uint8_t full_len = header[3]; + uint8_t full_len = data[4]; // length of data, excluding TFI uint8_t len = full_len - 1; if (full_len == 0) len = 0; - - 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; + return len; } -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_() { 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]; - memset(ack, 0, sizeof(ack)); + std::vector data; + if (!this->read_data(data, 6)) { + return false; + } - this->read_array(ack, 6); - this->disable(); - - bool matches = (ack[0] == 0x00 && // preamble - ack[1] == 0x00 && // start of packet - ack[2] == 0xFF && ack[3] == 0x00 && // ACK packet code - ack[4] == 0xFF && ack[5] == 0x00 // postamble - ); + bool matches = (data[1] == 0x00 && // preamble + data[2] == 0x00 && // start of packet + data[3] == 0xFF && data[4] == 0x00 && // ACK packet code + data[5] == 0xFF && data[6] == 0x00); // postamble ESP_LOGVV(TAG, "ACK valid: %s", YESNO(matches)); return matches; } -bool PN532::wait_ready_() { - uint32_t start_time = millis(); - while (!this->is_ready_()) { - if (millis() - start_time > 100) { - ESP_LOGE(TAG, "Timed out waiting for readiness from PN532!"); - return false; - } - yield(); - } - return true; -} + +float PN532::get_setup_priority() const { return setup_priority::DATA; } void PN532::dump_config() { ESP_LOGCONFIG(TAG, "PN532:"); @@ -364,7 +330,6 @@ void PN532::dump_config() { break; } - LOG_PIN(" CS Pin: ", this->cs_); LOG_UPDATE_INTERVAL(this); for (auto *child : this->binary_sensors_) { @@ -372,11 +337,11 @@ void PN532::dump_config() { } } -bool PN532BinarySensor::process(const uint8_t *data, uint8_t len) { - if (len != this->uid_.size()) +bool PN532BinarySensor::process(std::vector &data) { + if (data.size() != this->uid_.size()) 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]) return false; } @@ -385,11 +350,7 @@ bool PN532BinarySensor::process(const uint8_t *data, uint8_t len) { this->found_ = true; return true; } -void PN532Trigger::process(const uint8_t *uid, uint8_t uid_length) { - char buf[32]; - format_uid(buf, uid, uid_length); - this->trigger(std::string(buf)); -} +void PN532Trigger::process(std::vector &data) { this->trigger(format_uid(data)); } } // namespace pn532 } // namespace esphome diff --git a/esphome/components/pn532/pn532.h b/esphome/components/pn532/pn532.h index 3a734b7ba2..af49a02400 100644 --- a/esphome/components/pn532/pn532.h +++ b/esphome/components/pn532/pn532.h @@ -3,17 +3,20 @@ #include "esphome/core/component.h" #include "esphome/core/automation.h" #include "esphome/components/binary_sensor/binary_sensor.h" -#include "esphome/components/spi/spi.h" namespace esphome { 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 PN532Trigger; -class PN532 : public PollingComponent, - public spi::SPIDevice { +class PN532 : public PollingComponent { public: void setup() override; @@ -28,38 +31,19 @@ class PN532 : public PollingComponent, void register_trigger(PN532Trigger *trig) { this->triggers_.push_back(trig); } protected: - /// Write the full command given in data to the PN532 - void pn532_write_command_(const std::vector &data); - bool pn532_write_command_check_ack_(const std::vector &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 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_(); + bool write_command_(const std::vector &data); + bool read_response_(uint8_t command, std::vector &data); + bool read_ack_(); + uint8_t read_response_length_(); + + virtual bool write_data(const std::vector &data) = 0; + virtual bool read_data(std::vector &data, uint8_t len) = 0; bool requested_read_{false}; std::vector binary_sensors_; std::vector triggers_; + std::vector current_uid_; enum PN532Error { NONE = 0, WAKEUP_FAILED, @@ -71,7 +55,7 @@ class PN532BinarySensor : public binary_sensor::BinarySensor { public: void set_uid(const std::vector &uid) { uid_ = uid; } - bool process(const uint8_t *data, uint8_t len); + bool process(std::vector &data); void on_scan_end() { if (!this->found_) { @@ -87,7 +71,7 @@ class PN532BinarySensor : public binary_sensor::BinarySensor { class PN532Trigger : public Trigger { public: - void process(const uint8_t *uid, uint8_t uid_length); + void process(std::vector &data); }; } // namespace pn532 diff --git a/esphome/components/pn532_i2c/__init__.py b/esphome/components/pn532_i2c/__init__.py new file mode 100644 index 0000000000..f1c50adf45 --- /dev/null +++ b/esphome/components/pn532_i2c/__init__.py @@ -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) diff --git a/esphome/components/pn532_i2c/pn532_i2c.cpp b/esphome/components/pn532_i2c/pn532_i2c.cpp new file mode 100644 index 0000000000..b959e764e7 --- /dev/null +++ b/esphome/components/pn532_i2c/pn532_i2c.cpp @@ -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 &data) { return this->write_bytes_raw(data.data(), data.size()); } + +bool PN532I2C::read_data(std::vector &data, uint8_t len) { + delay(5); + + std::vector 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 diff --git a/esphome/components/pn532_i2c/pn532_i2c.h b/esphome/components/pn532_i2c/pn532_i2c.h new file mode 100644 index 0000000000..23cb00bb10 --- /dev/null +++ b/esphome/components/pn532_i2c/pn532_i2c.h @@ -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 &data) override; + bool read_data(std::vector &data, uint8_t len) override; +}; + +} // namespace pn532_i2c +} // namespace esphome diff --git a/esphome/components/pn532_spi/__init__.py b/esphome/components/pn532_spi/__init__.py new file mode 100644 index 0000000000..e378b96c2d --- /dev/null +++ b/esphome/components/pn532_spi/__init__.py @@ -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) diff --git a/esphome/components/pn532_spi/pn532_spi.cpp b/esphome/components/pn532_spi/pn532_spi.cpp new file mode 100644 index 0000000000..3da799fb24 --- /dev/null +++ b/esphome/components/pn532_spi/pn532_spi.cpp @@ -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 &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 &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 diff --git a/esphome/components/pn532_spi/pn532_spi.h b/esphome/components/pn532_spi/pn532_spi.h new file mode 100644 index 0000000000..967b8a66cf --- /dev/null +++ b/esphome/components/pn532_spi/pn532_spi.h @@ -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 { + public: + void setup() override; + + void dump_config() override; + + protected: + bool write_data(const std::vector &data) override; + bool read_data(std::vector &data, uint8_t len) override; +}; + +} // namespace pn532_spi +} // namespace esphome diff --git a/tests/test1.yaml b/tests/test1.yaml index 6a2f95c355..f5371eeab6 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1645,7 +1645,7 @@ remote_receiver: status_led: pin: GPIO2 -pn532: +pn532_spi: cs_pin: GPIO23 update_interval: 1s on_tag: @@ -1655,6 +1655,8 @@ pn532: topic: the/topic payload: !lambda 'return x;' +pn532_i2c: + rdm6300: rc522_spi: