add Wiegand reader component (#4288)

Co-authored-by: Samuel Sieb <samuel@sieb.net>
This commit is contained in:
Samuel Sieb 2023-01-17 17:37:54 -08:00 committed by GitHub
parent 029ac75a04
commit 5e2f33fde5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 250 additions and 0 deletions

View file

@ -277,6 +277,7 @@ esphome/components/wake_on_lan/* @willwill2will54
esphome/components/web_server_base/* @OttoWinter
esphome/components/whirlpool/* @glmnet
esphome/components/whynter/* @aeonsablaze
esphome/components/wiegand/* @ssieb
esphome/components/wl_134/* @hobbypunk90
esphome/components/x9c/* @EtienneMD
esphome/components/xiaomi_lywsd03mmc/* @ahpohl

View file

@ -0,0 +1,78 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins, automation
from esphome.components import key_provider
from esphome.const import CONF_ID, CONF_ON_TAG, CONF_TRIGGER_ID
CODEOWNERS = ["@ssieb"]
AUTO_LOAD = ["key_provider"]
MULTI_CONF = True
wiegand_ns = cg.esphome_ns.namespace("wiegand")
Wiegand = wiegand_ns.class_("Wiegand", key_provider.KeyProvider, cg.Component)
WiegandTagTrigger = wiegand_ns.class_(
"WiegandTagTrigger", automation.Trigger.template(cg.std_string)
)
WiegandRawTrigger = wiegand_ns.class_(
"WiegandRawTrigger", automation.Trigger.template(cg.uint8, cg.uint64)
)
WiegandKeyTrigger = wiegand_ns.class_(
"WiegandKeyTrigger", automation.Trigger.template(cg.uint8)
)
CONF_D0 = "d0"
CONF_D1 = "d1"
CONF_ON_KEY = "on_key"
CONF_ON_RAW = "on_raw"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(Wiegand),
cv.Required(CONF_D0): pins.internal_gpio_input_pin_schema,
cv.Required(CONF_D1): pins.internal_gpio_input_pin_schema,
cv.Optional(CONF_ON_TAG): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(WiegandTagTrigger),
}
),
cv.Optional(CONF_ON_RAW): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(WiegandRawTrigger),
}
),
cv.Optional(CONF_ON_KEY): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(WiegandKeyTrigger),
}
),
}
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
pin = await cg.gpio_pin_expression(config[CONF_D0])
cg.add(var.set_d0_pin(pin))
pin = await cg.gpio_pin_expression(config[CONF_D1])
cg.add(var.set_d1_pin(pin))
for conf in config.get(CONF_ON_TAG, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
cg.add(var.register_tag_trigger(trigger))
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
for conf in config.get(CONF_ON_RAW, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
cg.add(var.register_raw_trigger(trigger))
await automation.build_automation(
trigger, [(cg.uint8, "bits"), (cg.uint64, "value")], conf
)
for conf in config.get(CONF_ON_KEY, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
cg.add(var.register_key_trigger(trigger))
await automation.build_automation(trigger, [(cg.uint8, "x")], conf)

View file

@ -0,0 +1,117 @@
#include "wiegand.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace wiegand {
static const char *const TAG = "wiegand";
static const char *const KEYS = "0123456789*#";
void IRAM_ATTR HOT WiegandStore::d0_gpio_intr(WiegandStore *arg) {
if (arg->d0.digital_read())
return;
arg->count++;
arg->value <<= 1;
arg->last_bit_time = millis();
arg->done = false;
}
void IRAM_ATTR HOT WiegandStore::d1_gpio_intr(WiegandStore *arg) {
if (arg->d1.digital_read())
return;
arg->count++;
arg->value = (arg->value << 1) | 1;
arg->last_bit_time = millis();
arg->done = false;
}
void Wiegand::setup() {
this->d0_pin_->setup();
this->store_.d0 = this->d0_pin_->to_isr();
this->d1_pin_->setup();
this->store_.d1 = this->d1_pin_->to_isr();
this->d0_pin_->attach_interrupt(WiegandStore::d0_gpio_intr, &this->store_, gpio::INTERRUPT_FALLING_EDGE);
this->d1_pin_->attach_interrupt(WiegandStore::d1_gpio_intr, &this->store_, gpio::INTERRUPT_FALLING_EDGE);
}
bool check_eparity(uint64_t value, int start, int length) {
int parity = 0;
uint64_t mask = 1LL << start;
for (int i = 0; i <= length; i++, mask <<= 1) {
if (value & i)
parity++;
}
return !(parity & 1);
}
bool check_oparity(uint64_t value, int start, int length) {
int parity = 0;
uint64_t mask = 1LL << start;
for (int i = 0; i <= length; i++, mask <<= 1) {
if (value & i)
parity++;
}
return parity & 1;
}
void Wiegand::loop() {
if (this->store_.done)
return;
if (millis() - this->store_.last_bit_time < 100)
return;
uint8_t count = this->store_.count;
uint64_t value = this->store_.value;
this->store_.count = 0;
this->store_.value = 0;
this->store_.done = true;
ESP_LOGV(TAG, "received %d-bit value: %llx", count, value);
for (auto *trigger : this->raw_triggers_)
trigger->trigger(count, value);
if (count == 26) {
std::string tag = to_string((value >> 1) & 0xffffff);
ESP_LOGD(TAG, "received 26-bit tag: %s", tag.c_str());
if (!check_eparity(value, 13, 13) || !check_oparity(value, 0, 13)) {
ESP_LOGW(TAG, "invalid parity");
return;
}
for (auto *trigger : this->tag_triggers_)
trigger->trigger(tag);
} else if (count == 34) {
std::string tag = to_string((value >> 1) & 0xffffffff);
ESP_LOGD(TAG, "received 34-bit tag: %s", tag.c_str());
if (!check_eparity(value, 17, 17) || !check_oparity(value, 0, 17)) {
ESP_LOGW(TAG, "invalid parity");
return;
}
for (auto *trigger : this->tag_triggers_)
trigger->trigger(tag);
} else if (count == 37) {
std::string tag = to_string((value >> 1) & 0x7ffffffff);
ESP_LOGD(TAG, "received 37-bit tag: %s", tag.c_str());
if (!check_eparity(value, 18, 19) || !check_oparity(value, 0, 19)) {
ESP_LOGW(TAG, "invalid parity");
return;
}
for (auto *trigger : this->tag_triggers_)
trigger->trigger(tag);
} else if (count == 4) {
for (auto *trigger : this->key_triggers_)
trigger->trigger(value);
if (value < 12) {
uint8_t key = KEYS[value];
this->send_key_(key);
}
} else {
ESP_LOGD(TAG, "received unknown %d-bit value: %llx", count, value);
}
}
void Wiegand::dump_config() {
ESP_LOGCONFIG(TAG, "Wiegand reader:");
LOG_PIN(" D0 pin: ", this->d0_pin_);
LOG_PIN(" D1 pin: ", this->d1_pin_);
}
} // namespace wiegand
} // namespace esphome

View file

@ -0,0 +1,54 @@
#pragma once
#include "esphome/components/key_provider/key_provider.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace wiegand {
class Wiegand;
struct WiegandStore {
ISRInternalGPIOPin d0;
ISRInternalGPIOPin d1;
volatile uint64_t value{0};
volatile uint32_t last_bit_time{0};
volatile bool done{true};
volatile uint8_t count{0};
static void d0_gpio_intr(WiegandStore *arg);
static void d1_gpio_intr(WiegandStore *arg);
};
class WiegandTagTrigger : public Trigger<std::string> {};
class WiegandRawTrigger : public Trigger<uint8_t, uint64_t> {};
class WiegandKeyTrigger : public Trigger<uint8_t> {};
class Wiegand : public key_provider::KeyProvider, public Component {
public:
float get_setup_priority() const override { return setup_priority::HARDWARE; }
void setup() override;
void loop() override;
void dump_config() override;
void set_d0_pin(InternalGPIOPin *pin) { this->d0_pin_ = pin; };
void set_d1_pin(InternalGPIOPin *pin) { this->d1_pin_ = pin; };
void register_tag_trigger(WiegandTagTrigger *trig) { this->tag_triggers_.push_back(trig); }
void register_raw_trigger(WiegandRawTrigger *trig) { this->raw_triggers_.push_back(trig); }
void register_key_trigger(WiegandKeyTrigger *trig) { this->key_triggers_.push_back(trig); }
protected:
InternalGPIOPin *d0_pin_;
InternalGPIOPin *d1_pin_;
WiegandStore store_{};
std::vector<WiegandTagTrigger *> tag_triggers_;
std::vector<WiegandRawTrigger *> raw_triggers_;
std::vector<WiegandKeyTrigger *> key_triggers_;
};
} // namespace wiegand
} // namespace esphome