From 6f2ca4c2a704ff449ea1fcf62fe475ffc3865688 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 02:54:22 +0000 Subject: [PATCH 01/24] Initial plan From 0d63c755b73c60a1cc2de60b85f506a5fb77050e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 02:58:20 +0000 Subject: [PATCH 02/24] Add new cover triggers: on_opening, on_closing, on_idle, on_opened Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com> --- esphome/components/cover/__init__.py | 47 ++++++++++++++++- esphome/components/cover/automation.h | 59 ++++++++++++++++++++++ esphome/const.py | 4 ++ tests/components/template/common-base.yaml | 27 ++++++++++ 4 files changed, 135 insertions(+), 2 deletions(-) diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 383daee083a..673f0f1ad77 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -9,7 +9,11 @@ from esphome.const import ( CONF_ICON, CONF_ID, CONF_MQTT_ID, + CONF_ON_CLOSING, + CONF_ON_IDLE, CONF_ON_OPEN, + CONF_ON_OPENED, + CONF_ON_OPENING, CONF_POSITION, CONF_POSITION_COMMAND_TOPIC, CONF_POSITION_STATE_TOPIC, @@ -86,9 +90,13 @@ CoverIsClosedCondition = cover_ns.class_("CoverIsClosedCondition", Condition) # Triggers CoverOpenTrigger = cover_ns.class_("CoverOpenTrigger", automation.Trigger.template()) +CoverOpenedTrigger = cover_ns.class_("CoverOpenedTrigger", automation.Trigger.template()) CoverClosedTrigger = cover_ns.class_( "CoverClosedTrigger", automation.Trigger.template() ) +CoverOpeningTrigger = cover_ns.class_("CoverOpeningTrigger", automation.Trigger.template()) +CoverClosingTrigger = cover_ns.class_("CoverClosingTrigger", automation.Trigger.template()) +CoverIdleTrigger = cover_ns.class_("CoverIdleTrigger", automation.Trigger.template()) CONF_ON_CLOSED = "on_closed" @@ -111,9 +119,17 @@ _COVER_SCHEMA = ( cv.Optional(CONF_TILT_STATE_TOPIC): cv.All( cv.requires_component("mqtt"), cv.subscribe_topic ), - cv.Optional(CONF_ON_OPEN): automation.validate_automation( + cv.Optional(CONF_ON_OPEN): cv.All( + cv.deprecated(CONF_ON_OPENED, "2026.2.0"), + automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenTrigger), + } + ), + ), + cv.Optional(CONF_ON_OPENED): automation.validate_automation( { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenTrigger), + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenedTrigger), } ), cv.Optional(CONF_ON_CLOSED): automation.validate_automation( @@ -121,6 +137,21 @@ _COVER_SCHEMA = ( cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverClosedTrigger), } ), + cv.Optional(CONF_ON_OPENING): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpeningTrigger), + } + ), + cv.Optional(CONF_ON_CLOSING): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverClosingTrigger), + } + ), + cv.Optional(CONF_ON_IDLE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverIdleTrigger), + } + ), } ) ) @@ -160,9 +191,21 @@ async def setup_cover_core_(var, config): for conf in config.get(CONF_ON_OPEN, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_OPENED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) for conf in config.get(CONF_ON_CLOSED, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_OPENING, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_CLOSING, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_IDLE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: mqtt_ = cg.new_Pvariable(mqtt_id, var) diff --git a/esphome/components/cover/automation.h b/esphome/components/cover/automation.h index c0345a7cc61..28f665f8835 100644 --- a/esphome/components/cover/automation.h +++ b/esphome/components/cover/automation.h @@ -119,6 +119,17 @@ class CoverOpenTrigger : public Trigger<> { } }; +class CoverOpenedTrigger : public Trigger<> { + public: + CoverOpenedTrigger(Cover *a_cover) { + a_cover->add_on_state_callback([this, a_cover]() { + if (a_cover->is_fully_open()) { + this->trigger(); + } + }); + } +}; + class CoverClosedTrigger : public Trigger<> { public: CoverClosedTrigger(Cover *a_cover) { @@ -130,4 +141,52 @@ class CoverClosedTrigger : public Trigger<> { } }; +class CoverOpeningTrigger : public Trigger<> { + public: + CoverOpeningTrigger(Cover *a_cover) { + a_cover->add_on_state_callback([this, a_cover]() { + auto current_op = a_cover->current_operation; + if (current_op == COVER_OPERATION_OPENING && this->last_operation_ != COVER_OPERATION_OPENING) { + this->trigger(); + } + this->last_operation_ = current_op; + }); + } + + protected: + CoverOperation last_operation_{COVER_OPERATION_IDLE}; +}; + +class CoverClosingTrigger : public Trigger<> { + public: + CoverClosingTrigger(Cover *a_cover) { + a_cover->add_on_state_callback([this, a_cover]() { + auto current_op = a_cover->current_operation; + if (current_op == COVER_OPERATION_CLOSING && this->last_operation_ != COVER_OPERATION_CLOSING) { + this->trigger(); + } + this->last_operation_ = current_op; + }); + } + + protected: + CoverOperation last_operation_{COVER_OPERATION_IDLE}; +}; + +class CoverIdleTrigger : public Trigger<> { + public: + CoverIdleTrigger(Cover *a_cover) { + a_cover->add_on_state_callback([this, a_cover]() { + auto current_op = a_cover->current_operation; + if (current_op == COVER_OPERATION_IDLE && this->last_operation_ != COVER_OPERATION_IDLE) { + this->trigger(); + } + this->last_operation_ = current_op; + }); + } + + protected: + CoverOperation last_operation_{COVER_OPERATION_IDLE}; +}; + } // namespace esphome::cover diff --git a/esphome/const.py b/esphome/const.py index 4243b2e25d3..aee9b59ceab 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -703,6 +703,10 @@ CONF_ON_LOOP = "on_loop" CONF_ON_MESSAGE = "on_message" CONF_ON_MULTI_CLICK = "on_multi_click" CONF_ON_OPEN = "on_open" +CONF_ON_OPENED = "on_opened" +CONF_ON_OPENING = "on_opening" +CONF_ON_CLOSING = "on_closing" +CONF_ON_IDLE = "on_idle" CONF_ON_OSCILLATING_SET = "on_oscillating_set" CONF_ON_PRESET_SET = "on_preset_set" CONF_ON_PRESS = "on_press" diff --git a/tests/components/template/common-base.yaml b/tests/components/template/common-base.yaml index 9dc65fbab8e..93672243fcf 100644 --- a/tests/components/template/common-base.yaml +++ b/tests/components/template/common-base.yaml @@ -245,6 +245,33 @@ cover: stop_action: - logger.log: stop_action optimistic: true + - platform: template + name: "Template Cover with Triggers" + id: template_cover_with_triggers + lambda: |- + if (id(some_binary_sensor).state) { + return COVER_OPEN; + } + return COVER_CLOSED; + open_action: + - logger.log: open_action + close_action: + - logger.log: close_action + stop_action: + - logger.log: stop_action + optimistic: true + on_open: + - logger.log: "Cover on_open (deprecated)" + on_opened: + - logger.log: "Cover fully opened" + on_closed: + - logger.log: "Cover fully closed" + on_opening: + - logger.log: "Cover started opening" + on_closing: + - logger.log: "Cover started closing" + on_idle: + - logger.log: "Cover stopped moving" number: - platform: template From 1dfb8926d3328d1248d119edd2301b7c52a9abb4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 03:03:39 +0000 Subject: [PATCH 03/24] Test and validate cover triggers implementation Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com> --- esphome/components/cover/__init__.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 673f0f1ad77..da325cfc2c1 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -119,13 +119,10 @@ _COVER_SCHEMA = ( cv.Optional(CONF_TILT_STATE_TOPIC): cv.All( cv.requires_component("mqtt"), cv.subscribe_topic ), - cv.Optional(CONF_ON_OPEN): cv.All( - cv.deprecated(CONF_ON_OPENED, "2026.2.0"), - automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenTrigger), - } - ), + cv.Optional(CONF_ON_OPEN): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenTrigger), + } ), cv.Optional(CONF_ON_OPENED): automation.validate_automation( { From 35fb44da364af23d36db6e5efb47ee5f6983d2d4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 03:06:05 +0000 Subject: [PATCH 04/24] Address code review feedback: add comments and fix trigger initialization Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com> --- esphome/components/cover/automation.h | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/esphome/components/cover/automation.h b/esphome/components/cover/automation.h index 28f665f8835..ea3302ee51b 100644 --- a/esphome/components/cover/automation.h +++ b/esphome/components/cover/automation.h @@ -119,6 +119,8 @@ class CoverOpenTrigger : public Trigger<> { } }; +// Separate CoverOpenedTrigger class for improved naming clarity. +// Both on_open and on_opened are supported for backward compatibility. class CoverOpenedTrigger : public Trigger<> { public: CoverOpenedTrigger(Cover *a_cover) { @@ -146,15 +148,17 @@ class CoverOpeningTrigger : public Trigger<> { CoverOpeningTrigger(Cover *a_cover) { a_cover->add_on_state_callback([this, a_cover]() { auto current_op = a_cover->current_operation; - if (current_op == COVER_OPERATION_OPENING && this->last_operation_ != COVER_OPERATION_OPENING) { - this->trigger(); + if (current_op == COVER_OPERATION_OPENING) { + if (!this->last_operation_.has_value() || this->last_operation_.value() != COVER_OPERATION_OPENING) { + this->trigger(); + } } this->last_operation_ = current_op; }); } protected: - CoverOperation last_operation_{COVER_OPERATION_IDLE}; + optional last_operation_{}; }; class CoverClosingTrigger : public Trigger<> { @@ -162,15 +166,17 @@ class CoverClosingTrigger : public Trigger<> { CoverClosingTrigger(Cover *a_cover) { a_cover->add_on_state_callback([this, a_cover]() { auto current_op = a_cover->current_operation; - if (current_op == COVER_OPERATION_CLOSING && this->last_operation_ != COVER_OPERATION_CLOSING) { - this->trigger(); + if (current_op == COVER_OPERATION_CLOSING) { + if (!this->last_operation_.has_value() || this->last_operation_.value() != COVER_OPERATION_CLOSING) { + this->trigger(); + } } this->last_operation_ = current_op; }); } protected: - CoverOperation last_operation_{COVER_OPERATION_IDLE}; + optional last_operation_{}; }; class CoverIdleTrigger : public Trigger<> { @@ -178,15 +184,17 @@ class CoverIdleTrigger : public Trigger<> { CoverIdleTrigger(Cover *a_cover) { a_cover->add_on_state_callback([this, a_cover]() { auto current_op = a_cover->current_operation; - if (current_op == COVER_OPERATION_IDLE && this->last_operation_ != COVER_OPERATION_IDLE) { - this->trigger(); + if (current_op == COVER_OPERATION_IDLE) { + if (this->last_operation_.has_value() && this->last_operation_.value() != COVER_OPERATION_IDLE) { + this->trigger(); + } } this->last_operation_ = current_op; }); } protected: - CoverOperation last_operation_{COVER_OPERATION_IDLE}; + optional last_operation_{}; }; } // namespace esphome::cover From bccfe9eeadfa8065521cde7e94d76e2400067d1c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 03:15:06 +0000 Subject: [PATCH 05/24] Move cover-specific constants from const.py to cover component Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com> --- esphome/components/cover/__init__.py | 9 +++++---- esphome/const.py | 4 ---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index da325cfc2c1..36245f787ff 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -9,11 +9,7 @@ from esphome.const import ( CONF_ICON, CONF_ID, CONF_MQTT_ID, - CONF_ON_CLOSING, - CONF_ON_IDLE, CONF_ON_OPEN, - CONF_ON_OPENED, - CONF_ON_OPENING, CONF_POSITION, CONF_POSITION_COMMAND_TOPIC, CONF_POSITION_STATE_TOPIC, @@ -98,7 +94,12 @@ CoverOpeningTrigger = cover_ns.class_("CoverOpeningTrigger", automation.Trigger. CoverClosingTrigger = cover_ns.class_("CoverClosingTrigger", automation.Trigger.template()) CoverIdleTrigger = cover_ns.class_("CoverIdleTrigger", automation.Trigger.template()) +# Cover-specific constants CONF_ON_CLOSED = "on_closed" +CONF_ON_OPENED = "on_opened" +CONF_ON_OPENING = "on_opening" +CONF_ON_CLOSING = "on_closing" +CONF_ON_IDLE = "on_idle" _COVER_SCHEMA = ( cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) diff --git a/esphome/const.py b/esphome/const.py index aee9b59ceab..4243b2e25d3 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -703,10 +703,6 @@ CONF_ON_LOOP = "on_loop" CONF_ON_MESSAGE = "on_message" CONF_ON_MULTI_CLICK = "on_multi_click" CONF_ON_OPEN = "on_open" -CONF_ON_OPENED = "on_opened" -CONF_ON_OPENING = "on_opening" -CONF_ON_CLOSING = "on_closing" -CONF_ON_IDLE = "on_idle" CONF_ON_OSCILLATING_SET = "on_oscillating_set" CONF_ON_PRESET_SET = "on_preset_set" CONF_ON_PRESS = "on_press" From 8abb783b64cf1572799e2331a713f4d29e0bae6f Mon Sep 17 00:00:00 2001 From: clydebarrow <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 23 Jan 2026 16:42:32 +1100 Subject: [PATCH 06/24] Refactored using templates --- esphome/components/cover/__init__.py | 105 ++++++++++++++------------ esphome/components/cover/automation.h | 104 +++++-------------------- 2 files changed, 77 insertions(+), 132 deletions(-) diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 36245f787ff..87029a9fc97 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -1,3 +1,5 @@ +import logging + from esphome import automation from esphome.automation import Condition, maybe_simple_id import esphome.codegen as cg @@ -9,6 +11,7 @@ from esphome.const import ( CONF_ICON, CONF_ID, CONF_MQTT_ID, + CONF_ON_IDLE, CONF_ON_OPEN, CONF_POSITION, CONF_POSITION_COMMAND_TOPIC, @@ -53,6 +56,8 @@ DEVICE_CLASSES = [ DEVICE_CLASS_WINDOW, ] +_LOGGER = logging.getLogger(__name__) + cover_ns = cg.esphome_ns.namespace("cover") Cover = cover_ns.class_("Cover", cg.EntityBase) @@ -81,25 +86,28 @@ StopAction = cover_ns.class_("StopAction", automation.Action) ToggleAction = cover_ns.class_("ToggleAction", automation.Action) ControlAction = cover_ns.class_("ControlAction", automation.Action) CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action) -CoverIsOpenCondition = cover_ns.class_("CoverIsOpenCondition", Condition) -CoverIsClosedCondition = cover_ns.class_("CoverIsClosedCondition", Condition) - -# Triggers -CoverOpenTrigger = cover_ns.class_("CoverOpenTrigger", automation.Trigger.template()) -CoverOpenedTrigger = cover_ns.class_("CoverOpenedTrigger", automation.Trigger.template()) -CoverClosedTrigger = cover_ns.class_( - "CoverClosedTrigger", automation.Trigger.template() +CoverIsCondition = cover_ns.class_("CoverIsCondition", Condition) +CoverPositionTrigger = cover_ns.class_( + "CoverPositionTrigger", automation.Trigger.template() ) -CoverOpeningTrigger = cover_ns.class_("CoverOpeningTrigger", automation.Trigger.template()) -CoverClosingTrigger = cover_ns.class_("CoverClosingTrigger", automation.Trigger.template()) -CoverIdleTrigger = cover_ns.class_("CoverIdleTrigger", automation.Trigger.template()) +CoverTrigger = cover_ns.class_("CoverTrigger", automation.Trigger.template()) # Cover-specific constants CONF_ON_CLOSED = "on_closed" CONF_ON_OPENED = "on_opened" CONF_ON_OPENING = "on_opening" CONF_ON_CLOSING = "on_closing" -CONF_ON_IDLE = "on_idle" + +OPERATIONS = ( + CONF_ON_CLOSING, + CONF_ON_OPENING, + CONF_ON_IDLE, +) + + +def get_operation_from_conf_(conf: str) -> CoverOperation: + return getattr(CoverOperation, "COVER_OPERATION_" + conf.split("_")[1].upper()) + _COVER_SCHEMA = ( cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) @@ -120,36 +128,38 @@ _COVER_SCHEMA = ( cv.Optional(CONF_TILT_STATE_TOPIC): cv.All( cv.requires_component("mqtt"), cv.subscribe_topic ), + # Deprecated trigger cv.Optional(CONF_ON_OPEN): automation.validate_automation( { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenTrigger), + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + CoverPositionTrigger.template(1.0) + ), } ), cv.Optional(CONF_ON_OPENED): automation.validate_automation( { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenedTrigger), + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + CoverPositionTrigger.template(1.0) + ), } ), cv.Optional(CONF_ON_CLOSED): automation.validate_automation( { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverClosedTrigger), - } - ), - cv.Optional(CONF_ON_OPENING): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpeningTrigger), - } - ), - cv.Optional(CONF_ON_CLOSING): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverClosingTrigger), - } - ), - cv.Optional(CONF_ON_IDLE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverIdleTrigger), + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + CoverPositionTrigger.template(0.0) + ), } ), + **{ + cv.Optional(conf): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + CoverTrigger.template(get_operation_from_conf_(conf)) + ), + } + ) + for conf in OPERATIONS + }, } ) ) @@ -186,24 +196,23 @@ async def setup_cover_core_(var, config): if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: cg.add(var.set_device_class(device_class)) - for conf in config.get(CONF_ON_OPEN, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_OPENED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_CLOSED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_OPENING, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_CLOSING, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_IDLE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + if on_opens := config.get(CONF_ON_OPEN): + _LOGGER.warning( + "The 'on_open' trigger for covers is deprecated and will be removed in a future release. Please use 'on_opened' instead." + ) + for conf in on_opens: + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + for on_op in OPERATIONS: + if triggers := config.get(on_op): + for conf in triggers: + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + for on_state in [CONF_ON_OPENED, CONF_ON_CLOSED]: + if triggers := config.get(on_state): + for conf in triggers: + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: mqtt_ = cg.new_Pvariable(mqtt_id, var) diff --git a/esphome/components/cover/automation.h b/esphome/components/cover/automation.h index ea3302ee51b..840bf2fc4f3 100644 --- a/esphome/components/cover/automation.h +++ b/esphome/components/cover/automation.h @@ -5,7 +5,6 @@ #include "cover.h" namespace esphome::cover { - template class OpenAction : public Action { public: explicit OpenAction(Cover *cover) : cover_(cover) {} @@ -72,6 +71,7 @@ template class ControlAction : public Action { template class CoverPublishAction : public Action { public: CoverPublishAction(Cover *cover) : cover_(cover) {} + TEMPLATABLE_VALUE(float, position) TEMPLATABLE_VALUE(float, tilt) TEMPLATABLE_VALUE(CoverOperation, current_operation) @@ -90,66 +90,39 @@ template class CoverPublishAction : public Action { Cover *cover_; }; -template class CoverIsOpenCondition : public Condition { +template class CoverIsCondition : public Condition { public: - CoverIsOpenCondition(Cover *cover) : cover_(cover) {} - bool check(const Ts &...x) override { return this->cover_->is_fully_open(); } + CoverIsCondition(Cover *cover) : cover_(cover) {} + + bool check(const Ts &...x) override { return this->cover_->position == POS; } protected: Cover *cover_; }; -template class CoverIsClosedCondition : public Condition { +template class CoverPositionTrigger : public Trigger<> { public: - CoverIsClosedCondition(Cover *cover) : cover_(cover) {} - bool check(const Ts &...x) override { return this->cover_->is_fully_closed(); } + CoverPositionTrigger(Cover *a_cover) { + a_cover->add_on_state_callback([this, a_cover]() { + if (a_cover->position != this->last_position_) { + this->last_position_ = a_cover->position; + if (a_cover->position == POS) + this->trigger(); + } + }); + } protected: - Cover *cover_; + float last_position_{NAN}; }; -class CoverOpenTrigger : public Trigger<> { +template class CoverTrigger : public Trigger<> { public: - CoverOpenTrigger(Cover *a_cover) { - a_cover->add_on_state_callback([this, a_cover]() { - if (a_cover->is_fully_open()) { - this->trigger(); - } - }); - } -}; - -// Separate CoverOpenedTrigger class for improved naming clarity. -// Both on_open and on_opened are supported for backward compatibility. -class CoverOpenedTrigger : public Trigger<> { - public: - CoverOpenedTrigger(Cover *a_cover) { - a_cover->add_on_state_callback([this, a_cover]() { - if (a_cover->is_fully_open()) { - this->trigger(); - } - }); - } -}; - -class CoverClosedTrigger : public Trigger<> { - public: - CoverClosedTrigger(Cover *a_cover) { - a_cover->add_on_state_callback([this, a_cover]() { - if (a_cover->is_fully_closed()) { - this->trigger(); - } - }); - } -}; - -class CoverOpeningTrigger : public Trigger<> { - public: - CoverOpeningTrigger(Cover *a_cover) { + CoverTrigger(Cover *a_cover) { a_cover->add_on_state_callback([this, a_cover]() { auto current_op = a_cover->current_operation; - if (current_op == COVER_OPERATION_OPENING) { - if (!this->last_operation_.has_value() || this->last_operation_.value() != COVER_OPERATION_OPENING) { + if (current_op == OP) { + if (!this->last_operation_.has_value() || this->last_operation_.value() != OP) { this->trigger(); } } @@ -160,41 +133,4 @@ class CoverOpeningTrigger : public Trigger<> { protected: optional last_operation_{}; }; - -class CoverClosingTrigger : public Trigger<> { - public: - CoverClosingTrigger(Cover *a_cover) { - a_cover->add_on_state_callback([this, a_cover]() { - auto current_op = a_cover->current_operation; - if (current_op == COVER_OPERATION_CLOSING) { - if (!this->last_operation_.has_value() || this->last_operation_.value() != COVER_OPERATION_CLOSING) { - this->trigger(); - } - } - this->last_operation_ = current_op; - }); - } - - protected: - optional last_operation_{}; -}; - -class CoverIdleTrigger : public Trigger<> { - public: - CoverIdleTrigger(Cover *a_cover) { - a_cover->add_on_state_callback([this, a_cover]() { - auto current_op = a_cover->current_operation; - if (current_op == COVER_OPERATION_IDLE) { - if (this->last_operation_.has_value() && this->last_operation_.value() != COVER_OPERATION_IDLE) { - this->trigger(); - } - } - this->last_operation_ = current_op; - }); - } - - protected: - optional last_operation_{}; -}; - } // namespace esphome::cover From 081081f69ac6663cd7fecb58a4cdbafca3fefcca Mon Sep 17 00:00:00 2001 From: clydebarrow <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 23 Jan 2026 16:51:52 +1100 Subject: [PATCH 07/24] Fix constants; reduce logging spam --- esphome/components/cover/__init__.py | 6 +++--- esphome/components/cover/cover.cpp | 17 +++++++---------- esphome/components/cover/cover.h | 4 ++-- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 87029a9fc97..3f46777a84d 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -132,21 +132,21 @@ _COVER_SCHEMA = ( cv.Optional(CONF_ON_OPEN): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - CoverPositionTrigger.template(1.0) + CoverPositionTrigger.template(COVER_OPEN) ), } ), cv.Optional(CONF_ON_OPENED): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - CoverPositionTrigger.template(1.0) + CoverPositionTrigger.template(COVER_OPEN) ), } ), cv.Optional(CONF_ON_CLOSED): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - CoverPositionTrigger.template(0.0) + CoverPositionTrigger.template(COVER_CLOSED) ), } ), diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index 97b8c2213e4..b4ed71c7166 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -10,9 +10,6 @@ namespace esphome::cover { static const char *const TAG = "cover"; -const float COVER_OPEN = 1.0f; -const float COVER_CLOSED = 0.0f; - const LogString *cover_command_to_str(float pos) { if (pos == COVER_OPEN) { return LOG_STR("OPEN"); @@ -153,23 +150,23 @@ void Cover::publish_state(bool save) { this->position = clamp(this->position, 0.0f, 1.0f); this->tilt = clamp(this->tilt, 0.0f, 1.0f); - ESP_LOGD(TAG, "'%s' >>", this->name_.c_str()); + ESP_LOGV(TAG, "'%s' >>", this->name_.c_str()); auto traits = this->get_traits(); if (traits.get_supports_position()) { - ESP_LOGD(TAG, " Position: %.0f%%", this->position * 100.0f); + ESP_LOGV(TAG, " Position: %.0f%%", this->position * 100.0f); } else { if (this->position == COVER_OPEN) { - ESP_LOGD(TAG, " State: OPEN"); + ESP_LOGV(TAG, " State: OPEN"); } else if (this->position == COVER_CLOSED) { - ESP_LOGD(TAG, " State: CLOSED"); + ESP_LOGV(TAG, " State: CLOSED"); } else { - ESP_LOGD(TAG, " State: UNKNOWN"); + ESP_LOGV(TAG, " State: UNKNOWN"); } } if (traits.get_supports_tilt()) { - ESP_LOGD(TAG, " Tilt: %.0f%%", this->tilt * 100.0f); + ESP_LOGV(TAG, " Tilt: %.0f%%", this->tilt * 100.0f); } - ESP_LOGD(TAG, " Current Operation: %s", LOG_STR_ARG(cover_operation_to_str(this->current_operation))); + ESP_LOGV(TAG, " Current Operation: %s", LOG_STR_ARG(cover_operation_to_str(this->current_operation))); this->state_callback_.call(); #if defined(USE_COVER) && defined(USE_CONTROLLER_REGISTRY) diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index e710915a0e9..5258a0d9e45 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -10,8 +10,8 @@ namespace esphome::cover { -const extern float COVER_OPEN; -const extern float COVER_CLOSED; +static constexpr const float COVER_OPEN = 1.0f; +static constexpr const float COVER_CLOSED = 0.0f; #define LOG_COVER(prefix, type, obj) \ if ((obj) != nullptr) { \ From 48b80858a7e139bc6e426b7c1c0c257a16fbe087 Mon Sep 17 00:00:00 2001 From: clydebarrow <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:50:46 +1100 Subject: [PATCH 08/24] undo some templating --- esphome/components/cover/__init__.py | 19 ++++++-------- esphome/components/cover/automation.h | 38 ++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 3f46777a84d..7d1f22d7371 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -87,8 +87,11 @@ ToggleAction = cover_ns.class_("ToggleAction", automation.Action) ControlAction = cover_ns.class_("ControlAction", automation.Action) CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action) CoverIsCondition = cover_ns.class_("CoverIsCondition", Condition) -CoverPositionTrigger = cover_ns.class_( - "CoverPositionTrigger", automation.Trigger.template() +CoverOpenedTrigger = cover_ns.class_( + "CoverOpenedTrigger", automation.Trigger.template() +) +CoverClosedTrigger = cover_ns.class_( + "CoverClosedTrigger", automation.Trigger.template() ) CoverTrigger = cover_ns.class_("CoverTrigger", automation.Trigger.template()) @@ -131,23 +134,17 @@ _COVER_SCHEMA = ( # Deprecated trigger cv.Optional(CONF_ON_OPEN): automation.validate_automation( { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - CoverPositionTrigger.template(COVER_OPEN) - ), + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenedTrigger), } ), cv.Optional(CONF_ON_OPENED): automation.validate_automation( { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - CoverPositionTrigger.template(COVER_OPEN) - ), + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenedTrigger), } ), cv.Optional(CONF_ON_CLOSED): automation.validate_automation( { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - CoverPositionTrigger.template(COVER_CLOSED) - ), + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverClosedTrigger), } ), **{ diff --git a/esphome/components/cover/automation.h b/esphome/components/cover/automation.h index 840bf2fc4f3..76cb8a37b79 100644 --- a/esphome/components/cover/automation.h +++ b/esphome/components/cover/automation.h @@ -90,23 +90,49 @@ template class CoverPublishAction : public Action { Cover *cover_; }; -template class CoverIsCondition : public Condition { +template class CoverIsOpenCondition : public Condition { public: - CoverIsCondition(Cover *cover) : cover_(cover) {} + CoverIsOpenCondition(Cover *cover) : cover_(cover) {} - bool check(const Ts &...x) override { return this->cover_->position == POS; } + bool check(const Ts &...x) override { return this->cover_->position == COVER_OPEN; } protected: Cover *cover_; }; -template class CoverPositionTrigger : public Trigger<> { +template class CoverIsClosedCondition : public Condition { public: - CoverPositionTrigger(Cover *a_cover) { + CoverIsClosedCondition(Cover *cover) : cover_(cover) {} + + bool check(const Ts &...x) override { return this->cover_->position == COVER_CLOSED; } + + protected: + Cover *cover_; +}; + +class CoverOpenedTrigger : public Trigger<> { + public: + CoverOpenedTrigger(Cover *a_cover) { a_cover->add_on_state_callback([this, a_cover]() { if (a_cover->position != this->last_position_) { this->last_position_ = a_cover->position; - if (a_cover->position == POS) + if (a_cover->position == COVER_OPEN) + this->trigger(); + } + }); + } + + protected: + float last_position_{NAN}; +}; + +class CoverClosedTrigger : public Trigger<> { + public: + CoverClosedTrigger(Cover *a_cover) { + a_cover->add_on_state_callback([this, a_cover]() { + if (a_cover->position != this->last_position_) { + this->last_position_ = a_cover->position; + if (a_cover->position == COVER_CLOSED) this->trigger(); } }); From 4c0e0b8d76619d26f1a9f0c38d1bf927460eb4cc Mon Sep 17 00:00:00 2001 From: clydebarrow <2366188+clydebarrow@users.noreply.github.com> Date: Fri, 23 Jan 2026 18:10:01 +1100 Subject: [PATCH 09/24] Add conditions --- esphome/components/cover/__init__.py | 23 +++++++++++++++++++++- tests/components/template/common-base.yaml | 11 +++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 7d1f22d7371..88abfddb4e0 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -86,7 +86,8 @@ StopAction = cover_ns.class_("StopAction", automation.Action) ToggleAction = cover_ns.class_("ToggleAction", automation.Action) ControlAction = cover_ns.class_("ControlAction", automation.Action) CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action) -CoverIsCondition = cover_ns.class_("CoverIsCondition", Condition) +CoverIsOpenCondition = cover_ns.class_("CoverIsOpenCondition", Condition) +CoverIsClosedCondition = cover_ns.class_("CoverIsClosedCondition", Condition) CoverOpenedTrigger = cover_ns.class_( "CoverOpenedTrigger", automation.Trigger.template() ) @@ -305,6 +306,26 @@ async def cover_control_to_code(config, action_id, template_arg, args): return var +@automation.register_condition( + "cover.is_open", + CoverIsOpenCondition, + cv.maybe_simple_value({cv.Required(CONF_ID): cv.use_id(Cover)}, key=CONF_ID), +) +async def cover_is_open_to_code(config, condition_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(condition_id, template_arg, paren) + + +@automation.register_condition( + "cover.is_closed", + CoverIsClosedCondition, + cv.maybe_simple_value({cv.Required(CONF_ID): cv.use_id(Cover)}, key=CONF_ID), +) +async def cover_is_closed_to_code(config, condition_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(condition_id, template_arg, paren) + + @coroutine_with_priority(CoroPriority.CORE) async def to_code(config): cg.add_global(cover_ns.using) diff --git a/tests/components/template/common-base.yaml b/tests/components/template/common-base.yaml index 93672243fcf..7fe63f46228 100644 --- a/tests/components/template/common-base.yaml +++ b/tests/components/template/common-base.yaml @@ -272,6 +272,17 @@ cover: - logger.log: "Cover started closing" on_idle: - logger.log: "Cover stopped moving" + - logger.log: "Cover stopped moving" + - if: + condition: + cover.is_open: template_cover_with_triggers + then: + logger.log: Cover is open + - if: + condition: + cover.is_closed: template_cover_with_triggers + then: + logger.log: Cover is closed number: - platform: template From be1542cc8ac6a9d5cb8fbf7dcc8fca8fdc493b6d Mon Sep 17 00:00:00 2001 From: clydebarrow <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:04:32 +1100 Subject: [PATCH 10/24] Revert change of logging level --- esphome/components/cover/cover.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index ca01c7bf924..37cb908d9f3 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -150,23 +150,23 @@ void Cover::publish_state(bool save) { this->position = clamp(this->position, 0.0f, 1.0f); this->tilt = clamp(this->tilt, 0.0f, 1.0f); - ESP_LOGV(TAG, "'%s' >>", this->name_.c_str()); + ESP_LOGD(TAG, "'%s' >>", this->name_.c_str()); auto traits = this->get_traits(); if (traits.get_supports_position()) { - ESP_LOGV(TAG, " Position: %.0f%%", this->position * 100.0f); + ESP_LOGD(TAG, " Position: %.0f%%", this->position * 100.0f); } else { if (this->position == COVER_OPEN) { - ESP_LOGV(TAG, " State: OPEN"); + ESP_LOGD(TAG, " State: OPEN"); } else if (this->position == COVER_CLOSED) { - ESP_LOGV(TAG, " State: CLOSED"); + ESP_LOGD(TAG, " State: CLOSED"); } else { - ESP_LOGV(TAG, " State: UNKNOWN"); + ESP_LOGD(TAG, " State: UNKNOWN"); } } if (traits.get_supports_tilt()) { - ESP_LOGV(TAG, " Tilt: %.0f%%", this->tilt * 100.0f); + ESP_LOGD(TAG, " Tilt: %.0f%%", this->tilt * 100.0f); } - ESP_LOGV(TAG, " Current Operation: %s", LOG_STR_ARG(cover_operation_to_str(this->current_operation))); + ESP_LOGD(TAG, " Current Operation: %s", LOG_STR_ARG(cover_operation_to_str(this->current_operation))); this->state_callback_.call(); #if defined(USE_COVER) && defined(USE_CONTROLLER_REGISTRY) From 95f39149d76023fee4317fdc416efa6994ef214f Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 3 Feb 2026 17:28:59 -0500 Subject: [PATCH 11/24] [rtttl] Fix dotted note parsing order to match RTTTL spec (#13722) Co-authored-by: Claude Opus 4.5 --- esphome/components/rtttl/rtttl.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/esphome/components/rtttl/rtttl.cpp b/esphome/components/rtttl/rtttl.cpp index 65fcc207d48..c179282c50d 100644 --- a/esphome/components/rtttl/rtttl.cpp +++ b/esphome/components/rtttl/rtttl.cpp @@ -290,25 +290,26 @@ void Rtttl::loop() { this->position_++; } - // now, get optional '.' dotted note - if (this->rtttl_[this->position_] == '.') { - this->note_duration_ += this->note_duration_ / 2; - this->position_++; - } - // now, get scale uint8_t scale = get_integer_(); - if (scale == 0) + if (scale == 0) { scale = this->default_octave_; + } if (scale < 4 || scale > 7) { ESP_LOGE(TAG, "Octave must be between 4 and 7 (it is %d)", scale); this->finish_(); return; } - bool need_note_gap = false; + + // now, get optional '.' dotted note + if (this->rtttl_[this->position_] == '.') { + this->note_duration_ += this->note_duration_ / 2; + this->position_++; + } // Now play the note + bool need_note_gap = false; if (note) { auto note_index = (scale - 4) * 12 + note; if (note_index < 0 || note_index >= (int) sizeof(NOTES)) { From 2541ec15656e79d44501605be72c18edac66e78f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 09:42:13 +0100 Subject: [PATCH 12/24] [wifi] Fix wifi.connected condition returning false in connect state listener automations (#13733) --- esphome/components/wifi/wifi_component.cpp | 21 +++++++++++++++++++ esphome/components/wifi/wifi_component.h | 15 +++++++++++++ .../wifi/wifi_component_esp8266.cpp | 10 ++++++--- .../wifi/wifi_component_esp_idf.cpp | 9 +++++--- .../wifi/wifi_component_libretiny.cpp | 11 ++++++---- .../components/wifi/wifi_component_pico_w.cpp | 12 ++++++----- 6 files changed, 63 insertions(+), 15 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index c4bfdf3c42a..e9b78c92253 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -1464,6 +1464,12 @@ void WiFiComponent::check_connecting_finished(uint32_t now) { this->release_scan_results_(); +#ifdef USE_WIFI_CONNECT_STATE_LISTENERS + // Notify listeners now that state machine has reached STA_CONNECTED + // This ensures wifi.connected condition returns true in listener automations + this->notify_connect_state_listeners_(); +#endif + return; } @@ -2183,6 +2189,21 @@ void WiFiComponent::release_scan_results_() { } } +#ifdef USE_WIFI_CONNECT_STATE_LISTENERS +void WiFiComponent::notify_connect_state_listeners_() { + if (!this->pending_.connect_state) + return; + this->pending_.connect_state = false; + // Get current SSID and BSSID from the WiFi driver + char ssid_buf[SSID_BUFFER_SIZE]; + const char *ssid = this->wifi_ssid_to(ssid_buf); + bssid_t bssid = this->wifi_bssid(); + for (auto *listener : this->connect_state_listeners_) { + listener->on_wifi_connect_state(StringRef(ssid, strlen(ssid)), bssid); + } +} +#endif // USE_WIFI_CONNECT_STATE_LISTENERS + void WiFiComponent::check_roaming_(uint32_t now) { // Guard: not for hidden networks (may not appear in scan) const WiFiAP *selected = this->get_selected_sta_(); diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 4bdc253f666..98f339809a3 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -632,6 +632,11 @@ class WiFiComponent : public Component { /// Free scan results memory unless a component needs them void release_scan_results_(); +#ifdef USE_WIFI_CONNECT_STATE_LISTENERS + /// Notify connect state listeners (called after state machine reaches STA_CONNECTED) + void notify_connect_state_listeners_(); +#endif + #ifdef USE_ESP8266 static void wifi_event_callback(System_Event_t *event); void wifi_scan_done_callback_(void *arg, STATUS status); @@ -739,6 +744,16 @@ class WiFiComponent : public Component { SemaphoreHandle_t high_performance_semaphore_{nullptr}; #endif +#ifdef USE_WIFI_CONNECT_STATE_LISTENERS + // Pending listener notifications deferred until state machine reaches appropriate state. + // Listeners are notified after state transitions complete so conditions like + // wifi.connected return correct values in automations. + // Uses bitfields to minimize memory; more flags may be added as needed. + struct { + bool connect_state : 1; // Notify connect state listeners after STA_CONNECTED + } pending_{}; +#endif + #ifdef USE_WIFI_CONNECT_TRIGGER Trigger<> connect_trigger_; #endif diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index c714afaad30..c6bd40037d1 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -500,6 +500,10 @@ const LogString *get_disconnect_reason_str(uint8_t reason) { } } +// TODO: This callback runs in ESP8266 system context with limited stack (~2KB). +// All listener notifications should be deferred to wifi_loop_() via pending_ flags +// to avoid stack overflow. Currently only connect_state is deferred; disconnect, +// IP, and scan listeners still run in this context and should be migrated. void WiFiComponent::wifi_event_callback(System_Event_t *event) { switch (event->event) { case EVENT_STAMODE_CONNECTED: { @@ -512,9 +516,9 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { #endif s_sta_connected = true; #ifdef USE_WIFI_CONNECT_STATE_LISTENERS - for (auto *listener : global_wifi_component->connect_state_listeners_) { - listener->on_wifi_connect_state(StringRef(it.ssid, it.ssid_len), it.bssid); - } + // Defer listener notification until state machine reaches STA_CONNECTED + // This ensures wifi.connected condition returns true in listener automations + global_wifi_component->pending_.connect_state = true; #endif // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here #if defined(USE_WIFI_IP_STATE_LISTENERS) && defined(USE_WIFI_MANUAL_IP) diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index a32232a7584..22bf4be4833 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -710,6 +710,9 @@ void WiFiComponent::wifi_loop_() { delete data; // NOLINT(cppcoreguidelines-owning-memory) } } +// Events are processed from queue in main loop context, but listener notifications +// must be deferred until after the state machine transitions (in check_connecting_finished) +// so that conditions like wifi.connected return correct values in automations. void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { esp_err_t err; if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_START) { @@ -743,9 +746,9 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { #endif s_sta_connected = true; #ifdef USE_WIFI_CONNECT_STATE_LISTENERS - for (auto *listener : this->connect_state_listeners_) { - listener->on_wifi_connect_state(StringRef(it.ssid, it.ssid_len), it.bssid); - } + // Defer listener notification until state machine reaches STA_CONNECTED + // This ensures wifi.connected condition returns true in listener automations + this->pending_.connect_state = true; #endif // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here #if defined(USE_WIFI_IP_STATE_LISTENERS) && defined(USE_WIFI_MANUAL_IP) diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index af2b82c3c6b..285a520ef53 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -423,7 +423,10 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ } } -// Process a single event from the queue - runs in main loop context +// Process a single event from the queue - runs in main loop context. +// Listener notifications must be deferred until after the state machine transitions +// (in check_connecting_finished) so that conditions like wifi.connected return +// correct values in automations. void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) { switch (event->event_id) { case ESPHOME_EVENT_ID_WIFI_READY: { @@ -456,9 +459,9 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) { // This matches ESP32 IDF behavior where s_sta_connected is set but // wifi_sta_connect_status_() also checks got_ipv4_address_ #ifdef USE_WIFI_CONNECT_STATE_LISTENERS - for (auto *listener : this->connect_state_listeners_) { - listener->on_wifi_connect_state(StringRef(it.ssid, it.ssid_len), it.bssid); - } + // Defer listener notification until state machine reaches STA_CONNECTED + // This ensures wifi.connected condition returns true in listener automations + this->pending_.connect_state = true; #endif // For static IP configurations, GOT_IP event may not fire, so set connected state here #ifdef USE_WIFI_MANUAL_IP diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index 84c10d5d432..1ce36c2d93f 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -252,6 +252,10 @@ network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return network::IPAddress(dns_ip); } +// Pico W uses polling for connection state detection. +// Connect state listener notifications are deferred until after the state machine +// transitions (in check_connecting_finished) so that conditions like wifi.connected +// return correct values in automations. void WiFiComponent::wifi_loop_() { // Handle scan completion if (this->state_ == WIFI_COMPONENT_STATE_STA_SCANNING && !cyw43_wifi_scan_active(&cyw43_state)) { @@ -278,11 +282,9 @@ void WiFiComponent::wifi_loop_() { s_sta_was_connected = true; ESP_LOGV(TAG, "Connected"); #ifdef USE_WIFI_CONNECT_STATE_LISTENERS - String ssid = WiFi.SSID(); - bssid_t bssid = this->wifi_bssid(); - for (auto *listener : this->connect_state_listeners_) { - listener->on_wifi_connect_state(StringRef(ssid.c_str(), ssid.length()), bssid); - } + // Defer listener notification until state machine reaches STA_CONNECTED + // This ensures wifi.connected condition returns true in listener automations + this->pending_.connect_state = true; #endif // For static IP configurations, notify IP listeners immediately as the IP is already configured #if defined(USE_WIFI_IP_STATE_LISTENERS) && defined(USE_WIFI_MANUAL_IP) From 21815ddfa0e74639782ade0fcb5b285039dc8ecb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 10:15:05 +0100 Subject: [PATCH 13/24] avoid losing type safety --- esphome/components/cover/__init__.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 88abfddb4e0..4835c762df9 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -102,15 +102,11 @@ CONF_ON_OPENED = "on_opened" CONF_ON_OPENING = "on_opening" CONF_ON_CLOSING = "on_closing" -OPERATIONS = ( - CONF_ON_CLOSING, - CONF_ON_OPENING, - CONF_ON_IDLE, -) - - -def get_operation_from_conf_(conf: str) -> CoverOperation: - return getattr(CoverOperation, "COVER_OPERATION_" + conf.split("_")[1].upper()) +OPERATIONS = { + CONF_ON_CLOSING: CoverOperation.COVER_OPERATION_CLOSING, + CONF_ON_OPENING: CoverOperation.COVER_OPERATION_OPENING, + CONF_ON_IDLE: CoverOperation.COVER_OPERATION_IDLE, +} _COVER_SCHEMA = ( @@ -152,11 +148,11 @@ _COVER_SCHEMA = ( cv.Optional(conf): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - CoverTrigger.template(get_operation_from_conf_(conf)) + CoverTrigger.template(operation) ), } ) - for conf in OPERATIONS + for conf, operation in OPERATIONS.items() }, } ) From 0714c5b7b854ef80075edf8c8d36bff159ec0d73 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 10:16:07 +0100 Subject: [PATCH 14/24] cleanup duplicate code --- esphome/components/cover/__init__.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 4835c762df9..d0f42e224b3 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -197,16 +197,10 @@ async def setup_cover_core_(var, config): for conf in on_opens: trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) - for on_op in OPERATIONS: - if triggers := config.get(on_op): - for conf in triggers: - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for on_state in [CONF_ON_OPENED, CONF_ON_CLOSED]: - if triggers := config.get(on_state): - for conf in triggers: - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + for trigger_conf in (*OPERATIONS, CONF_ON_OPENED, CONF_ON_CLOSED): + for conf in config.get(trigger_conf, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: mqtt_ = cg.new_Pvariable(mqtt_id, var) From f2fdc476b398207dac650aebc3769666334884ed Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 10:17:22 +0100 Subject: [PATCH 15/24] cleanup duplicate code --- esphome/components/cover/__init__.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index d0f42e224b3..ccab6a84090 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -190,14 +190,11 @@ async def setup_cover_core_(var, config): if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: cg.add(var.set_device_class(device_class)) - if on_opens := config.get(CONF_ON_OPEN): + if CONF_ON_OPEN in config: _LOGGER.warning( - "The 'on_open' trigger for covers is deprecated and will be removed in a future release. Please use 'on_opened' instead." + "'on_open' is deprecated, use 'on_opened'. Will be removed in 2026.8.0" ) - for conf in on_opens: - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for trigger_conf in (*OPERATIONS, CONF_ON_OPENED, CONF_ON_CLOSED): + for trigger_conf in (*OPERATIONS, CONF_ON_OPEN, CONF_ON_OPENED, CONF_ON_CLOSED): for conf in config.get(trigger_conf, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) From 2dc2465b134f23793661fd3568fd524f2eaf2199 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 10:20:06 +0100 Subject: [PATCH 16/24] cleanup duplicate code --- esphome/components/cover/__init__.py | 31 ++++++++++++++-------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index ccab6a84090..97728c5cfd4 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -35,9 +35,10 @@ from esphome.const import ( DEVICE_CLASS_SHUTTER, DEVICE_CLASS_WINDOW, ) -from esphome.core import CORE, CoroPriority, coroutine_with_priority +from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity -from esphome.cpp_generator import MockObjClass +from esphome.cpp_generator import MockObj, MockObjClass +from esphome.types import ConfigType, TemplateArgsType IS_PLATFORM_COMPONENT = True @@ -293,24 +294,24 @@ async def cover_control_to_code(config, action_id, template_arg, args): return var -@automation.register_condition( - "cover.is_open", - CoverIsOpenCondition, - cv.maybe_simple_value({cv.Required(CONF_ID): cv.use_id(Cover)}, key=CONF_ID), +COVER_CONDITION_SCHEMA = cv.maybe_simple_value( + {cv.Required(CONF_ID): cv.use_id(Cover)}, key=CONF_ID ) -async def cover_is_open_to_code(config, condition_id, template_arg, args): + + +async def cover_condition_to_code( + config: ConfigType, condition_id: ID, template_arg: MockObj, args: TemplateArgsType +) -> MockObj: paren = await cg.get_variable(config[CONF_ID]) return cg.new_Pvariable(condition_id, template_arg, paren) -@automation.register_condition( - "cover.is_closed", - CoverIsClosedCondition, - cv.maybe_simple_value({cv.Required(CONF_ID): cv.use_id(Cover)}, key=CONF_ID), -) -async def cover_is_closed_to_code(config, condition_id, template_arg, args): - paren = await cg.get_variable(config[CONF_ID]) - return cg.new_Pvariable(condition_id, template_arg, paren) +automation.register_condition( + "cover.is_open", CoverIsOpenCondition, COVER_CONDITION_SCHEMA +)(cover_condition_to_code) +automation.register_condition( + "cover.is_closed", CoverIsClosedCondition, COVER_CONDITION_SCHEMA +)(cover_condition_to_code) @coroutine_with_priority(CoroPriority.CORE) From c7dfdfc6d740c5d1d2741f8f10f8de5fb849008f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 10:22:02 +0100 Subject: [PATCH 17/24] cleanup duplicate code --- esphome/components/cover/automation.h | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/esphome/components/cover/automation.h b/esphome/components/cover/automation.h index 76cb8a37b79..8dee297634e 100644 --- a/esphome/components/cover/automation.h +++ b/esphome/components/cover/automation.h @@ -110,13 +110,13 @@ template class CoverIsClosedCondition : public Condition Cover *cover_; }; -class CoverOpenedTrigger : public Trigger<> { +template class CoverPositionTrigger : public Trigger<> { public: - CoverOpenedTrigger(Cover *a_cover) { + CoverPositionTrigger(Cover *a_cover) { a_cover->add_on_state_callback([this, a_cover]() { if (a_cover->position != this->last_position_) { this->last_position_ = a_cover->position; - if (a_cover->position == COVER_OPEN) + if (a_cover->position == POS) this->trigger(); } }); @@ -126,21 +126,8 @@ class CoverOpenedTrigger : public Trigger<> { float last_position_{NAN}; }; -class CoverClosedTrigger : public Trigger<> { - public: - CoverClosedTrigger(Cover *a_cover) { - a_cover->add_on_state_callback([this, a_cover]() { - if (a_cover->position != this->last_position_) { - this->last_position_ = a_cover->position; - if (a_cover->position == COVER_CLOSED) - this->trigger(); - } - }); - } - - protected: - float last_position_{NAN}; -}; +using CoverOpenedTrigger = CoverPositionTrigger; +using CoverClosedTrigger = CoverPositionTrigger; template class CoverTrigger : public Trigger<> { public: From dc11bb8709d80e90e6d80563f186bc37ca90cb91 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 10:29:59 +0100 Subject: [PATCH 18/24] cleanup duplicate code --- esphome/components/cover/automation.h | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/esphome/components/cover/automation.h b/esphome/components/cover/automation.h index 8dee297634e..ad8a4a70222 100644 --- a/esphome/components/cover/automation.h +++ b/esphome/components/cover/automation.h @@ -90,25 +90,18 @@ template class CoverPublishAction : public Action { Cover *cover_; }; -template class CoverIsOpenCondition : public Condition { +template class CoverPositionCondition : public Condition { public: - CoverIsOpenCondition(Cover *cover) : cover_(cover) {} + CoverPositionCondition(Cover *cover) : cover_(cover) {} - bool check(const Ts &...x) override { return this->cover_->position == COVER_OPEN; } + bool check(const Ts &...x) override { return this->cover_->position == POS; } protected: Cover *cover_; }; -template class CoverIsClosedCondition : public Condition { - public: - CoverIsClosedCondition(Cover *cover) : cover_(cover) {} - - bool check(const Ts &...x) override { return this->cover_->position == COVER_CLOSED; } - - protected: - Cover *cover_; -}; +template using CoverIsOpenCondition = CoverPositionCondition; +template using CoverIsClosedCondition = CoverPositionCondition; template class CoverPositionTrigger : public Trigger<> { public: From b633444e11c4f322ca3227a053a2aa4b798ce60d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 10:32:09 +0100 Subject: [PATCH 19/24] cleanup duplicate code --- esphome/components/cover/__init__.py | 32 +++++++++++++--------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 97728c5cfd4..0bd0829fc0d 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -109,6 +109,12 @@ OPERATIONS = { CONF_ON_IDLE: CoverOperation.COVER_OPERATION_IDLE, } +POSITION_TRIGGERS = { + CONF_ON_OPEN: CoverOpenedTrigger, # Deprecated, use on_opened + CONF_ON_OPENED: CoverOpenedTrigger, + CONF_ON_CLOSED: CoverClosedTrigger, +} + _COVER_SCHEMA = ( cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) @@ -129,22 +135,14 @@ _COVER_SCHEMA = ( cv.Optional(CONF_TILT_STATE_TOPIC): cv.All( cv.requires_component("mqtt"), cv.subscribe_topic ), - # Deprecated trigger - cv.Optional(CONF_ON_OPEN): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenedTrigger), - } - ), - cv.Optional(CONF_ON_OPENED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenedTrigger), - } - ), - cv.Optional(CONF_ON_CLOSED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverClosedTrigger), - } - ), + **{ + cv.Optional(conf): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(trigger_class), + } + ) + for conf, trigger_class in POSITION_TRIGGERS.items() + }, **{ cv.Optional(conf): automation.validate_automation( { @@ -195,7 +193,7 @@ async def setup_cover_core_(var, config): _LOGGER.warning( "'on_open' is deprecated, use 'on_opened'. Will be removed in 2026.8.0" ) - for trigger_conf in (*OPERATIONS, CONF_ON_OPEN, CONF_ON_OPENED, CONF_ON_CLOSED): + for trigger_conf in (*OPERATIONS, *POSITION_TRIGGERS): for conf in config.get(trigger_conf, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) From 4704de51be50595cc876b3e496b652e8222db3aa Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 10:33:12 +0100 Subject: [PATCH 20/24] cleanup duplicate code --- esphome/components/cover/__init__.py | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 0bd0829fc0d..41774f3d71a 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -103,16 +103,13 @@ CONF_ON_OPENED = "on_opened" CONF_ON_OPENING = "on_opening" CONF_ON_CLOSING = "on_closing" -OPERATIONS = { - CONF_ON_CLOSING: CoverOperation.COVER_OPERATION_CLOSING, - CONF_ON_OPENING: CoverOperation.COVER_OPERATION_OPENING, - CONF_ON_IDLE: CoverOperation.COVER_OPERATION_IDLE, -} - -POSITION_TRIGGERS = { +TRIGGERS = { CONF_ON_OPEN: CoverOpenedTrigger, # Deprecated, use on_opened CONF_ON_OPENED: CoverOpenedTrigger, CONF_ON_CLOSED: CoverClosedTrigger, + CONF_ON_CLOSING: CoverTrigger.template(CoverOperation.COVER_OPERATION_CLOSING), + CONF_ON_OPENING: CoverTrigger.template(CoverOperation.COVER_OPERATION_OPENING), + CONF_ON_IDLE: CoverTrigger.template(CoverOperation.COVER_OPERATION_IDLE), } @@ -141,17 +138,7 @@ _COVER_SCHEMA = ( cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(trigger_class), } ) - for conf, trigger_class in POSITION_TRIGGERS.items() - }, - **{ - cv.Optional(conf): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - CoverTrigger.template(operation) - ), - } - ) - for conf, operation in OPERATIONS.items() + for conf, trigger_class in TRIGGERS.items() }, } ) @@ -193,7 +180,7 @@ async def setup_cover_core_(var, config): _LOGGER.warning( "'on_open' is deprecated, use 'on_opened'. Will be removed in 2026.8.0" ) - for trigger_conf in (*OPERATIONS, *POSITION_TRIGGERS): + for trigger_conf in TRIGGERS: for conf in config.get(trigger_conf, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) From ef3fcf66351dd3f7b62d7756bc7ee693e4fff83c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 10:37:06 +0100 Subject: [PATCH 21/24] cleanup --- esphome/components/cover/cover.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index 3da6109e37c..0af48f75de8 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -10,8 +10,8 @@ namespace esphome::cover { -static constexpr const float COVER_OPEN = 1.0f; -static constexpr const float COVER_CLOSED = 0.0f; +static constexpr float COVER_OPEN = 1.0f; +static constexpr float COVER_CLOSED = 0.0f; #define LOG_COVER(prefix, type, obj) \ if ((obj) != nullptr) { \ From e26996be93401e28d495274b46383997e58c12b8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 10:38:04 +0100 Subject: [PATCH 22/24] cleanup --- esphome/components/cover/automation.h | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/cover/automation.h b/esphome/components/cover/automation.h index ad8a4a70222..19ab40034cc 100644 --- a/esphome/components/cover/automation.h +++ b/esphome/components/cover/automation.h @@ -5,6 +5,7 @@ #include "cover.h" namespace esphome::cover { + template class OpenAction : public Action { public: explicit OpenAction(Cover *cover) : cover_(cover) {} From 4a45fe9849249aba5e619dd3cd2a745a6147ed82 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 10:38:54 +0100 Subject: [PATCH 23/24] cleanup --- esphome/components/cover/automation.h | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/components/cover/automation.h b/esphome/components/cover/automation.h index 19ab40034cc..39760424418 100644 --- a/esphome/components/cover/automation.h +++ b/esphome/components/cover/automation.h @@ -72,7 +72,6 @@ template class ControlAction : public Action { template class CoverPublishAction : public Action { public: CoverPublishAction(Cover *cover) : cover_(cover) {} - TEMPLATABLE_VALUE(float, position) TEMPLATABLE_VALUE(float, tilt) TEMPLATABLE_VALUE(CoverOperation, current_operation) From 2cc89c88886ff24a8ee1607b201a54b18053a115 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 4 Feb 2026 10:47:27 +0100 Subject: [PATCH 24/24] does not match float --- esphome/components/cover/automation.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/esphome/components/cover/automation.h b/esphome/components/cover/automation.h index 39760424418..12ec46725d6 100644 --- a/esphome/components/cover/automation.h +++ b/esphome/components/cover/automation.h @@ -90,26 +90,26 @@ template class CoverPublishAction : public Action { Cover *cover_; }; -template class CoverPositionCondition : public Condition { +template class CoverPositionCondition : public Condition { public: CoverPositionCondition(Cover *cover) : cover_(cover) {} - bool check(const Ts &...x) override { return this->cover_->position == POS; } + bool check(const Ts &...x) override { return this->cover_->position == (OPEN ? COVER_OPEN : COVER_CLOSED); } protected: Cover *cover_; }; -template using CoverIsOpenCondition = CoverPositionCondition; -template using CoverIsClosedCondition = CoverPositionCondition; +template using CoverIsOpenCondition = CoverPositionCondition; +template using CoverIsClosedCondition = CoverPositionCondition; -template class CoverPositionTrigger : public Trigger<> { +template class CoverPositionTrigger : public Trigger<> { public: CoverPositionTrigger(Cover *a_cover) { a_cover->add_on_state_callback([this, a_cover]() { if (a_cover->position != this->last_position_) { this->last_position_ = a_cover->position; - if (a_cover->position == POS) + if (a_cover->position == (OPEN ? COVER_OPEN : COVER_CLOSED)) this->trigger(); } }); @@ -119,8 +119,8 @@ template class CoverPositionTrigger : public Trigger<> { float last_position_{NAN}; }; -using CoverOpenedTrigger = CoverPositionTrigger; -using CoverClosedTrigger = CoverPositionTrigger; +using CoverOpenedTrigger = CoverPositionTrigger; +using CoverClosedTrigger = CoverPositionTrigger; template class CoverTrigger : public Trigger<> { public: