ESPHome 2026.10.0-dev
Loading...
Searching...
No Matches
sprinkler.cpp
Go to the documentation of this file.
1#include "automation.h"
2#include "sprinkler.h"
3
6#include "esphome/core/log.h"
8#include <cinttypes>
9#include <utility>
10
11namespace esphome::sprinkler {
12
13static const char *const TAG = "sprinkler";
14
16 float value;
17 if (!this->restore_value_) {
18 value = this->initial_value_;
19 } else {
21 if (!this->pref_.load(&value)) {
22 if (!std::isnan(this->initial_value_)) {
23 value = this->initial_value_;
24 } else {
25 value = this->traits.get_min_value();
26 }
27 }
28 }
29 this->publish_state(value);
30}
31
33 this->set_trigger_.trigger(value);
34
35 this->publish_state(value);
36
37 if (this->restore_value_)
38 this->pref_.save(&value);
39}
40
41void SprinklerControllerNumber::dump_config() { LOG_NUMBER("", "Sprinkler Controller Number", this); }
42
44
46 // Loop is only enabled when f_ has a value (see setup())
47 auto s = (*this->f_)(); // NOLINT(bugprone-unchecked-optional-access)
48 if (s.has_value()) {
49 this->publish_state(*s);
50 }
51}
52
54 if (this->prev_trigger_ != nullptr) {
56 }
57
58 if (state) {
59 this->prev_trigger_ = &this->turn_on_trigger_;
61 } else {
62 this->prev_trigger_ = &this->turn_off_trigger_;
64 }
65
66 this->publish_state(state);
67}
68
69void SprinklerControllerSwitch::set_state_lambda(std::function<optional<bool>()> &&f) { this->f_ = f; }
71
73 this->state = this->get_initial_state_with_restore_mode().value_or(false);
74 // Disable loop if no state lambda is set - nothing to poll
75 if (!this->f_.has_value()) {
76 this->disable_loop();
77 }
78}
79
80void SprinklerControllerSwitch::dump_config() { LOG_SWITCH("", "Sprinkler Switch", this); }
81
84 : controller_(controller), valve_(valve) {}
85
87 // Use wrapping subtraction so 32-bit millis() rollover is handled correctly:
88 // (now - start) yields the true elapsed time even across the 49.7-day boundary.
90 switch (this->state_) {
91 case STARTING:
92 if ((now - *this->start_millis_) > this->start_delay_) { // NOLINT(bugprone-unchecked-optional-access)
93 this->run_(); // start_delay_ has been exceeded, so ensure both valves are on and update the state
94 }
95 break;
96
97 case ACTIVE:
98 if ((now - *this->start_millis_) > // NOLINT(bugprone-unchecked-optional-access)
99 (this->start_delay_ + this->run_duration_)) {
100 this->stop(); // start_delay_ + run_duration_ has been exceeded, start shutting down
101 }
102 break;
103
104 case STOPPING:
105 if ((now - *this->stop_millis_) > this->stop_delay_) { // NOLINT(bugprone-unchecked-optional-access)
106 this->kill_(); // stop_delay_has been exceeded, ensure all valves are off
107 }
108 break;
109
110 default:
111 break;
112 }
113}
114
116 if (controller != nullptr) {
117 this->controller_ = controller;
118 }
119}
120
122 if (valve != nullptr) {
123 if (this->state_ != IDLE) { // Only kill if not already idle
124 this->kill_(); // ensure everything is off before we let go!
125 }
126 this->state_ = IDLE; // reset state
127 this->run_duration_ = 0; // reset to ensure the valve isn't started without updating it
128 this->start_millis_.reset(); // reset because (new) valve has not been started yet
129 this->stop_millis_.reset(); // reset because (new) valve has not been started yet
130 this->valve_ = valve; // finally, set the pointer to the new valve
131 }
132}
133
135 if (run_duration) {
136 this->run_duration_ = run_duration * 1000;
137 }
138}
139
140void SprinklerValveOperator::set_start_delay(uint32_t start_delay, bool start_delay_is_valve_delay) {
141 this->start_delay_is_valve_delay_ = start_delay_is_valve_delay;
142 this->start_delay_ = start_delay * 1000; // because 1000 milliseconds is one second
143}
144
145void SprinklerValveOperator::set_stop_delay(uint32_t stop_delay, bool stop_delay_is_valve_delay) {
146 this->stop_delay_is_valve_delay_ = stop_delay_is_valve_delay;
147 this->stop_delay_ = stop_delay * 1000; // because 1000 milliseconds is one second
148}
149
151 if (!this->run_duration_) { // can't start if zero run duration
152 return;
153 }
154 if (this->start_delay_ && (this->pump_switch() != nullptr)) {
155 this->state_ = STARTING; // STARTING state requires both a pump and a start_delay_
156 if (this->start_delay_is_valve_delay_) {
157 this->pump_on_();
158 } else if (!this->pump_switch()->state) { // if the pump is already on, wait to switch on the valve
159 this->valve_on_(); // to ensure consistent run time
160 }
161 } else {
162 this->run_(); // there is no start_delay_, so just start the pump and valve
163 }
164 this->stop_millis_.reset();
165 this->start_millis_ = millis(); // save the time the start request was made
166}
167
169 if ((this->state_ == IDLE) || (this->state_ == STOPPING)) { // can't stop if already stopped or stopping
170 return;
171 }
172 if (this->stop_delay_ && (this->pump_switch() != nullptr)) {
173 this->state_ = STOPPING; // STOPPING state requires both a pump and a stop_delay_
174 if (this->stop_delay_is_valve_delay_) {
175 this->pump_off_();
176 } else {
177 this->valve_off_();
178 }
179 if (this->pump_switch()->state) { // if the pump is still on at this point, it may be in use...
180 this->valve_off_(); // ...so just switch the valve off now to ensure consistent run time
181 }
182 } else {
183 this->kill_(); // there is no stop_delay_, so just stop the pump and valve
184 }
185 this->stop_millis_ = millis(); // save the time the stop request was made
186}
187
189
191 if (!this->start_millis_.has_value()) {
192 return this->run_duration(); // hasn't been started yet
193 }
194
195 if (this->stop_millis_.has_value()) {
196 uint32_t elapsed = *this->stop_millis_ - *this->start_millis_;
197 if (elapsed >= this->start_delay_ + this->run_duration_) {
198 return 0; // valve was active for more than its configured duration, so we are done
199 }
200 if (elapsed <= this->start_delay_) {
201 return this->run_duration_ / 1000; // stopped during start delay, full run duration remains
202 }
203 return (this->run_duration_ - (elapsed - this->start_delay_)) / 1000;
204 }
205
206 uint32_t elapsed = millis() - *this->start_millis_;
207 uint32_t total_duration = this->start_delay_ + this->run_duration_;
208 if (elapsed < total_duration) {
209 return (total_duration - elapsed) / 1000; // running now
210 }
211 return 0; // run completed
212}
213
215 if ((this->controller_ == nullptr) || (this->valve_ == nullptr)) {
216 return nullptr;
217 }
218 if (this->valve_->pump_switch_index.has_value()) {
220 }
221 return nullptr;
222}
223
225 auto *pump = this->pump_switch();
226 if ((this->valve_ == nullptr) || (pump == nullptr)) { // safety first!
227 return;
228 }
229 if (this->controller_ == nullptr) { // safety first!
230 pump->turn_off(); // if no controller was set, just switch off the pump
231 } else { // ...otherwise, do it "safely"
232 auto state = this->state_; // this is silly, but...
233 this->state_ = BYPASS; // ...exclude me from the pump-in-use check that set_pump_state() does
234 this->controller_->set_pump_state(pump, false);
235 this->state_ = state;
236 }
237}
238
240 auto *pump = this->pump_switch();
241 if ((this->valve_ == nullptr) || (pump == nullptr)) { // safety first!
242 return;
243 }
244 if (this->controller_ == nullptr) { // safety first!
245 pump->turn_on(); // if no controller was set, just switch on the pump
246 } else { // ...otherwise, do it "safely"
247 auto state = this->state_; // this is silly, but...
248 this->state_ = BYPASS; // ...exclude me from the pump-in-use check that set_pump_state() does
249 this->controller_->set_pump_state(pump, true);
250 this->state_ = state;
251 }
252}
253
255 if ((this->valve_ == nullptr) || (this->valve_->valve_switch == nullptr)) { // safety first!
256 return;
257 }
258 if (this->valve_->valve_switch->state) {
259 this->valve_->valve_switch->turn_off();
260 }
261}
262
264 if ((this->valve_ == nullptr) || (this->valve_->valve_switch == nullptr)) { // safety first!
265 return;
266 }
267 if (!this->valve_->valve_switch->state) {
268 this->valve_->valve_switch->turn_on();
269 }
270}
271
273 this->state_ = IDLE;
274 this->valve_off_();
275 this->pump_off_();
276}
277
279 this->state_ = ACTIVE;
280 this->valve_on_();
281 this->pump_on_();
282}
283
286 SprinklerValveOperator *valve_op)
287 : valve_number_(valve_number), run_duration_(run_duration), valve_op_(valve_op) {}
288
289bool SprinklerValveRunRequest::has_valve_operator() { return !(this->valve_op_ == nullptr); }
290
292
293void SprinklerValveRunRequest::set_valve(size_t valve_number) {
294 this->valve_number_ = valve_number;
295 this->run_duration_ = 0;
296 this->valve_op_ = nullptr;
297 this->has_valve_ = true;
298}
299
301 if (valve_op != nullptr) {
302 this->valve_op_ = valve_op;
303 }
304}
305
307 this->has_valve_ = false;
308 this->origin_ = USER;
309 this->run_duration_ = 0;
310 this->valve_op_ = nullptr;
311}
312
314
316 if (this->has_valve_) {
317 return this->valve_number_;
318 }
319 return nullopt;
320}
321
323
325Sprinkler::Sprinkler(const char *name) : name_(name) {
326 // The `name` is stored for dump_config logging
327 this->timer_.init(2);
328 // Timer names only need to be unique within this component instance
329 this->timer_.push_back({"sm", false, 0, 0, [this]() { this->sm_timer_callback_(); }});
330 this->timer_.push_back({"vs", false, 0, 0, [this]() { this->valve_selection_callback_(); }});
331}
332
334 this->all_valves_off_(true);
335 // Start with loop disabled - nothing to do when idle
336 this->disable_loop();
337}
338
340 for (auto &vo : this->valve_op_) {
341 vo.loop();
342 }
343 if (this->prev_req_.has_request()) {
344 if (this->prev_req_.has_valve_operator() && this->prev_req_.valve_operator()->state() == IDLE) {
345 this->prev_req_.reset();
346 }
347 } else if (this->state_ == IDLE) {
348 // Nothing more to do - disable loop until next activation
349 this->disable_loop();
350 }
351}
352
354 auto new_valve_number = this->number_of_valves();
355 this->valve_.resize(new_valve_number + 1);
356 SprinklerValve *new_valve = &this->valve_[new_valve_number];
357
358 new_valve->controller_switch = valve_sw;
359 new_valve->controller_switch->set_state_lambda([this, new_valve_number]() -> optional<bool> {
360 auto *valve = this->valve_switch(new_valve_number);
361 auto *pump = this->valve_pump_switch(new_valve_number);
362 if (valve == nullptr) {
363 return false;
364 }
365 if (pump != nullptr) {
366 return valve->state && pump->state;
367 }
368 return valve->state;
369 });
370
371 new_valve->valve_turn_off_automation =
372 make_unique<Automation<>>(new_valve->controller_switch->get_turn_off_trigger());
373 new_valve->valve_shutdown_action = make_unique<sprinkler::ShutdownAction<>>(this);
374 new_valve->valve_turn_off_automation->add_actions({new_valve->valve_shutdown_action.get()});
375
376 new_valve->valve_turn_on_automation = make_unique<Automation<>>(new_valve->controller_switch->get_turn_on_trigger());
377 new_valve->valve_resumeorstart_action = make_unique<sprinkler::StartSingleValveAction<>>(this);
378 new_valve->valve_resumeorstart_action->set_valve_to_start(new_valve_number);
379 new_valve->valve_turn_on_automation->add_actions({new_valve->valve_resumeorstart_action.get()});
380
381 if (enable_sw != nullptr) {
382 new_valve->enable_switch = enable_sw;
383 }
384}
385
386void Sprinkler::add_controller(Sprinkler *other_controller) { this->other_controllers_.push_back(other_controller); }
387
389 this->controller_sw_ = controller_switch;
390 controller_switch->set_state_lambda([this]() -> optional<bool> {
391 for (size_t valve_number = 0; valve_number < this->number_of_valves(); valve_number++) {
392 if (this->valve_[valve_number].controller_switch->state) {
393 return true;
394 }
395 }
396 return this->active_req_.has_request();
397 });
398
399 this->sprinkler_turn_off_automation_ = make_unique<Automation<>>(controller_switch->get_turn_off_trigger());
400 this->sprinkler_shutdown_action_ = make_unique<sprinkler::ShutdownAction<>>(this);
402
403 this->sprinkler_turn_on_automation_ = make_unique<Automation<>>(controller_switch->get_turn_on_trigger());
404 this->sprinkler_resumeorstart_action_ = make_unique<sprinkler::ResumeOrStartAction<>>(this);
406}
407
409 this->standby_sw_ = standby_switch;
410
411 this->sprinkler_standby_turn_on_automation_ = make_unique<Automation<>>(standby_switch->get_turn_on_trigger());
412 this->sprinkler_standby_shutdown_action_ = make_unique<sprinkler::ShutdownAction<>>(this);
414}
415
416void Sprinkler::configure_valve_switch(size_t valve_number, switch_::Switch *valve_switch, uint32_t run_duration) {
417 if (this->is_a_valid_valve(valve_number)) {
418 this->valve_[valve_number].valve_switch = valve_switch;
419 this->valve_[valve_number].run_duration = run_duration;
420 }
421}
422
423void Sprinkler::configure_valve_pump_switch(size_t valve_number, switch_::Switch *pump_switch) {
424 if (this->is_a_valid_valve(valve_number)) {
425 for (size_t i = 0; i < this->pump_.size(); i++) { // check each existing registered pump
426 if (this->pump_[i] == pump_switch) { // if the "new" pump matches one we already have...
427 this->valve_[valve_number].pump_switch_index = i; // ...save its index in the pump vector...
428 return; // ...and we are done
429 }
430 } // if we end up here, no pumps matched, so add a new one
431 this->pump_.push_back(pump_switch);
432 this->valve_[valve_number].pump_switch_index = this->pump_.size() - 1; // save the index to the new pump
433 }
434}
435
437 SprinklerControllerNumber *run_duration_number) {
438 if (this->is_a_valid_valve(valve_number)) {
439 this->valve_[valve_number].run_duration_number = run_duration_number;
440 }
441}
442
443void Sprinkler::set_divider(optional<uint32_t> divider) {
444 if (!divider.has_value()) {
445 return;
446 }
447 if (divider.value() > 0) {
448 this->set_multiplier(1.0 / divider.value());
449 this->set_repeat(divider.value() - 1);
450 } else if (divider.value() == 0) {
451 this->set_multiplier(1.0);
452 this->set_repeat(0);
453 }
454}
455
456void Sprinkler::set_multiplier(const optional<float> multiplier) {
457 if ((!multiplier.has_value()) || (multiplier.value() < 0)) {
458 return;
459 }
460 this->multiplier_ = multiplier.value();
461 if (this->multiplier_number_ == nullptr) {
462 return;
463 }
464 if (this->multiplier_number_->state == multiplier.value()) {
465 return;
466 }
467 auto call = this->multiplier_number_->make_call();
468 call.set_value(multiplier.value());
469 call.perform();
470}
471
473 this->start_delay_is_valve_delay_ = false;
474 this->start_delay_ = start_delay;
475}
476
478 this->stop_delay_is_valve_delay_ = false;
479 this->stop_delay_ = stop_delay;
480}
481
483 this->start_delay_is_valve_delay_ = true;
484 this->start_delay_ = start_delay;
485}
486
488 this->stop_delay_is_valve_delay_ = true;
489 this->stop_delay_ = stop_delay;
490}
491
492void Sprinkler::set_valve_open_delay(const uint32_t valve_open_delay) {
493 if (valve_open_delay > 0) {
494 this->valve_overlap_ = false;
495 this->switching_delay_ = valve_open_delay;
496 } else {
497 this->switching_delay_.reset();
498 }
499}
500
502 if (valve_overlap > 0) {
503 this->valve_overlap_ = true;
504 this->switching_delay_ = valve_overlap;
505 } else {
506 this->switching_delay_.reset();
507 }
508 this->pump_switch_off_during_valve_open_delay_ = false; // incompatible option
509}
510
512 if (manual_selection_delay > 0) {
513 this->manual_selection_delay_ = manual_selection_delay;
514 } else {
515 this->manual_selection_delay_.reset();
516 }
517}
518
519void Sprinkler::set_valve_run_duration(const optional<size_t> valve_number, const optional<uint32_t> run_duration) {
520 if (!valve_number.has_value() || !run_duration.has_value()) {
521 return;
522 }
523 if (!this->is_a_valid_valve(valve_number.value())) {
524 return;
525 }
526 this->valve_[valve_number.value()].run_duration = run_duration.value();
527 if (this->valve_[valve_number.value()].run_duration_number == nullptr) {
528 return;
529 }
530 if (this->valve_[valve_number.value()].run_duration_number->state == run_duration.value()) {
531 return;
532 }
533 auto call = this->valve_[valve_number.value()].run_duration_number->make_call();
534 if (this->valve_[valve_number.value()].run_duration_number->get_unit_of_measurement_ref() == MIN_STR) {
535 call.set_value(run_duration.value() / 60.0);
536 } else {
537 call.set_value(run_duration.value());
538 }
539 call.perform();
540}
541
542void Sprinkler::set_auto_advance(const bool auto_advance) {
543 if (this->auto_adv_sw_ == nullptr) {
544 return;
545 }
546 if (this->auto_adv_sw_->state == auto_advance) {
547 return;
548 }
549 if (auto_advance) {
550 this->auto_adv_sw_->turn_on();
551 } else {
552 this->auto_adv_sw_->turn_off();
553 }
554}
555
556void Sprinkler::set_repeat(optional<uint32_t> repeat) {
557 this->target_repeats_ = repeat;
558 if (this->repeat_number_ == nullptr) {
559 return;
560 }
561 if (this->repeat_number_->state == repeat.value_or(0)) {
562 return;
563 }
564 auto call = this->repeat_number_->make_call();
565 call.set_value(repeat.value_or(0));
566 call.perform();
567}
568
569void Sprinkler::set_queue_enable(bool queue_enable) {
570 if (this->queue_enable_sw_ == nullptr) {
571 return;
572 }
573 if (this->queue_enable_sw_->state == queue_enable) {
574 return;
575 }
576 if (queue_enable) {
577 this->queue_enable_sw_->turn_on();
578 } else {
579 this->queue_enable_sw_->turn_off();
580 }
581}
582
583void Sprinkler::set_reverse(const bool reverse) {
584 if (this->reverse_sw_ == nullptr) {
585 return;
586 }
587 if (this->reverse_sw_->state == reverse) {
588 return;
589 }
590 if (reverse) {
591 this->reverse_sw_->turn_on();
592 } else {
593 this->reverse_sw_->turn_off();
594 }
595}
596
597void Sprinkler::set_standby(const bool standby) {
598 if (this->standby_sw_ == nullptr) {
599 return;
600 }
601 if (this->standby_sw_->state == standby) {
602 return;
603 }
604 if (standby) {
605 this->standby_sw_->turn_on();
606 } else {
607 this->standby_sw_->turn_off();
608 }
609}
610
611uint32_t Sprinkler::valve_run_duration(const size_t valve_number) {
612 if (!this->is_a_valid_valve(valve_number)) {
613 return 0;
614 }
615 if (this->valve_[valve_number].run_duration_number != nullptr) {
616 if (this->valve_[valve_number].run_duration_number->get_unit_of_measurement_ref() == MIN_STR) {
617 return static_cast<uint32_t>(roundf(this->valve_[valve_number].run_duration_number->state * 60));
618 } else {
619 return static_cast<uint32_t>(roundf(this->valve_[valve_number].run_duration_number->state));
620 }
621 }
622 return this->valve_[valve_number].run_duration;
623}
624
626 uint32_t run_duration = 0;
627
628 if (this->is_a_valid_valve(valve_number)) {
629 run_duration = this->valve_run_duration(valve_number);
630 }
631 run_duration = static_cast<uint32_t>(roundf(run_duration * this->multiplier()));
632 // run_duration must not be less than any of these
633 if ((run_duration < this->start_delay_) || (run_duration < this->stop_delay_) ||
634 (run_duration < this->switching_delay_.value_or(0) * 2)) {
635 return std::max({this->switching_delay_.value_or(0) * 2, this->start_delay_, this->stop_delay_});
636 }
637 return run_duration;
638}
639
641 if (this->auto_adv_sw_ != nullptr) {
642 return this->auto_adv_sw_->state;
643 }
644 return true;
645}
646
648 if (this->multiplier_number_ != nullptr) {
649 return this->multiplier_number_->state;
650 }
651 return this->multiplier_;
652}
653
654optional<uint32_t> Sprinkler::repeat() {
655 if (this->repeat_number_ != nullptr) {
656 return static_cast<uint32_t>(roundf(this->repeat_number_->state));
657 }
658 return this->target_repeats_;
659}
660
661optional<uint32_t> Sprinkler::repeat_count() {
662 // if there is an active valve and auto-advance is enabled, we may be repeating, so return the count
663 if (this->active_req_.has_request() && this->auto_advance()) {
664 return this->repeat_count_;
665 }
666 return nullopt;
667}
668
670 if (this->queue_enable_sw_ != nullptr) {
671 return this->queue_enable_sw_->state;
672 }
673 return true;
674}
675
677 if (this->reverse_sw_ != nullptr) {
678 return this->reverse_sw_->state;
679 }
680 return false;
681}
682
684 if (this->standby_sw_ != nullptr) {
685 return this->standby_sw_->state;
686 }
687 return false;
688}
689
691 if (this->standby()) {
692 this->log_standby_warning_(LOG_STR("start_from_queue"));
693 return;
694 }
695 if (this->multiplier() == 0) {
696 this->log_multiplier_zero_warning_(LOG_STR("start_from_queue"));
697 return;
698 }
699 if (this->queued_valves_.empty()) {
700 return; // if there is nothing in the queue, don't do anything
701 }
702 if (this->queue_enabled() && this->active_valve().has_value()) {
703 return; // if there is already a valve running from the queue, do nothing
704 }
705
706 this->set_auto_advance(false);
707 this->set_queue_enable(true);
708
709 this->reset_cycle_states_(); // just in case auto-advance is switched on later
710 this->repeat_count_ = 0;
711 this->fsm_kick_(); // will automagically pick up from the queue (it has priority)
712}
713
715 if (this->standby()) {
716 this->log_standby_warning_(LOG_STR("start_full_cycle"));
717 return;
718 }
719 if (this->multiplier() == 0) {
720 this->log_multiplier_zero_warning_(LOG_STR("start_full_cycle"));
721 return;
722 }
723 if (this->auto_advance() && this->active_valve().has_value()) {
724 return; // if auto-advance is already enabled and there is already a valve running, do nothing
725 }
726
727 this->set_queue_enable(false);
728
729 this->prep_full_cycle_();
730 this->repeat_count_ = 0;
731 // if there is no active valve already, start the first valve in the cycle
732 if (!this->active_req_.has_request()) {
733 this->fsm_kick_();
734 }
735}
736
737void Sprinkler::start_single_valve(const optional<size_t> valve_number, optional<uint32_t> run_duration) {
738 if (this->standby()) {
739 this->log_standby_warning_(LOG_STR("start_single_valve"));
740 return;
741 }
742 if (this->multiplier() == 0) {
743 this->log_multiplier_zero_warning_(LOG_STR("start_single_valve"));
744 return;
745 }
746 if (!valve_number.has_value() || (valve_number == this->active_valve())) {
747 return;
748 }
749
750 this->set_auto_advance(false);
751 this->set_queue_enable(false);
752
753 this->reset_cycle_states_(); // just in case auto-advance is switched on later
754 this->repeat_count_ = 0;
755 this->fsm_request_(valve_number.value(), run_duration.value_or(0));
756}
757
758void Sprinkler::queue_valve(optional<size_t> valve_number, optional<uint32_t> run_duration) {
759 if (valve_number.has_value()) {
760 if (this->is_a_valid_valve(valve_number.value()) && (this->queued_valves_.size() < this->max_queue_size_)) {
761 SprinklerQueueItem item{valve_number.value(), run_duration.value_or(0)};
762 this->queued_valves_.insert(this->queued_valves_.begin(), item);
763 ESP_LOGD(TAG, "Valve %zu placed into queue with run duration of %" PRIu32 " seconds", valve_number.value_or(0),
764 run_duration.value_or(0));
765 }
766 }
767}
768
770 this->queued_valves_.clear();
771 ESP_LOGD(TAG, "Queue cleared");
772}
773
775 if (this->standby()) {
776 this->log_standby_warning_(LOG_STR("next_valve"));
777 return;
778 }
779
780 if (this->state_ == IDLE) {
781 this->reset_cycle_states_(); // just in case auto-advance is switched on later
782 }
783
784 this->manual_valve_ = this->next_valve_number_(
785 this->manual_valve_.value_or(this->active_req_.valve_as_opt().value_or(this->number_of_valves() - 1)),
786 !this->next_prev_ignore_disabled_, true);
787
788 if (!this->manual_valve_.has_value()) {
789 ESP_LOGD(TAG, "next_valve was called but no valve could be started; perhaps next_prev_ignore_disabled allows only "
790 "enabled valves and no valves are enabled?");
791 return;
792 }
793
794 if (this->manual_selection_delay_.has_value()) {
797 } else {
798 this->fsm_request_(this->manual_valve_.value());
799 }
800}
801
803 if (this->standby()) {
804 this->log_standby_warning_(LOG_STR("previous_valve"));
805 return;
806 }
807
808 if (this->state_ == IDLE) {
809 this->reset_cycle_states_(); // just in case auto-advance is switched on later
810 }
811
812 this->manual_valve_ =
813 this->previous_valve_number_(this->manual_valve_.value_or(this->active_req_.valve_as_opt().value_or(0)),
814 !this->next_prev_ignore_disabled_, true);
815
816 if (!this->manual_valve_.has_value()) {
817 ESP_LOGD(TAG, "previous_valve was called but no valve could be started; perhaps next_prev_ignore_disabled allows "
818 "only enabled valves and no valves are enabled?");
819 return;
820 }
821
822 if (this->manual_selection_delay_.has_value()) {
825 } else {
826 this->fsm_request_(this->manual_valve_.value());
827 }
828}
829
830void Sprinkler::shutdown(bool clear_queue) {
832 this->active_req_.reset();
833 this->manual_valve_.reset();
834 this->next_req_.reset();
835 for (auto &vo : this->valve_op_) {
836 vo.stop();
837 }
839 if (clear_queue) {
840 this->clear_queued_valves();
841 this->repeat_count_ = 0;
842 }
843}
844
846 if (this->paused_valve_.has_value() || !this->active_req_.has_request()) {
847 return; // we can't pause if we're already paused or if there is no active valve
848 }
849 this->paused_valve_ = this->active_valve();
851 this->shutdown(false);
852 ESP_LOGD(TAG, "Paused valve %zu with %" PRIu32 " seconds remaining", this->paused_valve_.value_or(0),
853 this->resume_duration_.value_or(0));
854}
855
857 if (this->standby()) {
858 this->log_standby_warning_(LOG_STR("resume"));
859 return;
860 }
861
862 if (this->paused_valve_.has_value() && (this->resume_duration_.has_value())) {
863 const size_t paused_valve = *this->paused_valve_;
864 const uint32_t resume_duration = *this->resume_duration_;
865 // Resume only if valve has not been completed yet
866 if (!this->valve_cycle_complete_(paused_valve)) {
867 ESP_LOGD(TAG, "Resuming valve %zu with %" PRIu32 " seconds remaining", paused_valve, resume_duration);
868 this->fsm_request_(paused_valve, resume_duration);
869 }
870 this->reset_resume();
871 } else {
872 ESP_LOGD(TAG, "No valve to resume!");
873 }
874}
875
877 if (this->paused_valve_.has_value() && (this->resume_duration_.has_value())) {
878 this->resume();
879 } else {
880 this->start_full_cycle();
881 }
882}
883
885 this->paused_valve_.reset();
886 this->resume_duration_.reset();
887}
888
889const char *Sprinkler::valve_name(const size_t valve_number) {
890 if (this->is_a_valid_valve(valve_number)) {
891 return this->valve_[valve_number].controller_switch->get_name().c_str();
892 }
893 return nullptr;
894}
895
896optional<SprinklerValveRunRequestOrigin> Sprinkler::active_valve_request_is_from() {
897 if (this->active_req_.has_request()) {
898 return this->active_req_.request_is_from();
899 }
900 return nullopt;
901}
902
903optional<size_t> Sprinkler::active_valve() {
904 if (!this->valve_overlap_ && this->prev_req_.has_request() && this->prev_req_.has_valve_operator() &&
905 (this->prev_req_.valve_operator()->state() == STARTING || this->prev_req_.valve_operator()->state() == ACTIVE)) {
906 return this->prev_req_.valve_as_opt();
907 }
908 return this->active_req_.valve_as_opt();
909}
910
911optional<size_t> Sprinkler::queued_valve() {
912 if (!this->queued_valves_.empty()) {
913 return this->queued_valves_.back().valve_number;
914 }
915 return nullopt;
916}
917
918bool Sprinkler::is_a_valid_valve(const size_t valve_number) { return (valve_number < this->number_of_valves()); }
919
921 if (pump_switch == nullptr) {
922 return false; // we can't do anything if there's nothing to check
923 }
924 // a pump must be considered "in use" if a (distribution) valve it supplies is active. this means:
925 // - at least one SprinklerValveOperator:
926 // - has a valve loaded that depends on this pump
927 // - is in a state that depends on the pump: (ACTIVE and _possibly_ STARTING/STOPPING)
928 // - if NO SprinklerValveOperator is active but there is a run request pending (active_req_.has_request()) and the
929 // controller state is STARTING, valve open delay is configured but NOT pump_switch_off_during_valve_open_delay_
930 for (auto &vo : this->valve_op_) { // first, check if any SprinklerValveOperator has a valve dependent on this pump
931 if ((vo.state() != BYPASS) && (vo.pump_switch() != nullptr)) {
932 // the SprinklerValveOperator is configured with a pump; now check if it is the pump of interest
933 if (vo.pump_switch() == pump_switch) {
934 // now if the SprinklerValveOperator has a pump and it is either ACTIVE, is STARTING with a valve delay or
935 // is STOPPING with a valve delay, its pump can be considered "in use", so just return indicating this now
936 if ((vo.state() == ACTIVE) ||
937 ((vo.state() == STARTING) && this->start_delay_ && this->start_delay_is_valve_delay_) ||
938 ((vo.state() == STOPPING) && this->stop_delay_ && this->stop_delay_is_valve_delay_)) {
939 return true;
940 }
941 }
942 }
943 } // if we end up here, no SprinklerValveOperator was in a "give-away" state indicating that the pump is in use...
944 if (!this->valve_overlap_ && !this->pump_switch_off_during_valve_open_delay_ && this->switching_delay_.has_value() &&
945 this->active_req_.has_request() && (this->state_ != STOPPING)) {
946 // ...the controller is configured to keep the pump on during a valve open delay, so just return
947 // whether or not the next valve shares the same pump
948 auto *valve_pump = this->valve_pump_switch(this->active_req_.valve());
949 if (valve_pump == nullptr) {
950 return false; // valve has no pump, so this pump isn't in use by it
951 }
952 return pump_switch == valve_pump;
953 }
954 return false;
955}
956
958 if (pump_switch == nullptr) {
959 return; // we can't do anything if there's nothing to check
960 }
961
962 bool hold_pump_on = false;
963
964 for (auto &controller : this->other_controllers_) { // check if the pump is in use by another controller
965 if (controller != this) { // dummy check
966 if (controller->pump_in_use(pump_switch)) {
967 hold_pump_on = true; // if another controller says it's using this pump, keep it on
968 }
969 }
970 }
971 if (hold_pump_on) {
972 ESP_LOGD(TAG, "Leaving pump on because another controller instance is using it");
973 }
974
975 if (state) { // ...and now we can set the new state of the switch
976 pump_switch->turn_on();
977 } else if (!hold_pump_on && !this->pump_in_use(pump_switch)) {
978 pump_switch->turn_off();
979 }
980}
981
983 uint32_t total_time_remaining = 0;
984
985 for (size_t valve = 0; valve < this->number_of_valves(); valve++) {
986 total_time_remaining += this->valve_run_duration_adjusted(valve);
987 }
988
989 if (this->valve_overlap_) {
990 total_time_remaining -= this->switching_delay_.value_or(0) * (this->number_of_valves() - 1);
991 } else {
992 total_time_remaining += this->switching_delay_.value_or(0) * (this->number_of_valves() - 1);
993 }
994
995 return total_time_remaining;
996}
997
999 uint32_t total_time_remaining = 0;
1000 uint32_t valve_count = 0;
1001
1002 for (size_t valve = 0; valve < this->number_of_valves(); valve++) {
1003 if (this->valve_is_enabled_(valve)) {
1004 total_time_remaining += this->valve_run_duration_adjusted(valve);
1005 valve_count++;
1006 }
1007 }
1008
1009 if (valve_count) {
1010 if (this->valve_overlap_) {
1011 total_time_remaining -= this->switching_delay_.value_or(0) * (valve_count - 1);
1012 } else {
1013 total_time_remaining += this->switching_delay_.value_or(0) * (valve_count - 1);
1014 }
1015 }
1016
1017 return total_time_remaining;
1018}
1019
1021 uint32_t total_time_remaining = 0;
1022 uint32_t enabled_valve_count = 0;
1023 uint32_t incomplete_valve_count = 0;
1024
1025 for (size_t valve = 0; valve < this->number_of_valves(); valve++) {
1026 if (this->valve_is_enabled_(valve)) {
1027 enabled_valve_count++;
1028 if (!this->valve_cycle_complete_(valve)) {
1029 auto active = this->active_valve();
1030 if (!active.has_value() || (valve != *active)) {
1031 total_time_remaining += this->valve_run_duration_adjusted(valve);
1032 incomplete_valve_count++;
1033 } else {
1034 // to get here, there must be an active valve and this valve must be equal to 'valve'
1035 if (this->active_req_.valve_operator() == nullptr) { // no SVO has been assigned yet so it hasn't started
1036 total_time_remaining += this->valve_run_duration_adjusted(valve);
1037 incomplete_valve_count++;
1038 }
1039 }
1040 }
1041 }
1042 }
1043
1044 if (incomplete_valve_count > 0 && incomplete_valve_count >= enabled_valve_count) {
1045 incomplete_valve_count--;
1046 }
1047 if (incomplete_valve_count) {
1048 if (this->valve_overlap_) {
1049 total_time_remaining -= this->switching_delay_.value_or(0) * incomplete_valve_count;
1050 } else {
1051 total_time_remaining += this->switching_delay_.value_or(0) * incomplete_valve_count;
1052 }
1053 }
1054
1055 return total_time_remaining;
1056}
1057
1059 uint32_t total_time_remaining = 0;
1060 uint32_t valve_count = 0;
1061
1062 for (auto &valve : this->queued_valves_) {
1063 if (valve.run_duration) {
1064 total_time_remaining += valve.run_duration;
1065 } else {
1066 total_time_remaining += this->valve_run_duration_adjusted(valve.valve_number);
1067 }
1068 valve_count++;
1069 }
1070
1071 if (valve_count) {
1072 if (this->valve_overlap_) {
1073 total_time_remaining -= this->switching_delay_.value_or(0) * (valve_count - 1);
1074 } else {
1075 total_time_remaining += this->switching_delay_.value_or(0) * (valve_count - 1);
1076 }
1077 }
1078
1079 return total_time_remaining;
1080}
1081
1083 if (this->active_req_.has_request()) { // first try to return the value based on active_req_...
1084 if (this->active_req_.valve_operator() != nullptr) {
1085 return this->active_req_.valve_operator()->time_remaining();
1086 }
1087 }
1088 if (this->prev_req_.has_request()) { // try to return the value based on prev_req_...
1089 if (this->prev_req_.valve_operator() != nullptr) {
1090 return this->prev_req_.valve_operator()->time_remaining();
1091 }
1092 }
1093 return nullopt;
1094}
1095
1097 if (!this->time_remaining_active_valve().has_value() && this->state_ == IDLE) {
1098 return nullopt;
1099 }
1100
1101 auto total_time_remaining = this->time_remaining_active_valve().value_or(0);
1102 if (this->auto_advance()) {
1103 total_time_remaining += this->total_cycle_time_enabled_incomplete_valves();
1104 if (this->repeat().value_or(0) > 0) {
1105 total_time_remaining +=
1106 (this->total_cycle_time_enabled_valves() * (this->repeat().value_or(0) - this->repeat_count().value_or(0)));
1107 }
1108 }
1109
1110 if (this->queue_enabled()) {
1111 total_time_remaining += this->total_queue_time();
1112 }
1113 return total_time_remaining;
1114}
1115
1117 if (this->state_ != IDLE) {
1118 return true;
1119 }
1120
1121 for (auto &controller : this->other_controllers_) {
1122 if (controller != this) { // dummy check
1123 if (controller->controller_state() != IDLE) {
1124 return true;
1125 }
1126 }
1127 }
1128 return false;
1129}
1130
1132 if (this->is_a_valid_valve(valve_number)) {
1133 return this->valve_[valve_number].controller_switch;
1134 }
1135 return nullptr;
1136}
1137
1139 if (this->is_a_valid_valve(valve_number)) {
1140 return this->valve_[valve_number].enable_switch;
1141 }
1142 return nullptr;
1143}
1144
1145switch_::Switch *Sprinkler::valve_switch(const size_t valve_number) {
1146 if (this->is_a_valid_valve(valve_number)) {
1147 return this->valve_[valve_number].valve_switch;
1148 }
1149 return nullptr;
1150}
1151
1153 if (this->is_a_valid_valve(valve_number)) {
1154 auto idx = this->valve_[valve_number].pump_switch_index;
1155 if (idx.has_value()) {
1156 return this->pump_[*idx];
1157 }
1158 }
1159 return nullptr;
1160}
1161
1163 if (pump_index < this->pump_.size()) {
1164 return this->pump_[pump_index];
1165 }
1166 return nullptr;
1167}
1168
1169bool Sprinkler::valve_is_enabled_(const size_t valve_number) {
1170 if (this->is_a_valid_valve(valve_number)) {
1171 if (this->valve_[valve_number].enable_switch != nullptr) {
1172 return this->valve_[valve_number].enable_switch->state;
1173 } else {
1174 return true;
1175 }
1176 }
1177 return false;
1178}
1179
1180void Sprinkler::mark_valve_cycle_complete_(const size_t valve_number) {
1181 if (this->is_a_valid_valve(valve_number)) {
1182 ESP_LOGD(TAG, "Marking valve %u complete", valve_number);
1183 this->valve_[valve_number].valve_cycle_complete = true;
1184 }
1185}
1186
1187bool Sprinkler::valve_cycle_complete_(const size_t valve_number) {
1188 if (this->is_a_valid_valve(valve_number)) {
1189 return this->valve_[valve_number].valve_cycle_complete;
1190 }
1191 return false;
1192}
1193
1194optional<size_t> Sprinkler::next_valve_number_(const optional<size_t> first_valve, const bool include_disabled,
1195 const bool include_complete) {
1196 auto valve = first_valve.value_or(0);
1197 size_t start = first_valve.has_value() ? 1 : 0;
1198
1199 if (!this->is_a_valid_valve(valve)) {
1200 valve = 0;
1201 }
1202
1203 for (size_t offset = start; offset < this->number_of_valves(); offset++) {
1204 auto valve_of_interest = valve + offset;
1205 if (!this->is_a_valid_valve(valve_of_interest)) {
1206 valve_of_interest -= this->number_of_valves();
1207 }
1208
1209 if ((this->valve_is_enabled_(valve_of_interest) || include_disabled) &&
1210 (!this->valve_cycle_complete_(valve_of_interest) || include_complete)) {
1211 return valve_of_interest;
1212 }
1213 }
1214 return nullopt;
1215}
1216
1217optional<size_t> Sprinkler::previous_valve_number_(const optional<size_t> first_valve, const bool include_disabled,
1218 const bool include_complete) {
1219 auto valve = first_valve.value_or(this->number_of_valves() - 1);
1220 size_t start = first_valve.has_value() ? 1 : 0;
1221
1222 if (!this->is_a_valid_valve(valve)) {
1223 valve = this->number_of_valves() - 1;
1224 }
1225
1226 for (size_t offset = start; offset < this->number_of_valves(); offset++) {
1227 auto valve_of_interest = valve - offset;
1228 if (!this->is_a_valid_valve(valve_of_interest)) {
1229 valve_of_interest += this->number_of_valves();
1230 }
1231
1232 if ((this->valve_is_enabled_(valve_of_interest) || include_disabled) &&
1233 (!this->valve_cycle_complete_(valve_of_interest) || include_complete)) {
1234 return valve_of_interest;
1235 }
1236 }
1237 return nullopt;
1238}
1239
1240optional<size_t> Sprinkler::next_valve_number_in_cycle_(const optional<size_t> first_valve) {
1241 if (this->reverse()) {
1242 return this->previous_valve_number_(first_valve, false, false);
1243 }
1244 return this->next_valve_number_(first_valve, false, false);
1245}
1246
1247void Sprinkler::load_next_valve_run_request_(const optional<size_t> first_valve) {
1248 if (this->active_req_.has_request()) {
1249 this->prev_req_ = this->active_req_;
1250 } else {
1251 this->prev_req_.reset();
1252 }
1253
1254 if (this->next_req_.has_request()) {
1255 if (!this->next_req_.run_duration()) { // ensure the run duration is set correctly for consumption later on
1257 }
1258 return; // there is already a request pending
1259 } else if (this->queue_enabled() && !this->queued_valves_.empty()) {
1260 this->next_req_.set_valve(this->queued_valves_.back().valve_number);
1262 if (this->queued_valves_.back().run_duration) {
1263 this->next_req_.set_run_duration(this->queued_valves_.back().run_duration);
1264 this->queued_valves_.pop_back();
1265 } else if (this->multiplier()) {
1266 this->next_req_.set_run_duration(this->valve_run_duration_adjusted(this->queued_valves_.back().valve_number));
1267 this->queued_valves_.pop_back();
1268 } else {
1269 this->next_req_.reset();
1270 }
1271 } else if (this->auto_advance() && this->multiplier()) {
1272 if (this->next_valve_number_in_cycle_(first_valve).has_value()) {
1273 // if there is another valve to run as a part of a cycle, load that
1274 this->next_req_.set_valve(this->next_valve_number_in_cycle_(first_valve).value_or(0));
1277 this->valve_run_duration_adjusted(this->next_valve_number_in_cycle_(first_valve).value_or(0)));
1278 } else if ((this->repeat_count_++ < this->repeat().value_or(0))) {
1279 ESP_LOGD(TAG, "Repeating - starting cycle %" PRIu32 " of %" PRIu32, this->repeat_count_ + 1,
1280 this->repeat().value_or(0) + 1);
1281 // if there are repeats remaining and no more valves were left in the cycle, start a new cycle
1282 this->prep_full_cycle_();
1283 if (this->next_valve_number_in_cycle_().has_value()) { // this should always succeed here, but just in case...
1284 this->next_req_.set_valve(this->next_valve_number_in_cycle_().value_or(0));
1287 this->valve_run_duration_adjusted(this->next_valve_number_in_cycle_().value_or(0)));
1288 }
1289 }
1290 }
1291}
1292
1294 for (size_t valve_number = 0; valve_number < this->number_of_valves(); valve_number++) {
1295 if (this->valve_is_enabled_(valve_number))
1296 return true;
1297 }
1298 return false;
1299}
1300
1302 if (!req->has_request()) {
1303 return; // we can't do anything if the request contains nothing
1304 }
1305 if (!this->is_a_valid_valve(req->valve())) {
1306 return; // we can't do anything if the valve number isn't valid
1307 }
1308 // Enable loop to monitor valve operator states
1309 this->enable_loop();
1310 for (auto &vo : this->valve_op_) { // find the first available SprinklerValveOperator, load it and start it up
1311 if (vo.state() == IDLE) {
1312 auto run_duration = req->run_duration() ? req->run_duration() : this->valve_run_duration_adjusted(req->valve());
1313 ESP_LOGD(TAG, "%s is starting valve %zu for %" PRIu32 " seconds, cycle %" PRIu32 " of %" PRIu32,
1314 LOG_STR_ARG(this->req_as_str_(req->request_is_from())), req->valve(), run_duration,
1315 this->repeat_count_ + 1, this->repeat().value_or(0) + 1);
1316 req->set_valve_operator(&vo);
1317 vo.set_controller(this);
1318 vo.set_valve(&this->valve_[req->valve()]);
1319 vo.set_run_duration(run_duration);
1320 vo.set_start_delay(this->start_delay_, this->start_delay_is_valve_delay_);
1321 vo.set_stop_delay(this->stop_delay_, this->stop_delay_is_valve_delay_);
1322 vo.start();
1323 return;
1324 }
1325 }
1326}
1327
1328void Sprinkler::all_valves_off_(const bool include_pump) {
1329 for (size_t valve_index = 0; valve_index < this->number_of_valves(); valve_index++) {
1330 auto *valve_sw = this->valve_[valve_index].valve_switch;
1331 if ((valve_sw != nullptr) && valve_sw->state) {
1332 valve_sw->turn_off();
1333 }
1334 if (include_pump) {
1335 this->set_pump_state(this->valve_pump_switch(valve_index), false);
1336 }
1337 }
1338 ESP_LOGD(TAG, "All valves stopped%s", include_pump ? LOG_STR_LITERAL(", including pumps") : "");
1339}
1340
1342 this->set_auto_advance(true);
1343
1344 if (!this->any_valve_is_enabled_()) {
1345 for (auto &valve : this->valve_) {
1346 if (valve.enable_switch != nullptr) {
1347 if (!valve.enable_switch->state) {
1348 valve.enable_switch->turn_on();
1349 }
1350 }
1351 }
1352 }
1353 this->reset_cycle_states_();
1354}
1355
1357 for (auto &valve : this->valve_) {
1358 valve.valve_cycle_complete = false;
1359 }
1360}
1361
1362void Sprinkler::fsm_request_(size_t requested_valve, uint32_t requested_run_duration) {
1363 this->next_req_.set_valve(requested_valve);
1364 this->next_req_.set_run_duration(requested_run_duration);
1365 // if state is IDLE or ACTIVE, call fsm_transition_() to start it immediately;
1366 // otherwise, fsm_transition() will pick up next_req_ at the next appropriate transition
1367 this->fsm_kick_();
1368}
1369
1371 if ((this->state_ == IDLE) || (this->state_ == ACTIVE)) {
1372 this->fsm_transition_();
1373 }
1374}
1375
1377 ESP_LOGVV(TAG, "fsm_transition_ called; state is %s", LOG_STR_ARG(this->state_as_str_(this->state_)));
1378 switch (this->state_) {
1379 case IDLE: // the system was off -> start it up
1380 // advances to ACTIVE
1382 break;
1383
1384 case ACTIVE:
1385 // advances to STOPPING or ACTIVE (again)
1387 break;
1388
1389 case STARTING: {
1390 // follows valve open delay interval
1391 uint32_t timer_duration = this->active_req_.run_duration();
1392 if (timer_duration > this->switching_delay_.value_or(0)) {
1393 timer_duration -= this->switching_delay_.value_or(0);
1394 }
1395 this->set_timer_duration_(sprinkler::TIMER_SM, timer_duration);
1397 this->start_valve_(&this->active_req_);
1398 this->state_ = ACTIVE;
1399 if (this->next_req_.has_request()) {
1400 // another valve has been requested, so restart the timer so we pick it up quickly
1403 }
1404 break;
1405 }
1406
1407 case STOPPING:
1408 // stop_delay_ has elapsed so just shut everything off
1409 this->active_req_.reset();
1410 this->manual_valve_.reset();
1411 this->all_valves_off_(true);
1412 this->state_ = IDLE;
1413 break;
1414
1415 default:
1416 break;
1417 }
1418 if (this->next_req_.has_request() && (this->state_ == IDLE)) {
1419 // another valve has been requested, so restart the timer so we pick it up quickly
1422 }
1423 ESP_LOGVV(TAG, "fsm_transition_ complete; new state is %s", LOG_STR_ARG(this->state_as_str_(this->state_)));
1424}
1425
1428
1429 if (this->next_req_.has_request()) { // there is a valve to run...
1430 this->active_req_.set_valve(this->next_req_.valve());
1433 this->next_req_.reset();
1434
1435 uint32_t timer_duration = this->active_req_.run_duration();
1436 if (timer_duration > this->switching_delay_.value_or(0)) {
1437 timer_duration -= this->switching_delay_.value_or(0);
1438 }
1439 this->set_timer_duration_(sprinkler::TIMER_SM, timer_duration);
1441 this->start_valve_(&this->active_req_);
1442 this->state_ = ACTIVE;
1443 }
1444}
1445
1447 if (!this->active_req_.has_request()) { // dummy check...
1449 return;
1450 }
1451
1452 if (!this->timer_active_(sprinkler::TIMER_SM)) { // only flag the valve as "complete" if the timer finished
1453 if ((this->active_req_.request_is_from() == CYCLE) || (this->active_req_.request_is_from() == USER)) {
1455 }
1456 } else {
1457 ESP_LOGD(TAG, "Valve cycle interrupted - NOT flagging valve as complete and stopping current valve");
1458 for (auto &vo : this->valve_op_) {
1459 vo.stop();
1460 }
1461 }
1462
1464
1465 if (this->next_req_.has_request()) { // there is another valve to run...
1466 auto *active_pump = this->valve_pump_switch(this->active_req_.valve());
1467 auto *next_pump = this->valve_pump_switch(this->next_req_.valve());
1468 bool same_pump = (active_pump != nullptr) && (next_pump != nullptr) && (active_pump == next_pump);
1469
1470 this->active_req_.set_valve(this->next_req_.valve());
1473 this->next_req_.reset();
1474
1475 // this->state_ = ACTIVE; // state isn't changing
1476 if (this->valve_overlap_ || !this->switching_delay_.has_value()) {
1477 uint32_t timer_duration = this->active_req_.run_duration();
1478 if (timer_duration > this->switching_delay_.value_or(0)) {
1479 timer_duration -= this->switching_delay_.value_or(0);
1480 }
1481 this->set_timer_duration_(sprinkler::TIMER_SM, timer_duration);
1483 this->start_valve_(&this->active_req_);
1484 } else {
1485 this->set_timer_duration_(
1487 this->switching_delay_.value() * 2 +
1488 (this->pump_switch_off_during_valve_open_delay_ && same_pump ? this->stop_delay_ : 0));
1490 this->state_ = STARTING;
1491 }
1492 } else { // there is NOT another valve to run...
1494 }
1495}
1496
1503
1504void Sprinkler::log_standby_warning_(const LogString *method_name) {
1505 ESP_LOGW(TAG, "%s called but standby is enabled; no action taken", LOG_STR_ARG(method_name));
1506}
1507
1508void Sprinkler::log_multiplier_zero_warning_(const LogString *method_name) {
1509 ESP_LOGW(TAG, "%s called but multiplier is set to zero; no action taken", LOG_STR_ARG(method_name));
1510}
1511
1512// Request origin strings indexed by SprinklerValveRunRequestOrigin enum (0-2): USER, CYCLE, QUEUE
1513PROGMEM_STRING_TABLE(SprinklerRequestOriginStrings, "USER", "CYCLE", "QUEUE", "UNKNOWN");
1514
1516 return SprinklerRequestOriginStrings::get_log_str(static_cast<uint8_t>(origin),
1517 SprinklerRequestOriginStrings::LAST_INDEX);
1518}
1519
1520// Sprinkler state strings indexed by SprinklerState enum (0-4): IDLE, STARTING, ACTIVE, STOPPING, BYPASS
1521PROGMEM_STRING_TABLE(SprinklerStateStrings, "IDLE", "STARTING", "ACTIVE", "STOPPING", "BYPASS", "UNKNOWN");
1522
1524 return SprinklerStateStrings::get_log_str(static_cast<uint8_t>(state), SprinklerStateStrings::LAST_INDEX);
1525}
1526
1528 if (this->timer_duration_(timer_index) > 0) {
1529 this->set_timeout(this->timer_[timer_index].name, this->timer_duration_(timer_index),
1530 this->timer_cbf_(timer_index));
1531 this->timer_[timer_index].start_time = millis();
1532 this->timer_[timer_index].active = true;
1533 }
1534 ESP_LOGVV(TAG, "Timer %zu started for %" PRIu32 " sec", static_cast<size_t>(timer_index),
1535 this->timer_duration_(timer_index) / 1000);
1536}
1537
1539 this->timer_[timer_index].active = false;
1540 return this->cancel_timeout(this->timer_[timer_index].name);
1541}
1542
1543bool Sprinkler::timer_active_(const SprinklerTimerIndex timer_index) { return this->timer_[timer_index].active; }
1544
1546 this->timer_[timer_index].time = 1000 * time;
1547}
1548
1549uint32_t Sprinkler::timer_duration_(const SprinklerTimerIndex timer_index) { return this->timer_[timer_index].time; }
1550
1551std::function<void()> Sprinkler::timer_cbf_(const SprinklerTimerIndex timer_index) {
1552 return this->timer_[timer_index].func;
1553}
1554
1556 this->timer_[sprinkler::TIMER_VALVE_SELECTION].active = false;
1557 ESP_LOGVV(TAG, "Valve selection timer expired");
1558 if (this->manual_valve_.has_value()) {
1559 this->fsm_request_(this->manual_valve_.value());
1560 this->manual_valve_.reset();
1561 }
1562}
1563
1565 this->timer_[sprinkler::TIMER_SM].active = false;
1566 ESP_LOGVV(TAG, "State machine timer expired");
1567 this->fsm_transition_();
1568}
1569
1571 ESP_LOGCONFIG(TAG, "Sprinkler Controller -- %s", this->name_);
1572 if (this->manual_selection_delay_.has_value()) {
1573 ESP_LOGCONFIG(TAG, " Manual Selection Delay: %" PRIu32 " seconds", this->manual_selection_delay_.value_or(0));
1574 }
1575 if (this->repeat().has_value()) {
1576 ESP_LOGCONFIG(TAG, " Repeat Cycles: %" PRIu32 " times", this->repeat().value_or(0));
1577 }
1578 if (this->start_delay_) {
1579 if (this->start_delay_is_valve_delay_) {
1580 ESP_LOGCONFIG(TAG, " Pump Start Valve Delay: %" PRIu32 " seconds", this->start_delay_);
1581 } else {
1582 ESP_LOGCONFIG(TAG, " Pump Start Pump Delay: %" PRIu32 " seconds", this->start_delay_);
1583 }
1584 }
1585 if (this->stop_delay_) {
1586 if (this->stop_delay_is_valve_delay_) {
1587 ESP_LOGCONFIG(TAG, " Pump Stop Valve Delay: %" PRIu32 " seconds", this->stop_delay_);
1588 } else {
1589 ESP_LOGCONFIG(TAG, " Pump Stop Pump Delay: %" PRIu32 " seconds", this->stop_delay_);
1590 }
1591 }
1592 if (this->switching_delay_.has_value()) {
1593 if (this->valve_overlap_) {
1594 ESP_LOGCONFIG(TAG, " Valve Overlap: %" PRIu32 " seconds", this->switching_delay_.value_or(0));
1595 } else {
1596 ESP_LOGCONFIG(TAG,
1597 " Valve Open Delay: %" PRIu32 " seconds\n"
1598 " Pump Switch Off During Valve Open Delay: %s",
1599 this->switching_delay_.value_or(0), YESNO(this->pump_switch_off_during_valve_open_delay_));
1600 }
1601 }
1602 for (size_t valve_number = 0; valve_number < this->number_of_valves(); valve_number++) {
1603 ESP_LOGCONFIG(TAG,
1604 " Valve %zu:\n"
1605 " Name: %s\n"
1606 " Run Duration: %" PRIu32 " seconds",
1607 valve_number, this->valve_name(valve_number), this->valve_run_duration(valve_number));
1608 }
1609 if (!this->pump_.empty()) {
1610 ESP_LOGCONFIG(TAG, " Total number of pumps: %zu", this->pump_.size());
1611 }
1612 if (!this->valve_.empty()) {
1613 ESP_LOGCONFIG(TAG, " Total number of valves: %zu", this->valve_.size());
1614 }
1615}
1616
1617} // namespace esphome::sprinkler
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
bool cancel_timeout(const char *name)
Cancel a timeout function.
void enable_loop()
Enable this component's loop.
Definition component.h:246
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
Definition component.cpp:96
void disable_loop()
Disable this component's loop.
ESPPreferenceObject make_entity_preference(uint32_t version=0)
Create a preference object for storing this entity's state/settings.
void stop_action()
Stop any action connected to this trigger.
Definition automation.h:469
void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE
Inform the parent automation that the event has triggered.
Definition automation.h:461
NumberCall & set_value(float value)
NumberCall make_call()
Definition number.h:35
void publish_state(float state)
Definition number.cpp:22
NumberTraits traits
Definition number.h:41
void set_state_lambda(std::function< optional< bool >()> &&f)
Definition sprinkler.cpp:69
optional< std::function< optional< bool >()> > f_
Definition sprinkler.h:109
void set_controller_standby_switch(SprinklerControllerSwitch *standby_switch)
void add_controller(Sprinkler *other_controller)
add another controller to the controller so it can check if pumps/main valves are in use
FixedVector< SprinklerTimer > timer_
Valve control timers - FixedVector enforces that this can never grow beyond init() size.
Definition sprinkler.h:568
void resume()
resumes a cycle that was suspended using pause()
void fsm_transition_to_shutdown_()
starts up the system from IDLE state
optional< size_t > previous_valve_number_(optional< size_t > first_valve=nullopt, bool include_disabled=true, bool include_complete=true)
returns the number of the previous valve in the vector or nullopt if no valves match criteria
bool is_a_valid_valve(size_t valve_number)
returns true if valve number is valid
bool reverse()
returns true if reverse is enabled
void reset_resume()
resets resume state
SprinklerControllerNumber * repeat_number_
Definition sprinkler.h:582
bool valve_is_enabled_(size_t valve_number)
returns true if valve number is enabled
SprinklerState state_
Sprinkler controller state.
Definition sprinkler.h:520
void next_valve()
advances to the next valve (numerically)
void log_standby_warning_(const LogString *method_name)
log error message when a method is called but standby is enabled
uint32_t repeat_count_
Number of times the full cycle has been repeated.
Definition sprinkler.h:550
void configure_valve_switch(size_t valve_number, switch_::Switch *valve_switch, uint32_t run_duration)
configure a valve's switch object and run duration. run_duration is time in seconds.
std::vector< SprinklerValve > valve_
Sprinkler valve objects.
Definition sprinkler.h:562
void queue_valve(optional< size_t > valve_number, optional< uint32_t > run_duration)
adds a valve into the queue.
void set_valve_run_duration(optional< size_t > valve_number, optional< uint32_t > run_duration)
set how long the valve should remain on/open. run_duration is time in seconds
optional< uint32_t > resume_duration_
Set from time_remaining() when paused.
Definition sprinkler.h:541
void set_repeat(optional< uint32_t > repeat)
set the number of times to repeat a full cycle
float multiplier()
returns the current value of the multiplier
bool auto_advance()
returns true if auto_advance is enabled
uint32_t total_queue_time()
returns the amount of time in seconds required for all valves in the queue
void fsm_transition_from_shutdown_()
starts up the system from IDLE state
optional< uint32_t > repeat_count()
if a cycle is active, returns the number of times the controller has repeated the cycle....
void all_valves_off_(bool include_pump=false)
turns off/closes all valves, including pump if include_pump is true
SprinklerValveRunRequest prev_req_
The previous run request the controller processed.
Definition sprinkler.h:529
void start_valve_(SprinklerValveRunRequest *req)
loads an available SprinklerValveOperator (valve_op_) based on req and starts it (switches it on).
optional< uint32_t > repeat()
returns the number of times the controller is set to repeat cycles, if at all. check with 'has_value(...
void mark_valve_cycle_complete_(size_t valve_number)
marks a valve's cycle as complete
void set_pump_stop_delay(uint32_t stop_delay)
set how long the pump should stop after the valve (when the pump is starting)
void log_multiplier_zero_warning_(const LogString *method_name)
log error message when a method is called but multiplier is zero
optional< size_t > paused_valve()
returns the number of the valve that is paused, if any. check with 'has_value()'
Definition sprinkler.h:348
uint32_t start_delay_
Pump start/stop delay intervals.
Definition sprinkler.h:514
const LogString * req_as_str_(SprinklerValveRunRequestOrigin origin)
return the specified SprinklerValveRunRequestOrigin as a string
void set_valve_overlap(uint32_t valve_overlap)
set how long the controller should wait after opening a valve before closing the previous valve
optional< SprinklerValveRunRequestOrigin > active_valve_request_is_from()
returns what invoked the valve that is currently active, if any. check with 'has_value()'
void set_reverse(bool reverse)
if reverse is true, controller will iterate through all enabled valves in reverse (descending) order
SprinklerControllerNumber * multiplier_number_
Number components we'll present to the front end.
Definition sprinkler.h:581
std::vector< SprinklerValveOperator > valve_op_
Sprinkler valve operator objects.
Definition sprinkler.h:565
SprinklerControllerSwitch * enable_switch(size_t valve_number)
returns a pointer to a valve's enable switch object
void start_full_cycle()
starts a full cycle of all enabled valves and enables auto_advance.
SprinklerControllerSwitch * controller_sw_
Definition sprinkler.h:575
void set_valve_stop_delay(uint32_t stop_delay)
set how long the valve should stop after the pump (when the pump is stopping)
void set_pump_state(switch_::Switch *pump_switch, bool state)
switches on/off a pump "safely" by checking that the new state will not conflict with another control...
optional< uint32_t > target_repeats_
Set the number of times to repeat a full cycle.
Definition sprinkler.h:538
void set_controller_main_switch(SprinklerControllerSwitch *controller_switch)
configure important controller switches
switch_::Switch * valve_switch(size_t valve_number)
returns a pointer to a valve's switch object
optional< uint32_t > time_remaining_current_operation()
returns the amount of time remaining in seconds for all valves remaining, including the active valve,...
const LogString * state_as_str_(SprinklerState state)
return the specified SprinklerState state as a string
SprinklerControllerSwitch * reverse_sw_
Definition sprinkler.h:577
optional< uint32_t > switching_delay_
Valve switching delay.
Definition sprinkler.h:547
void fsm_kick_()
kicks the state machine to advance, starting it if it is not already active
void shutdown(bool clear_queue=false)
turns off all valves, effectively shutting down the system.
SprinklerValveRunRequest active_req_
The valve run request that is currently active.
Definition sprinkler.h:523
bool any_controller_is_active()
returns true if this or any sprinkler controller this controller knows about is active
void set_valve_open_delay(uint32_t valve_open_delay)
set how long the controller should wait to open/switch on the valve after it becomes active
std::vector< switch_::Switch * > pump_
Sprinkler valve pump switches.
Definition sprinkler.h:559
bool start_delay_is_valve_delay_
Pump start/stop delay interval types.
Definition sprinkler.h:510
optional< size_t > manual_valve_
The number of the manually selected valve currently selected.
Definition sprinkler.h:532
bool pump_switch_off_during_valve_open_delay_
Pump should be off during valve_open_delay interval.
Definition sprinkler.h:504
bool cancel_timer_(SprinklerTimerIndex timer_index)
SprinklerControllerSwitch * queue_enable_sw_
Definition sprinkler.h:576
void start_timer_(SprinklerTimerIndex timer_index)
Start/cancel/get status of valve timers.
void previous_valve()
advances to the previous valve (numerically)
switch_::Switch * valve_pump_switch_by_pump_index(size_t pump_index)
returns a pointer to a valve's pump switch object
void set_auto_advance(bool auto_advance)
if auto_advance is true, controller will iterate through all enabled valves
void prep_full_cycle_()
prepares for a full cycle by verifying auto-advance is on as well as one or more valve enable switche...
uint32_t valve_run_duration_adjusted(size_t valve_number)
returns valve_number's run duration (in seconds) adjusted by multiplier_
std::unique_ptr< ShutdownAction<> > sprinkler_shutdown_action_
Definition sprinkler.h:584
std::unique_ptr< ResumeOrStartAction<> > sprinkler_resumeorstart_action_
Definition sprinkler.h:586
void clear_queued_valves()
clears/removes all valves from the queue
bool any_valve_is_enabled_()
returns true if any valve is enabled
size_t number_of_valves()
returns the number of valves the controller is configured with
Definition sprinkler.h:358
void set_pump_start_delay(uint32_t start_delay)
set how long the pump should start after the valve (when the pump is starting)
void set_standby(bool standby)
if standby is true, controller will refuse to activate any valves
std::vector< SprinklerQueueItem > queued_valves_
Queue of valves to activate next, regardless of auto-advance.
Definition sprinkler.h:556
bool queue_enabled()
returns true if the queue is enabled to run
void resume_or_start_full_cycle()
if a cycle was suspended using pause(), resumes it. otherwise calls start_full_cycle()
std::unique_ptr< Automation<> > sprinkler_turn_on_automation_
Definition sprinkler.h:589
void valve_selection_callback_()
callback functions for timers
optional< size_t > next_valve_number_(optional< size_t > first_valve=nullopt, bool include_disabled=true, bool include_complete=true)
returns the number of the next valve in the vector or nullopt if no valves match criteria
uint32_t total_cycle_time_all_valves()
returns the amount of time in seconds required for all valves
std::unique_ptr< Automation<> > sprinkler_turn_off_automation_
Definition sprinkler.h:588
optional< uint32_t > time_remaining_active_valve()
returns the amount of time remaining in seconds for the active valve, if any
optional< size_t > next_valve_number_in_cycle_(optional< size_t > first_valve=nullopt)
returns the number of the next valve that should be activated in a full cycle.
std::function< void()> timer_cbf_(SprinklerTimerIndex timer_index)
std::unique_ptr< ShutdownAction<> > sprinkler_standby_shutdown_action_
Definition sprinkler.h:585
optional< uint32_t > manual_selection_delay_
Manual switching delay.
Definition sprinkler.h:544
void start_single_valve(optional< size_t > valve_number, optional< uint32_t > run_duration=nullopt)
activates a single valve and disables auto_advance.
uint32_t valve_run_duration(size_t valve_number)
returns valve_number's run duration in seconds
void load_next_valve_run_request_(optional< size_t > first_valve=nullopt)
loads next_req_ with the next valve that should be activated, including its run duration.
void fsm_transition_()
advance controller state, advancing to target_valve if provided
void fsm_request_(size_t requested_valve, uint32_t requested_run_duration=0)
make a request of the state machine
optional< size_t > paused_valve_
The number of the valve to resume from (if paused)
Definition sprinkler.h:535
SprinklerValveRunRequest next_req_
The next run request for the controller to consume after active_req_ is complete.
Definition sprinkler.h:526
void configure_valve_run_duration_number(size_t valve_number, SprinklerControllerNumber *run_duration_number)
configure a valve's run duration number component
SprinklerControllerSwitch * auto_adv_sw_
Switches we'll present to the front end.
Definition sprinkler.h:574
void set_manual_selection_delay(uint32_t manual_selection_delay)
set how long the controller should wait to activate a valve after next_valve() or previous_valve() is...
uint32_t total_cycle_time_enabled_incomplete_valves()
returns the amount of time in seconds required for all enabled & incomplete valves,...
bool valve_overlap_
Sprinkler valve cycle should overlap.
Definition sprinkler.h:507
switch_::Switch * valve_pump_switch(size_t valve_number)
returns a pointer to a valve's pump switch object
void set_divider(optional< uint32_t > divider)
sets the multiplier value to '1 / divider' and sets repeat value to divider
std::unique_ptr< Automation<> > sprinkler_standby_turn_on_automation_
Definition sprinkler.h:590
void set_valve_start_delay(uint32_t start_delay)
set how long the valve should start after the pump (when the pump is stopping)
void pause()
same as shutdown(), but also stores active_valve() and time_remaining() allowing resume() to continue...
optional< size_t > active_valve()
returns the number of the valve that is currently active, if any. check with 'has_value()'
void set_queue_enable(bool queue_enable)
if queue_enable is true, controller will iterate through valves in the queue
bool pump_in_use(switch_::Switch *pump_switch)
returns true if the pump the pointer points to is in use
bool timer_active_(SprinklerTimerIndex timer_index)
returns true if the specified timer is active/running
optional< size_t > queued_valve()
returns the number of the next valve in the queue, if any. check with 'has_value()'
std::vector< Sprinkler * > other_controllers_
Other Sprinkler instances we should be aware of (used to check if pumps are in use)
Definition sprinkler.h:571
const char * valve_name(size_t valve_number)
returns a pointer to a valve's name string object; returns nullptr if valve_number is invalid
void start_from_queue()
starts the controller from the first valve in the queue and disables auto_advance.
void configure_valve_pump_switch(size_t valve_number, switch_::Switch *pump_switch)
configure a valve's associated pump switch object
void reset_cycle_states_()
resets the cycle state for all valves
float multiplier_
Sprinkler valve run time multiplier value.
Definition sprinkler.h:553
SprinklerControllerSwitch * control_switch(size_t valve_number)
returns a pointer to a valve's control switch object
void set_multiplier(optional< float > multiplier)
value multiplied by configured run times – used to extend or shorten the cycle
void set_timer_duration_(SprinklerTimerIndex timer_index, uint32_t time)
time is converted to milliseconds (ms) for set_timeout()
void fsm_transition_from_valve_run_()
transitions from ACTIVE state to ACTIVE (as in, next valve) or to a SHUTDOWN or IDLE state
bool standby()
returns true if standby is enabled
uint32_t total_cycle_time_enabled_valves()
returns the amount of time in seconds required for all enabled valves
bool valve_cycle_complete_(size_t valve_number)
returns true if valve's cycle is flagged as complete
void add_valve(SprinklerControllerSwitch *valve_sw, SprinklerControllerSwitch *enable_sw=nullptr)
add a valve to the controller
SprinklerControllerSwitch * standby_sw_
Definition sprinkler.h:578
uint32_t timer_duration_(SprinklerTimerIndex timer_index)
returns time in milliseconds (ms)
void set_valve(SprinklerValve *valve)
void set_run_duration(uint32_t run_duration)
void set_start_delay(uint32_t start_delay, bool start_delay_is_valve_delay)
void set_controller(Sprinkler *controller)
void set_stop_delay(uint32_t stop_delay, bool stop_delay_is_valve_delay)
SprinklerValveRunRequestOrigin origin_
Definition sprinkler.h:173
SprinklerValveRunRequestOrigin request_is_from()
Definition sprinkler.h:166
void set_request_from(SprinklerValveRunRequestOrigin origin)
Definition sprinkler.h:157
SprinklerValveOperator * valve_operator()
void set_run_duration(uint32_t run_duration)
void set_valve_operator(SprinklerValveOperator *valve_op)
Base class for all switches.
Definition switch.h:38
void turn_on()
Turn this switch on.
Definition switch.cpp:20
void turn_off()
Turn this switch off.
Definition switch.cpp:24
bool state
The current reported state of the binary sensor.
Definition switch.h:55
void publish_state(bool state)
Publish a state to the front-end from the back-end.
Definition switch.cpp:57
optional< bool > get_initial_state_with_restore_mode()
Returns the initial state of the switch, after applying restore mode rules.
Definition switch.cpp:43
bool state
Definition fan.h:2
constexpr float HARDWARE
For components that deal with hardware and are very important like GPIO switch.
Definition component.h:43
const char *const TAG
Definition spi.cpp:7
constexpr const char * MIN_STR
Definition sprinkler.h:14
PROGMEM_STRING_TABLE(SprinklerRequestOriginStrings, "USER", "CYCLE", "QUEUE", "UNKNOWN")
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:28
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t
std::unique_ptr< StartSingleValveAction<> > valve_resumeorstart_action
Definition sprinkler.h:68
std::unique_ptr< Automation<> > valve_turn_off_automation
Definition sprinkler.h:69
SprinklerControllerSwitch * enable_switch
Definition sprinkler.h:62
std::unique_ptr< ShutdownAction<> > valve_shutdown_action
Definition sprinkler.h:67
SprinklerControllerSwitch * controller_switch
Definition sprinkler.h:61
std::unique_ptr< Automation<> > valve_turn_on_automation
Definition sprinkler.h:70
optional< size_t > pump_switch_index
Definition sprinkler.h:65