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:
Daniel Müller 2021-09-27 22:31:15 +02:00 committed by GitHub
parent e30f17f64f
commit 2eb5f89d82
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 70 additions and 1 deletions

View file

@ -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),

View file

@ -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) {}

View file

@ -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) {

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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: