From 3744f571d3c95f681b01b29f116086304f302e39 Mon Sep 17 00:00:00 2001 From: mulcmu Date: Mon, 18 Dec 2023 12:56:25 -0500 Subject: [PATCH] add_learn --- base.yaml | 7 ++++ components/ratgdo/ratgdo.cpp | 49 +++++++++++++++++++++- components/ratgdo/ratgdo.h | 10 ++++- components/ratgdo/ratgdo_state.cpp | 14 +++++++ components/ratgdo/ratgdo_state.h | 7 ++++ components/ratgdo/switch/__init__.py | 35 ++++++++++++++++ components/ratgdo/switch/ratgdo_switch.cpp | 46 ++++++++++++++++++++ components/ratgdo/switch/ratgdo_switch.h | 29 +++++++++++++ 8 files changed, 194 insertions(+), 3 deletions(-) create mode 100644 components/ratgdo/switch/__init__.py create mode 100644 components/ratgdo/switch/ratgdo_switch.cpp create mode 100644 components/ratgdo/switch/ratgdo_switch.h diff --git a/base.yaml b/base.yaml index 68e8977..8f89f5c 100644 --- a/base.yaml +++ b/base.yaml @@ -58,6 +58,13 @@ switch: output: true name: "Status obstruction" entity_category: diagnostic + - platform: ratgdo + id: "${id_prefix}_learn" + type: learn + ratgdo_id: ${id_prefix} + name: "Learn" + icon: mdi:plus-box + entity_category: diagnostic binary_sensor: - platform: ratgdo diff --git a/components/ratgdo/ratgdo.cpp b/components/ratgdo/ratgdo.cpp index 21b97ca..852c1e2 100644 --- a/components/ratgdo/ratgdo.cpp +++ b/components/ratgdo/ratgdo.cpp @@ -194,6 +194,16 @@ namespace ratgdo { this->motion_state = MotionState::CLEAR; // when the status message is read, reset motion state to 0|clear this->motor_state = MotorState::OFF; // when the status message is read, reset motor state to 0|off + if (*this->learn_state != static_cast((byte2 >> 5) & 1)) { + this->learn_state = static_cast((byte2 >> 5) & 1); + if (*this->learn_state == LearnState::ACTIVE && this->learn_poll_status_) { + set_interval("learn_poll", 1000, [=] { this->send_command(Command::GET_STATUS); }); + } else { + cancel_interval("learn_poll"); + learn_poll_status_ = true; + } + } + if (this->obstruction_from_status_) { // ESP_LOGD(TAG, "Obstruction: reading from byte2, bit2, status=%d", ((byte2 >> 2) & 1) == 1); this->obstruction_state = static_cast((byte1 >> 6) & 1); @@ -207,10 +217,12 @@ namespace ratgdo { this->send_command(Command::GET_OPENINGS); } - ESP_LOGD(TAG, "Status: door=%s light=%s lock=%s", + ESP_LOGD(TAG, "Status: door=%s light=%s lock=%s learn=%s", DoorState_to_string(*this->door_state), LightState_to_string(*this->light_state), - LockState_to_string(*this->lock_state)); + LockState_to_string(*this->lock_state), + LearnState_to_string(*this->learn_state)); + } else if (cmd == Command::LIGHT) { if (nibble == 0) { this->light_state = LightState::OFF; @@ -251,6 +263,11 @@ namespace ratgdo { auto seconds = (byte1 << 8) | byte2; ESP_LOGD(TAG, "Time to close (TTC): %ds", seconds); } + if (cmd == Command::LEARN) { + if (nibble == 1) { // LEARN sent from wall control, it will poll status every second + learn_poll_status_ = false; + } + } return cmd; } @@ -724,6 +741,30 @@ namespace ratgdo { return *this->light_state; } + // Learn functions + void RATGDOComponent::activate_learn() + { + // Send LEARN with nibble = 0 then nibble = 1 to mimic wall control learn button + learn_poll_status_ = true; + this->send_command(Command::LEARN, 0); + set_timeout(150, [=] { this->send_command(Command::LEARN, 1); }); + set_timeout(500, [=] { this->send_command(Command::GET_STATUS); }); + } + + void RATGDOComponent::inactivate_learn() + { + // Send LEARN twice with nibble = 0 to inactivate learn and get status to update switch state + this->send_command(Command::LEARN, 0); + set_timeout(150, [=] { this->send_command(Command::LEARN, 0); }); + set_timeout(500, [=] { this->send_command(Command::GET_STATUS); }); + } + + void RATGDOComponent::toggle_learn() + { + this->learn_state = learn_state_toggle(*this->learn_state); + // this->send_command(Command::learn, data::LOCK_TOGGLE); + } + void RATGDOComponent::subscribe_rolling_code_counter(std::function&& f) { // change update to children is defered until after component loop @@ -779,6 +820,10 @@ namespace ratgdo { { this->sync_failed.subscribe(std::move(f)); } + void RATGDOComponent::subscribe_learn_state(std::function&& f) + { + this->learn_state.subscribe([=](LearnState state) { defer("learn_state", [=] { f(state); }); }); + } } // namespace ratgdo } // namespace esphome diff --git a/components/ratgdo/ratgdo.h b/components/ratgdo/ratgdo.h index 147b012..c793aba 100644 --- a/components/ratgdo/ratgdo.h +++ b/components/ratgdo/ratgdo.h @@ -64,7 +64,7 @@ namespace ratgdo { (PAIR_3, 0x0a0), (PAIR_3_RESP, 0x0a1), - (LEARN_2, 0x181), + (LEARN, 0x181), (LOCK, 0x18c), (DOOR_ACTION, 0x280), (LIGHT, 0x281), @@ -124,6 +124,7 @@ namespace ratgdo { observable motor_state { MotorState::UNKNOWN }; observable button_state { ButtonState::UNKNOWN }; observable motion_state { MotionState::UNKNOWN }; + observable learn_state { LearnState::UNKNOWN }; OnceCallbacks door_state_received; OnceCallbacks command_sent; @@ -173,6 +174,10 @@ namespace ratgdo { void lock(); void unlock(); + void toggle_learn(); + void activate_learn(); + void inactivate_learn(); + // button functionality void query_status(); void query_openings(); @@ -191,6 +196,7 @@ namespace ratgdo { void subscribe_button_state(std::function&& f); void subscribe_motion_state(std::function&& f); void subscribe_sync_failed(std::function&& f); + void subscribe_learn_state(std::function&& f); protected: // tx data @@ -203,6 +209,8 @@ namespace ratgdo { bool obstruction_from_status_ { false }; + bool learn_poll_status_ { true }; + InternalGPIOPin* output_gdo_pin_; InternalGPIOPin* input_gdo_pin_; InternalGPIOPin* input_obst_pin_; diff --git a/components/ratgdo/ratgdo_state.cpp b/components/ratgdo/ratgdo_state.cpp index 2a6ba40..f5b2054 100644 --- a/components/ratgdo/ratgdo_state.cpp +++ b/components/ratgdo/ratgdo_state.cpp @@ -31,5 +31,19 @@ namespace ratgdo { } } + LearnState learn_state_toggle(LearnState state) + { + switch (state) { + case LearnState::ACTIVE: + return LearnState::INACTIVE; + case LearnState::INACTIVE: + return LearnState::ACTIVE; + // 2 and 3 appears sometimes + case LearnState::UNKNOWN: + default: + return LearnState::UNKNOWN; + } + } + } // namespace ratgdo } // namespace esphome diff --git a/components/ratgdo/ratgdo_state.h b/components/ratgdo/ratgdo_state.h index e347279..e994045 100644 --- a/components/ratgdo/ratgdo_state.h +++ b/components/ratgdo/ratgdo_state.h @@ -64,5 +64,12 @@ namespace ratgdo { (RELEASED, 1), (UNKNOWN, 2)) + /// Enum for learn states. + ENUM(LearnState, uint8_t, + (INACTIVE, 0), + (ACTIVE, 1), + (UNKNOWN, 2)) + LearnState learn_state_toggle(LearnState state); + } // namespace ratgdo } // namespace esphome diff --git a/components/ratgdo/switch/__init__.py b/components/ratgdo/switch/__init__.py new file mode 100644 index 0000000..9028cca --- /dev/null +++ b/components/ratgdo/switch/__init__.py @@ -0,0 +1,35 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import switch +from esphome.const import CONF_ID + +from .. import RATGDO_CLIENT_SCHMEA, ratgdo_ns, register_ratgdo_child + +DEPENDENCIES = ["ratgdo"] + +RATGDOSwitch = ratgdo_ns.class_("RATGDOSwitch", switch.Switch, cg.Component) +SwitchType = ratgdo_ns.enum("SwitchType") + +CONF_TYPE = "type" +TYPES = { + "learn": SwitchType.RATGDO_LEARN, +} + + +CONFIG_SCHEMA = ( + switch.switch_schema(RATGDOSwitch) + .extend( + { + cv.Required(CONF_TYPE): cv.enum(TYPES, lower=True), + } + ) + .extend(RATGDO_CLIENT_SCHMEA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await switch.register_switch(var, config) + await cg.register_component(var, config) + cg.add(var.set_switch_type(config[CONF_TYPE])) + await register_ratgdo_child(var, config) diff --git a/components/ratgdo/switch/ratgdo_switch.cpp b/components/ratgdo/switch/ratgdo_switch.cpp new file mode 100644 index 0000000..9ab103a --- /dev/null +++ b/components/ratgdo/switch/ratgdo_switch.cpp @@ -0,0 +1,46 @@ +#include "ratgdo_switch.h" +#include "../ratgdo_state.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ratgdo { + + static const char* const TAG = "ratgdo.switch"; + + void RATGDOSwitch::dump_config() + { + LOG_SWITCH("", "RATGDO Switch", this); + if (this->switch_type_ == SwitchType::RATGDO_LEARN) { + ESP_LOGCONFIG(TAG, " Type: Learn"); + } + } + + void RATGDOSwitch::setup() + { + if (this->switch_type_ == SwitchType::RATGDO_LEARN) { + this->parent_->subscribe_learn_state([=](LearnState state) { + this->on_learn_state(state); + }); + } + } + + void RATGDOSwitch::on_learn_state(LearnState state) + { + bool value = state == LearnState::ACTIVE; + this->state = value; + this->publish_state(value); + } + + void RATGDOSwitch::write_state(bool state) + { + if (this->switch_type_ == SwitchType::RATGDO_LEARN) { + if (state) { + this->parent_->activate_learn(); + } else { + this->parent_->inactivate_learn(); + } + } + } + +} // namespace ratgdo +} // namespace esphome diff --git a/components/ratgdo/switch/ratgdo_switch.h b/components/ratgdo/switch/ratgdo_switch.h new file mode 100644 index 0000000..42ef1d4 --- /dev/null +++ b/components/ratgdo/switch/ratgdo_switch.h @@ -0,0 +1,29 @@ +#pragma once + +#include "../ratgdo.h" +#include "../ratgdo_state.h" +#include "esphome/components/switch/switch.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace ratgdo { + + enum SwitchType { + RATGDO_LEARN + }; + + class RATGDOSwitch : public switch_::Switch, public RATGDOClient, public Component { + public: + void dump_config() override; + void setup() override; + void set_switch_type(SwitchType switch_type_) { this->switch_type_ = switch_type_; } + + void on_learn_state(LearnState state); + void write_state(bool state) override; + + protected: + SwitchType switch_type_; + }; + +} // namespace ratgdo +} // namespace esphome