ESPHome 2026.3.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_)();
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
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.
89 uint32_t now = App.get_loop_component_start_time();
90 switch (this->state_) {
91 case STARTING:
92 if ((now - *this->start_millis_) > this->start_delay_) {
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_) > (this->start_delay_ + this->run_duration_)) {
99 this->stop(); // start_delay_ + run_duration_ has been exceeded, start shutting down
100 }
101 break;
102
103 case STOPPING:
104 if ((now - *this->stop_millis_) > this->stop_delay_) {
105 this->kill_(); // stop_delay_has been exceeded, ensure all valves are off
106 }
107 break;
108
109 default:
110 break;
111 }
112}
113
115 if (controller != nullptr) {
116 this->controller_ = controller;
117 }
118}
119
121 if (valve != nullptr) {
122 if (this->state_ != IDLE) { // Only kill if not already idle
123 this->kill_(); // ensure everything is off before we let go!
124 }
125 this->state_ = IDLE; // reset state
126 this->run_duration_ = 0; // reset to ensure the valve isn't started without updating it
127 this->start_millis_.reset(); // reset because (new) valve has not been started yet
128 this->stop_millis_.reset(); // reset because (new) valve has not been started yet
129 this->valve_ = valve; // finally, set the pointer to the new valve
130 }
131}
132
133void SprinklerValveOperator::set_run_duration(uint32_t run_duration) {
134 if (run_duration) {
135 this->run_duration_ = run_duration * 1000;
136 }
137}
138
139void SprinklerValveOperator::set_start_delay(uint32_t start_delay, bool start_delay_is_valve_delay) {
140 this->start_delay_is_valve_delay_ = start_delay_is_valve_delay;
141 this->start_delay_ = start_delay * 1000; // because 1000 milliseconds is one second
142}
143
144void SprinklerValveOperator::set_stop_delay(uint32_t stop_delay, bool stop_delay_is_valve_delay) {
145 this->stop_delay_is_valve_delay_ = stop_delay_is_valve_delay;
146 this->stop_delay_ = stop_delay * 1000; // because 1000 milliseconds is one second
147}
148
150 if (!this->run_duration_) { // can't start if zero run duration
151 return;
152 }
153 if (this->start_delay_ && (this->pump_switch() != nullptr)) {
154 this->state_ = STARTING; // STARTING state requires both a pump and a start_delay_
155 if (this->start_delay_is_valve_delay_) {
156 this->pump_on_();
157 } else if (!this->pump_switch()->state) { // if the pump is already on, wait to switch on the valve
158 this->valve_on_(); // to ensure consistent run time
159 }
160 } else {
161 this->run_(); // there is no start_delay_, so just start the pump and valve
162 }
163 this->stop_millis_.reset();
164 this->start_millis_ = millis(); // save the time the start request was made
165}
166
168 if ((this->state_ == IDLE) || (this->state_ == STOPPING)) { // can't stop if already stopped or stopping
169 return;
170 }
171 if (this->stop_delay_ && (this->pump_switch() != nullptr)) {
172 this->state_ = STOPPING; // STOPPING state requires both a pump and a stop_delay_
173 if (this->stop_delay_is_valve_delay_) {
174 this->pump_off_();
175 } else {
176 this->valve_off_();
177 }
178 if (this->pump_switch()->state) { // if the pump is still on at this point, it may be in use...
179 this->valve_off_(); // ...so just switch the valve off now to ensure consistent run time
180 }
181 } else {
182 this->kill_(); // there is no stop_delay_, so just stop the pump and valve
183 }
184 this->stop_millis_ = millis(); // save the time the stop request was made
185}
186
187uint32_t SprinklerValveOperator::run_duration() { return this->run_duration_ / 1000; }
188
190 if (!this->start_millis_.has_value()) {
191 return this->run_duration(); // hasn't been started yet
192 }
193
194 if (this->stop_millis_.has_value()) {
195 uint32_t elapsed = *this->stop_millis_ - *this->start_millis_;
196 if (elapsed >= this->start_delay_ + this->run_duration_) {
197 return 0; // valve was active for more than its configured duration, so we are done
198 }
199 if (elapsed <= this->start_delay_) {
200 return this->run_duration_ / 1000; // stopped during start delay, full run duration remains
201 }
202 return (this->run_duration_ - (elapsed - this->start_delay_)) / 1000;
203 }
204
205 uint32_t elapsed = millis() - *this->start_millis_;
206 uint32_t total_duration = this->start_delay_ + this->run_duration_;
207 if (elapsed < total_duration) {
208 return (total_duration - elapsed) / 1000; // running now
209 }
210 return 0; // run completed
211}
212
214
216 if ((this->controller_ == nullptr) || (this->valve_ == nullptr)) {
217 return nullptr;
218 }
219 if (this->valve_->pump_switch_index.has_value()) {
221 }
222 return nullptr;
223}
224
226 auto *pump = this->pump_switch();
227 if ((this->valve_ == nullptr) || (pump == nullptr)) { // safety first!
228 return;
229 }
230 if (this->controller_ == nullptr) { // safety first!
231 pump->turn_off(); // if no controller was set, just switch off the pump
232 } else { // ...otherwise, do it "safely"
233 auto state = this->state_; // this is silly, but...
234 this->state_ = BYPASS; // ...exclude me from the pump-in-use check that set_pump_state() does
235 this->controller_->set_pump_state(pump, false);
236 this->state_ = state;
237 }
238}
239
241 auto *pump = this->pump_switch();
242 if ((this->valve_ == nullptr) || (pump == nullptr)) { // safety first!
243 return;
244 }
245 if (this->controller_ == nullptr) { // safety first!
246 pump->turn_on(); // if no controller was set, just switch on the pump
247 } else { // ...otherwise, do it "safely"
248 auto state = this->state_; // this is silly, but...
249 this->state_ = BYPASS; // ...exclude me from the pump-in-use check that set_pump_state() does
250 this->controller_->set_pump_state(pump, true);
251 this->state_ = state;
252 }
253}
254
256 if ((this->valve_ == nullptr) || (this->valve_->valve_switch == nullptr)) { // safety first!
257 return;
258 }
259 if (this->valve_->valve_switch->state) {
260 this->valve_->valve_switch->turn_off();
261 }
262}
263
265 if ((this->valve_ == nullptr) || (this->valve_->valve_switch == nullptr)) { // safety first!
266 return;
267 }
268 if (!this->valve_->valve_switch->state) {
269 this->valve_->valve_switch->turn_on();
270 }
271}
272
274 this->state_ = IDLE;
275 this->valve_off_();
276 this->pump_off_();
277}
278
280 this->state_ = ACTIVE;
281 this->valve_on_();
282 this->pump_on_();
283}
284
286SprinklerValveRunRequest::SprinklerValveRunRequest(size_t valve_number, uint32_t run_duration,
287 SprinklerValveOperator *valve_op)
288 : valve_number_(valve_number), run_duration_(run_duration), valve_op_(valve_op) {}
289
291bool SprinklerValveRunRequest::has_valve_operator() { return !(this->valve_op_ == nullptr); }
292
294
296
297void SprinklerValveRunRequest::set_valve(size_t valve_number) {
298 this->valve_number_ = valve_number;
299 this->run_duration_ = 0;
300 this->valve_op_ = nullptr;
301 this->has_valve_ = true;
302}
303
305 if (valve_op != nullptr) {
306 this->valve_op_ = valve_op;
307 }
308}
309
311 this->has_valve_ = false;
312 this->origin_ = USER;
313 this->run_duration_ = 0;
314 this->valve_op_ = nullptr;
315}
316
318
320
322 if (this->has_valve_) {
323 return this->valve_number_;
324 }
325 return nullopt;
326}
327
329
331
333Sprinkler::Sprinkler(const char *name) : name_(name) {
334 // The `name` is stored for dump_config logging
335 this->timer_.init(2);
336 // Timer names only need to be unique within this component instance
337 this->timer_.push_back({"sm", false, 0, 0, std::bind(&Sprinkler::sm_timer_callback_, this)});
338 this->timer_.push_back({"vs", false, 0, 0, std::bind(&Sprinkler::valve_selection_callback_, this)});
339}
340
342 this->all_valves_off_(true);
343 // Start with loop disabled - nothing to do when idle
344 this->disable_loop();
345}
346
348 for (auto &vo : this->valve_op_) {
349 vo.loop();
350 }
351 if (this->prev_req_.has_request()) {
352 if (this->prev_req_.has_valve_operator() && this->prev_req_.valve_operator()->state() == IDLE) {
353 this->prev_req_.reset();
354 }
355 } else if (this->state_ == IDLE) {
356 // Nothing more to do - disable loop until next activation
357 this->disable_loop();
358 }
359}
360
362 auto new_valve_number = this->number_of_valves();
363 this->valve_.resize(new_valve_number + 1);
364 SprinklerValve *new_valve = &this->valve_[new_valve_number];
365
366 new_valve->controller_switch = valve_sw;
367 new_valve->controller_switch->set_state_lambda([this, new_valve_number]() -> optional<bool> {
368 auto *valve = this->valve_switch(new_valve_number);
369 auto *pump = this->valve_pump_switch(new_valve_number);
370 if (valve == nullptr) {
371 return false;
372 }
373 if (pump != nullptr) {
374 return valve->state && pump->state;
375 }
376 return valve->state;
377 });
378
379 new_valve->valve_turn_off_automation =
380 make_unique<Automation<>>(new_valve->controller_switch->get_turn_off_trigger());
381 new_valve->valve_shutdown_action = make_unique<sprinkler::ShutdownAction<>>(this);
382 new_valve->valve_turn_off_automation->add_actions({new_valve->valve_shutdown_action.get()});
383
384 new_valve->valve_turn_on_automation = make_unique<Automation<>>(new_valve->controller_switch->get_turn_on_trigger());
385 new_valve->valve_resumeorstart_action = make_unique<sprinkler::StartSingleValveAction<>>(this);
386 new_valve->valve_resumeorstart_action->set_valve_to_start(new_valve_number);
387 new_valve->valve_turn_on_automation->add_actions({new_valve->valve_resumeorstart_action.get()});
388
389 if (enable_sw != nullptr) {
390 new_valve->enable_switch = enable_sw;
391 }
392}
393
394void Sprinkler::add_controller(Sprinkler *other_controller) { this->other_controllers_.push_back(other_controller); }
395
397 this->controller_sw_ = controller_switch;
398 controller_switch->set_state_lambda([this]() -> optional<bool> {
399 for (size_t valve_number = 0; valve_number < this->number_of_valves(); valve_number++) {
400 if (this->valve_[valve_number].controller_switch->state) {
401 return true;
402 }
403 }
404 return this->active_req_.has_request();
405 });
406
407 this->sprinkler_turn_off_automation_ = make_unique<Automation<>>(controller_switch->get_turn_off_trigger());
408 this->sprinkler_shutdown_action_ = make_unique<sprinkler::ShutdownAction<>>(this);
410
411 this->sprinkler_turn_on_automation_ = make_unique<Automation<>>(controller_switch->get_turn_on_trigger());
412 this->sprinkler_resumeorstart_action_ = make_unique<sprinkler::ResumeOrStartAction<>>(this);
414}
415
417 this->auto_adv_sw_ = auto_adv_switch;
418}
419
421 this->queue_enable_sw_ = queue_enable_switch;
422}
423
425 this->reverse_sw_ = reverse_switch;
426}
427
429 this->standby_sw_ = standby_switch;
430
431 this->sprinkler_standby_turn_on_automation_ = make_unique<Automation<>>(standby_switch->get_turn_on_trigger());
432 this->sprinkler_standby_shutdown_action_ = make_unique<sprinkler::ShutdownAction<>>(this);
434}
435
437 this->multiplier_number_ = multiplier_number;
438}
439
441 this->repeat_number_ = repeat_number;
442}
443
444void Sprinkler::configure_valve_switch(size_t valve_number, switch_::Switch *valve_switch, uint32_t run_duration) {
445 if (this->is_a_valid_valve(valve_number)) {
446 this->valve_[valve_number].valve_switch = valve_switch;
447 this->valve_[valve_number].run_duration = run_duration;
448 }
449}
450
451void Sprinkler::configure_valve_pump_switch(size_t valve_number, switch_::Switch *pump_switch) {
452 if (this->is_a_valid_valve(valve_number)) {
453 for (size_t i = 0; i < this->pump_.size(); i++) { // check each existing registered pump
454 if (this->pump_[i] == pump_switch) { // if the "new" pump matches one we already have...
455 this->valve_[valve_number].pump_switch_index = i; // ...save its index in the pump vector...
456 return; // ...and we are done
457 }
458 } // if we end up here, no pumps matched, so add a new one
459 this->pump_.push_back(pump_switch);
460 this->valve_[valve_number].pump_switch_index = this->pump_.size() - 1; // save the index to the new pump
461 }
462}
463
465 SprinklerControllerNumber *run_duration_number) {
466 if (this->is_a_valid_valve(valve_number)) {
467 this->valve_[valve_number].run_duration_number = run_duration_number;
468 }
469}
470
472 if (!divider.has_value()) {
473 return;
474 }
475 if (divider.value() > 0) {
476 this->set_multiplier(1.0 / divider.value());
477 this->set_repeat(divider.value() - 1);
478 } else if (divider.value() == 0) {
479 this->set_multiplier(1.0);
480 this->set_repeat(0);
481 }
482}
483
485 if ((!multiplier.has_value()) || (multiplier.value() < 0)) {
486 return;
487 }
488 this->multiplier_ = multiplier.value();
489 if (this->multiplier_number_ == nullptr) {
490 return;
491 }
492 if (this->multiplier_number_->state == multiplier.value()) {
493 return;
494 }
495 auto call = this->multiplier_number_->make_call();
496 call.set_value(multiplier.value());
497 call.perform();
498}
499
501 this->next_prev_ignore_disabled_ = ignore_disabled;
502}
503
504void Sprinkler::set_pump_start_delay(uint32_t start_delay) {
505 this->start_delay_is_valve_delay_ = false;
506 this->start_delay_ = start_delay;
507}
508
509void Sprinkler::set_pump_stop_delay(uint32_t stop_delay) {
510 this->stop_delay_is_valve_delay_ = false;
511 this->stop_delay_ = stop_delay;
512}
513
514void Sprinkler::set_valve_start_delay(uint32_t start_delay) {
515 this->start_delay_is_valve_delay_ = true;
516 this->start_delay_ = start_delay;
517}
518
519void Sprinkler::set_valve_stop_delay(uint32_t stop_delay) {
520 this->stop_delay_is_valve_delay_ = true;
521 this->stop_delay_ = stop_delay;
522}
523
524void Sprinkler::set_pump_switch_off_during_valve_open_delay(bool pump_switch_off_during_valve_open_delay) {
525 this->pump_switch_off_during_valve_open_delay_ = pump_switch_off_during_valve_open_delay;
526}
527
528void Sprinkler::set_valve_open_delay(const uint32_t valve_open_delay) {
529 if (valve_open_delay > 0) {
530 this->valve_overlap_ = false;
531 this->switching_delay_ = valve_open_delay;
532 } else {
533 this->switching_delay_.reset();
534 }
535}
536
537void Sprinkler::set_valve_overlap(uint32_t valve_overlap) {
538 if (valve_overlap > 0) {
539 this->valve_overlap_ = true;
540 this->switching_delay_ = valve_overlap;
541 } else {
542 this->switching_delay_.reset();
543 }
544 this->pump_switch_off_during_valve_open_delay_ = false; // incompatible option
545}
546
547void Sprinkler::set_manual_selection_delay(uint32_t manual_selection_delay) {
548 if (manual_selection_delay > 0) {
549 this->manual_selection_delay_ = manual_selection_delay;
550 } else {
552 }
553}
554
555void Sprinkler::set_valve_run_duration(const optional<size_t> valve_number, const optional<uint32_t> run_duration) {
556 if (!valve_number.has_value() || !run_duration.has_value()) {
557 return;
558 }
559 if (!this->is_a_valid_valve(valve_number.value())) {
560 return;
561 }
562 this->valve_[valve_number.value()].run_duration = run_duration.value();
563 if (this->valve_[valve_number.value()].run_duration_number == nullptr) {
564 return;
565 }
566 if (this->valve_[valve_number.value()].run_duration_number->state == run_duration.value()) {
567 return;
568 }
569 auto call = this->valve_[valve_number.value()].run_duration_number->make_call();
570 if (this->valve_[valve_number.value()].run_duration_number->traits.get_unit_of_measurement_ref() == MIN_STR) {
571 call.set_value(run_duration.value() / 60.0);
572 } else {
573 call.set_value(run_duration.value());
574 }
575 call.perform();
576}
577
578void Sprinkler::set_auto_advance(const bool auto_advance) {
579 if (this->auto_adv_sw_ == nullptr) {
580 return;
581 }
582 if (this->auto_adv_sw_->state == auto_advance) {
583 return;
584 }
585 if (auto_advance) {
586 this->auto_adv_sw_->turn_on();
587 } else {
588 this->auto_adv_sw_->turn_off();
589 }
590}
591
593 this->target_repeats_ = repeat;
594 if (this->repeat_number_ == nullptr) {
595 return;
596 }
597 if (this->repeat_number_->state == repeat.value_or(0)) {
598 return;
599 }
600 auto call = this->repeat_number_->make_call();
601 call.set_value(repeat.value_or(0));
602 call.perform();
603}
604
605void Sprinkler::set_queue_enable(bool queue_enable) {
606 if (this->queue_enable_sw_ == nullptr) {
607 return;
608 }
609 if (this->queue_enable_sw_->state == queue_enable) {
610 return;
611 }
612 if (queue_enable) {
613 this->queue_enable_sw_->turn_on();
614 } else {
615 this->queue_enable_sw_->turn_off();
616 }
617}
618
619void Sprinkler::set_reverse(const bool reverse) {
620 if (this->reverse_sw_ == nullptr) {
621 return;
622 }
623 if (this->reverse_sw_->state == reverse) {
624 return;
625 }
626 if (reverse) {
627 this->reverse_sw_->turn_on();
628 } else {
629 this->reverse_sw_->turn_off();
630 }
631}
632
633void Sprinkler::set_standby(const bool standby) {
634 if (this->standby_sw_ == nullptr) {
635 return;
636 }
637 if (this->standby_sw_->state == standby) {
638 return;
639 }
640 if (standby) {
641 this->standby_sw_->turn_on();
642 } else {
643 this->standby_sw_->turn_off();
644 }
645}
646
647uint32_t Sprinkler::valve_run_duration(const size_t valve_number) {
648 if (!this->is_a_valid_valve(valve_number)) {
649 return 0;
650 }
651 if (this->valve_[valve_number].run_duration_number != nullptr) {
652 if (this->valve_[valve_number].run_duration_number->traits.get_unit_of_measurement_ref() == MIN_STR) {
653 return static_cast<uint32_t>(roundf(this->valve_[valve_number].run_duration_number->state * 60));
654 } else {
655 return static_cast<uint32_t>(roundf(this->valve_[valve_number].run_duration_number->state));
656 }
657 }
658 return this->valve_[valve_number].run_duration;
659}
660
661uint32_t Sprinkler::valve_run_duration_adjusted(const size_t valve_number) {
662 uint32_t run_duration = 0;
663
664 if (this->is_a_valid_valve(valve_number)) {
665 run_duration = this->valve_run_duration(valve_number);
666 }
667 run_duration = static_cast<uint32_t>(roundf(run_duration * this->multiplier()));
668 // run_duration must not be less than any of these
669 if ((run_duration < this->start_delay_) || (run_duration < this->stop_delay_) ||
670 (run_duration < this->switching_delay_.value_or(0) * 2)) {
671 return std::max(this->switching_delay_.value_or(0) * 2, std::max(this->start_delay_, this->stop_delay_));
672 }
673 return run_duration;
674}
675
677 if (this->auto_adv_sw_ != nullptr) {
678 return this->auto_adv_sw_->state;
679 }
680 return true;
681}
682
684 if (this->multiplier_number_ != nullptr) {
685 return this->multiplier_number_->state;
686 }
687 return this->multiplier_;
688}
689
691 if (this->repeat_number_ != nullptr) {
692 return static_cast<uint32_t>(roundf(this->repeat_number_->state));
693 }
694 return this->target_repeats_;
695}
696
698 // if there is an active valve and auto-advance is enabled, we may be repeating, so return the count
699 if (this->active_req_.has_request() && this->auto_advance()) {
700 return this->repeat_count_;
701 }
702 return nullopt;
703}
704
706 if (this->queue_enable_sw_ != nullptr) {
707 return this->queue_enable_sw_->state;
708 }
709 return true;
710}
711
713 if (this->reverse_sw_ != nullptr) {
714 return this->reverse_sw_->state;
715 }
716 return false;
717}
718
720 if (this->standby_sw_ != nullptr) {
721 return this->standby_sw_->state;
722 }
723 return false;
724}
725
727 if (this->standby()) {
728 this->log_standby_warning_(LOG_STR("start_from_queue"));
729 return;
730 }
731 if (this->multiplier() == 0) {
732 this->log_multiplier_zero_warning_(LOG_STR("start_from_queue"));
733 return;
734 }
735 if (this->queued_valves_.empty()) {
736 return; // if there is nothing in the queue, don't do anything
737 }
738 if (this->queue_enabled() && this->active_valve().has_value()) {
739 return; // if there is already a valve running from the queue, do nothing
740 }
741
742 this->set_auto_advance(false);
743 this->set_queue_enable(true);
744
745 this->reset_cycle_states_(); // just in case auto-advance is switched on later
746 this->repeat_count_ = 0;
747 this->fsm_kick_(); // will automagically pick up from the queue (it has priority)
748}
749
751 if (this->standby()) {
752 this->log_standby_warning_(LOG_STR("start_full_cycle"));
753 return;
754 }
755 if (this->multiplier() == 0) {
756 this->log_multiplier_zero_warning_(LOG_STR("start_full_cycle"));
757 return;
758 }
759 if (this->auto_advance() && this->active_valve().has_value()) {
760 return; // if auto-advance is already enabled and there is already a valve running, do nothing
761 }
762
763 this->set_queue_enable(false);
764
765 this->prep_full_cycle_();
766 this->repeat_count_ = 0;
767 // if there is no active valve already, start the first valve in the cycle
768 if (!this->active_req_.has_request()) {
769 this->fsm_kick_();
770 }
771}
772
774 if (this->standby()) {
775 this->log_standby_warning_(LOG_STR("start_single_valve"));
776 return;
777 }
778 if (this->multiplier() == 0) {
779 this->log_multiplier_zero_warning_(LOG_STR("start_single_valve"));
780 return;
781 }
782 if (!valve_number.has_value() || (valve_number == this->active_valve())) {
783 return;
784 }
785
786 this->set_auto_advance(false);
787 this->set_queue_enable(false);
788
789 this->reset_cycle_states_(); // just in case auto-advance is switched on later
790 this->repeat_count_ = 0;
791 this->fsm_request_(valve_number.value(), run_duration.value_or(0));
792}
793
795 if (valve_number.has_value()) {
796 if (this->is_a_valid_valve(valve_number.value()) && (this->queued_valves_.size() < this->max_queue_size_)) {
797 SprinklerQueueItem item{valve_number.value(), run_duration.value_or(0)};
798 this->queued_valves_.insert(this->queued_valves_.begin(), item);
799 ESP_LOGD(TAG, "Valve %zu placed into queue with run duration of %" PRIu32 " seconds", valve_number.value_or(0),
800 run_duration.value_or(0));
801 }
802 }
803}
804
806 this->queued_valves_.clear();
807 ESP_LOGD(TAG, "Queue cleared");
808}
809
811 if (this->standby()) {
812 this->log_standby_warning_(LOG_STR("next_valve"));
813 return;
814 }
815
816 if (this->state_ == IDLE) {
817 this->reset_cycle_states_(); // just in case auto-advance is switched on later
818 }
819
820 this->manual_valve_ = this->next_valve_number_(
821 this->manual_valve_.value_or(this->active_req_.valve_as_opt().value_or(this->number_of_valves() - 1)),
822 !this->next_prev_ignore_disabled_, true);
823
824 if (!this->manual_valve_.has_value()) {
825 ESP_LOGD(TAG, "next_valve was called but no valve could be started; perhaps next_prev_ignore_disabled allows only "
826 "enabled valves and no valves are enabled?");
827 return;
828 }
829
833 } else {
834 this->fsm_request_(this->manual_valve_.value());
835 }
836}
837
839 if (this->standby()) {
840 this->log_standby_warning_(LOG_STR("previous_valve"));
841 return;
842 }
843
844 if (this->state_ == IDLE) {
845 this->reset_cycle_states_(); // just in case auto-advance is switched on later
846 }
847
848 this->manual_valve_ =
849 this->previous_valve_number_(this->manual_valve_.value_or(this->active_req_.valve_as_opt().value_or(0)),
850 !this->next_prev_ignore_disabled_, true);
851
852 if (!this->manual_valve_.has_value()) {
853 ESP_LOGD(TAG, "previous_valve was called but no valve could be started; perhaps next_prev_ignore_disabled allows "
854 "only enabled valves and no valves are enabled?");
855 return;
856 }
857
861 } else {
862 this->fsm_request_(this->manual_valve_.value());
863 }
864}
865
866void Sprinkler::shutdown(bool clear_queue) {
868 this->active_req_.reset();
869 this->manual_valve_.reset();
870 this->next_req_.reset();
871 for (auto &vo : this->valve_op_) {
872 vo.stop();
873 }
875 if (clear_queue) {
876 this->clear_queued_valves();
877 this->repeat_count_ = 0;
878 }
879}
880
882 if (this->paused_valve_.has_value() || !this->active_req_.has_request()) {
883 return; // we can't pause if we're already paused or if there is no active valve
884 }
885 this->paused_valve_ = this->active_valve();
887 this->shutdown(false);
888 ESP_LOGD(TAG, "Paused valve %zu with %" PRIu32 " seconds remaining", this->paused_valve_.value_or(0),
889 this->resume_duration_.value_or(0));
890}
891
893 if (this->standby()) {
894 this->log_standby_warning_(LOG_STR("resume"));
895 return;
896 }
897
898 if (this->paused_valve_.has_value() && (this->resume_duration_.has_value())) {
899 // Resume only if valve has not been completed yet
900 if (!this->valve_cycle_complete_(this->paused_valve_.value())) {
901 ESP_LOGD(TAG, "Resuming valve %zu with %" PRIu32 " seconds remaining", this->paused_valve_.value_or(0),
902 this->resume_duration_.value_or(0));
903 this->fsm_request_(this->paused_valve_.value(), this->resume_duration_.value());
904 }
905 this->reset_resume();
906 } else {
907 ESP_LOGD(TAG, "No valve to resume!");
908 }
909}
910
912 if (this->paused_valve_.has_value() && (this->resume_duration_.has_value())) {
913 this->resume();
914 } else {
915 this->start_full_cycle();
916 }
917}
918
920 this->paused_valve_.reset();
921 this->resume_duration_.reset();
922}
923
924const char *Sprinkler::valve_name(const size_t valve_number) {
925 if (this->is_a_valid_valve(valve_number)) {
926 return this->valve_[valve_number].controller_switch->get_name().c_str();
927 }
928 return nullptr;
929}
930
937
939 if (!this->valve_overlap_ && this->prev_req_.has_request() && this->prev_req_.has_valve_operator() &&
940 (this->prev_req_.valve_operator()->state() == STARTING || this->prev_req_.valve_operator()->state() == ACTIVE)) {
941 return this->prev_req_.valve_as_opt();
942 }
943 return this->active_req_.valve_as_opt();
944}
945
947
949 if (!this->queued_valves_.empty()) {
950 return this->queued_valves_.back().valve_number;
951 }
952 return nullopt;
953}
954
956
957size_t Sprinkler::number_of_valves() { return this->valve_.size(); }
958
959bool Sprinkler::is_a_valid_valve(const size_t valve_number) { return (valve_number < this->number_of_valves()); }
960
962 if (pump_switch == nullptr) {
963 return false; // we can't do anything if there's nothing to check
964 }
965 // a pump must be considered "in use" if a (distribution) valve it supplies is active. this means:
966 // - at least one SprinklerValveOperator:
967 // - has a valve loaded that depends on this pump
968 // - is in a state that depends on the pump: (ACTIVE and _possibly_ STARTING/STOPPING)
969 // - if NO SprinklerValveOperator is active but there is a run request pending (active_req_.has_request()) and the
970 // controller state is STARTING, valve open delay is configured but NOT pump_switch_off_during_valve_open_delay_
971 for (auto &vo : this->valve_op_) { // first, check if any SprinklerValveOperator has a valve dependent on this pump
972 if ((vo.state() != BYPASS) && (vo.pump_switch() != nullptr)) {
973 // the SprinklerValveOperator is configured with a pump; now check if it is the pump of interest
974 if (vo.pump_switch() == pump_switch) {
975 // now if the SprinklerValveOperator has a pump and it is either ACTIVE, is STARTING with a valve delay or
976 // is STOPPING with a valve delay, its pump can be considered "in use", so just return indicating this now
977 if ((vo.state() == ACTIVE) ||
978 ((vo.state() == STARTING) && this->start_delay_ && this->start_delay_is_valve_delay_) ||
979 ((vo.state() == STOPPING) && this->stop_delay_ && this->stop_delay_is_valve_delay_)) {
980 return true;
981 }
982 }
983 }
984 } // if we end up here, no SprinklerValveOperator was in a "give-away" state indicating that the pump is in use...
986 this->active_req_.has_request() && (this->state_ != STOPPING)) {
987 // ...the controller is configured to keep the pump on during a valve open delay, so just return
988 // whether or not the next valve shares the same pump
989 auto *valve_pump = this->valve_pump_switch(this->active_req_.valve());
990 if (valve_pump == nullptr) {
991 return false; // valve has no pump, so this pump isn't in use by it
992 }
993 return pump_switch == valve_pump;
994 }
995 return false;
996}
997
999 if (pump_switch == nullptr) {
1000 return; // we can't do anything if there's nothing to check
1001 }
1002
1003 bool hold_pump_on = false;
1004
1005 for (auto &controller : this->other_controllers_) { // check if the pump is in use by another controller
1006 if (controller != this) { // dummy check
1007 if (controller->pump_in_use(pump_switch)) {
1008 hold_pump_on = true; // if another controller says it's using this pump, keep it on
1009 }
1010 }
1011 }
1012 if (hold_pump_on) {
1013 ESP_LOGD(TAG, "Leaving pump on because another controller instance is using it");
1014 }
1015
1016 if (state) { // ...and now we can set the new state of the switch
1017 pump_switch->turn_on();
1018 } else if (!hold_pump_on && !this->pump_in_use(pump_switch)) {
1019 pump_switch->turn_off();
1020 }
1021}
1022
1024 uint32_t total_time_remaining = 0;
1025
1026 for (size_t valve = 0; valve < this->number_of_valves(); valve++) {
1027 total_time_remaining += this->valve_run_duration_adjusted(valve);
1028 }
1029
1030 if (this->valve_overlap_) {
1031 total_time_remaining -= this->switching_delay_.value_or(0) * (this->number_of_valves() - 1);
1032 } else {
1033 total_time_remaining += this->switching_delay_.value_or(0) * (this->number_of_valves() - 1);
1034 }
1035
1036 return total_time_remaining;
1037}
1038
1040 uint32_t total_time_remaining = 0;
1041 uint32_t valve_count = 0;
1042
1043 for (size_t valve = 0; valve < this->number_of_valves(); valve++) {
1044 if (this->valve_is_enabled_(valve)) {
1045 total_time_remaining += this->valve_run_duration_adjusted(valve);
1046 valve_count++;
1047 }
1048 }
1049
1050 if (valve_count) {
1051 if (this->valve_overlap_) {
1052 total_time_remaining -= this->switching_delay_.value_or(0) * (valve_count - 1);
1053 } else {
1054 total_time_remaining += this->switching_delay_.value_or(0) * (valve_count - 1);
1055 }
1056 }
1057
1058 return total_time_remaining;
1059}
1060
1062 uint32_t total_time_remaining = 0;
1063 uint32_t enabled_valve_count = 0;
1064 uint32_t incomplete_valve_count = 0;
1065
1066 for (size_t valve = 0; valve < this->number_of_valves(); valve++) {
1067 if (this->valve_is_enabled_(valve)) {
1068 enabled_valve_count++;
1069 if (!this->valve_cycle_complete_(valve)) {
1070 if (!this->active_valve().has_value() || (valve != this->active_valve().value())) {
1071 total_time_remaining += this->valve_run_duration_adjusted(valve);
1072 incomplete_valve_count++;
1073 } else {
1074 // to get here, there must be an active valve and this valve must be equal to 'valve'
1075 if (this->active_req_.valve_operator() == nullptr) { // no SVO has been assigned yet so it hasn't started
1076 total_time_remaining += this->valve_run_duration_adjusted(valve);
1077 incomplete_valve_count++;
1078 }
1079 }
1080 }
1081 }
1082 }
1083
1084 if (incomplete_valve_count > 0 && incomplete_valve_count >= enabled_valve_count) {
1085 incomplete_valve_count--;
1086 }
1087 if (incomplete_valve_count) {
1088 if (this->valve_overlap_) {
1089 total_time_remaining -= this->switching_delay_.value_or(0) * incomplete_valve_count;
1090 } else {
1091 total_time_remaining += this->switching_delay_.value_or(0) * incomplete_valve_count;
1092 }
1093 }
1094
1095 return total_time_remaining;
1096}
1097
1099 uint32_t total_time_remaining = 0;
1100 uint32_t valve_count = 0;
1101
1102 for (auto &valve : this->queued_valves_) {
1103 if (valve.run_duration) {
1104 total_time_remaining += valve.run_duration;
1105 } else {
1106 total_time_remaining += this->valve_run_duration_adjusted(valve.valve_number);
1107 }
1108 valve_count++;
1109 }
1110
1111 if (valve_count) {
1112 if (this->valve_overlap_) {
1113 total_time_remaining -= this->switching_delay_.value_or(0) * (valve_count - 1);
1114 } else {
1115 total_time_remaining += this->switching_delay_.value_or(0) * (valve_count - 1);
1116 }
1117 }
1118
1119 return total_time_remaining;
1120}
1121
1123 if (this->active_req_.has_request()) { // first try to return the value based on active_req_...
1124 if (this->active_req_.valve_operator() != nullptr) {
1125 return this->active_req_.valve_operator()->time_remaining();
1126 }
1127 }
1128 if (this->prev_req_.has_request()) { // try to return the value based on prev_req_...
1129 if (this->prev_req_.valve_operator() != nullptr) {
1130 return this->prev_req_.valve_operator()->time_remaining();
1131 }
1132 }
1133 return nullopt;
1134}
1135
1137 if (!this->time_remaining_active_valve().has_value() && this->state_ == IDLE) {
1138 return nullopt;
1139 }
1140
1141 auto total_time_remaining = this->time_remaining_active_valve().value_or(0);
1142 if (this->auto_advance()) {
1143 total_time_remaining += this->total_cycle_time_enabled_incomplete_valves();
1144 if (this->repeat().value_or(0) > 0) {
1145 total_time_remaining +=
1146 (this->total_cycle_time_enabled_valves() * (this->repeat().value_or(0) - this->repeat_count().value_or(0)));
1147 }
1148 }
1149
1150 if (this->queue_enabled()) {
1151 total_time_remaining += this->total_queue_time();
1152 }
1153 return total_time_remaining;
1154}
1155
1157 if (this->state_ != IDLE) {
1158 return true;
1159 }
1160
1161 for (auto &controller : this->other_controllers_) {
1162 if (controller != this) { // dummy check
1163 if (controller->controller_state() != IDLE) {
1164 return true;
1165 }
1166 }
1167 }
1168 return false;
1169}
1170
1172 if (this->is_a_valid_valve(valve_number)) {
1173 return this->valve_[valve_number].controller_switch;
1174 }
1175 return nullptr;
1176}
1177
1179 if (this->is_a_valid_valve(valve_number)) {
1180 return this->valve_[valve_number].enable_switch;
1181 }
1182 return nullptr;
1183}
1184
1185switch_::Switch *Sprinkler::valve_switch(const size_t valve_number) {
1186 if (this->is_a_valid_valve(valve_number)) {
1187 return this->valve_[valve_number].valve_switch;
1188 }
1189 return nullptr;
1190}
1191
1193 if (this->is_a_valid_valve(valve_number) && this->valve_[valve_number].pump_switch_index.has_value()) {
1194 return this->pump_[this->valve_[valve_number].pump_switch_index.value()];
1195 }
1196 return nullptr;
1197}
1198
1200 if (pump_index < this->pump_.size()) {
1201 return this->pump_[pump_index];
1202 }
1203 return nullptr;
1204}
1205
1206bool Sprinkler::valve_is_enabled_(const size_t valve_number) {
1207 if (this->is_a_valid_valve(valve_number)) {
1208 if (this->valve_[valve_number].enable_switch != nullptr) {
1209 return this->valve_[valve_number].enable_switch->state;
1210 } else {
1211 return true;
1212 }
1213 }
1214 return false;
1215}
1216
1217void Sprinkler::mark_valve_cycle_complete_(const size_t valve_number) {
1218 if (this->is_a_valid_valve(valve_number)) {
1219 ESP_LOGD(TAG, "Marking valve %u complete", valve_number);
1220 this->valve_[valve_number].valve_cycle_complete = true;
1221 }
1222}
1223
1224bool Sprinkler::valve_cycle_complete_(const size_t valve_number) {
1225 if (this->is_a_valid_valve(valve_number)) {
1226 return this->valve_[valve_number].valve_cycle_complete;
1227 }
1228 return false;
1229}
1230
1231optional<size_t> Sprinkler::next_valve_number_(const optional<size_t> first_valve, const bool include_disabled,
1232 const bool include_complete) {
1233 auto valve = first_valve.value_or(0);
1234 size_t start = first_valve.has_value() ? 1 : 0;
1235
1236 if (!this->is_a_valid_valve(valve)) {
1237 valve = 0;
1238 }
1239
1240 for (size_t offset = start; offset < this->number_of_valves(); offset++) {
1241 auto valve_of_interest = valve + offset;
1242 if (!this->is_a_valid_valve(valve_of_interest)) {
1243 valve_of_interest -= this->number_of_valves();
1244 }
1245
1246 if ((this->valve_is_enabled_(valve_of_interest) || include_disabled) &&
1247 (!this->valve_cycle_complete_(valve_of_interest) || include_complete)) {
1248 return valve_of_interest;
1249 }
1250 }
1251 return nullopt;
1252}
1253
1254optional<size_t> Sprinkler::previous_valve_number_(const optional<size_t> first_valve, const bool include_disabled,
1255 const bool include_complete) {
1256 auto valve = first_valve.value_or(this->number_of_valves() - 1);
1257 size_t start = first_valve.has_value() ? 1 : 0;
1258
1259 if (!this->is_a_valid_valve(valve)) {
1260 valve = this->number_of_valves() - 1;
1261 }
1262
1263 for (size_t offset = start; offset < this->number_of_valves(); offset++) {
1264 auto valve_of_interest = valve - offset;
1265 if (!this->is_a_valid_valve(valve_of_interest)) {
1266 valve_of_interest += this->number_of_valves();
1267 }
1268
1269 if ((this->valve_is_enabled_(valve_of_interest) || include_disabled) &&
1270 (!this->valve_cycle_complete_(valve_of_interest) || include_complete)) {
1271 return valve_of_interest;
1272 }
1273 }
1274 return nullopt;
1275}
1276
1278 if (this->reverse()) {
1279 return this->previous_valve_number_(first_valve, false, false);
1280 }
1281 return this->next_valve_number_(first_valve, false, false);
1282}
1283
1285 if (this->active_req_.has_request()) {
1286 this->prev_req_ = this->active_req_;
1287 } else {
1288 this->prev_req_.reset();
1289 }
1290
1291 if (this->next_req_.has_request()) {
1292 if (!this->next_req_.run_duration()) { // ensure the run duration is set correctly for consumption later on
1294 }
1295 return; // there is already a request pending
1296 } else if (this->queue_enabled() && !this->queued_valves_.empty()) {
1297 this->next_req_.set_valve(this->queued_valves_.back().valve_number);
1299 if (this->queued_valves_.back().run_duration) {
1300 this->next_req_.set_run_duration(this->queued_valves_.back().run_duration);
1301 this->queued_valves_.pop_back();
1302 } else if (this->multiplier()) {
1303 this->next_req_.set_run_duration(this->valve_run_duration_adjusted(this->queued_valves_.back().valve_number));
1304 this->queued_valves_.pop_back();
1305 } else {
1306 this->next_req_.reset();
1307 }
1308 } else if (this->auto_advance() && this->multiplier()) {
1309 if (this->next_valve_number_in_cycle_(first_valve).has_value()) {
1310 // if there is another valve to run as a part of a cycle, load that
1311 this->next_req_.set_valve(this->next_valve_number_in_cycle_(first_valve).value_or(0));
1314 this->valve_run_duration_adjusted(this->next_valve_number_in_cycle_(first_valve).value_or(0)));
1315 } else if ((this->repeat_count_++ < this->repeat().value_or(0))) {
1316 ESP_LOGD(TAG, "Repeating - starting cycle %" PRIu32 " of %" PRIu32, this->repeat_count_ + 1,
1317 this->repeat().value_or(0) + 1);
1318 // if there are repeats remaining and no more valves were left in the cycle, start a new cycle
1319 this->prep_full_cycle_();
1320 if (this->next_valve_number_in_cycle_().has_value()) { // this should always succeed here, but just in case...
1321 this->next_req_.set_valve(this->next_valve_number_in_cycle_().value_or(0));
1324 this->valve_run_duration_adjusted(this->next_valve_number_in_cycle_().value_or(0)));
1325 }
1326 }
1327 }
1328}
1329
1331 for (size_t valve_number = 0; valve_number < this->number_of_valves(); valve_number++) {
1332 if (this->valve_is_enabled_(valve_number))
1333 return true;
1334 }
1335 return false;
1336}
1337
1339 if (!req->has_request()) {
1340 return; // we can't do anything if the request contains nothing
1341 }
1342 if (!this->is_a_valid_valve(req->valve())) {
1343 return; // we can't do anything if the valve number isn't valid
1344 }
1345 // Enable loop to monitor valve operator states
1346 this->enable_loop();
1347 for (auto &vo : this->valve_op_) { // find the first available SprinklerValveOperator, load it and start it up
1348 if (vo.state() == IDLE) {
1349 auto run_duration = req->run_duration() ? req->run_duration() : this->valve_run_duration_adjusted(req->valve());
1350 ESP_LOGD(TAG, "%s is starting valve %zu for %" PRIu32 " seconds, cycle %" PRIu32 " of %" PRIu32,
1351 LOG_STR_ARG(this->req_as_str_(req->request_is_from())), req->valve(), run_duration,
1352 this->repeat_count_ + 1, this->repeat().value_or(0) + 1);
1353 req->set_valve_operator(&vo);
1354 vo.set_controller(this);
1355 vo.set_valve(&this->valve_[req->valve()]);
1356 vo.set_run_duration(run_duration);
1357 vo.set_start_delay(this->start_delay_, this->start_delay_is_valve_delay_);
1358 vo.set_stop_delay(this->stop_delay_, this->stop_delay_is_valve_delay_);
1359 vo.start();
1360 return;
1361 }
1362 }
1363}
1364
1365void Sprinkler::all_valves_off_(const bool include_pump) {
1366 for (size_t valve_index = 0; valve_index < this->number_of_valves(); valve_index++) {
1367 auto *valve_sw = this->valve_[valve_index].valve_switch;
1368 if ((valve_sw != nullptr) && valve_sw->state) {
1369 valve_sw->turn_off();
1370 }
1371 if (include_pump) {
1372 this->set_pump_state(this->valve_pump_switch(valve_index), false);
1373 }
1374 }
1375 ESP_LOGD(TAG, "All valves stopped%s", include_pump ? ", including pumps" : "");
1376}
1377
1379 this->set_auto_advance(true);
1380
1381 if (!this->any_valve_is_enabled_()) {
1382 for (auto &valve : this->valve_) {
1383 if (valve.enable_switch != nullptr) {
1384 if (!valve.enable_switch->state) {
1385 valve.enable_switch->turn_on();
1386 }
1387 }
1388 }
1389 }
1390 this->reset_cycle_states_();
1391}
1392
1394 for (auto &valve : this->valve_) {
1395 valve.valve_cycle_complete = false;
1396 }
1397}
1398
1399void Sprinkler::fsm_request_(size_t requested_valve, uint32_t requested_run_duration) {
1400 this->next_req_.set_valve(requested_valve);
1401 this->next_req_.set_run_duration(requested_run_duration);
1402 // if state is IDLE or ACTIVE, call fsm_transition_() to start it immediately;
1403 // otherwise, fsm_transition() will pick up next_req_ at the next appropriate transition
1404 this->fsm_kick_();
1405}
1406
1408 if ((this->state_ == IDLE) || (this->state_ == ACTIVE)) {
1409 this->fsm_transition_();
1410 }
1411}
1412
1414 ESP_LOGVV(TAG, "fsm_transition_ called; state is %s", LOG_STR_ARG(this->state_as_str_(this->state_)));
1415 switch (this->state_) {
1416 case IDLE: // the system was off -> start it up
1417 // advances to ACTIVE
1419 break;
1420
1421 case ACTIVE:
1422 // advances to STOPPING or ACTIVE (again)
1424 break;
1425
1426 case STARTING: {
1427 // follows valve open delay interval
1428 uint32_t timer_duration = this->active_req_.run_duration();
1429 if (timer_duration > this->switching_delay_.value_or(0)) {
1430 timer_duration -= this->switching_delay_.value_or(0);
1431 }
1432 this->set_timer_duration_(sprinkler::TIMER_SM, timer_duration);
1434 this->start_valve_(&this->active_req_);
1435 this->state_ = ACTIVE;
1436 if (this->next_req_.has_request()) {
1437 // another valve has been requested, so restart the timer so we pick it up quickly
1440 }
1441 break;
1442 }
1443
1444 case STOPPING:
1445 // stop_delay_ has elapsed so just shut everything off
1446 this->active_req_.reset();
1447 this->manual_valve_.reset();
1448 this->all_valves_off_(true);
1449 this->state_ = IDLE;
1450 break;
1451
1452 default:
1453 break;
1454 }
1455 if (this->next_req_.has_request() && (this->state_ == IDLE)) {
1456 // another valve has been requested, so restart the timer so we pick it up quickly
1459 }
1460 ESP_LOGVV(TAG, "fsm_transition_ complete; new state is %s", LOG_STR_ARG(this->state_as_str_(this->state_)));
1461}
1462
1465
1466 if (this->next_req_.has_request()) { // there is a valve to run...
1467 this->active_req_.set_valve(this->next_req_.valve());
1470 this->next_req_.reset();
1471
1472 uint32_t timer_duration = this->active_req_.run_duration();
1473 if (timer_duration > this->switching_delay_.value_or(0)) {
1474 timer_duration -= this->switching_delay_.value_or(0);
1475 }
1476 this->set_timer_duration_(sprinkler::TIMER_SM, timer_duration);
1478 this->start_valve_(&this->active_req_);
1479 this->state_ = ACTIVE;
1480 }
1481}
1482
1484 if (!this->active_req_.has_request()) { // dummy check...
1486 return;
1487 }
1488
1489 if (!this->timer_active_(sprinkler::TIMER_SM)) { // only flag the valve as "complete" if the timer finished
1490 if ((this->active_req_.request_is_from() == CYCLE) || (this->active_req_.request_is_from() == USER)) {
1492 }
1493 } else {
1494 ESP_LOGD(TAG, "Valve cycle interrupted - NOT flagging valve as complete and stopping current valve");
1495 for (auto &vo : this->valve_op_) {
1496 vo.stop();
1497 }
1498 }
1499
1501
1502 if (this->next_req_.has_request()) { // there is another valve to run...
1503 auto *active_pump = this->valve_pump_switch(this->active_req_.valve());
1504 auto *next_pump = this->valve_pump_switch(this->next_req_.valve());
1505 bool same_pump = (active_pump != nullptr) && (next_pump != nullptr) && (active_pump == next_pump);
1506
1507 this->active_req_.set_valve(this->next_req_.valve());
1510 this->next_req_.reset();
1511
1512 // this->state_ = ACTIVE; // state isn't changing
1513 if (this->valve_overlap_ || !this->switching_delay_.has_value()) {
1514 uint32_t timer_duration = this->active_req_.run_duration();
1515 if (timer_duration > this->switching_delay_.value_or(0)) {
1516 timer_duration -= this->switching_delay_.value_or(0);
1517 }
1518 this->set_timer_duration_(sprinkler::TIMER_SM, timer_duration);
1520 this->start_valve_(&this->active_req_);
1521 } else {
1522 this->set_timer_duration_(
1524 this->switching_delay_.value() * 2 +
1525 (this->pump_switch_off_during_valve_open_delay_ && same_pump ? this->stop_delay_ : 0));
1527 this->state_ = STARTING;
1528 }
1529 } else { // there is NOT another valve to run...
1531 }
1532}
1533
1540
1541void Sprinkler::log_standby_warning_(const LogString *method_name) {
1542 ESP_LOGW(TAG, "%s called but standby is enabled; no action taken", LOG_STR_ARG(method_name));
1543}
1544
1545void Sprinkler::log_multiplier_zero_warning_(const LogString *method_name) {
1546 ESP_LOGW(TAG, "%s called but multiplier is set to zero; no action taken", LOG_STR_ARG(method_name));
1547}
1548
1549// Request origin strings indexed by SprinklerValveRunRequestOrigin enum (0-2): USER, CYCLE, QUEUE
1550PROGMEM_STRING_TABLE(SprinklerRequestOriginStrings, "USER", "CYCLE", "QUEUE", "UNKNOWN");
1551
1553 return SprinklerRequestOriginStrings::get_log_str(static_cast<uint8_t>(origin),
1554 SprinklerRequestOriginStrings::LAST_INDEX);
1555}
1556
1557// Sprinkler state strings indexed by SprinklerState enum (0-4): IDLE, STARTING, ACTIVE, STOPPING, BYPASS
1558PROGMEM_STRING_TABLE(SprinklerStateStrings, "IDLE", "STARTING", "ACTIVE", "STOPPING", "BYPASS", "UNKNOWN");
1559
1561 return SprinklerStateStrings::get_log_str(static_cast<uint8_t>(state), SprinklerStateStrings::LAST_INDEX);
1562}
1563
1565 if (this->timer_duration_(timer_index) > 0) {
1566 this->set_timeout(this->timer_[timer_index].name, this->timer_duration_(timer_index),
1567 this->timer_cbf_(timer_index));
1568 this->timer_[timer_index].start_time = millis();
1569 this->timer_[timer_index].active = true;
1570 }
1571 ESP_LOGVV(TAG, "Timer %zu started for %" PRIu32 " sec", static_cast<size_t>(timer_index),
1572 this->timer_duration_(timer_index) / 1000);
1573}
1574
1576 this->timer_[timer_index].active = false;
1577 return this->cancel_timeout(this->timer_[timer_index].name);
1578}
1579
1580bool Sprinkler::timer_active_(const SprinklerTimerIndex timer_index) { return this->timer_[timer_index].active; }
1581
1582void Sprinkler::set_timer_duration_(const SprinklerTimerIndex timer_index, const uint32_t time) {
1583 this->timer_[timer_index].time = 1000 * time;
1584}
1585
1586uint32_t Sprinkler::timer_duration_(const SprinklerTimerIndex timer_index) { return this->timer_[timer_index].time; }
1587
1588std::function<void()> Sprinkler::timer_cbf_(const SprinklerTimerIndex timer_index) {
1589 return this->timer_[timer_index].func;
1590}
1591
1593 this->timer_[sprinkler::TIMER_VALVE_SELECTION].active = false;
1594 ESP_LOGVV(TAG, "Valve selection timer expired");
1595 if (this->manual_valve_.has_value()) {
1596 this->fsm_request_(this->manual_valve_.value());
1597 this->manual_valve_.reset();
1598 }
1599}
1600
1602 this->timer_[sprinkler::TIMER_SM].active = false;
1603 ESP_LOGVV(TAG, "State machine timer expired");
1604 this->fsm_transition_();
1605}
1606
1608 ESP_LOGCONFIG(TAG, "Sprinkler Controller -- %s", this->name_);
1609 if (this->manual_selection_delay_.has_value()) {
1610 ESP_LOGCONFIG(TAG, " Manual Selection Delay: %" PRIu32 " seconds", this->manual_selection_delay_.value_or(0));
1611 }
1612 if (this->repeat().has_value()) {
1613 ESP_LOGCONFIG(TAG, " Repeat Cycles: %" PRIu32 " times", this->repeat().value_or(0));
1614 }
1615 if (this->start_delay_) {
1616 if (this->start_delay_is_valve_delay_) {
1617 ESP_LOGCONFIG(TAG, " Pump Start Valve Delay: %" PRIu32 " seconds", this->start_delay_);
1618 } else {
1619 ESP_LOGCONFIG(TAG, " Pump Start Pump Delay: %" PRIu32 " seconds", this->start_delay_);
1620 }
1621 }
1622 if (this->stop_delay_) {
1623 if (this->stop_delay_is_valve_delay_) {
1624 ESP_LOGCONFIG(TAG, " Pump Stop Valve Delay: %" PRIu32 " seconds", this->stop_delay_);
1625 } else {
1626 ESP_LOGCONFIG(TAG, " Pump Stop Pump Delay: %" PRIu32 " seconds", this->stop_delay_);
1627 }
1628 }
1629 if (this->switching_delay_.has_value()) {
1630 if (this->valve_overlap_) {
1631 ESP_LOGCONFIG(TAG, " Valve Overlap: %" PRIu32 " seconds", this->switching_delay_.value_or(0));
1632 } else {
1633 ESP_LOGCONFIG(TAG,
1634 " Valve Open Delay: %" PRIu32 " seconds\n"
1635 " Pump Switch Off During Valve Open Delay: %s",
1637 }
1638 }
1639 for (size_t valve_number = 0; valve_number < this->number_of_valves(); valve_number++) {
1640 ESP_LOGCONFIG(TAG,
1641 " Valve %zu:\n"
1642 " Name: %s\n"
1643 " Run Duration: %" PRIu32 " seconds",
1644 valve_number, this->valve_name(valve_number), this->valve_run_duration(valve_number));
1645 }
1646 if (!this->pump_.empty()) {
1647 ESP_LOGCONFIG(TAG, " Total number of pumps: %zu", this->pump_.size());
1648 }
1649 if (!this->valve_.empty()) {
1650 ESP_LOGCONFIG(TAG, " Total number of valves: %zu", this->valve_.size());
1651 }
1652}
1653
1654} // 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.
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") void set_timeout(const std voi set_timeout)(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
Definition component.h:443
void enable_loop()
Enable this component's loop.
void disable_loop()
Disable this component's loop.
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") bool cancel_timeout(const std boo cancel_timeout)(const char *name)
Cancel a timeout function.
Definition component.h:465
ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.", "2026.2.0") void set_retry(const std uint32_t uint8_t std::function< RetryResult(uint8_t)> && f
Definition component.h:387
bool save(const T *src)
Definition preferences.h:21
ESPPreferenceObject make_entity_preference(uint32_t version=0)
Create a preference object for storing this entity's state/settings.
void trigger(const Ts &...x)
Inform the parent automation that the event has triggered.
Definition automation.h:325
void stop_action()
Stop any action connected to this trigger.
Definition automation.h:333
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:39
bool has_value() const
Definition optional.h:92
value_type value_or(U const &v) const
Definition optional.h:98
value_type const & value() const
Definition optional.h:94
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:558
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
bool next_prev_ignore_disabled_
When set to true, the next and previous actions will skip disabled valves.
Definition sprinkler.h:491
void reset_resume()
resets resume state
SprinklerControllerNumber * repeat_number_
Definition sprinkler.h:572
bool valve_is_enabled_(size_t valve_number)
returns true if valve number is enabled
SprinklerState state_
Sprinkler controller state.
Definition sprinkler.h:510
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:540
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:552
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:531
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:519
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()'
void set_controller_reverse_switch(SprinklerControllerSwitch *reverse_switch)
uint32_t start_delay_
Pump start/stop delay intervals.
Definition sprinkler.h:504
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:571
std::vector< SprinklerValveOperator > valve_op_
Sprinkler valve operator objects.
Definition sprinkler.h:555
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:565
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_controller_queue_enable_switch(SprinklerControllerSwitch *queue_enable_switch)
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:528
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:567
optional< uint32_t > switching_delay_
Valve switching delay.
Definition sprinkler.h:537
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:513
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:549
bool start_delay_is_valve_delay_
Pump start/stop delay interval types.
Definition sprinkler.h:500
optional< size_t > manual_valve_
The number of the manually selected valve currently selected.
Definition sprinkler.h:522
bool pump_switch_off_during_valve_open_delay_
Pump should be off during valve_open_delay interval.
Definition sprinkler.h:494
bool cancel_timer_(SprinklerTimerIndex timer_index)
SprinklerControllerSwitch * queue_enable_sw_
Definition sprinkler.h:566
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:574
std::unique_ptr< ResumeOrStartAction<> > sprinkler_resumeorstart_action_
Definition sprinkler.h:576
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
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:546
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:579
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:578
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)
void set_pump_switch_off_during_valve_open_delay(bool pump_switch_off_during_valve_open_delay)
if pump_switch_off_during_valve_open_delay is true, the controller will switch off the pump during th...
std::unique_ptr< ShutdownAction<> > sprinkler_standby_shutdown_action_
Definition sprinkler.h:575
optional< uint32_t > manual_selection_delay_
Manual switching delay.
Definition sprinkler.h:534
void set_controller_repeat_number(SprinklerControllerNumber *repeat_number)
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:525
SprinklerValveRunRequest next_req_
The next run request for the controller to consume after active_req_ is complete.
Definition sprinkler.h:516
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:564
void set_controller_multiplier_number(SprinklerControllerNumber *multiplier_number)
configure important controller number components
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:497
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:580
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...
void set_next_prev_ignore_disabled_valves(bool ignore_disabled)
enable/disable skipping of disabled valves by the next and previous actions
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:561
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:543
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 set_controller_auto_adv_switch(SprinklerControllerSwitch *auto_adv_switch)
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
optional< size_t > manual_valve()
returns the number of the valve that is manually selected, if any.
void add_valve(SprinklerControllerSwitch *valve_sw, SprinklerControllerSwitch *enable_sw=nullptr)
add a valve to the controller
SprinklerControllerSwitch * standby_sw_
Definition sprinkler.h:568
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()
void set_request_from(SprinklerValveRunRequestOrigin origin)
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:56
optional< bool > get_initial_state_with_restore_mode()
Returns the initial state of the switch, after applying restore mode rules.
Definition switch.cpp:42
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:29
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 core.cpp:25
Application App
Global storage of Application pointer - only one Application can exist.
const nullopt_t nullopt((nullopt_t::init()))
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