From 9bf946e1962bbb7e43bcf809d1e8584a988bb7aa Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Wed, 17 May 2023 18:36:52 -0500 Subject: [PATCH] Sprinkler fixes (#4816) --- esphome/components/sprinkler/__init__.py | 140 ++++++++++----------- esphome/components/sprinkler/sprinkler.cpp | 126 +++++++++++++------ esphome/components/sprinkler/sprinkler.h | 12 +- 3 files changed, 162 insertions(+), 116 deletions(-) diff --git a/esphome/components/sprinkler/__init__.py b/esphome/components/sprinkler/__init__.py index 5097abc7e7..e1d855778a 100644 --- a/esphome/components/sprinkler/__init__.py +++ b/esphome/components/sprinkler/__init__.py @@ -599,15 +599,6 @@ async def to_code(config): ) cg.add(var.set_controller_auto_adv_switch(sw_aa_var)) - if CONF_QUEUE_ENABLE_SWITCH in sprinkler_controller: - sw_qen_var = await switch.new_switch( - sprinkler_controller[CONF_QUEUE_ENABLE_SWITCH] - ) - await cg.register_component( - sw_qen_var, sprinkler_controller[CONF_QUEUE_ENABLE_SWITCH] - ) - cg.add(var.set_controller_queue_enable_switch(sw_qen_var)) - if CONF_REVERSE_SWITCH in sprinkler_controller: sw_rev_var = await switch.new_switch( sprinkler_controller[CONF_REVERSE_SWITCH] @@ -617,78 +608,83 @@ async def to_code(config): ) cg.add(var.set_controller_reverse_switch(sw_rev_var)) - if CONF_STANDBY_SWITCH in sprinkler_controller: - sw_stb_var = await switch.new_switch( - sprinkler_controller[CONF_STANDBY_SWITCH] - ) - await cg.register_component( - sw_stb_var, sprinkler_controller[CONF_STANDBY_SWITCH] - ) - cg.add(var.set_controller_standby_switch(sw_stb_var)) + if CONF_STANDBY_SWITCH in sprinkler_controller: + sw_stb_var = await switch.new_switch( + sprinkler_controller[CONF_STANDBY_SWITCH] + ) + await cg.register_component( + sw_stb_var, sprinkler_controller[CONF_STANDBY_SWITCH] + ) + cg.add(var.set_controller_standby_switch(sw_stb_var)) - if CONF_MULTIPLIER_NUMBER in sprinkler_controller: - num_mult_var = await number.new_number( - sprinkler_controller[CONF_MULTIPLIER_NUMBER], - min_value=sprinkler_controller[CONF_MULTIPLIER_NUMBER][ - CONF_MIN_VALUE - ], - max_value=sprinkler_controller[CONF_MULTIPLIER_NUMBER][ - CONF_MAX_VALUE - ], - step=sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_STEP], + if CONF_QUEUE_ENABLE_SWITCH in sprinkler_controller: + sw_qen_var = await switch.new_switch( + sprinkler_controller[CONF_QUEUE_ENABLE_SWITCH] + ) + await cg.register_component( + sw_qen_var, sprinkler_controller[CONF_QUEUE_ENABLE_SWITCH] + ) + cg.add(var.set_controller_queue_enable_switch(sw_qen_var)) + + if CONF_MULTIPLIER_NUMBER in sprinkler_controller: + num_mult_var = await number.new_number( + sprinkler_controller[CONF_MULTIPLIER_NUMBER], + min_value=sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_MIN_VALUE], + max_value=sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_MAX_VALUE], + step=sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_STEP], + ) + await cg.register_component( + num_mult_var, sprinkler_controller[CONF_MULTIPLIER_NUMBER] + ) + cg.add( + num_mult_var.set_initial_value( + sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_INITIAL_VALUE] ) - await cg.register_component( - num_mult_var, sprinkler_controller[CONF_MULTIPLIER_NUMBER] + ) + cg.add( + num_mult_var.set_restore_value( + sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_RESTORE_VALUE] ) - cg.add( - num_mult_var.set_initial_value( - sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_INITIAL_VALUE] - ) - ) - cg.add( - num_mult_var.set_restore_value( - sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_RESTORE_VALUE] - ) + ) + + if CONF_SET_ACTION in sprinkler_controller[CONF_MULTIPLIER_NUMBER]: + await automation.build_automation( + num_mult_var.get_set_trigger(), + [(float, "x")], + sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_SET_ACTION], ) - if CONF_SET_ACTION in sprinkler_controller[CONF_MULTIPLIER_NUMBER]: - await automation.build_automation( - num_mult_var.get_set_trigger(), - [(float, "x")], - sprinkler_controller[CONF_MULTIPLIER_NUMBER][CONF_SET_ACTION], - ) + cg.add(var.set_controller_multiplier_number(num_mult_var)) - cg.add(var.set_controller_multiplier_number(num_mult_var)) + if CONF_REPEAT_NUMBER in sprinkler_controller: + num_repeat_var = await number.new_number( + sprinkler_controller[CONF_REPEAT_NUMBER], + min_value=sprinkler_controller[CONF_REPEAT_NUMBER][CONF_MIN_VALUE], + max_value=sprinkler_controller[CONF_REPEAT_NUMBER][CONF_MAX_VALUE], + step=sprinkler_controller[CONF_REPEAT_NUMBER][CONF_STEP], + ) + await cg.register_component( + num_repeat_var, sprinkler_controller[CONF_REPEAT_NUMBER] + ) + cg.add( + num_repeat_var.set_initial_value( + sprinkler_controller[CONF_REPEAT_NUMBER][CONF_INITIAL_VALUE] + ) + ) + cg.add( + num_repeat_var.set_restore_value( + sprinkler_controller[CONF_REPEAT_NUMBER][CONF_RESTORE_VALUE] + ) + ) - if CONF_REPEAT_NUMBER in sprinkler_controller: - num_repeat_var = await number.new_number( - sprinkler_controller[CONF_REPEAT_NUMBER], - min_value=sprinkler_controller[CONF_REPEAT_NUMBER][CONF_MIN_VALUE], - max_value=sprinkler_controller[CONF_REPEAT_NUMBER][CONF_MAX_VALUE], - step=sprinkler_controller[CONF_REPEAT_NUMBER][CONF_STEP], - ) - await cg.register_component( - num_repeat_var, sprinkler_controller[CONF_REPEAT_NUMBER] - ) - cg.add( - num_repeat_var.set_initial_value( - sprinkler_controller[CONF_REPEAT_NUMBER][CONF_INITIAL_VALUE] - ) - ) - cg.add( - num_repeat_var.set_restore_value( - sprinkler_controller[CONF_REPEAT_NUMBER][CONF_RESTORE_VALUE] - ) + if CONF_SET_ACTION in sprinkler_controller[CONF_REPEAT_NUMBER]: + await automation.build_automation( + num_repeat_var.get_set_trigger(), + [(float, "x")], + sprinkler_controller[CONF_REPEAT_NUMBER][CONF_SET_ACTION], ) - if CONF_SET_ACTION in sprinkler_controller[CONF_REPEAT_NUMBER]: - await automation.build_automation( - num_repeat_var.get_set_trigger(), - [(float, "x")], - sprinkler_controller[CONF_REPEAT_NUMBER][CONF_SET_ACTION], - ) - - cg.add(var.set_controller_repeat_number(num_repeat_var)) + cg.add(var.set_controller_repeat_number(num_repeat_var)) for valve in sprinkler_controller[CONF_VALVES]: sw_valve_var = await switch.new_switch(valve[CONF_VALVE_SWITCH]) diff --git a/esphome/components/sprinkler/sprinkler.cpp b/esphome/components/sprinkler/sprinkler.cpp index 52a6cd2af4..095884997c 100644 --- a/esphome/components/sprinkler/sprinkler.cpp +++ b/esphome/components/sprinkler/sprinkler.cpp @@ -147,22 +147,22 @@ SprinklerValveOperator::SprinklerValveOperator(SprinklerValve *valve, Sprinkler : controller_(controller), valve_(valve) {} void SprinklerValveOperator::loop() { - if (millis() >= this->pinned_millis_) { // dummy check + if (millis() >= this->start_millis_) { // dummy check switch (this->state_) { case STARTING: - if (millis() > (this->pinned_millis_ + this->start_delay_)) { + if (millis() > (this->start_millis_ + this->start_delay_)) { this->run_(); // start_delay_ has been exceeded, so ensure both valves are on and update the state } break; case ACTIVE: - if (millis() > (this->pinned_millis_ + this->start_delay_ + this->run_duration_)) { + if (millis() > (this->start_millis_ + this->start_delay_ + this->run_duration_)) { this->stop(); // start_delay_ + run_duration_ has been exceeded, start shutting down } break; case STOPPING: - if (millis() > (this->pinned_millis_ + this->stop_delay_)) { + if (millis() > (this->stop_millis_ + this->stop_delay_)) { this->kill_(); // stop_delay_has been exceeded, ensure all valves are off } break; @@ -183,11 +183,12 @@ void SprinklerValveOperator::set_controller(Sprinkler *controller) { void SprinklerValveOperator::set_valve(SprinklerValve *valve) { if (valve != nullptr) { - this->state_ = IDLE; // reset state - this->run_duration_ = 0; // reset to ensure the valve isn't started without updating it - this->pinned_millis_ = 0; // reset because (new) valve has not been started yet - this->kill_(); // ensure everything is off before we let go! - this->valve_ = valve; // finally, set the pointer to the new valve + this->state_ = IDLE; // reset state + this->run_duration_ = 0; // reset to ensure the valve isn't started without updating it + this->start_millis_ = 0; // reset because (new) valve has not been started yet + this->stop_millis_ = 0; // reset because (new) valve has not been started yet + this->kill_(); // ensure everything is off before we let go! + this->valve_ = valve; // finally, set the pointer to the new valve } } @@ -221,7 +222,8 @@ void SprinklerValveOperator::start() { } else { this->run_(); // there is no start_delay_, so just start the pump and valve } - this->pinned_millis_ = millis(); // save the time the start request was made + this->stop_millis_ = 0; + this->start_millis_ = millis(); // save the time the start request was made } void SprinklerValveOperator::stop() { @@ -238,19 +240,33 @@ void SprinklerValveOperator::stop() { if (this->pump_switch()->state()) { // if the pump is still on at this point, it may be in use... this->valve_off_(); // ...so just switch the valve off now to ensure consistent run time } - this->pinned_millis_ = millis(); // save the time the stop request was made } else { this->kill_(); // there is no stop_delay_, so just stop the pump and valve } + this->stop_millis_ = millis(); // save the time the stop request was made } -uint32_t SprinklerValveOperator::run_duration() { return this->run_duration_; } +uint32_t SprinklerValveOperator::run_duration() { return this->run_duration_ / 1000; } uint32_t SprinklerValveOperator::time_remaining() { - if ((this->state_ == STARTING) || (this->state_ == ACTIVE)) { - return (this->pinned_millis_ + this->start_delay_ + this->run_duration_ - millis()) / 1000; + if (this->start_millis_ == 0) { + return this->run_duration(); // hasn't been started yet } - return 0; + + if (this->stop_millis_) { + if (this->stop_millis_ - this->start_millis_ >= this->start_delay_ + this->run_duration_) { + return 0; // valve was active for more than its configured duration, so we are done + } else { + // we're stopped; return time remaining + return (this->run_duration_ - (this->stop_millis_ - this->start_millis_)) / 1000; + } + } + + auto completed_millis = this->start_millis_ + this->start_delay_ + this->run_duration_; + if (completed_millis > millis()) { + return (completed_millis - millis()) / 1000; // running now + } + return 0; // run completed } SprinklerState SprinklerValveOperator::state() { return this->state_; } @@ -386,6 +402,9 @@ void Sprinkler::loop() { for (auto &vo : this->valve_op_) { vo.loop(); } + if (this->prev_req_.has_request() && this->prev_req_.valve_operator()->state() == IDLE) { + this->prev_req_.reset(); + } } void Sprinkler::add_valve(SprinklerControllerSwitch *valve_sw, SprinklerControllerSwitch *enable_sw) { @@ -732,7 +751,7 @@ bool Sprinkler::auto_advance() { if (this->auto_adv_sw_ != nullptr) { return this->auto_adv_sw_->state; } - return false; + return true; } float Sprinkler::multiplier() { @@ -972,7 +991,14 @@ optional Sprinkler::active_valve_request_is_from return nullopt; } -optional Sprinkler::active_valve() { return this->active_req_.valve_as_opt(); } +optional Sprinkler::active_valve() { + if (!this->valve_overlap_ && this->prev_req_.has_request() && + (this->prev_req_.valve_operator()->state() == STARTING || this->prev_req_.valve_operator()->state() == ACTIVE)) { + return this->prev_req_.valve_as_opt(); + } + return this->active_req_.valve_as_opt(); +} + optional Sprinkler::paused_valve() { return this->paused_valve_; } optional Sprinkler::queued_valve() { @@ -1097,22 +1123,35 @@ uint32_t Sprinkler::total_cycle_time_enabled_valves() { uint32_t Sprinkler::total_cycle_time_enabled_incomplete_valves() { uint32_t total_time_remaining = 0; - uint32_t valve_count = 0; + uint32_t enabled_valve_count = 0; + uint32_t incomplete_valve_count = 0; for (size_t valve = 0; valve < this->number_of_valves(); valve++) { - if (this->valve_is_enabled_(valve) && !this->valve_cycle_complete_(valve)) { - if (!this->active_valve().has_value() || (valve != this->active_valve().value())) { - total_time_remaining += this->valve_run_duration_adjusted(valve); - valve_count++; + if (this->valve_is_enabled_(valve)) { + enabled_valve_count++; + if (!this->valve_cycle_complete_(valve)) { + if (!this->active_valve().has_value() || (valve != this->active_valve().value())) { + total_time_remaining += this->valve_run_duration_adjusted(valve); + incomplete_valve_count++; + } else { + // to get here, there must be an active valve and this valve must be equal to 'valve' + if (this->active_req_.valve_operator() == nullptr) { // no SVO has been assigned yet so it hasn't started + total_time_remaining += this->valve_run_duration_adjusted(valve); + incomplete_valve_count++; + } + } } } } - if (valve_count) { + if (incomplete_valve_count >= enabled_valve_count) { + incomplete_valve_count--; + } + if (incomplete_valve_count) { if (this->valve_overlap_) { - total_time_remaining -= this->switching_delay_.value_or(0) * (valve_count - 1); + total_time_remaining -= this->switching_delay_.value_or(0) * incomplete_valve_count; } else { - total_time_remaining += this->switching_delay_.value_or(0) * (valve_count - 1); + total_time_remaining += this->switching_delay_.value_or(0) * incomplete_valve_count; } } @@ -1149,31 +1188,32 @@ optional Sprinkler::time_remaining_active_valve() { return this->active_req_.valve_operator()->time_remaining(); } } - for (auto &vo : this->valve_op_) { // ...else return the value from the first non-IDLE SprinklerValveOperator - if (vo.state() != IDLE) { - return vo.time_remaining(); + if (this->prev_req_.has_request()) { // try to return the value based on prev_req_... + if (this->prev_req_.valve_operator() != nullptr) { + return this->prev_req_.valve_operator()->time_remaining(); } } return nullopt; } optional Sprinkler::time_remaining_current_operation() { - auto total_time_remaining = this->time_remaining_active_valve(); + if (!this->time_remaining_active_valve().has_value() && this->state_ == IDLE) { + return nullopt; + } - if (total_time_remaining.has_value()) { - if (this->auto_advance()) { - total_time_remaining = total_time_remaining.value() + this->total_cycle_time_enabled_incomplete_valves(); - total_time_remaining = - total_time_remaining.value() + + auto total_time_remaining = this->time_remaining_active_valve().value_or(0); + if (this->auto_advance()) { + total_time_remaining += this->total_cycle_time_enabled_incomplete_valves(); + if (this->repeat().value_or(0) > 0) { + total_time_remaining += (this->total_cycle_time_enabled_valves() * (this->repeat().value_or(0) - this->repeat_count().value_or(0))); } - - if (this->queue_enabled()) { - total_time_remaining = total_time_remaining.value() + this->total_queue_time(); - } - return total_time_remaining; } - return nullopt; + + if (this->queue_enabled()) { + total_time_remaining += this->total_queue_time(); + } + return total_time_remaining; } bool Sprinkler::any_controller_is_active() { @@ -1305,6 +1345,12 @@ optional Sprinkler::next_valve_number_in_cycle_(const optional f } void Sprinkler::load_next_valve_run_request_(const optional first_valve) { + if (this->active_req_.has_request()) { + this->prev_req_ = this->active_req_; + } else { + this->prev_req_.reset(); + } + if (this->next_req_.has_request()) { if (!this->next_req_.run_duration()) { // ensure the run duration is set correctly for consumption later on this->next_req_.set_run_duration(this->valve_run_duration_adjusted(this->next_req_.valve())); diff --git a/esphome/components/sprinkler/sprinkler.h b/esphome/components/sprinkler/sprinkler.h index 7a8285ae73..ae7554d3af 100644 --- a/esphome/components/sprinkler/sprinkler.h +++ b/esphome/components/sprinkler/sprinkler.h @@ -170,7 +170,8 @@ class SprinklerValveOperator { uint32_t start_delay_{0}; uint32_t stop_delay_{0}; uint32_t run_duration_{0}; - uint64_t pinned_millis_{0}; + uint64_t start_millis_{0}; + uint64_t stop_millis_{0}; Sprinkler *controller_{nullptr}; SprinklerValve *valve_{nullptr}; SprinklerState state_{IDLE}; @@ -538,15 +539,18 @@ class Sprinkler : public Component { /// The valve run request that is currently active SprinklerValveRunRequest active_req_; + /// The next run request for the controller to consume after active_req_ is complete + SprinklerValveRunRequest next_req_; + + /// The previous run request the controller processed + SprinklerValveRunRequest prev_req_; + /// The number of the manually selected valve currently selected optional manual_valve_; /// The number of the valve to resume from (if paused) optional paused_valve_; - /// The next run request for the controller to consume after active_req_ is complete - SprinklerValveRunRequest next_req_; - /// Set the number of times to repeat a full cycle optional target_repeats_;