diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index f1b3e32c18..165da95d67 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -728,6 +728,48 @@ async def rc5_action(var, config, args): cg.add(var.set_command(template_)) +# RC6 +RC6Data, RC6BinarySensor, RC6Trigger, RC6Action, RC6Dumper = declare_protocol("RC6") +RC6_SCHEMA = cv.Schema( + { + cv.Required(CONF_ADDRESS): cv.hex_uint8_t, + cv.Required(CONF_COMMAND): cv.hex_uint8_t, + } +) + + +@register_binary_sensor("rc6", RC6BinarySensor, RC6_SCHEMA) +def rc6_binary_sensor(var, config): + cg.add( + var.set_data( + cg.StructInitializer( + RC6Data, + ("device", config[CONF_DEVICE]), + ("address", config[CONF_ADDRESS]), + ("command", config[CONF_COMMAND]), + ) + ) + ) + + +@register_trigger("rc6", RC6Trigger, RC6Data) +def rc6_trigger(var, config): + pass + + +@register_dumper("rc6", RC6Dumper) +def rc6_dumper(var, config): + pass + + +@register_action("rc6", RC6Action, RC6_SCHEMA) +async def rc6_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_)) + + # RC Switch Raw RC_SWITCH_TIMING_SCHEMA = cv.All([cv.uint8_t], cv.Length(min=2, max=2)) diff --git a/esphome/components/remote_base/rc6_protocol.cpp b/esphome/components/remote_base/rc6_protocol.cpp new file mode 100644 index 0000000000..ad12c7c208 --- /dev/null +++ b/esphome/components/remote_base/rc6_protocol.cpp @@ -0,0 +1,181 @@ +#include "rc6_protocol.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace remote_base { + +static const char *const RC6_TAG = "remote.rc6"; + +static const uint16_t RC6_FREQ = 36000; +static const uint16_t RC6_UNIT = 444; +static const uint16_t RC6_HEADER_MARK = (6 * RC6_UNIT); +static const uint16_t RC6_HEADER_SPACE = (2 * RC6_UNIT); +static const uint16_t RC6_MODE_MASK = 0x07; + +void RC6Protocol::encode(RemoteTransmitData *dst, const RC6Data &data) { + dst->reserve(44); + dst->set_carrier_frequency(RC6_FREQ); + + // Encode header + dst->item(RC6_HEADER_MARK, RC6_HEADER_SPACE); + + int32_t next{0}; + + // Encode startbit+mode + uint8_t header{static_cast((1 << 3) | data.mode)}; + + for (uint8_t mask = 0x8; mask; mask >>= 1) { + if (header & mask) { + if (next < 0) { + dst->space(-next); + next = 0; + } + if (next >= 0) { + next = next + RC6_UNIT; + dst->mark(next); + next = -RC6_UNIT; + } + } else { + if (next > 0) { + dst->mark(next); + next = 0; + } + if (next <= 0) { + next = next - RC6_UNIT; + dst->space(-next); + next = RC6_UNIT; + } + } + } + + // Toggle + if (data.toggle) { + if (next < 0) { + dst->space(-next); + next = 0; + } + if (next >= 0) { + next = next + RC6_UNIT * 2; + dst->mark(next); + next = -RC6_UNIT * 2; + } + } else { + if (next > 0) { + dst->mark(next); + next = 0; + } + if (next <= 0) { + next = next - RC6_UNIT * 2; + dst->space(-next); + next = RC6_UNIT * 2; + } + } + + // Encode data + uint16_t raw{static_cast((data.address << 8) | data.command)}; + + for (uint16_t mask = 0x8000; mask; mask >>= 1) { + if (raw & mask) { + if (next < 0) { + dst->space(-next); + next = 0; + } + if (next >= 0) { + next = next + RC6_UNIT; + dst->mark(next); + next = -RC6_UNIT; + } + } else { + if (next > 0) { + dst->mark(next); + next = 0; + } + if (next <= 0) { + next = next - RC6_UNIT; + dst->space(-next); + next = RC6_UNIT; + } + } + } + + if (next > 0) { + dst->mark(next); + } else { + dst->space(-next); + } +} + +optional RC6Protocol::decode(RemoteReceiveData src) { + RC6Data data{ + .mode = 0, + .toggle = 0, + .address = 0, + .command = 0, + }; + + // Check if header matches + if (!src.expect_item(RC6_HEADER_MARK, RC6_HEADER_SPACE)) { + return {}; + } + + uint8_t bit{1}; + uint8_t offset{0}; + uint8_t header{0}; + uint32_t buffer{0}; + + // Startbit + mode + while (offset < 4) { + bit = src.peek() > 0; + header = header + (bit << (3 - offset++)); + src.advance(); + + if (src.peek_mark(RC6_UNIT) || src.peek_space(RC6_UNIT)) { + src.advance(); + } else if (offset == 4) { + break; + } else if (!src.peek_mark(RC6_UNIT * 2) && !src.peek_space(RC6_UNIT * 2)) { + return {}; + } + } + + data.mode = header & RC6_MODE_MASK; + + if (data.mode != 0) { + return {}; // I dont have a device to test other modes + } + + // Toggle + data.toggle = src.peek() > 0; + src.advance(); + if (src.peek_mark(RC6_UNIT * 2) || src.peek_space(RC6_UNIT * 2)) { + src.advance(); + } + + // Data + offset = 0; + while (offset < 16) { + bit = src.peek() > 0; + buffer = buffer + (bit << (15 - offset++)); + src.advance(); + + if (offset == 16) { + break; + } else if (src.peek_mark(RC6_UNIT) || src.peek_space(RC6_UNIT)) { + src.advance(); + } else if (!src.peek_mark(RC6_UNIT * 2) && !src.peek_space(RC6_UNIT * 2)) { + return {}; + } + } + + data.address = (0xFF00 & buffer) >> 8; + data.command = (0x00FF & buffer); + return data; +} + +void RC6Protocol::dump(const RC6Data &data) { + ESP_LOGD(RC6_TAG, "Received RC6: mode=0x%X, address=0x%02X, command=0x%02X, toggle=0x%X", data.mode, data.address, + data.command, data.toggle); +} + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/rc6_protocol.h b/esphome/components/remote_base/rc6_protocol.h new file mode 100644 index 0000000000..980509a2fc --- /dev/null +++ b/esphome/components/remote_base/rc6_protocol.h @@ -0,0 +1,46 @@ +#pragma once + +#include "remote_base.h" + +namespace esphome { +namespace remote_base { + +struct RC6Data { + uint8_t mode : 3; + uint8_t toggle : 1; + uint8_t address; + uint8_t command; + + bool operator==(const RC6Data &rhs) const { return address == rhs.address && command == rhs.command; } +}; + +class RC6Protocol : public RemoteProtocol { + public: + void encode(RemoteTransmitData *dst, const RC6Data &data) override; + optional decode(RemoteReceiveData src) override; + void dump(const RC6Data &data) override; +}; + +DECLARE_REMOTE_PROTOCOL(RC6) + +template class RC6Action : public RemoteTransmitterActionBase { + public: + TEMPLATABLE_VALUE(uint8_t, address) + TEMPLATABLE_VALUE(uint8_t, command) + + void encode(RemoteTransmitData *dst, Ts... x) { + RC6Data data{}; + data.mode = 0; + data.toggle = this->toggle_; + data.address = this->address_.value(x...); + data.command = this->command_.value(x...); + RC6Protocol().encode(dst, data); + this->toggle_ = !this->toggle_; + } + + protected: + uint8_t toggle_{0}; +}; + +} // namespace remote_base +} // namespace esphome