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
This commit is contained in:
Guillermo Ruffino 2021-01-12 10:13:53 -03:00 committed by GitHub
parent 400819175d
commit fbc1b3e316
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 1359 additions and 1092 deletions

View file

@ -55,6 +55,8 @@ esphome/components/pn532/* @OttoWinter @jesserockz
esphome/components/pn532_i2c/* @OttoWinter @jesserockz esphome/components/pn532_i2c/* @OttoWinter @jesserockz
esphome/components/pn532_spi/* @OttoWinter @jesserockz esphome/components/pn532_spi/* @OttoWinter @jesserockz
esphome/components/power_supply/* @esphome/core esphome/components/power_supply/* @esphome/core
esphome/components/rc522/* @glmnet
esphome/components/rc522_i2c/* @glmnet
esphome/components/rc522_spi/* @glmnet esphome/components/rc522_spi/* @glmnet
esphome/components/restart/* @esphome/core esphome/components/restart/* @esphome/core
esphome/components/rf_bridge/* @jesserockz esphome/components/rf_bridge/* @jesserockz

View file

@ -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)

View file

@ -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))

View file

@ -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

View file

@ -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<uint8_t> r_c522_read_data_();
GPIOPin *reset_pin_{nullptr};
uint8_t reset_count_{0};
uint32_t reset_timeout_{0};
bool initialize_pending_{false};
std::vector<RC522BinarySensor *> binary_sensors_;
std::vector<RC522Trigger *> triggers_;
enum RC522Error {
NONE = 0,
RESET_FAILED,
} error_code_{NONE};
};
class RC522BinarySensor : public binary_sensor::BinarySensor {
public:
void set_uid(const std::vector<uint8_t> &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<uint8_t> uid_;
bool found_{false};
};
class RC522Trigger : public Trigger<std::string> {
public:
void process(const uint8_t *uid, uint8_t uid_length);
};
} // namespace rc522
} // namespace esphome

View file

@ -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)

View file

@ -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<uint8_t> &data) {
// return this->write_bytes_raw(data.data(), data.size()); }
// bool RC522I2C::read_data(std::vector<uint8_t> &data, uint8_t len) {
// delay(5);
// std::vector<uint8_t> ready;
// ready.resize(1);
// uint32_t start_time = millis();
// while (true) {
// if (this->read_bytes_raw(ready.data(), 1)) {
// if (ready[0] == 0x01)
// break;
// }
// if (millis() - start_time > 100) {
// ESP_LOGV(TAG, "Timed out waiting for readiness from RC522!");
// return false;
// }
// }
// data.resize(len + 1);
// this->read_bytes_raw(data.data(), len + 1);
// return true;
// }
} // namespace rc522_i2c
} // namespace esphome

View file

@ -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

View file

@ -1,39 +1,21 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import automation, pins from esphome.components import spi, rc522
from esphome.components import spi from esphome.const import CONF_ID
from esphome.const import CONF_ID, CONF_ON_TAG, CONF_TRIGGER_ID, CONF_RESET_PIN, CONF_CS_PIN
CODEOWNERS = ['@glmnet'] CODEOWNERS = ['@glmnet']
DEPENDENCIES = ['spi'] DEPENDENCIES = ['spi']
AUTO_LOAD = ['binary_sensor'] AUTO_LOAD = ['rc522']
MULTI_CONF = True
rc522_spi_ns = cg.esphome_ns.namespace('rc522_spi') rc522_spi_ns = cg.esphome_ns.namespace('rc522_spi')
RC522 = rc522_spi_ns.class_('RC522', cg.PollingComponent, spi.SPIDevice) RC522Spi = rc522_spi_ns.class_('RC522Spi', rc522.RC522, spi.SPIDevice)
RC522Trigger = rc522_spi_ns.class_('RC522Trigger', automation.Trigger.template(cg.std_string))
CONFIG_SCHEMA = cv.Schema({ CONFIG_SCHEMA = cv.All(rc522.RC522_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(RC522), cv.GenerateID(): cv.declare_id(RC522Spi),
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, }).extend(spi.spi_device_schema(cs_pin_required=True)))
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())
def to_code(config): def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) 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) 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)

View file

@ -1,44 +1,9 @@
import esphome.codegen as cg import esphome.components.rc522.binary_sensor as rc522_binary_sensor
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
DEPENDENCIES = ['rc522_spi'] DEPENDENCIES = ['rc522']
CONF_RC522_ID = 'rc522_id' CONFIG_SCHEMA = rc522_binary_sensor.CONFIG_SCHEMA
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,
})
def to_code(config): def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) yield rc522_binary_sensor.to_code(config)
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))

View file

@ -9,218 +9,30 @@ namespace rc522_spi {
static const char *TAG = "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) { RC522::setup();
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() { void RC522Spi::dump_config() {
spi_setup(); RC522::dump_config();
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(" CS Pin: ", this->cs_); 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. * Reads a uint8_t from the specified register in the MFRC522 chip.
* The interface is described in the datasheet section 8.1.2. * 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; uint8_t value;
enable(); enable();
transfer_byte(0x80 | reg); transfer_byte(0x80 | reg);
value = read_byte(); value = read_byte();
disable(); disable();
ESP_LOGVV(TAG, "read_register_(%x) -> %x", reg, value); ESP_LOGV(TAG, "read_register_(%x) -> %x", reg, value);
return 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. * 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. * 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. 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 count, ///< The number of uint8_ts to read
uint8_t *values, ///< uint8_t array to store the values in. 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. uint8_t rx_align ///< Only bit positions rxAlign..7 in values[0] are updated.
) { ) {
std::string buf; std::string buf;
buf = "Rx"; buf = "Rx";
@ -278,8 +90,8 @@ void RC522::pcd_read_register_(PcdRegister reg, ///< The register to read from.
disable(); disable();
} }
void RC522::pcd_write_register_(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. 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. uint8_t value ///< The value to write.
) { ) {
enable(); enable();
// MSB == 0 is for writing. LSB is not used in address. Datasheet section 8.1.2.3. // 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. * 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. * 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. 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 count, ///< The number of uint8_ts to write to the register
uint8_t *values ///< The values to write. uint8_t array. uint8_t *values ///< The values to write. uint8_t array.
) { ) {
std::string buf; std::string buf;
buf = "Tx"; 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()); 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 rc522_spi
} // namespace esphome } // namespace esphome

View file

@ -10,292 +10,46 @@
#pragma once #pragma once
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/automation.h" #include "esphome/components/rc522/rc522.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/spi/spi.h" #include "esphome/components/spi/spi.h"
namespace esphome { namespace esphome {
namespace rc522_spi { namespace rc522_spi {
class RC522BinarySensor; class RC522Spi : public rc522::RC522,
class RC522Trigger; public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
spi::DATA_RATE_4MHZ> {
class RC522 : public PollingComponent,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
spi::DATA_RATE_4MHZ> {
public: public:
void setup() override; void setup() override;
void dump_config() 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: protected:
enum PcdRegister : uint8_t { uint8_t pcd_read_register(PcdRegister reg ///< The register to read from. One of the PCD_Register enums.
// Page 0: Command and status ) override;
// 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.
);
/** /**
* Reads a number of uint8_ts from the specified register in the MFRC522 chip. * 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. * 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. 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 count, ///< The number of uint8_ts to read
uint8_t *values, ///< uint8_t array to store the values in. 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. 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. void pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums.
uint8_t value ///< The value to write. uint8_t value ///< The value to write.
); ) override;
/** /**
* Writes a number of uint8_ts to the specified register in the MFRC522 chip. * 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. * 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. 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 count, ///< The number of uint8_ts to write to the register
uint8_t *values ///< The values to write. uint8_t array. uint8_t *values ///< The values to write. uint8_t array.
); ) override;
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<uint8_t> r_c522_read_data_();
GPIOPin *reset_pin_{nullptr};
uint8_t reset_count_{0};
uint32_t reset_timeout_{0};
bool initialize_pending_{false};
std::vector<RC522BinarySensor *> binary_sensors_;
std::vector<RC522Trigger *> triggers_;
enum RC522Error {
NONE = 0,
RESET_FAILED,
} error_code_{NONE};
}; };
class RC522BinarySensor : public binary_sensor::BinarySensor {
public:
void set_uid(const std::vector<uint8_t> &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<uint8_t> uid_;
bool found_{false};
};
class RC522Trigger : public Trigger<std::string> {
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 rc522_spi
} // namespace esphome } // namespace esphome

View file

@ -201,7 +201,7 @@ mcp23s08:
- id: 'mcp23s08_hub' - id: 'mcp23s08_hub'
cs_pin: GPIO12 cs_pin: GPIO12
deviceaddress: 0 deviceaddress: 0
mcp23s17: mcp23s17:
- id: 'mcp23s17_hub' - id: 'mcp23s17_hub'
cs_pin: GPIO12 cs_pin: GPIO12
@ -813,7 +813,7 @@ esp32_touch:
binary_sensor: binary_sensor:
- platform: gpio - platform: gpio
name: "MCP23S08 Pin #1" name: 'MCP23S08 Pin #1'
pin: pin:
mcp23s08: mcp23s08_hub mcp23s08: mcp23s08_hub
# Use pin number 1 # Use pin number 1
@ -822,7 +822,7 @@ binary_sensor:
mode: INPUT_PULLUP mode: INPUT_PULLUP
inverted: False inverted: False
- platform: gpio - platform: gpio
name: "MCP23S17 Pin #1" name: 'MCP23S17 Pin #1'
pin: pin:
mcp23s17: mcp23s17_hub mcp23s17: mcp23s17_hub
# Use pin number 1 # Use pin number 1
@ -1391,7 +1391,7 @@ climate:
switch: switch:
- platform: gpio - platform: gpio
name: "MCP23S08 Pin #0" name: 'MCP23S08 Pin #0'
pin: pin:
mcp23s08: mcp23s08_hub mcp23s08: mcp23s08_hub
# Use pin number 0 # Use pin number 0
@ -1399,7 +1399,7 @@ switch:
mode: OUTPUT mode: OUTPUT
inverted: False inverted: False
- platform: gpio - platform: gpio
name: "MCP23S17 Pin #0" name: 'MCP23S17 Pin #0'
pin: pin:
mcp23s17: mcp23s17_hub mcp23s17: mcp23s17_hub
# Use pin number 0 # Use pin number 0
@ -1823,6 +1823,12 @@ rc522_spi:
- lambda: |- - lambda: |-
ESP_LOGD("main", "Found tag %s", x.c_str()); 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: gps:
time: time:
@ -1933,7 +1939,7 @@ text_sensor:
value: '0' value: '0'
- canbus.send: - canbus.send:
can_id: 23 can_id: 23
data: [ 0x10, 0x20, 0x30 ] data: [0x10, 0x20, 0x30]
- platform: template - platform: template
name: Template Text Sensor name: Template Text Sensor
id: template_text id: template_text
@ -1967,15 +1973,15 @@ canbus:
can_id: 4 can_id: 4
bit_rate: 50kbps bit_rate: 50kbps
on_frame: on_frame:
- can_id: 500 - can_id: 500
then: then:
- lambda: |- - lambda: |-
std::string b(x.begin(), x.end()); std::string b(x.begin(), x.end());
ESP_LOGD("canid 500", "%s", &b[0] ); ESP_LOGD("canid 500", "%s", &b[0] );
- can_id: 23 - can_id: 23
then: then:
- if: - if:
condition: condition:
lambda: 'return x[0] == 0x11;' lambda: 'return x[0] == 0x11;'
then: then:
light.toggle: living_room_lights light.toggle: living_room_lights