Merge pull request #1475 from esphome/bump-1.16.0b4

1.16.0b4
This commit is contained in:
Jesse Hills 2021-01-17 17:20:36 +13:00 committed by GitHub
commit 828f7946ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 3572 additions and 1194 deletions

View file

@ -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
@ -48,6 +49,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
@ -55,6 +57,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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1 @@
CODEOWNERS = ['@jesserockz']

View file

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

View file

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

View file

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

View file

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

View file

@ -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<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
spi::DATA_RATE_1MHZ> { // 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

View file

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

View file

@ -0,0 +1,7 @@
import esphome.codegen as cg
CODEOWNERS = ['@jesserockz']
nfc_ns = cg.esphome_ns.namespace('nfc')
NfcTag = nfc_ns.class_('NfcTag')

View file

@ -0,0 +1,106 @@
#include "ndef_message.h"
namespace esphome {
namespace nfc {
static const char *TAG = "nfc.ndef_message";
NdefMessage::NdefMessage(std::vector<uint8_t> &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<uint32_t>(data[index]) << 24) | (static_cast<uint32_t>(data[index + 1]) << 16) |
(static_cast<uint32_t>(data[index + 2]) << 8) | static_cast<uint32_t>(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<uint8_t> NdefMessage::encode() {
std::vector<uint8_t> 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

View file

@ -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<uint8_t> &data);
std::vector<NdefRecord *> 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<uint8_t> encode();
protected:
std::vector<NdefRecord *> records_;
};
} // namespace nfc
} // namespace esphome

View file

@ -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<uint8_t> NdefRecord::encode(bool first, bool last) {
std::vector<uint8_t> 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

View file

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

View file

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

View file

@ -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<uint8_t> &uid);
std::string format_bytes(std::vector<uint8_t> &bytes);
uint8_t guess_tag_type(uint8_t uid_length);
uint8_t get_mifare_classic_ndef_start_index(std::vector<uint8_t> &data);
bool decode_mifare_classic_tlv(std::vector<uint8_t> &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

View file

@ -0,0 +1,9 @@
#include "nfc_tag.h"
namespace esphome {
namespace nfc {
static const char *TAG = "nfc.tag";
} // namespace nfc
} // namespace esphome

View file

@ -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<uint8_t> &uid) {
this->uid_ = uid;
this->tag_type_ = "Unknown";
};
NfcTag(std::vector<uint8_t> &uid, const std::string &tag_type) {
this->uid_ = uid;
this->tag_type_ = tag_type;
};
NfcTag(std::vector<uint8_t> &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<uint8_t> &uid, const std::string &tag_type, std::vector<uint8_t> &ndef_data) {
this->uid_ = uid;
this->tag_type_ = tag_type;
this->ndef_message_ = new NdefMessage(ndef_data);
};
std::vector<uint8_t> &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<uint8_t> uid_;
std::string tag_type_;
NdefMessage *ndef_message_{nullptr};
};
} // namespace nfc
} // namespace esphome

View file

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

View file

@ -11,18 +11,6 @@ namespace pn532 {
static const char *TAG = "pn532";
std::string format_uid(std::vector<uint8_t> &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<uint8_t> &data) {
@ -208,6 +229,22 @@ bool PN532::write_command_(const std::vector<uint8_t> &data) {
return this->read_ack_();
}
bool PN532::read_ack_() {
ESP_LOGVV(TAG, "Reading ACK...");
std::vector<uint8_t> 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<uint8_t> &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<uint8_t> &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<uint8_t> data;
if (!this->read_data(data, 6)) {
return false;
nfc::NfcTag *PN532::read_tag_(std::vector<uint8_t> &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<uint8_t> &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<uint8_t> &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<uint8_t> &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<uint8_t> &data) {
this->found_ = true;
return true;
}
void PN532Trigger::process(std::vector<uint8_t> &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

View file

@ -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<void()> 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<uint8_t> &data) = 0;
virtual bool read_data(std::vector<uint8_t> &data, uint8_t len) = 0;
nfc::NfcTag *read_tag_(std::vector<uint8_t> &uid);
bool format_tag_(std::vector<uint8_t> &uid);
bool clean_tag_(std::vector<uint8_t> &uid);
bool write_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message);
nfc::NfcTag *read_mifare_classic_tag_(std::vector<uint8_t> &uid);
bool read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
bool write_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
bool auth_mifare_classic_block_(std::vector<uint8_t> &uid, uint8_t block_num, uint8_t key_num, const uint8_t *key);
bool format_mifare_classic_mifare_(std::vector<uint8_t> &uid);
bool format_mifare_classic_ndef_(std::vector<uint8_t> &uid);
bool write_mifare_classic_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message);
nfc::NfcTag *read_mifare_ultralight_tag_(std::vector<uint8_t> &uid);
bool read_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &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<uint8_t> &write_data);
bool write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message);
bool clean_mifare_ultralight_();
bool requested_read_{false};
std::vector<PN532BinarySensor *> binary_sensors_;
std::vector<PN532Trigger *> triggers_;
std::vector<PN532OnTagTrigger *> triggers_;
std::vector<uint8_t> 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<void()> 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<std::string> {
class PN532OnTagTrigger : public Trigger<std::string, nfc::NfcTag> {
public:
void process(std::vector<uint8_t> &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<typename... Ts> class PN532IsWritingCondition : public Condition<Ts...>, public Parented<PN532> {
public:
bool check(Ts... x) override { return this->parent_->is_writing(); }
};
} // namespace pn532

View file

@ -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<uint8_t> &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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> &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<uint8_t> &uid, uint8_t block_num, uint8_t key_num,
const uint8_t *key) {
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> &uid) {
std::vector<uint8_t> blank_buffer(
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
std::vector<uint8_t> 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<uint8_t> &uid) {
std::vector<uint8_t> empty_ndef_message(
{0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
std::vector<uint8_t> blank_block(
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
std::vector<uint8_t> block_1_data(
{0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1});
std::vector<uint8_t> block_2_data(
{0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1});
std::vector<uint8_t> block_3_trailer(
{0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
std::vector<uint8_t> 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<uint8_t> &write_data) {
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> &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<uint8_t> 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

View file

@ -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<uint8_t> &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<uint8_t> data;
uint8_t index = 0;
for (uint8_t page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; page < nfc::MIFARE_ULTRALIGHT_MAX_PAGE; page++) {
std::vector<uint8_t> 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<uint8_t> &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<uint8_t> 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<uint8_t> 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<uint8_t> data;
for (int page = 4; page < 6; page++) {
std::vector<uint8_t> 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<uint8_t> &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<uint8_t> 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<uint8_t> 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<uint8_t> &write_data) {
std::vector<uint8_t> 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<uint8_t> 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

View file

@ -14,7 +14,7 @@ static const char *TAG = "pn532_i2c";
bool PN532I2C::write_data(const std::vector<uint8_t> &data) { return this->write_bytes_raw(data.data(), data.size()); }
bool PN532I2C::read_data(std::vector<uint8_t> &data, uint8_t len) {
delay(5);
delay(1);
std::vector<uint8_t> ready;
ready.resize(1);

View file

@ -0,0 +1,38 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation, pins
from esphome.components import i2c
from esphome.const import CONF_ON_TAG, CONF_TRIGGER_ID, CONF_RESET_PIN
from esphome.core import coroutine
CODEOWNERS = ['@glmnet']
AUTO_LOAD = ['binary_sensor']
MULTI_CONF = True
CONF_RC522_ID = 'rc522_id'
rc522_ns = cg.esphome_ns.namespace('rc522')
RC522 = rc522_ns.class_('RC522', cg.PollingComponent, i2c.I2CDevice)
RC522Trigger = rc522_ns.class_('RC522Trigger', automation.Trigger.template(cg.std_string))
RC522_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(RC522),
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_ON_TAG): automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RC522Trigger),
}),
}).extend(cv.polling_component_schema('1s'))
@coroutine
def setup_rc522(var, config):
yield cg.register_component(var, config)
if CONF_RESET_PIN in config:
reset = yield cg.gpio_pin_expression(config[CONF_RESET_PIN])
cg.add(var.set_reset_pin(reset))
for conf in config.get(CONF_ON_TAG, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
cg.add(var.register_trigger(trigger))
yield automation.build_automation(trigger, [(cg.std_string, 'x')], conf)

View file

@ -0,0 +1,43 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
from esphome.const import CONF_UID, CONF_ID
from esphome.core import HexInt, coroutine
from . import rc522_ns, RC522, CONF_RC522_ID
DEPENDENCIES = ['rc522']
def validate_uid(value):
value = cv.string_strict(value)
for x in value.split('-'):
if len(x) != 2:
raise cv.Invalid("Each part (separated by '-') of the UID must be two characters "
"long.")
try:
x = int(x, 16)
except ValueError as err:
raise cv.Invalid("Valid characters for parts of a UID are 0123456789ABCDEF.") from err
if x < 0 or x > 255:
raise cv.Invalid("Valid values for UID parts (separated by '-') are 00 to FF")
return value
RC522BinarySensor = rc522_ns.class_('RC522BinarySensor', binary_sensor.BinarySensor)
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(RC522BinarySensor),
cv.GenerateID(CONF_RC522_ID): cv.use_id(RC522),
cv.Required(CONF_UID): validate_uid,
})
@coroutine
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield binary_sensor.register_binary_sensor(var, config)
hub = yield cg.get_variable(config[CONF_RC522_ID])
cg.add(hub.register_tag(var))
addr = [HexInt(int(x, 16)) for x in config[CONF_UID].split('-')]
cg.add(var.set_uid(addr))

View file

@ -0,0 +1,758 @@
#include "rc522.h"
#include "esphome/core/log.h"
// Based on:
// - https://github.com/miguelbalboa/rfid
namespace esphome {
namespace rc522 {
static const char *TAG = "rc522";
static const uint8_t RESET_COUNT = 5;
void format_uid(char *buf, const uint8_t *uid, uint8_t uid_length) {
int offset = 0;
for (uint8_t i = 0; i < uid_length; i++) {
const char *format = "%02X";
if (i + 1 < uid_length)
format = "%02X-";
offset += sprintf(buf + offset, format, uid[i]);
}
}
void RC522::setup() {
initialize_pending_ = true;
// Pull device out of power down / reset state.
// First set the resetPowerDownPin as digital input, to check the MFRC522 power down mode.
if (reset_pin_ != nullptr) {
reset_pin_->pin_mode(INPUT);
if (reset_pin_->digital_read() == LOW) { // The MFRC522 chip is in power down mode.
ESP_LOGV(TAG, "Power down mode detected. Hard resetting...");
reset_pin_->pin_mode(OUTPUT); // Now set the resetPowerDownPin as digital output.
reset_pin_->digital_write(LOW); // Make sure we have a clean LOW state.
delayMicroseconds(2); // 8.8.1 Reset timing requirements says about 100ns. Let us be generous: 2μsl
reset_pin_->digital_write(HIGH); // Exit power down mode. This triggers a hard reset.
// Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs.
// Let us be generous: 50ms.
reset_timeout_ = millis();
return;
}
}
// Setup a soft reset
reset_count_ = RESET_COUNT;
reset_timeout_ = millis();
}
void RC522::initialize_() {
// Per originall code, wait 50 ms
if (millis() - reset_timeout_ < 50)
return;
// Reset baud rates
ESP_LOGV(TAG, "Initialize");
pcd_write_register(TX_MODE_REG, 0x00);
pcd_write_register(RX_MODE_REG, 0x00);
// Reset ModWidthReg
pcd_write_register(MOD_WIDTH_REG, 0x26);
// When communicating with a PICC we need a timeout if something goes wrong.
// f_timer = 13.56 MHz / (2*TPreScaler+1) where TPreScaler = [TPrescaler_Hi:TPrescaler_Lo].
// TPrescaler_Hi are the four low bits in TModeReg. TPrescaler_Lo is TPrescalerReg.
pcd_write_register(T_MODE_REG, 0x80); // TAuto=1; timer starts automatically at the end of the transmission in all
// communication modes at all speeds
// TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9 = 169 => f_timer=40kHz, ie a timer period of 25μs.
pcd_write_register(T_PRESCALER_REG, 0xA9);
pcd_write_register(T_RELOAD_REG_H, 0x03); // Reload timer with 0x3E8 = 1000, ie 25ms before timeout.
pcd_write_register(T_RELOAD_REG_L, 0xE8);
// Default 0x00. Force a 100 % ASK modulation independent of the ModGsPReg register setting
pcd_write_register(TX_ASK_REG, 0x40);
pcd_write_register(MODE_REG, 0x3D); // Default 0x3F. Set the preset value for the CRC coprocessor for the CalcCRC
// command to 0x6363 (ISO 14443-3 part 6.2.4)
pcd_antenna_on_(); // Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset)
initialize_pending_ = false;
}
void RC522::dump_config() {
ESP_LOGCONFIG(TAG, "RC522:");
switch (this->error_code_) {
case NONE:
break;
case RESET_FAILED:
ESP_LOGE(TAG, "Reset command failed!");
break;
}
LOG_PIN(" RESET Pin: ", this->reset_pin_);
LOG_UPDATE_INTERVAL(this);
for (auto *child : this->binary_sensors_) {
LOG_BINARY_SENSOR(" ", "Tag", child);
}
}
void RC522::loop() {
// First check reset is needed
if (reset_count_ > 0) {
pcd_reset_();
return;
}
if (initialize_pending_) {
initialize_();
return;
}
if (millis() - update_wait_ < this->update_interval_)
return;
auto status = picc_is_new_card_present_();
static StatusCode LAST_STATUS = StatusCode::STATUS_OK;
if (status != LAST_STATUS) {
ESP_LOGD(TAG, "Status is now: %d", status);
LAST_STATUS = status;
}
if (status == STATUS_ERROR) // No card
{
// ESP_LOGE(TAG, "Error");
// mark_failed();
return;
}
if (status != STATUS_OK) // We can receive STATUS_TIMEOUT when no card, or unexpected status.
return;
// Try process card
if (!picc_read_card_serial_()) {
ESP_LOGW(TAG, "Requesting tag read failed!");
return;
};
if (uid_.size < 4) {
return;
ESP_LOGW(TAG, "Read serial size: %d", uid_.size);
}
update_wait_ = millis();
bool report = true;
// 1. Go through all triggers
for (auto *trigger : this->triggers_)
trigger->process(uid_.uiduint8_t, uid_.size);
// 2. Find a binary sensor
for (auto *tag : this->binary_sensors_) {
if (tag->process(uid_.uiduint8_t, uid_.size)) {
// 2.1 if found, do not dump
report = false;
}
}
if (report) {
char buf[32];
format_uid(buf, uid_.uiduint8_t, uid_.size);
ESP_LOGD(TAG, "Found new tag '%s'", buf);
}
}
void RC522::update() {
for (auto *obj : this->binary_sensors_)
obj->on_scan_end();
}
/**
* Performs a soft reset on the MFRC522 chip and waits for it to be ready again.
*/
void RC522::pcd_reset_() {
// The datasheet does not mention how long the SoftRest command takes to complete.
// But the MFRC522 might have been in soft power-down mode (triggered by bit 4 of CommandReg)
// Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs. Let
// us be generous: 50ms.
if (millis() - reset_timeout_ < 50)
return;
if (reset_count_ == RESET_COUNT) {
ESP_LOGV(TAG, "Soft reset...");
// Issue the SoftReset command.
pcd_write_register(COMMAND_REG, PCD_SOFT_RESET);
}
// Expect the PowerDown bit in CommandReg to be cleared (max 3x50ms)
if ((pcd_read_register(COMMAND_REG) & (1 << 4)) == 0) {
reset_count_ = 0;
ESP_LOGI(TAG, "Device online.");
// Wait for initialize
reset_timeout_ = millis();
return;
}
if (--reset_count_ == 0) {
ESP_LOGE(TAG, "Unable to reset RC522.");
mark_failed();
}
}
/**
* Turns the antenna on by enabling pins TX1 and TX2.
* After a reset these pins are disabled.
*/
void RC522::pcd_antenna_on_() {
uint8_t value = pcd_read_register(TX_CONTROL_REG);
if ((value & 0x03) != 0x03) {
pcd_write_register(TX_CONTROL_REG, value | 0x03);
}
}
/**
* Transmits a REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or
* selection. 7 bit frame. Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT -
* probably due do bad antenna design.
*
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
RC522::StatusCode RC522::picc_request_a_(
uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in
uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK.
) {
return picc_reqa_or_wupa_(PICC_CMD_REQA, buffer_atqa, buffer_size);
}
/**
* Transmits REQA or WUPA commands.
* Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna
* design.
*
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
RC522::StatusCode RC522::picc_reqa_or_wupa_(
uint8_t command, ///< The command to send - PICC_CMD_REQA or PICC_CMD_WUPA
uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in
uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK.
) {
uint8_t valid_bits;
RC522::StatusCode status;
if (buffer_atqa == nullptr || *buffer_size < 2) { // The ATQA response is 2 uint8_ts long.
return STATUS_NO_ROOM;
}
pcd_clear_register_bit_mask_(COLL_REG, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared.
valid_bits = 7; // For REQA and WUPA we need the short frame format - transmit only 7 bits of the last (and only)
// uint8_t. TxLastBits = BitFramingReg[2..0]
status = pcd_transceive_data_(&command, 1, buffer_atqa, buffer_size, &valid_bits);
if (status != STATUS_OK)
return status;
if (*buffer_size != 2 || valid_bits != 0) { // ATQA must be exactly 16 bits.
ESP_LOGVV(TAG, "picc_reqa_or_wupa_() -> STATUS_ERROR");
return STATUS_ERROR;
}
return STATUS_OK;
}
/**
* Sets the bits given in mask in register reg.
*/
void RC522::pcd_set_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums.
uint8_t mask ///< The bits to set.
) {
uint8_t tmp = pcd_read_register(reg);
pcd_write_register(reg, tmp | mask); // set bit mask
}
/**
* Clears the bits given in mask from register reg.
*/
void RC522::pcd_clear_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums.
uint8_t mask ///< The bits to clear.
) {
uint8_t tmp = pcd_read_register(reg);
pcd_write_register(reg, tmp & (~mask)); // clear bit mask
}
/**
* Executes the Transceive command.
* CRC validation can only be done if backData and backLen are specified.
*
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
RC522::StatusCode RC522::pcd_transceive_data_(
uint8_t *send_data, ///< Pointer to the data to transfer to the FIFO.
uint8_t send_len, ///< Number of uint8_ts to transfer to the FIFO.
uint8_t *back_data, ///< nullptr or pointer to buffer if data should be read back after executing the command.
uint8_t *back_len, ///< In: Max number of uint8_ts to write to *backData. Out: The number of uint8_ts returned.
uint8_t
*valid_bits, ///< In/Out: The number of valid bits in the last uint8_t. 0 for 8 valid bits. Default nullptr.
uint8_t rx_align, ///< In: Defines the bit position in backData[0] for the first bit received. Default 0.
bool check_crc ///< In: True => The last two uint8_ts of the response is assumed to be a CRC_A that must be
///< validated.
) {
uint8_t wait_i_rq = 0x30; // RxIRq and IdleIRq
auto ret = pcd_communicate_with_picc_(PCD_TRANSCEIVE, wait_i_rq, send_data, send_len, back_data, back_len, valid_bits,
rx_align, check_crc);
if (ret == STATUS_OK && *back_len == 5)
ESP_LOGVV(TAG, "pcd_transceive_data_(..., %d, ) -> %d [%x, %x, %x, %x, %x]", send_len, ret, back_data[0],
back_data[1], back_data[2], back_data[3], back_data[4]);
else
ESP_LOGVV(TAG, "pcd_transceive_data_(..., %d, ... ) -> %d", send_len, ret);
return ret;
}
/**
* Transfers data to the MFRC522 FIFO, executes a command, waits for completion and transfers data back from the FIFO.
* CRC validation can only be done if backData and backLen are specified.
*
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
RC522::StatusCode RC522::pcd_communicate_with_picc_(
uint8_t command, ///< The command to execute. One of the PCD_Command enums.
uint8_t wait_i_rq, ///< The bits in the ComIrqReg register that signals successful completion of the command.
uint8_t *send_data, ///< Pointer to the data to transfer to the FIFO.
uint8_t send_len, ///< Number of uint8_ts to transfer to the FIFO.
uint8_t *back_data, ///< nullptr or pointer to buffer if data should be read back after executing the command.
uint8_t *back_len, ///< In: Max number of uint8_ts to write to *backData. Out: The number of uint8_ts returned.
uint8_t *valid_bits, ///< In/Out: The number of valid bits in the last uint8_t. 0 for 8 valid bits.
uint8_t rx_align, ///< In: Defines the bit position in backData[0] for the first bit received. Default 0.
bool check_crc ///< In: True => The last two uint8_ts of the response is assumed to be a CRC_A that must be
///< validated.
) {
ESP_LOGVV(TAG, "pcd_communicate_with_picc_(%d, %d,... %d)", command, wait_i_rq, check_crc);
// Prepare values for BitFramingReg
uint8_t tx_last_bits = valid_bits ? *valid_bits : 0;
uint8_t bit_framing =
(rx_align << 4) + tx_last_bits; // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0]
pcd_write_register(COMMAND_REG, PCD_IDLE); // Stop any active command.
pcd_write_register(COM_IRQ_REG, 0x7F); // Clear all seven interrupt request bits
pcd_write_register(FIFO_LEVEL_REG, 0x80); // FlushBuffer = 1, FIFO initialization
pcd_write_register(FIFO_DATA_REG, send_len, send_data); // Write sendData to the FIFO
pcd_write_register(BIT_FRAMING_REG, bit_framing); // Bit adjustments
pcd_write_register(COMMAND_REG, command); // Execute the command
if (command == PCD_TRANSCEIVE) {
pcd_set_register_bit_mask_(BIT_FRAMING_REG, 0x80); // StartSend=1, transmission of data starts
}
// Wait for the command to complete.
// In PCD_Init() we set the TAuto flag in TModeReg. This means the timer automatically starts when the PCD stops
// transmitting. Each iteration of the do-while-loop takes 17.86μs.
// TODO check/modify for other architectures than Arduino Uno 16bit
uint16_t i;
for (i = 4; i > 0; i--) {
uint8_t n = pcd_read_register(
COM_IRQ_REG); // ComIrqReg[7..0] bits are: Set1 TxIRq RxIRq IdleIRq HiAlertIRq LoAlertIRq ErrIRq TimerIRq
if (n & wait_i_rq) { // One of the interrupts that signal success has been set.
break;
}
if (n & 0x01) { // Timer interrupt - nothing received in 25ms
return STATUS_TIMEOUT;
}
}
// 35.7ms and nothing happend. Communication with the MFRC522 might be down.
if (i == 0) {
return STATUS_TIMEOUT;
}
// Stop now if any errors except collisions were detected.
uint8_t error_reg_value = pcd_read_register(
ERROR_REG); // ErrorReg[7..0] bits are: WrErr TempErr reserved BufferOvfl CollErr CRCErr ParityErr ProtocolErr
if (error_reg_value & 0x13) { // BufferOvfl ParityErr ProtocolErr
return STATUS_ERROR;
}
uint8_t valid_bits_local = 0;
// If the caller wants data back, get it from the MFRC522.
if (back_data && back_len) {
uint8_t n = pcd_read_register(FIFO_LEVEL_REG); // Number of uint8_ts in the FIFO
if (n > *back_len) {
return STATUS_NO_ROOM;
}
*back_len = n; // Number of uint8_ts returned
pcd_read_register(FIFO_DATA_REG, n, back_data, rx_align); // Get received data from FIFO
valid_bits_local =
pcd_read_register(CONTROL_REG) & 0x07; // RxLastBits[2:0] indicates the number of valid bits in the last
// received uint8_t. If this value is 000b, the whole uint8_t is valid.
if (valid_bits) {
*valid_bits = valid_bits_local;
}
}
// Tell about collisions
if (error_reg_value & 0x08) { // CollErr
return STATUS_COLLISION;
}
// Perform CRC_A validation if requested.
if (back_data && back_len && check_crc) {
// In this case a MIFARE Classic NAK is not OK.
if (*back_len == 1 && valid_bits_local == 4) {
return STATUS_MIFARE_NACK;
}
// We need at least the CRC_A value and all 8 bits of the last uint8_t must be received.
if (*back_len < 2 || valid_bits_local != 0) {
return STATUS_CRC_WRONG;
}
// Verify CRC_A - do our own calculation and store the control in controlBuffer.
uint8_t control_buffer[2];
RC522::StatusCode status = pcd_calculate_crc_(&back_data[0], *back_len - 2, &control_buffer[0]);
if (status != STATUS_OK) {
return status;
}
if ((back_data[*back_len - 2] != control_buffer[0]) || (back_data[*back_len - 1] != control_buffer[1])) {
return STATUS_CRC_WRONG;
}
}
return STATUS_OK;
}
/**
* Use the CRC coprocessor in the MFRC522 to calculate a CRC_A.
*
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
RC522::StatusCode RC522::pcd_calculate_crc_(
uint8_t *data, ///< In: Pointer to the data to transfer to the FIFO for CRC calculation.
uint8_t length, ///< In: The number of uint8_ts to transfer.
uint8_t *result ///< Out: Pointer to result buffer. Result is written to result[0..1], low uint8_t first.
) {
ESP_LOGVV(TAG, "pcd_calculate_crc_(..., %d, ...)", length);
pcd_write_register(COMMAND_REG, PCD_IDLE); // Stop any active command.
pcd_write_register(DIV_IRQ_REG, 0x04); // Clear the CRCIRq interrupt request bit
pcd_write_register(FIFO_LEVEL_REG, 0x80); // FlushBuffer = 1, FIFO initialization
pcd_write_register(FIFO_DATA_REG, length, data); // Write data to the FIFO
pcd_write_register(COMMAND_REG, PCD_CALC_CRC); // Start the calculation
// Wait for the CRC calculation to complete. Each iteration of the while-loop takes 17.73μs.
// TODO check/modify for other architectures than Arduino Uno 16bit
// Wait for the CRC calculation to complete. Each iteration of the while-loop takes 17.73us.
for (uint16_t i = 5000; i > 0; i--) {
// DivIrqReg[7..0] bits are: Set2 reserved reserved MfinActIRq reserved CRCIRq reserved reserved
uint8_t n = pcd_read_register(DIV_IRQ_REG);
if (n & 0x04) { // CRCIRq bit set - calculation done
pcd_write_register(COMMAND_REG, PCD_IDLE); // Stop calculating CRC for new content in the FIFO.
// Transfer the result from the registers to the result buffer
result[0] = pcd_read_register(CRC_RESULT_REG_L);
result[1] = pcd_read_register(CRC_RESULT_REG_H);
ESP_LOGVV(TAG, "pcd_calculate_crc_() STATUS_OK");
return STATUS_OK;
}
}
ESP_LOGVV(TAG, "pcd_calculate_crc_() TIMEOUT");
// 89ms passed and nothing happend. Communication with the MFRC522 might be down.
return STATUS_TIMEOUT;
}
/**
* Returns STATUS_OK if a PICC responds to PICC_CMD_REQA.
* Only "new" cards in state IDLE are invited. Sleeping cards in state HALT are ignored.
*
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
RC522::StatusCode RC522::picc_is_new_card_present_() {
uint8_t buffer_atqa[2];
uint8_t buffer_size = sizeof(buffer_atqa);
// Reset baud rates
pcd_write_register(TX_MODE_REG, 0x00);
pcd_write_register(RX_MODE_REG, 0x00);
// Reset ModWidthReg
pcd_write_register(MOD_WIDTH_REG, 0x26);
auto result = picc_request_a_(buffer_atqa, &buffer_size);
ESP_LOGV(TAG, "picc_is_new_card_present_() -> %d", result);
return result;
}
/**
* Simple wrapper around PICC_Select.
* Returns true if a UID could be read.
* Remember to call PICC_IsNewCardPresent(), PICC_RequestA() or PICC_WakeupA() first.
* The read UID is available in the class variable uid.
*
* @return bool
*/
bool RC522::picc_read_card_serial_() {
RC522::StatusCode result = picc_select_(&this->uid_);
ESP_LOGVV(TAG, "picc_select_(...) -> %d", result);
return (result == STATUS_OK);
}
/**
* Transmits SELECT/ANTICOLLISION commands to select a single PICC.
* Before calling this function the PICCs must be placed in the READY(*) state by calling PICC_RequestA() or
* PICC_WakeupA(). On success:
* - The chosen PICC is in state ACTIVE(*) and all other PICCs have returned to state IDLE/HALT. (Figure 7 of the
* ISO/IEC 14443-3 draft.)
* - The UID size and value of the chosen PICC is returned in *uid along with the SAK.
*
* A PICC UID consists of 4, 7 or 10 uint8_ts.
* Only 4 uint8_ts can be specified in a SELECT command, so for the longer UIDs two or three iterations are used:
* UID size Number of UID uint8_ts Cascade levels Example of PICC
* ======== =================== ============== ===============
* single 4 1 MIFARE Classic
* double 7 2 MIFARE Ultralight
* triple 10 3 Not currently in use?
*
* @return STATUS_OK on success, STATUS_??? otherwise.
*/
RC522::StatusCode RC522::picc_select_(
Uid *uid, ///< Pointer to Uid struct. Normally output, but can also be used to supply a known UID.
uint8_t valid_bits ///< The number of known UID bits supplied in *uid. Normally 0. If set you must also supply
///< uid->size.
) {
bool uid_complete;
bool select_done;
bool use_cascade_tag;
uint8_t cascade_level = 1;
RC522::StatusCode result;
uint8_t count;
uint8_t check_bit;
uint8_t index;
uint8_t uid_index; // The first index in uid->uiduint8_t[] that is used in the current Cascade Level.
int8_t current_level_known_bits; // The number of known UID bits in the current Cascade Level.
uint8_t buffer[9]; // The SELECT/ANTICOLLISION commands uses a 7 uint8_t standard frame + 2 uint8_ts CRC_A
uint8_t buffer_used; // The number of uint8_ts used in the buffer, ie the number of uint8_ts to transfer to the FIFO.
uint8_t rx_align; // Used in BitFramingReg. Defines the bit position for the first bit received.
uint8_t tx_last_bits; // Used in BitFramingReg. The number of valid bits in the last transmitted uint8_t.
uint8_t *response_buffer;
uint8_t response_length;
// Description of buffer structure:
// uint8_t 0: SEL Indicates the Cascade Level: PICC_CMD_SEL_CL1, PICC_CMD_SEL_CL2 or PICC_CMD_SEL_CL3
// uint8_t 1: NVB Number of Valid Bits (in complete command, not just the UID): High nibble: complete
// uint8_ts,
// Low nibble: Extra bits. uint8_t 2: UID-data or CT See explanation below. CT means Cascade Tag. uint8_t
// 3: UID-data uint8_t 4: UID-data uint8_t 5: UID-data uint8_t 6: BCC Block Check Character - XOR of
// uint8_ts 2-5 uint8_t 7: CRC_A uint8_t 8: CRC_A The BCC and CRC_A are only transmitted if we know all the UID bits
// of the current Cascade Level.
//
// Description of uint8_ts 2-5: (Section 6.5.4 of the ISO/IEC 14443-3 draft: UID contents and cascade levels)
// UID size Cascade level uint8_t2 uint8_t3 uint8_t4 uint8_t5
// ======== ============= ===== ===== ===== =====
// 4 uint8_ts 1 uid0 uid1 uid2 uid3
// 7 uint8_ts 1 CT uid0 uid1 uid2
// 2 uid3 uid4 uid5 uid6
// 10 uint8_ts 1 CT uid0 uid1 uid2
// 2 CT uid3 uid4 uid5
// 3 uid6 uid7 uid8 uid9
// Sanity checks
if (valid_bits > 80) {
return STATUS_INVALID;
}
ESP_LOGVV(TAG, "picc_select_(&, %d)", valid_bits);
// Prepare MFRC522
pcd_clear_register_bit_mask_(COLL_REG, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared.
// Repeat Cascade Level loop until we have a complete UID.
uid_complete = false;
while (!uid_complete) {
// Set the Cascade Level in the SEL uint8_t, find out if we need to use the Cascade Tag in uint8_t 2.
switch (cascade_level) {
case 1:
buffer[0] = PICC_CMD_SEL_CL1;
uid_index = 0;
use_cascade_tag = valid_bits && uid->size > 4; // When we know that the UID has more than 4 uint8_ts
break;
case 2:
buffer[0] = PICC_CMD_SEL_CL2;
uid_index = 3;
use_cascade_tag = valid_bits && uid->size > 7; // When we know that the UID has more than 7 uint8_ts
break;
case 3:
buffer[0] = PICC_CMD_SEL_CL3;
uid_index = 6;
use_cascade_tag = false; // Never used in CL3.
break;
default:
return STATUS_INTERNAL_ERROR;
break;
}
// How many UID bits are known in this Cascade Level?
current_level_known_bits = valid_bits - (8 * uid_index);
if (current_level_known_bits < 0) {
current_level_known_bits = 0;
}
// Copy the known bits from uid->uiduint8_t[] to buffer[]
index = 2; // destination index in buffer[]
if (use_cascade_tag) {
buffer[index++] = PICC_CMD_CT;
}
uint8_t uint8_ts_to_copy = current_level_known_bits / 8 +
(current_level_known_bits % 8
? 1
: 0); // The number of uint8_ts needed to represent the known bits for this level.
if (uint8_ts_to_copy) {
uint8_t maxuint8_ts =
use_cascade_tag ? 3 : 4; // Max 4 uint8_ts in each Cascade Level. Only 3 left if we use the Cascade Tag
if (uint8_ts_to_copy > maxuint8_ts) {
uint8_ts_to_copy = maxuint8_ts;
}
for (count = 0; count < uint8_ts_to_copy; count++) {
buffer[index++] = uid->uiduint8_t[uid_index + count];
}
}
// Now that the data has been copied we need to include the 8 bits in CT in currentLevelKnownBits
if (use_cascade_tag) {
current_level_known_bits += 8;
}
// Repeat anti collision loop until we can transmit all UID bits + BCC and receive a SAK - max 32 iterations.
select_done = false;
while (!select_done) {
// Find out how many bits and uint8_ts to send and receive.
if (current_level_known_bits >= 32) { // All UID bits in this Cascade Level are known. This is a SELECT.
if (response_length < 4) {
ESP_LOGW(TAG, "Not enough data received.");
return STATUS_INVALID;
}
// Serial.print(F("SELECT: currentLevelKnownBits=")); Serial.println(currentLevelKnownBits, DEC);
buffer[1] = 0x70; // NVB - Number of Valid Bits: Seven whole uint8_ts
// Calculate BCC - Block Check Character
buffer[6] = buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5];
// Calculate CRC_A
result = pcd_calculate_crc_(buffer, 7, &buffer[7]);
if (result != STATUS_OK) {
return result;
}
tx_last_bits = 0; // 0 => All 8 bits are valid.
buffer_used = 9;
// Store response in the last 3 uint8_ts of buffer (BCC and CRC_A - not needed after tx)
response_buffer = &buffer[6];
response_length = 3;
} else { // This is an ANTICOLLISION.
// Serial.print(F("ANTICOLLISION: currentLevelKnownBits=")); Serial.println(currentLevelKnownBits, DEC);
tx_last_bits = current_level_known_bits % 8;
count = current_level_known_bits / 8; // Number of whole uint8_ts in the UID part.
index = 2 + count; // Number of whole uint8_ts: SEL + NVB + UIDs
buffer[1] = (index << 4) + tx_last_bits; // NVB - Number of Valid Bits
buffer_used = index + (tx_last_bits ? 1 : 0);
// Store response in the unused part of buffer
response_buffer = &buffer[index];
response_length = sizeof(buffer) - index;
}
// Set bit adjustments
rx_align = tx_last_bits; // Having a separate variable is overkill. But it makes the next line easier to read.
pcd_write_register(
BIT_FRAMING_REG,
(rx_align << 4) + tx_last_bits); // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0]
// Transmit the buffer and receive the response.
result = pcd_transceive_data_(buffer, buffer_used, response_buffer, &response_length, &tx_last_bits, rx_align);
if (result == STATUS_COLLISION) { // More than one PICC in the field => collision.
uint8_t value_of_coll_reg = pcd_read_register(
COLL_REG); // CollReg[7..0] bits are: ValuesAfterColl reserved CollPosNotValid CollPos[4:0]
if (value_of_coll_reg & 0x20) { // CollPosNotValid
return STATUS_COLLISION; // Without a valid collision position we cannot continue
}
uint8_t collision_pos = value_of_coll_reg & 0x1F; // Values 0-31, 0 means bit 32.
if (collision_pos == 0) {
collision_pos = 32;
}
if (collision_pos <= current_level_known_bits) { // No progress - should not happen
return STATUS_INTERNAL_ERROR;
}
// Choose the PICC with the bit set.
current_level_known_bits = collision_pos;
count = current_level_known_bits % 8; // The bit to modify
check_bit = (current_level_known_bits - 1) % 8;
index = 1 + (current_level_known_bits / 8) + (count ? 1 : 0); // First uint8_t is index 0.
if (response_length > 2) // Note: Otherwise buffer[index] might be not initialized
buffer[index] |= (1 << check_bit);
} else if (result != STATUS_OK) {
return result;
} else { // STATUS_OK
if (current_level_known_bits >= 32) { // This was a SELECT.
select_done = true; // No more anticollision
// We continue below outside the while.
} else { // This was an ANTICOLLISION.
// We now have all 32 bits of the UID in this Cascade Level
current_level_known_bits = 32;
// Run loop again to do the SELECT.
}
}
} // End of while (!selectDone)
// We do not check the CBB - it was constructed by us above.
// Copy the found UID uint8_ts from buffer[] to uid->uiduint8_t[]
index = (buffer[2] == PICC_CMD_CT) ? 3 : 2; // source index in buffer[]
uint8_ts_to_copy = (buffer[2] == PICC_CMD_CT) ? 3 : 4;
for (count = 0; count < uint8_ts_to_copy; count++) {
uid->uiduint8_t[uid_index + count] = buffer[index++];
}
// Check response SAK (Select Acknowledge)
if (response_length != 3 || tx_last_bits != 0) { // SAK must be exactly 24 bits (1 uint8_t + CRC_A).
return STATUS_ERROR;
}
// Verify CRC_A - do our own calculation and store the control in buffer[2..3] - those uint8_ts are not needed
// anymore.
result = pcd_calculate_crc_(response_buffer, 1, &buffer[2]);
if (result != STATUS_OK) {
return result;
}
if ((buffer[2] != response_buffer[1]) || (buffer[3] != response_buffer[2])) {
return STATUS_CRC_WRONG;
}
if (response_buffer[0] & 0x04) { // Cascade bit set - UID not complete yes
cascade_level++;
} else {
uid_complete = true;
uid->sak = response_buffer[0];
}
} // End of while (!uidComplete)
// Set correct uid->size
uid->size = 3 * cascade_level + 1;
return STATUS_OK;
}
bool RC522BinarySensor::process(const uint8_t *data, uint8_t len) {
if (len != this->uid_.size())
return false;
for (uint8_t i = 0; i < len; i++) {
if (data[i] != this->uid_[i])
return false;
}
this->publish_state(true);
this->found_ = true;
return true;
}
void RC522Trigger::process(const uint8_t *uid, uint8_t uid_length) {
char buf[32];
format_uid(buf, uid, uid_length);
this->trigger(std::string(buf));
}
} // namespace rc522
} // namespace esphome

View file

@ -0,0 +1,284 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
namespace esphome {
namespace rc522 {
class RC522BinarySensor;
class RC522Trigger;
class RC522 : public PollingComponent {
public:
void setup() override;
void dump_config() override;
void update() override;
float get_setup_priority() const override { return setup_priority::DATA; };
void loop() override;
void register_tag(RC522BinarySensor *tag) { this->binary_sensors_.push_back(tag); }
void register_trigger(RC522Trigger *trig) { this->triggers_.push_back(trig); }
void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
protected:
enum PcdRegister : uint8_t {
// Page 0: Command and status
// 0x00 // reserved for future use
COMMAND_REG = 0x01 << 1, // starts and stops command execution
COM_I_EN_REG = 0x02 << 1, // enable and disable interrupt request control bits
DIV_I_EN_REG = 0x03 << 1, // enable and disable interrupt request control bits
COM_IRQ_REG = 0x04 << 1, // interrupt request bits
DIV_IRQ_REG = 0x05 << 1, // interrupt request bits
ERROR_REG = 0x06 << 1, // error bits showing the error status of the last command executed
STATUS1_REG = 0x07 << 1, // communication status bits
STATUS2_REG = 0x08 << 1, // receiver and transmitter status bits
FIFO_DATA_REG = 0x09 << 1, // input and output of 64 uint8_t FIFO buffer
FIFO_LEVEL_REG = 0x0A << 1, // number of uint8_ts stored in the FIFO buffer
WATER_LEVEL_REG = 0x0B << 1, // level for FIFO underflow and overflow warning
CONTROL_REG = 0x0C << 1, // miscellaneous control registers
BIT_FRAMING_REG = 0x0D << 1, // adjustments for bit-oriented frames
COLL_REG = 0x0E << 1, // bit position of the first bit-collision detected on the RF interface
// 0x0F // reserved for future use
// Page 1: Command
// 0x10 // reserved for future use
MODE_REG = 0x11 << 1, // defines general modes for transmitting and receiving
TX_MODE_REG = 0x12 << 1, // defines transmission data rate and framing
RX_MODE_REG = 0x13 << 1, // defines reception data rate and framing
TX_CONTROL_REG = 0x14 << 1, // controls the logical behavior of the antenna driver pins TX1 and TX2
TX_ASK_REG = 0x15 << 1, // controls the setting of the transmission modulation
TX_SEL_REG = 0x16 << 1, // selects the internal sources for the antenna driver
RX_SEL_REG = 0x17 << 1, // selects internal receiver settings
RX_THRESHOLD_REG = 0x18 << 1, // selects thresholds for the bit decoder
DEMOD_REG = 0x19 << 1, // defines demodulator settings
// 0x1A // reserved for future use
// 0x1B // reserved for future use
MF_TX_REG = 0x1C << 1, // controls some MIFARE communication transmit parameters
MF_RX_REG = 0x1D << 1, // controls some MIFARE communication receive parameters
// 0x1E // reserved for future use
SERIAL_SPEED_REG = 0x1F << 1, // selects the speed of the serial UART interface
// Page 2: Configuration
// 0x20 // reserved for future use
CRC_RESULT_REG_H = 0x21 << 1, // shows the MSB and LSB values of the CRC calculation
CRC_RESULT_REG_L = 0x22 << 1,
// 0x23 // reserved for future use
MOD_WIDTH_REG = 0x24 << 1, // controls the ModWidth setting?
// 0x25 // reserved for future use
RF_CFG_REG = 0x26 << 1, // configures the receiver gain
GS_N_REG = 0x27 << 1, // selects the conductance of the antenna driver pins TX1 and TX2 for modulation
CW_GS_P_REG = 0x28 << 1, // defines the conductance of the p-driver output during periods of no modulation
MOD_GS_P_REG = 0x29 << 1, // defines the conductance of the p-driver output during periods of modulation
T_MODE_REG = 0x2A << 1, // defines settings for the internal timer
T_PRESCALER_REG = 0x2B << 1, // the lower 8 bits of the TPrescaler value. The 4 high bits are in TModeReg.
T_RELOAD_REG_H = 0x2C << 1, // defines the 16-bit timer reload value
T_RELOAD_REG_L = 0x2D << 1,
T_COUNTER_VALUE_REG_H = 0x2E << 1, // shows the 16-bit timer value
T_COUNTER_VALUE_REG_L = 0x2F << 1,
// Page 3: Test Registers
// 0x30 // reserved for future use
TEST_SEL1_REG = 0x31 << 1, // general test signal configuration
TEST_SEL2_REG = 0x32 << 1, // general test signal configuration
TEST_PIN_EN_REG = 0x33 << 1, // enables pin output driver on pins D1 to D7
TEST_PIN_VALUE_REG = 0x34 << 1, // defines the values for D1 to D7 when it is used as an I/O bus
TEST_BUS_REG = 0x35 << 1, // shows the status of the internal test bus
AUTO_TEST_REG = 0x36 << 1, // controls the digital self-test
VERSION_REG = 0x37 << 1, // shows the software version
ANALOG_TEST_REG = 0x38 << 1, // controls the pins AUX1 and AUX2
TEST_DA_C1_REG = 0x39 << 1, // defines the test value for TestDAC1
TEST_DA_C2_REG = 0x3A << 1, // defines the test value for TestDAC2
TEST_ADC_REG = 0x3B << 1 // shows the value of ADC I and Q channels
// 0x3C // reserved for production tests
// 0x3D // reserved for production tests
// 0x3E // reserved for production tests
// 0x3F // reserved for production tests
};
// MFRC522 commands. Described in chapter 10 of the datasheet.
enum PcdCommand : uint8_t {
PCD_IDLE = 0x00, // no action, cancels current command execution
PCD_MEM = 0x01, // stores 25 uint8_ts into the internal buffer
PCD_GENERATE_RANDOM_ID = 0x02, // generates a 10-uint8_t random ID number
PCD_CALC_CRC = 0x03, // activates the CRC coprocessor or performs a self-test
PCD_TRANSMIT = 0x04, // transmits data from the FIFO buffer
PCD_NO_CMD_CHANGE = 0x07, // no command change, can be used to modify the CommandReg register bits without
// affecting the command, for example, the PowerDown bit
PCD_RECEIVE = 0x08, // activates the receiver circuits
PCD_TRANSCEIVE =
0x0C, // transmits data from FIFO buffer to antenna and automatically activates the receiver after transmission
PCD_MF_AUTHENT = 0x0E, // performs the MIFARE standard authentication as a reader
PCD_SOFT_RESET = 0x0F // resets the MFRC522
};
// Commands sent to the PICC.
enum PiccCommand : uint8_t {
// The commands used by the PCD to manage communication with several PICCs (ISO 14443-3, Type A, section 6.4)
PICC_CMD_REQA = 0x26, // REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for
// anticollision or selection. 7 bit frame.
PICC_CMD_WUPA = 0x52, // Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to go to READY(*) and
// prepare for anticollision or selection. 7 bit frame.
PICC_CMD_CT = 0x88, // Cascade Tag. Not really a command, but used during anti collision.
PICC_CMD_SEL_CL1 = 0x93, // Anti collision/Select, Cascade Level 1
PICC_CMD_SEL_CL2 = 0x95, // Anti collision/Select, Cascade Level 2
PICC_CMD_SEL_CL3 = 0x97, // Anti collision/Select, Cascade Level 3
PICC_CMD_HLTA = 0x50, // HaLT command, Type A. Instructs an ACTIVE PICC to go to state HALT.
PICC_CMD_RATS = 0xE0, // Request command for Answer To Reset.
// The commands used for MIFARE Classic (from http://www.mouser.com/ds/2/302/MF1S503x-89574.pdf, Section 9)
// Use PCD_MFAuthent to authenticate access to a sector, then use these commands to read/write/modify the blocks on
// the sector.
// The read/write commands can also be used for MIFARE Ultralight.
PICC_CMD_MF_AUTH_KEY_A = 0x60, // Perform authentication with Key A
PICC_CMD_MF_AUTH_KEY_B = 0x61, // Perform authentication with Key B
PICC_CMD_MF_READ =
0x30, // Reads one 16 uint8_t block from the authenticated sector of the PICC. Also used for MIFARE Ultralight.
PICC_CMD_MF_WRITE = 0xA0, // Writes one 16 uint8_t block to the authenticated sector of the PICC. Called
// "COMPATIBILITY WRITE" for MIFARE Ultralight.
PICC_CMD_MF_DECREMENT =
0xC0, // Decrements the contents of a block and stores the result in the internal data register.
PICC_CMD_MF_INCREMENT =
0xC1, // Increments the contents of a block and stores the result in the internal data register.
PICC_CMD_MF_RESTORE = 0xC2, // Reads the contents of a block into the internal data register.
PICC_CMD_MF_TRANSFER = 0xB0, // Writes the contents of the internal data register to a block.
// The commands used for MIFARE Ultralight (from http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf, Section 8.6)
// The PICC_CMD_MF_READ and PICC_CMD_MF_WRITE can also be used for MIFARE Ultralight.
PICC_CMD_UL_WRITE = 0xA2 // Writes one 4 uint8_t page to the PICC.
};
// Return codes from the functions in this class. Remember to update GetStatusCodeName() if you add more.
// last value set to 0xff, then compiler uses less ram, it seems some optimisations are triggered
enum StatusCode : uint8_t {
STATUS_OK, // Success
STATUS_ERROR, // Error in communication
STATUS_COLLISION, // Collission detected
STATUS_TIMEOUT, // Timeout in communication.
STATUS_NO_ROOM, // A buffer is not big enough.
STATUS_INTERNAL_ERROR, // Internal error in the code. Should not happen ;-)
STATUS_INVALID, // Invalid argument.
STATUS_CRC_WRONG, // The CRC_A does not match
STATUS_MIFARE_NACK = 0xff // A MIFARE PICC responded with NAK.
};
// A struct used for passing the UID of a PICC.
using Uid = struct {
uint8_t size; // Number of uint8_ts in the UID. 4, 7 or 10.
uint8_t uiduint8_t[10];
uint8_t sak; // The SAK (Select acknowledge) uint8_t returned from the PICC after successful selection.
};
Uid uid_;
uint32_t update_wait_{0};
void pcd_reset_();
void initialize_();
void pcd_antenna_on_();
virtual uint8_t pcd_read_register(PcdRegister reg ///< The register to read from. One of the PCD_Register enums.
) = 0;
/**
* Reads a number of uint8_ts from the specified register in the MFRC522 chip.
* The interface is described in the datasheet section 8.1.2.
*/
virtual void pcd_read_register(PcdRegister reg, ///< The register to read from. One of the PCD_Register enums.
uint8_t count, ///< The number of uint8_ts to read
uint8_t *values, ///< uint8_t array to store the values in.
uint8_t rx_align ///< Only bit positions rxAlign..7 in values[0] are updated.
) = 0;
virtual void pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums.
uint8_t value ///< The value to write.
) = 0;
/**
* Writes a number of uint8_ts to the specified register in the MFRC522 chip.
* The interface is described in the datasheet section 8.1.2.
*/
virtual void pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums.
uint8_t count, ///< The number of uint8_ts to write to the register
uint8_t *values ///< The values to write. uint8_t array.
) = 0;
StatusCode picc_request_a_(
uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in
uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK.
);
StatusCode picc_reqa_or_wupa_(
uint8_t command, ///< The command to send - PICC_CMD_REQA or PICC_CMD_WUPA
uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in
uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK.
);
void pcd_set_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums.
uint8_t mask ///< The bits to set.
);
void pcd_clear_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums.
uint8_t mask ///< The bits to clear.
);
StatusCode pcd_transceive_data_(uint8_t *send_data, uint8_t send_len, uint8_t *back_data, uint8_t *back_len,
uint8_t *valid_bits = nullptr, uint8_t rx_align = 0, bool check_crc = false);
StatusCode pcd_communicate_with_picc_(uint8_t command, uint8_t wait_i_rq, uint8_t *send_data, uint8_t send_len,
uint8_t *back_data = nullptr, uint8_t *back_len = nullptr,
uint8_t *valid_bits = nullptr, uint8_t rx_align = 0, bool check_crc = false);
StatusCode pcd_calculate_crc_(
uint8_t *data, ///< In: Pointer to the data to transfer to the FIFO for CRC calculation.
uint8_t length, ///< In: The number of uint8_ts to transfer.
uint8_t *result ///< Out: Pointer to result buffer. Result is written to result[0..1], low uint8_t first.
);
RC522::StatusCode picc_is_new_card_present_();
bool picc_read_card_serial_();
StatusCode picc_select_(
Uid *uid, ///< Pointer to Uid struct. Normally output, but can also be used to supply a known UID.
uint8_t valid_bits = 0 ///< The number of known UID bits supplied in *uid. Normally 0. If set you must also
///< supply uid->size.
);
/** Read a data frame from the RC522 and return the result as a vector.
*
* Note that is_ready needs to be checked first before requesting this method.
*
* On failure, an empty vector is returned.
*/
std::vector<uint8_t> r_c522_read_data_();
GPIOPin *reset_pin_{nullptr};
uint8_t reset_count_{0};
uint32_t reset_timeout_{0};
bool initialize_pending_{false};
std::vector<RC522BinarySensor *> binary_sensors_;
std::vector<RC522Trigger *> triggers_;
enum RC522Error {
NONE = 0,
RESET_FAILED,
} error_code_{NONE};
};
class RC522BinarySensor : public binary_sensor::BinarySensor {
public:
void set_uid(const std::vector<uint8_t> &uid) { uid_ = uid; }
bool process(const uint8_t *data, uint8_t len);
void on_scan_end() {
if (!this->found_) {
this->publish_state(false);
}
this->found_ = false;
}
protected:
std::vector<uint8_t> uid_;
bool found_{false};
};
class RC522Trigger : public Trigger<std::string> {
public:
void process(const uint8_t *uid, uint8_t uid_length);
};
} // namespace rc522
} // namespace esphome

View file

@ -0,0 +1,22 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, rc522
from esphome.const import CONF_ID
CODEOWNERS = ['@glmnet']
DEPENDENCIES = ['i2c']
AUTO_LOAD = ['rc522']
rc522_i2c_ns = cg.esphome_ns.namespace('rc522_i2c')
RC522I2C = rc522_i2c_ns.class_('RC522I2C', rc522.RC522, i2c.I2CDevice)
CONFIG_SCHEMA = cv.All(rc522.RC522_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(RC522I2C),
}).extend(i2c.i2c_device_schema(0x2c)))
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield rc522.setup_rc522(var, config)
yield i2c.register_i2c_device(var, config)

View file

@ -0,0 +1,99 @@
#include "rc522_i2c.h"
#include "esphome/core/log.h"
namespace esphome {
namespace rc522_i2c {
static const char *TAG = "rc522_i2c";
void RC522I2C::dump_config() {
RC522::dump_config();
LOG_I2C_DEVICE(this);
}
/**
* Reads a uint8_t from the specified register in the MFRC522 chip.
* The interface is described in the datasheet section 8.1.2.
*/
uint8_t RC522I2C::pcd_read_register(PcdRegister reg ///< The register to read from. One of the PCD_Register enums.
) {
uint8_t value;
read_byte(reg >> 1, &value);
ESP_LOGVV(TAG, "read_register_(%x) -> %x", reg, value);
return value;
}
/**
* Reads a number of uint8_ts from the specified register in the MFRC522 chip.
* The interface is described in the datasheet section 8.1.2.
*/
void RC522I2C::pcd_read_register(PcdRegister reg, ///< The register to read from. One of the PCD_Register enums.
uint8_t count, ///< The number of uint8_ts to read
uint8_t *values, ///< uint8_t array to store the values in.
uint8_t rx_align ///< Only bit positions rxAlign..7 in values[0] are updated.
) {
if (count == 0) {
return;
}
std::string buf;
buf = "Rx";
char cstrb[20];
uint8_t b = values[0];
read_bytes(reg >> 1, values, count);
if (rx_align) // Only update bit positions rxAlign..7 in values[0]
{
// Create bit mask for bit positions rxAlign..7
uint8_t mask = 0xFF << rx_align;
// Apply mask to both current value of values[0] and the new data in values array.
values[0] = (b & ~mask) | (values[0] & mask);
}
}
void RC522I2C::pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums.
uint8_t value ///< The value to write.
) {
this->write_byte(reg >> 1, value);
}
/**
* Writes a number of uint8_ts to the specified register in the MFRC522 chip.
* The interface is described in the datasheet section 8.1.2.
*/
void RC522I2C::pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums.
uint8_t count, ///< The number of uint8_ts to write to the register
uint8_t *values ///< The values to write. uint8_t array.
) {
write_bytes(reg >> 1, values, count);
}
// bool RC522I2C::write_data(const std::vector<uint8_t> &data) {
// return this->write_bytes_raw(data.data(), data.size()); }
// bool RC522I2C::read_data(std::vector<uint8_t> &data, uint8_t len) {
// delay(5);
// std::vector<uint8_t> ready;
// ready.resize(1);
// uint32_t start_time = millis();
// while (true) {
// if (this->read_bytes_raw(ready.data(), 1)) {
// if (ready[0] == 0x01)
// break;
// }
// if (millis() - start_time > 100) {
// ESP_LOGV(TAG, "Timed out waiting for readiness from RC522!");
// return false;
// }
// }
// data.resize(len + 1);
// this->read_bytes_raw(data.data(), len + 1);
// return true;
// }
} // namespace rc522_i2c
} // namespace esphome

View file

@ -0,0 +1,42 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/rc522/rc522.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace rc522_i2c {
class RC522I2C : public rc522::RC522, public i2c::I2CDevice {
public:
void dump_config() override;
protected:
uint8_t pcd_read_register(PcdRegister reg ///< The register to read from. One of the PCD_Register enums.
) override;
/**
* Reads a number of uint8_ts from the specified register in the MFRC522 chip.
* The interface is described in the datasheet section 8.1.2.
*/
void pcd_read_register(PcdRegister reg, ///< The register to read from. One of the PCD_Register enums.
uint8_t count, ///< The number of uint8_ts to read
uint8_t *values, ///< uint8_t array to store the values in.
uint8_t rx_align ///< Only bit positions rxAlign..7 in values[0] are updated.
) override;
void pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums.
uint8_t value ///< The value to write.
) override;
/**
* Writes a number of uint8_ts to the specified register in the MFRC522 chip.
* The interface is described in the datasheet section 8.1.2.
*/
void pcd_write_register(PcdRegister reg, ///< The register to write to. One of the PCD_Register enums.
uint8_t count, ///< The number of uint8_ts to write to the register
uint8_t *values ///< The values to write. uint8_t array.
) override;
};
} // namespace rc522_i2c
} // namespace esphome

View file

@ -1,39 +1,21 @@
import esphome.codegen as cg
import esphome.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)

View file

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

View file

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

View file

@ -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<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
spi::DATA_RATE_4MHZ> {
class RC522Spi : public rc522::RC522,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
spi::DATA_RATE_4MHZ> {
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<uint8_t> r_c522_read_data_();
GPIOPin *reset_pin_{nullptr};
uint8_t reset_count_{0};
uint32_t reset_timeout_{0};
bool initialize_pending_{false};
std::vector<RC522BinarySensor *> binary_sensors_;
std::vector<RC522Trigger *> triggers_;
enum RC522Error {
NONE = 0,
RESET_FAILED,
} error_code_{NONE};
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<uint8_t> &uid) { uid_ = uid; }
bool process(const uint8_t *data, uint8_t len);
void on_scan_end() {
if (!this->found_) {
this->publish_state(false);
}
this->found_ = false;
}
protected:
std::vector<uint8_t> uid_;
bool found_{false};
};
class RC522Trigger : public Trigger<std::string> {
public:
void process(const uint8_t *uid, uint8_t uid_length);
};
#ifndef MFRC522_SPICLOCK
#define MFRC522_SPICLOCK SPI_CLOCK_DIV4 // MFRC522 accept upto 10MHz
#endif
} // namespace rc522_spi
} // namespace esphome

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -197,16 +197,21 @@ wled:
adalight:
mcp3008:
- id: 'mcp3008_hub'
cs_pin: GPIO12
mcp23s08:
- id: 'mcp23s08_hub'
cs_pin: GPIO12
deviceaddress: 0
mcp23s17:
- id: 'mcp23s17_hub'
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
@ -813,7 +824,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 +833,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 +1402,7 @@ climate:
switch:
- platform: gpio
name: "MCP23S08 Pin #0"
name: 'MCP23S08 Pin #0'
pin:
mcp23s08: mcp23s08_hub
# Use pin number 0
@ -1399,7 +1410,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 +1834,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:
@ -1837,6 +1854,7 @@ time:
then:
- lambda: 'ESP_LOGD("main", "time");'
- platform: gps
update_interval: 1h
on_time:
seconds: 0
minutes: /15
@ -1845,13 +1863,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'
@ -1933,7 +1950,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 +1984,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