From a6c46eb8e5a601c252b21627009fe72da35fdd8b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 3 Nov 2020 07:34:29 +1300 Subject: [PATCH] Adds support for RF Bridge advanced codes (#1246) * WIP: Advanced commands for portisch firmware * Fix string code sending * clang formatting * Add new rf_bridge functions to test * Add advanced code received trigger * Fix copy-paste mistake in the advanced sending * Fix log message to be consistent * clang * Remove extra + --- esphome/components/rf_bridge/__init__.py | 108 ++++++++++++++++++++- esphome/components/rf_bridge/rf_bridge.cpp | 98 ++++++++++++++++++- esphome/components/rf_bridge/rf_bridge.h | 78 ++++++++++++++- esphome/const.py | 1 + tests/test3.yaml | 18 +++- 5 files changed, 293 insertions(+), 10 deletions(-) diff --git a/esphome/components/rf_bridge/__init__.py b/esphome/components/rf_bridge/__init__.py index 885e5765dd..a0a910118b 100644 --- a/esphome/components/rf_bridge/__init__.py +++ b/esphome/components/rf_bridge/__init__.py @@ -1,8 +1,18 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation -from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_CODE, CONF_LOW, CONF_SYNC, CONF_HIGH from esphome.components import uart +from esphome.const import ( + CONF_CODE, + CONF_HIGH, + CONF_ID, + CONF_LENGTH, + CONF_LOW, + CONF_PROTOCOL, + CONF_RAW, + CONF_SYNC, + CONF_TRIGGER_ID, +) DEPENDENCIES = ['uart'] CODEOWNERS = ['@jesserockz'] @@ -11,21 +21,39 @@ rf_bridge_ns = cg.esphome_ns.namespace('rf_bridge') RFBridgeComponent = rf_bridge_ns.class_('RFBridgeComponent', cg.Component, uart.UARTDevice) RFBridgeData = rf_bridge_ns.struct('RFBridgeData') +RFBridgeAdvancedData = rf_bridge_ns.struct('RFBridgeAdvancedData') RFBridgeReceivedCodeTrigger = rf_bridge_ns.class_('RFBridgeReceivedCodeTrigger', automation.Trigger.template(RFBridgeData)) +RFBridgeReceivedAdvancedCodeTrigger = rf_bridge_ns.class_( + 'RFBridgeReceivedAdvancedCodeTrigger', + automation.Trigger.template(RFBridgeAdvancedData), +) RFBridgeSendCodeAction = rf_bridge_ns.class_('RFBridgeSendCodeAction', automation.Action) +RFBridgeSendAdvancedCodeAction = rf_bridge_ns.class_( + 'RFBridgeSendAdvancedCodeAction', automation.Action) + RFBridgeLearnAction = rf_bridge_ns.class_('RFBridgeLearnAction', automation.Action) +RFBridgeStartAdvancedSniffingAction = rf_bridge_ns.class_( + 'RFBridgeStartAdvancedSniffingAction', automation.Action) +RFBridgeStopAdvancedSniffingAction = rf_bridge_ns.class_( + 'RFBridgeStopAdvancedSniffingAction', automation.Action) + +RFBridgeSendRawAction = rf_bridge_ns.class_('RFBridgeSendRawAction', automation.Action) CONF_ON_CODE_RECEIVED = 'on_code_received' +CONF_ON_ADVANCED_CODE_RECEIVED = 'on_advanced_code_received' CONFIG_SCHEMA = cv.All(cv.Schema({ cv.GenerateID(): cv.declare_id(RFBridgeComponent), cv.Optional(CONF_ON_CODE_RECEIVED): automation.validate_automation({ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RFBridgeReceivedCodeTrigger), }), + cv.Optional(CONF_ON_ADVANCED_CODE_RECEIVED): automation.validate_automation({ + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RFBridgeReceivedAdvancedCodeTrigger), + }), }).extend(uart.UART_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA)) @@ -38,6 +66,12 @@ def to_code(config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) yield automation.build_automation(trigger, [(RFBridgeData, 'data')], conf) + for conf in config.get(CONF_ON_ADVANCED_CODE_RECEIVED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + yield automation.build_automation( + trigger, [(RFBridgeAdvancedData, 'data')], conf + ) + RFBRIDGE_SEND_CODE_SCHEMA = cv.Schema({ cv.GenerateID(): cv.use_id(RFBridgeComponent), @@ -64,13 +98,81 @@ def rf_bridge_send_code_to_code(config, action_id, template_args, args): yield var -RFBRIDGE_LEARN_SCHEMA = cv.Schema({ +RFBRIDGE_ID_SCHEMA = cv.Schema({ cv.GenerateID(): cv.use_id(RFBridgeComponent) }) -@automation.register_action('rf_bridge.learn', RFBridgeLearnAction, RFBRIDGE_LEARN_SCHEMA) +@automation.register_action('rf_bridge.learn', RFBridgeLearnAction, RFBRIDGE_ID_SCHEMA) def rf_bridge_learnx_to_code(config, action_id, template_args, args): paren = yield cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_args, paren) yield var + + +@automation.register_action( + 'rf_bridge.start_advanced_sniffing', + RFBridgeStartAdvancedSniffingAction, + RFBRIDGE_ID_SCHEMA, +) +def rf_bridge_start_advanced_sniffing_to_code(config, action_id, template_args, args): + paren = yield cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_args, paren) + yield var + + +@automation.register_action( + 'rf_bridge.stop_advanced_sniffing', + RFBridgeStopAdvancedSniffingAction, + RFBRIDGE_ID_SCHEMA, +) +def rf_bridge_stop_advanced_sniffing_to_code(config, action_id, template_args, args): + paren = yield cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_args, paren) + yield var + + +RFBRIDGE_SEND_ADVANCED_CODE_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.use_id(RFBridgeComponent), + cv.Required(CONF_LENGTH): cv.templatable(cv.hex_uint8_t), + cv.Required(CONF_PROTOCOL): cv.templatable(cv.hex_uint8_t), + cv.Required(CONF_CODE): cv.templatable(cv.string), +}) + + +@automation.register_action( + 'rf_bridge.send_advanced_code', + RFBridgeSendAdvancedCodeAction, + RFBRIDGE_SEND_ADVANCED_CODE_SCHEMA +) +def rf_bridge_send_advanced_code_to_code(config, action_id, template_args, args): + paren = yield cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_args, paren) + template_ = yield cg.templatable(config[CONF_LENGTH], args, cg.uint16) + cg.add(var.set_length(template_)) + template_ = yield cg.templatable(config[CONF_PROTOCOL], args, cg.uint16) + cg.add(var.set_protocol(template_)) + template_ = yield cg.templatable(config[CONF_CODE], args, cg.std_string) + cg.add(var.set_code(template_)) + yield var + + +RFBRIDGE_SEND_RAW_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(RFBridgeComponent), + cv.Required(CONF_RAW): cv.templatable(cv.string), + } +) + + +@automation.register_action( + 'rf_bridge.send_raw', + RFBridgeSendRawAction, + RFBRIDGE_SEND_RAW_SCHEMA +) +def rf_bridge_send_raw_to_code(config, action_id, template_args, args): + paren = yield cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_args, paren) + template_ = yield cg.templatable(config[CONF_RAW], args, cg.std_string) + cg.add(var.set_raw(template_)) + yield var diff --git a/esphome/components/rf_bridge/rf_bridge.cpp b/esphome/components/rf_bridge/rf_bridge.cpp index 42689ecf82..32e72453e0 100644 --- a/esphome/components/rf_bridge/rf_bridge.cpp +++ b/esphome/components/rf_bridge/rf_bridge.cpp @@ -20,13 +20,15 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) { this->rx_buffer_.push_back(byte); const uint8_t *raw = &this->rx_buffer_[0]; + ESP_LOGVV(TAG, "Processing byte: 0x%02X", byte); + // Byte 0: Start if (at == 0) return byte == RF_CODE_START; // Byte 1: Action if (at == 1) - return byte >= RF_CODE_ACK && byte <= RF_CODE_RFOUT; + return byte >= RF_CODE_ACK && byte <= RF_CODE_RFIN_BUCKET; uint8_t action = raw[1]; switch (action) { @@ -37,8 +39,8 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) { ESP_LOGD(TAG, "Learning timeout"); break; case RF_CODE_LEARN_OK: - case RF_CODE_RFIN: - if (at < RF_MESSAGE_SIZE + 2) + case RF_CODE_RFIN: { + if (byte != RF_CODE_STOP || at < RF_MESSAGE_SIZE + 2) return true; RFBridgeData data; @@ -52,8 +54,52 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) { ESP_LOGD(TAG, "Received RFBridge Code: sync=0x%04X low=0x%04X high=0x%04X code=0x%06X", data.sync, data.low, data.high, data.code); - this->callback_.call(data); + this->data_callback_.call(data); break; + } + case RF_CODE_LEARN_OK_NEW: + case RF_CODE_ADVANCED_RFIN: { + if (byte != RF_CODE_STOP) { + return at < (raw[2] + 3); + } + + RFBridgeAdvancedData data{}; + + data.length = raw[2]; + data.protocol = raw[3]; + char next_byte[2]; + for (uint8_t i = 0; i < data.length - 1; i++) { + sprintf(next_byte, "%02X", raw[4 + i]); + data.code += next_byte; + } + + ESP_LOGD(TAG, "Received RFBridge Advanced Code: length=0x%02X protocol=0x%02X code=0x%s", data.length, + data.protocol, data.code.c_str()); + this->advanced_data_callback_.call(data); + break; + } + case RF_CODE_RFIN_BUCKET: { + if (byte != RF_CODE_STOP) { + return true; + } + + uint8_t buckets = raw[2] << 1; + std::string str; + char next_byte[2]; + + for (uint32_t i = 0; i <= at; i++) { + sprintf(next_byte, "%02X", raw[i]); + str += next_byte; + if ((i > 3) && buckets) { + buckets--; + } + if ((i < 3) || (buckets % 2) || (i == at - 1)) { + str += " "; + } + } + ESP_LOGD(TAG, "Received RFBridge Bucket: %s", str.c_str()); + break; + } default: ESP_LOGW(TAG, "Unknown action: 0x%02X", action); break; @@ -68,6 +114,15 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) { return false; } +void RFBridgeComponent::write_byte_str_(std::string codes) { + uint8_t code; + int size = codes.length(); + for (int i = 0; i < size; i += 2) { + code = strtol(codes.substr(i, 2).c_str(), nullptr, 16); + this->write(code); + } +} + void RFBridgeComponent::loop() { const uint32_t now = millis(); if (now - this->last_bridge_byte_ > 50) { @@ -105,6 +160,18 @@ void RFBridgeComponent::send_code(RFBridgeData data) { this->flush(); } +void RFBridgeComponent::send_advanced_code(RFBridgeAdvancedData data) { + ESP_LOGD(TAG, "Sending advanced code: length=0x%02X protocol=0x%02X code=0x%s", data.length, data.protocol, + data.code.c_str()); + this->write(RF_CODE_START); + this->write(RF_CODE_RFOUT_NEW); + this->write(data.length & 0xFF); + this->write(data.protocol & 0xFF); + this->write_byte_str_(data.code); + this->write(RF_CODE_STOP); + this->flush(); +} + void RFBridgeComponent::learn() { ESP_LOGD(TAG, "Learning mode"); this->write(RF_CODE_START); @@ -118,5 +185,28 @@ void RFBridgeComponent::dump_config() { this->check_uart_settings(19200); } +void RFBridgeComponent::start_advanced_sniffing() { + ESP_LOGD(TAG, "Advanced Sniffing on"); + this->write(RF_CODE_START); + this->write(RF_CODE_SNIFFING_ON); + this->write(RF_CODE_STOP); + this->flush(); +} + +void RFBridgeComponent::stop_advanced_sniffing() { + ESP_LOGD(TAG, "Advanced Sniffing off"); + this->write(RF_CODE_START); + this->write(RF_CODE_SNIFFING_OFF); + this->write(RF_CODE_STOP); + this->flush(); +} + +void RFBridgeComponent::send_raw(std::string raw_code) { + ESP_LOGD(TAG, "Sending Raw Code: %s", raw_code.c_str()); + + this->write_byte_str_(raw_code); + this->flush(); +} + } // namespace rf_bridge } // namespace esphome diff --git a/esphome/components/rf_bridge/rf_bridge.h b/esphome/components/rf_bridge/rf_bridge.h index 7ae84a032f..b850140b75 100644 --- a/esphome/components/rf_bridge/rf_bridge.h +++ b/esphome/components/rf_bridge/rf_bridge.h @@ -15,6 +15,7 @@ static const uint8_t RF_CODE_LEARN_KO = 0xA2; static const uint8_t RF_CODE_LEARN_OK = 0xA3; static const uint8_t RF_CODE_RFIN = 0xA4; static const uint8_t RF_CODE_RFOUT = 0xA5; +static const uint8_t RF_CODE_ADVANCED_RFIN = 0xA6; static const uint8_t RF_CODE_SNIFFING_ON = 0xA6; static const uint8_t RF_CODE_SNIFFING_OFF = 0xA7; static const uint8_t RF_CODE_RFOUT_NEW = 0xA8; @@ -22,6 +23,7 @@ static const uint8_t RF_CODE_LEARN_NEW = 0xA9; static const uint8_t RF_CODE_LEARN_KO_NEW = 0xAA; static const uint8_t RF_CODE_LEARN_OK_NEW = 0xAB; static const uint8_t RF_CODE_RFOUT_BUCKET = 0xB0; +static const uint8_t RF_CODE_RFIN_BUCKET = 0xB1; static const uint8_t RF_CODE_STOP = 0x55; static const uint8_t RF_DEBOUNCE = 200; @@ -32,25 +34,40 @@ struct RFBridgeData { uint32_t code; }; +struct RFBridgeAdvancedData { + uint8_t length; + uint8_t protocol; + std::string code; +}; + class RFBridgeComponent : public uart::UARTDevice, public Component { public: void loop() override; void dump_config() override; void add_on_code_received_callback(std::function callback) { - this->callback_.add(std::move(callback)); + this->data_callback_.add(std::move(callback)); + } + void add_on_advanced_code_received_callback(std::function callback) { + this->advanced_data_callback_.add(std::move(callback)); } void send_code(RFBridgeData data); + void send_advanced_code(RFBridgeAdvancedData data); void learn(); + void start_advanced_sniffing(); + void stop_advanced_sniffing(); + void send_raw(std::string code); protected: void ack_(); void decode_(); bool parse_bridge_byte_(uint8_t byte); + void write_byte_str_(std::string codes); std::vector rx_buffer_; uint32_t last_bridge_byte_{0}; - CallbackManager callback_; + CallbackManager data_callback_; + CallbackManager advanced_data_callback_; }; class RFBridgeReceivedCodeTrigger : public Trigger { @@ -60,6 +77,13 @@ class RFBridgeReceivedCodeTrigger : public Trigger { } }; +class RFBridgeReceivedAdvancedCodeTrigger : public Trigger { + public: + explicit RFBridgeReceivedAdvancedCodeTrigger(RFBridgeComponent *parent) { + parent->add_on_advanced_code_received_callback([this](RFBridgeAdvancedData data) { this->trigger(data); }); + } +}; + template class RFBridgeSendCodeAction : public Action { public: RFBridgeSendCodeAction(RFBridgeComponent *parent) : parent_(parent) {} @@ -81,6 +105,25 @@ template class RFBridgeSendCodeAction : public Action { RFBridgeComponent *parent_; }; +template class RFBridgeSendAdvancedCodeAction : public Action { + public: + RFBridgeSendAdvancedCodeAction(RFBridgeComponent *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(uint8_t, length) + TEMPLATABLE_VALUE(uint8_t, protocol) + TEMPLATABLE_VALUE(std::string, code) + + void play(Ts... x) { + RFBridgeAdvancedData data{}; + data.length = this->length_.value(x...); + data.protocol = this->protocol_.value(x...); + data.code = this->code_.value(x...); + this->parent_->send_advanced_code(data); + } + + protected: + RFBridgeComponent *parent_; +}; + template class RFBridgeLearnAction : public Action { public: RFBridgeLearnAction(RFBridgeComponent *parent) : parent_(parent) {} @@ -91,5 +134,36 @@ template class RFBridgeLearnAction : public Action { RFBridgeComponent *parent_; }; +template class RFBridgeStartAdvancedSniffingAction : public Action { + public: + RFBridgeStartAdvancedSniffingAction(RFBridgeComponent *parent) : parent_(parent) {} + + void play(Ts... x) { this->parent_->start_advanced_sniffing(); } + + protected: + RFBridgeComponent *parent_; +}; + +template class RFBridgeStopAdvancedSniffingAction : public Action { + public: + RFBridgeStopAdvancedSniffingAction(RFBridgeComponent *parent) : parent_(parent) {} + + void play(Ts... x) { this->parent_->stop_advanced_sniffing(); } + + protected: + RFBridgeComponent *parent_; +}; + +template class RFBridgeSendRawAction : public Action { + public: + RFBridgeSendRawAction(RFBridgeComponent *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(std::string, raw) + + void play(Ts... x) { this->parent_->send_raw(this->raw_.value(x...)); } + + protected: + RFBridgeComponent *parent_; +}; + } // namespace rf_bridge } // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index c7da84e743..535207958d 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -266,6 +266,7 @@ CONF_KEEP_ON_TIME = 'keep_on_time' CONF_KEEPALIVE = 'keepalive' CONF_KEY = 'key' CONF_LAMBDA = 'lambda' +CONF_LENGTH = 'length' CONF_LEVEL = 'level' CONF_LG = 'lg' CONF_LIBRARIES = 'libraries' diff --git a/tests/test3.yaml b/tests/test3.yaml index 53484bec6a..bdc93a036f 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -700,7 +700,7 @@ climate: kp: 0.0 ki: 0.0 kd: 0.0 - + cover: - platform: endstop @@ -862,6 +862,22 @@ rf_bridge: code: 0x123456 - rf_bridge.learn + on_advanced_code_received: + - lambda: |- + uint32_t test; + std::string test_code; + test = data.length; + test = data.protocol; + test_code = data.code; + - rf_bridge.start_advanced_sniffing + - rf_bridge.stop_advanced_sniffing + - rf_bridge.send_advanced_code: + length: 0x04 + protocol: 0x01 + code: 'ABC123' + - rf_bridge.send_raw: + raw: 'AAA5070008001000ABC12355' + display: - platform: max7219digit cs_pin: GPIO15