From 9f36b25d4e2f4564052f0b7b0b0aaddcc7a6563d Mon Sep 17 00:00:00 2001 From: mmanza <40872469+mmanza@users.noreply.github.com> Date: Tue, 12 Jan 2021 09:51:38 -0300 Subject: [PATCH 1/8] Whirlpool ac (#1467) * Checksum calc change * first checksum change for MODEL_DG11J1_3A --- esphome/components/whirlpool/whirlpool.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/whirlpool/whirlpool.cpp b/esphome/components/whirlpool/whirlpool.cpp index d8db3b7353..eba08d5bbe 100644 --- a/esphome/components/whirlpool/whirlpool.cpp +++ b/esphome/components/whirlpool/whirlpool.cpp @@ -105,7 +105,7 @@ void WhirlpoolClimate::transmit_state() { } // Checksum - for (uint8_t i = 2; i < 12; i++) + for (uint8_t i = 2; i < 13; i++) remote_state[13] ^= remote_state[i]; for (uint8_t i = 14; i < 20; i++) remote_state[20] ^= remote_state[i]; @@ -184,7 +184,7 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) { uint8_t checksum13 = 0; uint8_t checksum20 = 0; // Calculate checksum and compare with signal value. - for (uint8_t i = 2; i < 12; i++) + for (uint8_t i = 2; i < 13; i++) checksum13 ^= remote_state[i]; for (uint8_t i = 14; i < 20; i++) checksum20 ^= remote_state[i]; From 5e799b528495b94a8523140acbc609a4d22bd2a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Jan 2021 10:12:25 -0300 Subject: [PATCH 2/8] Bump pytest-mock from 3.3.1 to 3.5.1 (#1458) Bumps [pytest-mock](https://github.com/pytest-dev/pytest-mock) from 3.3.1 to 3.5.1. - [Release notes](https://github.com/pytest-dev/pytest-mock/releases) - [Changelog](https://github.com/pytest-dev/pytest-mock/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-mock/compare/v3.3.1...v3.5.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 3733d025fa..864dfe1a72 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -7,6 +7,6 @@ pexpect==4.8.0 # Unit tests pytest==6.2.1 pytest-cov==2.10.1 -pytest-mock==3.3.1 +pytest-mock==3.5.1 asyncmock==0.4.2 hypothesis==5.21.0 From 717aab7c8bce648b238b0a0a4f187555207f7644 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Tue, 12 Jan 2021 10:13:53 -0300 Subject: [PATCH 3/8] Add rc522 i2c (#1432) * split to spi and i2c * fix binary_sensor * i2c comms ready * fix rc522_spi binary sensor compat * lint * lint * add test and codeowners * fix refactor --- CODEOWNERS | 2 + esphome/components/rc522/__init__.py | 38 + esphome/components/rc522/binary_sensor.py | 43 + esphome/components/rc522/rc522.cpp | 758 +++++++++++++++++ esphome/components/rc522/rc522.h | 284 +++++++ esphome/components/rc522_i2c/__init__.py | 22 + esphome/components/rc522_i2c/rc522_i2c.cpp | 99 +++ esphome/components/rc522_i2c/rc522_i2c.h | 42 + esphome/components/rc522_spi/__init__.py | 34 +- esphome/components/rc522_spi/binary_sensor.py | 43 +- esphome/components/rc522_spi/rc522_spi.cpp | 762 +----------------- esphome/components/rc522_spi/rc522_spi.h | 282 +------ tests/test1.yaml | 42 +- 13 files changed, 1359 insertions(+), 1092 deletions(-) create mode 100644 esphome/components/rc522/__init__.py create mode 100644 esphome/components/rc522/binary_sensor.py create mode 100644 esphome/components/rc522/rc522.cpp create mode 100644 esphome/components/rc522/rc522.h create mode 100644 esphome/components/rc522_i2c/__init__.py create mode 100644 esphome/components/rc522_i2c/rc522_i2c.cpp create mode 100644 esphome/components/rc522_i2c/rc522_i2c.h diff --git a/CODEOWNERS b/CODEOWNERS index 95729bae8d..fb287b1cc9 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -55,6 +55,8 @@ esphome/components/pn532/* @OttoWinter @jesserockz esphome/components/pn532_i2c/* @OttoWinter @jesserockz esphome/components/pn532_spi/* @OttoWinter @jesserockz esphome/components/power_supply/* @esphome/core +esphome/components/rc522/* @glmnet +esphome/components/rc522_i2c/* @glmnet esphome/components/rc522_spi/* @glmnet esphome/components/restart/* @esphome/core esphome/components/rf_bridge/* @jesserockz diff --git a/esphome/components/rc522/__init__.py b/esphome/components/rc522/__init__.py new file mode 100644 index 0000000000..7b4df37ce2 --- /dev/null +++ b/esphome/components/rc522/__init__.py @@ -0,0 +1,38 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation, pins +from esphome.components import i2c +from esphome.const import CONF_ON_TAG, CONF_TRIGGER_ID, CONF_RESET_PIN +from esphome.core import coroutine + +CODEOWNERS = ['@glmnet'] +AUTO_LOAD = ['binary_sensor'] +MULTI_CONF = True + +CONF_RC522_ID = 'rc522_id' + +rc522_ns = cg.esphome_ns.namespace('rc522') +RC522 = rc522_ns.class_('RC522', cg.PollingComponent, i2c.I2CDevice) +RC522Trigger = rc522_ns.class_('RC522Trigger', automation.Trigger.template(cg.std_string)) + +RC522_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.declare_id(RC522), + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_ON_TAG): automation.validate_automation({ + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RC522Trigger), + }), +}).extend(cv.polling_component_schema('1s')) + + +@coroutine +def setup_rc522(var, config): + yield cg.register_component(var, config) + + if CONF_RESET_PIN in config: + reset = yield cg.gpio_pin_expression(config[CONF_RESET_PIN]) + cg.add(var.set_reset_pin(reset)) + + for conf in config.get(CONF_ON_TAG, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) + cg.add(var.register_trigger(trigger)) + yield automation.build_automation(trigger, [(cg.std_string, 'x')], conf) diff --git a/esphome/components/rc522/binary_sensor.py b/esphome/components/rc522/binary_sensor.py new file mode 100644 index 0000000000..675db2f130 --- /dev/null +++ b/esphome/components/rc522/binary_sensor.py @@ -0,0 +1,43 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import CONF_UID, CONF_ID +from esphome.core import HexInt, coroutine +from . import rc522_ns, RC522, CONF_RC522_ID + +DEPENDENCIES = ['rc522'] + + +def validate_uid(value): + value = cv.string_strict(value) + for x in value.split('-'): + if len(x) != 2: + raise cv.Invalid("Each part (separated by '-') of the UID must be two characters " + "long.") + try: + x = int(x, 16) + except ValueError as err: + raise cv.Invalid("Valid characters for parts of a UID are 0123456789ABCDEF.") from err + if x < 0 or x > 255: + raise cv.Invalid("Valid values for UID parts (separated by '-') are 00 to FF") + return value + + +RC522BinarySensor = rc522_ns.class_('RC522BinarySensor', binary_sensor.BinarySensor) + +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(RC522BinarySensor), + cv.GenerateID(CONF_RC522_ID): cv.use_id(RC522), + cv.Required(CONF_UID): validate_uid, +}) + + +@coroutine +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield binary_sensor.register_binary_sensor(var, config) + + hub = yield cg.get_variable(config[CONF_RC522_ID]) + cg.add(hub.register_tag(var)) + addr = [HexInt(int(x, 16)) for x in config[CONF_UID].split('-')] + cg.add(var.set_uid(addr)) diff --git a/esphome/components/rc522/rc522.cpp b/esphome/components/rc522/rc522.cpp new file mode 100644 index 0000000000..ff8d18e3ea --- /dev/null +++ b/esphome/components/rc522/rc522.cpp @@ -0,0 +1,758 @@ +#include "rc522.h" +#include "esphome/core/log.h" + +// Based on: +// - https://github.com/miguelbalboa/rfid + +namespace esphome { +namespace rc522 { + +static const char *TAG = "rc522"; + +static const uint8_t RESET_COUNT = 5; + +void format_uid(char *buf, const uint8_t *uid, uint8_t uid_length) { + int offset = 0; + for (uint8_t i = 0; i < uid_length; i++) { + const char *format = "%02X"; + if (i + 1 < uid_length) + format = "%02X-"; + offset += sprintf(buf + offset, format, uid[i]); + } +} + +void RC522::setup() { + initialize_pending_ = true; + // Pull device out of power down / reset state. + + // First set the resetPowerDownPin as digital input, to check the MFRC522 power down mode. + if (reset_pin_ != nullptr) { + reset_pin_->pin_mode(INPUT); + + if (reset_pin_->digital_read() == LOW) { // The MFRC522 chip is in power down mode. + ESP_LOGV(TAG, "Power down mode detected. Hard resetting..."); + reset_pin_->pin_mode(OUTPUT); // Now set the resetPowerDownPin as digital output. + reset_pin_->digital_write(LOW); // Make sure we have a clean LOW state. + delayMicroseconds(2); // 8.8.1 Reset timing requirements says about 100ns. Let us be generous: 2μsl + reset_pin_->digital_write(HIGH); // Exit power down mode. This triggers a hard reset. + // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs. + // Let us be generous: 50ms. + reset_timeout_ = millis(); + return; + } + } + + // Setup a soft reset + reset_count_ = RESET_COUNT; + reset_timeout_ = millis(); +} + +void RC522::initialize_() { + // Per originall code, wait 50 ms + if (millis() - reset_timeout_ < 50) + return; + + // Reset baud rates + ESP_LOGV(TAG, "Initialize"); + + pcd_write_register(TX_MODE_REG, 0x00); + pcd_write_register(RX_MODE_REG, 0x00); + // Reset ModWidthReg + pcd_write_register(MOD_WIDTH_REG, 0x26); + + // When communicating with a PICC we need a timeout if something goes wrong. + // f_timer = 13.56 MHz / (2*TPreScaler+1) where TPreScaler = [TPrescaler_Hi:TPrescaler_Lo]. + // TPrescaler_Hi are the four low bits in TModeReg. TPrescaler_Lo is TPrescalerReg. + pcd_write_register(T_MODE_REG, 0x80); // TAuto=1; timer starts automatically at the end of the transmission in all + // communication modes at all speeds + + // TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9 = 169 => f_timer=40kHz, ie a timer period of 25μs. + pcd_write_register(T_PRESCALER_REG, 0xA9); + pcd_write_register(T_RELOAD_REG_H, 0x03); // Reload timer with 0x3E8 = 1000, ie 25ms before timeout. + pcd_write_register(T_RELOAD_REG_L, 0xE8); + + // Default 0x00. Force a 100 % ASK modulation independent of the ModGsPReg register setting + pcd_write_register(TX_ASK_REG, 0x40); + pcd_write_register(MODE_REG, 0x3D); // Default 0x3F. Set the preset value for the CRC coprocessor for the CalcCRC + // command to 0x6363 (ISO 14443-3 part 6.2.4) + pcd_antenna_on_(); // Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset) + + initialize_pending_ = false; +} + +void RC522::dump_config() { + ESP_LOGCONFIG(TAG, "RC522:"); + switch (this->error_code_) { + case NONE: + break; + case RESET_FAILED: + ESP_LOGE(TAG, "Reset command failed!"); + break; + } + + LOG_PIN(" RESET Pin: ", this->reset_pin_); + + LOG_UPDATE_INTERVAL(this); + + for (auto *child : this->binary_sensors_) { + LOG_BINARY_SENSOR(" ", "Tag", child); + } +} + +void RC522::loop() { + // First check reset is needed + if (reset_count_ > 0) { + pcd_reset_(); + return; + } + if (initialize_pending_) { + initialize_(); + return; + } + + if (millis() - update_wait_ < this->update_interval_) + return; + + auto status = picc_is_new_card_present_(); + + static StatusCode LAST_STATUS = StatusCode::STATUS_OK; + + if (status != LAST_STATUS) { + ESP_LOGD(TAG, "Status is now: %d", status); + LAST_STATUS = status; + } + + if (status == STATUS_ERROR) // No card + { + // ESP_LOGE(TAG, "Error"); + // mark_failed(); + return; + } + + if (status != STATUS_OK) // We can receive STATUS_TIMEOUT when no card, or unexpected status. + return; + + // Try process card + if (!picc_read_card_serial_()) { + ESP_LOGW(TAG, "Requesting tag read failed!"); + return; + }; + + if (uid_.size < 4) { + return; + ESP_LOGW(TAG, "Read serial size: %d", uid_.size); + } + + update_wait_ = millis(); + + bool report = true; + // 1. Go through all triggers + for (auto *trigger : this->triggers_) + trigger->process(uid_.uiduint8_t, uid_.size); + + // 2. Find a binary sensor + for (auto *tag : this->binary_sensors_) { + if (tag->process(uid_.uiduint8_t, uid_.size)) { + // 2.1 if found, do not dump + report = false; + } + } + + if (report) { + char buf[32]; + format_uid(buf, uid_.uiduint8_t, uid_.size); + ESP_LOGD(TAG, "Found new tag '%s'", buf); + } +} + +void RC522::update() { + for (auto *obj : this->binary_sensors_) + obj->on_scan_end(); +} + +/** + * Performs a soft reset on the MFRC522 chip and waits for it to be ready again. + */ +void RC522::pcd_reset_() { + // The datasheet does not mention how long the SoftRest command takes to complete. + // But the MFRC522 might have been in soft power-down mode (triggered by bit 4 of CommandReg) + // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs. Let + // us be generous: 50ms. + + if (millis() - reset_timeout_ < 50) + return; + + if (reset_count_ == RESET_COUNT) { + ESP_LOGV(TAG, "Soft reset..."); + // Issue the SoftReset command. + pcd_write_register(COMMAND_REG, PCD_SOFT_RESET); + } + + // Expect the PowerDown bit in CommandReg to be cleared (max 3x50ms) + if ((pcd_read_register(COMMAND_REG) & (1 << 4)) == 0) { + reset_count_ = 0; + ESP_LOGI(TAG, "Device online."); + // Wait for initialize + reset_timeout_ = millis(); + return; + } + + if (--reset_count_ == 0) { + ESP_LOGE(TAG, "Unable to reset RC522."); + mark_failed(); + } +} + +/** + * Turns the antenna on by enabling pins TX1 and TX2. + * After a reset these pins are disabled. + */ +void RC522::pcd_antenna_on_() { + uint8_t value = pcd_read_register(TX_CONTROL_REG); + if ((value & 0x03) != 0x03) { + pcd_write_register(TX_CONTROL_REG, value | 0x03); + } +} + +/** + * Transmits a REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or + * selection. 7 bit frame. Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - + * probably due do bad antenna design. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +RC522::StatusCode RC522::picc_request_a_( + uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in + uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK. +) { + return picc_reqa_or_wupa_(PICC_CMD_REQA, buffer_atqa, buffer_size); +} + +/** + * Transmits REQA or WUPA commands. + * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna + * design. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +RC522::StatusCode RC522::picc_reqa_or_wupa_( + uint8_t command, ///< The command to send - PICC_CMD_REQA or PICC_CMD_WUPA + uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in + uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK. +) { + uint8_t valid_bits; + RC522::StatusCode status; + + if (buffer_atqa == nullptr || *buffer_size < 2) { // The ATQA response is 2 uint8_ts long. + return STATUS_NO_ROOM; + } + pcd_clear_register_bit_mask_(COLL_REG, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared. + valid_bits = 7; // For REQA and WUPA we need the short frame format - transmit only 7 bits of the last (and only) + // uint8_t. TxLastBits = BitFramingReg[2..0] + status = pcd_transceive_data_(&command, 1, buffer_atqa, buffer_size, &valid_bits); + if (status != STATUS_OK) + return status; + if (*buffer_size != 2 || valid_bits != 0) { // ATQA must be exactly 16 bits. + ESP_LOGVV(TAG, "picc_reqa_or_wupa_() -> STATUS_ERROR"); + return STATUS_ERROR; + } + + return STATUS_OK; +} + +/** + * Sets the bits given in mask in register reg. + */ +void RC522::pcd_set_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums. + uint8_t mask ///< The bits to set. +) { + uint8_t tmp = pcd_read_register(reg); + pcd_write_register(reg, tmp | mask); // set bit mask +} + +/** + * Clears the bits given in mask from register reg. + */ +void RC522::pcd_clear_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums. + uint8_t mask ///< The bits to clear. +) { + uint8_t tmp = pcd_read_register(reg); + pcd_write_register(reg, tmp & (~mask)); // clear bit mask +} + +/** + * Executes the Transceive command. + * CRC validation can only be done if backData and backLen are specified. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +RC522::StatusCode RC522::pcd_transceive_data_( + uint8_t *send_data, ///< Pointer to the data to transfer to the FIFO. + uint8_t send_len, ///< Number of uint8_ts to transfer to the FIFO. + uint8_t *back_data, ///< nullptr or pointer to buffer if data should be read back after executing the command. + uint8_t *back_len, ///< In: Max number of uint8_ts to write to *backData. Out: The number of uint8_ts returned. + uint8_t + *valid_bits, ///< In/Out: The number of valid bits in the last uint8_t. 0 for 8 valid bits. Default nullptr. + uint8_t rx_align, ///< In: Defines the bit position in backData[0] for the first bit received. Default 0. + bool check_crc ///< In: True => The last two uint8_ts of the response is assumed to be a CRC_A that must be + ///< validated. +) { + uint8_t wait_i_rq = 0x30; // RxIRq and IdleIRq + auto ret = pcd_communicate_with_picc_(PCD_TRANSCEIVE, wait_i_rq, send_data, send_len, back_data, back_len, valid_bits, + rx_align, check_crc); + + if (ret == STATUS_OK && *back_len == 5) + ESP_LOGVV(TAG, "pcd_transceive_data_(..., %d, ) -> %d [%x, %x, %x, %x, %x]", send_len, ret, back_data[0], + back_data[1], back_data[2], back_data[3], back_data[4]); + else + ESP_LOGVV(TAG, "pcd_transceive_data_(..., %d, ... ) -> %d", send_len, ret); + return ret; +} + +/** + * Transfers data to the MFRC522 FIFO, executes a command, waits for completion and transfers data back from the FIFO. + * CRC validation can only be done if backData and backLen are specified. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +RC522::StatusCode RC522::pcd_communicate_with_picc_( + uint8_t command, ///< The command to execute. One of the PCD_Command enums. + uint8_t wait_i_rq, ///< The bits in the ComIrqReg register that signals successful completion of the command. + uint8_t *send_data, ///< Pointer to the data to transfer to the FIFO. + uint8_t send_len, ///< Number of uint8_ts to transfer to the FIFO. + uint8_t *back_data, ///< nullptr or pointer to buffer if data should be read back after executing the command. + uint8_t *back_len, ///< In: Max number of uint8_ts to write to *backData. Out: The number of uint8_ts returned. + uint8_t *valid_bits, ///< In/Out: The number of valid bits in the last uint8_t. 0 for 8 valid bits. + uint8_t rx_align, ///< In: Defines the bit position in backData[0] for the first bit received. Default 0. + bool check_crc ///< In: True => The last two uint8_ts of the response is assumed to be a CRC_A that must be + ///< validated. +) { + ESP_LOGVV(TAG, "pcd_communicate_with_picc_(%d, %d,... %d)", command, wait_i_rq, check_crc); + + // Prepare values for BitFramingReg + uint8_t tx_last_bits = valid_bits ? *valid_bits : 0; + uint8_t bit_framing = + (rx_align << 4) + tx_last_bits; // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] + + pcd_write_register(COMMAND_REG, PCD_IDLE); // Stop any active command. + pcd_write_register(COM_IRQ_REG, 0x7F); // Clear all seven interrupt request bits + pcd_write_register(FIFO_LEVEL_REG, 0x80); // FlushBuffer = 1, FIFO initialization + pcd_write_register(FIFO_DATA_REG, send_len, send_data); // Write sendData to the FIFO + pcd_write_register(BIT_FRAMING_REG, bit_framing); // Bit adjustments + pcd_write_register(COMMAND_REG, command); // Execute the command + if (command == PCD_TRANSCEIVE) { + pcd_set_register_bit_mask_(BIT_FRAMING_REG, 0x80); // StartSend=1, transmission of data starts + } + + // Wait for the command to complete. + // In PCD_Init() we set the TAuto flag in TModeReg. This means the timer automatically starts when the PCD stops + // transmitting. Each iteration of the do-while-loop takes 17.86μs. + // TODO check/modify for other architectures than Arduino Uno 16bit + uint16_t i; + for (i = 4; i > 0; i--) { + uint8_t n = pcd_read_register( + COM_IRQ_REG); // ComIrqReg[7..0] bits are: Set1 TxIRq RxIRq IdleIRq HiAlertIRq LoAlertIRq ErrIRq TimerIRq + if (n & wait_i_rq) { // One of the interrupts that signal success has been set. + break; + } + if (n & 0x01) { // Timer interrupt - nothing received in 25ms + return STATUS_TIMEOUT; + } + } + // 35.7ms and nothing happend. Communication with the MFRC522 might be down. + if (i == 0) { + return STATUS_TIMEOUT; + } + + // Stop now if any errors except collisions were detected. + uint8_t error_reg_value = pcd_read_register( + ERROR_REG); // ErrorReg[7..0] bits are: WrErr TempErr reserved BufferOvfl CollErr CRCErr ParityErr ProtocolErr + if (error_reg_value & 0x13) { // BufferOvfl ParityErr ProtocolErr + return STATUS_ERROR; + } + + uint8_t valid_bits_local = 0; + + // If the caller wants data back, get it from the MFRC522. + if (back_data && back_len) { + uint8_t n = pcd_read_register(FIFO_LEVEL_REG); // Number of uint8_ts in the FIFO + if (n > *back_len) { + return STATUS_NO_ROOM; + } + *back_len = n; // Number of uint8_ts returned + pcd_read_register(FIFO_DATA_REG, n, back_data, rx_align); // Get received data from FIFO + valid_bits_local = + pcd_read_register(CONTROL_REG) & 0x07; // RxLastBits[2:0] indicates the number of valid bits in the last + // received uint8_t. If this value is 000b, the whole uint8_t is valid. + if (valid_bits) { + *valid_bits = valid_bits_local; + } + } + + // Tell about collisions + if (error_reg_value & 0x08) { // CollErr + return STATUS_COLLISION; + } + + // Perform CRC_A validation if requested. + if (back_data && back_len && check_crc) { + // In this case a MIFARE Classic NAK is not OK. + if (*back_len == 1 && valid_bits_local == 4) { + return STATUS_MIFARE_NACK; + } + // We need at least the CRC_A value and all 8 bits of the last uint8_t must be received. + if (*back_len < 2 || valid_bits_local != 0) { + return STATUS_CRC_WRONG; + } + // Verify CRC_A - do our own calculation and store the control in controlBuffer. + uint8_t control_buffer[2]; + RC522::StatusCode status = pcd_calculate_crc_(&back_data[0], *back_len - 2, &control_buffer[0]); + if (status != STATUS_OK) { + return status; + } + if ((back_data[*back_len - 2] != control_buffer[0]) || (back_data[*back_len - 1] != control_buffer[1])) { + return STATUS_CRC_WRONG; + } + } + + return STATUS_OK; +} + +/** + * Use the CRC coprocessor in the MFRC522 to calculate a CRC_A. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ + +RC522::StatusCode RC522::pcd_calculate_crc_( + uint8_t *data, ///< In: Pointer to the data to transfer to the FIFO for CRC calculation. + uint8_t length, ///< In: The number of uint8_ts to transfer. + uint8_t *result ///< Out: Pointer to result buffer. Result is written to result[0..1], low uint8_t first. +) { + ESP_LOGVV(TAG, "pcd_calculate_crc_(..., %d, ...)", length); + pcd_write_register(COMMAND_REG, PCD_IDLE); // Stop any active command. + pcd_write_register(DIV_IRQ_REG, 0x04); // Clear the CRCIRq interrupt request bit + pcd_write_register(FIFO_LEVEL_REG, 0x80); // FlushBuffer = 1, FIFO initialization + pcd_write_register(FIFO_DATA_REG, length, data); // Write data to the FIFO + pcd_write_register(COMMAND_REG, PCD_CALC_CRC); // Start the calculation + + // Wait for the CRC calculation to complete. Each iteration of the while-loop takes 17.73μs. + // TODO check/modify for other architectures than Arduino Uno 16bit + + // Wait for the CRC calculation to complete. Each iteration of the while-loop takes 17.73us. + for (uint16_t i = 5000; i > 0; i--) { + // DivIrqReg[7..0] bits are: Set2 reserved reserved MfinActIRq reserved CRCIRq reserved reserved + uint8_t n = pcd_read_register(DIV_IRQ_REG); + if (n & 0x04) { // CRCIRq bit set - calculation done + pcd_write_register(COMMAND_REG, PCD_IDLE); // Stop calculating CRC for new content in the FIFO. + // Transfer the result from the registers to the result buffer + result[0] = pcd_read_register(CRC_RESULT_REG_L); + result[1] = pcd_read_register(CRC_RESULT_REG_H); + + ESP_LOGVV(TAG, "pcd_calculate_crc_() STATUS_OK"); + return STATUS_OK; + } + } + ESP_LOGVV(TAG, "pcd_calculate_crc_() TIMEOUT"); + // 89ms passed and nothing happend. Communication with the MFRC522 might be down. + return STATUS_TIMEOUT; +} +/** + * Returns STATUS_OK if a PICC responds to PICC_CMD_REQA. + * Only "new" cards in state IDLE are invited. Sleeping cards in state HALT are ignored. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ + +RC522::StatusCode RC522::picc_is_new_card_present_() { + uint8_t buffer_atqa[2]; + uint8_t buffer_size = sizeof(buffer_atqa); + + // Reset baud rates + pcd_write_register(TX_MODE_REG, 0x00); + pcd_write_register(RX_MODE_REG, 0x00); + // Reset ModWidthReg + pcd_write_register(MOD_WIDTH_REG, 0x26); + + auto result = picc_request_a_(buffer_atqa, &buffer_size); + + ESP_LOGV(TAG, "picc_is_new_card_present_() -> %d", result); + return result; +} + +/** + * Simple wrapper around PICC_Select. + * Returns true if a UID could be read. + * Remember to call PICC_IsNewCardPresent(), PICC_RequestA() or PICC_WakeupA() first. + * The read UID is available in the class variable uid. + * + * @return bool + */ +bool RC522::picc_read_card_serial_() { + RC522::StatusCode result = picc_select_(&this->uid_); + ESP_LOGVV(TAG, "picc_select_(...) -> %d", result); + return (result == STATUS_OK); +} + +/** + * Transmits SELECT/ANTICOLLISION commands to select a single PICC. + * Before calling this function the PICCs must be placed in the READY(*) state by calling PICC_RequestA() or + * PICC_WakeupA(). On success: + * - The chosen PICC is in state ACTIVE(*) and all other PICCs have returned to state IDLE/HALT. (Figure 7 of the + * ISO/IEC 14443-3 draft.) + * - The UID size and value of the chosen PICC is returned in *uid along with the SAK. + * + * A PICC UID consists of 4, 7 or 10 uint8_ts. + * Only 4 uint8_ts can be specified in a SELECT command, so for the longer UIDs two or three iterations are used: + * UID size Number of UID uint8_ts Cascade levels Example of PICC + * ======== =================== ============== =============== + * single 4 1 MIFARE Classic + * double 7 2 MIFARE Ultralight + * triple 10 3 Not currently in use? + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +RC522::StatusCode RC522::picc_select_( + Uid *uid, ///< Pointer to Uid struct. Normally output, but can also be used to supply a known UID. + uint8_t valid_bits ///< The number of known UID bits supplied in *uid. Normally 0. If set you must also supply + ///< uid->size. +) { + bool uid_complete; + bool select_done; + bool use_cascade_tag; + uint8_t cascade_level = 1; + RC522::StatusCode result; + uint8_t count; + uint8_t check_bit; + uint8_t index; + uint8_t uid_index; // The first index in uid->uiduint8_t[] that is used in the current Cascade Level. + int8_t current_level_known_bits; // The number of known UID bits in the current Cascade Level. + uint8_t buffer[9]; // The SELECT/ANTICOLLISION commands uses a 7 uint8_t standard frame + 2 uint8_ts CRC_A + uint8_t buffer_used; // The number of uint8_ts used in the buffer, ie the number of uint8_ts to transfer to the FIFO. + uint8_t rx_align; // Used in BitFramingReg. Defines the bit position for the first bit received. + uint8_t tx_last_bits; // Used in BitFramingReg. The number of valid bits in the last transmitted uint8_t. + uint8_t *response_buffer; + uint8_t response_length; + + // Description of buffer structure: + // uint8_t 0: SEL Indicates the Cascade Level: PICC_CMD_SEL_CL1, PICC_CMD_SEL_CL2 or PICC_CMD_SEL_CL3 + // uint8_t 1: NVB Number of Valid Bits (in complete command, not just the UID): High nibble: complete + // uint8_ts, + // Low nibble: Extra bits. uint8_t 2: UID-data or CT See explanation below. CT means Cascade Tag. uint8_t + // 3: UID-data uint8_t 4: UID-data uint8_t 5: UID-data uint8_t 6: BCC Block Check Character - XOR of + // uint8_ts 2-5 uint8_t 7: CRC_A uint8_t 8: CRC_A The BCC and CRC_A are only transmitted if we know all the UID bits + // of the current Cascade Level. + // + // Description of uint8_ts 2-5: (Section 6.5.4 of the ISO/IEC 14443-3 draft: UID contents and cascade levels) + // UID size Cascade level uint8_t2 uint8_t3 uint8_t4 uint8_t5 + // ======== ============= ===== ===== ===== ===== + // 4 uint8_ts 1 uid0 uid1 uid2 uid3 + // 7 uint8_ts 1 CT uid0 uid1 uid2 + // 2 uid3 uid4 uid5 uid6 + // 10 uint8_ts 1 CT uid0 uid1 uid2 + // 2 CT uid3 uid4 uid5 + // 3 uid6 uid7 uid8 uid9 + + // Sanity checks + if (valid_bits > 80) { + return STATUS_INVALID; + } + + ESP_LOGVV(TAG, "picc_select_(&, %d)", valid_bits); + + // Prepare MFRC522 + pcd_clear_register_bit_mask_(COLL_REG, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared. + + // Repeat Cascade Level loop until we have a complete UID. + uid_complete = false; + while (!uid_complete) { + // Set the Cascade Level in the SEL uint8_t, find out if we need to use the Cascade Tag in uint8_t 2. + switch (cascade_level) { + case 1: + buffer[0] = PICC_CMD_SEL_CL1; + uid_index = 0; + use_cascade_tag = valid_bits && uid->size > 4; // When we know that the UID has more than 4 uint8_ts + break; + + case 2: + buffer[0] = PICC_CMD_SEL_CL2; + uid_index = 3; + use_cascade_tag = valid_bits && uid->size > 7; // When we know that the UID has more than 7 uint8_ts + break; + + case 3: + buffer[0] = PICC_CMD_SEL_CL3; + uid_index = 6; + use_cascade_tag = false; // Never used in CL3. + break; + + default: + return STATUS_INTERNAL_ERROR; + break; + } + + // How many UID bits are known in this Cascade Level? + current_level_known_bits = valid_bits - (8 * uid_index); + if (current_level_known_bits < 0) { + current_level_known_bits = 0; + } + // Copy the known bits from uid->uiduint8_t[] to buffer[] + index = 2; // destination index in buffer[] + if (use_cascade_tag) { + buffer[index++] = PICC_CMD_CT; + } + uint8_t uint8_ts_to_copy = current_level_known_bits / 8 + + (current_level_known_bits % 8 + ? 1 + : 0); // The number of uint8_ts needed to represent the known bits for this level. + if (uint8_ts_to_copy) { + uint8_t maxuint8_ts = + use_cascade_tag ? 3 : 4; // Max 4 uint8_ts in each Cascade Level. Only 3 left if we use the Cascade Tag + if (uint8_ts_to_copy > maxuint8_ts) { + uint8_ts_to_copy = maxuint8_ts; + } + for (count = 0; count < uint8_ts_to_copy; count++) { + buffer[index++] = uid->uiduint8_t[uid_index + count]; + } + } + // Now that the data has been copied we need to include the 8 bits in CT in currentLevelKnownBits + if (use_cascade_tag) { + current_level_known_bits += 8; + } + + // Repeat anti collision loop until we can transmit all UID bits + BCC and receive a SAK - max 32 iterations. + select_done = false; + while (!select_done) { + // Find out how many bits and uint8_ts to send and receive. + if (current_level_known_bits >= 32) { // All UID bits in this Cascade Level are known. This is a SELECT. + + if (response_length < 4) { + ESP_LOGW(TAG, "Not enough data received."); + return STATUS_INVALID; + } + + // Serial.print(F("SELECT: currentLevelKnownBits=")); Serial.println(currentLevelKnownBits, DEC); + buffer[1] = 0x70; // NVB - Number of Valid Bits: Seven whole uint8_ts + // Calculate BCC - Block Check Character + buffer[6] = buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5]; + // Calculate CRC_A + result = pcd_calculate_crc_(buffer, 7, &buffer[7]); + if (result != STATUS_OK) { + return result; + } + tx_last_bits = 0; // 0 => All 8 bits are valid. + buffer_used = 9; + // Store response in the last 3 uint8_ts of buffer (BCC and CRC_A - not needed after tx) + response_buffer = &buffer[6]; + response_length = 3; + } else { // This is an ANTICOLLISION. + // Serial.print(F("ANTICOLLISION: currentLevelKnownBits=")); Serial.println(currentLevelKnownBits, DEC); + tx_last_bits = current_level_known_bits % 8; + count = current_level_known_bits / 8; // Number of whole uint8_ts in the UID part. + index = 2 + count; // Number of whole uint8_ts: SEL + NVB + UIDs + buffer[1] = (index << 4) + tx_last_bits; // NVB - Number of Valid Bits + buffer_used = index + (tx_last_bits ? 1 : 0); + // Store response in the unused part of buffer + response_buffer = &buffer[index]; + response_length = sizeof(buffer) - index; + } + + // Set bit adjustments + rx_align = tx_last_bits; // Having a separate variable is overkill. But it makes the next line easier to read. + pcd_write_register( + BIT_FRAMING_REG, + (rx_align << 4) + tx_last_bits); // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] + + // Transmit the buffer and receive the response. + result = pcd_transceive_data_(buffer, buffer_used, response_buffer, &response_length, &tx_last_bits, rx_align); + if (result == STATUS_COLLISION) { // More than one PICC in the field => collision. + uint8_t value_of_coll_reg = pcd_read_register( + COLL_REG); // CollReg[7..0] bits are: ValuesAfterColl reserved CollPosNotValid CollPos[4:0] + if (value_of_coll_reg & 0x20) { // CollPosNotValid + return STATUS_COLLISION; // Without a valid collision position we cannot continue + } + uint8_t collision_pos = value_of_coll_reg & 0x1F; // Values 0-31, 0 means bit 32. + if (collision_pos == 0) { + collision_pos = 32; + } + if (collision_pos <= current_level_known_bits) { // No progress - should not happen + return STATUS_INTERNAL_ERROR; + } + // Choose the PICC with the bit set. + current_level_known_bits = collision_pos; + count = current_level_known_bits % 8; // The bit to modify + check_bit = (current_level_known_bits - 1) % 8; + index = 1 + (current_level_known_bits / 8) + (count ? 1 : 0); // First uint8_t is index 0. + if (response_length > 2) // Note: Otherwise buffer[index] might be not initialized + buffer[index] |= (1 << check_bit); + } else if (result != STATUS_OK) { + return result; + } else { // STATUS_OK + if (current_level_known_bits >= 32) { // This was a SELECT. + select_done = true; // No more anticollision + // We continue below outside the while. + } else { // This was an ANTICOLLISION. + // We now have all 32 bits of the UID in this Cascade Level + current_level_known_bits = 32; + // Run loop again to do the SELECT. + } + } + } // End of while (!selectDone) + + // We do not check the CBB - it was constructed by us above. + + // Copy the found UID uint8_ts from buffer[] to uid->uiduint8_t[] + index = (buffer[2] == PICC_CMD_CT) ? 3 : 2; // source index in buffer[] + uint8_ts_to_copy = (buffer[2] == PICC_CMD_CT) ? 3 : 4; + for (count = 0; count < uint8_ts_to_copy; count++) { + uid->uiduint8_t[uid_index + count] = buffer[index++]; + } + + // Check response SAK (Select Acknowledge) + if (response_length != 3 || tx_last_bits != 0) { // SAK must be exactly 24 bits (1 uint8_t + CRC_A). + return STATUS_ERROR; + } + // Verify CRC_A - do our own calculation and store the control in buffer[2..3] - those uint8_ts are not needed + // anymore. + result = pcd_calculate_crc_(response_buffer, 1, &buffer[2]); + if (result != STATUS_OK) { + return result; + } + if ((buffer[2] != response_buffer[1]) || (buffer[3] != response_buffer[2])) { + return STATUS_CRC_WRONG; + } + if (response_buffer[0] & 0x04) { // Cascade bit set - UID not complete yes + cascade_level++; + } else { + uid_complete = true; + uid->sak = response_buffer[0]; + } + } // End of while (!uidComplete) + + // Set correct uid->size + uid->size = 3 * cascade_level + 1; + + return STATUS_OK; +} + +bool RC522BinarySensor::process(const uint8_t *data, uint8_t len) { + if (len != this->uid_.size()) + return false; + + for (uint8_t i = 0; i < len; i++) { + if (data[i] != this->uid_[i]) + return false; + } + + this->publish_state(true); + this->found_ = true; + return true; +} +void RC522Trigger::process(const uint8_t *uid, uint8_t uid_length) { + char buf[32]; + format_uid(buf, uid, uid_length); + this->trigger(std::string(buf)); +} + +} // namespace rc522 +} // namespace esphome diff --git a/esphome/components/rc522/rc522.h b/esphome/components/rc522/rc522.h new file mode 100644 index 0000000000..cabcf8db0b --- /dev/null +++ b/esphome/components/rc522/rc522.h @@ -0,0 +1,284 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/components/binary_sensor/binary_sensor.h" + +namespace esphome { +namespace rc522 { + +class RC522BinarySensor; +class RC522Trigger; +class RC522 : public PollingComponent { + public: + void setup() override; + + void dump_config() override; + + void update() override; + float get_setup_priority() const override { return setup_priority::DATA; }; + + void loop() override; + + void register_tag(RC522BinarySensor *tag) { this->binary_sensors_.push_back(tag); } + void register_trigger(RC522Trigger *trig) { this->triggers_.push_back(trig); } + + void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } + + protected: + enum PcdRegister : uint8_t { + // Page 0: Command and status + // 0x00 // reserved for future use + COMMAND_REG = 0x01 << 1, // starts and stops command execution + COM_I_EN_REG = 0x02 << 1, // enable and disable interrupt request control bits + DIV_I_EN_REG = 0x03 << 1, // enable and disable interrupt request control bits + COM_IRQ_REG = 0x04 << 1, // interrupt request bits + DIV_IRQ_REG = 0x05 << 1, // interrupt request bits + ERROR_REG = 0x06 << 1, // error bits showing the error status of the last command executed + STATUS1_REG = 0x07 << 1, // communication status bits + STATUS2_REG = 0x08 << 1, // receiver and transmitter status bits + FIFO_DATA_REG = 0x09 << 1, // input and output of 64 uint8_t FIFO buffer + FIFO_LEVEL_REG = 0x0A << 1, // number of uint8_ts stored in the FIFO buffer + WATER_LEVEL_REG = 0x0B << 1, // level for FIFO underflow and overflow warning + CONTROL_REG = 0x0C << 1, // miscellaneous control registers + BIT_FRAMING_REG = 0x0D << 1, // adjustments for bit-oriented frames + COLL_REG = 0x0E << 1, // bit position of the first bit-collision detected on the RF interface + // 0x0F // reserved for future use + + // Page 1: Command + // 0x10 // reserved for future use + MODE_REG = 0x11 << 1, // defines general modes for transmitting and receiving + TX_MODE_REG = 0x12 << 1, // defines transmission data rate and framing + RX_MODE_REG = 0x13 << 1, // defines reception data rate and framing + TX_CONTROL_REG = 0x14 << 1, // controls the logical behavior of the antenna driver pins TX1 and TX2 + TX_ASK_REG = 0x15 << 1, // controls the setting of the transmission modulation + TX_SEL_REG = 0x16 << 1, // selects the internal sources for the antenna driver + RX_SEL_REG = 0x17 << 1, // selects internal receiver settings + RX_THRESHOLD_REG = 0x18 << 1, // selects thresholds for the bit decoder + DEMOD_REG = 0x19 << 1, // defines demodulator settings + // 0x1A // reserved for future use + // 0x1B // reserved for future use + MF_TX_REG = 0x1C << 1, // controls some MIFARE communication transmit parameters + MF_RX_REG = 0x1D << 1, // controls some MIFARE communication receive parameters + // 0x1E // reserved for future use + SERIAL_SPEED_REG = 0x1F << 1, // selects the speed of the serial UART interface + + // Page 2: Configuration + // 0x20 // reserved for future use + CRC_RESULT_REG_H = 0x21 << 1, // shows the MSB and LSB values of the CRC calculation + CRC_RESULT_REG_L = 0x22 << 1, + // 0x23 // reserved for future use + MOD_WIDTH_REG = 0x24 << 1, // controls the ModWidth setting? + // 0x25 // reserved for future use + RF_CFG_REG = 0x26 << 1, // configures the receiver gain + GS_N_REG = 0x27 << 1, // selects the conductance of the antenna driver pins TX1 and TX2 for modulation + CW_GS_P_REG = 0x28 << 1, // defines the conductance of the p-driver output during periods of no modulation + MOD_GS_P_REG = 0x29 << 1, // defines the conductance of the p-driver output during periods of modulation + T_MODE_REG = 0x2A << 1, // defines settings for the internal timer + T_PRESCALER_REG = 0x2B << 1, // the lower 8 bits of the TPrescaler value. The 4 high bits are in TModeReg. + T_RELOAD_REG_H = 0x2C << 1, // defines the 16-bit timer reload value + T_RELOAD_REG_L = 0x2D << 1, + T_COUNTER_VALUE_REG_H = 0x2E << 1, // shows the 16-bit timer value + T_COUNTER_VALUE_REG_L = 0x2F << 1, + + // Page 3: Test Registers + // 0x30 // reserved for future use + TEST_SEL1_REG = 0x31 << 1, // general test signal configuration + TEST_SEL2_REG = 0x32 << 1, // general test signal configuration + TEST_PIN_EN_REG = 0x33 << 1, // enables pin output driver on pins D1 to D7 + TEST_PIN_VALUE_REG = 0x34 << 1, // defines the values for D1 to D7 when it is used as an I/O bus + TEST_BUS_REG = 0x35 << 1, // shows the status of the internal test bus + AUTO_TEST_REG = 0x36 << 1, // controls the digital self-test + VERSION_REG = 0x37 << 1, // shows the software version + ANALOG_TEST_REG = 0x38 << 1, // controls the pins AUX1 and AUX2 + TEST_DA_C1_REG = 0x39 << 1, // defines the test value for TestDAC1 + TEST_DA_C2_REG = 0x3A << 1, // defines the test value for TestDAC2 + TEST_ADC_REG = 0x3B << 1 // shows the value of ADC I and Q channels + // 0x3C // reserved for production tests + // 0x3D // reserved for production tests + // 0x3E // reserved for production tests + // 0x3F // reserved for production tests + }; + + // MFRC522 commands. Described in chapter 10 of the datasheet. + enum PcdCommand : uint8_t { + PCD_IDLE = 0x00, // no action, cancels current command execution + PCD_MEM = 0x01, // stores 25 uint8_ts into the internal buffer + PCD_GENERATE_RANDOM_ID = 0x02, // generates a 10-uint8_t random ID number + PCD_CALC_CRC = 0x03, // activates the CRC coprocessor or performs a self-test + PCD_TRANSMIT = 0x04, // transmits data from the FIFO buffer + PCD_NO_CMD_CHANGE = 0x07, // no command change, can be used to modify the CommandReg register bits without + // affecting the command, for example, the PowerDown bit + PCD_RECEIVE = 0x08, // activates the receiver circuits + PCD_TRANSCEIVE = + 0x0C, // transmits data from FIFO buffer to antenna and automatically activates the receiver after transmission + PCD_MF_AUTHENT = 0x0E, // performs the MIFARE standard authentication as a reader + PCD_SOFT_RESET = 0x0F // resets the MFRC522 + }; + + // Commands sent to the PICC. + enum PiccCommand : uint8_t { + // The commands used by the PCD to manage communication with several PICCs (ISO 14443-3, Type A, section 6.4) + PICC_CMD_REQA = 0x26, // REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for + // anticollision or selection. 7 bit frame. + PICC_CMD_WUPA = 0x52, // Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to go to READY(*) and + // prepare for anticollision or selection. 7 bit frame. + PICC_CMD_CT = 0x88, // Cascade Tag. Not really a command, but used during anti collision. + PICC_CMD_SEL_CL1 = 0x93, // Anti collision/Select, Cascade Level 1 + PICC_CMD_SEL_CL2 = 0x95, // Anti collision/Select, Cascade Level 2 + PICC_CMD_SEL_CL3 = 0x97, // Anti collision/Select, Cascade Level 3 + PICC_CMD_HLTA = 0x50, // HaLT command, Type A. Instructs an ACTIVE PICC to go to state HALT. + PICC_CMD_RATS = 0xE0, // Request command for Answer To Reset. + // The commands used for MIFARE Classic (from http://www.mouser.com/ds/2/302/MF1S503x-89574.pdf, Section 9) + // Use PCD_MFAuthent to authenticate access to a sector, then use these commands to read/write/modify the blocks on + // the sector. + // The read/write commands can also be used for MIFARE Ultralight. + PICC_CMD_MF_AUTH_KEY_A = 0x60, // Perform authentication with Key A + PICC_CMD_MF_AUTH_KEY_B = 0x61, // Perform authentication with Key B + PICC_CMD_MF_READ = + 0x30, // Reads one 16 uint8_t block from the authenticated sector of the PICC. Also used for MIFARE Ultralight. + PICC_CMD_MF_WRITE = 0xA0, // Writes one 16 uint8_t block to the authenticated sector of the PICC. Called + // "COMPATIBILITY WRITE" for MIFARE Ultralight. + PICC_CMD_MF_DECREMENT = + 0xC0, // Decrements the contents of a block and stores the result in the internal data register. + PICC_CMD_MF_INCREMENT = + 0xC1, // Increments the contents of a block and stores the result in the internal data register. + PICC_CMD_MF_RESTORE = 0xC2, // Reads the contents of a block into the internal data register. + PICC_CMD_MF_TRANSFER = 0xB0, // Writes the contents of the internal data register to a block. + // The commands used for MIFARE Ultralight (from http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf, Section 8.6) + // The PICC_CMD_MF_READ and PICC_CMD_MF_WRITE can also be used for MIFARE Ultralight. + PICC_CMD_UL_WRITE = 0xA2 // Writes one 4 uint8_t page to the PICC. + }; + + // Return codes from the functions in this class. Remember to update GetStatusCodeName() if you add more. + // last value set to 0xff, then compiler uses less ram, it seems some optimisations are triggered + enum StatusCode : uint8_t { + STATUS_OK, // Success + STATUS_ERROR, // Error in communication + STATUS_COLLISION, // Collission detected + STATUS_TIMEOUT, // Timeout in communication. + STATUS_NO_ROOM, // A buffer is not big enough. + STATUS_INTERNAL_ERROR, // Internal error in the code. Should not happen ;-) + STATUS_INVALID, // Invalid argument. + STATUS_CRC_WRONG, // The CRC_A does not match + STATUS_MIFARE_NACK = 0xff // A MIFARE PICC responded with NAK. + }; + + // A struct used for passing the UID of a PICC. + using Uid = struct { + uint8_t size; // Number of uint8_ts in the UID. 4, 7 or 10. + uint8_t uiduint8_t[10]; + uint8_t sak; // The SAK (Select acknowledge) uint8_t returned from the PICC after successful selection. + }; + + Uid uid_; + uint32_t update_wait_{0}; + + void pcd_reset_(); + void initialize_(); + void pcd_antenna_on_(); + virtual uint8_t pcd_read_register(PcdRegister reg ///< The register to read from. One of the PCD_Register enums. + ) = 0; + + /** + * Reads a number of uint8_ts from the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ + virtual void pcd_read_register(PcdRegister reg, ///< The register to read from. One of the PCD_Register enums. + uint8_t count, ///< The number of uint8_ts to read + uint8_t *values, ///< uint8_t array to store the values in. + uint8_t rx_align ///< Only bit positions rxAlign..7 in values[0] are updated. + ) = 0; + virtual void pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. + uint8_t value ///< The value to write. + ) = 0; + + /** + * Writes a number of uint8_ts to the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ + virtual void pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. + uint8_t count, ///< The number of uint8_ts to write to the register + uint8_t *values ///< The values to write. uint8_t array. + ) = 0; + + StatusCode picc_request_a_( + uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in + uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK. + ); + StatusCode picc_reqa_or_wupa_( + uint8_t command, ///< The command to send - PICC_CMD_REQA or PICC_CMD_WUPA + uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in + uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK. + ); + void pcd_set_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums. + uint8_t mask ///< The bits to set. + ); + void pcd_clear_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums. + uint8_t mask ///< The bits to clear. + ); + + StatusCode pcd_transceive_data_(uint8_t *send_data, uint8_t send_len, uint8_t *back_data, uint8_t *back_len, + uint8_t *valid_bits = nullptr, uint8_t rx_align = 0, bool check_crc = false); + StatusCode pcd_communicate_with_picc_(uint8_t command, uint8_t wait_i_rq, uint8_t *send_data, uint8_t send_len, + uint8_t *back_data = nullptr, uint8_t *back_len = nullptr, + uint8_t *valid_bits = nullptr, uint8_t rx_align = 0, bool check_crc = false); + StatusCode pcd_calculate_crc_( + uint8_t *data, ///< In: Pointer to the data to transfer to the FIFO for CRC calculation. + uint8_t length, ///< In: The number of uint8_ts to transfer. + uint8_t *result ///< Out: Pointer to result buffer. Result is written to result[0..1], low uint8_t first. + ); + RC522::StatusCode picc_is_new_card_present_(); + bool picc_read_card_serial_(); + StatusCode picc_select_( + Uid *uid, ///< Pointer to Uid struct. Normally output, but can also be used to supply a known UID. + uint8_t valid_bits = 0 ///< The number of known UID bits supplied in *uid. Normally 0. If set you must also + ///< supply uid->size. + ); + + /** Read a data frame from the RC522 and return the result as a vector. + * + * Note that is_ready needs to be checked first before requesting this method. + * + * On failure, an empty vector is returned. + */ + std::vector r_c522_read_data_(); + + GPIOPin *reset_pin_{nullptr}; + uint8_t reset_count_{0}; + uint32_t reset_timeout_{0}; + bool initialize_pending_{false}; + std::vector binary_sensors_; + std::vector triggers_; + + enum RC522Error { + NONE = 0, + RESET_FAILED, + } error_code_{NONE}; +}; + +class RC522BinarySensor : public binary_sensor::BinarySensor { + public: + void set_uid(const std::vector &uid) { uid_ = uid; } + + bool process(const uint8_t *data, uint8_t len); + + void on_scan_end() { + if (!this->found_) { + this->publish_state(false); + } + this->found_ = false; + } + + protected: + std::vector uid_; + bool found_{false}; +}; + +class RC522Trigger : public Trigger { + public: + void process(const uint8_t *uid, uint8_t uid_length); +}; + +} // namespace rc522 +} // namespace esphome diff --git a/esphome/components/rc522_i2c/__init__.py b/esphome/components/rc522_i2c/__init__.py new file mode 100644 index 0000000000..c5bb72ee53 --- /dev/null +++ b/esphome/components/rc522_i2c/__init__.py @@ -0,0 +1,22 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, rc522 +from esphome.const import CONF_ID + +CODEOWNERS = ['@glmnet'] +DEPENDENCIES = ['i2c'] +AUTO_LOAD = ['rc522'] + + +rc522_i2c_ns = cg.esphome_ns.namespace('rc522_i2c') +RC522I2C = rc522_i2c_ns.class_('RC522I2C', rc522.RC522, i2c.I2CDevice) + +CONFIG_SCHEMA = cv.All(rc522.RC522_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(RC522I2C), +}).extend(i2c.i2c_device_schema(0x2c))) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield rc522.setup_rc522(var, config) + yield i2c.register_i2c_device(var, config) diff --git a/esphome/components/rc522_i2c/rc522_i2c.cpp b/esphome/components/rc522_i2c/rc522_i2c.cpp new file mode 100644 index 0000000000..8248e79b50 --- /dev/null +++ b/esphome/components/rc522_i2c/rc522_i2c.cpp @@ -0,0 +1,99 @@ +#include "rc522_i2c.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace rc522_i2c { + +static const char *TAG = "rc522_i2c"; + +void RC522I2C::dump_config() { + RC522::dump_config(); + LOG_I2C_DEVICE(this); +} + +/** + * Reads a uint8_t from the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ +uint8_t RC522I2C::pcd_read_register(PcdRegister reg ///< The register to read from. One of the PCD_Register enums. +) { + uint8_t value; + read_byte(reg >> 1, &value); + ESP_LOGVV(TAG, "read_register_(%x) -> %x", reg, value); + return value; +} + +/** + * Reads a number of uint8_ts from the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ +void RC522I2C::pcd_read_register(PcdRegister reg, ///< The register to read from. One of the PCD_Register enums. + uint8_t count, ///< The number of uint8_ts to read + uint8_t *values, ///< uint8_t array to store the values in. + uint8_t rx_align ///< Only bit positions rxAlign..7 in values[0] are updated. +) { + if (count == 0) { + return; + } + + std::string buf; + buf = "Rx"; + char cstrb[20]; + + uint8_t b = values[0]; + read_bytes(reg >> 1, values, count); + + if (rx_align) // Only update bit positions rxAlign..7 in values[0] + { + // Create bit mask for bit positions rxAlign..7 + uint8_t mask = 0xFF << rx_align; + // Apply mask to both current value of values[0] and the new data in values array. + values[0] = (b & ~mask) | (values[0] & mask); + } +} + +void RC522I2C::pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. + uint8_t value ///< The value to write. +) { + this->write_byte(reg >> 1, value); +} + +/** + * Writes a number of uint8_ts to the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ +void RC522I2C::pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. + uint8_t count, ///< The number of uint8_ts to write to the register + uint8_t *values ///< The values to write. uint8_t array. +) { + write_bytes(reg >> 1, values, count); +} + +// bool RC522I2C::write_data(const std::vector &data) { +// return this->write_bytes_raw(data.data(), data.size()); } + +// bool RC522I2C::read_data(std::vector &data, uint8_t len) { +// delay(5); + +// std::vector ready; +// ready.resize(1); +// uint32_t start_time = millis(); +// while (true) { +// if (this->read_bytes_raw(ready.data(), 1)) { +// if (ready[0] == 0x01) +// break; +// } + +// if (millis() - start_time > 100) { +// ESP_LOGV(TAG, "Timed out waiting for readiness from RC522!"); +// return false; +// } +// } + +// data.resize(len + 1); +// this->read_bytes_raw(data.data(), len + 1); +// return true; +// } + +} // namespace rc522_i2c +} // namespace esphome diff --git a/esphome/components/rc522_i2c/rc522_i2c.h b/esphome/components/rc522_i2c/rc522_i2c.h new file mode 100644 index 0000000000..8d8b0a0716 --- /dev/null +++ b/esphome/components/rc522_i2c/rc522_i2c.h @@ -0,0 +1,42 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/rc522/rc522.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace rc522_i2c { + +class RC522I2C : public rc522::RC522, public i2c::I2CDevice { + public: + void dump_config() override; + + protected: + uint8_t pcd_read_register(PcdRegister reg ///< The register to read from. One of the PCD_Register enums. + ) override; + + /** + * Reads a number of uint8_ts from the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ + void pcd_read_register(PcdRegister reg, ///< The register to read from. One of the PCD_Register enums. + uint8_t count, ///< The number of uint8_ts to read + uint8_t *values, ///< uint8_t array to store the values in. + uint8_t rx_align ///< Only bit positions rxAlign..7 in values[0] are updated. + ) override; + void pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. + uint8_t value ///< The value to write. + ) override; + + /** + * Writes a number of uint8_ts to the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ + void pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. + uint8_t count, ///< The number of uint8_ts to write to the register + uint8_t *values ///< The values to write. uint8_t array. + ) override; +}; + +} // namespace rc522_i2c +} // namespace esphome diff --git a/esphome/components/rc522_spi/__init__.py b/esphome/components/rc522_spi/__init__.py index 043d083c4c..37f78b66d0 100644 --- a/esphome/components/rc522_spi/__init__.py +++ b/esphome/components/rc522_spi/__init__.py @@ -1,39 +1,21 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome import automation, pins -from esphome.components import spi -from esphome.const import CONF_ID, CONF_ON_TAG, CONF_TRIGGER_ID, CONF_RESET_PIN, CONF_CS_PIN +from esphome.components import spi, rc522 +from esphome.const import CONF_ID CODEOWNERS = ['@glmnet'] DEPENDENCIES = ['spi'] -AUTO_LOAD = ['binary_sensor'] -MULTI_CONF = True - +AUTO_LOAD = ['rc522'] rc522_spi_ns = cg.esphome_ns.namespace('rc522_spi') -RC522 = rc522_spi_ns.class_('RC522', cg.PollingComponent, spi.SPIDevice) -RC522Trigger = rc522_spi_ns.class_('RC522Trigger', automation.Trigger.template(cg.std_string)) +RC522Spi = rc522_spi_ns.class_('RC522Spi', rc522.RC522, spi.SPIDevice) -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(RC522), - cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, - cv.Required(CONF_CS_PIN): pins.gpio_output_pin_schema, - cv.Optional(CONF_ON_TAG): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RC522Trigger), - }), -}).extend(cv.polling_component_schema('1s')).extend(spi.spi_device_schema()) +CONFIG_SCHEMA = cv.All(rc522.RC522_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(RC522Spi), +}).extend(spi.spi_device_schema(cs_pin_required=True))) def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - yield cg.register_component(var, config) + yield rc522.setup_rc522(var, config) yield spi.register_spi_device(var, config) - - if CONF_RESET_PIN in config: - reset = yield cg.gpio_pin_expression(config[CONF_RESET_PIN]) - cg.add(var.set_reset_pin(reset)) - - for conf in config.get(CONF_ON_TAG, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) - cg.add(var.register_trigger(trigger)) - yield automation.build_automation(trigger, [(cg.std_string, 'x')], conf) diff --git a/esphome/components/rc522_spi/binary_sensor.py b/esphome/components/rc522_spi/binary_sensor.py index d58d62c797..1f0eafe6af 100644 --- a/esphome/components/rc522_spi/binary_sensor.py +++ b/esphome/components/rc522_spi/binary_sensor.py @@ -1,44 +1,9 @@ -import esphome.codegen as cg -import esphome.config_validation as cv -from esphome.components import binary_sensor -from esphome.const import CONF_UID, CONF_ID -from esphome.core import HexInt -from . import rc522_spi_ns, RC522 +import esphome.components.rc522.binary_sensor as rc522_binary_sensor -DEPENDENCIES = ['rc522_spi'] +DEPENDENCIES = ['rc522'] -CONF_RC522_ID = 'rc522_id' - - -def validate_uid(value): - value = cv.string_strict(value) - for x in value.split('-'): - if len(x) != 2: - raise cv.Invalid("Each part (separated by '-') of the UID must be two characters " - "long.") - try: - x = int(x, 16) - except ValueError as err: - raise cv.Invalid("Valid characters for parts of a UID are 0123456789ABCDEF.") from err - if x < 0 or x > 255: - raise cv.Invalid("Valid values for UID parts (separated by '-') are 00 to FF") - return value - - -RC522BinarySensor = rc522_spi_ns.class_('RC522BinarySensor', binary_sensor.BinarySensor) - -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ - cv.GenerateID(): cv.declare_id(RC522BinarySensor), - cv.GenerateID(CONF_RC522_ID): cv.use_id(RC522), - cv.Required(CONF_UID): validate_uid, -}) +CONFIG_SCHEMA = rc522_binary_sensor.CONFIG_SCHEMA def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - yield binary_sensor.register_binary_sensor(var, config) - - hub = yield cg.get_variable(config[CONF_RC522_ID]) - cg.add(hub.register_tag(var)) - addr = [HexInt(int(x, 16)) for x in config[CONF_UID].split('-')] - cg.add(var.set_uid(addr)) + yield rc522_binary_sensor.to_code(config) diff --git a/esphome/components/rc522_spi/rc522_spi.cpp b/esphome/components/rc522_spi/rc522_spi.cpp index b332e50c53..61236393e4 100644 --- a/esphome/components/rc522_spi/rc522_spi.cpp +++ b/esphome/components/rc522_spi/rc522_spi.cpp @@ -9,218 +9,30 @@ namespace rc522_spi { static const char *TAG = "rc522_spi"; -static const uint8_t RESET_COUNT = 5; +void RC522Spi::setup() { + ESP_LOGI(TAG, "SPI Setup"); + this->spi_setup(); -void format_uid(char *buf, const uint8_t *uid, uint8_t uid_length) { - int offset = 0; - for (uint8_t i = 0; i < uid_length; i++) { - const char *format = "%02X"; - if (i + 1 < uid_length) - format = "%02X-"; - offset += sprintf(buf + offset, format, uid[i]); - } + RC522::setup(); } -void RC522::setup() { - spi_setup(); - initialize_pending_ = true; - // Pull device out of power down / reset state. - - // First set the resetPowerDownPin as digital input, to check the MFRC522 power down mode. - if (reset_pin_ != nullptr) { - reset_pin_->pin_mode(INPUT); - - if (reset_pin_->digital_read() == LOW) { // The MFRC522 chip is in power down mode. - ESP_LOGV(TAG, "Power down mode detected. Hard resetting..."); - reset_pin_->pin_mode(OUTPUT); // Now set the resetPowerDownPin as digital output. - reset_pin_->digital_write(LOW); // Make sure we have a clean LOW state. - delayMicroseconds(2); // 8.8.1 Reset timing requirements says about 100ns. Let us be generous: 2μsl - reset_pin_->digital_write(HIGH); // Exit power down mode. This triggers a hard reset. - // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs. - // Let us be generous: 50ms. - reset_timeout_ = millis(); - return; - } - } - - // Setup a soft reset - reset_count_ = RESET_COUNT; - reset_timeout_ = millis(); -} - -void RC522::initialize_() { - // Per originall code, wait 50 ms - if (millis() - reset_timeout_ < 50) - return; - - // Reset baud rates - ESP_LOGV(TAG, "Initialize"); - - pcd_write_register_(TX_MODE_REG, 0x00); - pcd_write_register_(RX_MODE_REG, 0x00); - // Reset ModWidthReg - pcd_write_register_(MOD_WIDTH_REG, 0x26); - - // When communicating with a PICC we need a timeout if something goes wrong. - // f_timer = 13.56 MHz / (2*TPreScaler+1) where TPreScaler = [TPrescaler_Hi:TPrescaler_Lo]. - // TPrescaler_Hi are the four low bits in TModeReg. TPrescaler_Lo is TPrescalerReg. - pcd_write_register_(T_MODE_REG, 0x80); // TAuto=1; timer starts automatically at the end of the transmission in all - // communication modes at all speeds - - // TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9 = 169 => f_timer=40kHz, ie a timer period of 25μs. - pcd_write_register_(T_PRESCALER_REG, 0xA9); - pcd_write_register_(T_RELOAD_REG_H, 0x03); // Reload timer with 0x3E8 = 1000, ie 25ms before timeout. - pcd_write_register_(T_RELOAD_REG_L, 0xE8); - - // Default 0x00. Force a 100 % ASK modulation independent of the ModGsPReg register setting - pcd_write_register_(TX_ASK_REG, 0x40); - pcd_write_register_(MODE_REG, 0x3D); // Default 0x3F. Set the preset value for the CRC coprocessor for the CalcCRC - // command to 0x6363 (ISO 14443-3 part 6.2.4) - pcd_antenna_on_(); // Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset) - - initialize_pending_ = false; -} - -void RC522::dump_config() { - ESP_LOGCONFIG(TAG, "RC522:"); - switch (this->error_code_) { - case NONE: - break; - case RESET_FAILED: - ESP_LOGE(TAG, "Reset command failed!"); - break; - } - +void RC522Spi::dump_config() { + RC522::dump_config(); LOG_PIN(" CS Pin: ", this->cs_); - LOG_PIN(" RESET Pin: ", this->reset_pin_); - - LOG_UPDATE_INTERVAL(this); - - for (auto *child : this->binary_sensors_) { - LOG_BINARY_SENSOR(" ", "Tag", child); - } -} - -void RC522::loop() { - // First check reset is needed - if (reset_count_ > 0) { - pcd_reset_(); - return; - } - if (initialize_pending_) { - initialize_(); - return; - } - - if (millis() - update_wait_ < this->update_interval_) - return; - - auto status = picc_is_new_card_present_(); - - if (status == STATUS_ERROR) // No card - { - ESP_LOGE(TAG, "Error"); - // mark_failed(); - return; - } - - if (status != STATUS_OK) // We can receive STATUS_TIMEOUT when no card, or unexpected status. - return; - - // Try process card - if (!picc_read_card_serial_()) { - ESP_LOGW(TAG, "Requesting tag read failed!"); - return; - }; - - if (uid_.size < 4) { - return; - ESP_LOGW(TAG, "Read serial size: %d", uid_.size); - } - - update_wait_ = millis(); - - bool report = true; - // 1. Go through all triggers - for (auto *trigger : this->triggers_) - trigger->process(uid_.uiduint8_t, uid_.size); - - // 2. Find a binary sensor - for (auto *tag : this->binary_sensors_) { - if (tag->process(uid_.uiduint8_t, uid_.size)) { - // 2.1 if found, do not dump - report = false; - } - } - - if (report) { - char buf[32]; - format_uid(buf, uid_.uiduint8_t, uid_.size); - ESP_LOGD(TAG, "Found new tag '%s'", buf); - } -} - -void RC522::update() { - for (auto *obj : this->binary_sensors_) - obj->on_scan_end(); -} - -/** - * Performs a soft reset on the MFRC522 chip and waits for it to be ready again. - */ -void RC522::pcd_reset_() { - // The datasheet does not mention how long the SoftRest command takes to complete. - // But the MFRC522 might have been in soft power-down mode (triggered by bit 4 of CommandReg) - // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs. Let - // us be generous: 50ms. - - if (millis() - reset_timeout_ < 50) - return; - - if (reset_count_ == RESET_COUNT) { - ESP_LOGV(TAG, "Soft reset..."); - // Issue the SoftReset command. - pcd_write_register_(COMMAND_REG, PCD_SOFT_RESET); - } - - // Expect the PowerDown bit in CommandReg to be cleared (max 3x50ms) - if ((pcd_read_register_(COMMAND_REG) & (1 << 4)) == 0) { - reset_count_ = 0; - ESP_LOGI(TAG, "Device online."); - // Wait for initialize - reset_timeout_ = millis(); - return; - } - - if (--reset_count_ == 0) { - ESP_LOGE(TAG, "Unable to reset RC522."); - mark_failed(); - } -} - -/** - * Turns the antenna on by enabling pins TX1 and TX2. - * After a reset these pins are disabled. - */ -void RC522::pcd_antenna_on_() { - uint8_t value = pcd_read_register_(TX_CONTROL_REG); - if ((value & 0x03) != 0x03) { - pcd_write_register_(TX_CONTROL_REG, value | 0x03); - } } /** * Reads a uint8_t from the specified register in the MFRC522 chip. * The interface is described in the datasheet section 8.1.2. */ -uint8_t RC522::pcd_read_register_(PcdRegister reg ///< The register to read from. One of the PCD_Register enums. +uint8_t RC522Spi::pcd_read_register(PcdRegister reg ///< The register to read from. One of the PCD_Register enums. ) { uint8_t value; enable(); transfer_byte(0x80 | reg); value = read_byte(); disable(); - ESP_LOGVV(TAG, "read_register_(%x) -> %x", reg, value); + ESP_LOGV(TAG, "read_register_(%x) -> %x", reg, value); return value; } @@ -228,10 +40,10 @@ uint8_t RC522::pcd_read_register_(PcdRegister reg ///< The register to read fro * Reads a number of uint8_ts from the specified register in the MFRC522 chip. * The interface is described in the datasheet section 8.1.2. */ -void RC522::pcd_read_register_(PcdRegister reg, ///< The register to read from. One of the PCD_Register enums. - uint8_t count, ///< The number of uint8_ts to read - uint8_t *values, ///< uint8_t array to store the values in. - uint8_t rx_align ///< Only bit positions rxAlign..7 in values[0] are updated. +void RC522Spi::pcd_read_register(PcdRegister reg, ///< The register to read from. One of the PCD_Register enums. + uint8_t count, ///< The number of uint8_ts to read + uint8_t *values, ///< uint8_t array to store the values in. + uint8_t rx_align ///< Only bit positions rxAlign..7 in values[0] are updated. ) { std::string buf; buf = "Rx"; @@ -278,8 +90,8 @@ void RC522::pcd_read_register_(PcdRegister reg, ///< The register to read from. disable(); } -void RC522::pcd_write_register_(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. - uint8_t value ///< The value to write. +void RC522Spi::pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. + uint8_t value ///< The value to write. ) { enable(); // MSB == 0 is for writing. LSB is not used in address. Datasheet section 8.1.2.3. @@ -292,9 +104,9 @@ void RC522::pcd_write_register_(PcdRegister reg, ///< The register to write to. * Writes a number of uint8_ts to the specified register in the MFRC522 chip. * The interface is described in the datasheet section 8.1.2. */ -void RC522::pcd_write_register_(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. - uint8_t count, ///< The number of uint8_ts to write to the register - uint8_t *values ///< The values to write. uint8_t array. +void RC522Spi::pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. + uint8_t count, ///< The number of uint8_ts to write to the register + uint8_t *values ///< The values to write. uint8_t array. ) { std::string buf; buf = "Tx"; @@ -313,545 +125,5 @@ void RC522::pcd_write_register_(PcdRegister reg, ///< The register to write to. ESP_LOGVV(TAG, "write_register_(%x, %d) -> %s", reg, count, buf.c_str()); } -/** - * Transmits a REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or - * selection. 7 bit frame. Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - - * probably due do bad antenna design. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -RC522::StatusCode RC522::picc_request_a_( - uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in - uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK. -) { - return picc_reqa_or_wupa_(PICC_CMD_REQA, buffer_atqa, buffer_size); -} - -/** - * Transmits REQA or WUPA commands. - * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna - * design. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -RC522::StatusCode RC522::picc_reqa_or_wupa_( - uint8_t command, ///< The command to send - PICC_CMD_REQA or PICC_CMD_WUPA - uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in - uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK. -) { - uint8_t valid_bits; - RC522::StatusCode status; - - if (buffer_atqa == nullptr || *buffer_size < 2) { // The ATQA response is 2 uint8_ts long. - return STATUS_NO_ROOM; - } - pcd_clear_register_bit_mask_(COLL_REG, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared. - valid_bits = 7; // For REQA and WUPA we need the short frame format - transmit only 7 bits of the last (and only) - // uint8_t. TxLastBits = BitFramingReg[2..0] - status = pcd_transceive_data_(&command, 1, buffer_atqa, buffer_size, &valid_bits); - if (status != STATUS_OK) - return status; - if (*buffer_size != 2 || valid_bits != 0) { // ATQA must be exactly 16 bits. - ESP_LOGVV(TAG, "picc_reqa_or_wupa_() -> STATUS_ERROR"); - return STATUS_ERROR; - } - - return STATUS_OK; -} - -/** - * Sets the bits given in mask in register reg. - */ -void RC522::pcd_set_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums. - uint8_t mask ///< The bits to set. -) { - uint8_t tmp = pcd_read_register_(reg); - pcd_write_register_(reg, tmp | mask); // set bit mask -} - -/** - * Clears the bits given in mask from register reg. - */ -void RC522::pcd_clear_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums. - uint8_t mask ///< The bits to clear. -) { - uint8_t tmp = pcd_read_register_(reg); - pcd_write_register_(reg, tmp & (~mask)); // clear bit mask -} - -/** - * Executes the Transceive command. - * CRC validation can only be done if backData and backLen are specified. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -RC522::StatusCode RC522::pcd_transceive_data_( - uint8_t *send_data, ///< Pointer to the data to transfer to the FIFO. - uint8_t send_len, ///< Number of uint8_ts to transfer to the FIFO. - uint8_t *back_data, ///< nullptr or pointer to buffer if data should be read back after executing the command. - uint8_t *back_len, ///< In: Max number of uint8_ts to write to *backData. Out: The number of uint8_ts returned. - uint8_t - *valid_bits, ///< In/Out: The number of valid bits in the last uint8_t. 0 for 8 valid bits. Default nullptr. - uint8_t rx_align, ///< In: Defines the bit position in backData[0] for the first bit received. Default 0. - bool check_crc ///< In: True => The last two uint8_ts of the response is assumed to be a CRC_A that must be - ///< validated. -) { - uint8_t wait_i_rq = 0x30; // RxIRq and IdleIRq - auto ret = pcd_communicate_with_picc_(PCD_TRANSCEIVE, wait_i_rq, send_data, send_len, back_data, back_len, valid_bits, - rx_align, check_crc); - - if (ret == STATUS_OK && *back_len == 5) - ESP_LOGVV(TAG, "pcd_transceive_data_(..., %d, ) -> %d [%x, %x, %x, %x, %x]", send_len, ret, back_data[0], - back_data[1], back_data[2], back_data[3], back_data[4]); - else - ESP_LOGVV(TAG, "pcd_transceive_data_(..., %d, ... ) -> %d", send_len, ret); - return ret; -} - -/** - * Transfers data to the MFRC522 FIFO, executes a command, waits for completion and transfers data back from the FIFO. - * CRC validation can only be done if backData and backLen are specified. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -RC522::StatusCode RC522::pcd_communicate_with_picc_( - uint8_t command, ///< The command to execute. One of the PCD_Command enums. - uint8_t wait_i_rq, ///< The bits in the ComIrqReg register that signals successful completion of the command. - uint8_t *send_data, ///< Pointer to the data to transfer to the FIFO. - uint8_t send_len, ///< Number of uint8_ts to transfer to the FIFO. - uint8_t *back_data, ///< nullptr or pointer to buffer if data should be read back after executing the command. - uint8_t *back_len, ///< In: Max number of uint8_ts to write to *backData. Out: The number of uint8_ts returned. - uint8_t *valid_bits, ///< In/Out: The number of valid bits in the last uint8_t. 0 for 8 valid bits. - uint8_t rx_align, ///< In: Defines the bit position in backData[0] for the first bit received. Default 0. - bool check_crc ///< In: True => The last two uint8_ts of the response is assumed to be a CRC_A that must be - ///< validated. -) { - ESP_LOGVV(TAG, "pcd_communicate_with_picc_(%d, %d,... %d)", command, wait_i_rq, check_crc); - - // Prepare values for BitFramingReg - uint8_t tx_last_bits = valid_bits ? *valid_bits : 0; - uint8_t bit_framing = - (rx_align << 4) + tx_last_bits; // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] - - pcd_write_register_(COMMAND_REG, PCD_IDLE); // Stop any active command. - pcd_write_register_(COM_IRQ_REG, 0x7F); // Clear all seven interrupt request bits - pcd_write_register_(FIFO_LEVEL_REG, 0x80); // FlushBuffer = 1, FIFO initialization - pcd_write_register_(FIFO_DATA_REG, send_len, send_data); // Write sendData to the FIFO - pcd_write_register_(BIT_FRAMING_REG, bit_framing); // Bit adjustments - pcd_write_register_(COMMAND_REG, command); // Execute the command - if (command == PCD_TRANSCEIVE) { - pcd_set_register_bit_mask_(BIT_FRAMING_REG, 0x80); // StartSend=1, transmission of data starts - } - - // Wait for the command to complete. - // In PCD_Init() we set the TAuto flag in TModeReg. This means the timer automatically starts when the PCD stops - // transmitting. Each iteration of the do-while-loop takes 17.86μs. - // TODO check/modify for other architectures than Arduino Uno 16bit - uint16_t i; - for (i = 2000; i > 0; i--) { - uint8_t n = pcd_read_register_( - COM_IRQ_REG); // ComIrqReg[7..0] bits are: Set1 TxIRq RxIRq IdleIRq HiAlertIRq LoAlertIRq ErrIRq TimerIRq - if (n & wait_i_rq) { // One of the interrupts that signal success has been set. - break; - } - if (n & 0x01) { // Timer interrupt - nothing received in 25ms - return STATUS_TIMEOUT; - } - } - // 35.7ms and nothing happend. Communication with the MFRC522 might be down. - if (i == 0) { - return STATUS_TIMEOUT; - } - - // Stop now if any errors except collisions were detected. - uint8_t error_reg_value = pcd_read_register_( - ERROR_REG); // ErrorReg[7..0] bits are: WrErr TempErr reserved BufferOvfl CollErr CRCErr ParityErr ProtocolErr - if (error_reg_value & 0x13) { // BufferOvfl ParityErr ProtocolErr - return STATUS_ERROR; - } - - uint8_t valid_bits_local = 0; - - // If the caller wants data back, get it from the MFRC522. - if (back_data && back_len) { - uint8_t n = pcd_read_register_(FIFO_LEVEL_REG); // Number of uint8_ts in the FIFO - if (n > *back_len) { - return STATUS_NO_ROOM; - } - *back_len = n; // Number of uint8_ts returned - pcd_read_register_(FIFO_DATA_REG, n, back_data, rx_align); // Get received data from FIFO - valid_bits_local = - pcd_read_register_(CONTROL_REG) & 0x07; // RxLastBits[2:0] indicates the number of valid bits in the last - // received uint8_t. If this value is 000b, the whole uint8_t is valid. - if (valid_bits) { - *valid_bits = valid_bits_local; - } - } - - // Tell about collisions - if (error_reg_value & 0x08) { // CollErr - return STATUS_COLLISION; - } - - // Perform CRC_A validation if requested. - if (back_data && back_len && check_crc) { - // In this case a MIFARE Classic NAK is not OK. - if (*back_len == 1 && valid_bits_local == 4) { - return STATUS_MIFARE_NACK; - } - // We need at least the CRC_A value and all 8 bits of the last uint8_t must be received. - if (*back_len < 2 || valid_bits_local != 0) { - return STATUS_CRC_WRONG; - } - // Verify CRC_A - do our own calculation and store the control in controlBuffer. - uint8_t control_buffer[2]; - RC522::StatusCode status = pcd_calculate_crc_(&back_data[0], *back_len - 2, &control_buffer[0]); - if (status != STATUS_OK) { - return status; - } - if ((back_data[*back_len - 2] != control_buffer[0]) || (back_data[*back_len - 1] != control_buffer[1])) { - return STATUS_CRC_WRONG; - } - } - - return STATUS_OK; -} - -/** - * Use the CRC coprocessor in the MFRC522 to calculate a CRC_A. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ - -RC522::StatusCode RC522::pcd_calculate_crc_( - uint8_t *data, ///< In: Pointer to the data to transfer to the FIFO for CRC calculation. - uint8_t length, ///< In: The number of uint8_ts to transfer. - uint8_t *result ///< Out: Pointer to result buffer. Result is written to result[0..1], low uint8_t first. -) { - ESP_LOGVV(TAG, "pcd_calculate_crc_(..., %d, ...)", length); - pcd_write_register_(COMMAND_REG, PCD_IDLE); // Stop any active command. - pcd_write_register_(DIV_IRQ_REG, 0x04); // Clear the CRCIRq interrupt request bit - pcd_write_register_(FIFO_LEVEL_REG, 0x80); // FlushBuffer = 1, FIFO initialization - pcd_write_register_(FIFO_DATA_REG, length, data); // Write data to the FIFO - pcd_write_register_(COMMAND_REG, PCD_CALC_CRC); // Start the calculation - - // Wait for the CRC calculation to complete. Each iteration of the while-loop takes 17.73μs. - // TODO check/modify for other architectures than Arduino Uno 16bit - - // Wait for the CRC calculation to complete. Each iteration of the while-loop takes 17.73us. - for (uint16_t i = 5000; i > 0; i--) { - // DivIrqReg[7..0] bits are: Set2 reserved reserved MfinActIRq reserved CRCIRq reserved reserved - uint8_t n = pcd_read_register_(DIV_IRQ_REG); - if (n & 0x04) { // CRCIRq bit set - calculation done - pcd_write_register_(COMMAND_REG, PCD_IDLE); // Stop calculating CRC for new content in the FIFO. - // Transfer the result from the registers to the result buffer - result[0] = pcd_read_register_(CRC_RESULT_REG_L); - result[1] = pcd_read_register_(CRC_RESULT_REG_H); - - ESP_LOGVV(TAG, "pcd_calculate_crc_() STATUS_OK"); - return STATUS_OK; - } - } - ESP_LOGVV(TAG, "pcd_calculate_crc_() TIMEOUT"); - // 89ms passed and nothing happend. Communication with the MFRC522 might be down. - return STATUS_TIMEOUT; -} -/** - * Returns STATUS_OK if a PICC responds to PICC_CMD_REQA. - * Only "new" cards in state IDLE are invited. Sleeping cards in state HALT are ignored. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ - -RC522::StatusCode RC522::picc_is_new_card_present_() { - uint8_t buffer_atqa[2]; - uint8_t buffer_size = sizeof(buffer_atqa); - - // Reset baud rates - pcd_write_register_(TX_MODE_REG, 0x00); - pcd_write_register_(RX_MODE_REG, 0x00); - // Reset ModWidthReg - pcd_write_register_(MOD_WIDTH_REG, 0x26); - - auto result = picc_request_a_(buffer_atqa, &buffer_size); - - ESP_LOGV(TAG, "picc_is_new_card_present_() -> %d", result); - return result; -} - -/** - * Simple wrapper around PICC_Select. - * Returns true if a UID could be read. - * Remember to call PICC_IsNewCardPresent(), PICC_RequestA() or PICC_WakeupA() first. - * The read UID is available in the class variable uid. - * - * @return bool - */ -bool RC522::picc_read_card_serial_() { - RC522::StatusCode result = picc_select_(&this->uid_); - ESP_LOGVV(TAG, "picc_select_(...) -> %d", result); - return (result == STATUS_OK); -} - -/** - * Transmits SELECT/ANTICOLLISION commands to select a single PICC. - * Before calling this function the PICCs must be placed in the READY(*) state by calling PICC_RequestA() or - * PICC_WakeupA(). On success: - * - The chosen PICC is in state ACTIVE(*) and all other PICCs have returned to state IDLE/HALT. (Figure 7 of the - * ISO/IEC 14443-3 draft.) - * - The UID size and value of the chosen PICC is returned in *uid along with the SAK. - * - * A PICC UID consists of 4, 7 or 10 uint8_ts. - * Only 4 uint8_ts can be specified in a SELECT command, so for the longer UIDs two or three iterations are used: - * UID size Number of UID uint8_ts Cascade levels Example of PICC - * ======== =================== ============== =============== - * single 4 1 MIFARE Classic - * double 7 2 MIFARE Ultralight - * triple 10 3 Not currently in use? - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -RC522::StatusCode RC522::picc_select_( - Uid *uid, ///< Pointer to Uid struct. Normally output, but can also be used to supply a known UID. - uint8_t valid_bits ///< The number of known UID bits supplied in *uid. Normally 0. If set you must also supply - ///< uid->size. -) { - bool uid_complete; - bool select_done; - bool use_cascade_tag; - uint8_t cascade_level = 1; - RC522::StatusCode result; - uint8_t count; - uint8_t check_bit; - uint8_t index; - uint8_t uid_index; // The first index in uid->uiduint8_t[] that is used in the current Cascade Level. - int8_t current_level_known_bits; // The number of known UID bits in the current Cascade Level. - uint8_t buffer[9]; // The SELECT/ANTICOLLISION commands uses a 7 uint8_t standard frame + 2 uint8_ts CRC_A - uint8_t buffer_used; // The number of uint8_ts used in the buffer, ie the number of uint8_ts to transfer to the FIFO. - uint8_t rx_align; // Used in BitFramingReg. Defines the bit position for the first bit received. - uint8_t tx_last_bits; // Used in BitFramingReg. The number of valid bits in the last transmitted uint8_t. - uint8_t *response_buffer; - uint8_t response_length; - - // Description of buffer structure: - // uint8_t 0: SEL Indicates the Cascade Level: PICC_CMD_SEL_CL1, PICC_CMD_SEL_CL2 or PICC_CMD_SEL_CL3 - // uint8_t 1: NVB Number of Valid Bits (in complete command, not just the UID): High nibble: complete - // uint8_ts, - // Low nibble: Extra bits. uint8_t 2: UID-data or CT See explanation below. CT means Cascade Tag. uint8_t - // 3: UID-data uint8_t 4: UID-data uint8_t 5: UID-data uint8_t 6: BCC Block Check Character - XOR of - // uint8_ts 2-5 uint8_t 7: CRC_A uint8_t 8: CRC_A The BCC and CRC_A are only transmitted if we know all the UID bits - // of the current Cascade Level. - // - // Description of uint8_ts 2-5: (Section 6.5.4 of the ISO/IEC 14443-3 draft: UID contents and cascade levels) - // UID size Cascade level uint8_t2 uint8_t3 uint8_t4 uint8_t5 - // ======== ============= ===== ===== ===== ===== - // 4 uint8_ts 1 uid0 uid1 uid2 uid3 - // 7 uint8_ts 1 CT uid0 uid1 uid2 - // 2 uid3 uid4 uid5 uid6 - // 10 uint8_ts 1 CT uid0 uid1 uid2 - // 2 CT uid3 uid4 uid5 - // 3 uid6 uid7 uid8 uid9 - - // Sanity checks - if (valid_bits > 80) { - return STATUS_INVALID; - } - - ESP_LOGVV(TAG, "picc_select_(&, %d)", valid_bits); - - // Prepare MFRC522 - pcd_clear_register_bit_mask_(COLL_REG, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared. - - // Repeat Cascade Level loop until we have a complete UID. - uid_complete = false; - while (!uid_complete) { - // Set the Cascade Level in the SEL uint8_t, find out if we need to use the Cascade Tag in uint8_t 2. - switch (cascade_level) { - case 1: - buffer[0] = PICC_CMD_SEL_CL1; - uid_index = 0; - use_cascade_tag = valid_bits && uid->size > 4; // When we know that the UID has more than 4 uint8_ts - break; - - case 2: - buffer[0] = PICC_CMD_SEL_CL2; - uid_index = 3; - use_cascade_tag = valid_bits && uid->size > 7; // When we know that the UID has more than 7 uint8_ts - break; - - case 3: - buffer[0] = PICC_CMD_SEL_CL3; - uid_index = 6; - use_cascade_tag = false; // Never used in CL3. - break; - - default: - return STATUS_INTERNAL_ERROR; - break; - } - - // How many UID bits are known in this Cascade Level? - current_level_known_bits = valid_bits - (8 * uid_index); - if (current_level_known_bits < 0) { - current_level_known_bits = 0; - } - // Copy the known bits from uid->uiduint8_t[] to buffer[] - index = 2; // destination index in buffer[] - if (use_cascade_tag) { - buffer[index++] = PICC_CMD_CT; - } - uint8_t uint8_ts_to_copy = current_level_known_bits / 8 + - (current_level_known_bits % 8 - ? 1 - : 0); // The number of uint8_ts needed to represent the known bits for this level. - if (uint8_ts_to_copy) { - uint8_t maxuint8_ts = - use_cascade_tag ? 3 : 4; // Max 4 uint8_ts in each Cascade Level. Only 3 left if we use the Cascade Tag - if (uint8_ts_to_copy > maxuint8_ts) { - uint8_ts_to_copy = maxuint8_ts; - } - for (count = 0; count < uint8_ts_to_copy; count++) { - buffer[index++] = uid->uiduint8_t[uid_index + count]; - } - } - // Now that the data has been copied we need to include the 8 bits in CT in currentLevelKnownBits - if (use_cascade_tag) { - current_level_known_bits += 8; - } - - // Repeat anti collision loop until we can transmit all UID bits + BCC and receive a SAK - max 32 iterations. - select_done = false; - while (!select_done) { - // Find out how many bits and uint8_ts to send and receive. - if (current_level_known_bits >= 32) { // All UID bits in this Cascade Level are known. This is a SELECT. - - if (response_length < 4) { - ESP_LOGW(TAG, "Not enough data received."); - return STATUS_INVALID; - } - - // Serial.print(F("SELECT: currentLevelKnownBits=")); Serial.println(currentLevelKnownBits, DEC); - buffer[1] = 0x70; // NVB - Number of Valid Bits: Seven whole uint8_ts - // Calculate BCC - Block Check Character - buffer[6] = buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5]; - // Calculate CRC_A - result = pcd_calculate_crc_(buffer, 7, &buffer[7]); - if (result != STATUS_OK) { - return result; - } - tx_last_bits = 0; // 0 => All 8 bits are valid. - buffer_used = 9; - // Store response in the last 3 uint8_ts of buffer (BCC and CRC_A - not needed after tx) - response_buffer = &buffer[6]; - response_length = 3; - } else { // This is an ANTICOLLISION. - // Serial.print(F("ANTICOLLISION: currentLevelKnownBits=")); Serial.println(currentLevelKnownBits, DEC); - tx_last_bits = current_level_known_bits % 8; - count = current_level_known_bits / 8; // Number of whole uint8_ts in the UID part. - index = 2 + count; // Number of whole uint8_ts: SEL + NVB + UIDs - buffer[1] = (index << 4) + tx_last_bits; // NVB - Number of Valid Bits - buffer_used = index + (tx_last_bits ? 1 : 0); - // Store response in the unused part of buffer - response_buffer = &buffer[index]; - response_length = sizeof(buffer) - index; - } - - // Set bit adjustments - rx_align = tx_last_bits; // Having a separate variable is overkill. But it makes the next line easier to read. - pcd_write_register_( - BIT_FRAMING_REG, - (rx_align << 4) + tx_last_bits); // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] - - // Transmit the buffer and receive the response. - result = pcd_transceive_data_(buffer, buffer_used, response_buffer, &response_length, &tx_last_bits, rx_align); - if (result == STATUS_COLLISION) { // More than one PICC in the field => collision. - uint8_t value_of_coll_reg = pcd_read_register_( - COLL_REG); // CollReg[7..0] bits are: ValuesAfterColl reserved CollPosNotValid CollPos[4:0] - if (value_of_coll_reg & 0x20) { // CollPosNotValid - return STATUS_COLLISION; // Without a valid collision position we cannot continue - } - uint8_t collision_pos = value_of_coll_reg & 0x1F; // Values 0-31, 0 means bit 32. - if (collision_pos == 0) { - collision_pos = 32; - } - if (collision_pos <= current_level_known_bits) { // No progress - should not happen - return STATUS_INTERNAL_ERROR; - } - // Choose the PICC with the bit set. - current_level_known_bits = collision_pos; - count = current_level_known_bits % 8; // The bit to modify - check_bit = (current_level_known_bits - 1) % 8; - index = 1 + (current_level_known_bits / 8) + (count ? 1 : 0); // First uint8_t is index 0. - if (response_length > 2) // Note: Otherwise buffer[index] might be not initialized - buffer[index] |= (1 << check_bit); - } else if (result != STATUS_OK) { - return result; - } else { // STATUS_OK - if (current_level_known_bits >= 32) { // This was a SELECT. - select_done = true; // No more anticollision - // We continue below outside the while. - } else { // This was an ANTICOLLISION. - // We now have all 32 bits of the UID in this Cascade Level - current_level_known_bits = 32; - // Run loop again to do the SELECT. - } - } - } // End of while (!selectDone) - - // We do not check the CBB - it was constructed by us above. - - // Copy the found UID uint8_ts from buffer[] to uid->uiduint8_t[] - index = (buffer[2] == PICC_CMD_CT) ? 3 : 2; // source index in buffer[] - uint8_ts_to_copy = (buffer[2] == PICC_CMD_CT) ? 3 : 4; - for (count = 0; count < uint8_ts_to_copy; count++) { - uid->uiduint8_t[uid_index + count] = buffer[index++]; - } - - // Check response SAK (Select Acknowledge) - if (response_length != 3 || tx_last_bits != 0) { // SAK must be exactly 24 bits (1 uint8_t + CRC_A). - return STATUS_ERROR; - } - // Verify CRC_A - do our own calculation and store the control in buffer[2..3] - those uint8_ts are not needed - // anymore. - result = pcd_calculate_crc_(response_buffer, 1, &buffer[2]); - if (result != STATUS_OK) { - return result; - } - if ((buffer[2] != response_buffer[1]) || (buffer[3] != response_buffer[2])) { - return STATUS_CRC_WRONG; - } - if (response_buffer[0] & 0x04) { // Cascade bit set - UID not complete yes - cascade_level++; - } else { - uid_complete = true; - uid->sak = response_buffer[0]; - } - } // End of while (!uidComplete) - - // Set correct uid->size - uid->size = 3 * cascade_level + 1; - - return STATUS_OK; -} - -bool RC522BinarySensor::process(const uint8_t *data, uint8_t len) { - if (len != this->uid_.size()) - return false; - - for (uint8_t i = 0; i < len; i++) { - if (data[i] != this->uid_[i]) - return false; - } - - this->publish_state(true); - this->found_ = true; - return true; -} -void RC522Trigger::process(const uint8_t *uid, uint8_t uid_length) { - char buf[32]; - format_uid(buf, uid, uid_length); - this->trigger(std::string(buf)); -} - } // namespace rc522_spi } // namespace esphome diff --git a/esphome/components/rc522_spi/rc522_spi.h b/esphome/components/rc522_spi/rc522_spi.h index 51d0a9cb69..58edbbed4f 100644 --- a/esphome/components/rc522_spi/rc522_spi.h +++ b/esphome/components/rc522_spi/rc522_spi.h @@ -10,292 +10,46 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/automation.h" -#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/rc522/rc522.h" #include "esphome/components/spi/spi.h" namespace esphome { namespace rc522_spi { -class RC522BinarySensor; -class RC522Trigger; - -class RC522 : public PollingComponent, - public spi::SPIDevice { +class RC522Spi : public rc522::RC522, + public spi::SPIDevice { public: void setup() override; void dump_config() override; - void update() override; - float get_setup_priority() const override { return setup_priority::DATA; }; - - void loop() override; - - void register_tag(RC522BinarySensor *tag) { this->binary_sensors_.push_back(tag); } - void register_trigger(RC522Trigger *trig) { this->triggers_.push_back(trig); } - - void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } - protected: - enum PcdRegister : uint8_t { - // Page 0: Command and status - // 0x00 // reserved for future use - COMMAND_REG = 0x01 << 1, // starts and stops command execution - COM_I_EN_REG = 0x02 << 1, // enable and disable interrupt request control bits - DIV_I_EN_REG = 0x03 << 1, // enable and disable interrupt request control bits - COM_IRQ_REG = 0x04 << 1, // interrupt request bits - DIV_IRQ_REG = 0x05 << 1, // interrupt request bits - ERROR_REG = 0x06 << 1, // error bits showing the error status of the last command executed - STATUS1_REG = 0x07 << 1, // communication status bits - STATUS2_REG = 0x08 << 1, // receiver and transmitter status bits - FIFO_DATA_REG = 0x09 << 1, // input and output of 64 uint8_t FIFO buffer - FIFO_LEVEL_REG = 0x0A << 1, // number of uint8_ts stored in the FIFO buffer - WATER_LEVEL_REG = 0x0B << 1, // level for FIFO underflow and overflow warning - CONTROL_REG = 0x0C << 1, // miscellaneous control registers - BIT_FRAMING_REG = 0x0D << 1, // adjustments for bit-oriented frames - COLL_REG = 0x0E << 1, // bit position of the first bit-collision detected on the RF interface - // 0x0F // reserved for future use - - // Page 1: Command - // 0x10 // reserved for future use - MODE_REG = 0x11 << 1, // defines general modes for transmitting and receiving - TX_MODE_REG = 0x12 << 1, // defines transmission data rate and framing - RX_MODE_REG = 0x13 << 1, // defines reception data rate and framing - TX_CONTROL_REG = 0x14 << 1, // controls the logical behavior of the antenna driver pins TX1 and TX2 - TX_ASK_REG = 0x15 << 1, // controls the setting of the transmission modulation - TX_SEL_REG = 0x16 << 1, // selects the internal sources for the antenna driver - RX_SEL_REG = 0x17 << 1, // selects internal receiver settings - RX_THRESHOLD_REG = 0x18 << 1, // selects thresholds for the bit decoder - DEMOD_REG = 0x19 << 1, // defines demodulator settings - // 0x1A // reserved for future use - // 0x1B // reserved for future use - MF_TX_REG = 0x1C << 1, // controls some MIFARE communication transmit parameters - MF_RX_REG = 0x1D << 1, // controls some MIFARE communication receive parameters - // 0x1E // reserved for future use - SERIAL_SPEED_REG = 0x1F << 1, // selects the speed of the serial UART interface - - // Page 2: Configuration - // 0x20 // reserved for future use - CRC_RESULT_REG_H = 0x21 << 1, // shows the MSB and LSB values of the CRC calculation - CRC_RESULT_REG_L = 0x22 << 1, - // 0x23 // reserved for future use - MOD_WIDTH_REG = 0x24 << 1, // controls the ModWidth setting? - // 0x25 // reserved for future use - RF_CFG_REG = 0x26 << 1, // configures the receiver gain - GS_N_REG = 0x27 << 1, // selects the conductance of the antenna driver pins TX1 and TX2 for modulation - CW_GS_P_REG = 0x28 << 1, // defines the conductance of the p-driver output during periods of no modulation - MOD_GS_P_REG = 0x29 << 1, // defines the conductance of the p-driver output during periods of modulation - T_MODE_REG = 0x2A << 1, // defines settings for the internal timer - T_PRESCALER_REG = 0x2B << 1, // the lower 8 bits of the TPrescaler value. The 4 high bits are in TModeReg. - T_RELOAD_REG_H = 0x2C << 1, // defines the 16-bit timer reload value - T_RELOAD_REG_L = 0x2D << 1, - T_COUNTER_VALUE_REG_H = 0x2E << 1, // shows the 16-bit timer value - T_COUNTER_VALUE_REG_L = 0x2F << 1, - - // Page 3: Test Registers - // 0x30 // reserved for future use - TEST_SEL1_REG = 0x31 << 1, // general test signal configuration - TEST_SEL2_REG = 0x32 << 1, // general test signal configuration - TEST_PIN_EN_REG = 0x33 << 1, // enables pin output driver on pins D1 to D7 - TEST_PIN_VALUE_REG = 0x34 << 1, // defines the values for D1 to D7 when it is used as an I/O bus - TEST_BUS_REG = 0x35 << 1, // shows the status of the internal test bus - AUTO_TEST_REG = 0x36 << 1, // controls the digital self-test - VERSION_REG = 0x37 << 1, // shows the software version - ANALOG_TEST_REG = 0x38 << 1, // controls the pins AUX1 and AUX2 - TEST_DA_C1_REG = 0x39 << 1, // defines the test value for TestDAC1 - TEST_DA_C2_REG = 0x3A << 1, // defines the test value for TestDAC2 - TEST_ADC_REG = 0x3B << 1 // shows the value of ADC I and Q channels - // 0x3C // reserved for production tests - // 0x3D // reserved for production tests - // 0x3E // reserved for production tests - // 0x3F // reserved for production tests - }; - - // MFRC522 commands. Described in chapter 10 of the datasheet. - enum PcdCommand : uint8_t { - PCD_IDLE = 0x00, // no action, cancels current command execution - PCD_MEM = 0x01, // stores 25 uint8_ts into the internal buffer - PCD_GENERATE_RANDOM_ID = 0x02, // generates a 10-uint8_t random ID number - PCD_CALC_CRC = 0x03, // activates the CRC coprocessor or performs a self-test - PCD_TRANSMIT = 0x04, // transmits data from the FIFO buffer - PCD_NO_CMD_CHANGE = 0x07, // no command change, can be used to modify the CommandReg register bits without - // affecting the command, for example, the PowerDown bit - PCD_RECEIVE = 0x08, // activates the receiver circuits - PCD_TRANSCEIVE = - 0x0C, // transmits data from FIFO buffer to antenna and automatically activates the receiver after transmission - PCD_MF_AUTHENT = 0x0E, // performs the MIFARE standard authentication as a reader - PCD_SOFT_RESET = 0x0F // resets the MFRC522 - }; - - // Commands sent to the PICC. - enum PiccCommand : uint8_t { - // The commands used by the PCD to manage communication with several PICCs (ISO 14443-3, Type A, section 6.4) - PICC_CMD_REQA = 0x26, // REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for - // anticollision or selection. 7 bit frame. - PICC_CMD_WUPA = 0x52, // Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to go to READY(*) and - // prepare for anticollision or selection. 7 bit frame. - PICC_CMD_CT = 0x88, // Cascade Tag. Not really a command, but used during anti collision. - PICC_CMD_SEL_CL1 = 0x93, // Anti collision/Select, Cascade Level 1 - PICC_CMD_SEL_CL2 = 0x95, // Anti collision/Select, Cascade Level 2 - PICC_CMD_SEL_CL3 = 0x97, // Anti collision/Select, Cascade Level 3 - PICC_CMD_HLTA = 0x50, // HaLT command, Type A. Instructs an ACTIVE PICC to go to state HALT. - PICC_CMD_RATS = 0xE0, // Request command for Answer To Reset. - // The commands used for MIFARE Classic (from http://www.mouser.com/ds/2/302/MF1S503x-89574.pdf, Section 9) - // Use PCD_MFAuthent to authenticate access to a sector, then use these commands to read/write/modify the blocks on - // the sector. - // The read/write commands can also be used for MIFARE Ultralight. - PICC_CMD_MF_AUTH_KEY_A = 0x60, // Perform authentication with Key A - PICC_CMD_MF_AUTH_KEY_B = 0x61, // Perform authentication with Key B - PICC_CMD_MF_READ = - 0x30, // Reads one 16 uint8_t block from the authenticated sector of the PICC. Also used for MIFARE Ultralight. - PICC_CMD_MF_WRITE = 0xA0, // Writes one 16 uint8_t block to the authenticated sector of the PICC. Called - // "COMPATIBILITY WRITE" for MIFARE Ultralight. - PICC_CMD_MF_DECREMENT = - 0xC0, // Decrements the contents of a block and stores the result in the internal data register. - PICC_CMD_MF_INCREMENT = - 0xC1, // Increments the contents of a block and stores the result in the internal data register. - PICC_CMD_MF_RESTORE = 0xC2, // Reads the contents of a block into the internal data register. - PICC_CMD_MF_TRANSFER = 0xB0, // Writes the contents of the internal data register to a block. - // The commands used for MIFARE Ultralight (from http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf, Section 8.6) - // The PICC_CMD_MF_READ and PICC_CMD_MF_WRITE can also be used for MIFARE Ultralight. - PICC_CMD_UL_WRITE = 0xA2 // Writes one 4 uint8_t page to the PICC. - }; - - // Return codes from the functions in this class. Remember to update GetStatusCodeName() if you add more. - // last value set to 0xff, then compiler uses less ram, it seems some optimisations are triggered - enum StatusCode : uint8_t { - STATUS_OK, // Success - STATUS_ERROR, // Error in communication - STATUS_COLLISION, // Collission detected - STATUS_TIMEOUT, // Timeout in communication. - STATUS_NO_ROOM, // A buffer is not big enough. - STATUS_INTERNAL_ERROR, // Internal error in the code. Should not happen ;-) - STATUS_INVALID, // Invalid argument. - STATUS_CRC_WRONG, // The CRC_A does not match - STATUS_MIFARE_NACK = 0xff // A MIFARE PICC responded with NAK. - }; - - // A struct used for passing the UID of a PICC. - using Uid = struct { - uint8_t size; // Number of uint8_ts in the UID. 4, 7 or 10. - uint8_t uiduint8_t[10]; - uint8_t sak; // The SAK (Select acknowledge) uint8_t returned from the PICC after successful selection. - }; - - Uid uid_; - uint32_t update_wait_{0}; - - void pcd_reset_(); - void initialize_(); - void pcd_antenna_on_(); - uint8_t pcd_read_register_(PcdRegister reg ///< The register to read from. One of the PCD_Register enums. - ); + uint8_t pcd_read_register(PcdRegister reg ///< The register to read from. One of the PCD_Register enums. + ) override; /** * Reads a number of uint8_ts from the specified register in the MFRC522 chip. * The interface is described in the datasheet section 8.1.2. */ - void pcd_read_register_(PcdRegister reg, ///< The register to read from. One of the PCD_Register enums. - uint8_t count, ///< The number of uint8_ts to read - uint8_t *values, ///< uint8_t array to store the values in. - uint8_t rx_align ///< Only bit positions rxAlign..7 in values[0] are updated. - ); - void pcd_write_register_(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. - uint8_t value ///< The value to write. - ); + void pcd_read_register(PcdRegister reg, ///< The register to read from. One of the PCD_Register enums. + uint8_t count, ///< The number of uint8_ts to read + uint8_t *values, ///< uint8_t array to store the values in. + uint8_t rx_align ///< Only bit positions rxAlign..7 in values[0] are updated. + ) override; + void pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. + uint8_t value ///< The value to write. + ) override; /** * Writes a number of uint8_ts to the specified register in the MFRC522 chip. * The interface is described in the datasheet section 8.1.2. */ - void pcd_write_register_(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. - uint8_t count, ///< The number of uint8_ts to write to the register - uint8_t *values ///< The values to write. uint8_t array. - ); - - StatusCode picc_request_a_( - uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in - uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK. - ); - StatusCode picc_reqa_or_wupa_( - uint8_t command, ///< The command to send - PICC_CMD_REQA or PICC_CMD_WUPA - uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in - uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK. - ); - void pcd_set_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums. - uint8_t mask ///< The bits to set. - ); - void pcd_clear_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums. - uint8_t mask ///< The bits to clear. - ); - - StatusCode pcd_transceive_data_(uint8_t *send_data, uint8_t send_len, uint8_t *back_data, uint8_t *back_len, - uint8_t *valid_bits = nullptr, uint8_t rx_align = 0, bool check_crc = false); - StatusCode pcd_communicate_with_picc_(uint8_t command, uint8_t wait_i_rq, uint8_t *send_data, uint8_t send_len, - uint8_t *back_data = nullptr, uint8_t *back_len = nullptr, - uint8_t *valid_bits = nullptr, uint8_t rx_align = 0, bool check_crc = false); - StatusCode pcd_calculate_crc_( - uint8_t *data, ///< In: Pointer to the data to transfer to the FIFO for CRC calculation. - uint8_t length, ///< In: The number of uint8_ts to transfer. - uint8_t *result ///< Out: Pointer to result buffer. Result is written to result[0..1], low uint8_t first. - ); - RC522::StatusCode picc_is_new_card_present_(); - bool picc_read_card_serial_(); - StatusCode picc_select_( - Uid *uid, ///< Pointer to Uid struct. Normally output, but can also be used to supply a known UID. - uint8_t valid_bits = 0 ///< The number of known UID bits supplied in *uid. Normally 0. If set you must also - ///< supply uid->size. - ); - - /** Read a data frame from the RC522 and return the result as a vector. - * - * Note that is_ready needs to be checked first before requesting this method. - * - * On failure, an empty vector is returned. - */ - std::vector r_c522_read_data_(); - - GPIOPin *reset_pin_{nullptr}; - uint8_t reset_count_{0}; - uint32_t reset_timeout_{0}; - bool initialize_pending_{false}; - std::vector binary_sensors_; - std::vector triggers_; - - enum RC522Error { - NONE = 0, - RESET_FAILED, - } error_code_{NONE}; + void pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums. + uint8_t count, ///< The number of uint8_ts to write to the register + uint8_t *values ///< The values to write. uint8_t array. + ) override; }; -class RC522BinarySensor : public binary_sensor::BinarySensor { - public: - void set_uid(const std::vector &uid) { uid_ = uid; } - - bool process(const uint8_t *data, uint8_t len); - - void on_scan_end() { - if (!this->found_) { - this->publish_state(false); - } - this->found_ = false; - } - - protected: - std::vector uid_; - bool found_{false}; -}; - -class RC522Trigger : public Trigger { - public: - void process(const uint8_t *uid, uint8_t uid_length); -}; - -#ifndef MFRC522_SPICLOCK -#define MFRC522_SPICLOCK SPI_CLOCK_DIV4 // MFRC522 accept upto 10MHz -#endif - } // namespace rc522_spi } // namespace esphome diff --git a/tests/test1.yaml b/tests/test1.yaml index 58fc7d4bb2..ef98c10ad5 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -201,7 +201,7 @@ mcp23s08: - id: 'mcp23s08_hub' cs_pin: GPIO12 deviceaddress: 0 - + mcp23s17: - id: 'mcp23s17_hub' cs_pin: GPIO12 @@ -813,7 +813,7 @@ esp32_touch: binary_sensor: - platform: gpio - name: "MCP23S08 Pin #1" + name: 'MCP23S08 Pin #1' pin: mcp23s08: mcp23s08_hub # Use pin number 1 @@ -822,7 +822,7 @@ binary_sensor: mode: INPUT_PULLUP inverted: False - platform: gpio - name: "MCP23S17 Pin #1" + name: 'MCP23S17 Pin #1' pin: mcp23s17: mcp23s17_hub # Use pin number 1 @@ -1391,7 +1391,7 @@ climate: switch: - platform: gpio - name: "MCP23S08 Pin #0" + name: 'MCP23S08 Pin #0' pin: mcp23s08: mcp23s08_hub # Use pin number 0 @@ -1399,7 +1399,7 @@ switch: mode: OUTPUT inverted: False - platform: gpio - name: "MCP23S17 Pin #0" + name: 'MCP23S17 Pin #0' pin: mcp23s17: mcp23s17_hub # Use pin number 0 @@ -1823,6 +1823,12 @@ rc522_spi: - lambda: |- ESP_LOGD("main", "Found tag %s", x.c_str()); +rc522_i2c: + update_interval: 1s + on_tag: + - lambda: |- + ESP_LOGD("main", "Found tag %s", x.c_str()); + gps: time: @@ -1933,7 +1939,7 @@ text_sensor: value: '0' - canbus.send: can_id: 23 - data: [ 0x10, 0x20, 0x30 ] + data: [0x10, 0x20, 0x30] - platform: template name: Template Text Sensor id: template_text @@ -1967,15 +1973,15 @@ canbus: can_id: 4 bit_rate: 50kbps on_frame: - - can_id: 500 - then: - - lambda: |- - std::string b(x.begin(), x.end()); - ESP_LOGD("canid 500", "%s", &b[0] ); - - can_id: 23 - then: - - if: - condition: - lambda: 'return x[0] == 0x11;' - then: - light.toggle: living_room_lights + - can_id: 500 + then: + - lambda: |- + std::string b(x.begin(), x.end()); + ESP_LOGD("canid 500", "%s", &b[0] ); + - can_id: 23 + then: + - if: + condition: + lambda: 'return x[0] == 0x11;' + then: + light.toggle: living_room_lights From 652f6058d10502d40357271238ab74c3d337f97f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=B6sch?= Date: Tue, 12 Jan 2021 19:37:22 +0100 Subject: [PATCH 4/8] make time components polling components (#1443) * make real time clock components polling components * add test --- esphome/components/ds1307/ds1307.cpp | 3 ++- esphome/components/ds1307/ds1307.h | 1 + esphome/components/gps/time/__init__.py | 4 ++-- esphome/components/gps/time/gps_time.h | 4 +--- .../homeassistant/time/homeassistant_time.cpp | 14 +++++--------- .../homeassistant/time/homeassistant_time.h | 1 + esphome/components/sntp/sntp_component.cpp | 1 + esphome/components/sntp/sntp_component.h | 1 + esphome/components/time/__init__.py | 4 ++-- esphome/components/time/real_time_clock.cpp | 2 +- esphome/components/time/real_time_clock.h | 2 +- tests/test1.yaml | 4 ++-- 12 files changed, 20 insertions(+), 21 deletions(-) diff --git a/esphome/components/ds1307/ds1307.cpp b/esphome/components/ds1307/ds1307.cpp index 990767ddd4..599ec16e4f 100644 --- a/esphome/components/ds1307/ds1307.cpp +++ b/esphome/components/ds1307/ds1307.cpp @@ -14,9 +14,10 @@ void DS1307Component::setup() { if (!this->read_rtc_()) { this->mark_failed(); } - this->set_interval(15 * 60 * 1000, [&]() { this->read(); }); } +void DS1307Component::update() { this->read(); } + void DS1307Component::dump_config() { ESP_LOGCONFIG(TAG, "DS1307:"); LOG_I2C_DEVICE(this); diff --git a/esphome/components/ds1307/ds1307.h b/esphome/components/ds1307/ds1307.h index 1f732a7c7d..8e318bf395 100644 --- a/esphome/components/ds1307/ds1307.h +++ b/esphome/components/ds1307/ds1307.h @@ -10,6 +10,7 @@ namespace ds1307 { class DS1307Component : public time::RealTimeClock, public i2c::I2CDevice { public: void setup() override; + void update() override; void dump_config() override; float get_setup_priority() const override; void read(); diff --git a/esphome/components/gps/time/__init__.py b/esphome/components/gps/time/__init__.py index bf746d19b2..421d2e6717 100644 --- a/esphome/components/gps/time/__init__.py +++ b/esphome/components/gps/time/__init__.py @@ -6,12 +6,12 @@ from .. import gps_ns, GPSListener, CONF_GPS_ID, GPS DEPENDENCIES = ['gps'] -GPSTime = gps_ns.class_('GPSTime', time_.RealTimeClock, GPSListener) +GPSTime = gps_ns.class_('GPSTime', cg.PollingComponent, time_.RealTimeClock, GPSListener) CONFIG_SCHEMA = time_.TIME_SCHEMA.extend({ cv.GenerateID(): cv.declare_id(GPSTime), cv.GenerateID(CONF_GPS_ID): cv.use_id(GPS), -}).extend(cv.COMPONENT_SCHEMA) +}).extend(cv.polling_component_schema('5min')) def to_code(config): diff --git a/esphome/components/gps/time/gps_time.h b/esphome/components/gps/time/gps_time.h index f6462be3e0..a1f69a7130 100644 --- a/esphome/components/gps/time/gps_time.h +++ b/esphome/components/gps/time/gps_time.h @@ -9,13 +9,11 @@ namespace gps { class GPSTime : public time::RealTimeClock, public GPSListener { public: + void update() override { this->from_tiny_gps_(this->get_tiny_gps()); }; void on_update(TinyGPSPlus &tiny_gps) override { if (!this->has_time_) this->from_tiny_gps_(tiny_gps); } - void setup() override { - this->set_interval(5 * 60 * 1000, [this]() { this->from_tiny_gps_(this->get_tiny_gps()); }); - } protected: void from_tiny_gps_(TinyGPSPlus &tiny_gps); diff --git a/esphome/components/homeassistant/time/homeassistant_time.cpp b/esphome/components/homeassistant/time/homeassistant_time.cpp index e9d97690fb..9ace8cf67f 100644 --- a/esphome/components/homeassistant/time/homeassistant_time.cpp +++ b/esphome/components/homeassistant/time/homeassistant_time.cpp @@ -10,17 +10,13 @@ void HomeassistantTime::dump_config() { ESP_LOGCONFIG(TAG, "Home Assistant Time:"); ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str()); } -float HomeassistantTime::get_setup_priority() const { return setup_priority::DATA; } -void HomeassistantTime::setup() { - global_homeassistant_time = this; - this->set_interval(15 * 60 * 1000, []() { - // re-request time every 15 minutes - api::global_api_server->request_time(); - }); -} +float HomeassistantTime::get_setup_priority() const { return setup_priority::DATA; } + +void HomeassistantTime::setup() { global_homeassistant_time = this; } + +void HomeassistantTime::update() { api::global_api_server->request_time(); } HomeassistantTime *global_homeassistant_time = nullptr; - } // namespace homeassistant } // namespace esphome diff --git a/esphome/components/homeassistant/time/homeassistant_time.h b/esphome/components/homeassistant/time/homeassistant_time.h index 8ab09d1185..94f4704c2f 100644 --- a/esphome/components/homeassistant/time/homeassistant_time.h +++ b/esphome/components/homeassistant/time/homeassistant_time.h @@ -10,6 +10,7 @@ namespace homeassistant { class HomeassistantTime : public time::RealTimeClock { public: void setup() override; + void update() override; void dump_config() override; void set_epoch_time(uint32_t epoch) { this->synchronize_epoch_(epoch); } float get_setup_priority() const override; diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index 253f3ba2c1..641d66091c 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -42,6 +42,7 @@ void SNTPComponent::dump_config() { ESP_LOGCONFIG(TAG, " Server 3: '%s'", this->server_3_.c_str()); ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str()); } +void SNTPComponent::update() {} void SNTPComponent::loop() { if (this->has_time_) return; diff --git a/esphome/components/sntp/sntp_component.h b/esphome/components/sntp/sntp_component.h index 785f458d6c..4c70a6b09f 100644 --- a/esphome/components/sntp/sntp_component.h +++ b/esphome/components/sntp/sntp_component.h @@ -24,6 +24,7 @@ class SNTPComponent : public time::RealTimeClock { } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } + void update() override; void loop() override; protected: diff --git a/esphome/components/time/__init__.py b/esphome/components/time/__init__.py index 5f071aef11..e5ed5034ab 100644 --- a/esphome/components/time/__init__.py +++ b/esphome/components/time/__init__.py @@ -22,7 +22,7 @@ CODEOWNERS = ['@OttoWinter'] IS_PLATFORM_COMPONENT = True time_ns = cg.esphome_ns.namespace('time') -RealTimeClock = time_ns.class_('RealTimeClock', cg.Component) +RealTimeClock = time_ns.class_('RealTimeClock', cg.PollingComponent) CronTrigger = time_ns.class_('CronTrigger', automation.Trigger.template(), cg.Component) ESPTime = time_ns.struct('ESPTime') TimeHasTimeCondition = time_ns.class_('TimeHasTimeCondition', Condition) @@ -294,7 +294,7 @@ TIME_SCHEMA = cv.Schema({ cv.Optional(CONF_CRON): validate_cron_raw, cv.Optional(CONF_AT): validate_time_at, }, validate_cron_keys), -}) +}).extend(cv.polling_component_schema('15min')) @coroutine diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index cdcfcb14ad..7d7c1013dd 100644 --- a/esphome/components/time/real_time_clock.cpp +++ b/esphome/components/time/real_time_clock.cpp @@ -15,7 +15,7 @@ RealTimeClock::RealTimeClock() = default; void RealTimeClock::call_setup() { setenv("TZ", this->timezone_.c_str(), 1); tzset(); - this->setup(); + PollingComponent::call_setup(); } void RealTimeClock::synchronize_epoch_(uint32_t epoch) { struct timeval timev { diff --git a/esphome/components/time/real_time_clock.h b/esphome/components/time/real_time_clock.h index 8de4509089..880a4e9d5c 100644 --- a/esphome/components/time/real_time_clock.h +++ b/esphome/components/time/real_time_clock.h @@ -106,7 +106,7 @@ struct ESPTime { /// The C library (newlib) available on ESPs only supports TZ strings that specify an offset and DST info; /// you cannot specify zone names or paths to zoneinfo files. /// \see https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html -class RealTimeClock : public Component { +class RealTimeClock : public PollingComponent { public: explicit RealTimeClock(); diff --git a/tests/test1.yaml b/tests/test1.yaml index ef98c10ad5..4598049732 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1843,6 +1843,7 @@ time: then: - lambda: 'ESP_LOGD("main", "time");' - platform: gps + update_interval: 1h on_time: seconds: 0 minutes: /15 @@ -1851,13 +1852,12 @@ time: id: ds1307_time - platform: ds1307 id: ds1307_time + update_interval: never on_time: seconds: 0 then: ds1307.read - - cover: - platform: template name: 'Template Cover' From 28f2582256d030f8b180d3de9178d8d7d685dd40 Mon Sep 17 00:00:00 2001 From: SenexCrenshaw <35600301+SenexCrenshaw@users.noreply.github.com> Date: Wed, 13 Jan 2021 13:33:19 -0500 Subject: [PATCH 5/8] Updated Mcp3008 to support reference_voltage and voltage_sampler::VoltageSampler (#1387) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/mcp3008/mcp3008.cpp | 30 +++++++++++++++++--------- esphome/components/mcp3008/mcp3008.h | 19 +++++++++------- esphome/components/mcp3008/sensor.py | 14 ++++++++---- esphome/components/spi/spi.h | 1 + tests/test1.yaml | 11 ++++++++++ 5 files changed, 53 insertions(+), 22 deletions(-) diff --git a/esphome/components/mcp3008/mcp3008.cpp b/esphome/components/mcp3008/mcp3008.cpp index a4d019ab8f..d09c2e4e92 100644 --- a/esphome/components/mcp3008/mcp3008.cpp +++ b/esphome/components/mcp3008/mcp3008.cpp @@ -15,19 +15,18 @@ void MCP3008::setup() { void MCP3008::dump_config() { ESP_LOGCONFIG(TAG, "MCP3008:"); - LOG_PIN(" CS Pin: ", this->cs_); + LOG_PIN(" CS Pin:", this->cs_); } -float MCP3008::read_data_(uint8_t pin) { - uint8_t data_msb = 0; - uint8_t data_lsb = 0; +float MCP3008::read_data(uint8_t pin) { + uint8_t data_msb, data_lsb = 0; uint8_t command = ((0x01 << 7) | // start bit ((pin & 0x07) << 4)); // channel number this->enable(); - this->transfer_byte(0x01); + data_msb = this->transfer_byte(command) & 0x03; data_lsb = this->transfer_byte(0x00); @@ -35,18 +34,29 @@ float MCP3008::read_data_(uint8_t pin) { int data = data_msb << 8 | data_lsb; - return data / 1024.0f; + return data / 1023.0f; } -MCP3008Sensor::MCP3008Sensor(MCP3008 *parent, std::string name, uint8_t pin) +MCP3008Sensor::MCP3008Sensor(MCP3008 *parent, std::string name, uint8_t pin, float reference_voltage) : PollingComponent(1000), parent_(parent), pin_(pin) { this->set_name(name); + this->reference_voltage_ = reference_voltage; } + +float MCP3008Sensor::get_setup_priority() const { return setup_priority::DATA; } + void MCP3008Sensor::setup() { LOG_SENSOR("", "Setting up MCP3008 Sensor '%s'...", this); } -void MCP3008Sensor::update() { - float value_v = this->parent_->read_data_(pin_); - this->publish_state(value_v); +void MCP3008Sensor::dump_config() { + ESP_LOGCONFIG(TAG, "MCP3008Sensor:"); + ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_); + ESP_LOGCONFIG(TAG, " Reference Voltage: %.2fV", this->reference_voltage_); } +float MCP3008Sensor::sample() { + float value_v = this->parent_->read_data(pin_); + value_v = (value_v * this->reference_voltage_); + return value_v; +} +void MCP3008Sensor::update() { this->publish_state(this->sample()); } } // namespace mcp3008 } // namespace esphome diff --git a/esphome/components/mcp3008/mcp3008.h b/esphome/components/mcp3008/mcp3008.h index 594bdc4204..129f299a14 100644 --- a/esphome/components/mcp3008/mcp3008.h +++ b/esphome/components/mcp3008/mcp3008.h @@ -4,38 +4,41 @@ #include "esphome/core/esphal.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/spi/spi.h" +#include "esphome/components/voltage_sampler/voltage_sampler.h" namespace esphome { namespace mcp3008 { -class MCP3008Sensor; - class MCP3008 : public Component, public spi::SPIDevice { // At 3.3V 2MHz is too fast 1.35MHz is about right + spi::DATA_RATE_75KHZ> { // Running at the slowest max speed supported by the + // mcp3008. 2.7v = 75ksps public: MCP3008() = default; void setup() override; void dump_config() override; float get_setup_priority() const override; + float read_data(uint8_t pin); protected: - float read_data_(uint8_t pin); - - friend class MCP3008Sensor; }; -class MCP3008Sensor : public PollingComponent, public sensor::Sensor { +class MCP3008Sensor : public PollingComponent, public sensor::Sensor, public voltage_sampler::VoltageSampler { public: - MCP3008Sensor(MCP3008 *parent, std::string name, uint8_t pin); + MCP3008Sensor(MCP3008 *parent, std::string name, uint8_t pin, float reference_voltage); + void set_reference_voltage(float reference_voltage) { reference_voltage_ = reference_voltage; } void setup() override; void update() override; + void dump_config() override; + float get_setup_priority() const override; + float sample() override; protected: MCP3008 *parent_; uint8_t pin_; + float reference_voltage_; }; } // namespace mcp3008 diff --git a/esphome/components/mcp3008/sensor.py b/esphome/components/mcp3008/sensor.py index ec7df0429a..9248d51a42 100644 --- a/esphome/components/mcp3008/sensor.py +++ b/esphome/components/mcp3008/sensor.py @@ -1,23 +1,29 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import sensor +from esphome.components import sensor, voltage_sampler from esphome.const import CONF_ID, CONF_NUMBER, CONF_NAME from . import mcp3008_ns, MCP3008 +AUTO_LOAD = ['voltage_sampler'] + DEPENDENCIES = ['mcp3008'] -MCP3008Sensor = mcp3008_ns.class_('MCP3008Sensor', sensor.Sensor, cg.PollingComponent) - +MCP3008Sensor = mcp3008_ns.class_('MCP3008Sensor', sensor.Sensor, cg.PollingComponent, + voltage_sampler.VoltageSampler) +CONF_REFERENCE_VOLTAGE = 'reference_voltage' CONF_MCP3008_ID = 'mcp3008_id' CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend({ cv.GenerateID(): cv.declare_id(MCP3008Sensor), cv.GenerateID(CONF_MCP3008_ID): cv.use_id(MCP3008), cv.Required(CONF_NUMBER): cv.int_, + cv.Optional(CONF_REFERENCE_VOLTAGE, default='3.3V'): cv.voltage, }).extend(cv.polling_component_schema('1s')) def to_code(config): parent = yield cg.get_variable(config[CONF_MCP3008_ID]) - var = cg.new_Pvariable(config[CONF_ID], parent, config[CONF_NAME], config[CONF_NUMBER]) + var = cg.new_Pvariable(config[CONF_ID], parent, config[CONF_NAME], + config[CONF_NUMBER], config[CONF_REFERENCE_VOLTAGE]) yield cg.register_component(var, config) + yield sensor.register_sensor(var, config) diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 38ed9fb1d4..64364d70c9 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -50,6 +50,7 @@ enum SPIClockPhase { */ enum SPIDataRate : uint32_t { DATA_RATE_1KHZ = 1000, + DATA_RATE_75KHZ = 75000, DATA_RATE_200KHZ = 200000, DATA_RATE_1MHZ = 1000000, DATA_RATE_2MHZ = 2000000, diff --git a/tests/test1.yaml b/tests/test1.yaml index 4598049732..f2ebca05ab 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -197,6 +197,10 @@ wled: adalight: +mcp3008: + - id: 'mcp3008_hub' + cs_pin: GPIO12 + mcp23s08: - id: 'mcp23s08_hub' cs_pin: GPIO12 @@ -207,6 +211,7 @@ mcp23s17: cs_pin: GPIO12 deviceaddress: 1 + sensor: - platform: adc pin: A0 @@ -801,6 +806,12 @@ sensor: id: ph_ezo address: 99 unit_of_measurement: 'pH' + - platform: mcp3008 + update_interval: 5s + mcp3008_id: 'mcp3008_hub' + id: freezer_temp_source + reference_voltage: 3.19 + number: 0 esp32_touch: setup_mode: False From 52e13164b43f7241326f0a9e3004c5a2a6dee822 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 15 Jan 2021 09:29:55 +1300 Subject: [PATCH 6/8] Add NDEF reading and writing to PN532 (#1351) --- CODEOWNERS | 1 + esphome/components/nfc/__init__.py | 7 + esphome/components/nfc/ndef_message.cpp | 106 ++++++++ esphome/components/nfc/ndef_message.h | 31 +++ esphome/components/nfc/ndef_record.cpp | 85 ++++++ esphome/components/nfc/ndef_record.h | 101 +++++++ esphome/components/nfc/nfc.cpp | 107 ++++++++ esphome/components/nfc/nfc.h | 57 ++++ esphome/components/nfc/nfc_tag.cpp | 9 + esphome/components/nfc/nfc_tag.h | 47 ++++ esphome/components/pn532/__init__.py | 34 ++- esphome/components/pn532/pn532.cpp | 179 +++++++++---- esphome/components/pn532/pn532.h | 66 ++++- .../components/pn532/pn532_mifare_classic.cpp | 249 ++++++++++++++++++ .../pn532/pn532_mifare_ultralight.cpp | 180 +++++++++++++ esphome/components/pn532_i2c/pn532_i2c.cpp | 2 +- 16 files changed, 1206 insertions(+), 55 deletions(-) create mode 100644 esphome/components/nfc/__init__.py create mode 100644 esphome/components/nfc/ndef_message.cpp create mode 100644 esphome/components/nfc/ndef_message.h create mode 100644 esphome/components/nfc/ndef_record.cpp create mode 100644 esphome/components/nfc/ndef_record.h create mode 100644 esphome/components/nfc/nfc.cpp create mode 100644 esphome/components/nfc/nfc.h create mode 100644 esphome/components/nfc/nfc_tag.cpp create mode 100644 esphome/components/nfc/nfc_tag.h create mode 100644 esphome/components/pn532/pn532_mifare_classic.cpp create mode 100644 esphome/components/pn532/pn532_mifare_ultralight.cpp diff --git a/CODEOWNERS b/CODEOWNERS index fb287b1cc9..5cf0f591d2 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -48,6 +48,7 @@ esphome/components/mcp23s17/* @SenexCrenshaw esphome/components/mcp2515/* @danielschramm @mvturnho esphome/components/mcp9808/* @k7hpn esphome/components/network/* @esphome/core +esphome/components/nfc/* @jesserockz esphome/components/ota/* @esphome/core esphome/components/output/* @esphome/core esphome/components/pid/* @OttoWinter diff --git a/esphome/components/nfc/__init__.py b/esphome/components/nfc/__init__.py new file mode 100644 index 0000000000..ae3c9a4c0a --- /dev/null +++ b/esphome/components/nfc/__init__.py @@ -0,0 +1,7 @@ +import esphome.codegen as cg + +CODEOWNERS = ['@jesserockz'] + +nfc_ns = cg.esphome_ns.namespace('nfc') + +NfcTag = nfc_ns.class_('NfcTag') diff --git a/esphome/components/nfc/ndef_message.cpp b/esphome/components/nfc/ndef_message.cpp new file mode 100644 index 0000000000..caba833932 --- /dev/null +++ b/esphome/components/nfc/ndef_message.cpp @@ -0,0 +1,106 @@ +#include "ndef_message.h" + +namespace esphome { +namespace nfc { + +static const char *TAG = "nfc.ndef_message"; + +NdefMessage::NdefMessage(std::vector &data) { + ESP_LOGV(TAG, "Building NdefMessage with %zu bytes", data.size()); + uint8_t index = 0; + while (index <= data.size()) { + uint8_t tnf_byte = data[index++]; + bool me = tnf_byte & 0x40; + bool sr = tnf_byte & 0x10; + bool il = tnf_byte & 0x08; + uint8_t tnf = tnf_byte & 0x07; + + ESP_LOGVV(TAG, "me=%s, sr=%s, il=%s, tnf=%d", YESNO(me), YESNO(sr), YESNO(il), tnf); + + auto record = new NdefRecord(); + record->set_tnf(tnf); + + uint8_t type_length = data[index++]; + uint32_t payload_length = 0; + if (sr) { + payload_length = data[index++]; + } else { + payload_length = (static_cast(data[index]) << 24) | (static_cast(data[index + 1]) << 16) | + (static_cast(data[index + 2]) << 8) | static_cast(data[index + 3]); + index += 4; + } + + uint8_t id_length = 0; + if (il) { + id_length = data[index++]; + } + + ESP_LOGVV(TAG, "Lengths: type=%d, payload=%d, id=%d", type_length, payload_length, id_length); + + std::string type_str(data.begin() + index, data.begin() + index + type_length); + record->set_type(type_str); + index += type_length; + + if (il) { + std::string id_str(data.begin() + index, data.begin() + index + id_length); + record->set_id(id_str); + index += id_length; + } + + uint8_t payload_identifier = 0x00; + if (type_str == "U") { + payload_identifier = data[index++]; + payload_length -= 1; + } + + std::string payload_str(data.begin() + index, data.begin() + index + payload_length); + + if (payload_identifier > 0x00 && payload_identifier <= PAYLOAD_IDENTIFIERS_COUNT) { + payload_str.insert(0, PAYLOAD_IDENTIFIERS[payload_identifier]); + } + + record->set_payload(payload_str); + index += payload_length; + + this->add_record(record); + ESP_LOGV(TAG, "Adding record type %s = %s", record->get_type().c_str(), record->get_payload().c_str()); + + if (me) + break; + } +} + +bool NdefMessage::add_record(NdefRecord *record) { + if (this->records_.size() >= MAX_NDEF_RECORDS) { + ESP_LOGE(TAG, "Too many records. Max: %d", MAX_NDEF_RECORDS); + return false; + } + this->records_.push_back(record); + return true; +} + +bool NdefMessage::add_text_record(const std::string &text) { return this->add_text_record(text, "en"); }; + +bool NdefMessage::add_text_record(const std::string &text, const std::string &encoding) { + std::string payload = to_string(text.length()) + encoding + text; + auto r = new NdefRecord(TNF_WELL_KNOWN, "T", payload); + return this->add_record(r); +} + +bool NdefMessage::add_uri_record(const std::string &uri) { + auto r = new NdefRecord(TNF_WELL_KNOWN, "U", uri); + return this->add_record(r); +} + +std::vector NdefMessage::encode() { + std::vector data; + + for (uint8_t i = 0; i < this->records_.size(); i++) { + auto encoded_record = this->records_[i]->encode(i == 0, (i + 1) == this->records_.size()); + data.insert(data.end(), encoded_record.begin(), encoded_record.end()); + } + return data; +} + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/ndef_message.h b/esphome/components/nfc/ndef_message.h new file mode 100644 index 0000000000..e47a7b992a --- /dev/null +++ b/esphome/components/nfc/ndef_message.h @@ -0,0 +1,31 @@ +#pragma once + +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "ndef_record.h" + +namespace esphome { +namespace nfc { + +static const uint8_t MAX_NDEF_RECORDS = 4; + +class NdefMessage { + public: + NdefMessage(){}; + NdefMessage(std::vector &data); + + std::vector get_records() { return this->records_; }; + + bool add_record(NdefRecord *record); + bool add_text_record(const std::string &text); + bool add_text_record(const std::string &text, const std::string &encoding); + bool add_uri_record(const std::string &uri); + + std::vector encode(); + + protected: + std::vector records_; +}; + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/ndef_record.cpp b/esphome/components/nfc/ndef_record.cpp new file mode 100644 index 0000000000..53fa221e41 --- /dev/null +++ b/esphome/components/nfc/ndef_record.cpp @@ -0,0 +1,85 @@ +#include "ndef_record.h" + +namespace esphome { +namespace nfc { + +static const char* TAG = "nfc.ndef_record"; + +uint32_t NdefRecord::get_encoded_size() { + uint32_t size = 2; + if (this->payload_.length() > 255) { + size += 4; + } else { + size += 1; + } + if (this->id_.length()) { + size += 1; + } + size += (this->type_.length() + this->payload_.length() + this->id_.length()); + return size; +} + +std::vector NdefRecord::encode(bool first, bool last) { + std::vector data; + + data.push_back(this->get_tnf_byte(first, last)); + + data.push_back(this->type_.length()); + + uint8_t payload_prefix = 0x00; + uint8_t payload_prefix_length = 0x00; + for (uint8_t i = 1; i < PAYLOAD_IDENTIFIERS_COUNT; i++) { + std::string prefix = PAYLOAD_IDENTIFIERS[i]; + if (this->payload_.substr(0, prefix.length()).find(prefix) != std::string::npos) { + payload_prefix = i; + payload_prefix_length = prefix.length(); + break; + } + } + + uint32_t payload_length = this->payload_.length() - payload_prefix_length + 1; + + if (payload_length <= 255) { + data.push_back(payload_length); + } else { + data.push_back(0); + data.push_back(0); + data.push_back((payload_length >> 8) & 0xFF); + data.push_back(payload_length & 0xFF); + } + + if (this->id_.length()) { + data.push_back(this->id_.length()); + } + + data.insert(data.end(), this->type_.begin(), this->type_.end()); + + if (this->id_.length()) { + data.insert(data.end(), this->id_.begin(), this->id_.end()); + } + + data.push_back(payload_prefix); + + data.insert(data.end(), this->payload_.begin() + payload_prefix_length, this->payload_.end()); + return data; +} + +uint8_t NdefRecord::get_tnf_byte(bool first, bool last) { + uint8_t value = this->tnf_; + if (first) { + value = value | 0x80; + } + if (last) { + value = value | 0x40; + } + if (this->payload_.length() <= 255) { + value = value | 0x10; + } + if (this->id_.length()) { + value = value | 0x08; + } + return value; +}; + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/ndef_record.h b/esphome/components/nfc/ndef_record.h new file mode 100644 index 0000000000..bc03ccf093 --- /dev/null +++ b/esphome/components/nfc/ndef_record.h @@ -0,0 +1,101 @@ +#pragma once + +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace nfc { + +static const uint8_t TNF_EMPTY = 0x00; +static const uint8_t TNF_WELL_KNOWN = 0x01; +static const uint8_t TNF_MIME_MEDIA = 0x02; +static const uint8_t TNF_ABSOLUTE_URI = 0x03; +static const uint8_t TNF_EXTERNAL_TYPE = 0x04; +static const uint8_t TNF_UNKNOWN = 0x05; +static const uint8_t TNF_UNCHANGED = 0x06; +static const uint8_t TNF_RESERVED = 0x07; + +static const uint8_t PAYLOAD_IDENTIFIERS_COUNT = 0x23; +static const char *PAYLOAD_IDENTIFIERS[] = {"", + "http://www.", + "https://www.", + "http://", + "https://", + "tel:", + "mailto:", + "ftp://anonymous:anonymous@", + "ftp://ftp.", + "ftps://", + "sftp://", + "smb://", + "nfs://", + "ftp://", + "dav://", + "news:", + "telnet://", + "imap:", + "rtsp://", + "urn:", + "pop:", + "sip:", + "sips:", + "tftp:", + "btspp://", + "btl2cap://", + "btgoep://", + "tcpobex://", + "irdaobex://", + "file://", + "urn:epc:id:", + "urn:epc:tag:", + "urn:epc:pat:", + "urn:epc:raw:", + "urn:epc:", + "urn:nfc:"}; + +class NdefRecord { + public: + NdefRecord(){}; + NdefRecord(uint8_t tnf, const std::string &type, const std::string &payload) { + this->tnf_ = tnf; + this->type_ = type; + this->set_payload(payload); + }; + NdefRecord(uint8_t tnf, const std::string &type, const std::string &payload, const std::string &id) { + this->tnf_ = tnf; + this->type_ = type; + this->set_payload(payload); + this->id_ = id; + }; + NdefRecord(const NdefRecord &rhs) { + this->tnf_ = rhs.tnf_; + this->type_ = rhs.type_; + this->payload_ = rhs.payload_; + this->payload_identifier_ = rhs.payload_identifier_; + this->id_ = rhs.id_; + }; + void set_tnf(uint8_t tnf) { this->tnf_ = tnf; }; + void set_type(const std::string &type) { this->type_ = type; }; + void set_payload_identifier(uint8_t payload_identifier) { this->payload_identifier_ = payload_identifier; }; + void set_payload(const std::string &payload) { this->payload_ = payload; }; + void set_id(const std::string &id) { this->id_ = id; }; + + uint32_t get_encoded_size(); + + std::vector encode(bool first, bool last); + uint8_t get_tnf_byte(bool first, bool last); + + const std::string &get_type() { return this->type_; }; + const std::string &get_id() { return this->id_; }; + const std::string &get_payload() { return this->payload_; }; + + protected: + uint8_t tnf_; + std::string type_; + uint8_t payload_identifier_; + std::string payload_; + std::string id_; +}; + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/nfc.cpp b/esphome/components/nfc/nfc.cpp new file mode 100644 index 0000000000..d1f7cbe15f --- /dev/null +++ b/esphome/components/nfc/nfc.cpp @@ -0,0 +1,107 @@ +#include "nfc.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nfc { + +static const char *TAG = "nfc"; + +std::string format_uid(std::vector &uid) { + char buf[(uid.size() * 2) + uid.size() - 1]; + int offset = 0; + for (uint8_t i = 0; i < uid.size(); i++) { + const char *format = "%02X"; + if (i + 1 < uid.size()) + format = "%02X-"; + offset += sprintf(buf + offset, format, uid[i]); + } + return std::string(buf); +} + +std::string format_bytes(std::vector &bytes) { + char buf[(bytes.size() * 2) + bytes.size() - 1]; + int offset = 0; + for (uint8_t i = 0; i < bytes.size(); i++) { + const char *format = "%02X"; + if (i + 1 < bytes.size()) + format = "%02X "; + offset += sprintf(buf + offset, format, bytes[i]); + } + return std::string(buf); +} + +uint8_t guess_tag_type(uint8_t uid_length) { + if (uid_length == 4) { + return TAG_TYPE_MIFARE_CLASSIC; + } else { + return TAG_TYPE_2; + } +} + +uint8_t get_mifare_classic_ndef_start_index(std::vector &data) { + for (uint8_t i = 0; i < MIFARE_CLASSIC_BLOCK_SIZE; i++) { + if (data[i] == 0x00) { + // Do nothing, skip + } else if (data[i] == 0x03) { + return i; + } else { + return -2; + } + } + return -1; +} + +bool decode_mifare_classic_tlv(std::vector &data, uint32_t &message_length, uint8_t &message_start_index) { + uint8_t i = get_mifare_classic_ndef_start_index(data); + if (i < 0 || data[i] != 0x03) { + ESP_LOGE(TAG, "Error, Can't decode message length."); + return false; + } + if (data[i + 1] == 0xFF) { + message_length = ((0xFF & data[i + 2]) << 8) | (0xFF & data[i + 3]); + message_start_index = i + MIFARE_CLASSIC_LONG_TLV_SIZE; + } else { + message_length = data[i + 1]; + message_start_index = i + MIFARE_CLASSIC_SHORT_TLV_SIZE; + } + return true; +} + +uint32_t get_mifare_ultralight_buffer_size(uint32_t message_length) { + uint32_t buffer_size = message_length + 2 + 1; + if (buffer_size % MIFARE_ULTRALIGHT_READ_SIZE != 0) + buffer_size = ((buffer_size / MIFARE_ULTRALIGHT_READ_SIZE) + 1) * MIFARE_ULTRALIGHT_READ_SIZE; + return buffer_size; +} + +uint32_t get_mifare_classic_buffer_size(uint32_t message_length) { + uint32_t buffer_size = message_length; + if (message_length < 255) { + buffer_size += MIFARE_CLASSIC_SHORT_TLV_SIZE + 1; + } else { + buffer_size += MIFARE_CLASSIC_LONG_TLV_SIZE + 1; + } + if (buffer_size % MIFARE_CLASSIC_BLOCK_SIZE != 0) { + buffer_size = ((buffer_size / MIFARE_CLASSIC_BLOCK_SIZE) + 1) * MIFARE_CLASSIC_BLOCK_SIZE; + } + return buffer_size; +} + +bool mifare_classic_is_first_block(uint8_t block_num) { + if (block_num < 128) { + return (block_num % 4 == 0); + } else { + return (block_num % 16 == 0); + } +} + +bool mifare_classic_is_trailer_block(uint8_t block_num) { + if (block_num < 128) { + return ((block_num + 1) % 4 == 0); + } else { + return ((block_num + 1) % 16 == 0); + } +} + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/nfc.h b/esphome/components/nfc/nfc.h new file mode 100644 index 0000000000..cf56f9f4de --- /dev/null +++ b/esphome/components/nfc/nfc.h @@ -0,0 +1,57 @@ +#pragma once + +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" +#include "ndef_record.h" +#include "ndef_message.h" +#include "nfc_tag.h" + +namespace esphome { +namespace nfc { + +static const uint8_t MIFARE_CLASSIC_BLOCK_SIZE = 16; +static const uint8_t MIFARE_CLASSIC_LONG_TLV_SIZE = 4; +static const uint8_t MIFARE_CLASSIC_SHORT_TLV_SIZE = 2; + +static const uint8_t MIFARE_ULTRALIGHT_PAGE_SIZE = 4; +static const uint8_t MIFARE_ULTRALIGHT_READ_SIZE = 4; +static const uint8_t MIFARE_ULTRALIGHT_DATA_START_PAGE = 4; +static const uint8_t MIFARE_ULTRALIGHT_MAX_PAGE = 63; + +static const uint8_t TAG_TYPE_MIFARE_CLASSIC = 0; +static const uint8_t TAG_TYPE_1 = 1; +static const uint8_t TAG_TYPE_2 = 2; +static const uint8_t TAG_TYPE_3 = 3; +static const uint8_t TAG_TYPE_4 = 4; +static const uint8_t TAG_TYPE_UNKNOWN = 99; + +// Mifare Commands +static const uint8_t MIFARE_CMD_AUTH_A = 0x60; +static const uint8_t MIFARE_CMD_AUTH_B = 0x61; +static const uint8_t MIFARE_CMD_READ = 0x30; +static const uint8_t MIFARE_CMD_WRITE = 0xA0; +static const uint8_t MIFARE_CMD_WRITE_ULTRALIGHT = 0xA2; + +static const char *MIFARE_CLASSIC = "Mifare Classic"; +static const char *NFC_FORUM_TYPE_2 = "NFC Forum Type 2"; +static const char *ERROR = "Error"; + +static const uint8_t DEFAULT_KEY[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; +static const uint8_t NDEF_KEY[6] = {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7}; +static const uint8_t MAD_KEY[6] = {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5}; + +std::string format_uid(std::vector &uid); +std::string format_bytes(std::vector &bytes); + +uint8_t guess_tag_type(uint8_t uid_length); +uint8_t get_mifare_classic_ndef_start_index(std::vector &data); +bool decode_mifare_classic_tlv(std::vector &data, uint32_t &message_length, uint8_t &message_start_index); +uint32_t get_mifare_classic_buffer_size(uint32_t message_length); + +bool mifare_classic_is_first_block(uint8_t block_num); +bool mifare_classic_is_trailer_block(uint8_t block_num); + +uint32_t get_mifare_ultralight_buffer_size(uint32_t message_length); + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/nfc_tag.cpp b/esphome/components/nfc/nfc_tag.cpp new file mode 100644 index 0000000000..39e84e2032 --- /dev/null +++ b/esphome/components/nfc/nfc_tag.cpp @@ -0,0 +1,9 @@ +#include "nfc_tag.h" + +namespace esphome { +namespace nfc { + +static const char *TAG = "nfc.tag"; + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/nfc_tag.h b/esphome/components/nfc/nfc_tag.h new file mode 100644 index 0000000000..ff0d1c39b4 --- /dev/null +++ b/esphome/components/nfc/nfc_tag.h @@ -0,0 +1,47 @@ +#pragma once + +#include "esphome/core/log.h" +#include "ndef_message.h" + +namespace esphome { +namespace nfc { + +class NfcTag { + public: + NfcTag() { + this->uid_ = {}; + this->tag_type_ = "Unknown"; + }; + NfcTag(std::vector &uid) { + this->uid_ = uid; + this->tag_type_ = "Unknown"; + }; + NfcTag(std::vector &uid, const std::string &tag_type) { + this->uid_ = uid; + this->tag_type_ = tag_type; + }; + NfcTag(std::vector &uid, const std::string &tag_type, nfc::NdefMessage *ndef_message) { + this->uid_ = uid; + this->tag_type_ = tag_type; + this->ndef_message_ = ndef_message; + }; + NfcTag(std::vector &uid, const std::string &tag_type, std::vector &ndef_data) { + this->uid_ = uid; + this->tag_type_ = tag_type; + this->ndef_message_ = new NdefMessage(ndef_data); + }; + + std::vector &get_uid() { return this->uid_; }; + const std::string &get_tag_type() { return this->tag_type_; }; + bool has_ndef_message() { return this->ndef_message_ != nullptr; }; + NdefMessage *get_ndef_message() { return this->ndef_message_; }; + void set_ndef_message(NdefMessage *ndef_message) { this->ndef_message_ = ndef_message; }; + + protected: + std::vector uid_; + std::string tag_type_; + NdefMessage *ndef_message_{nullptr}; +}; + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/pn532/__init__.py b/esphome/components/pn532/__init__.py index 02215e2f5a..c96ebe2b4d 100644 --- a/esphome/components/pn532/__init__.py +++ b/esphome/components/pn532/__init__.py @@ -1,24 +1,34 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation -from esphome.const import CONF_ON_TAG, CONF_TRIGGER_ID +from esphome.components import nfc +from esphome.const import CONF_ID, CONF_ON_TAG, CONF_TRIGGER_ID from esphome.core import coroutine CODEOWNERS = ['@OttoWinter', '@jesserockz'] -AUTO_LOAD = ['binary_sensor'] +AUTO_LOAD = ['binary_sensor', 'nfc'] MULTI_CONF = True CONF_PN532_ID = 'pn532_id' +CONF_ON_FINISHED_WRITE = 'on_finished_write' pn532_ns = cg.esphome_ns.namespace('pn532') PN532 = pn532_ns.class_('PN532', cg.PollingComponent) -PN532Trigger = pn532_ns.class_('PN532Trigger', automation.Trigger.template(cg.std_string)) +PN532OnTagTrigger = pn532_ns.class_('PN532OnTagTrigger', + automation.Trigger.template(cg.std_string, nfc.NfcTag)) +PN532OnFinishedWriteTrigger = pn532_ns.class_('PN532OnFinishedWriteTrigger', + automation.Trigger.template()) + +PN532IsWritingCondition = pn532_ns.class_('PN532IsWritingCondition', automation.Condition) PN532_SCHEMA = cv.Schema({ cv.GenerateID(): cv.declare_id(PN532), cv.Optional(CONF_ON_TAG): automation.validate_automation({ - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PN532Trigger), + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PN532OnTagTrigger), + }), + cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation({ + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PN532OnFinishedWriteTrigger), }), }).extend(cv.polling_component_schema('1s')) @@ -36,4 +46,18 @@ def setup_pn532(var, config): 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) + yield automation.build_automation(trigger, [(cg.std_string, 'x'), (nfc.NfcTag, 'tag')], + conf) + + for conf in config.get(CONF_ON_FINISHED_WRITE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + yield automation.build_automation(trigger, [], conf) + + +@automation.register_condition('pn532.is_writing', PN532IsWritingCondition, cv.Schema({ + cv.GenerateID(): cv.use_id(PN532), +})) +def pn532_is_writing_to_code(config, condition_id, template_arg, args): + var = cg.new_Pvariable(condition_id, template_arg) + yield cg.register_parented(var, config[CONF_ID]) + yield var diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index a9601185bb..e2511648b6 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -11,18 +11,6 @@ namespace pn532 { static const char *TAG = "pn532"; -std::string format_uid(std::vector &uid) { - char buf[32]; - int offset = 0; - for (uint8_t i = 0; i < uid.size(); i++) { - const char *format = "%02X"; - if (i + 1 < uid.size()) - format = "%02X-"; - offset += sprintf(buf + offset, format, uid[i]); - } - return std::string(buf); -} - void PN532::setup() { ESP_LOGCONFIG(TAG, "Setting up PN532..."); @@ -152,23 +140,56 @@ void PN532::loop() { this->current_uid_ = nfcid; - for (auto *trigger : this->triggers_) - trigger->process(nfcid); + if (next_task_ == READ) { + auto tag = this->read_tag_(nfcid); + for (auto *trigger : this->triggers_) + trigger->process(tag); - if (report) { - ESP_LOGD(TAG, "Found new tag '%s'", format_uid(nfcid).c_str()); + if (report) { + ESP_LOGD(TAG, "Found new tag '%s'", nfc::format_uid(nfcid).c_str()); + if (tag->has_ndef_message()) { + auto message = tag->get_ndef_message(); + auto records = message->get_records(); + ESP_LOGD(TAG, " NDEF formatted records:"); + for (auto &record : records) { + ESP_LOGD(TAG, " %s - %s", record->get_type().c_str(), record->get_payload().c_str()); + } + } + } + } else if (next_task_ == CLEAN) { + ESP_LOGD(TAG, " Tag cleaning..."); + if (!this->clean_tag_(nfcid)) { + ESP_LOGE(TAG, " Tag was not fully cleaned successfully"); + } + ESP_LOGD(TAG, " Tag cleaned!"); + } else if (next_task_ == FORMAT) { + ESP_LOGD(TAG, " Tag formatting..."); + if (!this->format_tag_(nfcid)) { + ESP_LOGE(TAG, "Error formatting tag as NDEF"); + } + ESP_LOGD(TAG, " Tag formatted!"); + } else if (next_task_ == WRITE) { + if (this->next_task_message_to_write_ != nullptr) { + ESP_LOGD(TAG, " Tag writing..."); + ESP_LOGD(TAG, " Tag formatting..."); + if (!this->format_tag_(nfcid)) { + ESP_LOGE(TAG, " Tag could not be formatted for writing"); + } else { + ESP_LOGD(TAG, " Writing NDEF data"); + if (!this->write_tag_(nfcid, this->next_task_message_to_write_)) { + ESP_LOGE(TAG, " Failed to write message to tag"); + } + ESP_LOGD(TAG, " Finished writing NDEF data"); + delete this->next_task_message_to_write_; + this->next_task_message_to_write_ = nullptr; + this->on_finished_write_callback_.call(); + } + } } - this->turn_off_rf_(); -} + this->read_mode(); -void PN532::turn_off_rf_() { - ESP_LOGVV(TAG, "Turning RF field OFF"); - this->write_command_({ - PN532_COMMAND_RFCONFIGURATION, - 0x1, // RF Field - 0x0 // Off - }); + this->turn_off_rf_(); } bool PN532::write_command_(const std::vector &data) { @@ -208,6 +229,22 @@ bool PN532::write_command_(const std::vector &data) { return this->read_ack_(); } +bool PN532::read_ack_() { + ESP_LOGVV(TAG, "Reading ACK..."); + + std::vector data; + if (!this->read_data(data, 6)) { + return false; + } + + bool matches = (data[1] == 0x00 && // preamble + data[2] == 0x00 && // start of packet + data[3] == 0xFF && data[4] == 0x00 && // ACK packet code + data[5] == 0xFF && data[6] == 0x00); // postamble + ESP_LOGVV(TAG, "ACK valid: %s", YESNO(matches)); + return matches; +} + bool PN532::read_response_(uint8_t command, std::vector &data) { ESP_LOGV(TAG, "Reading response"); uint8_t len = this->read_response_length_(); @@ -258,13 +295,6 @@ bool PN532::read_response_(uint8_t command, std::vector &data) { data.erase(data.begin(), data.begin() + 2); // Remove TFI and command code data.erase(data.end() - 2, data.end()); // Remove checksum and postamble -#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE - ESP_LOGD(TAG, "PN532 Data Frame: (%u)", data.size()); // NOLINT - for (uint8_t dat : data) { - ESP_LOGD(TAG, " 0x%02X", dat); - } -#endif - return true; } @@ -299,20 +329,81 @@ uint8_t PN532::read_response_length_() { return len; } -bool PN532::read_ack_() { - ESP_LOGVV(TAG, "Reading ACK..."); +void PN532::turn_off_rf_() { + ESP_LOGVV(TAG, "Turning RF field OFF"); + this->write_command_({ + PN532_COMMAND_RFCONFIGURATION, + 0x01, // RF Field + 0x00, // Off + }); +} - std::vector data; - if (!this->read_data(data, 6)) { - return false; +nfc::NfcTag *PN532::read_tag_(std::vector &uid) { + uint8_t type = nfc::guess_tag_type(uid.size()); + + if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) { + ESP_LOGD(TAG, "Mifare classic"); + return this->read_mifare_classic_tag_(uid); + } else if (type == nfc::TAG_TYPE_2) { + ESP_LOGD(TAG, "Mifare ultralight"); + return this->read_mifare_ultralight_tag_(uid); + } else if (type == nfc::TAG_TYPE_UNKNOWN) { + ESP_LOGV(TAG, "Cannot determine tag type"); + return new nfc::NfcTag(uid); + } else { + return new nfc::NfcTag(uid); } +} - bool matches = (data[1] == 0x00 && // preamble - data[2] == 0x00 && // start of packet - data[3] == 0xFF && data[4] == 0x00 && // ACK packet code - data[5] == 0xFF && data[6] == 0x00); // postamble - ESP_LOGVV(TAG, "ACK valid: %s", YESNO(matches)); - return matches; +void PN532::read_mode() { + this->next_task_ = READ; + ESP_LOGD(TAG, "Waiting to read next tag"); +} +void PN532::clean_mode() { + this->next_task_ = CLEAN; + ESP_LOGD(TAG, "Waiting to clean next tag"); +} +void PN532::format_mode() { + this->next_task_ = FORMAT; + ESP_LOGD(TAG, "Waiting to format next tag"); +} +void PN532::write_mode(nfc::NdefMessage *message) { + this->next_task_ = WRITE; + this->next_task_message_to_write_ = message; + ESP_LOGD(TAG, "Waiting to write next tag"); +} + +bool PN532::clean_tag_(std::vector &uid) { + uint8_t type = nfc::guess_tag_type(uid.size()); + if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) { + return this->format_mifare_classic_mifare_(uid); + } else if (type == nfc::TAG_TYPE_2) { + return this->clean_mifare_ultralight_(); + } + ESP_LOGE(TAG, "Unsupported Tag for formatting"); + return false; +} + +bool PN532::format_tag_(std::vector &uid) { + uint8_t type = nfc::guess_tag_type(uid.size()); + if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) { + return this->format_mifare_classic_ndef_(uid); + } else if (type == nfc::TAG_TYPE_2) { + return this->clean_mifare_ultralight_(); + } + ESP_LOGE(TAG, "Unsupported Tag for formatting"); + return false; +} + +bool PN532::write_tag_(std::vector &uid, nfc::NdefMessage *message) { + uint8_t type = nfc::guess_tag_type(uid.size()); + if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) { + return this->write_mifare_classic_tag_(uid, message); + } else if (type == nfc::TAG_TYPE_2) { + return this->write_mifare_ultralight_tag_(uid, message); + } + ESP_LOGE(TAG, "Unsupported Tag for formatting"); + return false; } float PN532::get_setup_priority() const { return setup_priority::DATA; } @@ -350,7 +441,7 @@ bool PN532BinarySensor::process(std::vector &data) { this->found_ = true; return true; } -void PN532Trigger::process(std::vector &data) { this->trigger(format_uid(data)); } +void PN532OnTagTrigger::process(nfc::NfcTag *tag) { this->trigger(nfc::format_uid(tag->get_uid()), *tag); } } // namespace pn532 } // namespace esphome diff --git a/esphome/components/pn532/pn532.h b/esphome/components/pn532/pn532.h index af49a02400..6b3bef2918 100644 --- a/esphome/components/pn532/pn532.h +++ b/esphome/components/pn532/pn532.h @@ -3,6 +3,8 @@ #include "esphome/core/component.h" #include "esphome/core/automation.h" #include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/nfc/nfc_tag.h" +#include "esphome/components/nfc/nfc.h" namespace esphome { namespace pn532 { @@ -14,7 +16,7 @@ static const uint8_t PN532_COMMAND_INDATAEXCHANGE = 0x40; static const uint8_t PN532_COMMAND_INLISTPASSIVETARGET = 0x4A; class PN532BinarySensor; -class PN532Trigger; +class PN532OnTagTrigger; class PN532 : public PollingComponent { public: @@ -28,7 +30,18 @@ class PN532 : public PollingComponent { void loop() override; void register_tag(PN532BinarySensor *tag) { this->binary_sensors_.push_back(tag); } - void register_trigger(PN532Trigger *trig) { this->triggers_.push_back(trig); } + void register_trigger(PN532OnTagTrigger *trig) { this->triggers_.push_back(trig); } + + void add_on_finished_write_callback(std::function callback) { + this->on_finished_write_callback_.add(std::move(callback)); + } + + bool is_writing() { return this->next_task_ != READ; }; + + void read_mode(); + void clean_mode(); + void format_mode(); + void write_mode(nfc::NdefMessage *message); protected: void turn_off_rf_(); @@ -40,15 +53,46 @@ class PN532 : public PollingComponent { virtual bool write_data(const std::vector &data) = 0; virtual bool read_data(std::vector &data, uint8_t len) = 0; + nfc::NfcTag *read_tag_(std::vector &uid); + + bool format_tag_(std::vector &uid); + bool clean_tag_(std::vector &uid); + bool write_tag_(std::vector &uid, nfc::NdefMessage *message); + + nfc::NfcTag *read_mifare_classic_tag_(std::vector &uid); + bool read_mifare_classic_block_(uint8_t block_num, std::vector &data); + bool write_mifare_classic_block_(uint8_t block_num, std::vector &data); + bool auth_mifare_classic_block_(std::vector &uid, uint8_t block_num, uint8_t key_num, const uint8_t *key); + bool format_mifare_classic_mifare_(std::vector &uid); + bool format_mifare_classic_ndef_(std::vector &uid); + bool write_mifare_classic_tag_(std::vector &uid, nfc::NdefMessage *message); + + nfc::NfcTag *read_mifare_ultralight_tag_(std::vector &uid); + bool read_mifare_ultralight_page_(uint8_t page_num, std::vector &data); + bool is_mifare_ultralight_formatted_(); + uint16_t read_mifare_ultralight_capacity_(); + bool find_mifare_ultralight_ndef_(uint8_t &message_length, uint8_t &message_start_index); + bool write_mifare_ultralight_page_(uint8_t page_num, std::vector &write_data); + bool write_mifare_ultralight_tag_(std::vector &uid, nfc::NdefMessage *message); + bool clean_mifare_ultralight_(); + bool requested_read_{false}; std::vector binary_sensors_; - std::vector triggers_; + std::vector triggers_; std::vector current_uid_; + nfc::NdefMessage *next_task_message_to_write_; + enum NfcTask { + READ = 0, + CLEAN, + FORMAT, + WRITE, + } next_task_{READ}; enum PN532Error { NONE = 0, WAKEUP_FAILED, SAM_COMMAND_FAILED, } error_code_{NONE}; + CallbackManager on_finished_write_callback_; }; class PN532BinarySensor : public binary_sensor::BinarySensor { @@ -69,9 +113,21 @@ class PN532BinarySensor : public binary_sensor::BinarySensor { bool found_{false}; }; -class PN532Trigger : public Trigger { +class PN532OnTagTrigger : public Trigger { public: - void process(std::vector &data); + void process(nfc::NfcTag *tag); +}; + +class PN532OnFinishedWriteTrigger : public Trigger<> { + public: + explicit PN532OnFinishedWriteTrigger(PN532 *parent) { + parent->add_on_finished_write_callback([this]() { this->trigger(); }); + } +}; + +template class PN532IsWritingCondition : public Condition, public Parented { + public: + bool check(Ts... x) override { return this->parent_->is_writing(); } }; } // namespace pn532 diff --git a/esphome/components/pn532/pn532_mifare_classic.cpp b/esphome/components/pn532/pn532_mifare_classic.cpp new file mode 100644 index 0000000000..4e8c255755 --- /dev/null +++ b/esphome/components/pn532/pn532_mifare_classic.cpp @@ -0,0 +1,249 @@ +#include "pn532.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace pn532 { + +static const char *TAG = "pn532.mifare_classic"; + +nfc::NfcTag *PN532::read_mifare_classic_tag_(std::vector &uid) { + uint8_t current_block = 4; + uint8_t message_start_index = 0; + uint32_t message_length = 0; + + if (this->auth_mifare_classic_block_(uid, current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY)) { + std::vector data; + if (this->read_mifare_classic_block_(current_block, data)) { + if (!nfc::decode_mifare_classic_tlv(data, message_length, message_start_index)) { + return new nfc::NfcTag(uid, nfc::ERROR); + } + } else { + ESP_LOGE(TAG, "Failed to read block %d", current_block); + return new nfc::NfcTag(uid, nfc::MIFARE_CLASSIC); + } + } else { + ESP_LOGV(TAG, "Tag is not NDEF formatted"); + return new nfc::NfcTag(uid, nfc::MIFARE_CLASSIC); + } + + uint32_t index = 0; + uint32_t buffer_size = nfc::get_mifare_classic_buffer_size(message_length); + std::vector buffer; + + while (index < buffer_size) { + if (nfc::mifare_classic_is_first_block(current_block)) { + if (!this->auth_mifare_classic_block_(uid, current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY)) { + ESP_LOGE(TAG, "Error, Block authentication failed for %d", current_block); + } + } + std::vector block_data; + if (this->read_mifare_classic_block_(current_block, block_data)) { + buffer.insert(buffer.end(), block_data.begin(), block_data.end()); + } else { + ESP_LOGE(TAG, "Error reading block %d", current_block); + } + + index += nfc::MIFARE_CLASSIC_BLOCK_SIZE; + current_block++; + + if (nfc::mifare_classic_is_trailer_block(current_block)) { + current_block++; + } + } + buffer.erase(buffer.begin(), buffer.begin() + message_start_index); + return new nfc::NfcTag(uid, nfc::MIFARE_CLASSIC, buffer); +} + +bool PN532::read_mifare_classic_block_(uint8_t block_num, std::vector &data) { + if (!this->write_command_({ + PN532_COMMAND_INDATAEXCHANGE, + 0x01, // One card + nfc::MIFARE_CMD_READ, + block_num, + })) { + return false; + } + + if (!this->read_response_(PN532_COMMAND_INDATAEXCHANGE, data) || data[0] != 0x00) { + return false; + } + data.erase(data.begin()); + + ESP_LOGVV(TAG, " Block %d: %s", block_num, nfc::format_bytes(data).c_str()); + return true; +} + +bool PN532::auth_mifare_classic_block_(std::vector &uid, uint8_t block_num, uint8_t key_num, + const uint8_t *key) { + std::vector data({ + PN532_COMMAND_INDATAEXCHANGE, + 0x01, // One card + key_num, // Mifare Key slot + block_num, // Block number + }); + data.insert(data.end(), key, key + 6); + data.insert(data.end(), uid.begin(), uid.end()); + if (!this->write_command_(data)) { + ESP_LOGE(TAG, "Authentication failed - Block %d", block_num); + return false; + } + + std::vector response; + if (!this->read_response_(PN532_COMMAND_INDATAEXCHANGE, response) || response[0] != 0x00) { + ESP_LOGE(TAG, "Authentication failed - Block 0x%02x", block_num); + return false; + } + + return true; +} + +bool PN532::format_mifare_classic_mifare_(std::vector &uid) { + std::vector blank_buffer( + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + std::vector trailer_buffer( + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + + bool error = false; + + for (int block = 0; block < 64; block += 4) { + if (!this->auth_mifare_classic_block_(uid, block + 3, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY)) { + continue; + } + if (block != 0) { + if (!this->write_mifare_classic_block_(block, blank_buffer)) { + ESP_LOGE(TAG, "Unable to write block %d", block); + error = true; + } + } + if (!this->write_mifare_classic_block_(block + 1, blank_buffer)) { + ESP_LOGE(TAG, "Unable to write block %d", block + 1); + error = true; + } + if (!this->write_mifare_classic_block_(block + 2, blank_buffer)) { + ESP_LOGE(TAG, "Unable to write block %d", block + 2); + error = true; + } + if (!this->write_mifare_classic_block_(block + 3, trailer_buffer)) { + ESP_LOGE(TAG, "Unable to write block %d", block + 3); + error = true; + } + } + + return !error; +} + +bool PN532::format_mifare_classic_ndef_(std::vector &uid) { + std::vector empty_ndef_message( + {0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + std::vector blank_block( + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + std::vector block_1_data( + {0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}); + std::vector block_2_data( + {0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}); + std::vector block_3_trailer( + {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + std::vector ndef_trailer( + {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + + if (!this->auth_mifare_classic_block_(uid, 0, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY)) { + ESP_LOGE(TAG, "Unable to authenticate block 0 for formatting!"); + return false; + } + if (!this->write_mifare_classic_block_(1, block_1_data)) + return false; + if (!this->write_mifare_classic_block_(2, block_2_data)) + return false; + if (!this->write_mifare_classic_block_(3, block_3_trailer)) + return false; + + ESP_LOGD(TAG, "Sector 0 formatted to NDEF"); + + for (int block = 4; block < 64; block += 4) { + if (!this->auth_mifare_classic_block_(uid, block + 3, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY)) { + return false; + } + if (block == 4) { + if (!this->write_mifare_classic_block_(block, empty_ndef_message)) + ESP_LOGE(TAG, "Unable to write block %d", block); + } else { + if (!this->write_mifare_classic_block_(block, blank_block)) + ESP_LOGE(TAG, "Unable to write block %d", block); + } + if (!this->write_mifare_classic_block_(block + 1, blank_block)) + ESP_LOGE(TAG, "Unable to write block %d", block + 1); + if (!this->write_mifare_classic_block_(block + 2, blank_block)) + ESP_LOGE(TAG, "Unable to write block %d", block + 2); + if (!this->write_mifare_classic_block_(block + 3, ndef_trailer)) + ESP_LOGE(TAG, "Unable to write trailer block %d", block + 3); + } + return true; +} + +bool PN532::write_mifare_classic_block_(uint8_t block_num, std::vector &write_data) { + std::vector data({ + PN532_COMMAND_INDATAEXCHANGE, + 0x01, // One card + nfc::MIFARE_CMD_WRITE, + block_num, + }); + data.insert(data.end(), write_data.begin(), write_data.end()); + if (!this->write_command_(data)) { + ESP_LOGE(TAG, "Error writing block %d", block_num); + return false; + } + + std::vector response; + if (!this->read_response_(PN532_COMMAND_INDATAEXCHANGE, response)) { + ESP_LOGE(TAG, "Error writing block %d", block_num); + return false; + } + + return true; +} + +bool PN532::write_mifare_classic_tag_(std::vector &uid, nfc::NdefMessage *message) { + auto encoded = message->encode(); + + uint32_t message_length = encoded.size(); + uint32_t buffer_length = nfc::get_mifare_classic_buffer_size(message_length); + + encoded.insert(encoded.begin(), 0x03); + if (message_length < 255) { + encoded.insert(encoded.begin() + 1, message_length); + } else { + encoded.insert(encoded.begin() + 1, 0xFF); + encoded.insert(encoded.begin() + 2, (message_length >> 8) & 0xFF); + encoded.insert(encoded.begin() + 3, message_length & 0xFF); + } + encoded.push_back(0xFE); + + encoded.resize(buffer_length, 0); + + uint32_t index = 0; + uint8_t current_block = 4; + + while (index < buffer_length) { + if (nfc::mifare_classic_is_first_block(current_block)) { + if (!this->auth_mifare_classic_block_(uid, current_block, nfc::MIFARE_CMD_AUTH_A, nfc::NDEF_KEY)) { + return false; + } + } + + std::vector data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_CLASSIC_BLOCK_SIZE); + if (!this->write_mifare_classic_block_(current_block, data)) { + return false; + } + index += nfc::MIFARE_CLASSIC_BLOCK_SIZE; + current_block++; + + if (nfc::mifare_classic_is_trailer_block(current_block)) { + // Skipping as cannot write to trailer + current_block++; + } + } + return true; +} + +} // namespace pn532 +} // namespace esphome diff --git a/esphome/components/pn532/pn532_mifare_ultralight.cpp b/esphome/components/pn532/pn532_mifare_ultralight.cpp new file mode 100644 index 0000000000..00cb18aacd --- /dev/null +++ b/esphome/components/pn532/pn532_mifare_ultralight.cpp @@ -0,0 +1,180 @@ +#include "pn532.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace pn532 { + +static const char *TAG = "pn532.mifare_ultralight"; + +nfc::NfcTag *PN532::read_mifare_ultralight_tag_(std::vector &uid) { + if (!this->is_mifare_ultralight_formatted_()) { + ESP_LOGD(TAG, "Not NDEF formatted"); + return new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2); + } + + uint8_t message_length; + uint8_t message_start_index; + if (!this->find_mifare_ultralight_ndef_(message_length, message_start_index)) { + return new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2); + } + + if (message_length == 0) { + return new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2); + } + std::vector data; + uint8_t index = 0; + for (uint8_t page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; page < nfc::MIFARE_ULTRALIGHT_MAX_PAGE; page++) { + std::vector page_data; + if (!this->read_mifare_ultralight_page_(page, page_data)) { + ESP_LOGE(TAG, "Error reading page %d", page); + return new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2); + } + data.insert(data.end(), page_data.begin(), page_data.end()); + + if (index >= (message_length + message_start_index)) + break; + + index += page_data.size(); + } + + data.erase(data.begin(), data.begin() + message_start_index); + + return new nfc::NfcTag(uid, nfc::NFC_FORUM_TYPE_2, data); +} + +bool PN532::read_mifare_ultralight_page_(uint8_t page_num, std::vector &data) { + if (!this->write_command_({ + PN532_COMMAND_INDATAEXCHANGE, + 0x01, // One card + nfc::MIFARE_CMD_READ, + page_num, + })) { + return false; + } + + if (!this->read_response_(PN532_COMMAND_INDATAEXCHANGE, data) || data[0] != 0x00) { + return false; + } + data.erase(data.begin()); + // We only want 1 page of data but the PN532 returns 4 at once. + data.erase(data.begin() + 4, data.end()); + + ESP_LOGVV(TAG, "Pages %d-%d: %s", page_num, page_num + 4, nfc::format_bytes(data).c_str()); + + return true; +} + +bool PN532::is_mifare_ultralight_formatted_() { + std::vector data; + if (this->read_mifare_ultralight_page_(4, data)) { + return !(data[0] == 0xFF && data[1] == 0xFF && data[2] == 0xFF && data[3] == 0xFF); + } + return true; +} + +uint16_t PN532::read_mifare_ultralight_capacity_() { + std::vector data; + if (this->read_mifare_ultralight_page_(3, data)) { + return data[2] * 8U; + } + return 0; +} + +bool PN532::find_mifare_ultralight_ndef_(uint8_t &message_length, uint8_t &message_start_index) { + std::vector data; + for (int page = 4; page < 6; page++) { + std::vector page_data; + if (!this->read_mifare_ultralight_page_(page, page_data)) { + return false; + } + data.insert(data.end(), page_data.begin(), page_data.end()); + } + if (data[0] == 0x03) { + message_length = data[1]; + message_start_index = 2; + return true; + } else if (data[5] == 0x03) { + message_length = data[6]; + message_start_index = 7; + return true; + } + return false; +} + +bool PN532::write_mifare_ultralight_tag_(std::vector &uid, nfc::NdefMessage *message) { + uint32_t capacity = this->read_mifare_ultralight_capacity_(); + + auto encoded = message->encode(); + + uint32_t message_length = encoded.size(); + uint32_t buffer_length = nfc::get_mifare_ultralight_buffer_size(message_length); + + if (buffer_length > capacity) { + ESP_LOGE(TAG, "Message length exceeds tag capacity %d > %d", buffer_length, capacity); + return false; + } + + encoded.insert(encoded.begin(), 0x03); + if (message_length < 255) { + encoded.insert(encoded.begin() + 1, message_length); + } else { + encoded.insert(encoded.begin() + 1, 0xFF); + encoded.insert(encoded.begin() + 2, (message_length >> 8) & 0xFF); + encoded.insert(encoded.begin() + 2, message_length & 0xFF); + } + encoded.push_back(0xFE); + + encoded.resize(buffer_length, 0); + + uint32_t index = 0; + uint8_t current_page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; + + while (index < buffer_length) { + std::vector data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE); + if (!this->write_mifare_ultralight_page_(current_page, data)) { + return false; + } + index += nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; + current_page++; + } + return true; +} + +bool PN532::clean_mifare_ultralight_() { + uint32_t capacity = this->read_mifare_ultralight_capacity_(); + uint8_t pages = (capacity / nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) + nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; + + std::vector blank_data = {0x00, 0x00, 0x00, 0x00}; + + for (int i = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; i < pages; i++) { + if (!this->write_mifare_ultralight_page_(i, blank_data)) { + return false; + } + } + return true; +} + +bool PN532::write_mifare_ultralight_page_(uint8_t page_num, std::vector &write_data) { + std::vector data({ + PN532_COMMAND_INDATAEXCHANGE, + 0x01, // One card + nfc::MIFARE_CMD_WRITE_ULTRALIGHT, + page_num, + }); + data.insert(data.end(), write_data.begin(), write_data.end()); + if (!this->write_command_(data)) { + ESP_LOGE(TAG, "Error writing page %d", page_num); + return false; + } + + std::vector response; + if (!this->read_response_(PN532_COMMAND_INDATAEXCHANGE, response)) { + ESP_LOGE(TAG, "Error writing page %d", page_num); + return false; + } + + return true; +} + +} // namespace pn532 +} // namespace esphome diff --git a/esphome/components/pn532_i2c/pn532_i2c.cpp b/esphome/components/pn532_i2c/pn532_i2c.cpp index b959e764e7..162487de58 100644 --- a/esphome/components/pn532_i2c/pn532_i2c.cpp +++ b/esphome/components/pn532_i2c/pn532_i2c.cpp @@ -14,7 +14,7 @@ static const char *TAG = "pn532_i2c"; bool PN532I2C::write_data(const std::vector &data) { return this->write_bytes_raw(data.data(), data.size()); } bool PN532I2C::read_data(std::vector &data, uint8_t len) { - delay(5); + delay(1); std::vector ready; ready.resize(1); From 6a99789c92bfb09ee842ea45c0da10985cd872ed Mon Sep 17 00:00:00 2001 From: David Zovko Date: Sat, 16 Jan 2021 01:19:35 +0100 Subject: [PATCH 7/8] Inkplate 6 support for ESPHome (#1283) * Add Inkplate 6 support Inkplate 6 is e-paper display based on ESP32. This commit adds support for integrating Inkplate 6 into the ESPHome. Find more info here: inkplate.io * Greyscale working * Update inkplate.h * Fix formatting * Formatting * Update esphome/components/inkplate6/display.py Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> * Update esphome/components/inkplate6/display.py Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> * Fix some lint errors Ignore some lint errors Only allow on ESP32 * Update the codeowners file Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/inkplate6/__init__.py | 1 + esphome/components/inkplate6/display.py | 141 +++++ esphome/components/inkplate6/inkplate.cpp | 630 ++++++++++++++++++++++ esphome/components/inkplate6/inkplate.h | 157 ++++++ 5 files changed, 930 insertions(+) create mode 100644 esphome/components/inkplate6/__init__.py create mode 100644 esphome/components/inkplate6/display.py create mode 100644 esphome/components/inkplate6/inkplate.cpp create mode 100644 esphome/components/inkplate6/inkplate.h diff --git a/CODEOWNERS b/CODEOWNERS index 5cf0f591d2..4c24586bb6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -37,6 +37,7 @@ esphome/components/globals/* @esphome/core esphome/components/gpio/* @esphome/core esphome/components/homeassistant/* @OttoWinter esphome/components/i2c/* @esphome/core +esphome/components/inkplate6/* @jesserockz esphome/components/integration/* @OttoWinter esphome/components/interval/* @esphome/core esphome/components/json/* @OttoWinter diff --git a/esphome/components/inkplate6/__init__.py b/esphome/components/inkplate6/__init__.py new file mode 100644 index 0000000000..ba7653988b --- /dev/null +++ b/esphome/components/inkplate6/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ['@jesserockz'] diff --git a/esphome/components/inkplate6/display.py b/esphome/components/inkplate6/display.py new file mode 100644 index 0000000000..4f35901bd4 --- /dev/null +++ b/esphome/components/inkplate6/display.py @@ -0,0 +1,141 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import display, i2c +from esphome.const import CONF_FULL_UPDATE_EVERY, CONF_ID, CONF_LAMBDA, CONF_PAGES, \ + CONF_WAKEUP_PIN, ESP_PLATFORM_ESP32 + +DEPENDENCIES = ['i2c'] +ESP_PLATFORMS = [ESP_PLATFORM_ESP32] + +CONF_DISPLAY_DATA_0_PIN = 'display_data_0_pin' +CONF_DISPLAY_DATA_1_PIN = 'display_data_1_pin' +CONF_DISPLAY_DATA_2_PIN = 'display_data_2_pin' +CONF_DISPLAY_DATA_3_PIN = 'display_data_3_pin' +CONF_DISPLAY_DATA_4_PIN = 'display_data_4_pin' +CONF_DISPLAY_DATA_5_PIN = 'display_data_5_pin' +CONF_DISPLAY_DATA_6_PIN = 'display_data_6_pin' +CONF_DISPLAY_DATA_7_PIN = 'display_data_7_pin' + +CONF_CL_PIN = 'cl_pin' +CONF_CKV_PIN = 'ckv_pin' +CONF_GREYSCALE = 'greyscale' +CONF_GMOD_PIN = 'gmod_pin' +CONF_GPIO0_ENABLE_PIN = 'gpio0_enable_pin' +CONF_LE_PIN = 'le_pin' +CONF_OE_PIN = 'oe_pin' +CONF_PARTIAL_UPDATING = 'partial_updating' +CONF_POWERUP_PIN = 'powerup_pin' +CONF_SPH_PIN = 'sph_pin' +CONF_SPV_PIN = 'spv_pin' +CONF_VCOM_PIN = 'vcom_pin' + + +inkplate6_ns = cg.esphome_ns.namespace('inkplate6') +Inkplate6 = inkplate6_ns.class_('Inkplate6', cg.PollingComponent, i2c.I2CDevice, + display.DisplayBuffer) + +CONFIG_SCHEMA = cv.All(display.FULL_DISPLAY_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(Inkplate6), + cv.Optional(CONF_GREYSCALE, default=False): cv.boolean, + cv.Optional(CONF_PARTIAL_UPDATING, default=True): cv.boolean, + cv.Optional(CONF_FULL_UPDATE_EVERY, default=10): cv.uint32_t, + # Control pins + cv.Required(CONF_CKV_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_GMOD_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_GPIO0_ENABLE_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_OE_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_POWERUP_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_SPH_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_SPV_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_VCOM_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_WAKEUP_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_CL_PIN, default=0): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_LE_PIN, default=2): pins.internal_gpio_output_pin_schema, + # Data pins + cv.Optional(CONF_DISPLAY_DATA_0_PIN, default=4): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_DISPLAY_DATA_1_PIN, default=5): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_DISPLAY_DATA_2_PIN, default=18): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_DISPLAY_DATA_3_PIN, default=19): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_DISPLAY_DATA_4_PIN, default=23): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_DISPLAY_DATA_5_PIN, default=25): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_DISPLAY_DATA_6_PIN, default=26): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_DISPLAY_DATA_7_PIN, default=27): pins.internal_gpio_output_pin_schema, +}).extend(cv.polling_component_schema('5s').extend(i2c.i2c_device_schema(0x48))), + cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA)) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + + yield cg.register_component(var, config) + yield display.register_display(var, config) + yield i2c.register_i2c_device(var, config) + + if CONF_LAMBDA in config: + lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], [(display.DisplayBufferRef, 'it')], + return_type=cg.void) + cg.add(var.set_writer(lambda_)) + + cg.add(var.set_greyscale(config[CONF_GREYSCALE])) + cg.add(var.set_partial_updating(config[CONF_PARTIAL_UPDATING])) + cg.add(var.set_full_update_every(config[CONF_FULL_UPDATE_EVERY])) + + ckv = yield cg.gpio_pin_expression(config[CONF_CKV_PIN]) + cg.add(var.set_ckv_pin(ckv)) + + gmod = yield cg.gpio_pin_expression(config[CONF_GMOD_PIN]) + cg.add(var.set_gmod_pin(gmod)) + + gpio0_enable = yield cg.gpio_pin_expression(config[CONF_GPIO0_ENABLE_PIN]) + cg.add(var.set_gpio0_enable_pin(gpio0_enable)) + + oe = yield cg.gpio_pin_expression(config[CONF_OE_PIN]) + cg.add(var.set_oe_pin(oe)) + + powerup = yield cg.gpio_pin_expression(config[CONF_POWERUP_PIN]) + cg.add(var.set_powerup_pin(powerup)) + + sph = yield cg.gpio_pin_expression(config[CONF_SPH_PIN]) + cg.add(var.set_sph_pin(sph)) + + spv = yield cg.gpio_pin_expression(config[CONF_SPV_PIN]) + cg.add(var.set_spv_pin(spv)) + + vcom = yield cg.gpio_pin_expression(config[CONF_VCOM_PIN]) + cg.add(var.set_vcom_pin(vcom)) + + wakeup = yield cg.gpio_pin_expression(config[CONF_WAKEUP_PIN]) + cg.add(var.set_wakeup_pin(wakeup)) + + cl = yield cg.gpio_pin_expression(config[CONF_CL_PIN]) + cg.add(var.set_cl_pin(cl)) + + le = yield cg.gpio_pin_expression(config[CONF_LE_PIN]) + cg.add(var.set_le_pin(le)) + + display_data_0 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_0_PIN]) + cg.add(var.set_display_data_0_pin(display_data_0)) + + display_data_1 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_1_PIN]) + cg.add(var.set_display_data_1_pin(display_data_1)) + + display_data_2 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_2_PIN]) + cg.add(var.set_display_data_2_pin(display_data_2)) + + display_data_3 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_3_PIN]) + cg.add(var.set_display_data_3_pin(display_data_3)) + + display_data_4 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_4_PIN]) + cg.add(var.set_display_data_4_pin(display_data_4)) + + display_data_5 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_5_PIN]) + cg.add(var.set_display_data_5_pin(display_data_5)) + + display_data_6 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_6_PIN]) + cg.add(var.set_display_data_6_pin(display_data_6)) + + display_data_7 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_7_PIN]) + cg.add(var.set_display_data_7_pin(display_data_7)) + + cg.add_build_flag('-DBOARD_HAS_PSRAM') diff --git a/esphome/components/inkplate6/inkplate.cpp b/esphome/components/inkplate6/inkplate.cpp new file mode 100644 index 0000000000..3b17ba8f52 --- /dev/null +++ b/esphome/components/inkplate6/inkplate.cpp @@ -0,0 +1,630 @@ +#include "inkplate.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" +#include "esphome/core/helpers.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace inkplate6 { + +static const char *TAG = "inkplate"; + +void Inkplate6::setup() { + this->initialize_(); + + this->vcom_pin_->setup(); + this->powerup_pin_->setup(); + this->wakeup_pin_->setup(); + this->gpio0_enable_pin_->setup(); + this->gpio0_enable_pin_->digital_write(true); + + this->cl_pin_->setup(); + this->le_pin_->setup(); + this->ckv_pin_->setup(); + this->gmod_pin_->setup(); + this->oe_pin_->setup(); + this->sph_pin_->setup(); + this->spv_pin_->setup(); + + this->display_data_0_pin_->setup(); + this->display_data_1_pin_->setup(); + this->display_data_2_pin_->setup(); + this->display_data_3_pin_->setup(); + this->display_data_4_pin_->setup(); + this->display_data_5_pin_->setup(); + this->display_data_6_pin_->setup(); + this->display_data_7_pin_->setup(); + + this->clean(); + this->display(); +} +void Inkplate6::initialize_() { + uint32_t buffer_size = this->get_buffer_length_(); + + if (this->partial_buffer_ != nullptr) { + free(this->partial_buffer_); // NOLINT + } + if (this->partial_buffer_2_ != nullptr) { + free(this->partial_buffer_2_); // NOLINT + } + if (this->buffer_ != nullptr) { + free(this->buffer_); // NOLINT + } + + this->buffer_ = (uint8_t *) ps_malloc(buffer_size); + if (this->buffer_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate buffer for display!"); + this->mark_failed(); + return; + } + if (!this->greyscale_) { + this->partial_buffer_ = (uint8_t *) ps_malloc(buffer_size); + if (this->partial_buffer_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate partial buffer for display!"); + this->mark_failed(); + return; + } + this->partial_buffer_2_ = (uint8_t *) ps_malloc(buffer_size * 2); + if (this->partial_buffer_2_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate partial buffer 2 for display!"); + this->mark_failed(); + return; + } + memset(this->partial_buffer_, 0, buffer_size); + memset(this->partial_buffer_2_, 0, buffer_size * 2); + } + + memset(this->buffer_, 0, buffer_size); +} +float Inkplate6::get_setup_priority() const { return setup_priority::PROCESSOR; } +size_t Inkplate6::get_buffer_length_() { + if (this->greyscale_) { + return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 2u; + } else { + return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 8u; + } +} +void Inkplate6::update() { + this->do_update_(); + + if (this->full_update_every_ > 0 && this->partial_updates_ >= this->full_update_every_) { + this->block_partial_ = true; + } + + this->display(); +} +void HOT Inkplate6::draw_absolute_pixel_internal(int x, int y, Color color) { + if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0) + return; + + if (this->greyscale_) { + int x1 = x / 2; + int x_sub = x % 2; + uint32_t pos = (x1 + y * (this->get_width_internal() / 2)); + uint8_t current = this->buffer_[pos]; + + // float px = (0.2126 * (color.red / 255.0)) + (0.7152 * (color.green / 255.0)) + (0.0722 * (color.blue / 255.0)); + // px = pow(px, 1.5); + // uint8_t gs = (uint8_t)(px*7); + + uint8_t gs = ((color.red * 2126 / 10000) + (color.green * 7152 / 10000) + (color.blue * 722 / 10000)) >> 5; + this->buffer_[pos] = (pixelMaskGLUT[x_sub] & current) | (x_sub ? gs : gs << 4); + + } else { + int x1 = x / 8; + int x_sub = x % 8; + uint32_t pos = (x1 + y * (this->get_width_internal() / 8)); + uint8_t current = this->partial_buffer_[pos]; + this->partial_buffer_[pos] = (~pixelMaskLUT[x_sub] & current) | (color.is_on() ? 0 : pixelMaskLUT[x_sub]); + } +} +void Inkplate6::dump_config() { + LOG_DISPLAY("", "Inkplate", this); + ESP_LOGCONFIG(TAG, " Greyscale: %s", YESNO(this->greyscale_)); + ESP_LOGCONFIG(TAG, " Partial Updating: %s", YESNO(this->partial_updating_)); + ESP_LOGCONFIG(TAG, " Full Update Every: %d", this->full_update_every_); + // Log pins + LOG_PIN(" CKV Pin: ", this->ckv_pin_); + LOG_PIN(" CL Pin: ", this->cl_pin_); + LOG_PIN(" GPIO0 Enable Pin: ", this->gpio0_enable_pin_); + LOG_PIN(" GMOD Pin: ", this->gmod_pin_); + LOG_PIN(" LE Pin: ", this->le_pin_); + LOG_PIN(" OE Pin: ", this->oe_pin_); + LOG_PIN(" POWERUP Pin: ", this->powerup_pin_); + LOG_PIN(" SPH Pin: ", this->sph_pin_); + LOG_PIN(" SPV Pin: ", this->spv_pin_); + LOG_PIN(" VCOM Pin: ", this->vcom_pin_); + LOG_PIN(" WAKEUP Pin: ", this->wakeup_pin_); + + LOG_PIN(" Data 0 Pin: ", this->display_data_0_pin_); + LOG_PIN(" Data 1 Pin: ", this->display_data_1_pin_); + LOG_PIN(" Data 2 Pin: ", this->display_data_2_pin_); + LOG_PIN(" Data 3 Pin: ", this->display_data_3_pin_); + LOG_PIN(" Data 4 Pin: ", this->display_data_4_pin_); + LOG_PIN(" Data 5 Pin: ", this->display_data_5_pin_); + LOG_PIN(" Data 6 Pin: ", this->display_data_6_pin_); + LOG_PIN(" Data 7 Pin: ", this->display_data_7_pin_); + + LOG_UPDATE_INTERVAL(this); +} +void Inkplate6::eink_off_() { + ESP_LOGV(TAG, "Eink off called"); + unsigned long start_time = millis(); + if (panel_on_ == 0) + return; + panel_on_ = 0; + this->gmod_pin_->digital_write(false); + this->oe_pin_->digital_write(false); + + GPIO.out &= ~(get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()) | (1 << this->le_pin_->get_pin())); + + this->sph_pin_->digital_write(false); + this->spv_pin_->digital_write(false); + + this->powerup_pin_->digital_write(false); + this->wakeup_pin_->digital_write(false); + this->vcom_pin_->digital_write(false); + + pins_z_state_(); +} +void Inkplate6::eink_on_() { + ESP_LOGV(TAG, "Eink on called"); + unsigned long start_time = millis(); + if (panel_on_ == 1) + return; + panel_on_ = 1; + pins_as_outputs_(); + this->wakeup_pin_->digital_write(true); + this->powerup_pin_->digital_write(true); + this->vcom_pin_->digital_write(true); + + this->write_byte(0x01, 0x3F); + + delay(40); + + this->write_byte(0x0D, 0x80); + + delay(2); + + this->read_byte(0x00, &temperature_, 0); + + this->le_pin_->digital_write(false); + this->oe_pin_->digital_write(false); + this->cl_pin_->digital_write(false); + this->sph_pin_->digital_write(true); + this->gmod_pin_->digital_write(true); + this->spv_pin_->digital_write(true); + this->ckv_pin_->digital_write(false); + this->oe_pin_->digital_write(true); +} +void Inkplate6::fill(Color color) { + ESP_LOGV(TAG, "Fill called"); + unsigned long start_time = millis(); + + if (this->greyscale_) { + uint8_t fill = ((color.red * 2126 / 10000) + (color.green * 7152 / 10000) + (color.blue * 722 / 10000)) >> 5; + for (uint32_t i = 0; i < this->get_buffer_length_(); i++) + this->buffer_[i] = (fill << 4) | fill; + } else { + uint8_t fill = color.is_on() ? 0x00 : 0xFF; + for (uint32_t i = 0; i < this->get_buffer_length_(); i++) + this->partial_buffer_[i] = fill; + } + + ESP_LOGV(TAG, "Fill finished (%lums)", millis() - start_time); +} +void Inkplate6::display() { + ESP_LOGV(TAG, "Display called"); + unsigned long start_time = millis(); + + if (this->greyscale_) { + this->display3b_(); + } else { + if (this->partial_updating_ && this->partial_update_()) { + ESP_LOGV(TAG, "Display finished (partial) (%lums)", millis() - start_time); + return; + } + this->display1b_(); + } + ESP_LOGV(TAG, "Display finished (full) (%lums)", millis() - start_time); +} +void Inkplate6::display1b_() { + ESP_LOGV(TAG, "Display1b called"); + unsigned long start_time = millis(); + + for (int i = 0; i < this->get_buffer_length_(); i++) { + this->buffer_[i] &= this->partial_buffer_[i]; + this->buffer_[i] |= this->partial_buffer_[i]; + } + + uint16_t pos; + uint32_t send; + uint8_t data; + uint8_t buffer_value; + eink_on_(); + clean_fast_(0, 1); + clean_fast_(1, 5); + clean_fast_(2, 1); + clean_fast_(0, 5); + clean_fast_(2, 1); + clean_fast_(1, 12); + clean_fast_(2, 1); + clean_fast_(0, 11); + + ESP_LOGV(TAG, "Display1b start loops (%lums)", millis() - start_time); + for (int k = 0; k < 3; k++) { + pos = this->get_buffer_length_() - 1; + vscan_start_(); + for (int i = 0; i < this->get_height_internal(); i++) { + buffer_value = this->buffer_[pos]; + data = LUTB[(buffer_value >> 4) & 0x0F]; + send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | + (((data & B11100000) >> 5) << 25); + hscan_start_(send); + data = LUTB[buffer_value & 0x0F]; + send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | + (((data & B11100000) >> 5) << 25); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + pos--; + + for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) { + buffer_value = this->buffer_[pos]; + data = LUTB[(buffer_value >> 4) & 0x0F]; + send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | + (((data & B11100000) >> 5) << 25); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + data = LUTB[buffer_value & 0x0F]; + send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | + (((data & B11100000) >> 5) << 25); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + pos--; + } + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + vscan_end_(); + } + delayMicroseconds(230); + } + ESP_LOGV(TAG, "Display1b first loop x %d (%lums)", 3, millis() - start_time); + + pos = this->get_buffer_length_() - 1; + vscan_start_(); + for (int i = 0; i < this->get_height_internal(); i++) { + buffer_value = this->buffer_[pos]; + data = LUT2[(buffer_value >> 4) & 0x0F]; + send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | + (((data & B11100000) >> 5) << 25); + hscan_start_(send); + data = LUT2[buffer_value & 0x0F]; + send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | + (((data & B11100000) >> 5) << 25); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + pos--; + for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) { + buffer_value = this->buffer_[pos]; + data = LUT2[(buffer_value >> 4) & 0x0F]; + send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | + (((data & B11100000) >> 5) << 25); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + data = LUT2[buffer_value & 0x0F]; + send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | + (((data & B11100000) >> 5) << 25); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + pos--; + } + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + vscan_end_(); + } + delayMicroseconds(230); + ESP_LOGV(TAG, "Display1b second loop (%lums)", millis() - start_time); + + vscan_start_(); + for (int i = 0; i < this->get_height_internal(); i++) { + buffer_value = this->buffer_[pos]; + data = 0b00000000; + send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | + (((data & B11100000) >> 5) << 25); + hscan_start_(send); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) { + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + } + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + vscan_end_(); + } + delayMicroseconds(230); + ESP_LOGV(TAG, "Display1b third loop (%lums)", millis() - start_time); + + vscan_start_(); + eink_off_(); + this->block_partial_ = false; + this->partial_updates_ = 0; + ESP_LOGV(TAG, "Display1b finished (%lums)", millis() - start_time); +} +void Inkplate6::display3b_() { + ESP_LOGV(TAG, "Display3b called"); + unsigned long start_time = millis(); + + eink_on_(); + clean_fast_(0, 1); + clean_fast_(1, 12); + clean_fast_(2, 1); + clean_fast_(0, 11); + clean_fast_(2, 1); + clean_fast_(1, 12); + clean_fast_(2, 1); + clean_fast_(0, 11); + + for (int k = 0; k < 8; k++) { + uint32_t pos = this->get_buffer_length_() - 1; + uint32_t send; + uint8_t pix1; + uint8_t pix2; + uint8_t pix3; + uint8_t pix4; + uint8_t pixel; + uint8_t pixel2; + + vscan_start_(); + for (int i = 0; i < this->get_height_internal(); i++) { + pix1 = this->buffer_[pos--]; + pix2 = this->buffer_[pos--]; + pix3 = this->buffer_[pos--]; + pix4 = this->buffer_[pos--]; + pixel = (waveform3Bit[pix1 & 0x07][k] << 6) | (waveform3Bit[(pix1 >> 4) & 0x07][k] << 4) | + (waveform3Bit[pix2 & 0x07][k] << 2) | (waveform3Bit[(pix2 >> 4) & 0x07][k] << 0); + pixel2 = (waveform3Bit[pix3 & 0x07][k] << 6) | (waveform3Bit[(pix3 >> 4) & 0x07][k] << 4) | + (waveform3Bit[pix4 & 0x07][k] << 2) | (waveform3Bit[(pix4 >> 4) & 0x07][k] << 0); + + send = ((pixel & B00000011) << 4) | (((pixel & B00001100) >> 2) << 18) | (((pixel & B00010000) >> 4) << 23) | + (((pixel & B11100000) >> 5) << 25); + hscan_start_(send); + send = ((pixel2 & B00000011) << 4) | (((pixel2 & B00001100) >> 2) << 18) | (((pixel2 & B00010000) >> 4) << 23) | + (((pixel2 & B11100000) >> 5) << 25); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + + for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) { + pix1 = this->buffer_[pos--]; + pix2 = this->buffer_[pos--]; + pix3 = this->buffer_[pos--]; + pix4 = this->buffer_[pos--]; + pixel = (waveform3Bit[pix1 & 0x07][k] << 6) | (waveform3Bit[(pix1 >> 4) & 0x07][k] << 4) | + (waveform3Bit[pix2 & 0x07][k] << 2) | (waveform3Bit[(pix2 >> 4) & 0x07][k] << 0); + pixel2 = (waveform3Bit[pix3 & 0x07][k] << 6) | (waveform3Bit[(pix3 >> 4) & 0x07][k] << 4) | + (waveform3Bit[pix4 & 0x07][k] << 2) | (waveform3Bit[(pix4 >> 4) & 0x07][k] << 0); + + send = ((pixel & B00000011) << 4) | (((pixel & B00001100) >> 2) << 18) | (((pixel & B00010000) >> 4) << 23) | + (((pixel & B11100000) >> 5) << 25); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + + send = ((pixel2 & B00000011) << 4) | (((pixel2 & B00001100) >> 2) << 18) | (((pixel2 & B00010000) >> 4) << 23) | + (((pixel2 & B11100000) >> 5) << 25); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + } + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + vscan_end_(); + } + delayMicroseconds(230); + } + clean_fast_(2, 1); + clean_fast_(3, 1); + vscan_start_(); + eink_off_(); + ESP_LOGV(TAG, "Display3b finished (%lums)", millis() - start_time); +} +bool Inkplate6::partial_update_() { + ESP_LOGV(TAG, "Partial update called"); + unsigned long start_time = millis(); + if (this->greyscale_) + return false; + if (this->block_partial_) + return false; + + this->partial_updates_++; + + uint16_t pos = this->get_buffer_length_() - 1; + uint32_t send; + uint8_t data; + uint8_t diffw, diffb; + uint32_t n = (this->get_buffer_length_() * 2) - 1; + + for (int i = 0; i < this->get_height_internal(); i++) { + for (int j = 0; j < (this->get_width_internal() / 8); j++) { + diffw = (this->buffer_[pos] ^ this->partial_buffer_[pos]) & ~(this->partial_buffer_[pos]); + diffb = (this->buffer_[pos] ^ this->partial_buffer_[pos]) & this->partial_buffer_[pos]; + pos--; + this->partial_buffer_2_[n--] = LUTW[diffw >> 4] & LUTB[diffb >> 4]; + this->partial_buffer_2_[n--] = LUTW[diffw & 0x0F] & LUTB[diffb & 0x0F]; + } + } + ESP_LOGV(TAG, "Partial update buffer built after (%lums)", millis() - start_time); + + eink_on_(); + for (int k = 0; k < 5; k++) { + vscan_start_(); + n = (this->get_buffer_length_() * 2) - 1; + for (int i = 0; i < this->get_height_internal(); i++) { + data = this->partial_buffer_2_[n--]; + send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | + (((data & B11100000) >> 5) << 25); + hscan_start_(send); + for (int j = 0; j < (this->get_width_internal() / 4) - 1; j++) { + data = this->partial_buffer_2_[n--]; + send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | + (((data & B11100000) >> 5) << 25); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + } + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + vscan_end_(); + } + delayMicroseconds(230); + ESP_LOGV(TAG, "Partial update loop k=%d (%lums)", k, millis() - start_time); + } + clean_fast_(2, 2); + clean_fast_(3, 1); + vscan_start_(); + eink_off_(); + + for (int i = 0; i < this->get_buffer_length_(); i++) { + this->buffer_[i] = this->partial_buffer_[i]; + } + ESP_LOGV(TAG, "Partial update finished (%lums)", millis() - start_time); + return true; +} +void Inkplate6::vscan_start_() { + this->ckv_pin_->digital_write(true); + delayMicroseconds(7); + this->spv_pin_->digital_write(false); + delayMicroseconds(10); + this->ckv_pin_->digital_write(false); + delayMicroseconds(0); + this->ckv_pin_->digital_write(true); + delayMicroseconds(8); + this->spv_pin_->digital_write(true); + delayMicroseconds(10); + this->ckv_pin_->digital_write(false); + delayMicroseconds(0); + this->ckv_pin_->digital_write(true); + delayMicroseconds(18); + this->ckv_pin_->digital_write(false); + delayMicroseconds(0); + this->ckv_pin_->digital_write(true); + delayMicroseconds(18); + this->ckv_pin_->digital_write(false); + delayMicroseconds(0); + this->ckv_pin_->digital_write(true); +} +void Inkplate6::vscan_write_() { + this->ckv_pin_->digital_write(false); + this->le_pin_->digital_write(true); + this->le_pin_->digital_write(false); + delayMicroseconds(0); + this->sph_pin_->digital_write(false); + this->cl_pin_->digital_write(true); + this->cl_pin_->digital_write(false); + this->sph_pin_->digital_write(true); + this->ckv_pin_->digital_write(true); +} +void Inkplate6::hscan_start_(uint32_t d) { + this->sph_pin_->digital_write(false); + GPIO.out_w1ts = (d) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + this->sph_pin_->digital_write(true); +} +void Inkplate6::vscan_end_() { + this->ckv_pin_->digital_write(false); + this->le_pin_->digital_write(true); + this->le_pin_->digital_write(false); + delayMicroseconds(1); + this->ckv_pin_->digital_write(true); +} +void Inkplate6::clean() { + ESP_LOGV(TAG, "Clean called"); + unsigned long start_time = millis(); + + eink_on_(); + clean_fast_(0, 1); // White + clean_fast_(0, 8); // White to White + clean_fast_(0, 1); // White to Black + clean_fast_(0, 8); // Black to Black + clean_fast_(2, 1); // Black to White + clean_fast_(1, 10); // White to White + ESP_LOGV(TAG, "Clean finished (%lums)", millis() - start_time); +} +void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) { + ESP_LOGV(TAG, "Clean fast called with: (%d, %d)", c, rep); + unsigned long start_time = millis(); + + eink_on_(); + uint8_t data = 0; + if (c == 0) // White + data = B10101010; + else if (c == 1) // Black + data = B01010101; + else if (c == 2) // Discharge + data = B00000000; + else if (c == 3) // Skip + data = B11111111; + + uint32_t send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | + (((data & B11100000) >> 5) << 25); + + for (int k = 0; k < rep; k++) { + vscan_start_(); + for (int i = 0; i < this->get_height_internal(); i++) { + hscan_start_(send); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + for (int j = 0; j < this->get_width_internal() / 8; j++) { + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + } + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + vscan_end_(); + } + delayMicroseconds(230); + ESP_LOGV(TAG, "Clean fast rep loop %d finished (%lums)", k, millis() - start_time); + } + ESP_LOGV(TAG, "Clean fast finished (%lums)", millis() - start_time); +} +void Inkplate6::pins_z_state_() { + this->ckv_pin_->pin_mode(INPUT); + this->sph_pin_->pin_mode(INPUT); + + this->oe_pin_->pin_mode(INPUT); + this->gmod_pin_->pin_mode(INPUT); + this->spv_pin_->pin_mode(INPUT); + + this->display_data_0_pin_->pin_mode(INPUT); + this->display_data_1_pin_->pin_mode(INPUT); + this->display_data_2_pin_->pin_mode(INPUT); + this->display_data_3_pin_->pin_mode(INPUT); + this->display_data_4_pin_->pin_mode(INPUT); + this->display_data_5_pin_->pin_mode(INPUT); + this->display_data_6_pin_->pin_mode(INPUT); + this->display_data_7_pin_->pin_mode(INPUT); +} +void Inkplate6::pins_as_outputs_() { + this->ckv_pin_->pin_mode(OUTPUT); + this->sph_pin_->pin_mode(OUTPUT); + + this->oe_pin_->pin_mode(OUTPUT); + this->gmod_pin_->pin_mode(OUTPUT); + this->spv_pin_->pin_mode(OUTPUT); + + this->display_data_0_pin_->pin_mode(OUTPUT); + this->display_data_1_pin_->pin_mode(OUTPUT); + this->display_data_2_pin_->pin_mode(OUTPUT); + this->display_data_3_pin_->pin_mode(OUTPUT); + this->display_data_4_pin_->pin_mode(OUTPUT); + this->display_data_5_pin_->pin_mode(OUTPUT); + this->display_data_6_pin_->pin_mode(OUTPUT); + this->display_data_7_pin_->pin_mode(OUTPUT); +} + +} // namespace inkplate6 +} // namespace esphome + +#endif diff --git a/esphome/components/inkplate6/inkplate.h b/esphome/components/inkplate6/inkplate.h new file mode 100644 index 0000000000..94d14b4f6e --- /dev/null +++ b/esphome/components/inkplate6/inkplate.h @@ -0,0 +1,157 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/display/display_buffer.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace inkplate6 { + +class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public i2c::I2CDevice { + public: + const uint8_t LUT2[16] = {B10101010, B10101001, B10100110, B10100101, B10011010, B10011001, B10010110, B10010101, + B01101010, B01101001, B01100110, B01100101, B01011010, B01011001, B01010110, B01010101}; + const uint8_t LUTW[16] = {B11111111, B11111110, B11111011, B11111010, B11101111, B11101110, B11101011, B11101010, + B10111111, B10111110, B10111011, B10111010, B10101111, B10101110, B10101011, B10101010}; + const uint8_t LUTB[16] = {B11111111, B11111101, B11110111, B11110101, B11011111, B11011101, B11010111, B11010101, + B01111111, B01111101, B01110111, B01110101, B01011111, B01011101, B01010111, B01010101}; + const uint8_t pixelMaskLUT[8] = {B00000001, B00000010, B00000100, B00001000, + B00010000, B00100000, B01000000, B10000000}; + const uint8_t pixelMaskGLUT[2] = {B00001111, B11110000}; + const uint8_t waveform3Bit[8][8] = {{0, 0, 0, 0, 1, 1, 1, 0}, {1, 2, 2, 2, 1, 1, 1, 0}, {0, 1, 2, 1, 1, 2, 1, 0}, + {0, 2, 1, 2, 1, 2, 1, 0}, {0, 0, 0, 1, 1, 1, 2, 0}, {2, 1, 1, 1, 2, 1, 2, 0}, + {1, 1, 1, 2, 1, 2, 2, 0}, {0, 0, 0, 0, 0, 0, 2, 0}}; + const uint32_t waveform[50] = { + 0x00000008, 0x00000008, 0x00200408, 0x80281888, 0x60a81898, 0x60a8a8a8, 0x60a8a8a8, 0x6068a868, 0x6868a868, + 0x6868a868, 0x68686868, 0x6a686868, 0x5a686868, 0x5a686868, 0x5a586a68, 0x5a5a6a68, 0x5a5a6a68, 0x55566a68, + 0x55565a64, 0x55555654, 0x55555556, 0x55555556, 0x55555556, 0x55555516, 0x55555596, 0x15555595, 0x95955595, + 0x95959595, 0x95949495, 0x94949495, 0x94949495, 0xa4949494, 0x9494a4a4, 0x84a49494, 0x84948484, 0x84848484, + 0x84848484, 0x84848484, 0xa5a48484, 0xa9a4a4a8, 0xa9a8a8a8, 0xa5a9a9a4, 0xa5a5a5a4, 0xa1a5a5a1, 0xa9a9a9a9, + 0xa9a9a9a9, 0xa9a9a9a9, 0xa9a9a9a9, 0x15151515, 0x11111111}; + + void set_greyscale(bool greyscale) { + this->greyscale_ = greyscale; + this->initialize_(); + this->block_partial_ = true; + } + void set_partial_updating(bool partial_updating) { this->partial_updating_ = partial_updating; } + void set_full_update_every(uint32_t full_update_every) { this->full_update_every_ = full_update_every; } + + void set_display_data_0_pin(GPIOPin *data) { this->display_data_0_pin_ = data; } + void set_display_data_1_pin(GPIOPin *data) { this->display_data_1_pin_ = data; } + void set_display_data_2_pin(GPIOPin *data) { this->display_data_2_pin_ = data; } + void set_display_data_3_pin(GPIOPin *data) { this->display_data_3_pin_ = data; } + void set_display_data_4_pin(GPIOPin *data) { this->display_data_4_pin_ = data; } + void set_display_data_5_pin(GPIOPin *data) { this->display_data_5_pin_ = data; } + void set_display_data_6_pin(GPIOPin *data) { this->display_data_6_pin_ = data; } + void set_display_data_7_pin(GPIOPin *data) { this->display_data_7_pin_ = data; } + + void set_ckv_pin(GPIOPin *ckv) { this->ckv_pin_ = ckv; } + void set_cl_pin(GPIOPin *cl) { this->cl_pin_ = cl; } + void set_gpio0_enable_pin(GPIOPin *gpio0_enable) { this->gpio0_enable_pin_ = gpio0_enable; } + void set_gmod_pin(GPIOPin *gmod) { this->gmod_pin_ = gmod; } + void set_le_pin(GPIOPin *le) { this->le_pin_ = le; } + void set_oe_pin(GPIOPin *oe) { this->oe_pin_ = oe; } + void set_powerup_pin(GPIOPin *powerup) { this->powerup_pin_ = powerup; } + void set_sph_pin(GPIOPin *sph) { this->sph_pin_ = sph; } + void set_spv_pin(GPIOPin *spv) { this->spv_pin_ = spv; } + void set_vcom_pin(GPIOPin *vcom) { this->vcom_pin_ = vcom; } + void set_wakeup_pin(GPIOPin *wakeup) { this->wakeup_pin_ = wakeup; } + + float get_setup_priority() const override; + + void dump_config() override; + + void display(); + void clean(); + void fill(Color color) override; + + void update() override; + + void setup() override; + + uint8_t get_panel_state() { return this->panel_on_; } + bool get_greyscale() { return this->greyscale_; } + bool get_partial_updating() { return this->partial_updating_; } + uint8_t get_temperature() { return this->temperature_; } + + protected: + void draw_absolute_pixel_internal(int x, int y, Color color) override; + void display1b_(); + void display3b_(); + void initialize_(); + bool partial_update_(); + void clean_fast_(uint8_t c, uint8_t rep); + + void hscan_start_(uint32_t d); + void vscan_end_(); + void vscan_start_(); + void vscan_write_(); + + void eink_off_(); + void eink_on_(); + + void setup_pins_(); + void pins_z_state_(); + void pins_as_outputs_(); + + int get_width_internal() override { return 800; } + + int get_height_internal() override { return 600; } + + size_t get_buffer_length_(); + + int get_data_pin_mask_() { + int data = 0; + data |= (1 << this->display_data_0_pin_->get_pin()); + data |= (1 << this->display_data_1_pin_->get_pin()); + data |= (1 << this->display_data_2_pin_->get_pin()); + data |= (1 << this->display_data_3_pin_->get_pin()); + data |= (1 << this->display_data_4_pin_->get_pin()); + data |= (1 << this->display_data_5_pin_->get_pin()); + data |= (1 << this->display_data_6_pin_->get_pin()); + data |= (1 << this->display_data_7_pin_->get_pin()); + return data; + } + + uint8_t panel_on_ = 0; + uint8_t temperature_; + + uint8_t *partial_buffer_{nullptr}; + uint8_t *partial_buffer_2_{nullptr}; + + uint32_t full_update_every_; + uint32_t partial_updates_{0}; + + bool block_partial_; + bool greyscale_; + bool partial_updating_; + + GPIOPin *display_data_0_pin_; + GPIOPin *display_data_1_pin_; + GPIOPin *display_data_2_pin_; + GPIOPin *display_data_3_pin_; + GPIOPin *display_data_4_pin_; + GPIOPin *display_data_5_pin_; + GPIOPin *display_data_6_pin_; + GPIOPin *display_data_7_pin_; + + GPIOPin *ckv_pin_; + GPIOPin *cl_pin_; + GPIOPin *gpio0_enable_pin_; + GPIOPin *gmod_pin_; + GPIOPin *le_pin_; + GPIOPin *oe_pin_; + GPIOPin *powerup_pin_; + GPIOPin *sph_pin_; + GPIOPin *spv_pin_; + GPIOPin *vcom_pin_; + GPIOPin *wakeup_pin_; +}; + +} // namespace inkplate6 +} // namespace esphome + +#endif From 23c663d5d4b5182da9fe1c532f60ce1db65d494f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sun, 17 Jan 2021 17:06:38 +1300 Subject: [PATCH 8/8] Bump version to v1.16.0b4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index a650954a95..4a5dd2665e 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 16 -PATCH_VERSION = '0b3' +PATCH_VERSION = '0b4' __short_version__ = f'{MAJOR_VERSION}.{MINOR_VERSION}' __version__ = f'{__short_version__}.{PATCH_VERSION}'