diff --git a/.gitignore b/.gitignore index 297bf82..b614c27 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ # You can modify this file to suit your needs. /.esphome/ /secrets.yaml -__pycache__/ \ No newline at end of file +__pycache__/ +.vscode diff --git a/components/secplus_gdo/cover/__init__.py b/components/secplus_gdo/cover/__init__.py index fc29ce5..7b01d78 100644 --- a/components/secplus_gdo/cover/__init__.py +++ b/components/secplus_gdo/cover/__init__.py @@ -40,7 +40,6 @@ CONF_PRE_CLOSE_WARNING_DURATION = "pre_close_warning_duration" CONF_PRE_CLOSE_WARNING_START = "pre_close_warning_start" CONF_PRE_CLOSE_WARNING_END = "pre_close_warning_end" -CONF_TOGGLE_ONLY = "toggle_only" CONFIG_SCHEMA = cover.COVER_SCHEMA.extend( { @@ -52,7 +51,6 @@ cv.Optional(CONF_PRE_CLOSE_WARNING_END): automation.validate_automation( {cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverClosingEndTrigger)} ), - cv.Optional(CONF_TOGGLE_ONLY, default=False): cv.boolean, } ).extend(SECPLUS_GDO_CONFIG_SCHEMA) @@ -62,10 +60,8 @@ async def to_code(config): await cg.register_component(var, config) await cover.register_cover(var, config) parent = await cg.get_variable(config[CONF_SECPLUS_GDO_ID]) - text = "std::bind(&" + str(GDODoor) + "::set_state," + str(config[CONF_ID]) + ",std::placeholders::_1,std::placeholders::_2)" - cg.add(parent.register_door(cg.RawExpression(text))) + cg.add(parent.register_door(var)) cg.add(var.set_pre_close_warning_duration(config[CONF_PRE_CLOSE_WARNING_DURATION])) - cg.add(var.set_toggle_only(config[CONF_TOGGLE_ONLY])) for conf in config.get(CONF_PRE_CLOSE_WARNING_START, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) diff --git a/components/secplus_gdo/cover/gdo_door.cpp b/components/secplus_gdo/cover/gdo_door.cpp index 776f9c5..a8dcdaa 100644 --- a/components/secplus_gdo/cover/gdo_door.cpp +++ b/components/secplus_gdo/cover/gdo_door.cpp @@ -41,6 +41,8 @@ void GDODoor::set_state(gdo_door_state_t state, float position) { this->position = position; break; case GDO_DOOR_STATE_STOPPED: + this->prev_operation = this->current_operation; + // falls through case GDO_DOOR_STATE_MAX: default: this->current_operation = COVER_OPERATION_IDLE; @@ -49,6 +51,7 @@ void GDODoor::set_state(gdo_door_state_t state, float position) { } this->publish_state(false); + this->state_ = state; } void GDODoor::do_action_after_warning(const cover::CoverCall& call) { @@ -79,7 +82,8 @@ void GDODoor::do_action(const cover::CoverCall& call) { ESP_LOGD(TAG, "Sending STOP action"); gdo_door_stop(); } - if (call.get_toggle()) { + + if (call.get_toggle()) { if (this->position == COVER_CLOSED) { this->set_state(GDO_DOOR_STATE_OPENING, this->position); } else if (this->position == COVER_OPEN) { @@ -88,26 +92,47 @@ void GDODoor::do_action(const cover::CoverCall& call) { ESP_LOGD(TAG, "Sending TOGGLE action"); gdo_door_toggle(); } + if (call.get_position().has_value()) { auto pos = *call.get_position(); if (pos == COVER_OPEN) { - this->set_state(GDO_DOOR_STATE_OPENING, this->position); if (this->toggle_only_) { ESP_LOGD(TAG, "Sending TOGGLE action"); gdo_door_toggle(); + if (this->state_ == GDO_DOOR_STATE_STOPPED && this->prev_operation == COVER_OPERATION_OPENING) { + // If the door was stopped while opening, then we need to toggle to stop, then toggle again to open, + this->set_timeout("stop_door", 1000, [=]() { + gdo_door_stop(); + }); + this->set_timeout("open_door", 2000, [=]() { + gdo_door_toggle(); + }); + } } else { ESP_LOGD(TAG, "Sending OPEN action"); gdo_door_open(); } - } else if (pos == COVER_CLOSED) { - this->set_state(GDO_DOOR_STATE_CLOSING, this->position); + + this->set_state(GDO_DOOR_STATE_OPENING, this->position); + } else if (pos == COVER_CLOSED) { if (this->toggle_only_) { ESP_LOGD(TAG, "Sending TOGGLE action"); gdo_door_toggle(); + if (this->state_ == GDO_DOOR_STATE_STOPPED && this->prev_operation == COVER_OPERATION_CLOSING) { + // If the door was stopped while closing, then we need to toggle to stop, then toggle again to close, + this->set_timeout("stop_door", 1000, [=]() { + gdo_door_stop(); + }); + this->set_timeout("close_door", 2000, [=]() { + gdo_door_toggle(); + }); + } } else { ESP_LOGD(TAG, "Sending CLOSE action"); gdo_door_close(); } + + this->set_state(GDO_DOOR_STATE_CLOSING, this->position); } else { ESP_LOGD(TAG, "Moving garage door to position %f", pos); gdo_door_move_to_target(10000 - (pos * 10000)); @@ -126,22 +151,44 @@ void GDODoor::control(const cover::CoverCall& call) { this->pre_close_end_trigger->trigger(); } } + this->target_position_ = this->position; this->do_action(call); } if (call.get_toggle()) { ESP_LOGD(TAG, "Toggle command received"); if (this->position != COVER_CLOSED) { + this->target_position_ = COVER_CLOSED; this->do_action_after_warning(call); } else { + this->target_position_ = COVER_OPEN; this->do_action(call); } } if (call.get_position().has_value()) { auto pos = *call.get_position(); + if (this->position == pos) { + ESP_LOGD(TAG, "Door is already %s", pos == COVER_OPEN ? "open" : "closed"); + this->publish_state(false); + return; + } + + if ((this->current_operation == COVER_OPERATION_OPENING && pos > this->position) || + (this->current_operation == COVER_OPERATION_CLOSING && pos < this->position)) { + ESP_LOGD(TAG, "Door is already moving in target direction; target position: %.0f%%", *this->target_position_); + this->publish_state(false); + return; + } if (this->pre_close_active_) { + // don't start the pre-close again if the door is already going to close. + if (pos < this->position) { + ESP_LOGD(TAG, "Door is already closing"); + this->publish_state(false); + return; + } + ESP_LOGD(TAG, "Canceling pending action"); this->cancel_timeout("pre_close"); this->pre_close_active_ = false; @@ -172,6 +219,8 @@ void GDODoor::control(const cover::CoverCall& call) { this->do_action(call); } } + + this->target_position_ = pos; } } diff --git a/components/secplus_gdo/cover/gdo_door.h b/components/secplus_gdo/cover/gdo_door.h index 96276e7..3f23580 100644 --- a/components/secplus_gdo/cover/gdo_door.h +++ b/components/secplus_gdo/cover/gdo_door.h @@ -45,7 +45,7 @@ using namespace esphome::cover; } void do_action(const cover::CoverCall& call); - void do_action_after_warning(const cover::CoverCall& call); + void do_action_after_warning(const cover::CoverCall& call); void set_pre_close_warning_duration(uint32_t ms) { this->pre_close_duration_ = ms; } void set_toggle_only(bool val) { this->toggle_only_ = val; } void set_state(gdo_door_state_t state, float position); @@ -55,9 +55,12 @@ using namespace esphome::cover; CoverClosingStartTrigger *pre_close_start_trigger{nullptr}; CoverClosingEndTrigger *pre_close_end_trigger{nullptr}; - uint32_t pre_close_duration_{0}; + uint32_t pre_close_duration_{0}; bool pre_close_active_{false}; bool toggle_only_{false}; + optional target_position_{0}; + CoverOperation prev_operation{COVER_OPERATION_IDLE}; + gdo_door_state_t state_{GDO_DOOR_STATE_MAX}; }; } // namespace secplus_gdo } // namespace esphome diff --git a/components/secplus_gdo/secplus_gdo.cpp b/components/secplus_gdo/secplus_gdo.cpp index bc6c110..ca9cb37 100644 --- a/components/secplus_gdo/secplus_gdo.cpp +++ b/components/secplus_gdo/secplus_gdo.cpp @@ -117,6 +117,11 @@ namespace secplus_gdo { } void GDOComponent::setup() { + // Set the toggle only state and control here because we cannot guarantee the cover instance was created before the switch + this->door_->set_toggle_only(this->toggle_only_switch_->state); + this->toggle_only_switch_->set_control_function(std::bind(&esphome::secplus_gdo::GDODoor::set_toggle_only, + this->door_, std::placeholders::_1)); + gdo_config_t gdo_conf = { .uart_num = UART_NUM_1, .obst_from_status = true, diff --git a/components/secplus_gdo/secplus_gdo.h b/components/secplus_gdo/secplus_gdo.h index b3d3cab..4f16d7e 100644 --- a/components/secplus_gdo/secplus_gdo.h +++ b/components/secplus_gdo/secplus_gdo.h @@ -21,6 +21,8 @@ #include "number/gdo_number.h" #include "esphome/core/defines.h" #include "select/gdo_select.h" +#include "switch/gdo_switch.h" +#include "cover/gdo_door.h" #include "gdo.h" namespace esphome { @@ -35,7 +37,9 @@ namespace secplus_gdo { float get_setup_priority() const override { return setup_priority::LATE; } void register_protocol_select(GDOSelect *select) { this->protocol_select_ = select; } - void set_protocol_state(gdo_protocol_type_t protocol) { if (this->protocol_select_) { this->protocol_select_->update_state(protocol); } } + void set_protocol_state(gdo_protocol_type_t protocol) { if (this->protocol_select_) { + this->protocol_select_->update_state(protocol); } + } void register_motion(std::function f) { f_motion = f; } void set_motion_state(gdo_motion_state_t state) { if (f_motion) { f_motion(state == GDO_MOTION_STATE_DETECTED); } } @@ -54,8 +58,8 @@ namespace secplus_gdo { void register_openings(std::function f) { f_openings = f; } void set_openings(uint16_t openings) { if (f_openings) { f_openings(openings); } } - void register_door(std::function f) { f_door = f; } - void set_door_state(gdo_door_state_t state, float position) { if (f_door) { f_door(state, position); } } + void register_door(GDODoor *door) { this->door_ = door; } + void set_door_state(gdo_door_state_t state, float position) { if (this->door_) { this->door_->set_state(state, position); } } void register_light(std::function f) { f_light = f; } void set_light_state(gdo_light_state_t state) { if (f_light) { f_light(state); } } @@ -63,8 +67,10 @@ namespace secplus_gdo { void register_lock(std::function f) { f_lock = f; } void set_lock_state(gdo_lock_state_t state) { if (f_lock) { f_lock(state); } } - void register_learn(std::function f) { f_learn = f; } - void set_learn_state(gdo_learn_state_t state) { if (f_learn) { f_learn(state == GDO_LEARN_STATE_ACTIVE); } } + void register_learn(GDOSwitch *sw) { this->learn_switch_ = sw; } + void set_learn_state(gdo_learn_state_t state) { if (this->learn_switch_) { + this->learn_switch_->write_state(state == GDO_LEARN_STATE_ACTIVE); } + } void register_open_duration(GDONumber* num) { open_duration_ = num; } void set_open_duration(uint16_t ms ) { if (open_duration_) { open_duration_->update_state(ms); } } @@ -78,9 +84,10 @@ namespace secplus_gdo { void register_rolling_code(GDONumber* num) { rolling_code_ = num; } void set_rolling_code(uint32_t num) { if (rolling_code_) { rolling_code_->update_state(num); } } + void register_toggle_only(GDOSwitch *sw) { this->toggle_only_switch_ = sw; } + protected: - gdo_status_t status_; - std::function f_door{nullptr}; + gdo_status_t status_{}; std::function f_lock{nullptr}; std::function f_light{nullptr}; std::function f_openings{nullptr}; @@ -88,12 +95,14 @@ namespace secplus_gdo { std::function f_obstruction{nullptr}; std::function f_button{nullptr}; std::function f_motor{nullptr}; - std::function f_learn{nullptr}; + GDODoor* door_{nullptr}; GDONumber* open_duration_{nullptr}; GDONumber* close_duration_{nullptr}; GDONumber* client_id_{nullptr}; GDONumber* rolling_code_{nullptr}; GDOSelect* protocol_select_{nullptr}; + GDOSwitch* learn_switch_{nullptr}; + GDOSwitch* toggle_only_switch_{nullptr}; }; // GDOComponent } // namespace secplus_gdo diff --git a/components/secplus_gdo/switch/__init__.py b/components/secplus_gdo/switch/__init__.py index aed0497..c9be5ff 100644 --- a/components/secplus_gdo/switch/__init__.py +++ b/components/secplus_gdo/switch/__init__.py @@ -12,6 +12,7 @@ CONF_TYPE = "type" TYPES = { "learn": "register_learn", + "toggle_only": "register_toggle_only", } @@ -32,5 +33,7 @@ async def to_code(config): await cg.register_component(var, config) parent = await cg.get_variable(config[CONF_SECPLUS_GDO_ID]) fcall = str(parent) + "->" + str(TYPES[config[CONF_TYPE]]) - text = fcall + "(std::bind(&" + str(GDOSwitch) + "::write_state," + str(config[CONF_ID]) + ",std::placeholders::_1))" - cg.add((cg.RawExpression(text))) \ No newline at end of file + text = fcall + "(" + str(var) + ")" + cg.add((cg.RawExpression(text))) + text = "secplus_gdo::SwitchType::" + str(config[CONF_TYPE]).upper() + cg.add(var.set_type(cg.RawExpression(text))) diff --git a/components/secplus_gdo/switch/gdo_switch.h b/components/secplus_gdo/switch/gdo_switch.h index 16b8fab..6a802e8 100644 --- a/components/secplus_gdo/switch/gdo_switch.h +++ b/components/secplus_gdo/switch/gdo_switch.h @@ -8,21 +8,61 @@ #pragma once #include "esphome/components/switch/switch.h" +#include "esphome/core/preferences.h" #include "esphome/core/component.h" #include "gdo.h" namespace esphome { namespace secplus_gdo { + enum SwitchType { + LEARN, + TOGGLE_ONLY, + }; + class GDOSwitch : public switch_::Switch, public Component { public: + void dump_config() override {} + void setup() override { + bool value = false; + this->pref_ = global_preferences->make_preference(this->get_object_id_hash()); + if (!this->pref_.load(&value)) { + value = false; + } + + this->write_state(value); + } + void write_state(bool state) override { - if (state) { - gdo_activate_learn(); - } else { - gdo_deactivate_learn(); + if (state == this->state) { + return; } + + if (this->type_ == SwitchType::TOGGLE_ONLY) { + if (this->f_control) { + this->f_control(state); + this->pref_.save(&state); + } + } + + if (this->type_ == SwitchType::LEARN) { + if (state) { + gdo_activate_learn(); + } else { + gdo_deactivate_learn(); + } + } + + this->publish_state(state); } + + void set_type(SwitchType type) { this->type_ = type; } + void set_control_function(std::function f) { f_control = f; } + + protected: + SwitchType type_{SwitchType::LEARN}; + std::function f_control{nullptr}; + ESPPreferenceObject pref_; }; } // namespace secplus_gdo diff --git a/garage-door-GDOv2-Q.yaml b/garage-door-GDOv2-Q.yaml index 2dc2deb..51119ca 100644 --- a/garage-door-GDOv2-Q.yaml +++ b/garage-door-GDOv2-Q.yaml @@ -89,7 +89,7 @@ substitutions: status_led: GPIO18 external_components: - - source: github://konnected-io/konnected-esphome@master + - source: github://konnected-io/konnected-esphome@door-op-fixes components: [ mdns, secplus_gdo ] # Un-comment below and comment above for local modification @@ -106,7 +106,7 @@ packages: remote_package: url: https://github.com/konnected-io/konnected-esphome - ref: master + ref: door-op-fixes refresh: 5min files: @@ -155,7 +155,7 @@ packages: # Enables automatic discovery and upgrades via ESPHome Dashboard # more: https://esphome.io/guides/getting_started_hassio.html dashboard_import: - package_import_url: github://konnected-io/konnected-esphome/garage-door-GDOv2-Q.yaml@master + package_import_url: github://konnected-io/konnected-esphome/garage-door-GDOv2-Q.yaml@door-op-fixes import_full_config: false #### @@ -193,6 +193,6 @@ web_server: esphome: platformio_options: lib_deps: - - https://github.com/konnected-io/gdolib + - https://github.com/konnected-io/gdolib#command-delay-rx-fix build_flags: - -DUART_SCLK_DEFAULT=UART_SCLK_APB \ No newline at end of file diff --git a/packages/core-esp32-s3.yaml b/packages/core-esp32-s3.yaml index 18e280b..3b2b473 100644 --- a/packages/core-esp32-s3.yaml +++ b/packages/core-esp32-s3.yaml @@ -14,11 +14,13 @@ esphome: id: device_id state: !lambda 'return get_mac_address();' -esp32: +esp32: board: esp32-s3-devkitc-1 framework: type: esp-idf version: recommended + sdkconfig_options: + CONFIG_LWIP_MAX_SOCKETS: "16" substitutions: status_led_inverted: "false" diff --git a/packages/secplus-gdo.yaml b/packages/secplus-gdo.yaml index 194a27e..0e30338 100644 --- a/packages/secplus-gdo.yaml +++ b/packages/secplus-gdo.yaml @@ -74,6 +74,13 @@ switch: secplus_gdo_id: gdo_blaq name: Learn icon: mdi:plus-box + - platform: secplus_gdo + id: gdo_toggle_only + type: toggle_only + secplus_gdo_id: gdo_blaq + name: Toggle Only + icon: mdi:plus-box + entity_category: config select: - platform: secplus_gdo