diff --git a/esphome/components/time_based_tilt/cover.py b/esphome/components/time_based_tilt/cover.py index d3ca1ef69e..26af083d2c 100644 --- a/esphome/components/time_based_tilt/cover.py +++ b/esphome/components/time_based_tilt/cover.py @@ -1,15 +1,15 @@ -import esphome.codegen as cg -import esphome.config_validation as cv from esphome import automation +import esphome.codegen as cg from esphome.components import cover +import esphome.config_validation as cv from esphome.const import ( + CONF_ASSUMED_STATE, CONF_CLOSE_ACTION, CONF_CLOSE_DURATION, CONF_ID, CONF_OPEN_ACTION, CONF_OPEN_DURATION, CONF_STOP_ACTION, - CONF_ASSUMED_STATE, ) CODEOWNERS = ["@klaudiusz223"] @@ -22,7 +22,10 @@ TimeBasedTiltCover = time_based_tilt_ns.class_( CONF_TILT_OPEN_DURATION = "tilt_open_duration" CONF_TILT_CLOSE_DURATION = "tilt_close_duration" CONF_INTERLOCK_WAIT_TIME = "interlock_wait_time" -CONF_RECALIBRATION_TIME = "recalibration_time" +CONF_RECALIBRATION_OPEN_TIME = "recalibration_open_time" +CONF_RECALIBRATION_CLOSE_TIME = "recalibration_close_time" +CONF_ACTUATOR_ACTIVATION_OPEN_TIME = "actuator_activation_open_time" +CONF_ACTUATOR_ACTIVATION_CLOSE_TIME = "actuator_activation_close_time" CONF_INERTIA_OPEN_TIME = "inertia_open_time" CONF_INERTIA_CLOSE_TIME = "inertia_close_time" @@ -45,7 +48,10 @@ CONFIG_SCHEMA = cover.COVER_SCHEMA.extend( CONF_INTERLOCK_WAIT_TIME, default="0ms" ): cv.positive_time_period_milliseconds, cv.Optional( - CONF_RECALIBRATION_TIME, default="0ms" + CONF_RECALIBRATION_OPEN_TIME, default="0ms" + ): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_RECALIBRATION_CLOSE_TIME, default="0ms" ): cv.positive_time_period_milliseconds, cv.Optional( CONF_INERTIA_OPEN_TIME, default="0ms" @@ -53,6 +59,12 @@ CONFIG_SCHEMA = cover.COVER_SCHEMA.extend( cv.Optional( CONF_INERTIA_CLOSE_TIME, default="0ms" ): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_ACTUATOR_ACTIVATION_OPEN_TIME, default="0ms" + ): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_ACTUATOR_ACTIVATION_CLOSE_TIME, default="0ms" + ): cv.positive_time_period_milliseconds, } ).extend(cv.COMPONENT_SCHEMA) @@ -79,7 +91,18 @@ async def to_code(config): cg.add(var.set_tilt_open_duration(config[CONF_TILT_OPEN_DURATION])) cg.add(var.set_tilt_close_duration(config[CONF_TILT_CLOSE_DURATION])) cg.add(var.set_interlock_wait_time(config[CONF_INTERLOCK_WAIT_TIME])) - cg.add(var.set_recalibration_time(config[CONF_RECALIBRATION_TIME])) + cg.add(var.set_recalibration_open_time(config[CONF_RECALIBRATION_OPEN_TIME])) + cg.add(var.set_recalibration_close_time(config[CONF_RECALIBRATION_CLOSE_TIME])) cg.add(var.set_inertia_close_time(config[CONF_INERTIA_CLOSE_TIME])) cg.add(var.set_inertia_open_time(config[CONF_INERTIA_OPEN_TIME])) cg.add(var.set_assumed_state(config[CONF_ASSUMED_STATE])) + cg.add( + var.set_actuator_activation_open_time( + config[CONF_ACTUATOR_ACTIVATION_OPEN_TIME] + ) + ) + cg.add( + var.set_actuator_activation_close_time( + config[CONF_ACTUATOR_ACTIVATION_CLOSE_TIME] + ) + ) diff --git a/esphome/components/time_based_tilt/time_based_tilt_cover.cpp b/esphome/components/time_based_tilt/time_based_tilt_cover.cpp index dde95891b9..d3dbc5baea 100644 --- a/esphome/components/time_based_tilt/time_based_tilt_cover.cpp +++ b/esphome/components/time_based_tilt/time_based_tilt_cover.cpp @@ -20,7 +20,12 @@ void TimeBasedTiltCover::dump_config() { ESP_LOGCONFIG(TAG, " Interlock wait time: %.3fs", this->interlock_wait_time_ / 1e3f); ESP_LOGCONFIG(TAG, " Inertia close time: %.3fs", this->inertia_close_time_ / 1e3f); ESP_LOGCONFIG(TAG, " Inertia open time: %.3fs", this->inertia_open_time_ / 1e3f); - ESP_LOGCONFIG(TAG, " Recalibration time: %.3fs", this->recalibration_time_ / 1e3f); + ESP_LOGCONFIG(TAG, " Recalibration close time: %.3fs", this->recalibration_close_time_ / 1e3f); + ESP_LOGCONFIG(TAG, " Recalibration open time: %.3fs", this->recalibration_open_time_ / 1e3f); + ESP_LOGCONFIG(TAG, " Actuator activation close time: %.3fs", this->actuator_activation_close_time_ / 1e3f); + ESP_LOGCONFIG(TAG, " Actuator activation open time: %.3fs", this->actuator_activation_open_time_ / 1e3f); + ESP_LOGCONFIG(TAG, " Current position: %.4f", this->position); + ESP_LOGCONFIG(TAG, " Current tilt: %.4f", this->tilt); } void TimeBasedTiltCover::setup() { if (this->tilt_close_duration_ == 0 || this->tilt_open_duration_ == 0) { @@ -59,6 +64,11 @@ bool TimeBasedTiltCover::is_at_target_tilt_() const { } } +bool TimeBasedTiltCover::is_at_extreme_position_() const { + return (this->position == COVER_CLOSED && (tilt_close_duration_ == 0 || this->tilt == COVER_CLOSED)) || + (this->position == COVER_OPEN && (tilt_open_duration_ == 0 || this->tilt == COVER_OPEN)); +} + void TimeBasedTiltCover::loop() { if (this->fsm_state_ == STATE_IDLE && this->target_position_ == TARGET_NONE && this->target_tilt_ == TARGET_NONE) return; @@ -67,13 +77,14 @@ void TimeBasedTiltCover::loop() { // recalibrating in extreme postions if (this->fsm_state_ == STATE_CALIBRATING) { - if (now - this->last_recompute_time_ >= this->recalibration_time_) { + if (now - this->last_recompute_time_ >= this->current_recalibration_time_) { this->fsm_state_ = STATE_STOPPING; + ESP_LOGD(TAG, "Transition to the stopping state"); } return; } - // STOPPING - determining the direction of the last movement and the stopping time. Needed to support interlocking + // STOPPING – determining the direction of the last movement and the stopping time. Necessary to support interlocking. if (this->fsm_state_ == STATE_STOPPING) { this->stop_trigger_->trigger(); if (this->current_operation != COVER_OPERATION_IDLE) { @@ -84,30 +95,55 @@ void TimeBasedTiltCover::loop() { this->interlocked_direction_ = COVER_OPERATION_IDLE; } this->fsm_state_ = STATE_IDLE; + ESP_LOGD(TAG, "Transition to the idle state"); this->last_operation_ = this->current_operation; this->current_operation = COVER_OPERATION_IDLE; + this->position = this->round_position(this->position); + this->tilt = this->round_position(this->tilt); this->publish_state(); return; } - // if the cover is not moving, check whether the new targets are set and if they are, compute move direction + // If the cover is not moving, check whether new targets are set. If they are, compute the movement direction. if (this->fsm_state_ == STATE_IDLE && (this->target_position_ != TARGET_NONE || this->target_tilt_ != TARGET_NONE)) { - if (this->target_position_ != TARGET_NONE) { + if (this->target_position_ != TARGET_NONE) { // First, calculate based on the target position. this->current_operation = this->compute_direction(this->target_position_, this->position); + if (this->current_operation == COVER_OPERATION_IDLE) { // Already at the target position. + this->target_position_ = TARGET_NONE; + if (this->target_tilt_ != TARGET_NONE) { + this->current_operation = this->compute_direction( + this->target_tilt_, this->tilt); // Calculate the direction based on the target tilt. + } + } } else { - this->current_operation = this->compute_direction(this->target_tilt_, this->tilt); + this->current_operation = + this->compute_direction(this->target_tilt_, this->tilt); // Calculate the direction based on the target tilt. } - // interlocking support + + if (this->current_operation == COVER_OPERATION_IDLE) { // Already at the target tilt and target position. + this->target_tilt_ = TARGET_NONE; + return; + } + + // Interlocking support. if (this->current_operation == this->interlocked_direction_ && now - this->interlocked_time_ < this->interlock_wait_time_) return; Trigger<> *trig = this->current_operation == COVER_OPERATION_CLOSING ? this->close_trigger_ : this->open_trigger_; + this->current_recalibration_time_ = this->current_operation == COVER_OPERATION_CLOSING + ? this->recalibration_close_time_ + : this->recalibration_open_time_; + this->current_actuator_activation_time_ = this->current_operation == COVER_OPERATION_CLOSING + ? this->actuator_activation_close_time_ + : this->actuator_activation_open_time_; + trig->trigger(); this->last_recompute_time_ = now; this->fsm_state_ = STATE_MOVING; + ESP_LOGD(TAG, "Transition to the moving state"); return; } @@ -116,46 +152,58 @@ void TimeBasedTiltCover::loop() { auto travel_time = now - this->last_recompute_time_; this->last_recompute_time_ = now; + // Actuator activation time support. + if (this->current_actuator_activation_time_ > 0) { + if (travel_time <= this->current_actuator_activation_time_) { + this->current_actuator_activation_time_ = this->current_actuator_activation_time_ - travel_time; + return; + } else { + travel_time = travel_time - this->current_actuator_activation_time_; + this->current_actuator_activation_time_ = 0; + } + } + float dir_factor = this->current_operation == COVER_OPERATION_CLOSING ? -1.0 : 1.0; auto inertia_time = this->current_operation == COVER_OPERATION_CLOSING ? this->inertia_close_time_ : this->inertia_open_time_; - if (inertia_time > 0 && this->inertia_ * dir_factor < 0.5f) { // inertia before movement + if (inertia_time > 0 && this->inertia_ * dir_factor < 0.5f) { // Inertia before movement auto inertia_step = dir_factor * travel_time / inertia_time; this->inertia_ += inertia_step; - auto rest = this->inertia_ - clamp(this->inertia_, -0.5f, 0.5f); + auto inertia_rest = this->inertia_ - clamp(this->inertia_, -0.5f, 0.5f); this->inertia_ = clamp(this->inertia_, -0.5f, 0.5f); - if (!rest) - return; // the movement has not yet actually started - travel_time = dir_factor * rest * inertia_time; // actual movement time taking inertia into account + if (!inertia_rest) + return; // The movement has not yet started. + travel_time = dir_factor * inertia_rest * inertia_time; // Actual movement time, taking inertia into account. } auto tilt_time = this->current_operation == COVER_OPERATION_CLOSING ? this->tilt_close_duration_ : this->tilt_open_duration_; - if (tilt_time > 0 && (this->tilt - 0.5f) * dir_factor < 0.5f) { // tilting before movement + if (tilt_time > 0 && (this->tilt - 0.5f) * dir_factor < 0.5f) { // Tilting before movement. auto tilt_step = dir_factor * travel_time / tilt_time; this->tilt += tilt_step; - auto rest = this->tilt - 0.5f - clamp(this->tilt - 0.5f, -0.5f, 0.5f); + auto tilt_rest = this->tilt - 0.5f - clamp(this->tilt - 0.5f, -0.5f, 0.5f); this->tilt = clamp(this->tilt, 0.0f, 1.0f); - if (this->target_position_ == TARGET_NONE && this->is_at_target_tilt_()) { // only tilting w/o position change + if (this->target_position_ == TARGET_NONE && this->is_at_target_tilt_()) { // Only tilting w/o position change this->last_recompute_time_ = now; this->target_tilt_ = TARGET_NONE; this->last_publish_time_ = now; + this->tilt = this->round_position(this->tilt); - // If the cover is in extreme positions, run recalibration - if (this->recalibration_time_ > 0 && - (((this->position == COVER_CLOSED && (tilt_time == 0 || this->tilt == COVER_CLOSED)) || - (this->position == COVER_OPEN && (tilt_time == 0 || this->tilt == COVER_OPEN))))) { + // If the cover is in extreme positions, perform recalibration. + if (this->current_recalibration_time_ > 0 && this->is_at_extreme_position_()) { this->fsm_state_ = STATE_CALIBRATING; this->publish_state(false); + ESP_LOGD(TAG, "Transition to the calibration state"); } else { this->fsm_state_ = STATE_STOPPING; + ESP_LOGD(TAG, "Transition to the stopping state"); } - return; // only tilting w/o position change so no need to recompute position + return; // Only tilting w/o position change, so there is no need to recompute the position. } if (now - this->last_publish_time_ > ((tilt_time / 5) > 1000 ? 1000 : (tilt_time / 5))) { @@ -163,15 +211,15 @@ void TimeBasedTiltCover::loop() { this->last_publish_time_ = now; } - if (!rest) - return; // the movement has not yet actually started + if (!tilt_rest) + return; // The movement has not started yet. - travel_time = dir_factor * rest * tilt_time; // actual movement time taking tilt into account + travel_time = dir_factor * tilt_rest * tilt_time; // Actual movement time, taking tilt into account. } auto move_time = this->current_operation == COVER_OPERATION_CLOSING ? this->close_duration_ : this->open_duration_; - if (move_time > 0 && (this->position - 0.5f) * dir_factor < 0.5f) { + if ((this->position - 0.5f) * dir_factor < 0.5f) { auto move_step = dir_factor * travel_time / move_time; this->position += move_step; this->position = clamp(this->position, 0.0f, 1.0f); @@ -181,15 +229,16 @@ void TimeBasedTiltCover::loop() { this->last_recompute_time_ = now; this->target_position_ = TARGET_NONE; this->last_publish_time_ = now; + this->position = this->round_position(this->position); - // If the cover is in extreme positions, run recalibration - if (this->recalibration_time_ > 0 && - (((this->position == COVER_CLOSED && (tilt_time == 0 || this->tilt == COVER_CLOSED)) || - (this->position == COVER_OPEN && (tilt_time == 0 || this->tilt == COVER_OPEN))))) { + // If the cover is in extreme positions, perform recalibration. + if (this->current_recalibration_time_ > 0 && this->is_at_extreme_position_()) { this->fsm_state_ = STATE_CALIBRATING; this->publish_state(false); + ESP_LOGD(TAG, "Transition to the calibration state"); } else { this->fsm_state_ = STATE_STOPPING; + ESP_LOGD(TAG, "Transition to the stopping state"); } } @@ -215,19 +264,22 @@ void TimeBasedTiltCover::control(const CoverCall &call) { this->target_position_ = TARGET_NONE; this->target_tilt_ = TARGET_NONE; this->fsm_state_ = STATE_STOPPING; + ESP_LOGD(TAG, "Transition to the stopping state"); return; } - if (call.get_position().has_value() && call.get_tilt().has_value()) { - auto pos = *call.get_position(); - auto til = *call.get_tilt(); - if (this->round_position(pos) == this->round_position(this->position)) - pos = TARGET_NONE; - if (this->round_position(til) == this->round_position(this->tilt)) - til = TARGET_NONE; + if (call.get_position().has_value() || call.get_tilt().has_value()) { + if (call.get_position().has_value()) { + this->target_position_ = *call.get_position(); + } else { + this->target_position_ = TARGET_NONE; + } - this->target_position_ = pos; - this->target_tilt_ = til; + if (call.get_tilt().has_value()) { + this->target_tilt_ = *call.get_tilt(); + } else { + this->target_tilt_ = TARGET_NONE; + } if (this->fsm_state_ == STATE_MOVING) { auto direction = COVER_OPERATION_IDLE; @@ -239,53 +291,24 @@ void TimeBasedTiltCover::control(const CoverCall &call) { if (direction != this->current_operation) { this->fsm_state_ = STATE_STOPPING; + ESP_LOGD(TAG, "Transition to the stopping state"); } } - } else if (call.get_position().has_value()) { - auto pos = *call.get_position(); - if (pos == COVER_CLOSED && this->position == COVER_CLOSED && this->tilt != COVER_CLOSED) { - pos = TARGET_NONE; - this->target_tilt_ = COVER_CLOSED; - } else if (pos == COVER_OPEN && this->position == COVER_OPEN && this->tilt != COVER_OPEN) { - pos = TARGET_NONE; - this->target_tilt_ = COVER_OPEN; - } else if (this->round_position(pos) == this->round_position(this->position)) { - pos = TARGET_NONE; - } - - this->target_position_ = pos; - - if (this->fsm_state_ == STATE_MOVING) { - auto direction = COVER_OPERATION_IDLE; - if (this->target_position_ != TARGET_NONE && this->target_position_ != this->position) { - direction = this->compute_direction(this->target_position_, this->position); - this->target_tilt_ = TARGET_NONE; // unset previous target tilt - } else if (this->target_tilt_ != TARGET_NONE && this->target_tilt_ != this->tilt) { - direction = this->compute_direction(this->target_tilt_, this->tilt); + if (this->fsm_state_ == STATE_CALIBRATING) { + if (this->target_position_ != TARGET_NONE) { + if ((this->position == COVER_CLOSED && this->target_position_ != COVER_CLOSED) || + (this->position == COVER_OPEN && this->target_position_ != COVER_OPEN)) { + this->fsm_state_ = STATE_STOPPING; + ESP_LOGD(TAG, "Transition to the stopping state"); + } } - - if (direction != this->current_operation) { - this->fsm_state_ = STATE_STOPPING; - } - } - } else if (call.get_tilt().has_value()) { - auto til = *call.get_tilt(); - if (this->round_position(til) == this->round_position(this->tilt)) { - til = TARGET_NONE; - } - - this->target_tilt_ = til; - - if (this->fsm_state_ == STATE_MOVING) { - auto direction = COVER_OPERATION_IDLE; - if (this->target_tilt_ != TARGET_NONE && this->target_tilt_ != this->tilt) { - direction = this->compute_direction(this->target_tilt_, this->tilt); - this->target_position_ = TARGET_NONE; - } - - if (direction != this->current_operation) { - this->fsm_state_ = STATE_STOPPING; + if (this->target_tilt_ != TARGET_NONE) { + if ((this->tilt == COVER_CLOSED && this->target_tilt_ != COVER_CLOSED) || + (this->tilt == COVER_OPEN && this->target_tilt_ != COVER_OPEN)) { + this->fsm_state_ = STATE_STOPPING; + ESP_LOGD(TAG, "Transition to the stopping state"); + } } } } @@ -293,6 +316,7 @@ void TimeBasedTiltCover::control(const CoverCall &call) { if (call.get_toggle().has_value()) { if (this->current_operation != COVER_OPERATION_IDLE) { this->fsm_state_ = STATE_STOPPING; + ESP_LOGD(TAG, "Transition to the stopping state"); this->target_position_ = TARGET_NONE; this->target_tilt_ = TARGET_NONE; } else { diff --git a/esphome/components/time_based_tilt/time_based_tilt_cover.h b/esphome/components/time_based_tilt/time_based_tilt_cover.h index 9384c26b7e..b21c95a073 100644 --- a/esphome/components/time_based_tilt/time_based_tilt_cover.h +++ b/esphome/components/time_based_tilt/time_based_tilt_cover.h @@ -22,10 +22,22 @@ class TimeBasedTiltCover : public cover::Cover, public Component { void set_tilt_open_duration(uint32_t tilt_open_duration) { this->tilt_open_duration_ = tilt_open_duration; } void set_tilt_close_duration(uint32_t tilt_close_duration) { this->tilt_close_duration_ = tilt_close_duration; } void set_interlock_wait_time(uint32_t interlock_wait_time) { this->interlock_wait_time_ = interlock_wait_time; } - void set_recalibration_time(uint32_t recalibration_time) { this->recalibration_time_ = recalibration_time; } + void set_recalibration_open_time(uint32_t recalibration_time) { this->recalibration_open_time_ = recalibration_time; } + void set_recalibration_close_time(uint32_t recalibration_time) { + this->recalibration_close_time_ = recalibration_time; + } void set_inertia_open_time(uint32_t inertia_time) { this->inertia_open_time_ = inertia_time; } void set_inertia_close_time(uint32_t inertia_time) { this->inertia_close_time_ = inertia_time; } + void set_actuator_activation_open_time(uint32_t activation_time) { + this->actuator_activation_open_time_ = activation_time; + } + void set_actuator_activation_close_time(uint32_t activation_time) { + this->actuator_activation_close_time_ = activation_time; + } cover::CoverOperation compute_direction(float target, float current) { + if (target == current) { + return cover::COVER_OPERATION_IDLE; + } return target < current ? cover::COVER_OPERATION_CLOSING : cover::COVER_OPERATION_OPENING; }; float round_position(float pos) { return round(100 * pos) / 100; }; @@ -36,6 +48,7 @@ class TimeBasedTiltCover : public cover::Cover, public Component { void control(const cover::CoverCall &call) override; bool is_at_target_position_() const; bool is_at_target_tilt_() const; + bool is_at_extreme_position_() const; Trigger<> *open_trigger_{new Trigger<>()}; Trigger<> *close_trigger_{new Trigger<>()}; @@ -48,9 +61,15 @@ class TimeBasedTiltCover : public cover::Cover, public Component { uint32_t tilt_open_duration_; uint32_t interlock_wait_time_; - uint32_t recalibration_time_; + uint32_t recalibration_open_time_; + uint32_t recalibration_close_time_; uint32_t inertia_open_time_; uint32_t inertia_close_time_; + uint32_t actuator_activation_open_time_; + uint32_t actuator_activation_close_time_; + + uint32_t current_recalibration_time_; + uint32_t current_actuator_activation_time_; const static float TARGET_NONE; enum State : uint8_t { STATE_IDLE, STATE_MOVING, STATE_STOPPING, STATE_CALIBRATING };