ESPHome 2026.5.0b1
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
217 if ((this->controller_ == nullptr) || (this->valve_ == nullptr)) {
218 return nullptr;
219 }
220 if (this->valve_->pump_switch_index.has_value()) {
222 }
223 return nullptr;
224}
225
227 auto *pump = this->pump_switch();
228 if ((this->valve_ == nullptr) || (pump == nullptr)) { // safety first!
229 return;
230 }
231 if (this->controller_ == nullptr) { // safety first!
232 pump->turn_off(); // if no controller was set, just switch off the pump
233 } else { // ...otherwise, do it "safely"
234 auto state = this->state_; // this is silly, but...
235 this->state_ = BYPASS; // ...exclude me from the pump-in-use check that set_pump_state() does
236 this->controller_->set_pump_state(pump, false);
237 this->state_ = state;
238 }
239}
240
242 auto *pump = this->pump_switch();
243 if ((this->valve_ == nullptr) || (pump == nullptr)) { // safety first!
244 return;
245 }
246 if (this->controller_ == nullptr) { // safety first!
247 pump->turn_on(); // if no controller was set, just switch on the pump
248 } else { // ...otherwise, do it "safely"
249 auto state = this->state_; // this is silly, but...
250 this->state_ = BYPASS; // ...exclude me from the pump-in-use check that set_pump_state() does
251 this->controller_->set_pump_state(pump, true);
252 this->state_ = state;
253 }
254}
255
257 if ((this->valve_ == nullptr) || (this->valve_->valve_switch == nullptr)) { // safety first!
258 return;
259 }
260 if (this->valve_->valve_switch->state) {
261 this->valve_->valve_switch->turn_off();
262 }
263}
264
266 if ((this->valve_ == nullptr) || (this->valve_->valve_switch == nullptr)) { // safety first!
267 return;
268 }
269 if (!this->valve_->valve_switch->state) {
270 this->valve_->valve_switch->turn_on();
271 }
272}
273
275 this->state_ = IDLE;
276 this->valve_off_();
277 this->pump_off_();
278}
279
281 this->state_ = ACTIVE;
282 this->valve_on_();
283 this->pump_on_();
284}
285
288 SprinklerValveOperator *valve_op)
289 : valve_number_(valve_number), run_duration_(run_duration), valve_op_(valve_op) {}
290
292bool SprinklerValveRunRequest::has_valve_operator() { return !(this->valve_op_ == nullptr); }
293
295
297
298void SprinklerValveRunRequest::set_valve(size_t valve_number) {
299 this->valve_number_ = valve_number;
300 this->run_duration_ = 0;
301 this->valve_op_ = nullptr;
302 this->has_valve_ = true;
303}
304
306 if (valve_op != nullptr) {
307 this->valve_op_ = valve_op;
308 }
309}
310
312 this->has_valve_ = false;
313 this->origin_ = USER;
314 this->run_duration_ = 0;
315 this->valve_op_ = nullptr;
316}
317
319
321
323 if (this->has_valve_) {
324 return this->valve_number_;
325 }
326 return nullopt;
327}
328
330
332
334Sprinkler::Sprinkler(const char *name) : name_(name) {
335 // The `name` is stored for dump_config logging
336 this->timer_.init(2);
337 // Timer names only need to be unique within this component instance
338 this->timer_.push_back({"sm", false, 0, 0, [this]() { this->sm_timer_callback_(); }});
339 this->timer_.push_back({"vs", false, 0, 0, [this]() { this->valve_selection_callback_(); }});
340}
341
343 this->all_valves_off_(true);
344 // Start with loop disabled - nothing to do when idle
345 this->disable_loop();
346}
347
349 for (auto &vo : this->valve_op_) {
350 vo.loop();
351 }
352 if (this->prev_req_.has_request()) {
353 if (this->prev_req_.has_valve_operator() && this->prev_req_.valve_operator()->state() == IDLE) {
354 this->prev_req_.reset();
355 }
356 } else if (this->state_ == IDLE) {
357 // Nothing more to do - disable loop until next activation
358 this->disable_loop();
359 }
360}
361
363 auto new_valve_number = this->number_of_valves();
364 this->valve_.resize(new_valve_number + 1);
365 SprinklerValve *new_valve = &this->valve_[new_valve_number];
366
367 new_valve->controller_switch = valve_sw;
368 new_valve->controller_switch->set_state_lambda([this, new_valve_number]() -> optional<bool> {
369 auto *valve = this->valve_switch(new_valve_number);
370 auto *pump = this->valve_pump_switch(new_valve_number);
371 if (valve == nullptr) {
372 return false;
373 }
374 if (pump != nullptr) {
375 return valve->state && pump->state;
376 }
377 return valve->state;
378 });
379
380 new_valve->valve_turn_off_automation =
381 make_unique<Automation<>>(new_valve->controller_switch->get_turn_off_trigger());
382 new_valve->valve_shutdown_action = make_unique<sprinkler::ShutdownAction<>>(this);
383 new_valve->valve_turn_off_automation->add_actions({new_valve->valve_shutdown_action.get()});
384
385 new_valve->valve_turn_on_automation = make_unique<Automation<>>(new_valve->controller_switch->get_turn_on_trigger());
386 new_valve->valve_resumeorstart_action = make_unique<sprinkler::StartSingleValveAction<>>(this);
387 new_valve->valve_resumeorstart_action->set_valve_to_start(new_valve_number);
388 new_valve->valve_turn_on_automation->add_actions({new_valve->valve_resumeorstart_action.get()});
389
390 if (enable_sw != nullptr) {
391 new_valve->enable_switch = enable_sw;
392 }
393}
394
395void Sprinkler::add_controller(Sprinkler *other_controller) { this->other_controllers_.push_back(other_controller); }
396
398 this->controller_sw_ = controller_switch;
399 controller_switch->set_state_lambda([this]() -> optional<bool> {
400 for (size_t valve_number = 0; valve_number < this->number_of_valves(); valve_number++) {
401 if (this->valve_[valve_number].controller_switch->state) {
402 return true;
403 }
404 }
405 return this->active_req_.has_request();
406 });
407
408 this->sprinkler_turn_off_automation_ = make_unique<Automation<>>(controller_switch->get_turn_off_trigger());
409 this->sprinkler_shutdown_action_ = make_unique<sprinkler::ShutdownAction<>>(this);
411
412 this->sprinkler_turn_on_automation_ = make_unique<Automation<>>(controller_switch->get_turn_on_trigger());
413 this->sprinkler_resumeorstart_action_ = make_unique<sprinkler::ResumeOrStartAction<>>(this);
415}
416
418 this->auto_adv_sw_ = auto_adv_switch;
419}
420
422 this->queue_enable_sw_ = queue_enable_switch;
423}
424
426 this->reverse_sw_ = reverse_switch;
427}
428
430 this->standby_sw_ = standby_switch;
431
432 this->sprinkler_standby_turn_on_automation_ = make_unique<Automation<>>(standby_switch->get_turn_on_trigger());
433 this->sprinkler_standby_shutdown_action_ = make_unique<sprinkler::ShutdownAction<>>(this);
435}
436
438 this->multiplier_number_ = multiplier_number;
439}
440
442 this->repeat_number_ = repeat_number;
443}
444
445void Sprinkler::configure_valve_switch(size_t valve_number, switch_::Switch *valve_switch, uint32_t run_duration) {
446 if (this->is_a_valid_valve(valve_number)) {
447 this->valve_[valve_number].valve_switch = valve_switch;
448 this->valve_[valve_number].run_duration = run_duration;
449 }
450}
451
452void Sprinkler::configure_valve_pump_switch(size_t valve_number, switch_::Switch *pump_switch) {
453 if (this->is_a_valid_valve(valve_number)) {
454 for (size_t i = 0; i < this->pump_.size(); i++) { // check each existing registered pump
455 if (this->pump_[i] == pump_switch) { // if the "new" pump matches one we already have...
456 this->valve_[valve_number].pump_switch_index = i; // ...save its index in the pump vector...
457 return; // ...and we are done
458 }
459 } // if we end up here, no pumps matched, so add a new one
460 this->pump_.push_back(pump_switch);
461 this->valve_[valve_number].pump_switch_index = this->pump_.size() - 1; // save the index to the new pump
462 }
463}
464
466 SprinklerControllerNumber *run_duration_number) {
467 if (this->is_a_valid_valve(valve_number)) {
468 this->valve_[valve_number].run_duration_number = run_duration_number;
469 }
470}
471
472void Sprinkler::set_divider(optional<uint32_t> divider) {
473 if (!divider.has_value()) {
474 return;
475 }
476 if (divider.value() > 0) {
477 this->set_multiplier(1.0 / divider.value());
478 this->set_repeat(divider.value() - 1);
479 } else if (divider.value() == 0) {
480 this->set_multiplier(1.0);
481 this->set_repeat(0);
482 }
483}
484
485void Sprinkler::set_multiplier(const optional<float> multiplier) {
486 if ((!multiplier.has_value()) || (multiplier.value() < 0)) {
487 return;
488 }
489 this->multiplier_ = multiplier.value();
490 if (this->multiplier_number_ == nullptr) {
491 return;
492 }
493 if (this->multiplier_number_->state == multiplier.value()) {
494 return;
495 }
496 auto call = this->multiplier_number_->make_call();
497 call.set_value(multiplier.value());
498 call.perform();
499}
500
502 this->next_prev_ignore_disabled_ = ignore_disabled;
503}
504
506 this->start_delay_is_valve_delay_ = false;
507 this->start_delay_ = start_delay;
508}
509
511 this->stop_delay_is_valve_delay_ = false;
512 this->stop_delay_ = stop_delay;
513}
514
516 this->start_delay_is_valve_delay_ = true;
517 this->start_delay_ = start_delay;
518}
519
521 this->stop_delay_is_valve_delay_ = true;
522 this->stop_delay_ = stop_delay;
523}
524
525void Sprinkler::set_pump_switch_off_during_valve_open_delay(bool pump_switch_off_during_valve_open_delay) {
526 this->pump_switch_off_during_valve_open_delay_ = pump_switch_off_during_valve_open_delay;
527}
528
529void Sprinkler::set_valve_open_delay(const uint32_t valve_open_delay) {
530 if (valve_open_delay > 0) {
531 this->valve_overlap_ = false;
532 this->switching_delay_ = valve_open_delay;
533 } else {
534 this->switching_delay_.reset();
535 }
536}
537
539 if (valve_overlap > 0) {
540 this->valve_overlap_ = true;
541 this->switching_delay_ = valve_overlap;
542 } else {
543 this->switching_delay_.reset();
544 }
545 this->pump_switch_off_during_valve_open_delay_ = false; // incompatible option
546}
547
549 if (manual_selection_delay > 0) {
550 this->manual_selection_delay_ = manual_selection_delay;
551 } else {
552 this->manual_selection_delay_.reset();
553 }
554}
555
556void Sprinkler::set_valve_run_duration(const optional<size_t> valve_number, const optional<uint32_t> run_duration) {
557 if (!valve_number.has_value() || !run_duration.has_value()) {
558 return;
559 }
560 if (!this->is_a_valid_valve(valve_number.value())) {
561 return;
562 }
563 this->valve_[valve_number.value()].run_duration = run_duration.value();
564 if (this->valve_[valve_number.value()].run_duration_number == nullptr) {
565 return;
566 }
567 if (this->valve_[valve_number.value()].run_duration_number->state == run_duration.value()) {
568 return;
569 }
570 auto call = this->valve_[valve_number.value()].run_duration_number->make_call();
571 if (this->valve_[valve_number.value()].run_duration_number->get_unit_of_measurement_ref() == MIN_STR) {
572 call.set_value(run_duration.value() / 60.0);
573 } else {
574 call.set_value(run_duration.value());
575 }
576 call.perform();
577}
578
579void Sprinkler::set_auto_advance(const bool auto_advance) {
580 if (this->auto_adv_sw_ == nullptr) {
581 return;
582 }
583 if (this->auto_adv_sw_->state == auto_advance) {
584 return;
585 }
586 if (auto_advance) {
587 this->auto_adv_sw_->turn_on();
588 } else {
589 this->auto_adv_sw_->turn_off();
590 }
591}
592
593void Sprinkler::set_repeat(optional<uint32_t> repeat) {
594 this->target_repeats_ = repeat;
595 if (this->repeat_number_ == nullptr) {
596 return;
597 }
598 if (this->repeat_number_->state == repeat.value_or(0)) {
599 return;
600 }
601 auto call = this->repeat_number_->make_call();
602 call.set_value(repeat.value_or(0));
603 call.perform();
604}
605
606void Sprinkler::set_queue_enable(bool queue_enable) {
607 if (this->queue_enable_sw_ == nullptr) {
608 return;
609 }
610 if (this->queue_enable_sw_->state == queue_enable) {
611 return;
612 }
613 if (queue_enable) {
614 this->queue_enable_sw_->turn_on();
615 } else {
616 this->queue_enable_sw_->turn_off();
617 }
618}
619
620void Sprinkler::set_reverse(const bool reverse) {
621 if (this->reverse_sw_ == nullptr) {
622 return;
623 }
624 if (this->reverse_sw_->state == reverse) {
625 return;
626 }
627 if (reverse) {
628 this->reverse_sw_->turn_on();
629 } else {
630 this->reverse_sw_->turn_off();
631 }
632}
633
634void Sprinkler::set_standby(const bool standby) {
635 if (this->standby_sw_ == nullptr) {
636 return;
637 }
638 if (this->standby_sw_->state == standby) {
639 return;
640 }
641 if (standby) {
642 this->standby_sw_->turn_on();
643 } else {
644 this->standby_sw_->turn_off();
645 }
646}
647
648uint32_t Sprinkler::valve_run_duration(const size_t valve_number) {
649 if (!this->is_a_valid_valve(valve_number)) {
650 return 0;
651 }
652 if (this->valve_[valve_number].run_duration_number != nullptr) {
653 if (this->valve_[valve_number].run_duration_number->get_unit_of_measurement_ref() == MIN_STR) {
654 return static_cast<uint32_t>(roundf(this->valve_[valve_number].run_duration_number->state * 60));
655 } else {
656 return static_cast<uint32_t>(roundf(this->valve_[valve_number].run_duration_number->state));
657 }
658 }
659 return this->valve_[valve_number].run_duration;
660}
661
663 uint32_t run_duration = 0;
664
665 if (this->is_a_valid_valve(valve_number)) {
666 run_duration = this->valve_run_duration(valve_number);
667 }
668 run_duration = static_cast<uint32_t>(roundf(run_duration * this->multiplier()));
669 // run_duration must not be less than any of these
670 if ((run_duration < this->start_delay_) || (run_duration < this->stop_delay_) ||
671 (run_duration < this->switching_delay_.value_or(0) * 2)) {
672 return std::max({this->switching_delay_.value_or(0) * 2, this->start_delay_, this->stop_delay_});
673 }
674 return run_duration;
675}
676
678 if (this->auto_adv_sw_ != nullptr) {
679 return this->auto_adv_sw_->state;
680 }
681 return true;
682}
683
685 if (this->multiplier_number_ != nullptr) {
686 return this->multiplier_number_->state;
687 }
688 return this->multiplier_;
689}
690
691optional<uint32_t> Sprinkler::repeat() {
692 if (this->repeat_number_ != nullptr) {
693 return static_cast<uint32_t>(roundf(this->repeat_number_->state));
694 }
695 return this->target_repeats_;
696}
697
698optional<uint32_t> Sprinkler::repeat_count() {
699 // if there is an active valve and auto-advance is enabled, we may be repeating, so return the count
700 if (this->active_req_.has_request() && this->auto_advance()) {
701 return this->repeat_count_;
702 }
703 return nullopt;
704}
705
707 if (this->queue_enable_sw_ != nullptr) {
708 return this->queue_enable_sw_->state;
709 }
710 return true;
711}
712
714 if (this->reverse_sw_ != nullptr) {
715 return this->reverse_sw_->state;
716 }
717 return false;
718}
719
721 if (this->standby_sw_ != nullptr) {
722 return this->standby_sw_->state;
723 }
724 return false;
725}
726
728 if (this->standby()) {
729 this->log_standby_warning_(LOG_STR("start_from_queue"));
730 return;
731 }
732 if (this->multiplier() == 0) {
733 this->log_multiplier_zero_warning_(LOG_STR("start_from_queue"));
734 return;
735 }
736 if (this->queued_valves_.empty()) {
737 return; // if there is nothing in the queue, don't do anything
738 }
739 if (this->queue_enabled() && this->active_valve().has_value()) {
740 return; // if there is already a valve running from the queue, do nothing
741 }
742
743 this->set_auto_advance(false);
744 this->set_queue_enable(true);
745
746 this->reset_cycle_states_(); // just in case auto-advance is switched on later
747 this->repeat_count_ = 0;
748 this->fsm_kick_(); // will automagically pick up from the queue (it has priority)
749}
750
752 if (this->standby()) {
753 this->log_standby_warning_(LOG_STR("start_full_cycle"));
754 return;
755 }
756 if (this->multiplier() == 0) {
757 this->log_multiplier_zero_warning_(LOG_STR("start_full_cycle"));
758 return;
759 }
760 if (this->auto_advance() && this->active_valve().has_value()) {
761 return; // if auto-advance is already enabled and there is already a valve running, do nothing
762 }
763
764 this->set_queue_enable(false);
765
766 this->prep_full_cycle_();
767 this->repeat_count_ = 0;
768 // if there is no active valve already, start the first valve in the cycle
769 if (!this->active_req_.has_request()) {
770 this->fsm_kick_();
771 }
772}
773
774void Sprinkler::start_single_valve(const optional<size_t> valve_number, optional<uint32_t> run_duration) {
775 if (this->standby()) {
776 this->log_standby_warning_(LOG_STR("start_single_valve"));
777 return;
778 }
779 if (this->multiplier() == 0) {
780 this->log_multiplier_zero_warning_(LOG_STR("start_single_valve"));
781 return;
782 }
783 if (!valve_number.has_value() || (valve_number == this->active_valve())) {
784 return;
785 }
786
787 this->set_auto_advance(false);
788 this->set_queue_enable(false);
789
790 this->reset_cycle_states_(); // just in case auto-advance is switched on later
791 this->repeat_count_ = 0;
792 this->fsm_request_(valve_number.value(), run_duration.value_or(0));
793}
794
795void Sprinkler::queue_valve(optional<size_t> valve_number, optional<uint32_t> run_duration) {
796 if (valve_number.has_value()) {
797 if (this->is_a_valid_valve(valve_number.value()) && (this->queued_valves_.size() < this->max_queue_size_)) {
798 SprinklerQueueItem item{valve_number.value(), run_duration.value_or(0)};
799 this->queued_valves_.insert(this->queued_valves_.begin(), item);
800 ESP_LOGD(TAG, "Valve %zu placed into queue with run duration of %" PRIu32 " seconds", valve_number.value_or(0),
801 run_duration.value_or(0));
802 }
803 }
804}
805
807 this->queued_valves_.clear();
808 ESP_LOGD(TAG, "Queue cleared");
809}
810
812 if (this->standby()) {
813 this->log_standby_warning_(LOG_STR("next_valve"));
814 return;
815 }
816
817 if (this->state_ == IDLE) {
818 this->reset_cycle_states_(); // just in case auto-advance is switched on later
819 }
820
821 this->manual_valve_ = this->next_valve_number_(
822 this->manual_valve_.value_or(this->active_req_.valve_as_opt().value_or(this->number_of_valves() - 1)),
823 !this->next_prev_ignore_disabled_, true);
824
825 if (!this->manual_valve_.has_value()) {
826 ESP_LOGD(TAG, "next_valve was called but no valve could be started; perhaps next_prev_ignore_disabled allows only "
827 "enabled valves and no valves are enabled?");
828 return;
829 }
830
831 if (this->manual_selection_delay_.has_value()) {
834 } else {
835 this->fsm_request_(this->manual_valve_.value());
836 }
837}
838
840 if (this->standby()) {
841 this->log_standby_warning_(LOG_STR("previous_valve"));
842 return;
843 }
844
845 if (this->state_ == IDLE) {
846 this->reset_cycle_states_(); // just in case auto-advance is switched on later
847 }
848
849 this->manual_valve_ =
850 this->previous_valve_number_(this->manual_valve_.value_or(this->active_req_.valve_as_opt().value_or(0)),
851 !this->next_prev_ignore_disabled_, true);
852
853 if (!this->manual_valve_.has_value()) {
854 ESP_LOGD(TAG, "previous_valve was called but no valve could be started; perhaps next_prev_ignore_disabled allows "
855 "only enabled valves and no valves are enabled?");
856 return;
857 }
858
859 if (this->manual_selection_delay_.has_value()) {
862 } else {
863 this->fsm_request_(this->manual_valve_.value());
864 }
865}
866
867void Sprinkler::shutdown(bool clear_queue) {
869 this->active_req_.reset();
870 this->manual_valve_.reset();
871 this->next_req_.reset();
872 for (auto &vo : this->valve_op_) {
873 vo.stop();
874 }
876 if (clear_queue) {
877 this->clear_queued_valves();
878 this->repeat_count_ = 0;
879 }
880}
881
883 if (this->paused_valve_.has_value() || !this->active_req_.has_request()) {
884 return; // we can't pause if we're already paused or if there is no active valve
885 }
886 this->paused_valve_ = this->active_valve();
888 this->shutdown(false);
889 ESP_LOGD(TAG, "Paused valve %zu with %" PRIu32 " seconds remaining", this->paused_valve_.value_or(0),
890 this->resume_duration_.value_or(0));
891}
892
894 if (this->standby()) {
895 this->log_standby_warning_(LOG_STR("resume"));
896 return;
897 }
898
899 if (this->paused_valve_.has_value() && (this->resume_duration_.has_value())) {
900 const size_t paused_valve = *this->paused_valve_;
901 const uint32_t resume_duration = *this->resume_duration_;
902 // Resume only if valve has not been completed yet
903 if (!this->valve_cycle_complete_(paused_valve)) {
904 ESP_LOGD(TAG, "Resuming valve %zu with %" PRIu32 " seconds remaining", paused_valve, resume_duration);
905 this->fsm_request_(paused_valve, resume_duration);
906 }
907 this->reset_resume();
908 } else {
909 ESP_LOGD(TAG, "No valve to resume!");
910 }
911}
912
914 if (this->paused_valve_.has_value() && (this->resume_duration_.has_value())) {
915 this->resume();
916 } else {
917 this->start_full_cycle();
918 }
919}
920
922 this->paused_valve_.reset();
923 this->resume_duration_.reset();
924}
925
926const char *Sprinkler::valve_name(const size_t valve_number) {
927 if (this->is_a_valid_valve(valve_number)) {
928 return this->valve_[valve_number].controller_switch->get_name().c_str();
929 }
930 return nullptr;
931}
932
933optional<SprinklerValveRunRequestOrigin> Sprinkler::active_valve_request_is_from() {
934 if (this->active_req_.has_request()) {
935 return this->active_req_.request_is_from();
936 }
937 return nullopt;
938}
939
940optional<size_t> Sprinkler::active_valve() {
941 if (!this->valve_overlap_ && this->prev_req_.has_request() && this->prev_req_.has_valve_operator() &&
942 (this->prev_req_.valve_operator()->state() == STARTING || this->prev_req_.valve_operator()->state() == ACTIVE)) {
943 return this->prev_req_.valve_as_opt();
944 }
945 return this->active_req_.valve_as_opt();
946}
947
948optional<size_t> Sprinkler::paused_valve() { return this->paused_valve_; }
949
950optional<size_t> Sprinkler::queued_valve() {
951 if (!this->queued_valves_.empty()) {
952 return this->queued_valves_.back().valve_number;
953 }
954 return nullopt;
955}
956
957optional<size_t> Sprinkler::manual_valve() { return this->manual_valve_; }
958
959size_t Sprinkler::number_of_valves() { return this->valve_.size(); }
960
961bool Sprinkler::is_a_valid_valve(const size_t valve_number) { return (valve_number < this->number_of_valves()); }
962
964 if (pump_switch == nullptr) {
965 return false; // we can't do anything if there's nothing to check
966 }
967 // a pump must be considered "in use" if a (distribution) valve it supplies is active. this means:
968 // - at least one SprinklerValveOperator:
969 // - has a valve loaded that depends on this pump
970 // - is in a state that depends on the pump: (ACTIVE and _possibly_ STARTING/STOPPING)
971 // - if NO SprinklerValveOperator is active but there is a run request pending (active_req_.has_request()) and the
972 // controller state is STARTING, valve open delay is configured but NOT pump_switch_off_during_valve_open_delay_
973 for (auto &vo : this->valve_op_) { // first, check if any SprinklerValveOperator has a valve dependent on this pump
974 if ((vo.state() != BYPASS) && (vo.pump_switch() != nullptr)) {
975 // the SprinklerValveOperator is configured with a pump; now check if it is the pump of interest
976 if (vo.pump_switch() == pump_switch) {
977 // now if the SprinklerValveOperator has a pump and it is either ACTIVE, is STARTING with a valve delay or
978 // is STOPPING with a valve delay, its pump can be considered "in use", so just return indicating this now
979 if ((vo.state() == ACTIVE) ||
980 ((vo.state() == STARTING) && this->start_delay_ && this->start_delay_is_valve_delay_) ||
981 ((vo.state() == STOPPING) && this->stop_delay_ && this->stop_delay_is_valve_delay_)) {
982 return true;
983 }
984 }
985 }
986 } // if we end up here, no SprinklerValveOperator was in a "give-away" state indicating that the pump is in use...
987 if (!this->valve_overlap_ && !this->pump_switch_off_during_valve_open_delay_ && this->switching_delay_.has_value() &&
988 this->active_req_.has_request() && (this->state_ != STOPPING)) {
989 // ...the controller is configured to keep the pump on during a valve open delay, so just return
990 // whether or not the next valve shares the same pump
991 auto *valve_pump = this->valve_pump_switch(this->active_req_.valve());
992 if (valve_pump == nullptr) {
993 return false; // valve has no pump, so this pump isn't in use by it
994 }
995 return pump_switch == valve_pump;
996 }
997 return false;
998}
999
1001 if (pump_switch == nullptr) {
1002 return; // we can't do anything if there's nothing to check
1003 }
1004
1005 bool hold_pump_on = false;
1006
1007 for (auto &controller : this->other_controllers_) { // check if the pump is in use by another controller
1008 if (controller != this) { // dummy check
1009 if (controller->pump_in_use(pump_switch)) {
1010 hold_pump_on = true; // if another controller says it's using this pump, keep it on
1011 }
1012 }
1013 }
1014 if (hold_pump_on) {
1015 ESP_LOGD(TAG, "Leaving pump on because another controller instance is using it");
1016 }
1017
1018 if (state) { // ...and now we can set the new state of the switch
1019 pump_switch->turn_on();
1020 } else if (!hold_pump_on && !this->pump_in_use(pump_switch)) {
1021 pump_switch->turn_off();
1022 }
1023}
1024
1026 uint32_t total_time_remaining = 0;
1027
1028 for (size_t valve = 0; valve < this->number_of_valves(); valve++) {
1029 total_time_remaining += this->valve_run_duration_adjusted(valve);
1030 }
1031
1032 if (this->valve_overlap_) {
1033 total_time_remaining -= this->switching_delay_.value_or(0) * (this->number_of_valves() - 1);
1034 } else {
1035 total_time_remaining += this->switching_delay_.value_or(0) * (this->number_of_valves() - 1);
1036 }
1037
1038 return total_time_remaining;
1039}
1040
1042 uint32_t total_time_remaining = 0;
1043 uint32_t valve_count = 0;
1044
1045 for (size_t valve = 0; valve < this->number_of_valves(); valve++) {
1046 if (this->valve_is_enabled_(valve)) {
1047 total_time_remaining += this->valve_run_duration_adjusted(valve);
1048 valve_count++;
1049 }
1050 }
1051
1052 if (valve_count) {
1053 if (this->valve_overlap_) {
1054 total_time_remaining -= this->switching_delay_.value_or(0) * (valve_count - 1);
1055 } else {
1056 total_time_remaining += this->switching_delay_.value_or(0) * (valve_count - 1);
1057 }
1058 }
1059
1060 return total_time_remaining;
1061}
1062
1064 uint32_t total_time_remaining = 0;
1065 uint32_t enabled_valve_count = 0;
1066 uint32_t incomplete_valve_count = 0;
1067
1068 for (size_t valve = 0; valve < this->number_of_valves(); valve++) {
1069 if (this->valve_is_enabled_(valve)) {
1070 enabled_valve_count++;
1071 if (!this->valve_cycle_complete_(valve)) {
1072 auto active = this->active_valve();
1073 if (!active.has_value() || (valve != *active)) {
1074 total_time_remaining += this->valve_run_duration_adjusted(valve);
1075 incomplete_valve_count++;
1076 } else {
1077 // to get here, there must be an active valve and this valve must be equal to 'valve'
1078 if (this->active_req_.valve_operator() == nullptr) { // no SVO has been assigned yet so it hasn't started
1079 total_time_remaining += this->valve_run_duration_adjusted(valve);
1080 incomplete_valve_count++;
1081 }
1082 }
1083 }
1084 }
1085 }
1086
1087 if (incomplete_valve_count > 0 && incomplete_valve_count >= enabled_valve_count) {
1088 incomplete_valve_count--;
1089 }
1090 if (incomplete_valve_count) {
1091 if (this->valve_overlap_) {
1092 total_time_remaining -= this->switching_delay_.value_or(0) * incomplete_valve_count;
1093 } else {
1094 total_time_remaining += this->switching_delay_.value_or(0) * incomplete_valve_count;
1095 }
1096 }
1097
1098 return total_time_remaining;
1099}
1100
1102 uint32_t total_time_remaining = 0;
1103 uint32_t valve_count = 0;
1104
1105 for (auto &valve : this->queued_valves_) {
1106 if (valve.run_duration) {
1107 total_time_remaining += valve.run_duration;
1108 } else {
1109 total_time_remaining += this->valve_run_duration_adjusted(valve.valve_number);
1110 }
1111 valve_count++;
1112 }
1113
1114 if (valve_count) {
1115 if (this->valve_overlap_) {
1116 total_time_remaining -= this->switching_delay_.value_or(0) * (valve_count - 1);
1117 } else {
1118 total_time_remaining += this->switching_delay_.value_or(0) * (valve_count - 1);
1119 }
1120 }
1121
1122 return total_time_remaining;
1123}
1124
1126 if (this->active_req_.has_request()) { // first try to return the value based on active_req_...
1127 if (this->active_req_.valve_operator() != nullptr) {
1128 return this->active_req_.valve_operator()->time_remaining();
1129 }
1130 }
1131 if (this->prev_req_.has_request()) { // try to return the value based on prev_req_...
1132 if (this->prev_req_.valve_operator() != nullptr) {
1133 return this->prev_req_.valve_operator()->time_remaining();
1134 }
1135 }
1136 return nullopt;
1137}
1138
1140 if (!this->time_remaining_active_valve().has_value() && this->state_ == IDLE) {
1141 return nullopt;
1142 }
1143
1144 auto total_time_remaining = this->time_remaining_active_valve().value_or(0);
1145 if (this->auto_advance()) {
1146 total_time_remaining += this->total_cycle_time_enabled_incomplete_valves();
1147 if (this->repeat().value_or(0) > 0) {
1148 total_time_remaining +=
1149 (this->total_cycle_time_enabled_valves() * (this->repeat().value_or(0) - this->repeat_count().value_or(0)));
1150 }
1151 }
1152
1153 if (this->queue_enabled()) {
1154 total_time_remaining += this->total_queue_time();
1155 }
1156 return total_time_remaining;
1157}
1158
1160 if (this->state_ != IDLE) {
1161 return true;
1162 }
1163
1164 for (auto &controller : this->other_controllers_) {
1165 if (controller != this) { // dummy check
1166 if (controller->controller_state() != IDLE) {
1167 return true;
1168 }
1169 }
1170 }
1171 return false;
1172}
1173
1175 if (this->is_a_valid_valve(valve_number)) {
1176 return this->valve_[valve_number].controller_switch;
1177 }
1178 return nullptr;
1179}
1180
1182 if (this->is_a_valid_valve(valve_number)) {
1183 return this->valve_[valve_number].enable_switch;
1184 }
1185 return nullptr;
1186}
1187
1188switch_::Switch *Sprinkler::valve_switch(const size_t valve_number) {
1189 if (this->is_a_valid_valve(valve_number)) {
1190 return this->valve_[valve_number].valve_switch;
1191 }
1192 return nullptr;
1193}
1194
1196 if (this->is_a_valid_valve(valve_number)) {
1197 auto idx = this->valve_[valve_number].pump_switch_index;
1198 if (idx.has_value()) {
1199 return this->pump_[*idx];
1200 }
1201 }
1202 return nullptr;
1203}
1204
1206 if (pump_index < this->pump_.size()) {
1207 return this->pump_[pump_index];
1208 }
1209 return nullptr;
1210}
1211
1212bool Sprinkler::valve_is_enabled_(const size_t valve_number) {
1213 if (this->is_a_valid_valve(valve_number)) {
1214 if (this->valve_[valve_number].enable_switch != nullptr) {
1215 return this->valve_[valve_number].enable_switch->state;
1216 } else {
1217 return true;
1218 }
1219 }
1220 return false;
1221}
1222
1223void Sprinkler::mark_valve_cycle_complete_(const size_t valve_number) {
1224 if (this->is_a_valid_valve(valve_number)) {
1225 ESP_LOGD(TAG, "Marking valve %u complete", valve_number);
1226 this->valve_[valve_number].valve_cycle_complete = true;
1227 }
1228}
1229
1230bool Sprinkler::valve_cycle_complete_(const size_t valve_number) {
1231 if (this->is_a_valid_valve(valve_number)) {
1232 return this->valve_[valve_number].valve_cycle_complete;
1233 }
1234 return false;
1235}
1236
1237optional<size_t> Sprinkler::next_valve_number_(const optional<size_t> first_valve, const bool include_disabled,
1238 const bool include_complete) {
1239 auto valve = first_valve.value_or(0);
1240 size_t start = first_valve.has_value() ? 1 : 0;
1241
1242 if (!this->is_a_valid_valve(valve)) {
1243 valve = 0;
1244 }
1245
1246 for (size_t offset = start; offset < this->number_of_valves(); offset++) {
1247 auto valve_of_interest = valve + offset;
1248 if (!this->is_a_valid_valve(valve_of_interest)) {
1249 valve_of_interest -= this->number_of_valves();
1250 }
1251
1252 if ((this->valve_is_enabled_(valve_of_interest) || include_disabled) &&
1253 (!this->valve_cycle_complete_(valve_of_interest) || include_complete)) {
1254 return valve_of_interest;
1255 }
1256 }
1257 return nullopt;
1258}
1259
1260optional<size_t> Sprinkler::previous_valve_number_(const optional<size_t> first_valve, const bool include_disabled,
1261 const bool include_complete) {
1262 auto valve = first_valve.value_or(this->number_of_valves() - 1);
1263 size_t start = first_valve.has_value() ? 1 : 0;
1264
1265 if (!this->is_a_valid_valve(valve)) {
1266 valve = this->number_of_valves() - 1;
1267 }
1268
1269 for (size_t offset = start; offset < this->number_of_valves(); offset++) {
1270 auto valve_of_interest = valve - offset;
1271 if (!this->is_a_valid_valve(valve_of_interest)) {
1272 valve_of_interest += this->number_of_valves();
1273 }
1274
1275 if ((this->valve_is_enabled_(valve_of_interest) || include_disabled) &&
1276 (!this->valve_cycle_complete_(valve_of_interest) || include_complete)) {
1277 return valve_of_interest;
1278 }
1279 }
1280 return nullopt;
1281}
1282
1283optional<size_t> Sprinkler::next_valve_number_in_cycle_(const optional<size_t> first_valve) {
1284 if (this->reverse()) {
1285 return this->previous_valve_number_(first_valve, false, false);
1286 }
1287 return this->next_valve_number_(first_valve, false, false);
1288}
1289
1290void Sprinkler::load_next_valve_run_request_(const optional<size_t> first_valve) {
1291 if (this->active_req_.has_request()) {
1292 this->prev_req_ = this->active_req_;
1293 } else {
1294 this->prev_req_.reset();
1295 }
1296
1297 if (this->next_req_.has_request()) {
1298 if (!this->next_req_.run_duration()) { // ensure the run duration is set correctly for consumption later on
1300 }
1301 return; // there is already a request pending
1302 } else if (this->queue_enabled() && !this->queued_valves_.empty()) {
1303 this->next_req_.set_valve(this->queued_valves_.back().valve_number);
1305 if (this->queued_valves_.back().run_duration) {
1306 this->next_req_.set_run_duration(this->queued_valves_.back().run_duration);
1307 this->queued_valves_.pop_back();
1308 } else if (this->multiplier()) {
1309 this->next_req_.set_run_duration(this->valve_run_duration_adjusted(this->queued_valves_.back().valve_number));
1310 this->queued_valves_.pop_back();
1311 } else {
1312 this->next_req_.reset();
1313 }
1314 } else if (this->auto_advance() && this->multiplier()) {
1315 if (this->next_valve_number_in_cycle_(first_valve).has_value()) {
1316 // if there is another valve to run as a part of a cycle, load that
1317 this->next_req_.set_valve(this->next_valve_number_in_cycle_(first_valve).value_or(0));
1320 this->valve_run_duration_adjusted(this->next_valve_number_in_cycle_(first_valve).value_or(0)));
1321 } else if ((this->repeat_count_++ < this->repeat().value_or(0))) {
1322 ESP_LOGD(TAG, "Repeating - starting cycle %" PRIu32 " of %" PRIu32, this->repeat_count_ + 1,
1323 this->repeat().value_or(0) + 1);
1324 // if there are repeats remaining and no more valves were left in the cycle, start a new cycle
1325 this->prep_full_cycle_();
1326 if (this->next_valve_number_in_cycle_().has_value()) { // this should always succeed here, but just in case...
1327 this->next_req_.set_valve(this->next_valve_number_in_cycle_().value_or(0));
1330 this->valve_run_duration_adjusted(this->next_valve_number_in_cycle_().value_or(0)));
1331 }
1332 }
1333 }
1334}
1335
1337 for (size_t valve_number = 0; valve_number < this->number_of_valves(); valve_number++) {
1338 if (this->valve_is_enabled_(valve_number))
1339 return true;
1340 }
1341 return false;
1342}
1343
1345 if (!req->has_request()) {
1346 return; // we can't do anything if the request contains nothing
1347 }
1348 if (!this->is_a_valid_valve(req->valve())) {
1349 return; // we can't do anything if the valve number isn't valid
1350 }
1351 // Enable loop to monitor valve operator states
1352 this->enable_loop();
1353 for (auto &vo : this->valve_op_) { // find the first available SprinklerValveOperator, load it and start it up
1354 if (vo.state() == IDLE) {
1355 auto run_duration = req->run_duration() ? req->run_duration() : this->valve_run_duration_adjusted(req->valve());
1356 ESP_LOGD(TAG, "%s is starting valve %zu for %" PRIu32 " seconds, cycle %" PRIu32 " of %" PRIu32,
1357 LOG_STR_ARG(this->req_as_str_(req->request_is_from())), req->valve(), run_duration,
1358 this->repeat_count_ + 1, this->repeat().value_or(0) + 1);
1359 req->set_valve_operator(&vo);
1360 vo.set_controller(this);
1361 vo.set_valve(&this->valve_[req->valve()]);
1362 vo.set_run_duration(run_duration);
1363 vo.set_start_delay(this->start_delay_, this->start_delay_is_valve_delay_);
1364 vo.set_stop_delay(this->stop_delay_, this->stop_delay_is_valve_delay_);
1365 vo.start();
1366 return;
1367 }
1368 }
1369}
1370
1371void Sprinkler::all_valves_off_(const bool include_pump) {
1372 for (size_t valve_index = 0; valve_index < this->number_of_valves(); valve_index++) {
1373 auto *valve_sw = this->valve_[valve_index].valve_switch;
1374 if ((valve_sw != nullptr) && valve_sw->state) {
1375 valve_sw->turn_off();
1376 }
1377 if (include_pump) {
1378 this->set_pump_state(this->valve_pump_switch(valve_index), false);
1379 }
1380 }
1381 ESP_LOGD(TAG, "All valves stopped%s", include_pump ? ", including pumps" : "");
1382}
1383
1385 this->set_auto_advance(true);
1386
1387 if (!this->any_valve_is_enabled_()) {
1388 for (auto &valve : this->valve_) {
1389 if (valve.enable_switch != nullptr) {
1390 if (!valve.enable_switch->state) {
1391 valve.enable_switch->turn_on();
1392 }
1393 }
1394 }
1395 }
1396 this->reset_cycle_states_();
1397}
1398
1400 for (auto &valve : this->valve_) {
1401 valve.valve_cycle_complete = false;
1402 }
1403}
1404
1405void Sprinkler::fsm_request_(size_t requested_valve, uint32_t requested_run_duration) {
1406 this->next_req_.set_valve(requested_valve);
1407 this->next_req_.set_run_duration(requested_run_duration);
1408 // if state is IDLE or ACTIVE, call fsm_transition_() to start it immediately;
1409 // otherwise, fsm_transition() will pick up next_req_ at the next appropriate transition
1410 this->fsm_kick_();
1411}
1412
1414 if ((this->state_ == IDLE) || (this->state_ == ACTIVE)) {
1415 this->fsm_transition_();
1416 }
1417}
1418
1420 ESP_LOGVV(TAG, "fsm_transition_ called; state is %s", LOG_STR_ARG(this->state_as_str_(this->state_)));
1421 switch (this->state_) {
1422 case IDLE: // the system was off -> start it up
1423 // advances to ACTIVE
1425 break;
1426
1427 case ACTIVE:
1428 // advances to STOPPING or ACTIVE (again)
1430 break;
1431
1432 case STARTING: {
1433 // follows valve open delay interval
1434 uint32_t timer_duration = this->active_req_.run_duration();
1435 if (timer_duration > this->switching_delay_.value_or(0)) {
1436 timer_duration -= this->switching_delay_.value_or(0);
1437 }
1438 this->set_timer_duration_(sprinkler::TIMER_SM, timer_duration);
1440 this->start_valve_(&this->active_req_);
1441 this->state_ = ACTIVE;
1442 if (this->next_req_.has_request()) {
1443 // another valve has been requested, so restart the timer so we pick it up quickly
1446 }
1447 break;
1448 }
1449
1450 case STOPPING:
1451 // stop_delay_ has elapsed so just shut everything off
1452 this->active_req_.reset();
1453 this->manual_valve_.reset();
1454 this->all_valves_off_(true);
1455 this->state_ = IDLE;
1456 break;
1457
1458 default:
1459 break;
1460 }
1461 if (this->next_req_.has_request() && (this->state_ == IDLE)) {
1462 // another valve has been requested, so restart the timer so we pick it up quickly
1465 }
1466 ESP_LOGVV(TAG, "fsm_transition_ complete; new state is %s", LOG_STR_ARG(this->state_as_str_(this->state_)));
1467}
1468
1471
1472 if (this->next_req_.has_request()) { // there is a valve to run...
1473 this->active_req_.set_valve(this->next_req_.valve());
1476 this->next_req_.reset();
1477
1478 uint32_t timer_duration = this->active_req_.run_duration();
1479 if (timer_duration > this->switching_delay_.value_or(0)) {
1480 timer_duration -= this->switching_delay_.value_or(0);
1481 }
1482 this->set_timer_duration_(sprinkler::TIMER_SM, timer_duration);
1484 this->start_valve_(&this->active_req_);
1485 this->state_ = ACTIVE;
1486 }
1487}
1488
1490 if (!this->active_req_.has_request()) { // dummy check...
1492 return;
1493 }
1494
1495 if (!this->timer_active_(sprinkler::TIMER_SM)) { // only flag the valve as "complete" if the timer finished
1496 if ((this->active_req_.request_is_from() == CYCLE) || (this->active_req_.request_is_from() == USER)) {
1498 }
1499 } else {
1500 ESP_LOGD(TAG, "Valve cycle interrupted - NOT flagging valve as complete and stopping current valve");
1501 for (auto &vo : this->valve_op_) {
1502 vo.stop();
1503 }
1504 }
1505
1507
1508 if (this->next_req_.has_request()) { // there is another valve to run...
1509 auto *active_pump = this->valve_pump_switch(this->active_req_.valve());
1510 auto *next_pump = this->valve_pump_switch(this->next_req_.valve());
1511 bool same_pump = (active_pump != nullptr) && (next_pump != nullptr) && (active_pump == next_pump);
1512
1513 this->active_req_.set_valve(this->next_req_.valve());
1516 this->next_req_.reset();
1517
1518 // this->state_ = ACTIVE; // state isn't changing
1519 if (this->valve_overlap_ || !this->switching_delay_.has_value()) {
1520 uint32_t timer_duration = this->active_req_.run_duration();
1521 if (timer_duration > this->switching_delay_.value_or(0)) {
1522 timer_duration -= this->switching_delay_.value_or(0);
1523 }
1524 this->set_timer_duration_(sprinkler::TIMER_SM, timer_duration);
1526 this->start_valve_(&this->active_req_);
1527 } else {
1528 this->set_timer_duration_(
1530 this->switching_delay_.value() * 2 +
1531 (this->pump_switch_off_during_valve_open_delay_ && same_pump ? this->stop_delay_ : 0));
1533 this->state_ = STARTING;
1534 }
1535 } else { // there is NOT another valve to run...
1537 }
1538}
1539
1546
1547void Sprinkler::log_standby_warning_(const LogString *method_name) {
1548 ESP_LOGW(TAG, "%s called but standby is enabled; no action taken", LOG_STR_ARG(method_name));
1549}
1550
1551void Sprinkler::log_multiplier_zero_warning_(const LogString *method_name) {
1552 ESP_LOGW(TAG, "%s called but multiplier is set to zero; no action taken", LOG_STR_ARG(method_name));
1553}
1554
1555// Request origin strings indexed by SprinklerValveRunRequestOrigin enum (0-2): USER, CYCLE, QUEUE
1556PROGMEM_STRING_TABLE(SprinklerRequestOriginStrings, "USER", "CYCLE", "QUEUE", "UNKNOWN");
1557
1559 return SprinklerRequestOriginStrings::get_log_str(static_cast<uint8_t>(origin),
1560 SprinklerRequestOriginStrings::LAST_INDEX);
1561}
1562
1563// Sprinkler state strings indexed by SprinklerState enum (0-4): IDLE, STARTING, ACTIVE, STOPPING, BYPASS
1564PROGMEM_STRING_TABLE(SprinklerStateStrings, "IDLE", "STARTING", "ACTIVE", "STOPPING", "BYPASS", "UNKNOWN");
1565
1567 return SprinklerStateStrings::get_log_str(static_cast<uint8_t>(state), SprinklerStateStrings::LAST_INDEX);
1568}
1569
1571 if (this->timer_duration_(timer_index) > 0) {
1572 this->set_timeout(this->timer_[timer_index].name, this->timer_duration_(timer_index),
1573 this->timer_cbf_(timer_index));
1574 this->timer_[timer_index].start_time = millis();
1575 this->timer_[timer_index].active = true;
1576 }
1577 ESP_LOGVV(TAG, "Timer %zu started for %" PRIu32 " sec", static_cast<size_t>(timer_index),
1578 this->timer_duration_(timer_index) / 1000);
1579}
1580
1582 this->timer_[timer_index].active = false;
1583 return this->cancel_timeout(this->timer_[timer_index].name);
1584}
1585
1586bool Sprinkler::timer_active_(const SprinklerTimerIndex timer_index) { return this->timer_[timer_index].active; }
1587
1589 this->timer_[timer_index].time = 1000 * time;
1590}
1591
1592uint32_t Sprinkler::timer_duration_(const SprinklerTimerIndex timer_index) { return this->timer_[timer_index].time; }
1593
1594std::function<void()> Sprinkler::timer_cbf_(const SprinklerTimerIndex timer_index) {
1595 return this->timer_[timer_index].func;
1596}
1597
1599 this->timer_[sprinkler::TIMER_VALVE_SELECTION].active = false;
1600 ESP_LOGVV(TAG, "Valve selection timer expired");
1601 if (this->manual_valve_.has_value()) {
1602 this->fsm_request_(this->manual_valve_.value());
1603 this->manual_valve_.reset();
1604 }
1605}
1606
1608 this->timer_[sprinkler::TIMER_SM].active = false;
1609 ESP_LOGVV(TAG, "State machine timer expired");
1610 this->fsm_transition_();
1611}
1612
1614 ESP_LOGCONFIG(TAG, "Sprinkler Controller -- %s", this->name_);
1615 if (this->manual_selection_delay_.has_value()) {
1616 ESP_LOGCONFIG(TAG, " Manual Selection Delay: %" PRIu32 " seconds", this->manual_selection_delay_.value_or(0));
1617 }
1618 if (this->repeat().has_value()) {
1619 ESP_LOGCONFIG(TAG, " Repeat Cycles: %" PRIu32 " times", this->repeat().value_or(0));
1620 }
1621 if (this->start_delay_) {
1622 if (this->start_delay_is_valve_delay_) {
1623 ESP_LOGCONFIG(TAG, " Pump Start Valve Delay: %" PRIu32 " seconds", this->start_delay_);
1624 } else {
1625 ESP_LOGCONFIG(TAG, " Pump Start Pump Delay: %" PRIu32 " seconds", this->start_delay_);
1626 }
1627 }
1628 if (this->stop_delay_) {
1629 if (this->stop_delay_is_valve_delay_) {
1630 ESP_LOGCONFIG(TAG, " Pump Stop Valve Delay: %" PRIu32 " seconds", this->stop_delay_);
1631 } else {
1632 ESP_LOGCONFIG(TAG, " Pump Stop Pump Delay: %" PRIu32 " seconds", this->stop_delay_);
1633 }
1634 }
1635 if (this->switching_delay_.has_value()) {
1636 if (this->valve_overlap_) {
1637 ESP_LOGCONFIG(TAG, " Valve Overlap: %" PRIu32 " seconds", this->switching_delay_.value_or(0));
1638 } else {
1639 ESP_LOGCONFIG(TAG,
1640 " Valve Open Delay: %" PRIu32 " seconds\n"
1641 " Pump Switch Off During Valve Open Delay: %s",
1642 this->switching_delay_.value_or(0), YESNO(this->pump_switch_off_during_valve_open_delay_));
1643 }
1644 }
1645 for (size_t valve_number = 0; valve_number < this->number_of_valves(); valve_number++) {
1646 ESP_LOGCONFIG(TAG,
1647 " Valve %zu:\n"
1648 " Name: %s\n"
1649 " Run Duration: %" PRIu32 " seconds",
1650 valve_number, this->valve_name(valve_number), this->valve_run_duration(valve_number));
1651 }
1652 if (!this->pump_.empty()) {
1653 ESP_LOGCONFIG(TAG, " Total number of pumps: %zu", this->pump_.size());
1654 }
1655 if (!this->valve_.empty()) {
1656 ESP_LOGCONFIG(TAG, " Total number of valves: %zu", this->valve_.size());
1657 }
1658}
1659
1660} // 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:510
void enable_loop()
Enable this component's loop.
Definition component.h:258
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:532
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:454
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:490
void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE
Inform the parent automation that the event has triggered.
Definition automation.h:482
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: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:41
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