From b978985aa19df5874b190d56f5df4764964f51ca Mon Sep 17 00:00:00 2001 From: marshn Date: Mon, 6 Nov 2023 19:30:23 +0000 Subject: [PATCH] Add Byron Doorbell RF protocol (#4718) --- esphome/components/remote_base/__init__.py | 49 +++++++ .../remote_base/byronsx_protocol.cpp | 134 ++++++++++++++++++ .../components/remote_base/byronsx_protocol.h | 46 ++++++ 3 files changed, 229 insertions(+) create mode 100644 esphome/components/remote_base/byronsx_protocol.cpp create mode 100644 esphome/components/remote_base/byronsx_protocol.h diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index fb9d5e56a6..25dedd71d8 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -242,6 +242,55 @@ async def build_dumpers(config): return dumpers +# ByronSX +( + ByronSXData, + ByronSXBinarySensor, + ByronSXTrigger, + ByronSXAction, + ByronSXDumper, +) = declare_protocol("ByronSX") +BYRONSX_SCHEMA = cv.Schema( + { + cv.Required(CONF_ADDRESS): cv.All(cv.hex_int, cv.Range(min=0, max=0xFF)), + cv.Optional(CONF_COMMAND, default=0x10): cv.All( + cv.hex_int, cv.one_of(1, 2, 3, 5, 6, 9, 0xD, 0xE, 0x10, int=True) + ), + } +) + + +@register_binary_sensor("byronsx", ByronSXBinarySensor, BYRONSX_SCHEMA) +def byronsx_binary_sensor(var, config): + cg.add( + var.set_data( + cg.StructInitializer( + ByronSXData, + ("address", config[CONF_ADDRESS]), + ("command", config[CONF_COMMAND]), + ) + ) + ) + + +@register_trigger("byronsx", ByronSXTrigger, ByronSXData) +def byronsx_trigger(var, config): + pass + + +@register_dumper("byronsx", ByronSXDumper) +def byronsx_dumper(var, config): + pass + + +@register_action("byronsx", ByronSXAction, BYRONSX_SCHEMA) +async def byronsx_action(var, config, args): + template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint8) + cg.add(var.set_address(template_)) + template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint8) + cg.add(var.set_command(template_)) + + # CanalSat ( CanalSatData, diff --git a/esphome/components/remote_base/byronsx_protocol.cpp b/esphome/components/remote_base/byronsx_protocol.cpp new file mode 100644 index 0000000000..3096283b30 --- /dev/null +++ b/esphome/components/remote_base/byronsx_protocol.cpp @@ -0,0 +1,134 @@ +#include "byronsx_protocol.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace remote_base { + +static const char *const TAG = "remote.byronsx"; + +static const uint32_t BIT_TIME_US = 333; +static const uint8_t NBITS_ADDRESS = 8; +static const uint8_t NBITS_COMMAND = 4; +static const uint8_t NBITS_START_BIT = 1; +static const uint8_t NBITS_DATA = NBITS_ADDRESS + NBITS_COMMAND /*+ NBITS_COMMAND*/; + +/* +ByronSX Protocol +Each transmitted packet appears to consist of thirteen bits of PWM encoded +data. Each bit period of aprox 1ms consists of a transmitter OFF period +followed by a transmitter ON period. The 'on' and 'off' periods are either +short (approx 332us) or long (664us). + +A short 'off' followed by a long 'on' represents a '1' bit. +A long 'off' followed by a short 'on' represents a '0' bit. + +A the beginning of each packet is and initial 'off' period of approx 5.6ms +followed by a short 'on'. + +The data payload consists of twelve bits which appear to be an eight bit +address floowied by a 4 bit chime number. + +SAAAAAAAACCCC + +Whese: +S = the initial short start pulse +A = The eight address bits +C - The four chime bits + +-------------------- + +I have also used 'RFLink' software (RLink Firmware Version: 1.1 Revision: 48) +to capture these packets, eg: + +20;19;Byron;ID=004f;SWITCH=02;CMD=ON;CHIME=02; + +This module transmits and interprets packets in the same way as RFLink. + +marshn + +*/ + +void ByronSXProtocol::encode(RemoteTransmitData *dst, const ByronSXData &data) { + uint32_t out_data = 0x0; + + ESP_LOGD(TAG, "Send ByronSX: address=%04x command=%03x", data.address, data.command); + + out_data = data.address; + out_data <<= NBITS_COMMAND; + out_data |= data.command; + + ESP_LOGV(TAG, "Send ByronSX: out_data %03x", out_data); + + // Initial Mark start bit + dst->mark(1 * BIT_TIME_US); + + for (uint32_t mask = 1UL << (NBITS_DATA - 1); mask != 0; mask >>= 1) { + if (out_data & mask) { + dst->space(2 * BIT_TIME_US); + dst->mark(1 * BIT_TIME_US); + } else { + dst->space(1 * BIT_TIME_US); + dst->mark(2 * BIT_TIME_US); + } + } + // final space at end of packet + dst->space(17 * BIT_TIME_US); +} + +optional ByronSXProtocol::decode(RemoteReceiveData src) { + ByronSXData out{ + .address = 0, + .command = 0, + }; + + if (src.size() != (NBITS_DATA + NBITS_START_BIT) * 2) { + return {}; + } + + // Skip start bit + if (!src.expect_mark(BIT_TIME_US)) { + return {}; + } + + ESP_LOGVV(TAG, "%3d: %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)); + + ESP_LOGVV(TAG, " %d %d %d %d %d %d", src.peek(20), src.peek(21), src.peek(22), src.peek(23), src.peek(24), + src.peek(25)); + + // Read data bits + uint32_t out_data = 0; + int8_t bit = NBITS_DATA; + while (--bit >= 0) { + if (src.expect_space(2 * BIT_TIME_US) && src.expect_mark(BIT_TIME_US)) { + out_data |= 1 << bit; + } else if (src.expect_space(BIT_TIME_US) && src.expect_mark(2 * BIT_TIME_US)) { + out_data |= 0 << bit; + } else { + ESP_LOGV(TAG, "Decode ByronSX: Fail 2, %2d %08x", bit, out_data); + return {}; + } + ESP_LOGVV(TAG, "Decode ByronSX: Data, %2d %08x", bit, out_data); + } + + // last bit followed by a long space + if (!src.peek_space_at_least(BIT_TIME_US)) { + ESP_LOGV(TAG, "Decode ByronSX: Fail 4 "); + return {}; + } + + out.command = (uint8_t) (out_data & 0xF); + out_data >>= NBITS_COMMAND; + out.address = (uint16_t) (out_data & 0xFF); + + return out; +} + +void ByronSXProtocol::dump(const ByronSXData &data) { + ESP_LOGD(TAG, "Received ByronSX: address=0x%08X, command=0x%02x", data.address, data.command); +} + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/byronsx_protocol.h b/esphome/components/remote_base/byronsx_protocol.h new file mode 100644 index 0000000000..5d23237ab1 --- /dev/null +++ b/esphome/components/remote_base/byronsx_protocol.h @@ -0,0 +1,46 @@ +#pragma once + +#include "esphome/core/component.h" +#include "remote_base.h" + +namespace esphome { +namespace remote_base { + +struct ByronSXData { + uint8_t address; + uint8_t command; + + bool operator==(const ByronSXData &rhs) const { + // Treat 0x10 as a special, wildcard command/chime + // 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 ByronSXProtocol : public RemoteProtocol { + public: + void encode(RemoteTransmitData *dst, const ByronSXData &data) override; + optional decode(RemoteReceiveData src) override; + void dump(const ByronSXData &data) override; +}; + +DECLARE_REMOTE_PROTOCOL(ByronSX) + +template class ByronSXAction : public RemoteTransmitterActionBase { + public: + TEMPLATABLE_VALUE(uint8_t, address) + TEMPLATABLE_VALUE(uint8_t, command) + + void encode(RemoteTransmitData *dst, Ts... x) override { + ByronSXData data{}; + data.address = this->address_.value(x...); + data.command = this->command_.value(x...); + ByronSXProtocol().encode(dst, data); + } +}; + +} // namespace remote_base +} // namespace esphome