mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 13:34:54 +01:00
add Wiegand reader component (#4288)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
This commit is contained in:
parent
029ac75a04
commit
5e2f33fde5
4 changed files with 250 additions and 0 deletions
|
@ -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
|
||||
|
|
78
esphome/components/wiegand/__init__.py
Normal file
78
esphome/components/wiegand/__init__.py
Normal 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)
|
117
esphome/components/wiegand/wiegand.cpp
Normal file
117
esphome/components/wiegand/wiegand.cpp
Normal 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
|
54
esphome/components/wiegand/wiegand.h
Normal file
54
esphome/components/wiegand/wiegand.h
Normal 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
|
Loading…
Reference in a new issue