From fbc1b3e316590776dc87dc90e1a98bb781517fde Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Tue, 12 Jan 2021 10:13:53 -0300 Subject: [PATCH] Add rc522 i2c (#1432) * split to spi and i2c * fix binary_sensor * i2c comms ready * fix rc522_spi binary sensor compat * lint * lint * add test and codeowners * fix refactor --- CODEOWNERS | 2 + esphome/components/rc522/__init__.py | 38 + esphome/components/rc522/binary_sensor.py | 43 + esphome/components/rc522/rc522.cpp | 758 +++++++++++++++++ esphome/components/rc522/rc522.h | 284 +++++++ esphome/components/rc522_i2c/__init__.py | 22 + esphome/components/rc522_i2c/rc522_i2c.cpp | 99 +++ esphome/components/rc522_i2c/rc522_i2c.h | 42 + esphome/components/rc522_spi/__init__.py | 34 +- esphome/components/rc522_spi/binary_sensor.py | 43 +- esphome/components/rc522_spi/rc522_spi.cpp | 762 +----------------- esphome/components/rc522_spi/rc522_spi.h | 282 +------ tests/test1.yaml | 42 +- 13 files changed, 1359 insertions(+), 1092 deletions(-) create mode 100644 esphome/components/rc522/__init__.py create mode 100644 esphome/components/rc522/binary_sensor.py create mode 100644 esphome/components/rc522/rc522.cpp create mode 100644 esphome/components/rc522/rc522.h create mode 100644 esphome/components/rc522_i2c/__init__.py create mode 100644 esphome/components/rc522_i2c/rc522_i2c.cpp create mode 100644 esphome/components/rc522_i2c/rc522_i2c.h diff --git a/CODEOWNERS b/CODEOWNERS index 95729bae8d..fb287b1cc9 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -55,6 +55,8 @@ 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/* @glmnet +esphome/components/rc522_i2c/* @glmnet esphome/components/rc522_spi/* @glmnet esphome/components/restart/* @esphome/core esphome/components/rf_bridge/* @jesserockz diff --git a/esphome/components/rc522/__init__.py b/esphome/components/rc522/__init__.py new file mode 100644 index 0000000000..7b4df37ce2 --- /dev/null +++ b/esphome/components/rc522/__init__.py @@ -0,0 +1,38 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation, pins +from esphome.components import i2c +from esphome.const import CONF_ON_TAG, CONF_TRIGGER_ID, CONF_RESET_PIN +from esphome.core import coroutine + +CODEOWNERS = ['@glmnet'] +AUTO_LOAD = ['binary_sensor'] +MULTI_CONF = True + +CONF_RC522_ID = 'rc522_id' + +rc522_ns = cg.esphome_ns.namespace('rc522') +RC522 = rc522_ns.class_('RC522', cg.PollingComponent, i2c.I2CDevice) +RC522Trigger = rc522_ns.class_('RC522Trigger', automation.Trigger.template(cg.std_string)) + +RC522_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.declare_id(RC522), + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_ON_TAG): automation.validate_automation({ + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RC522Trigger), + }), +}).extend(cv.polling_component_schema('1s')) + + +@coroutine +def setup_rc522(var, config): + yield cg.register_component(var, config) + + if CONF_RESET_PIN in config: + reset = yield cg.gpio_pin_expression(config[CONF_RESET_PIN]) + cg.add(var.set_reset_pin(reset)) + + for conf in config.get(CONF_ON_TAG, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) + cg.add(var.register_trigger(trigger)) + yield automation.build_automation(trigger, [(cg.std_string, 'x')], conf) diff --git a/esphome/components/rc522/binary_sensor.py b/esphome/components/rc522/binary_sensor.py new file mode 100644 index 0000000000..675db2f130 --- /dev/null +++ b/esphome/components/rc522/binary_sensor.py @@ -0,0 +1,43 @@ +import esphome.codegen as cg +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, coroutine +from . import rc522_ns, RC522, CONF_RC522_ID + +DEPENDENCIES = ['rc522'] + + +def validate_uid(value): + value = cv.string_strict(value) + for x in value.split('-'): + if len(x) != 2: + raise cv.Invalid("Each part (separated by '-') of the UID must be two characters " + "long.") + try: + x = int(x, 16) + except ValueError as err: + raise cv.Invalid("Valid characters for parts of a UID are 0123456789ABCDEF.") from err + if x < 0 or x > 255: + raise cv.Invalid("Valid values for UID parts (separated by '-') are 00 to FF") + return value + + +RC522BinarySensor = rc522_ns.class_('RC522BinarySensor', binary_sensor.BinarySensor) + +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(RC522BinarySensor), + cv.GenerateID(CONF_RC522_ID): cv.use_id(RC522), + cv.Required(CONF_UID): validate_uid, +}) + + +@coroutine +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield binary_sensor.register_binary_sensor(var, config) + + hub = yield cg.get_variable(config[CONF_RC522_ID]) + cg.add(hub.register_tag(var)) + addr = [HexInt(int(x, 16)) for x in config[CONF_UID].split('-')] + cg.add(var.set_uid(addr)) diff --git a/esphome/components/rc522/rc522.cpp b/esphome/components/rc522/rc522.cpp new file mode 100644 index 0000000000..ff8d18e3ea --- /dev/null +++ b/esphome/components/rc522/rc522.cpp @@ -0,0 +1,758 @@ +#include "rc522.h" +#include "esphome/core/log.h" + +// Based on: +// - https://github.com/miguelbalboa/rfid + +namespace esphome { +namespace rc522 { + +static const char *TAG = "rc522"; + +static const uint8_t RESET_COUNT = 5; + +void format_uid(char *buf, const uint8_t *uid, uint8_t uid_length) { + int offset = 0; + for (uint8_t i = 0; i < uid_length; i++) { + const char *format = "%02X"; + if (i + 1 < uid_length) + format = "%02X-"; + offset += sprintf(buf + offset, format, uid[i]); + } +} + +void RC522::setup() { + initialize_pending_ = true; + // Pull device out of power down / reset state. + + // First set the resetPowerDownPin as digital input, to check the MFRC522 power down mode. + if (reset_pin_ != nullptr) { + reset_pin_->pin_mode(INPUT); + + if (reset_pin_->digital_read() == LOW) { // The MFRC522 chip is in power down mode. + ESP_LOGV(TAG, "Power down mode detected. Hard resetting..."); + reset_pin_->pin_mode(OUTPUT); // Now set the resetPowerDownPin as digital output. + reset_pin_->digital_write(LOW); // Make sure we have a clean LOW state. + delayMicroseconds(2); // 8.8.1 Reset timing requirements says about 100ns. Let us be generous: 2μsl + reset_pin_->digital_write(HIGH); // Exit power down mode. This triggers a hard reset. + // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs. + // Let us be generous: 50ms. + reset_timeout_ = millis(); + return; + } + } + + // Setup a soft reset + reset_count_ = RESET_COUNT; + reset_timeout_ = millis(); +} + +void RC522::initialize_() { + // Per originall code, wait 50 ms + if (millis() - reset_timeout_ < 50) + return; + + // Reset baud rates + ESP_LOGV(TAG, "Initialize"); + + pcd_write_register(TX_MODE_REG, 0x00); + pcd_write_register(RX_MODE_REG, 0x00); + // Reset ModWidthReg + pcd_write_register(MOD_WIDTH_REG, 0x26); + + // When communicating with a PICC we need a timeout if something goes wrong. + // f_timer = 13.56 MHz / (2*TPreScaler+1) where TPreScaler = [TPrescaler_Hi:TPrescaler_Lo]. + // TPrescaler_Hi are the four low bits in TModeReg. TPrescaler_Lo is TPrescalerReg. + pcd_write_register(T_MODE_REG, 0x80); // TAuto=1; timer starts automatically at the end of the transmission in all + // communication modes at all speeds + + // TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9 = 169 => f_timer=40kHz, ie a timer period of 25μs. + pcd_write_register(T_PRESCALER_REG, 0xA9); + pcd_write_register(T_RELOAD_REG_H, 0x03); // Reload timer with 0x3E8 = 1000, ie 25ms before timeout. + pcd_write_register(T_RELOAD_REG_L, 0xE8); + + // Default 0x00. Force a 100 % ASK modulation independent of the ModGsPReg register setting + pcd_write_register(TX_ASK_REG, 0x40); + pcd_write_register(MODE_REG, 0x3D); // Default 0x3F. Set the preset value for the CRC coprocessor for the CalcCRC + // command to 0x6363 (ISO 14443-3 part 6.2.4) + pcd_antenna_on_(); // Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset) + + initialize_pending_ = false; +} + +void RC522::dump_config() { + ESP_LOGCONFIG(TAG, "RC522:"); + switch (this->error_code_) { + case NONE: + break; + case RESET_FAILED: + ESP_LOGE(TAG, "Reset command failed!"); + break; + } + + LOG_PIN(" RESET Pin: ", this->reset_pin_); + + LOG_UPDATE_INTERVAL(this); + + for (auto *child : this->binary_sensors_) { + LOG_BINARY_SENSOR(" ", "Tag", child); + } +} + +void RC522::loop() { + // First check reset is needed + if (reset_count_ > 0) { + pcd_reset_(); + return; + } + if (initialize_pending_) { + initialize_(); + return; + } + + if (millis() - update_wait_ < this->update_interval_) + return; + + auto status = picc_is_new_card_present_(); + + static StatusCode LAST_STATUS = StatusCode::STATUS_OK; + + if (status != LAST_STATUS) { + ESP_LOGD(TAG, "Status is now: %d", status); + LAST_STATUS = status; + } + + if (status == STATUS_ERROR) // No card + { + // ESP_LOGE(TAG, "Error"); + // mark_failed(); + return; + } + + if (status != STATUS_OK) // We can receive STATUS_TIMEOUT when no card, or unexpected status. + return; + + // Try process card + if (!picc_read_card_serial_()) { + ESP_LOGW(TAG, "Requesting tag read failed!"); + return; + }; + + if (uid_.size < 4) { + return; + ESP_LOGW(TAG, "Read serial size: %d", uid_.size); + } + + update_wait_ = millis(); + + bool report = true; + // 1. Go through all triggers + for (auto *trigger : this->triggers_) + trigger->process(uid_.uiduint8_t, uid_.size); + + // 2. Find a binary sensor + for (auto *tag : this->binary_sensors_) { + if (tag->process(uid_.uiduint8_t, uid_.size)) { + // 2.1 if found, do not dump + report = false; + } + } + + if (report) { + char buf[32]; + format_uid(buf, uid_.uiduint8_t, uid_.size); + ESP_LOGD(TAG, "Found new tag '%s'", buf); + } +} + +void RC522::update() { + for (auto *obj : this->binary_sensors_) + obj->on_scan_end(); +} + +/** + * Performs a soft reset on the MFRC522 chip and waits for it to be ready again. + */ +void RC522::pcd_reset_() { + // The datasheet does not mention how long the SoftRest command takes to complete. + // But the MFRC522 might have been in soft power-down mode (triggered by bit 4 of CommandReg) + // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs. Let + // us be generous: 50ms. + + if (millis() - reset_timeout_ < 50) + return; + + if (reset_count_ == RESET_COUNT) { + ESP_LOGV(TAG, "Soft reset..."); + // Issue the SoftReset command. + pcd_write_register(COMMAND_REG, PCD_SOFT_RESET); + } + + // Expect the PowerDown bit in CommandReg to be cleared (max 3x50ms) + if ((pcd_read_register(COMMAND_REG) & (1 << 4)) == 0) { + reset_count_ = 0; + ESP_LOGI(TAG, "Device online."); + // Wait for initialize + reset_timeout_ = millis(); + return; + } + + if (--reset_count_ == 0) { + ESP_LOGE(TAG, "Unable to reset RC522."); + mark_failed(); + } +} + +/** + * Turns the antenna on by enabling pins TX1 and TX2. + * After a reset these pins are disabled. + */ +void RC522::pcd_antenna_on_() { + uint8_t value = pcd_read_register(TX_CONTROL_REG); + if ((value & 0x03) != 0x03) { + pcd_write_register(TX_CONTROL_REG, value | 0x03); + } +} + +/** + * Transmits a REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or + * selection. 7 bit frame. Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - + * probably due do bad antenna design. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +RC522::StatusCode RC522::picc_request_a_( + uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in + uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK. +) { + return picc_reqa_or_wupa_(PICC_CMD_REQA, buffer_atqa, buffer_size); +} + +/** + * Transmits REQA or WUPA commands. + * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna + * design. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +RC522::StatusCode RC522::picc_reqa_or_wupa_( + uint8_t command, ///< The command to send - PICC_CMD_REQA or PICC_CMD_WUPA + uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in + uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK. +) { + uint8_t valid_bits; + RC522::StatusCode status; + + if (buffer_atqa == nullptr || *buffer_size < 2) { // The ATQA response is 2 uint8_ts long. + return STATUS_NO_ROOM; + } + pcd_clear_register_bit_mask_(COLL_REG, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared. + valid_bits = 7; // For REQA and WUPA we need the short frame format - transmit only 7 bits of the last (and only) + // uint8_t. TxLastBits = BitFramingReg[2..0] + status = pcd_transceive_data_(&command, 1, buffer_atqa, buffer_size, &valid_bits); + if (status != STATUS_OK) + return status; + if (*buffer_size != 2 || valid_bits != 0) { // ATQA must be exactly 16 bits. + ESP_LOGVV(TAG, "picc_reqa_or_wupa_() -> STATUS_ERROR"); + return STATUS_ERROR; + } + + return STATUS_OK; +} + +/** + * Sets the bits given in mask in register reg. + */ +void RC522::pcd_set_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums. + uint8_t mask ///< The bits to set. +) { + uint8_t tmp = pcd_read_register(reg); + pcd_write_register(reg, tmp | mask); // set bit mask +} + +/** + * Clears the bits given in mask from register reg. + */ +void RC522::pcd_clear_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums. + uint8_t mask ///< The bits to clear. +) { + uint8_t tmp = pcd_read_register(reg); + pcd_write_register(reg, tmp & (~mask)); // clear bit mask +} + +/** + * Executes the Transceive command. + * CRC validation can only be done if backData and backLen are specified. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +RC522::StatusCode RC522::pcd_transceive_data_( + uint8_t *send_data, ///< Pointer to the data to transfer to the FIFO. + uint8_t send_len, ///< Number of uint8_ts to transfer to the FIFO. + uint8_t *back_data, ///< nullptr or pointer to buffer if data should be read back after executing the command. + uint8_t *back_len, ///< In: Max number of uint8_ts to write to *backData. Out: The number of uint8_ts returned. + uint8_t + *valid_bits, ///< In/Out: The number of valid bits in the last uint8_t. 0 for 8 valid bits. Default nullptr. + uint8_t rx_align, ///< In: Defines the bit position in backData[0] for the first bit received. Default 0. + bool check_crc ///< In: True => The last two uint8_ts of the response is assumed to be a CRC_A that must be + ///< validated. +) { + uint8_t wait_i_rq = 0x30; // RxIRq and IdleIRq + auto ret = pcd_communicate_with_picc_(PCD_TRANSCEIVE, wait_i_rq, send_data, send_len, back_data, back_len, valid_bits, + rx_align, check_crc); + + if (ret == STATUS_OK && *back_len == 5) + ESP_LOGVV(TAG, "pcd_transceive_data_(..., %d, ) -> %d [%x, %x, %x, %x, %x]", send_len, ret, back_data[0], + back_data[1], back_data[2], back_data[3], back_data[4]); + else + ESP_LOGVV(TAG, "pcd_transceive_data_(..., %d, ... ) -> %d", send_len, ret); + return ret; +} + +/** + * Transfers data to the MFRC522 FIFO, executes a command, waits for completion and transfers data back from the FIFO. + * CRC validation can only be done if backData and backLen are specified. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +RC522::StatusCode RC522::pcd_communicate_with_picc_( + uint8_t command, ///< The command to execute. One of the PCD_Command enums. + uint8_t wait_i_rq, ///< The bits in the ComIrqReg register that signals successful completion of the command. + uint8_t *send_data, ///< Pointer to the data to transfer to the FIFO. + uint8_t send_len, ///< Number of uint8_ts to transfer to the FIFO. + uint8_t *back_data, ///< nullptr or pointer to buffer if data should be read back after executing the command. + uint8_t *back_len, ///< In: Max number of uint8_ts to write to *backData. Out: The number of uint8_ts returned. + uint8_t *valid_bits, ///< In/Out: The number of valid bits in the last uint8_t. 0 for 8 valid bits. + uint8_t rx_align, ///< In: Defines the bit position in backData[0] for the first bit received. Default 0. + bool check_crc ///< In: True => The last two uint8_ts of the response is assumed to be a CRC_A that must be + ///< validated. +) { + ESP_LOGVV(TAG, "pcd_communicate_with_picc_(%d, %d,... %d)", command, wait_i_rq, check_crc); + + // Prepare values for BitFramingReg + uint8_t tx_last_bits = valid_bits ? *valid_bits : 0; + uint8_t bit_framing = + (rx_align << 4) + tx_last_bits; // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] + + pcd_write_register(COMMAND_REG, PCD_IDLE); // Stop any active command. + pcd_write_register(COM_IRQ_REG, 0x7F); // Clear all seven interrupt request bits + pcd_write_register(FIFO_LEVEL_REG, 0x80); // FlushBuffer = 1, FIFO initialization + pcd_write_register(FIFO_DATA_REG, send_len, send_data); // Write sendData to the FIFO + pcd_write_register(BIT_FRAMING_REG, bit_framing); // Bit adjustments + pcd_write_register(COMMAND_REG, command); // Execute the command + if (command == PCD_TRANSCEIVE) { + pcd_set_register_bit_mask_(BIT_FRAMING_REG, 0x80); // StartSend=1, transmission of data starts + } + + // Wait for the command to complete. + // In PCD_Init() we set the TAuto flag in TModeReg. This means the timer automatically starts when the PCD stops + // transmitting. Each iteration of the do-while-loop takes 17.86μs. + // TODO check/modify for other architectures than Arduino Uno 16bit + uint16_t i; + for (i = 4; i > 0; i--) { + uint8_t n = pcd_read_register( + COM_IRQ_REG); // ComIrqReg[7..0] bits are: Set1 TxIRq RxIRq IdleIRq HiAlertIRq LoAlertIRq ErrIRq TimerIRq + if (n & wait_i_rq) { // One of the interrupts that signal success has been set. + break; + } + if (n & 0x01) { // Timer interrupt - nothing received in 25ms + return STATUS_TIMEOUT; + } + } + // 35.7ms and nothing happend. Communication with the MFRC522 might be down. + if (i == 0) { + return STATUS_TIMEOUT; + } + + // Stop now if any errors except collisions were detected. + uint8_t error_reg_value = pcd_read_register( + ERROR_REG); // ErrorReg[7..0] bits are: WrErr TempErr reserved BufferOvfl CollErr CRCErr ParityErr ProtocolErr + if (error_reg_value & 0x13) { // BufferOvfl ParityErr ProtocolErr + return STATUS_ERROR; + } + + uint8_t valid_bits_local = 0; + + // If the caller wants data back, get it from the MFRC522. + if (back_data && back_len) { + uint8_t n = pcd_read_register(FIFO_LEVEL_REG); // Number of uint8_ts in the FIFO + if (n > *back_len) { + return STATUS_NO_ROOM; + } + *back_len = n; // Number of uint8_ts returned + pcd_read_register(FIFO_DATA_REG, n, back_data, rx_align); // Get received data from FIFO + valid_bits_local = + pcd_read_register(CONTROL_REG) & 0x07; // RxLastBits[2:0] indicates the number of valid bits in the last + // received uint8_t. If this value is 000b, the whole uint8_t is valid. + if (valid_bits) { + *valid_bits = valid_bits_local; + } + } + + // Tell about collisions + if (error_reg_value & 0x08) { // CollErr + return STATUS_COLLISION; + } + + // Perform CRC_A validation if requested. + if (back_data && back_len && check_crc) { + // In this case a MIFARE Classic NAK is not OK. + if (*back_len == 1 && valid_bits_local == 4) { + return STATUS_MIFARE_NACK; + } + // We need at least the CRC_A value and all 8 bits of the last uint8_t must be received. + if (*back_len < 2 || valid_bits_local != 0) { + return STATUS_CRC_WRONG; + } + // Verify CRC_A - do our own calculation and store the control in controlBuffer. + uint8_t control_buffer[2]; + RC522::StatusCode status = pcd_calculate_crc_(&back_data[0], *back_len - 2, &control_buffer[0]); + if (status != STATUS_OK) { + return status; + } + if ((back_data[*back_len - 2] != control_buffer[0]) || (back_data[*back_len - 1] != control_buffer[1])) { + return STATUS_CRC_WRONG; + } + } + + return STATUS_OK; +} + +/** + * Use the CRC coprocessor in the MFRC522 to calculate a CRC_A. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ + +RC522::StatusCode RC522::pcd_calculate_crc_( + uint8_t *data, ///< In: Pointer to the data to transfer to the FIFO for CRC calculation. + uint8_t length, ///< In: The number of uint8_ts to transfer. + uint8_t *result ///< Out: Pointer to result buffer. Result is written to result[0..1], low uint8_t first. +) { + ESP_LOGVV(TAG, "pcd_calculate_crc_(..., %d, ...)", length); + pcd_write_register(COMMAND_REG, PCD_IDLE); // Stop any active command. + pcd_write_register(DIV_IRQ_REG, 0x04); // Clear the CRCIRq interrupt request bit + pcd_write_register(FIFO_LEVEL_REG, 0x80); // FlushBuffer = 1, FIFO initialization + pcd_write_register(FIFO_DATA_REG, length, data); // Write data to the FIFO + pcd_write_register(COMMAND_REG, PCD_CALC_CRC); // Start the calculation + + // Wait for the CRC calculation to complete. Each iteration of the while-loop takes 17.73μs. + // TODO check/modify for other architectures than Arduino Uno 16bit + + // Wait for the CRC calculation to complete. Each iteration of the while-loop takes 17.73us. + for (uint16_t i = 5000; i > 0; i--) { + // DivIrqReg[7..0] bits are: Set2 reserved reserved MfinActIRq reserved CRCIRq reserved reserved + uint8_t n = pcd_read_register(DIV_IRQ_REG); + if (n & 0x04) { // CRCIRq bit set - calculation done + pcd_write_register(COMMAND_REG, PCD_IDLE); // Stop calculating CRC for new content in the FIFO. + // Transfer the result from the registers to the result buffer + result[0] = pcd_read_register(CRC_RESULT_REG_L); + result[1] = pcd_read_register(CRC_RESULT_REG_H); + + ESP_LOGVV(TAG, "pcd_calculate_crc_() STATUS_OK"); + return STATUS_OK; + } + } + ESP_LOGVV(TAG, "pcd_calculate_crc_() TIMEOUT"); + // 89ms passed and nothing happend. Communication with the MFRC522 might be down. + return STATUS_TIMEOUT; +} +/** + * Returns STATUS_OK if a PICC responds to PICC_CMD_REQA. + * Only "new" cards in state IDLE are invited. Sleeping cards in state HALT are ignored. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ + +RC522::StatusCode RC522::picc_is_new_card_present_() { + uint8_t buffer_atqa[2]; + uint8_t buffer_size = sizeof(buffer_atqa); + + // Reset baud rates + pcd_write_register(TX_MODE_REG, 0x00); + pcd_write_register(RX_MODE_REG, 0x00); + // Reset ModWidthReg + pcd_write_register(MOD_WIDTH_REG, 0x26); + + auto result = picc_request_a_(buffer_atqa, &buffer_size); + + ESP_LOGV(TAG, "picc_is_new_card_present_() -> %d", result); + return result; +} + +/** + * Simple wrapper around PICC_Select. + * Returns true if a UID could be read. + * Remember to call PICC_IsNewCardPresent(), PICC_RequestA() or PICC_WakeupA() first. + * The read UID is available in the class variable uid. + * + * @return bool + */ +bool RC522::picc_read_card_serial_() { + RC522::StatusCode result = picc_select_(&this->uid_); + ESP_LOGVV(TAG, "picc_select_(...) -> %d", result); + return (result == STATUS_OK); +} + +/** + * Transmits SELECT/ANTICOLLISION commands to select a single PICC. + * Before calling this function the PICCs must be placed in the READY(*) state by calling PICC_RequestA() or + * PICC_WakeupA(). On success: + * - The chosen PICC is in state ACTIVE(*) and all other PICCs have returned to state IDLE/HALT. (Figure 7 of the + * ISO/IEC 14443-3 draft.) + * - The UID size and value of the chosen PICC is returned in *uid along with the SAK. + * + * A PICC UID consists of 4, 7 or 10 uint8_ts. + * Only 4 uint8_ts can be specified in a SELECT command, so for the longer UIDs two or three iterations are used: + * UID size Number of UID uint8_ts Cascade levels Example of PICC + * ======== =================== ============== =============== + * single 4 1 MIFARE Classic + * double 7 2 MIFARE Ultralight + * triple 10 3 Not currently in use? + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +RC522::StatusCode RC522::picc_select_( + Uid *uid, ///< Pointer to Uid struct. Normally output, but can also be used to supply a known UID. + uint8_t valid_bits ///< The number of known UID bits supplied in *uid. Normally 0. If set you must also supply + ///< uid->size. +) { + bool uid_complete; + bool select_done; + bool use_cascade_tag; + uint8_t cascade_level = 1; + RC522::StatusCode result; + uint8_t count; + uint8_t check_bit; + uint8_t index; + uint8_t uid_index; // The first index in uid->uiduint8_t[] that is used in the current Cascade Level. + int8_t current_level_known_bits; // The number of known UID bits in the current Cascade Level. + uint8_t buffer[9]; // The SELECT/ANTICOLLISION commands uses a 7 uint8_t standard frame + 2 uint8_ts CRC_A + uint8_t buffer_used; // The number of uint8_ts used in the buffer, ie the number of uint8_ts to transfer to the FIFO. + uint8_t rx_align; // Used in BitFramingReg. Defines the bit position for the first bit received. + uint8_t tx_last_bits; // Used in BitFramingReg. The number of valid bits in the last transmitted uint8_t. + uint8_t *response_buffer; + uint8_t response_length; + + // Description of buffer structure: + // uint8_t 0: SEL Indicates the Cascade Level: PICC_CMD_SEL_CL1, PICC_CMD_SEL_CL2 or PICC_CMD_SEL_CL3 + // uint8_t 1: NVB Number of Valid Bits (in complete command, not just the UID): High nibble: complete + // uint8_ts, + // Low nibble: Extra bits. uint8_t 2: UID-data or CT See explanation below. CT means Cascade Tag. uint8_t + // 3: UID-data uint8_t 4: UID-data uint8_t 5: UID-data uint8_t 6: BCC Block Check Character - XOR of + // uint8_ts 2-5 uint8_t 7: CRC_A uint8_t 8: CRC_A The BCC and CRC_A are only transmitted if we know all the UID bits + // of the current Cascade Level. + // + // Description of uint8_ts 2-5: (Section 6.5.4 of the ISO/IEC 14443-3 draft: UID contents and cascade levels) + // UID size Cascade level uint8_t2 uint8_t3 uint8_t4 uint8_t5 + // ======== ============= ===== ===== ===== ===== + // 4 uint8_ts 1 uid0 uid1 uid2 uid3 + // 7 uint8_ts 1 CT uid0 uid1 uid2 + // 2 uid3 uid4 uid5 uid6 + // 10 uint8_ts 1 CT uid0 uid1 uid2 + // 2 CT uid3 uid4 uid5 + // 3 uid6 uid7 uid8 uid9 + + // Sanity checks + if (valid_bits > 80) { + return STATUS_INVALID; + } + + ESP_LOGVV(TAG, "picc_select_(&, %d)", valid_bits); + + // Prepare MFRC522 + pcd_clear_register_bit_mask_(COLL_REG, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared. + + // Repeat Cascade Level loop until we have a complete UID. + uid_complete = false; + while (!uid_complete) { + // Set the Cascade Level in the SEL uint8_t, find out if we need to use the Cascade Tag in uint8_t 2. + switch (cascade_level) { + case 1: + buffer[0] = PICC_CMD_SEL_CL1; + uid_index = 0; + use_cascade_tag = valid_bits && uid->size > 4; // When we know that the UID has more than 4 uint8_ts + break; + + case 2: + buffer[0] = PICC_CMD_SEL_CL2; + uid_index = 3; + use_cascade_tag = valid_bits && uid->size > 7; // When we know that the UID has more than 7 uint8_ts + break; + + case 3: + buffer[0] = PICC_CMD_SEL_CL3; + uid_index = 6; + use_cascade_tag = false; // Never used in CL3. + break; + + default: + return STATUS_INTERNAL_ERROR; + break; + } + + // How many UID bits are known in this Cascade Level? + current_level_known_bits = valid_bits - (8 * uid_index); + if (current_level_known_bits < 0) { + current_level_known_bits = 0; + } + // Copy the known bits from uid->uiduint8_t[] to buffer[] + index = 2; // destination index in buffer[] + if (use_cascade_tag) { + buffer[index++] = PICC_CMD_CT; + } + uint8_t uint8_ts_to_copy = current_level_known_bits / 8 + + (current_level_known_bits % 8 + ? 1 + : 0); // The number of uint8_ts needed to represent the known bits for this level. + if (uint8_ts_to_copy) { + uint8_t maxuint8_ts = + use_cascade_tag ? 3 : 4; // Max 4 uint8_ts in each Cascade Level. Only 3 left if we use the Cascade Tag + if (uint8_ts_to_copy > maxuint8_ts) { + uint8_ts_to_copy = maxuint8_ts; + } + for (count = 0; count < uint8_ts_to_copy; count++) { + buffer[index++] = uid->uiduint8_t[uid_index + count]; + } + } + // Now that the data has been copied we need to include the 8 bits in CT in currentLevelKnownBits + if (use_cascade_tag) { + current_level_known_bits += 8; + } + + // Repeat anti collision loop until we can transmit all UID bits + BCC and receive a SAK - max 32 iterations. + select_done = false; + while (!select_done) { + // Find out how many bits and uint8_ts to send and receive. + if (current_level_known_bits >= 32) { // All UID bits in this Cascade Level are known. This is a SELECT. + + if (response_length < 4) { + ESP_LOGW(TAG, "Not enough data received."); + return STATUS_INVALID; + } + + // Serial.print(F("SELECT: currentLevelKnownBits=")); Serial.println(currentLevelKnownBits, DEC); + buffer[1] = 0x70; // NVB - Number of Valid Bits: Seven whole uint8_ts + // Calculate BCC - Block Check Character + buffer[6] = buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5]; + // Calculate CRC_A + result = pcd_calculate_crc_(buffer, 7, &buffer[7]); + if (result != STATUS_OK) { + return result; + } + tx_last_bits = 0; // 0 => All 8 bits are valid. + buffer_used = 9; + // Store response in the last 3 uint8_ts of buffer (BCC and CRC_A - not needed after tx) + response_buffer = &buffer[6]; + response_length = 3; + } else { // This is an ANTICOLLISION. + // Serial.print(F("ANTICOLLISION: currentLevelKnownBits=")); Serial.println(currentLevelKnownBits, DEC); + tx_last_bits = current_level_known_bits % 8; + count = current_level_known_bits / 8; // Number of whole uint8_ts in the UID part. + index = 2 + count; // Number of whole uint8_ts: SEL + NVB + UIDs + buffer[1] = (index << 4) + tx_last_bits; // NVB - Number of Valid Bits + buffer_used = index + (tx_last_bits ? 1 : 0); + // Store response in the unused part of buffer + response_buffer = &buffer[index]; + response_length = sizeof(buffer) - index; + } + + // Set bit adjustments + rx_align = tx_last_bits; // Having a separate variable is overkill. But it makes the next line easier to read. + pcd_write_register( + BIT_FRAMING_REG, + (rx_align << 4) + tx_last_bits); // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] + + // Transmit the buffer and receive the response. + result = pcd_transceive_data_(buffer, buffer_used, response_buffer, &response_length, &tx_last_bits, rx_align); + if (result == STATUS_COLLISION) { // More than one PICC in the field => collision. + uint8_t value_of_coll_reg = pcd_read_register( + COLL_REG); // CollReg[7..0] bits are: ValuesAfterColl reserved CollPosNotValid CollPos[4:0] + if (value_of_coll_reg & 0x20) { // CollPosNotValid + return STATUS_COLLISION; // Without a valid collision position we cannot continue + } + uint8_t collision_pos = value_of_coll_reg & 0x1F; // Values 0-31, 0 means bit 32. + if (collision_pos == 0) { + collision_pos = 32; + } + if (collision_pos <= current_level_known_bits) { // No progress - should not happen + return STATUS_INTERNAL_ERROR; + } + // Choose the PICC with the bit set. + current_level_known_bits = collision_pos; + count = current_level_known_bits % 8; // The bit to modify + check_bit = (current_level_known_bits - 1) % 8; + index = 1 + (current_level_known_bits / 8) + (count ? 1 : 0); // First uint8_t is index 0. + if (response_length > 2) // Note: Otherwise buffer[index] might be not initialized + buffer[index] |= (1 << check_bit); + } else if (result != STATUS_OK) { + return result; + } else { // STATUS_OK + if (current_level_known_bits >= 32) { // This was a SELECT. + select_done = true; // No more anticollision + // We continue below outside the while. + } else { // This was an ANTICOLLISION. + // We now have all 32 bits of the UID in this Cascade Level + current_level_known_bits = 32; + // Run loop again to do the SELECT. + } + } + } // End of while (!selectDone) + + // We do not check the CBB - it was constructed by us above. + + // Copy the found UID uint8_ts from buffer[] to uid->uiduint8_t[] + index = (buffer[2] == PICC_CMD_CT) ? 3 : 2; // source index in buffer[] + uint8_ts_to_copy = (buffer[2] == PICC_CMD_CT) ? 3 : 4; + for (count = 0; count < uint8_ts_to_copy; count++) { + uid->uiduint8_t[uid_index + count] = buffer[index++]; + } + + // Check response SAK (Select Acknowledge) + if (response_length != 3 || tx_last_bits != 0) { // SAK must be exactly 24 bits (1 uint8_t + CRC_A). + return STATUS_ERROR; + } + // Verify CRC_A - do our own calculation and store the control in buffer[2..3] - those uint8_ts are not needed + // anymore. + result = pcd_calculate_crc_(response_buffer, 1, &buffer[2]); + if (result != STATUS_OK) { + return result; + } + if ((buffer[2] != response_buffer[1]) || (buffer[3] != response_buffer[2])) { + return STATUS_CRC_WRONG; + } + if (response_buffer[0] & 0x04) { // Cascade bit set - UID not complete yes + cascade_level++; + } else { + uid_complete = true; + uid->sak = response_buffer[0]; + } + } // End of while (!uidComplete) + + // Set correct uid->size + uid->size = 3 * cascade_level + 1; + + return STATUS_OK; +} + +bool RC522BinarySensor::process(const uint8_t *data, uint8_t len) { + if (len != this->uid_.size()) + return false; + + for (uint8_t i = 0; i < len; i++) { + if (data[i] != this->uid_[i]) + return false; + } + + this->publish_state(true); + this->found_ = true; + return true; +} +void RC522Trigger::process(const uint8_t *uid, uint8_t uid_length) { + char buf[32]; + format_uid(buf, uid, uid_length); + this->trigger(std::string(buf)); +} + +} // namespace rc522 +} // namespace esphome diff --git a/esphome/components/rc522/rc522.h b/esphome/components/rc522/rc522.h new file mode 100644 index 0000000000..cabcf8db0b --- /dev/null +++ b/esphome/components/rc522/rc522.h @@ -0,0 +1,284 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/components/binary_sensor/binary_sensor.h" + +namespace esphome { +namespace rc522 { + +class RC522BinarySensor; +class RC522Trigger; +class RC522 : public PollingComponent { + public: + void setup() override; + + void dump_config() override; + + void update() override; + float get_setup_priority() const override { return setup_priority::DATA; }; + + void loop() override; + + void register_tag(RC522BinarySensor *tag) { this->binary_sensors_.push_back(tag); } + void register_trigger(RC522Trigger *trig) { this->triggers_.push_back(trig); } + + void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } + + protected: + enum PcdRegister : uint8_t { + // Page 0: Command and status + // 0x00 // reserved for future use + COMMAND_REG = 0x01 << 1, // starts and stops command execution + COM_I_EN_REG = 0x02 << 1, // enable and disable interrupt request control bits + DIV_I_EN_REG = 0x03 << 1, // enable and disable interrupt request control bits + COM_IRQ_REG = 0x04 << 1, // interrupt request bits + DIV_IRQ_REG = 0x05 << 1, // interrupt request bits + ERROR_REG = 0x06 << 1, // error bits showing the error status of the last command executed + STATUS1_REG = 0x07 << 1, // communication status bits + STATUS2_REG = 0x08 << 1, // receiver and transmitter status bits + FIFO_DATA_REG = 0x09 << 1, // input and output of 64 uint8_t FIFO buffer + FIFO_LEVEL_REG = 0x0A << 1, // number of uint8_ts stored in the FIFO buffer + WATER_LEVEL_REG = 0x0B << 1, // level for FIFO underflow and overflow warning + CONTROL_REG = 0x0C << 1, // miscellaneous control registers + BIT_FRAMING_REG = 0x0D << 1, // adjustments for bit-oriented frames + COLL_REG = 0x0E << 1, // bit position of the first bit-collision detected on the RF interface + // 0x0F // reserved for future use + + // Page 1: Command + // 0x10 // reserved for future use + MODE_REG = 0x11 << 1, // defines general modes for transmitting and receiving + TX_MODE_REG = 0x12 << 1, // defines transmission data rate and framing + RX_MODE_REG = 0x13 << 1, // defines reception data rate and framing + TX_CONTROL_REG = 0x14 << 1, // controls the logical behavior of the antenna driver pins TX1 and TX2 + TX_ASK_REG = 0x15 << 1, // controls the setting of the transmission modulation + TX_SEL_REG = 0x16 << 1, // selects the internal sources for the antenna driver + RX_SEL_REG = 0x17 << 1, // selects internal receiver settings + RX_THRESHOLD_REG = 0x18 << 1, // selects thresholds for the bit decoder + DEMOD_REG = 0x19 << 1, // defines demodulator settings + // 0x1A // reserved for future use + // 0x1B // reserved for future use + MF_TX_REG = 0x1C << 1, // controls some MIFARE communication transmit parameters + MF_RX_REG = 0x1D << 1, // controls some MIFARE communication receive parameters + // 0x1E // reserved for future use + SERIAL_SPEED_REG = 0x1F << 1, // selects the speed of the serial UART interface + + // Page 2: Configuration + // 0x20 // reserved for future use + CRC_RESULT_REG_H = 0x21 << 1, // shows the MSB and LSB values of the CRC calculation + CRC_RESULT_REG_L = 0x22 << 1, + // 0x23 // reserved for future use + MOD_WIDTH_REG = 0x24 << 1, // controls the ModWidth setting? + // 0x25 // reserved for future use + RF_CFG_REG = 0x26 << 1, // configures the receiver gain + GS_N_REG = 0x27 << 1, // selects the conductance of the antenna driver pins TX1 and TX2 for modulation + CW_GS_P_REG = 0x28 << 1, // defines the conductance of the p-driver output during periods of no modulation + MOD_GS_P_REG = 0x29 << 1, // defines the conductance of the p-driver output during periods of modulation + T_MODE_REG = 0x2A << 1, // defines settings for the internal timer + T_PRESCALER_REG = 0x2B << 1, // the lower 8 bits of the TPrescaler value. The 4 high bits are in TModeReg. + T_RELOAD_REG_H = 0x2C << 1, // defines the 16-bit timer reload value + T_RELOAD_REG_L = 0x2D << 1, + T_COUNTER_VALUE_REG_H = 0x2E << 1, // shows the 16-bit timer value + T_COUNTER_VALUE_REG_L = 0x2F << 1, + + // Page 3: Test Registers + // 0x30 // reserved for future use + TEST_SEL1_REG = 0x31 << 1, // general test signal configuration + TEST_SEL2_REG = 0x32 << 1, // general test signal configuration + TEST_PIN_EN_REG = 0x33 << 1, // enables pin output driver on pins D1 to D7 + TEST_PIN_VALUE_REG = 0x34 << 1, // defines the values for D1 to D7 when it is used as an I/O bus + TEST_BUS_REG = 0x35 << 1, // shows the status of the internal test bus + AUTO_TEST_REG = 0x36 << 1, // controls the digital self-test + VERSION_REG = 0x37 << 1, // shows the software version + ANALOG_TEST_REG = 0x38 << 1, // controls the pins AUX1 and AUX2 + TEST_DA_C1_REG = 0x39 << 1, // defines the test value for TestDAC1 + TEST_DA_C2_REG = 0x3A << 1, // defines the test value for TestDAC2 + TEST_ADC_REG = 0x3B << 1 // shows the value of ADC I and Q channels + // 0x3C // reserved for production tests + // 0x3D // reserved for production tests + // 0x3E // reserved for production tests + // 0x3F // reserved for production tests + }; + + // MFRC522 commands. Described in chapter 10 of the datasheet. + enum PcdCommand : uint8_t { + PCD_IDLE = 0x00, // no action, cancels current command execution + PCD_MEM = 0x01, // stores 25 uint8_ts into the internal buffer + PCD_GENERATE_RANDOM_ID = 0x02, // generates a 10-uint8_t random ID number + PCD_CALC_CRC = 0x03, // activates the CRC coprocessor or performs a self-test + PCD_TRANSMIT = 0x04, // transmits data from the FIFO buffer + PCD_NO_CMD_CHANGE = 0x07, // no command change, can be used to modify the CommandReg register bits without + // affecting the command, for example, the PowerDown bit + PCD_RECEIVE = 0x08, // activates the receiver circuits + PCD_TRANSCEIVE = + 0x0C, // transmits data from FIFO buffer to antenna and automatically activates the receiver after transmission + PCD_MF_AUTHENT = 0x0E, // performs the MIFARE standard authentication as a reader + PCD_SOFT_RESET = 0x0F // resets the MFRC522 + }; + + // Commands sent to the PICC. + enum PiccCommand : uint8_t { + // The commands used by the PCD to manage communication with several PICCs (ISO 14443-3, Type A, section 6.4) + PICC_CMD_REQA = 0x26, // REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for + // anticollision or selection. 7 bit frame. + PICC_CMD_WUPA = 0x52, // Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to go to READY(*) and + // prepare for anticollision or selection. 7 bit frame. + PICC_CMD_CT = 0x88, // Cascade Tag. Not really a command, but used during anti collision. + PICC_CMD_SEL_CL1 = 0x93, // Anti collision/Select, Cascade Level 1 + PICC_CMD_SEL_CL2 = 0x95, // Anti collision/Select, Cascade Level 2 + PICC_CMD_SEL_CL3 = 0x97, // Anti collision/Select, Cascade Level 3 + PICC_CMD_HLTA = 0x50, // HaLT command, Type A. Instructs an ACTIVE PICC to go to state HALT. + PICC_CMD_RATS = 0xE0, // Request command for Answer To Reset. + // The commands used for MIFARE Classic (from http://www.mouser.com/ds/2/302/MF1S503x-89574.pdf, Section 9) + // Use PCD_MFAuthent to authenticate access to a sector, then use these commands to read/write/modify the blocks on + // the sector. + // The read/write commands can also be used for MIFARE Ultralight. + PICC_CMD_MF_AUTH_KEY_A = 0x60, // Perform authentication with Key A + PICC_CMD_MF_AUTH_KEY_B = 0x61, // Perform authentication with Key B + PICC_CMD_MF_READ = + 0x30, // Reads one 16 uint8_t block from the authenticated sector of the PICC. Also used for MIFARE Ultralight. + PICC_CMD_MF_WRITE = 0xA0, // Writes one 16 uint8_t block to the authenticated sector of the PICC. Called + // "COMPATIBILITY WRITE" for MIFARE Ultralight. + PICC_CMD_MF_DECREMENT = + 0xC0, // Decrements the contents of a block and stores the result in the internal data register. + PICC_CMD_MF_INCREMENT = + 0xC1, // Increments the contents of a block and stores the result in the internal data register. + PICC_CMD_MF_RESTORE = 0xC2, // Reads the contents of a block into the internal data register. + PICC_CMD_MF_TRANSFER = 0xB0, // Writes the contents of the internal data register to a block. + // The commands used for MIFARE Ultralight (from http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf, Section 8.6) + // The PICC_CMD_MF_READ and PICC_CMD_MF_WRITE can also be used for MIFARE Ultralight. + PICC_CMD_UL_WRITE = 0xA2 // Writes one 4 uint8_t page to the PICC. + }; + + // Return codes from the functions in this class. Remember to update GetStatusCodeName() if you add more. + // last value set to 0xff, then compiler uses less ram, it seems some optimisations are triggered + enum StatusCode : uint8_t { + STATUS_OK, // Success + STATUS_ERROR, // Error in communication + STATUS_COLLISION, // Collission detected + STATUS_TIMEOUT, // Timeout in communication. + STATUS_NO_ROOM, // A buffer is not big enough. + STATUS_INTERNAL_ERROR, // Internal error in the code. Should not happen ;-) + STATUS_INVALID, // Invalid argument. + STATUS_CRC_WRONG, // The CRC_A does not match + STATUS_MIFARE_NACK = 0xff // A MIFARE PICC responded with NAK. + }; + + // A struct used for passing the UID of a PICC. + using Uid = struct { + uint8_t size; // Number of uint8_ts in the UID. 4, 7 or 10. + uint8_t uiduint8_t[10]; + uint8_t sak; // The SAK (Select acknowledge) uint8_t returned from the PICC after successful selection. + }; + + Uid uid_; + uint32_t update_wait_{0}; + + void pcd_reset_(); + void initialize_(); + void pcd_antenna_on_(); + virtual uint8_t pcd_read_register(PcdRegister reg ///< The register to read from. One of the PCD_Register enums. + ) = 0; + + /** + * Reads a number of uint8_ts from the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ + virtual void pcd_read_register(PcdRegister reg, ///< The register to read from. One of the PCD_Register enums. + uint8_t count, ///< The number of uint8_ts to read + uint8_t *values, ///< uint8_t array to store the values in. + uint8_t rx_align ///< Only bit positions rxAlign..7 in values[0] are updated. + ) = 0; + virtual void pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. + uint8_t value ///< The value to write. + ) = 0; + + /** + * Writes a number of uint8_ts to the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ + virtual void pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. + uint8_t count, ///< The number of uint8_ts to write to the register + uint8_t *values ///< The values to write. uint8_t array. + ) = 0; + + StatusCode picc_request_a_( + uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in + uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK. + ); + StatusCode picc_reqa_or_wupa_( + uint8_t command, ///< The command to send - PICC_CMD_REQA or PICC_CMD_WUPA + uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in + uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK. + ); + void pcd_set_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums. + uint8_t mask ///< The bits to set. + ); + void pcd_clear_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums. + uint8_t mask ///< The bits to clear. + ); + + StatusCode pcd_transceive_data_(uint8_t *send_data, uint8_t send_len, uint8_t *back_data, uint8_t *back_len, + uint8_t *valid_bits = nullptr, uint8_t rx_align = 0, bool check_crc = false); + StatusCode pcd_communicate_with_picc_(uint8_t command, uint8_t wait_i_rq, uint8_t *send_data, uint8_t send_len, + uint8_t *back_data = nullptr, uint8_t *back_len = nullptr, + uint8_t *valid_bits = nullptr, uint8_t rx_align = 0, bool check_crc = false); + StatusCode pcd_calculate_crc_( + uint8_t *data, ///< In: Pointer to the data to transfer to the FIFO for CRC calculation. + uint8_t length, ///< In: The number of uint8_ts to transfer. + uint8_t *result ///< Out: Pointer to result buffer. Result is written to result[0..1], low uint8_t first. + ); + RC522::StatusCode picc_is_new_card_present_(); + bool picc_read_card_serial_(); + StatusCode picc_select_( + Uid *uid, ///< Pointer to Uid struct. Normally output, but can also be used to supply a known UID. + uint8_t valid_bits = 0 ///< The number of known UID bits supplied in *uid. Normally 0. If set you must also + ///< supply uid->size. + ); + + /** Read a data frame from the RC522 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 r_c522_read_data_(); + + GPIOPin *reset_pin_{nullptr}; + uint8_t reset_count_{0}; + uint32_t reset_timeout_{0}; + bool initialize_pending_{false}; + std::vector binary_sensors_; + std::vector triggers_; + + enum RC522Error { + NONE = 0, + RESET_FAILED, + } error_code_{NONE}; +}; + +class RC522BinarySensor : public binary_sensor::BinarySensor { + public: + void set_uid(const std::vector &uid) { uid_ = uid; } + + bool process(const uint8_t *data, uint8_t len); + + void on_scan_end() { + if (!this->found_) { + this->publish_state(false); + } + this->found_ = false; + } + + protected: + std::vector uid_; + bool found_{false}; +}; + +class RC522Trigger : public Trigger { + public: + void process(const uint8_t *uid, uint8_t uid_length); +}; + +} // namespace rc522 +} // namespace esphome diff --git a/esphome/components/rc522_i2c/__init__.py b/esphome/components/rc522_i2c/__init__.py new file mode 100644 index 0000000000..c5bb72ee53 --- /dev/null +++ b/esphome/components/rc522_i2c/__init__.py @@ -0,0 +1,22 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, rc522 +from esphome.const import CONF_ID + +CODEOWNERS = ['@glmnet'] +DEPENDENCIES = ['i2c'] +AUTO_LOAD = ['rc522'] + + +rc522_i2c_ns = cg.esphome_ns.namespace('rc522_i2c') +RC522I2C = rc522_i2c_ns.class_('RC522I2C', rc522.RC522, i2c.I2CDevice) + +CONFIG_SCHEMA = cv.All(rc522.RC522_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(RC522I2C), +}).extend(i2c.i2c_device_schema(0x2c))) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield rc522.setup_rc522(var, config) + yield i2c.register_i2c_device(var, config) diff --git a/esphome/components/rc522_i2c/rc522_i2c.cpp b/esphome/components/rc522_i2c/rc522_i2c.cpp new file mode 100644 index 0000000000..8248e79b50 --- /dev/null +++ b/esphome/components/rc522_i2c/rc522_i2c.cpp @@ -0,0 +1,99 @@ +#include "rc522_i2c.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace rc522_i2c { + +static const char *TAG = "rc522_i2c"; + +void RC522I2C::dump_config() { + RC522::dump_config(); + LOG_I2C_DEVICE(this); +} + +/** + * Reads a uint8_t from the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ +uint8_t RC522I2C::pcd_read_register(PcdRegister reg ///< The register to read from. One of the PCD_Register enums. +) { + uint8_t value; + read_byte(reg >> 1, &value); + ESP_LOGVV(TAG, "read_register_(%x) -> %x", reg, value); + return value; +} + +/** + * Reads a number of uint8_ts from the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ +void RC522I2C::pcd_read_register(PcdRegister reg, ///< The register to read from. One of the PCD_Register enums. + uint8_t count, ///< The number of uint8_ts to read + uint8_t *values, ///< uint8_t array to store the values in. + uint8_t rx_align ///< Only bit positions rxAlign..7 in values[0] are updated. +) { + if (count == 0) { + return; + } + + std::string buf; + buf = "Rx"; + char cstrb[20]; + + uint8_t b = values[0]; + read_bytes(reg >> 1, values, count); + + if (rx_align) // Only update bit positions rxAlign..7 in values[0] + { + // Create bit mask for bit positions rxAlign..7 + uint8_t mask = 0xFF << rx_align; + // Apply mask to both current value of values[0] and the new data in values array. + values[0] = (b & ~mask) | (values[0] & mask); + } +} + +void RC522I2C::pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. + uint8_t value ///< The value to write. +) { + this->write_byte(reg >> 1, value); +} + +/** + * Writes a number of uint8_ts to the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ +void RC522I2C::pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. + uint8_t count, ///< The number of uint8_ts to write to the register + uint8_t *values ///< The values to write. uint8_t array. +) { + write_bytes(reg >> 1, values, count); +} + +// bool RC522I2C::write_data(const std::vector &data) { +// return this->write_bytes_raw(data.data(), data.size()); } + +// bool RC522I2C::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 RC522!"); +// return false; +// } +// } + +// data.resize(len + 1); +// this->read_bytes_raw(data.data(), len + 1); +// return true; +// } + +} // namespace rc522_i2c +} // namespace esphome diff --git a/esphome/components/rc522_i2c/rc522_i2c.h b/esphome/components/rc522_i2c/rc522_i2c.h new file mode 100644 index 0000000000..8d8b0a0716 --- /dev/null +++ b/esphome/components/rc522_i2c/rc522_i2c.h @@ -0,0 +1,42 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/rc522/rc522.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace rc522_i2c { + +class RC522I2C : public rc522::RC522, public i2c::I2CDevice { + public: + void dump_config() override; + + protected: + uint8_t pcd_read_register(PcdRegister reg ///< The register to read from. One of the PCD_Register enums. + ) override; + + /** + * Reads a number of uint8_ts from the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ + void pcd_read_register(PcdRegister reg, ///< The register to read from. One of the PCD_Register enums. + uint8_t count, ///< The number of uint8_ts to read + uint8_t *values, ///< uint8_t array to store the values in. + uint8_t rx_align ///< Only bit positions rxAlign..7 in values[0] are updated. + ) override; + void pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. + uint8_t value ///< The value to write. + ) override; + + /** + * Writes a number of uint8_ts to the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ + void pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. + uint8_t count, ///< The number of uint8_ts to write to the register + uint8_t *values ///< The values to write. uint8_t array. + ) override; +}; + +} // namespace rc522_i2c +} // namespace esphome diff --git a/esphome/components/rc522_spi/__init__.py b/esphome/components/rc522_spi/__init__.py index 043d083c4c..37f78b66d0 100644 --- a/esphome/components/rc522_spi/__init__.py +++ b/esphome/components/rc522_spi/__init__.py @@ -1,39 +1,21 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome import automation, pins -from esphome.components import spi -from esphome.const import CONF_ID, CONF_ON_TAG, CONF_TRIGGER_ID, CONF_RESET_PIN, CONF_CS_PIN +from esphome.components import spi, rc522 +from esphome.const import CONF_ID CODEOWNERS = ['@glmnet'] DEPENDENCIES = ['spi'] -AUTO_LOAD = ['binary_sensor'] -MULTI_CONF = True - +AUTO_LOAD = ['rc522'] rc522_spi_ns = cg.esphome_ns.namespace('rc522_spi') -RC522 = rc522_spi_ns.class_('RC522', cg.PollingComponent, spi.SPIDevice) -RC522Trigger = rc522_spi_ns.class_('RC522Trigger', automation.Trigger.template(cg.std_string)) +RC522Spi = rc522_spi_ns.class_('RC522Spi', rc522.RC522, spi.SPIDevice) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(RC522), - cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_CS_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_ON_TAG): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RC522Trigger), - }), -}).extend(cv.polling_component_schema('1s')).extend(spi.spi_device_schema()) +CONFIG_SCHEMA = cv.All(rc522.RC522_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(RC522Spi), +}).extend(spi.spi_device_schema(cs_pin_required=True))) def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - yield cg.register_component(var, config) + yield rc522.setup_rc522(var, config) yield spi.register_spi_device(var, config) - - if CONF_RESET_PIN in config: - reset = yield cg.gpio_pin_expression(config[CONF_RESET_PIN]) - cg.add(var.set_reset_pin(reset)) - - for conf in config.get(CONF_ON_TAG, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) - cg.add(var.register_trigger(trigger)) - yield automation.build_automation(trigger, [(cg.std_string, 'x')], conf) diff --git a/esphome/components/rc522_spi/binary_sensor.py b/esphome/components/rc522_spi/binary_sensor.py index d58d62c797..1f0eafe6af 100644 --- a/esphome/components/rc522_spi/binary_sensor.py +++ b/esphome/components/rc522_spi/binary_sensor.py @@ -1,44 +1,9 @@ -import esphome.codegen as cg -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 rc522_spi_ns, RC522 +import esphome.components.rc522.binary_sensor as rc522_binary_sensor -DEPENDENCIES = ['rc522_spi'] +DEPENDENCIES = ['rc522'] -CONF_RC522_ID = 'rc522_id' - - -def validate_uid(value): - value = cv.string_strict(value) - for x in value.split('-'): - if len(x) != 2: - raise cv.Invalid("Each part (separated by '-') of the UID must be two characters " - "long.") - try: - x = int(x, 16) - except ValueError as err: - raise cv.Invalid("Valid characters for parts of a UID are 0123456789ABCDEF.") from err - if x < 0 or x > 255: - raise cv.Invalid("Valid values for UID parts (separated by '-') are 00 to FF") - return value - - -RC522BinarySensor = rc522_spi_ns.class_('RC522BinarySensor', binary_sensor.BinarySensor) - -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(RC522BinarySensor), - cv.GenerateID(CONF_RC522_ID): cv.use_id(RC522), - cv.Required(CONF_UID): validate_uid, -}) +CONFIG_SCHEMA = rc522_binary_sensor.CONFIG_SCHEMA def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - yield binary_sensor.register_binary_sensor(var, config) - - hub = yield cg.get_variable(config[CONF_RC522_ID]) - cg.add(hub.register_tag(var)) - addr = [HexInt(int(x, 16)) for x in config[CONF_UID].split('-')] - cg.add(var.set_uid(addr)) + yield rc522_binary_sensor.to_code(config) diff --git a/esphome/components/rc522_spi/rc522_spi.cpp b/esphome/components/rc522_spi/rc522_spi.cpp index b332e50c53..61236393e4 100644 --- a/esphome/components/rc522_spi/rc522_spi.cpp +++ b/esphome/components/rc522_spi/rc522_spi.cpp @@ -9,218 +9,30 @@ namespace rc522_spi { static const char *TAG = "rc522_spi"; -static const uint8_t RESET_COUNT = 5; +void RC522Spi::setup() { + ESP_LOGI(TAG, "SPI Setup"); + this->spi_setup(); -void format_uid(char *buf, const uint8_t *uid, uint8_t uid_length) { - int offset = 0; - for (uint8_t i = 0; i < uid_length; i++) { - const char *format = "%02X"; - if (i + 1 < uid_length) - format = "%02X-"; - offset += sprintf(buf + offset, format, uid[i]); - } + RC522::setup(); } -void RC522::setup() { - spi_setup(); - initialize_pending_ = true; - // Pull device out of power down / reset state. - - // First set the resetPowerDownPin as digital input, to check the MFRC522 power down mode. - if (reset_pin_ != nullptr) { - reset_pin_->pin_mode(INPUT); - - if (reset_pin_->digital_read() == LOW) { // The MFRC522 chip is in power down mode. - ESP_LOGV(TAG, "Power down mode detected. Hard resetting..."); - reset_pin_->pin_mode(OUTPUT); // Now set the resetPowerDownPin as digital output. - reset_pin_->digital_write(LOW); // Make sure we have a clean LOW state. - delayMicroseconds(2); // 8.8.1 Reset timing requirements says about 100ns. Let us be generous: 2μsl - reset_pin_->digital_write(HIGH); // Exit power down mode. This triggers a hard reset. - // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs. - // Let us be generous: 50ms. - reset_timeout_ = millis(); - return; - } - } - - // Setup a soft reset - reset_count_ = RESET_COUNT; - reset_timeout_ = millis(); -} - -void RC522::initialize_() { - // Per originall code, wait 50 ms - if (millis() - reset_timeout_ < 50) - return; - - // Reset baud rates - ESP_LOGV(TAG, "Initialize"); - - pcd_write_register_(TX_MODE_REG, 0x00); - pcd_write_register_(RX_MODE_REG, 0x00); - // Reset ModWidthReg - pcd_write_register_(MOD_WIDTH_REG, 0x26); - - // When communicating with a PICC we need a timeout if something goes wrong. - // f_timer = 13.56 MHz / (2*TPreScaler+1) where TPreScaler = [TPrescaler_Hi:TPrescaler_Lo]. - // TPrescaler_Hi are the four low bits in TModeReg. TPrescaler_Lo is TPrescalerReg. - pcd_write_register_(T_MODE_REG, 0x80); // TAuto=1; timer starts automatically at the end of the transmission in all - // communication modes at all speeds - - // TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9 = 169 => f_timer=40kHz, ie a timer period of 25μs. - pcd_write_register_(T_PRESCALER_REG, 0xA9); - pcd_write_register_(T_RELOAD_REG_H, 0x03); // Reload timer with 0x3E8 = 1000, ie 25ms before timeout. - pcd_write_register_(T_RELOAD_REG_L, 0xE8); - - // Default 0x00. Force a 100 % ASK modulation independent of the ModGsPReg register setting - pcd_write_register_(TX_ASK_REG, 0x40); - pcd_write_register_(MODE_REG, 0x3D); // Default 0x3F. Set the preset value for the CRC coprocessor for the CalcCRC - // command to 0x6363 (ISO 14443-3 part 6.2.4) - pcd_antenna_on_(); // Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset) - - initialize_pending_ = false; -} - -void RC522::dump_config() { - ESP_LOGCONFIG(TAG, "RC522:"); - switch (this->error_code_) { - case NONE: - break; - case RESET_FAILED: - ESP_LOGE(TAG, "Reset command failed!"); - break; - } - +void RC522Spi::dump_config() { + RC522::dump_config(); LOG_PIN(" CS Pin: ", this->cs_); - LOG_PIN(" RESET Pin: ", this->reset_pin_); - - LOG_UPDATE_INTERVAL(this); - - for (auto *child : this->binary_sensors_) { - LOG_BINARY_SENSOR(" ", "Tag", child); - } -} - -void RC522::loop() { - // First check reset is needed - if (reset_count_ > 0) { - pcd_reset_(); - return; - } - if (initialize_pending_) { - initialize_(); - return; - } - - if (millis() - update_wait_ < this->update_interval_) - return; - - auto status = picc_is_new_card_present_(); - - if (status == STATUS_ERROR) // No card - { - ESP_LOGE(TAG, "Error"); - // mark_failed(); - return; - } - - if (status != STATUS_OK) // We can receive STATUS_TIMEOUT when no card, or unexpected status. - return; - - // Try process card - if (!picc_read_card_serial_()) { - ESP_LOGW(TAG, "Requesting tag read failed!"); - return; - }; - - if (uid_.size < 4) { - return; - ESP_LOGW(TAG, "Read serial size: %d", uid_.size); - } - - update_wait_ = millis(); - - bool report = true; - // 1. Go through all triggers - for (auto *trigger : this->triggers_) - trigger->process(uid_.uiduint8_t, uid_.size); - - // 2. Find a binary sensor - for (auto *tag : this->binary_sensors_) { - if (tag->process(uid_.uiduint8_t, uid_.size)) { - // 2.1 if found, do not dump - report = false; - } - } - - if (report) { - char buf[32]; - format_uid(buf, uid_.uiduint8_t, uid_.size); - ESP_LOGD(TAG, "Found new tag '%s'", buf); - } -} - -void RC522::update() { - for (auto *obj : this->binary_sensors_) - obj->on_scan_end(); -} - -/** - * Performs a soft reset on the MFRC522 chip and waits for it to be ready again. - */ -void RC522::pcd_reset_() { - // The datasheet does not mention how long the SoftRest command takes to complete. - // But the MFRC522 might have been in soft power-down mode (triggered by bit 4 of CommandReg) - // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs. Let - // us be generous: 50ms. - - if (millis() - reset_timeout_ < 50) - return; - - if (reset_count_ == RESET_COUNT) { - ESP_LOGV(TAG, "Soft reset..."); - // Issue the SoftReset command. - pcd_write_register_(COMMAND_REG, PCD_SOFT_RESET); - } - - // Expect the PowerDown bit in CommandReg to be cleared (max 3x50ms) - if ((pcd_read_register_(COMMAND_REG) & (1 << 4)) == 0) { - reset_count_ = 0; - ESP_LOGI(TAG, "Device online."); - // Wait for initialize - reset_timeout_ = millis(); - return; - } - - if (--reset_count_ == 0) { - ESP_LOGE(TAG, "Unable to reset RC522."); - mark_failed(); - } -} - -/** - * Turns the antenna on by enabling pins TX1 and TX2. - * After a reset these pins are disabled. - */ -void RC522::pcd_antenna_on_() { - uint8_t value = pcd_read_register_(TX_CONTROL_REG); - if ((value & 0x03) != 0x03) { - pcd_write_register_(TX_CONTROL_REG, value | 0x03); - } } /** * Reads a uint8_t from the specified register in the MFRC522 chip. * The interface is described in the datasheet section 8.1.2. */ -uint8_t RC522::pcd_read_register_(PcdRegister reg ///< The register to read from. One of the PCD_Register enums. +uint8_t RC522Spi::pcd_read_register(PcdRegister reg ///< The register to read from. One of the PCD_Register enums. ) { uint8_t value; enable(); transfer_byte(0x80 | reg); value = read_byte(); disable(); - ESP_LOGVV(TAG, "read_register_(%x) -> %x", reg, value); + ESP_LOGV(TAG, "read_register_(%x) -> %x", reg, value); return value; } @@ -228,10 +40,10 @@ uint8_t RC522::pcd_read_register_(PcdRegister reg ///< The register to read fro * Reads a number of uint8_ts from the specified register in the MFRC522 chip. * The interface is described in the datasheet section 8.1.2. */ -void RC522::pcd_read_register_(PcdRegister reg, ///< The register to read from. One of the PCD_Register enums. - uint8_t count, ///< The number of uint8_ts to read - uint8_t *values, ///< uint8_t array to store the values in. - uint8_t rx_align ///< Only bit positions rxAlign..7 in values[0] are updated. +void RC522Spi::pcd_read_register(PcdRegister reg, ///< The register to read from. One of the PCD_Register enums. + uint8_t count, ///< The number of uint8_ts to read + uint8_t *values, ///< uint8_t array to store the values in. + uint8_t rx_align ///< Only bit positions rxAlign..7 in values[0] are updated. ) { std::string buf; buf = "Rx"; @@ -278,8 +90,8 @@ void RC522::pcd_read_register_(PcdRegister reg, ///< The register to read from. disable(); } -void RC522::pcd_write_register_(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. - uint8_t value ///< The value to write. +void RC522Spi::pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. + uint8_t value ///< The value to write. ) { enable(); // MSB == 0 is for writing. LSB is not used in address. Datasheet section 8.1.2.3. @@ -292,9 +104,9 @@ void RC522::pcd_write_register_(PcdRegister reg, ///< The register to write to. * Writes a number of uint8_ts to the specified register in the MFRC522 chip. * The interface is described in the datasheet section 8.1.2. */ -void RC522::pcd_write_register_(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. - uint8_t count, ///< The number of uint8_ts to write to the register - uint8_t *values ///< The values to write. uint8_t array. +void RC522Spi::pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. + uint8_t count, ///< The number of uint8_ts to write to the register + uint8_t *values ///< The values to write. uint8_t array. ) { std::string buf; buf = "Tx"; @@ -313,545 +125,5 @@ void RC522::pcd_write_register_(PcdRegister reg, ///< The register to write to. ESP_LOGVV(TAG, "write_register_(%x, %d) -> %s", reg, count, buf.c_str()); } -/** - * Transmits a REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or - * selection. 7 bit frame. Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - - * probably due do bad antenna design. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -RC522::StatusCode RC522::picc_request_a_( - uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in - uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK. -) { - return picc_reqa_or_wupa_(PICC_CMD_REQA, buffer_atqa, buffer_size); -} - -/** - * Transmits REQA or WUPA commands. - * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna - * design. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -RC522::StatusCode RC522::picc_reqa_or_wupa_( - uint8_t command, ///< The command to send - PICC_CMD_REQA or PICC_CMD_WUPA - uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in - uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK. -) { - uint8_t valid_bits; - RC522::StatusCode status; - - if (buffer_atqa == nullptr || *buffer_size < 2) { // The ATQA response is 2 uint8_ts long. - return STATUS_NO_ROOM; - } - pcd_clear_register_bit_mask_(COLL_REG, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared. - valid_bits = 7; // For REQA and WUPA we need the short frame format - transmit only 7 bits of the last (and only) - // uint8_t. TxLastBits = BitFramingReg[2..0] - status = pcd_transceive_data_(&command, 1, buffer_atqa, buffer_size, &valid_bits); - if (status != STATUS_OK) - return status; - if (*buffer_size != 2 || valid_bits != 0) { // ATQA must be exactly 16 bits. - ESP_LOGVV(TAG, "picc_reqa_or_wupa_() -> STATUS_ERROR"); - return STATUS_ERROR; - } - - return STATUS_OK; -} - -/** - * Sets the bits given in mask in register reg. - */ -void RC522::pcd_set_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums. - uint8_t mask ///< The bits to set. -) { - uint8_t tmp = pcd_read_register_(reg); - pcd_write_register_(reg, tmp | mask); // set bit mask -} - -/** - * Clears the bits given in mask from register reg. - */ -void RC522::pcd_clear_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums. - uint8_t mask ///< The bits to clear. -) { - uint8_t tmp = pcd_read_register_(reg); - pcd_write_register_(reg, tmp & (~mask)); // clear bit mask -} - -/** - * Executes the Transceive command. - * CRC validation can only be done if backData and backLen are specified. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -RC522::StatusCode RC522::pcd_transceive_data_( - uint8_t *send_data, ///< Pointer to the data to transfer to the FIFO. - uint8_t send_len, ///< Number of uint8_ts to transfer to the FIFO. - uint8_t *back_data, ///< nullptr or pointer to buffer if data should be read back after executing the command. - uint8_t *back_len, ///< In: Max number of uint8_ts to write to *backData. Out: The number of uint8_ts returned. - uint8_t - *valid_bits, ///< In/Out: The number of valid bits in the last uint8_t. 0 for 8 valid bits. Default nullptr. - uint8_t rx_align, ///< In: Defines the bit position in backData[0] for the first bit received. Default 0. - bool check_crc ///< In: True => The last two uint8_ts of the response is assumed to be a CRC_A that must be - ///< validated. -) { - uint8_t wait_i_rq = 0x30; // RxIRq and IdleIRq - auto ret = pcd_communicate_with_picc_(PCD_TRANSCEIVE, wait_i_rq, send_data, send_len, back_data, back_len, valid_bits, - rx_align, check_crc); - - if (ret == STATUS_OK && *back_len == 5) - ESP_LOGVV(TAG, "pcd_transceive_data_(..., %d, ) -> %d [%x, %x, %x, %x, %x]", send_len, ret, back_data[0], - back_data[1], back_data[2], back_data[3], back_data[4]); - else - ESP_LOGVV(TAG, "pcd_transceive_data_(..., %d, ... ) -> %d", send_len, ret); - return ret; -} - -/** - * Transfers data to the MFRC522 FIFO, executes a command, waits for completion and transfers data back from the FIFO. - * CRC validation can only be done if backData and backLen are specified. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -RC522::StatusCode RC522::pcd_communicate_with_picc_( - uint8_t command, ///< The command to execute. One of the PCD_Command enums. - uint8_t wait_i_rq, ///< The bits in the ComIrqReg register that signals successful completion of the command. - uint8_t *send_data, ///< Pointer to the data to transfer to the FIFO. - uint8_t send_len, ///< Number of uint8_ts to transfer to the FIFO. - uint8_t *back_data, ///< nullptr or pointer to buffer if data should be read back after executing the command. - uint8_t *back_len, ///< In: Max number of uint8_ts to write to *backData. Out: The number of uint8_ts returned. - uint8_t *valid_bits, ///< In/Out: The number of valid bits in the last uint8_t. 0 for 8 valid bits. - uint8_t rx_align, ///< In: Defines the bit position in backData[0] for the first bit received. Default 0. - bool check_crc ///< In: True => The last two uint8_ts of the response is assumed to be a CRC_A that must be - ///< validated. -) { - ESP_LOGVV(TAG, "pcd_communicate_with_picc_(%d, %d,... %d)", command, wait_i_rq, check_crc); - - // Prepare values for BitFramingReg - uint8_t tx_last_bits = valid_bits ? *valid_bits : 0; - uint8_t bit_framing = - (rx_align << 4) + tx_last_bits; // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] - - pcd_write_register_(COMMAND_REG, PCD_IDLE); // Stop any active command. - pcd_write_register_(COM_IRQ_REG, 0x7F); // Clear all seven interrupt request bits - pcd_write_register_(FIFO_LEVEL_REG, 0x80); // FlushBuffer = 1, FIFO initialization - pcd_write_register_(FIFO_DATA_REG, send_len, send_data); // Write sendData to the FIFO - pcd_write_register_(BIT_FRAMING_REG, bit_framing); // Bit adjustments - pcd_write_register_(COMMAND_REG, command); // Execute the command - if (command == PCD_TRANSCEIVE) { - pcd_set_register_bit_mask_(BIT_FRAMING_REG, 0x80); // StartSend=1, transmission of data starts - } - - // Wait for the command to complete. - // In PCD_Init() we set the TAuto flag in TModeReg. This means the timer automatically starts when the PCD stops - // transmitting. Each iteration of the do-while-loop takes 17.86μs. - // TODO check/modify for other architectures than Arduino Uno 16bit - uint16_t i; - for (i = 2000; i > 0; i--) { - uint8_t n = pcd_read_register_( - COM_IRQ_REG); // ComIrqReg[7..0] bits are: Set1 TxIRq RxIRq IdleIRq HiAlertIRq LoAlertIRq ErrIRq TimerIRq - if (n & wait_i_rq) { // One of the interrupts that signal success has been set. - break; - } - if (n & 0x01) { // Timer interrupt - nothing received in 25ms - return STATUS_TIMEOUT; - } - } - // 35.7ms and nothing happend. Communication with the MFRC522 might be down. - if (i == 0) { - return STATUS_TIMEOUT; - } - - // Stop now if any errors except collisions were detected. - uint8_t error_reg_value = pcd_read_register_( - ERROR_REG); // ErrorReg[7..0] bits are: WrErr TempErr reserved BufferOvfl CollErr CRCErr ParityErr ProtocolErr - if (error_reg_value & 0x13) { // BufferOvfl ParityErr ProtocolErr - return STATUS_ERROR; - } - - uint8_t valid_bits_local = 0; - - // If the caller wants data back, get it from the MFRC522. - if (back_data && back_len) { - uint8_t n = pcd_read_register_(FIFO_LEVEL_REG); // Number of uint8_ts in the FIFO - if (n > *back_len) { - return STATUS_NO_ROOM; - } - *back_len = n; // Number of uint8_ts returned - pcd_read_register_(FIFO_DATA_REG, n, back_data, rx_align); // Get received data from FIFO - valid_bits_local = - pcd_read_register_(CONTROL_REG) & 0x07; // RxLastBits[2:0] indicates the number of valid bits in the last - // received uint8_t. If this value is 000b, the whole uint8_t is valid. - if (valid_bits) { - *valid_bits = valid_bits_local; - } - } - - // Tell about collisions - if (error_reg_value & 0x08) { // CollErr - return STATUS_COLLISION; - } - - // Perform CRC_A validation if requested. - if (back_data && back_len && check_crc) { - // In this case a MIFARE Classic NAK is not OK. - if (*back_len == 1 && valid_bits_local == 4) { - return STATUS_MIFARE_NACK; - } - // We need at least the CRC_A value and all 8 bits of the last uint8_t must be received. - if (*back_len < 2 || valid_bits_local != 0) { - return STATUS_CRC_WRONG; - } - // Verify CRC_A - do our own calculation and store the control in controlBuffer. - uint8_t control_buffer[2]; - RC522::StatusCode status = pcd_calculate_crc_(&back_data[0], *back_len - 2, &control_buffer[0]); - if (status != STATUS_OK) { - return status; - } - if ((back_data[*back_len - 2] != control_buffer[0]) || (back_data[*back_len - 1] != control_buffer[1])) { - return STATUS_CRC_WRONG; - } - } - - return STATUS_OK; -} - -/** - * Use the CRC coprocessor in the MFRC522 to calculate a CRC_A. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ - -RC522::StatusCode RC522::pcd_calculate_crc_( - uint8_t *data, ///< In: Pointer to the data to transfer to the FIFO for CRC calculation. - uint8_t length, ///< In: The number of uint8_ts to transfer. - uint8_t *result ///< Out: Pointer to result buffer. Result is written to result[0..1], low uint8_t first. -) { - ESP_LOGVV(TAG, "pcd_calculate_crc_(..., %d, ...)", length); - pcd_write_register_(COMMAND_REG, PCD_IDLE); // Stop any active command. - pcd_write_register_(DIV_IRQ_REG, 0x04); // Clear the CRCIRq interrupt request bit - pcd_write_register_(FIFO_LEVEL_REG, 0x80); // FlushBuffer = 1, FIFO initialization - pcd_write_register_(FIFO_DATA_REG, length, data); // Write data to the FIFO - pcd_write_register_(COMMAND_REG, PCD_CALC_CRC); // Start the calculation - - // Wait for the CRC calculation to complete. Each iteration of the while-loop takes 17.73μs. - // TODO check/modify for other architectures than Arduino Uno 16bit - - // Wait for the CRC calculation to complete. Each iteration of the while-loop takes 17.73us. - for (uint16_t i = 5000; i > 0; i--) { - // DivIrqReg[7..0] bits are: Set2 reserved reserved MfinActIRq reserved CRCIRq reserved reserved - uint8_t n = pcd_read_register_(DIV_IRQ_REG); - if (n & 0x04) { // CRCIRq bit set - calculation done - pcd_write_register_(COMMAND_REG, PCD_IDLE); // Stop calculating CRC for new content in the FIFO. - // Transfer the result from the registers to the result buffer - result[0] = pcd_read_register_(CRC_RESULT_REG_L); - result[1] = pcd_read_register_(CRC_RESULT_REG_H); - - ESP_LOGVV(TAG, "pcd_calculate_crc_() STATUS_OK"); - return STATUS_OK; - } - } - ESP_LOGVV(TAG, "pcd_calculate_crc_() TIMEOUT"); - // 89ms passed and nothing happend. Communication with the MFRC522 might be down. - return STATUS_TIMEOUT; -} -/** - * Returns STATUS_OK if a PICC responds to PICC_CMD_REQA. - * Only "new" cards in state IDLE are invited. Sleeping cards in state HALT are ignored. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ - -RC522::StatusCode RC522::picc_is_new_card_present_() { - uint8_t buffer_atqa[2]; - uint8_t buffer_size = sizeof(buffer_atqa); - - // Reset baud rates - pcd_write_register_(TX_MODE_REG, 0x00); - pcd_write_register_(RX_MODE_REG, 0x00); - // Reset ModWidthReg - pcd_write_register_(MOD_WIDTH_REG, 0x26); - - auto result = picc_request_a_(buffer_atqa, &buffer_size); - - ESP_LOGV(TAG, "picc_is_new_card_present_() -> %d", result); - return result; -} - -/** - * Simple wrapper around PICC_Select. - * Returns true if a UID could be read. - * Remember to call PICC_IsNewCardPresent(), PICC_RequestA() or PICC_WakeupA() first. - * The read UID is available in the class variable uid. - * - * @return bool - */ -bool RC522::picc_read_card_serial_() { - RC522::StatusCode result = picc_select_(&this->uid_); - ESP_LOGVV(TAG, "picc_select_(...) -> %d", result); - return (result == STATUS_OK); -} - -/** - * Transmits SELECT/ANTICOLLISION commands to select a single PICC. - * Before calling this function the PICCs must be placed in the READY(*) state by calling PICC_RequestA() or - * PICC_WakeupA(). On success: - * - The chosen PICC is in state ACTIVE(*) and all other PICCs have returned to state IDLE/HALT. (Figure 7 of the - * ISO/IEC 14443-3 draft.) - * - The UID size and value of the chosen PICC is returned in *uid along with the SAK. - * - * A PICC UID consists of 4, 7 or 10 uint8_ts. - * Only 4 uint8_ts can be specified in a SELECT command, so for the longer UIDs two or three iterations are used: - * UID size Number of UID uint8_ts Cascade levels Example of PICC - * ======== =================== ============== =============== - * single 4 1 MIFARE Classic - * double 7 2 MIFARE Ultralight - * triple 10 3 Not currently in use? - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -RC522::StatusCode RC522::picc_select_( - Uid *uid, ///< Pointer to Uid struct. Normally output, but can also be used to supply a known UID. - uint8_t valid_bits ///< The number of known UID bits supplied in *uid. Normally 0. If set you must also supply - ///< uid->size. -) { - bool uid_complete; - bool select_done; - bool use_cascade_tag; - uint8_t cascade_level = 1; - RC522::StatusCode result; - uint8_t count; - uint8_t check_bit; - uint8_t index; - uint8_t uid_index; // The first index in uid->uiduint8_t[] that is used in the current Cascade Level. - int8_t current_level_known_bits; // The number of known UID bits in the current Cascade Level. - uint8_t buffer[9]; // The SELECT/ANTICOLLISION commands uses a 7 uint8_t standard frame + 2 uint8_ts CRC_A - uint8_t buffer_used; // The number of uint8_ts used in the buffer, ie the number of uint8_ts to transfer to the FIFO. - uint8_t rx_align; // Used in BitFramingReg. Defines the bit position for the first bit received. - uint8_t tx_last_bits; // Used in BitFramingReg. The number of valid bits in the last transmitted uint8_t. - uint8_t *response_buffer; - uint8_t response_length; - - // Description of buffer structure: - // uint8_t 0: SEL Indicates the Cascade Level: PICC_CMD_SEL_CL1, PICC_CMD_SEL_CL2 or PICC_CMD_SEL_CL3 - // uint8_t 1: NVB Number of Valid Bits (in complete command, not just the UID): High nibble: complete - // uint8_ts, - // Low nibble: Extra bits. uint8_t 2: UID-data or CT See explanation below. CT means Cascade Tag. uint8_t - // 3: UID-data uint8_t 4: UID-data uint8_t 5: UID-data uint8_t 6: BCC Block Check Character - XOR of - // uint8_ts 2-5 uint8_t 7: CRC_A uint8_t 8: CRC_A The BCC and CRC_A are only transmitted if we know all the UID bits - // of the current Cascade Level. - // - // Description of uint8_ts 2-5: (Section 6.5.4 of the ISO/IEC 14443-3 draft: UID contents and cascade levels) - // UID size Cascade level uint8_t2 uint8_t3 uint8_t4 uint8_t5 - // ======== ============= ===== ===== ===== ===== - // 4 uint8_ts 1 uid0 uid1 uid2 uid3 - // 7 uint8_ts 1 CT uid0 uid1 uid2 - // 2 uid3 uid4 uid5 uid6 - // 10 uint8_ts 1 CT uid0 uid1 uid2 - // 2 CT uid3 uid4 uid5 - // 3 uid6 uid7 uid8 uid9 - - // Sanity checks - if (valid_bits > 80) { - return STATUS_INVALID; - } - - ESP_LOGVV(TAG, "picc_select_(&, %d)", valid_bits); - - // Prepare MFRC522 - pcd_clear_register_bit_mask_(COLL_REG, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared. - - // Repeat Cascade Level loop until we have a complete UID. - uid_complete = false; - while (!uid_complete) { - // Set the Cascade Level in the SEL uint8_t, find out if we need to use the Cascade Tag in uint8_t 2. - switch (cascade_level) { - case 1: - buffer[0] = PICC_CMD_SEL_CL1; - uid_index = 0; - use_cascade_tag = valid_bits && uid->size > 4; // When we know that the UID has more than 4 uint8_ts - break; - - case 2: - buffer[0] = PICC_CMD_SEL_CL2; - uid_index = 3; - use_cascade_tag = valid_bits && uid->size > 7; // When we know that the UID has more than 7 uint8_ts - break; - - case 3: - buffer[0] = PICC_CMD_SEL_CL3; - uid_index = 6; - use_cascade_tag = false; // Never used in CL3. - break; - - default: - return STATUS_INTERNAL_ERROR; - break; - } - - // How many UID bits are known in this Cascade Level? - current_level_known_bits = valid_bits - (8 * uid_index); - if (current_level_known_bits < 0) { - current_level_known_bits = 0; - } - // Copy the known bits from uid->uiduint8_t[] to buffer[] - index = 2; // destination index in buffer[] - if (use_cascade_tag) { - buffer[index++] = PICC_CMD_CT; - } - uint8_t uint8_ts_to_copy = current_level_known_bits / 8 + - (current_level_known_bits % 8 - ? 1 - : 0); // The number of uint8_ts needed to represent the known bits for this level. - if (uint8_ts_to_copy) { - uint8_t maxuint8_ts = - use_cascade_tag ? 3 : 4; // Max 4 uint8_ts in each Cascade Level. Only 3 left if we use the Cascade Tag - if (uint8_ts_to_copy > maxuint8_ts) { - uint8_ts_to_copy = maxuint8_ts; - } - for (count = 0; count < uint8_ts_to_copy; count++) { - buffer[index++] = uid->uiduint8_t[uid_index + count]; - } - } - // Now that the data has been copied we need to include the 8 bits in CT in currentLevelKnownBits - if (use_cascade_tag) { - current_level_known_bits += 8; - } - - // Repeat anti collision loop until we can transmit all UID bits + BCC and receive a SAK - max 32 iterations. - select_done = false; - while (!select_done) { - // Find out how many bits and uint8_ts to send and receive. - if (current_level_known_bits >= 32) { // All UID bits in this Cascade Level are known. This is a SELECT. - - if (response_length < 4) { - ESP_LOGW(TAG, "Not enough data received."); - return STATUS_INVALID; - } - - // Serial.print(F("SELECT: currentLevelKnownBits=")); Serial.println(currentLevelKnownBits, DEC); - buffer[1] = 0x70; // NVB - Number of Valid Bits: Seven whole uint8_ts - // Calculate BCC - Block Check Character - buffer[6] = buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5]; - // Calculate CRC_A - result = pcd_calculate_crc_(buffer, 7, &buffer[7]); - if (result != STATUS_OK) { - return result; - } - tx_last_bits = 0; // 0 => All 8 bits are valid. - buffer_used = 9; - // Store response in the last 3 uint8_ts of buffer (BCC and CRC_A - not needed after tx) - response_buffer = &buffer[6]; - response_length = 3; - } else { // This is an ANTICOLLISION. - // Serial.print(F("ANTICOLLISION: currentLevelKnownBits=")); Serial.println(currentLevelKnownBits, DEC); - tx_last_bits = current_level_known_bits % 8; - count = current_level_known_bits / 8; // Number of whole uint8_ts in the UID part. - index = 2 + count; // Number of whole uint8_ts: SEL + NVB + UIDs - buffer[1] = (index << 4) + tx_last_bits; // NVB - Number of Valid Bits - buffer_used = index + (tx_last_bits ? 1 : 0); - // Store response in the unused part of buffer - response_buffer = &buffer[index]; - response_length = sizeof(buffer) - index; - } - - // Set bit adjustments - rx_align = tx_last_bits; // Having a separate variable is overkill. But it makes the next line easier to read. - pcd_write_register_( - BIT_FRAMING_REG, - (rx_align << 4) + tx_last_bits); // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] - - // Transmit the buffer and receive the response. - result = pcd_transceive_data_(buffer, buffer_used, response_buffer, &response_length, &tx_last_bits, rx_align); - if (result == STATUS_COLLISION) { // More than one PICC in the field => collision. - uint8_t value_of_coll_reg = pcd_read_register_( - COLL_REG); // CollReg[7..0] bits are: ValuesAfterColl reserved CollPosNotValid CollPos[4:0] - if (value_of_coll_reg & 0x20) { // CollPosNotValid - return STATUS_COLLISION; // Without a valid collision position we cannot continue - } - uint8_t collision_pos = value_of_coll_reg & 0x1F; // Values 0-31, 0 means bit 32. - if (collision_pos == 0) { - collision_pos = 32; - } - if (collision_pos <= current_level_known_bits) { // No progress - should not happen - return STATUS_INTERNAL_ERROR; - } - // Choose the PICC with the bit set. - current_level_known_bits = collision_pos; - count = current_level_known_bits % 8; // The bit to modify - check_bit = (current_level_known_bits - 1) % 8; - index = 1 + (current_level_known_bits / 8) + (count ? 1 : 0); // First uint8_t is index 0. - if (response_length > 2) // Note: Otherwise buffer[index] might be not initialized - buffer[index] |= (1 << check_bit); - } else if (result != STATUS_OK) { - return result; - } else { // STATUS_OK - if (current_level_known_bits >= 32) { // This was a SELECT. - select_done = true; // No more anticollision - // We continue below outside the while. - } else { // This was an ANTICOLLISION. - // We now have all 32 bits of the UID in this Cascade Level - current_level_known_bits = 32; - // Run loop again to do the SELECT. - } - } - } // End of while (!selectDone) - - // We do not check the CBB - it was constructed by us above. - - // Copy the found UID uint8_ts from buffer[] to uid->uiduint8_t[] - index = (buffer[2] == PICC_CMD_CT) ? 3 : 2; // source index in buffer[] - uint8_ts_to_copy = (buffer[2] == PICC_CMD_CT) ? 3 : 4; - for (count = 0; count < uint8_ts_to_copy; count++) { - uid->uiduint8_t[uid_index + count] = buffer[index++]; - } - - // Check response SAK (Select Acknowledge) - if (response_length != 3 || tx_last_bits != 0) { // SAK must be exactly 24 bits (1 uint8_t + CRC_A). - return STATUS_ERROR; - } - // Verify CRC_A - do our own calculation and store the control in buffer[2..3] - those uint8_ts are not needed - // anymore. - result = pcd_calculate_crc_(response_buffer, 1, &buffer[2]); - if (result != STATUS_OK) { - return result; - } - if ((buffer[2] != response_buffer[1]) || (buffer[3] != response_buffer[2])) { - return STATUS_CRC_WRONG; - } - if (response_buffer[0] & 0x04) { // Cascade bit set - UID not complete yes - cascade_level++; - } else { - uid_complete = true; - uid->sak = response_buffer[0]; - } - } // End of while (!uidComplete) - - // Set correct uid->size - uid->size = 3 * cascade_level + 1; - - return STATUS_OK; -} - -bool RC522BinarySensor::process(const uint8_t *data, uint8_t len) { - if (len != this->uid_.size()) - return false; - - for (uint8_t i = 0; i < len; i++) { - if (data[i] != this->uid_[i]) - return false; - } - - this->publish_state(true); - this->found_ = true; - return true; -} -void RC522Trigger::process(const uint8_t *uid, uint8_t uid_length) { - char buf[32]; - format_uid(buf, uid, uid_length); - this->trigger(std::string(buf)); -} - } // namespace rc522_spi } // namespace esphome diff --git a/esphome/components/rc522_spi/rc522_spi.h b/esphome/components/rc522_spi/rc522_spi.h index 51d0a9cb69..58edbbed4f 100644 --- a/esphome/components/rc522_spi/rc522_spi.h +++ b/esphome/components/rc522_spi/rc522_spi.h @@ -10,292 +10,46 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/automation.h" -#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/rc522/rc522.h" #include "esphome/components/spi/spi.h" namespace esphome { namespace rc522_spi { -class RC522BinarySensor; -class RC522Trigger; - -class RC522 : public PollingComponent, - public spi::SPIDevice { +class RC522Spi : public rc522::RC522, + public spi::SPIDevice { public: void setup() override; void dump_config() override; - void update() override; - float get_setup_priority() const override { return setup_priority::DATA; }; - - void loop() override; - - void register_tag(RC522BinarySensor *tag) { this->binary_sensors_.push_back(tag); } - void register_trigger(RC522Trigger *trig) { this->triggers_.push_back(trig); } - - void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } - protected: - enum PcdRegister : uint8_t { - // Page 0: Command and status - // 0x00 // reserved for future use - COMMAND_REG = 0x01 << 1, // starts and stops command execution - COM_I_EN_REG = 0x02 << 1, // enable and disable interrupt request control bits - DIV_I_EN_REG = 0x03 << 1, // enable and disable interrupt request control bits - COM_IRQ_REG = 0x04 << 1, // interrupt request bits - DIV_IRQ_REG = 0x05 << 1, // interrupt request bits - ERROR_REG = 0x06 << 1, // error bits showing the error status of the last command executed - STATUS1_REG = 0x07 << 1, // communication status bits - STATUS2_REG = 0x08 << 1, // receiver and transmitter status bits - FIFO_DATA_REG = 0x09 << 1, // input and output of 64 uint8_t FIFO buffer - FIFO_LEVEL_REG = 0x0A << 1, // number of uint8_ts stored in the FIFO buffer - WATER_LEVEL_REG = 0x0B << 1, // level for FIFO underflow and overflow warning - CONTROL_REG = 0x0C << 1, // miscellaneous control registers - BIT_FRAMING_REG = 0x0D << 1, // adjustments for bit-oriented frames - COLL_REG = 0x0E << 1, // bit position of the first bit-collision detected on the RF interface - // 0x0F // reserved for future use - - // Page 1: Command - // 0x10 // reserved for future use - MODE_REG = 0x11 << 1, // defines general modes for transmitting and receiving - TX_MODE_REG = 0x12 << 1, // defines transmission data rate and framing - RX_MODE_REG = 0x13 << 1, // defines reception data rate and framing - TX_CONTROL_REG = 0x14 << 1, // controls the logical behavior of the antenna driver pins TX1 and TX2 - TX_ASK_REG = 0x15 << 1, // controls the setting of the transmission modulation - TX_SEL_REG = 0x16 << 1, // selects the internal sources for the antenna driver - RX_SEL_REG = 0x17 << 1, // selects internal receiver settings - RX_THRESHOLD_REG = 0x18 << 1, // selects thresholds for the bit decoder - DEMOD_REG = 0x19 << 1, // defines demodulator settings - // 0x1A // reserved for future use - // 0x1B // reserved for future use - MF_TX_REG = 0x1C << 1, // controls some MIFARE communication transmit parameters - MF_RX_REG = 0x1D << 1, // controls some MIFARE communication receive parameters - // 0x1E // reserved for future use - SERIAL_SPEED_REG = 0x1F << 1, // selects the speed of the serial UART interface - - // Page 2: Configuration - // 0x20 // reserved for future use - CRC_RESULT_REG_H = 0x21 << 1, // shows the MSB and LSB values of the CRC calculation - CRC_RESULT_REG_L = 0x22 << 1, - // 0x23 // reserved for future use - MOD_WIDTH_REG = 0x24 << 1, // controls the ModWidth setting? - // 0x25 // reserved for future use - RF_CFG_REG = 0x26 << 1, // configures the receiver gain - GS_N_REG = 0x27 << 1, // selects the conductance of the antenna driver pins TX1 and TX2 for modulation - CW_GS_P_REG = 0x28 << 1, // defines the conductance of the p-driver output during periods of no modulation - MOD_GS_P_REG = 0x29 << 1, // defines the conductance of the p-driver output during periods of modulation - T_MODE_REG = 0x2A << 1, // defines settings for the internal timer - T_PRESCALER_REG = 0x2B << 1, // the lower 8 bits of the TPrescaler value. The 4 high bits are in TModeReg. - T_RELOAD_REG_H = 0x2C << 1, // defines the 16-bit timer reload value - T_RELOAD_REG_L = 0x2D << 1, - T_COUNTER_VALUE_REG_H = 0x2E << 1, // shows the 16-bit timer value - T_COUNTER_VALUE_REG_L = 0x2F << 1, - - // Page 3: Test Registers - // 0x30 // reserved for future use - TEST_SEL1_REG = 0x31 << 1, // general test signal configuration - TEST_SEL2_REG = 0x32 << 1, // general test signal configuration - TEST_PIN_EN_REG = 0x33 << 1, // enables pin output driver on pins D1 to D7 - TEST_PIN_VALUE_REG = 0x34 << 1, // defines the values for D1 to D7 when it is used as an I/O bus - TEST_BUS_REG = 0x35 << 1, // shows the status of the internal test bus - AUTO_TEST_REG = 0x36 << 1, // controls the digital self-test - VERSION_REG = 0x37 << 1, // shows the software version - ANALOG_TEST_REG = 0x38 << 1, // controls the pins AUX1 and AUX2 - TEST_DA_C1_REG = 0x39 << 1, // defines the test value for TestDAC1 - TEST_DA_C2_REG = 0x3A << 1, // defines the test value for TestDAC2 - TEST_ADC_REG = 0x3B << 1 // shows the value of ADC I and Q channels - // 0x3C // reserved for production tests - // 0x3D // reserved for production tests - // 0x3E // reserved for production tests - // 0x3F // reserved for production tests - }; - - // MFRC522 commands. Described in chapter 10 of the datasheet. - enum PcdCommand : uint8_t { - PCD_IDLE = 0x00, // no action, cancels current command execution - PCD_MEM = 0x01, // stores 25 uint8_ts into the internal buffer - PCD_GENERATE_RANDOM_ID = 0x02, // generates a 10-uint8_t random ID number - PCD_CALC_CRC = 0x03, // activates the CRC coprocessor or performs a self-test - PCD_TRANSMIT = 0x04, // transmits data from the FIFO buffer - PCD_NO_CMD_CHANGE = 0x07, // no command change, can be used to modify the CommandReg register bits without - // affecting the command, for example, the PowerDown bit - PCD_RECEIVE = 0x08, // activates the receiver circuits - PCD_TRANSCEIVE = - 0x0C, // transmits data from FIFO buffer to antenna and automatically activates the receiver after transmission - PCD_MF_AUTHENT = 0x0E, // performs the MIFARE standard authentication as a reader - PCD_SOFT_RESET = 0x0F // resets the MFRC522 - }; - - // Commands sent to the PICC. - enum PiccCommand : uint8_t { - // The commands used by the PCD to manage communication with several PICCs (ISO 14443-3, Type A, section 6.4) - PICC_CMD_REQA = 0x26, // REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for - // anticollision or selection. 7 bit frame. - PICC_CMD_WUPA = 0x52, // Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to go to READY(*) and - // prepare for anticollision or selection. 7 bit frame. - PICC_CMD_CT = 0x88, // Cascade Tag. Not really a command, but used during anti collision. - PICC_CMD_SEL_CL1 = 0x93, // Anti collision/Select, Cascade Level 1 - PICC_CMD_SEL_CL2 = 0x95, // Anti collision/Select, Cascade Level 2 - PICC_CMD_SEL_CL3 = 0x97, // Anti collision/Select, Cascade Level 3 - PICC_CMD_HLTA = 0x50, // HaLT command, Type A. Instructs an ACTIVE PICC to go to state HALT. - PICC_CMD_RATS = 0xE0, // Request command for Answer To Reset. - // The commands used for MIFARE Classic (from http://www.mouser.com/ds/2/302/MF1S503x-89574.pdf, Section 9) - // Use PCD_MFAuthent to authenticate access to a sector, then use these commands to read/write/modify the blocks on - // the sector. - // The read/write commands can also be used for MIFARE Ultralight. - PICC_CMD_MF_AUTH_KEY_A = 0x60, // Perform authentication with Key A - PICC_CMD_MF_AUTH_KEY_B = 0x61, // Perform authentication with Key B - PICC_CMD_MF_READ = - 0x30, // Reads one 16 uint8_t block from the authenticated sector of the PICC. Also used for MIFARE Ultralight. - PICC_CMD_MF_WRITE = 0xA0, // Writes one 16 uint8_t block to the authenticated sector of the PICC. Called - // "COMPATIBILITY WRITE" for MIFARE Ultralight. - PICC_CMD_MF_DECREMENT = - 0xC0, // Decrements the contents of a block and stores the result in the internal data register. - PICC_CMD_MF_INCREMENT = - 0xC1, // Increments the contents of a block and stores the result in the internal data register. - PICC_CMD_MF_RESTORE = 0xC2, // Reads the contents of a block into the internal data register. - PICC_CMD_MF_TRANSFER = 0xB0, // Writes the contents of the internal data register to a block. - // The commands used for MIFARE Ultralight (from http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf, Section 8.6) - // The PICC_CMD_MF_READ and PICC_CMD_MF_WRITE can also be used for MIFARE Ultralight. - PICC_CMD_UL_WRITE = 0xA2 // Writes one 4 uint8_t page to the PICC. - }; - - // Return codes from the functions in this class. Remember to update GetStatusCodeName() if you add more. - // last value set to 0xff, then compiler uses less ram, it seems some optimisations are triggered - enum StatusCode : uint8_t { - STATUS_OK, // Success - STATUS_ERROR, // Error in communication - STATUS_COLLISION, // Collission detected - STATUS_TIMEOUT, // Timeout in communication. - STATUS_NO_ROOM, // A buffer is not big enough. - STATUS_INTERNAL_ERROR, // Internal error in the code. Should not happen ;-) - STATUS_INVALID, // Invalid argument. - STATUS_CRC_WRONG, // The CRC_A does not match - STATUS_MIFARE_NACK = 0xff // A MIFARE PICC responded with NAK. - }; - - // A struct used for passing the UID of a PICC. - using Uid = struct { - uint8_t size; // Number of uint8_ts in the UID. 4, 7 or 10. - uint8_t uiduint8_t[10]; - uint8_t sak; // The SAK (Select acknowledge) uint8_t returned from the PICC after successful selection. - }; - - Uid uid_; - uint32_t update_wait_{0}; - - void pcd_reset_(); - void initialize_(); - void pcd_antenna_on_(); - uint8_t pcd_read_register_(PcdRegister reg ///< The register to read from. One of the PCD_Register enums. - ); + uint8_t pcd_read_register(PcdRegister reg ///< The register to read from. One of the PCD_Register enums. + ) override; /** * Reads a number of uint8_ts from the specified register in the MFRC522 chip. * The interface is described in the datasheet section 8.1.2. */ - void pcd_read_register_(PcdRegister reg, ///< The register to read from. One of the PCD_Register enums. - uint8_t count, ///< The number of uint8_ts to read - uint8_t *values, ///< uint8_t array to store the values in. - uint8_t rx_align ///< Only bit positions rxAlign..7 in values[0] are updated. - ); - void pcd_write_register_(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. - uint8_t value ///< The value to write. - ); + void pcd_read_register(PcdRegister reg, ///< The register to read from. One of the PCD_Register enums. + uint8_t count, ///< The number of uint8_ts to read + uint8_t *values, ///< uint8_t array to store the values in. + uint8_t rx_align ///< Only bit positions rxAlign..7 in values[0] are updated. + ) override; + void pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. + uint8_t value ///< The value to write. + ) override; /** * Writes a number of uint8_ts to the specified register in the MFRC522 chip. * The interface is described in the datasheet section 8.1.2. */ - void pcd_write_register_(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. - uint8_t count, ///< The number of uint8_ts to write to the register - uint8_t *values ///< The values to write. uint8_t array. - ); - - StatusCode picc_request_a_( - uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in - uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK. - ); - StatusCode picc_reqa_or_wupa_( - uint8_t command, ///< The command to send - PICC_CMD_REQA or PICC_CMD_WUPA - uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in - uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK. - ); - void pcd_set_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums. - uint8_t mask ///< The bits to set. - ); - void pcd_clear_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums. - uint8_t mask ///< The bits to clear. - ); - - StatusCode pcd_transceive_data_(uint8_t *send_data, uint8_t send_len, uint8_t *back_data, uint8_t *back_len, - uint8_t *valid_bits = nullptr, uint8_t rx_align = 0, bool check_crc = false); - StatusCode pcd_communicate_with_picc_(uint8_t command, uint8_t wait_i_rq, uint8_t *send_data, uint8_t send_len, - uint8_t *back_data = nullptr, uint8_t *back_len = nullptr, - uint8_t *valid_bits = nullptr, uint8_t rx_align = 0, bool check_crc = false); - StatusCode pcd_calculate_crc_( - uint8_t *data, ///< In: Pointer to the data to transfer to the FIFO for CRC calculation. - uint8_t length, ///< In: The number of uint8_ts to transfer. - uint8_t *result ///< Out: Pointer to result buffer. Result is written to result[0..1], low uint8_t first. - ); - RC522::StatusCode picc_is_new_card_present_(); - bool picc_read_card_serial_(); - StatusCode picc_select_( - Uid *uid, ///< Pointer to Uid struct. Normally output, but can also be used to supply a known UID. - uint8_t valid_bits = 0 ///< The number of known UID bits supplied in *uid. Normally 0. If set you must also - ///< supply uid->size. - ); - - /** Read a data frame from the RC522 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 r_c522_read_data_(); - - GPIOPin *reset_pin_{nullptr}; - uint8_t reset_count_{0}; - uint32_t reset_timeout_{0}; - bool initialize_pending_{false}; - std::vector binary_sensors_; - std::vector triggers_; - - enum RC522Error { - NONE = 0, - RESET_FAILED, - } error_code_{NONE}; + void pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. + uint8_t count, ///< The number of uint8_ts to write to the register + uint8_t *values ///< The values to write. uint8_t array. + ) override; }; -class RC522BinarySensor : public binary_sensor::BinarySensor { - public: - void set_uid(const std::vector &uid) { uid_ = uid; } - - bool process(const uint8_t *data, uint8_t len); - - void on_scan_end() { - if (!this->found_) { - this->publish_state(false); - } - this->found_ = false; - } - - protected: - std::vector uid_; - bool found_{false}; -}; - -class RC522Trigger : public Trigger { - public: - void process(const uint8_t *uid, uint8_t uid_length); -}; - -#ifndef MFRC522_SPICLOCK -#define MFRC522_SPICLOCK SPI_CLOCK_DIV4 // MFRC522 accept upto 10MHz -#endif - } // namespace rc522_spi } // namespace esphome diff --git a/tests/test1.yaml b/tests/test1.yaml index 58fc7d4bb2..ef98c10ad5 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -201,7 +201,7 @@ mcp23s08: - id: 'mcp23s08_hub' cs_pin: GPIO12 deviceaddress: 0 - + mcp23s17: - id: 'mcp23s17_hub' cs_pin: GPIO12 @@ -813,7 +813,7 @@ esp32_touch: binary_sensor: - platform: gpio - name: "MCP23S08 Pin #1" + name: 'MCP23S08 Pin #1' pin: mcp23s08: mcp23s08_hub # Use pin number 1 @@ -822,7 +822,7 @@ binary_sensor: mode: INPUT_PULLUP inverted: False - platform: gpio - name: "MCP23S17 Pin #1" + name: 'MCP23S17 Pin #1' pin: mcp23s17: mcp23s17_hub # Use pin number 1 @@ -1391,7 +1391,7 @@ climate: switch: - platform: gpio - name: "MCP23S08 Pin #0" + name: 'MCP23S08 Pin #0' pin: mcp23s08: mcp23s08_hub # Use pin number 0 @@ -1399,7 +1399,7 @@ switch: mode: OUTPUT inverted: False - platform: gpio - name: "MCP23S17 Pin #0" + name: 'MCP23S17 Pin #0' pin: mcp23s17: mcp23s17_hub # Use pin number 0 @@ -1823,6 +1823,12 @@ rc522_spi: - lambda: |- ESP_LOGD("main", "Found tag %s", x.c_str()); +rc522_i2c: + update_interval: 1s + on_tag: + - lambda: |- + ESP_LOGD("main", "Found tag %s", x.c_str()); + gps: time: @@ -1933,7 +1939,7 @@ text_sensor: value: '0' - canbus.send: can_id: 23 - data: [ 0x10, 0x20, 0x30 ] + data: [0x10, 0x20, 0x30] - platform: template name: Template Text Sensor id: template_text @@ -1967,15 +1973,15 @@ canbus: can_id: 4 bit_rate: 50kbps on_frame: - - can_id: 500 - then: - - lambda: |- - std::string b(x.begin(), x.end()); - ESP_LOGD("canid 500", "%s", &b[0] ); - - can_id: 23 - then: - - if: - condition: - lambda: 'return x[0] == 0x11;' - then: - light.toggle: living_room_lights + - can_id: 500 + then: + - lambda: |- + std::string b(x.begin(), x.end()); + ESP_LOGD("canid 500", "%s", &b[0] ); + - can_id: 23 + then: + - if: + condition: + lambda: 'return x[0] == 0x11;' + then: + light.toggle: living_room_lights