mirror of
https://github.com/esphome/esphome.git
synced 2024-08-29 23:50:20 +02:00
commit
828f7946ea
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
1
esphome/components/inkplate6/__init__.py
Normal file
1
esphome/components/inkplate6/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ['@jesserockz']
|
141
esphome/components/inkplate6/display.py
Normal file
141
esphome/components/inkplate6/display.py
Normal 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')
|
630
esphome/components/inkplate6/inkplate.cpp
Normal file
630
esphome/components/inkplate6/inkplate.cpp
Normal 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
|
157
esphome/components/inkplate6/inkplate.h
Normal file
157
esphome/components/inkplate6/inkplate.h
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
7
esphome/components/nfc/__init__.py
Normal file
7
esphome/components/nfc/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
import esphome.codegen as cg
|
||||
|
||||
CODEOWNERS = ['@jesserockz']
|
||||
|
||||
nfc_ns = cg.esphome_ns.namespace('nfc')
|
||||
|
||||
NfcTag = nfc_ns.class_('NfcTag')
|
106
esphome/components/nfc/ndef_message.cpp
Normal file
106
esphome/components/nfc/ndef_message.cpp
Normal 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
|
31
esphome/components/nfc/ndef_message.h
Normal file
31
esphome/components/nfc/ndef_message.h
Normal 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
|
85
esphome/components/nfc/ndef_record.cpp
Normal file
85
esphome/components/nfc/ndef_record.cpp
Normal 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
|
101
esphome/components/nfc/ndef_record.h
Normal file
101
esphome/components/nfc/ndef_record.h
Normal 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
|
107
esphome/components/nfc/nfc.cpp
Normal file
107
esphome/components/nfc/nfc.cpp
Normal 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
|
57
esphome/components/nfc/nfc.h
Normal file
57
esphome/components/nfc/nfc.h
Normal 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
|
9
esphome/components/nfc/nfc_tag.cpp
Normal file
9
esphome/components/nfc/nfc_tag.cpp
Normal file
|
@ -0,0 +1,9 @@
|
|||
#include "nfc_tag.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace nfc {
|
||||
|
||||
static const char *TAG = "nfc.tag";
|
||||
|
||||
} // namespace nfc
|
||||
} // namespace esphome
|
47
esphome/components/nfc/nfc_tag.h
Normal file
47
esphome/components/nfc/nfc_tag.h
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
249
esphome/components/pn532/pn532_mifare_classic.cpp
Normal file
249
esphome/components/pn532/pn532_mifare_classic.cpp
Normal 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
|
180
esphome/components/pn532/pn532_mifare_ultralight.cpp
Normal file
180
esphome/components/pn532/pn532_mifare_ultralight.cpp
Normal 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
|
|
@ -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);
|
||||
|
|
38
esphome/components/rc522/__init__.py
Normal file
38
esphome/components/rc522/__init__.py
Normal 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)
|
43
esphome/components/rc522/binary_sensor.py
Normal file
43
esphome/components/rc522/binary_sensor.py
Normal 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))
|
758
esphome/components/rc522/rc522.cpp
Normal file
758
esphome/components/rc522/rc522.cpp
Normal 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
|
284
esphome/components/rc522/rc522.h
Normal file
284
esphome/components/rc522/rc522.h
Normal 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
|
22
esphome/components/rc522_i2c/__init__.py
Normal file
22
esphome/components/rc522_i2c/__init__.py
Normal 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)
|
99
esphome/components/rc522_i2c/rc522_i2c.cpp
Normal file
99
esphome/components/rc522_i2c/rc522_i2c.cpp
Normal 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
|
42
esphome/components/rc522_i2c/rc522_i2c.h
Normal file
42
esphome/components/rc522_i2c/rc522_i2c.h
Normal 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
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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}'
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user