diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 36245f787f..87029a9fc9 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 ea3302ee51..840bf2fc4f 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