mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Add cover toggle support (#1809)
* Add cover toggle support Step through open/stop/close/stop sequence with every toggle * Move the cover toggle logic to perform() * Add clang-tidy CI suggestion * Implement cover toggle action as cover trait * Handle toggle correctly if cover fully closed on POR * Fix CI finding * Add deprecated warning * Don't add already deprecated interface Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl> * Don't add already deprecated interface Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl> * Don't add already deprecated interface Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl> Co-authored-by: Mueller, Daniel <daniel.mueller@karlstorz.com> Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
This commit is contained in:
		| @@ -59,6 +59,7 @@ validate_cover_operation = cv.enum(COVER_OPERATIONS, upper=True) | |||||||
| OpenAction = cover_ns.class_("OpenAction", automation.Action) | OpenAction = cover_ns.class_("OpenAction", automation.Action) | ||||||
| CloseAction = cover_ns.class_("CloseAction", automation.Action) | CloseAction = cover_ns.class_("CloseAction", automation.Action) | ||||||
| StopAction = cover_ns.class_("StopAction", automation.Action) | StopAction = cover_ns.class_("StopAction", automation.Action) | ||||||
|  | ToggleAction = cover_ns.class_("ToggleAction", automation.Action) | ||||||
| ControlAction = cover_ns.class_("ControlAction", automation.Action) | ControlAction = cover_ns.class_("ControlAction", automation.Action) | ||||||
| CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action) | CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action) | ||||||
| CoverIsOpenCondition = cover_ns.class_("CoverIsOpenCondition", Condition) | CoverIsOpenCondition = cover_ns.class_("CoverIsOpenCondition", Condition) | ||||||
| @@ -119,6 +120,12 @@ async def cover_stop_to_code(config, action_id, template_arg, args): | |||||||
|     return cg.new_Pvariable(action_id, template_arg, paren) |     return cg.new_Pvariable(action_id, template_arg, paren) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action("cover.toggle", ToggleAction, COVER_ACTION_SCHEMA) | ||||||
|  | def cover_toggle_to_code(config, action_id, template_arg, args): | ||||||
|  |     paren = yield cg.get_variable(config[CONF_ID]) | ||||||
|  |     yield cg.new_Pvariable(action_id, template_arg, paren) | ||||||
|  |  | ||||||
|  |  | ||||||
| COVER_CONTROL_ACTION_SCHEMA = cv.Schema( | COVER_CONTROL_ACTION_SCHEMA = cv.Schema( | ||||||
|     { |     { | ||||||
|         cv.Required(CONF_ID): cv.use_id(Cover), |         cv.Required(CONF_ID): cv.use_id(Cover), | ||||||
|   | |||||||
| @@ -37,6 +37,16 @@ template<typename... Ts> class StopAction : public Action<Ts...> { | |||||||
|   Cover *cover_; |   Cover *cover_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class ToggleAction : public Action<Ts...> { | ||||||
|  |  public: | ||||||
|  |   explicit ToggleAction(Cover *cover) : cover_(cover) {} | ||||||
|  |  | ||||||
|  |   void play(Ts... x) override { this->cover_->make_call().set_command_toggle().perform(); } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   Cover *cover_; | ||||||
|  | }; | ||||||
|  |  | ||||||
| template<typename... Ts> class ControlAction : public Action<Ts...> { | template<typename... Ts> class ControlAction : public Action<Ts...> { | ||||||
|  public: |  public: | ||||||
|   explicit ControlAction(Cover *cover) : cover_(cover) {} |   explicit ControlAction(Cover *cover) : cover_(cover) {} | ||||||
|   | |||||||
| @@ -43,6 +43,8 @@ CoverCall &CoverCall::set_command(const char *command) { | |||||||
|     this->set_command_close(); |     this->set_command_close(); | ||||||
|   } else if (strcasecmp(command, "STOP") == 0) { |   } else if (strcasecmp(command, "STOP") == 0) { | ||||||
|     this->set_command_stop(); |     this->set_command_stop(); | ||||||
|  |   } else if (strcasecmp(command, "TOGGLE") == 0) { | ||||||
|  |     this->set_command_toggle(); | ||||||
|   } else { |   } else { | ||||||
|     ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command); |     ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command); | ||||||
|   } |   } | ||||||
| @@ -60,6 +62,10 @@ CoverCall &CoverCall::set_command_stop() { | |||||||
|   this->stop_ = true; |   this->stop_ = true; | ||||||
|   return *this; |   return *this; | ||||||
| } | } | ||||||
|  | CoverCall &CoverCall::set_command_toggle() { | ||||||
|  |   this->toggle_ = true; | ||||||
|  |   return *this; | ||||||
|  | } | ||||||
| CoverCall &CoverCall::set_position(float position) { | CoverCall &CoverCall::set_position(float position) { | ||||||
|   this->position_ = position; |   this->position_ = position; | ||||||
|   return *this; |   return *this; | ||||||
| @@ -85,10 +91,14 @@ void CoverCall::perform() { | |||||||
|   if (this->tilt_.has_value()) { |   if (this->tilt_.has_value()) { | ||||||
|     ESP_LOGD(TAG, "  Tilt: %.0f%%", *this->tilt_ * 100.0f); |     ESP_LOGD(TAG, "  Tilt: %.0f%%", *this->tilt_ * 100.0f); | ||||||
|   } |   } | ||||||
|  |   if (this->toggle_.has_value()) { | ||||||
|  |     ESP_LOGD(TAG, "  Command: TOGGLE"); | ||||||
|  |   } | ||||||
|   this->parent_->control(*this); |   this->parent_->control(*this); | ||||||
| } | } | ||||||
| const optional<float> &CoverCall::get_position() const { return this->position_; } | const optional<float> &CoverCall::get_position() const { return this->position_; } | ||||||
| const optional<float> &CoverCall::get_tilt() const { return this->tilt_; } | const optional<float> &CoverCall::get_tilt() const { return this->tilt_; } | ||||||
|  | const optional<bool> &CoverCall::get_toggle() const { return this->toggle_; } | ||||||
| void CoverCall::validate_() { | void CoverCall::validate_() { | ||||||
|   auto traits = this->parent_->get_traits(); |   auto traits = this->parent_->get_traits(); | ||||||
|   if (this->position_.has_value()) { |   if (this->position_.has_value()) { | ||||||
| @@ -111,6 +121,12 @@ void CoverCall::validate_() { | |||||||
|       this->tilt_ = clamp(tilt, 0.0f, 1.0f); |       this->tilt_ = clamp(tilt, 0.0f, 1.0f); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |   if (this->toggle_.has_value()) { | ||||||
|  |     if (!traits.get_supports_toggle()) { | ||||||
|  |       ESP_LOGW(TAG, "'%s' - This cover device does not support toggle!", this->parent_->get_name().c_str()); | ||||||
|  |       this->toggle_.reset(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|   if (this->stop_) { |   if (this->stop_) { | ||||||
|     if (this->position_.has_value()) { |     if (this->position_.has_value()) { | ||||||
|       ESP_LOGW(TAG, "Cannot set position when stopping a cover!"); |       ESP_LOGW(TAG, "Cannot set position when stopping a cover!"); | ||||||
| @@ -120,6 +136,10 @@ void CoverCall::validate_() { | |||||||
|       ESP_LOGW(TAG, "Cannot set tilt when stopping a cover!"); |       ESP_LOGW(TAG, "Cannot set tilt when stopping a cover!"); | ||||||
|       this->tilt_.reset(); |       this->tilt_.reset(); | ||||||
|     } |     } | ||||||
|  |     if (this->toggle_.has_value()) { | ||||||
|  |       ESP_LOGW(TAG, "Cannot set toggle when stopping a cover!"); | ||||||
|  |       this->toggle_.reset(); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
| CoverCall &CoverCall::set_stop(bool stop) { | CoverCall &CoverCall::set_stop(bool stop) { | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ class CoverCall { | |||||||
|  public: |  public: | ||||||
|   CoverCall(Cover *parent); |   CoverCall(Cover *parent); | ||||||
|  |  | ||||||
|   /// Set the command as a string, "STOP", "OPEN", "CLOSE". |   /// Set the command as a string, "STOP", "OPEN", "CLOSE", "TOGGLE". | ||||||
|   CoverCall &set_command(const char *command); |   CoverCall &set_command(const char *command); | ||||||
|   /// Set the command to open the cover. |   /// Set the command to open the cover. | ||||||
|   CoverCall &set_command_open(); |   CoverCall &set_command_open(); | ||||||
| @@ -37,6 +37,8 @@ class CoverCall { | |||||||
|   CoverCall &set_command_close(); |   CoverCall &set_command_close(); | ||||||
|   /// Set the command to stop the cover. |   /// Set the command to stop the cover. | ||||||
|   CoverCall &set_command_stop(); |   CoverCall &set_command_stop(); | ||||||
|  |   /// Set the command to toggle the cover. | ||||||
|  |   CoverCall &set_command_toggle(); | ||||||
|   /// Set the call to a certain target position. |   /// Set the call to a certain target position. | ||||||
|   CoverCall &set_position(float position); |   CoverCall &set_position(float position); | ||||||
|   /// Set the call to a certain target tilt. |   /// Set the call to a certain target tilt. | ||||||
| @@ -50,6 +52,7 @@ class CoverCall { | |||||||
|   const optional<float> &get_position() const; |   const optional<float> &get_position() const; | ||||||
|   bool get_stop() const; |   bool get_stop() const; | ||||||
|   const optional<float> &get_tilt() const; |   const optional<float> &get_tilt() const; | ||||||
|  |   const optional<bool> &get_toggle() const; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void validate_(); |   void validate_(); | ||||||
| @@ -58,6 +61,7 @@ class CoverCall { | |||||||
|   bool stop_{false}; |   bool stop_{false}; | ||||||
|   optional<float> position_{}; |   optional<float> position_{}; | ||||||
|   optional<float> tilt_{}; |   optional<float> tilt_{}; | ||||||
|  |   optional<bool> toggle_{}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /// Struct used to store the restored state of a cover | /// Struct used to store the restored state of a cover | ||||||
|   | |||||||
| @@ -13,11 +13,14 @@ class CoverTraits { | |||||||
|   void set_supports_position(bool supports_position) { this->supports_position_ = supports_position; } |   void set_supports_position(bool supports_position) { this->supports_position_ = supports_position; } | ||||||
|   bool get_supports_tilt() const { return this->supports_tilt_; } |   bool get_supports_tilt() const { return this->supports_tilt_; } | ||||||
|   void set_supports_tilt(bool supports_tilt) { this->supports_tilt_ = supports_tilt; } |   void set_supports_tilt(bool supports_tilt) { this->supports_tilt_ = supports_tilt; } | ||||||
|  |   bool get_supports_toggle() const { return this->supports_toggle_; } | ||||||
|  |   void set_supports_toggle(bool supports_toggle) { this->supports_toggle_ = supports_toggle; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   bool is_assumed_state_{false}; |   bool is_assumed_state_{false}; | ||||||
|   bool supports_position_{false}; |   bool supports_position_{false}; | ||||||
|   bool supports_tilt_{false}; |   bool supports_tilt_{false}; | ||||||
|  |   bool supports_toggle_{false}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace cover | }  // namespace cover | ||||||
|   | |||||||
| @@ -52,6 +52,7 @@ float TimeBasedCover::get_setup_priority() const { return setup_priority::DATA; | |||||||
| CoverTraits TimeBasedCover::get_traits() { | CoverTraits TimeBasedCover::get_traits() { | ||||||
|   auto traits = CoverTraits(); |   auto traits = CoverTraits(); | ||||||
|   traits.set_supports_position(true); |   traits.set_supports_position(true); | ||||||
|  |   traits.set_supports_toggle(true); | ||||||
|   traits.set_is_assumed_state(this->assumed_state_); |   traits.set_is_assumed_state(this->assumed_state_); | ||||||
|   return traits; |   return traits; | ||||||
| } | } | ||||||
| @@ -60,6 +61,20 @@ void TimeBasedCover::control(const CoverCall &call) { | |||||||
|     this->start_direction_(COVER_OPERATION_IDLE); |     this->start_direction_(COVER_OPERATION_IDLE); | ||||||
|     this->publish_state(); |     this->publish_state(); | ||||||
|   } |   } | ||||||
|  |   if (call.get_toggle().has_value()) { | ||||||
|  |     if (this->current_operation != COVER_OPERATION_IDLE) { | ||||||
|  |       this->start_direction_(COVER_OPERATION_IDLE); | ||||||
|  |       this->publish_state(); | ||||||
|  |     } else { | ||||||
|  |       if (this->position == COVER_CLOSED || this->last_operation_ == COVER_OPERATION_CLOSING) { | ||||||
|  |         this->target_position_ = COVER_OPEN; | ||||||
|  |         this->start_direction_(COVER_OPERATION_OPENING); | ||||||
|  |       } else { | ||||||
|  |         this->target_position_ = COVER_CLOSED; | ||||||
|  |         this->start_direction_(COVER_OPERATION_CLOSING); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|   if (call.get_position().has_value()) { |   if (call.get_position().has_value()) { | ||||||
|     auto pos = *call.get_position(); |     auto pos = *call.get_position(); | ||||||
|     if (pos == this->position) { |     if (pos == this->position) { | ||||||
| @@ -105,9 +120,11 @@ void TimeBasedCover::start_direction_(CoverOperation dir) { | |||||||
|       trig = this->stop_trigger_; |       trig = this->stop_trigger_; | ||||||
|       break; |       break; | ||||||
|     case COVER_OPERATION_OPENING: |     case COVER_OPERATION_OPENING: | ||||||
|  |       this->last_operation_ = dir; | ||||||
|       trig = this->open_trigger_; |       trig = this->open_trigger_; | ||||||
|       break; |       break; | ||||||
|     case COVER_OPERATION_CLOSING: |     case COVER_OPERATION_CLOSING: | ||||||
|  |       this->last_operation_ = dir; | ||||||
|       trig = this->close_trigger_; |       trig = this->close_trigger_; | ||||||
|       break; |       break; | ||||||
|     default: |     default: | ||||||
|   | |||||||
| @@ -45,6 +45,7 @@ class TimeBasedCover : public cover::Cover, public Component { | |||||||
|   float target_position_{0}; |   float target_position_{0}; | ||||||
|   bool has_built_in_endstop_{false}; |   bool has_built_in_endstop_{false}; | ||||||
|   bool assumed_state_{false}; |   bool assumed_state_{false}; | ||||||
|  |   cover::CoverOperation last_operation_{cover::COVER_OPERATION_OPENING}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace time_based | }  // namespace time_based | ||||||
|   | |||||||
| @@ -729,6 +729,12 @@ binary_sensor: | |||||||
|     id: r0_sensor |     id: r0_sensor | ||||||
|     name: 'R0 Sensor' |     name: 'R0 Sensor' | ||||||
|     component_name: page0.r0 |     component_name: page0.r0 | ||||||
|  |   - platform: template | ||||||
|  |     id: 'cover_toggle' | ||||||
|  |     on_press: | ||||||
|  |       then: | ||||||
|  |         - cover.toggle: time_based_cover | ||||||
|  |  | ||||||
| globals: | globals: | ||||||
|   - id: my_global_string |   - id: my_global_string | ||||||
|     type: std::string |     type: std::string | ||||||
| @@ -1018,6 +1024,7 @@ cover: | |||||||
|     max_duration: 10min |     max_duration: 10min | ||||||
|   - platform: time_based |   - platform: time_based | ||||||
|     name: Time Based Cover |     name: Time Based Cover | ||||||
|  |     id: time_based_cover | ||||||
|     stop_action: |     stop_action: | ||||||
|       - switch.turn_on: gpio_switch1 |       - switch.turn_on: gpio_switch1 | ||||||
|     open_action: |     open_action: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user