mirror of
https://github.com/esphome/esphome.git
synced 2024-11-21 22:48:10 +01:00
Add Keeloq RF protocol (#5511)
This commit is contained in:
parent
31448a4fcd
commit
513a02ce11
3 changed files with 297 additions and 0 deletions
|
@ -633,6 +633,62 @@ async def magiquest_action(var, config, args):
|
|||
cg.add(var.set_magnitude(template_))
|
||||
|
||||
|
||||
# Microchip HCS301 KeeLoq OOK
|
||||
(
|
||||
KeeloqData,
|
||||
KeeloqBinarySensor,
|
||||
KeeloqTrigger,
|
||||
KeeloqAction,
|
||||
KeeloqDumper,
|
||||
) = declare_protocol("Keeloq")
|
||||
KEELOQ_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ADDRESS): cv.All(cv.hex_int, cv.Range(min=0, max=0xFFFFFFF)),
|
||||
cv.Required(CONF_CODE): cv.All(cv.hex_int, cv.Range(min=0, max=0xFFFFFFFF)),
|
||||
cv.Optional(CONF_COMMAND, default=0x10): cv.All(
|
||||
cv.hex_int,
|
||||
cv.Range(min=0, max=0x10),
|
||||
),
|
||||
cv.Optional(CONF_LEVEL, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@register_binary_sensor("keeloq", KeeloqBinarySensor, KEELOQ_SCHEMA)
|
||||
def Keeloq_binary_sensor(var, config):
|
||||
cg.add(
|
||||
var.set_data(
|
||||
cg.StructInitializer(
|
||||
KeeloqData,
|
||||
("address", config[CONF_ADDRESS]),
|
||||
("command", config[CONF_COMMAND]),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@register_trigger("keeloq", KeeloqTrigger, KeeloqData)
|
||||
def keeloq_trigger(var, config):
|
||||
pass
|
||||
|
||||
|
||||
@register_dumper("keeloq", KeeloqDumper)
|
||||
def keeloq_dumper(var, config):
|
||||
pass
|
||||
|
||||
|
||||
@register_action("keeloq", KeeloqAction, KEELOQ_SCHEMA)
|
||||
async def keeloq_action(var, config, args):
|
||||
template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint32)
|
||||
cg.add(var.set_address(template_))
|
||||
template_ = await cg.templatable(config[CONF_CODE], args, cg.uint32)
|
||||
cg.add(var.set_encrypted(template_))
|
||||
template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint8)
|
||||
cg.add(var.set_command(template_))
|
||||
template_ = await cg.templatable(config[CONF_LEVEL], args, bool)
|
||||
cg.add(var.set_vlow(template_))
|
||||
|
||||
|
||||
# NEC
|
||||
NECData, NECBinarySensor, NECTrigger, NECAction, NECDumper = declare_protocol("NEC")
|
||||
NEC_SCHEMA = cv.Schema(
|
||||
|
|
188
esphome/components/remote_base/keeloq_protocol.cpp
Normal file
188
esphome/components/remote_base/keeloq_protocol.cpp
Normal file
|
@ -0,0 +1,188 @@
|
|||
#include "keeloq_protocol.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_base {
|
||||
|
||||
static const char *const TAG = "remote.keeloq";
|
||||
|
||||
static const uint32_t BIT_TIME_US = 380;
|
||||
static const uint8_t NBITS_PREAMBLE = 12;
|
||||
static const uint8_t NBITS_REPEAT = 1;
|
||||
static const uint8_t NBITS_VLOW = 1;
|
||||
static const uint8_t NBITS_SERIAL = 28;
|
||||
static const uint8_t NBITS_BUTTONS = 4;
|
||||
static const uint8_t NBITS_DISC = 12;
|
||||
static const uint8_t NBITS_SYNC_CNT = 16;
|
||||
|
||||
static const uint8_t NBITS_FIXED_DATA = NBITS_REPEAT + NBITS_VLOW + NBITS_BUTTONS + NBITS_SERIAL;
|
||||
static const uint8_t NBITS_ENCRYPTED_DATA = NBITS_BUTTONS + NBITS_DISC + NBITS_SYNC_CNT;
|
||||
static const uint8_t NBITS_DATA = NBITS_FIXED_DATA + NBITS_ENCRYPTED_DATA;
|
||||
|
||||
/*
|
||||
KeeLoq Protocol
|
||||
|
||||
Coded using information from datasheet for Microchip HCS301 KeeLow Code Hopping Encoder
|
||||
|
||||
Encoder - Hopping code is generated at random.
|
||||
|
||||
Decoder - Hopping code is ignored and not checked when received. Serial number of
|
||||
transmitter and nutton command is decoded.
|
||||
|
||||
*/
|
||||
|
||||
void KeeloqProtocol::encode(RemoteTransmitData *dst, const KeeloqData &data) {
|
||||
uint32_t out_data = 0x0;
|
||||
|
||||
ESP_LOGD(TAG, "Send Keeloq: address=%07x command=%03x encrypted=%08x", data.address, data.command, data.encrypted);
|
||||
ESP_LOGV(TAG, "Send Keeloq: data bits (%d + %d)", NBITS_ENCRYPTED_DATA, NBITS_FIXED_DATA);
|
||||
|
||||
// Preamble = '01' x 12
|
||||
for (uint8_t cnt = NBITS_PREAMBLE; cnt; cnt--) {
|
||||
dst->space(BIT_TIME_US);
|
||||
dst->mark(BIT_TIME_US);
|
||||
}
|
||||
|
||||
// Header = 10 bit space
|
||||
dst->space(10 * BIT_TIME_US);
|
||||
|
||||
// Encrypted field
|
||||
out_data = data.encrypted;
|
||||
|
||||
ESP_LOGV(TAG, "Send Keeloq: Encrypted data %04x", out_data);
|
||||
|
||||
for (uint32_t mask = 1, cnt = 0; cnt < NBITS_ENCRYPTED_DATA; cnt++, mask <<= 1) {
|
||||
if (out_data & mask) {
|
||||
dst->mark(1 * BIT_TIME_US);
|
||||
dst->space(2 * BIT_TIME_US);
|
||||
} else {
|
||||
dst->mark(2 * BIT_TIME_US);
|
||||
dst->space(1 * BIT_TIME_US);
|
||||
}
|
||||
}
|
||||
|
||||
// first 32 bits of fixed portion
|
||||
out_data = (data.command & 0x0f);
|
||||
out_data <<= NBITS_SERIAL;
|
||||
out_data |= data.address;
|
||||
ESP_LOGV(TAG, "Send Keeloq: Fixed data %04x", out_data);
|
||||
|
||||
for (uint32_t mask = 1, cnt = 0; cnt < (NBITS_FIXED_DATA - 2); cnt++, mask <<= 1) {
|
||||
if (out_data & mask) {
|
||||
dst->mark(1 * BIT_TIME_US);
|
||||
dst->space(2 * BIT_TIME_US);
|
||||
} else {
|
||||
dst->mark(2 * BIT_TIME_US);
|
||||
dst->space(1 * BIT_TIME_US);
|
||||
}
|
||||
}
|
||||
|
||||
// low battery flag
|
||||
if (data.vlow) {
|
||||
dst->mark(1 * BIT_TIME_US);
|
||||
dst->space(2 * BIT_TIME_US);
|
||||
} else {
|
||||
dst->mark(2 * BIT_TIME_US);
|
||||
dst->space(1 * BIT_TIME_US);
|
||||
}
|
||||
|
||||
// repeat flag - always sent as a '1'
|
||||
dst->mark(1 * BIT_TIME_US);
|
||||
dst->space(2 * BIT_TIME_US);
|
||||
|
||||
// Guard time at end of packet
|
||||
dst->space(39 * BIT_TIME_US);
|
||||
}
|
||||
|
||||
optional<KeeloqData> KeeloqProtocol::decode(RemoteReceiveData src) {
|
||||
KeeloqData out{
|
||||
.encrypted = 0,
|
||||
.address = 0,
|
||||
.command = 0,
|
||||
.repeat = false,
|
||||
.vlow = false,
|
||||
|
||||
};
|
||||
|
||||
if (src.size() != (NBITS_PREAMBLE + NBITS_DATA) * 2) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "%2d: %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", src.size(), src.peek(0),
|
||||
src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6), src.peek(7), src.peek(8),
|
||||
src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14), src.peek(15),
|
||||
src.peek(16), src.peek(17), src.peek(18), src.peek(19));
|
||||
|
||||
// Check preamble bits
|
||||
int8_t bit = NBITS_PREAMBLE - 1;
|
||||
while (--bit >= 0) {
|
||||
if (!src.expect_mark(BIT_TIME_US) || !src.expect_space(BIT_TIME_US)) {
|
||||
ESP_LOGV(TAG, "Decode KeeLoq: Fail 1, %d %d", bit + 1, src.peek());
|
||||
return {};
|
||||
}
|
||||
}
|
||||
if (!src.expect_mark(BIT_TIME_US) || !src.expect_space(10 * BIT_TIME_US)) {
|
||||
ESP_LOGV(TAG, "Decode KeeLoq: Fail 1, %d %d", bit + 1, src.peek());
|
||||
return {};
|
||||
}
|
||||
|
||||
// Read encrypted bits
|
||||
uint32_t out_data = 0;
|
||||
for (bit = 0; bit < NBITS_ENCRYPTED_DATA; bit++) {
|
||||
if (src.expect_mark(2 * BIT_TIME_US) && src.expect_space(BIT_TIME_US)) {
|
||||
out_data |= 0 << bit;
|
||||
} else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) {
|
||||
out_data |= 1 << bit;
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Decode KeeLoq: Fail 2, %d %d", src.get_index(), src.peek());
|
||||
return {};
|
||||
}
|
||||
}
|
||||
ESP_LOGVV(TAG, "Decode KeeLoq: Data, %d %08x", bit, out_data);
|
||||
out.encrypted = out_data;
|
||||
|
||||
// Read Serial Number and Button Status
|
||||
out_data = 0;
|
||||
for (bit = 0; bit < NBITS_SERIAL + NBITS_BUTTONS; bit++) {
|
||||
if (src.expect_mark(2 * BIT_TIME_US) && src.expect_space(BIT_TIME_US)) {
|
||||
out_data |= 0 << bit;
|
||||
} else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) {
|
||||
out_data |= 1 << bit;
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Decode KeeLoq: Fail 3, %d %d", src.get_index(), src.peek());
|
||||
return {};
|
||||
}
|
||||
}
|
||||
ESP_LOGVV(TAG, "Decode KeeLoq: Data, %2d %08x", bit, out_data);
|
||||
out.command = (out_data >> 28) & 0xf;
|
||||
out.address = out_data & 0xfffffff;
|
||||
|
||||
// Read Vlow bit
|
||||
if (src.expect_mark(2 * BIT_TIME_US) && src.expect_space(BIT_TIME_US)) {
|
||||
out.vlow = false;
|
||||
} else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) {
|
||||
out.vlow = true;
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Decode KeeLoq: Fail 4, %08x", src.peek());
|
||||
return {};
|
||||
}
|
||||
|
||||
// Read Repeat bit
|
||||
if (src.expect_mark(2 * BIT_TIME_US) && src.peek_space_at_least(BIT_TIME_US)) {
|
||||
out.repeat = false;
|
||||
} else if (src.expect_mark(BIT_TIME_US) && src.peek_space_at_least(2 * BIT_TIME_US)) {
|
||||
out.repeat = true;
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Decode KeeLoq: Fail 5, %08x", src.peek());
|
||||
return {};
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void KeeloqProtocol::dump(const KeeloqData &data) {
|
||||
ESP_LOGD(TAG, "Received Keeloq: address=0x%08X, command=0x%02x", data.address, data.command);
|
||||
}
|
||||
|
||||
} // namespace remote_base
|
||||
} // namespace esphome
|
53
esphome/components/remote_base/keeloq_protocol.h
Normal file
53
esphome/components/remote_base/keeloq_protocol.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "remote_base.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_base {
|
||||
|
||||
struct KeeloqData {
|
||||
uint32_t encrypted; // 32 bit encrypted field
|
||||
uint32_t address; // 28 bit serial number
|
||||
uint8_t command; // Button Status S2-S1-S0-S3
|
||||
bool repeat; // Repeated command bit
|
||||
bool vlow; // Battery status bit
|
||||
|
||||
bool operator==(const KeeloqData &rhs) const {
|
||||
// Treat 0x10 as a special, wildcard button press
|
||||
// This allows us to match on just the address if wanted.
|
||||
if (address != rhs.address) {
|
||||
return false;
|
||||
}
|
||||
return (rhs.command == 0x10 || command == rhs.command);
|
||||
}
|
||||
};
|
||||
|
||||
class KeeloqProtocol : public RemoteProtocol<KeeloqData> {
|
||||
public:
|
||||
void encode(RemoteTransmitData *dst, const KeeloqData &data) override;
|
||||
optional<KeeloqData> decode(RemoteReceiveData src) override;
|
||||
void dump(const KeeloqData &data) override;
|
||||
};
|
||||
|
||||
DECLARE_REMOTE_PROTOCOL(Keeloq)
|
||||
|
||||
template<typename... Ts> class KeeloqAction : public RemoteTransmitterActionBase<Ts...> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint32_t, address)
|
||||
TEMPLATABLE_VALUE(uint32_t, encrypted)
|
||||
TEMPLATABLE_VALUE(uint8_t, command)
|
||||
TEMPLATABLE_VALUE(bool, vlow)
|
||||
|
||||
void encode(RemoteTransmitData *dst, Ts... x) override {
|
||||
KeeloqData data{};
|
||||
data.address = this->address_.value(x...);
|
||||
data.encrypted = this->encrypted_.value(x...);
|
||||
data.command = this->command_.value(x...);
|
||||
data.vlow = this->vlow_.value(x...);
|
||||
KeeloqProtocol().encode(dst, data);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace remote_base
|
||||
} // namespace esphome
|
Loading…
Reference in a new issue