ESPHome 2026.1.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"
7#include <cinttypes>
8#include <utility>
9
10namespace esphome::sprinkler {
11
12static const char *const TAG = "sprinkler";
13
15 float value;
16 if (!this->restore_value_) {
17 value = this->initial_value_;
18 } else {
20 if (!this->pref_.load(&value)) {
21 if (!std::isnan(this->initial_value_)) {
22 value = this->initial_value_;
23 } else {
24 value = this->traits.get_min_value();
25 }
26 }
27 }
28 this->publish_state(value);
29}
30
32 this->set_trigger_->trigger(value);
33
34 this->publish_state(value);
35
36 if (this->restore_value_)
37 this->pref_.save(&value);
38}
39
40void SprinklerControllerNumber::dump_config() { LOG_NUMBER("", "Sprinkler Controller Number", this); }
41
43 : turn_on_trigger_(new Trigger<>()), turn_off_trigger_(new Trigger<>()) {}
44
46 if (!this->f_.has_value())
47 return;
48 auto s = (*this->f_)();
49 if (!s.has_value())
50 return;
51
52 this->publish_state(*s);
53}
54
56 if (this->prev_trigger_ != nullptr) {
58 }
59
60 if (state) {
61 this->prev_trigger_ = this->turn_on_trigger_;
63 } else {
64 this->prev_trigger_ = this->turn_off_trigger_;
66 }
67
68 this->publish_state(state);
69}
70
71void SprinklerControllerSwitch::set_state_lambda(std::function<optional<bool>()> &&f) { this->f_ = f; }
73
76
78
79void SprinklerControllerSwitch::dump_config() { LOG_SWITCH("", "Sprinkler Switch", this); }
80
83 : controller_(controller), valve_(valve) {}
84
86 uint32_t now = App.get_loop_component_start_time();
87 if (now >= this->start_millis_) { // dummy check
88 switch (this->state_) {
89 case STARTING:
90 if (now > (this->start_millis_ + this->start_delay_)) {
91 this->run_(); // start_delay_ has been exceeded, so ensure both valves are on and update the state
92 }
93 break;
94
95 case ACTIVE:
96 if (now > (this->start_millis_ + this->start_delay_ + this->run_duration_)) {
97 this->stop(); // start_delay_ + run_duration_ has been exceeded, start shutting down
98 }
99 break;
100
101 case STOPPING:
102 if (now > (this->stop_millis_ + this->stop_delay_)) {
103 this->kill_(); // stop_delay_has been exceeded, ensure all valves are off
104 }
105 break;
106
107 default:
108 break;
109 }
110 } else { // perhaps millis() rolled over...or something else is horribly wrong!
111 this->stop(); // bail out (TODO: handle this highly unlikely situation better...)
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_ = 0; // reset because (new) valve has not been started yet
129 this->stop_millis_ = 0; // reset because (new) valve has not been started yet
130 this->valve_ = valve; // finally, set the pointer to the new valve
131 }
132}
133
134void SprinklerValveOperator::set_run_duration(uint32_t run_duration) {
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_ = 0;
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
188uint32_t SprinklerValveOperator::run_duration() { return this->run_duration_ / 1000; }
189
191 if (this->start_millis_ == 0) {
192 return this->run_duration(); // hasn't been started yet
193 }
194
195 if (this->stop_millis_) {
196 if (this->stop_millis_ - this->start_millis_ >= this->start_delay_ + this->run_duration_) {
197 return 0; // valve was active for more than its configured duration, so we are done
198 } else {
199 // we're stopped; return time remaining
200 return (this->run_duration_ - (this->stop_millis_ - this->start_millis_)) / 1000;
201 }
202 }
203
204 auto completed_millis = this->start_millis_ + this->start_delay_ + this->run_duration_;
205 if (completed_millis > millis()) {
206 return (completed_millis - millis()) / 1000; // running now
207 }
208 return 0; // run completed
209}
210
212
214 if ((this->controller_ == nullptr) || (this->valve_ == nullptr)) {
215 return nullptr;
216 }
217 if (this->valve_->pump_switch_index.has_value()) {
219 }
220 return nullptr;
221}
222
224 auto *pump = this->pump_switch();
225 if ((this->valve_ == nullptr) || (pump == nullptr)) { // safety first!
226 return;
227 }
228 if (this->controller_ == nullptr) { // safety first!
229 pump->turn_off(); // if no controller was set, just switch off the pump
230 } else { // ...otherwise, do it "safely"
231 auto state = this->state_; // this is silly, but...
232 this->state_ = BYPASS; // ...exclude me from the pump-in-use check that set_pump_state() does
233 this->controller_->set_pump_state(pump, false);
234 this->state_ = state;
235 }
236}
237
239 auto *pump = this->pump_switch();
240 if ((this->valve_ == nullptr) || (pump == nullptr)) { // safety first!
241 return;
242 }
243 if (this->controller_ == nullptr) { // safety first!
244 pump->turn_on(); // if no controller was set, just switch on the pump
245 } else { // ...otherwise, do it "safely"
246 auto state = this->state_; // this is silly, but...
247 this->state_ = BYPASS; // ...exclude me from the pump-in-use check that set_pump_state() does
248 this->controller_->set_pump_state(pump, true);
249 this->state_ = state;
250 }
251}
252
254 if ((this->valve_ == nullptr) || (this->valve_->valve_switch == nullptr)) { // safety first!
255 return;
256 }
257 if (this->valve_->valve_switch->state) {
258 this->valve_->valve_switch->turn_off();
259 }
260}
261
263 if ((this->valve_ == nullptr) || (this->valve_->valve_switch == nullptr)) { // safety first!
264 return;
265 }
266 if (!this->valve_->valve_switch->state) {
267 this->valve_->valve_switch->turn_on();
268 }
269}
270
272 this->state_ = IDLE;
273 this->valve_off_();
274 this->pump_off_();
275}
276
278 this->state_ = ACTIVE;
279 this->valve_on_();
280 this->pump_on_();
281}
282
284SprinklerValveRunRequest::SprinklerValveRunRequest(size_t valve_number, uint32_t run_duration,
285 SprinklerValveOperator *valve_op)
286 : valve_number_(valve_number), run_duration_(run_duration), valve_op_(valve_op) {}
287
289bool SprinklerValveRunRequest::has_valve_operator() { return !(this->valve_op_ == nullptr); }
290
292
294
295void SprinklerValveRunRequest::set_valve(size_t valve_number) {
296 this->valve_number_ = valve_number;
297 this->run_duration_ = 0;
298 this->valve_op_ = nullptr;
299 this->has_valve_ = true;
300}
301
303 if (valve_op != nullptr) {
304 this->valve_op_ = valve_op;
305 }
306}
307
309 this->has_valve_ = false;
310 this->origin_ = USER;
311 this->run_duration_ = 0;
312 this->valve_op_ = nullptr;
313}
314
316
318
320 if (this->has_valve_) {
321 return this->valve_number_;
322 }
323 return nullopt;
324}
325
327
329
331Sprinkler::Sprinkler(const std::string &name) {
332 // The `name` is needed to set timers up, hence non-default constructor
333 // replaces `set_name()` method previously existed
334 this->name_ = name;
335 this->timer_.push_back({this->name_ + "sm", false, 0, 0, std::bind(&Sprinkler::sm_timer_callback_, this)});
336 this->timer_.push_back({this->name_ + "vs", false, 0, 0, std::bind(&Sprinkler::valve_selection_callback_, this)});
337}
338
339void Sprinkler::setup() { this->all_valves_off_(true); }
340
342 for (auto &vo : this->valve_op_) {
343 vo.loop();
344 }
345 if (this->prev_req_.has_request() && this->prev_req_.has_valve_operator() &&
346 this->prev_req_.valve_operator()->state() == IDLE) {
347 this->prev_req_.reset();
348 }
349}
350
352 auto new_valve_number = this->number_of_valves();
353 this->valve_.resize(new_valve_number + 1);
354 SprinklerValve *new_valve = &this->valve_[new_valve_number];
355
356 new_valve->controller_switch = valve_sw;
357 new_valve->controller_switch->set_state_lambda([this, new_valve_number]() -> optional<bool> {
358 auto *valve = this->valve_switch(new_valve_number);
359 auto *pump = this->valve_pump_switch(new_valve_number);
360 if (valve == nullptr) {
361 return false;
362 }
363 if (pump != nullptr) {
364 return valve->state && pump->state;
365 }
366 return valve->state;
367 });
368
369 new_valve->valve_turn_off_automation =
370 make_unique<Automation<>>(new_valve->controller_switch->get_turn_off_trigger());
371 new_valve->valve_shutdown_action = make_unique<sprinkler::ShutdownAction<>>(this);
372 new_valve->valve_turn_off_automation->add_actions({new_valve->valve_shutdown_action.get()});
373
374 new_valve->valve_turn_on_automation = make_unique<Automation<>>(new_valve->controller_switch->get_turn_on_trigger());
375 new_valve->valve_resumeorstart_action = make_unique<sprinkler::StartSingleValveAction<>>(this);
376 new_valve->valve_resumeorstart_action->set_valve_to_start(new_valve_number);
377 new_valve->valve_turn_on_automation->add_actions({new_valve->valve_resumeorstart_action.get()});
378
379 if (enable_sw != nullptr) {
380 new_valve->enable_switch = enable_sw;
381 }
382}
383
384void Sprinkler::add_controller(Sprinkler *other_controller) { this->other_controllers_.push_back(other_controller); }
385
387 this->controller_sw_ = controller_switch;
388 controller_switch->set_state_lambda([this]() -> optional<bool> {
389 for (size_t valve_number = 0; valve_number < this->number_of_valves(); valve_number++) {
390 if (this->valve_[valve_number].controller_switch->state) {
391 return true;
392 }
393 }
394 return this->active_req_.has_request();
395 });
396
397 this->sprinkler_turn_off_automation_ = make_unique<Automation<>>(controller_switch->get_turn_off_trigger());
398 this->sprinkler_shutdown_action_ = make_unique<sprinkler::ShutdownAction<>>(this);
400
401 this->sprinkler_turn_on_automation_ = make_unique<Automation<>>(controller_switch->get_turn_on_trigger());
402 this->sprinkler_resumeorstart_action_ = make_unique<sprinkler::ResumeOrStartAction<>>(this);
404}
405
407 this->auto_adv_sw_ = auto_adv_switch;
408}
409
411 this->queue_enable_sw_ = queue_enable_switch;
412}
413
415 this->reverse_sw_ = reverse_switch;
416}
417
419 this->standby_sw_ = standby_switch;
420
421 this->sprinkler_standby_turn_on_automation_ = make_unique<Automation<>>(standby_switch->get_turn_on_trigger());
422 this->sprinkler_standby_shutdown_action_ = make_unique<sprinkler::ShutdownAction<>>(this);
424}
425
427 this->multiplier_number_ = multiplier_number;
428}
429
431 this->repeat_number_ = repeat_number;
432}
433
434void Sprinkler::configure_valve_switch(size_t valve_number, switch_::Switch *valve_switch, uint32_t run_duration) {
435 if (this->is_a_valid_valve(valve_number)) {
436 this->valve_[valve_number].valve_switch = valve_switch;
437 this->valve_[valve_number].run_duration = run_duration;
438 }
439}
440
441void Sprinkler::configure_valve_pump_switch(size_t valve_number, switch_::Switch *pump_switch) {
442 if (this->is_a_valid_valve(valve_number)) {
443 for (size_t i = 0; i < this->pump_.size(); i++) { // check each existing registered pump
444 if (this->pump_[i] == pump_switch) { // if the "new" pump matches one we already have...
445 this->valve_[valve_number].pump_switch_index = i; // ...save its index in the pump vector...
446 return; // ...and we are done
447 }
448 } // if we end up here, no pumps matched, so add a new one
449 this->pump_.push_back(pump_switch);
450 this->valve_[valve_number].pump_switch_index = this->pump_.size() - 1; // save the index to the new pump
451 }
452}
453
455 SprinklerControllerNumber *run_duration_number) {
456 if (this->is_a_valid_valve(valve_number)) {
457 this->valve_[valve_number].run_duration_number = run_duration_number;
458 }
459}
460
462 if (!divider.has_value()) {
463 return;
464 }
465 if (divider.value() > 0) {
466 this->set_multiplier(1.0 / divider.value());
467 this->set_repeat(divider.value() - 1);
468 } else if (divider.value() == 0) {
469 this->set_multiplier(1.0);
470 this->set_repeat(0);
471 }
472}
473
475 if ((!multiplier.has_value()) || (multiplier.value() < 0)) {
476 return;
477 }
478 this->multiplier_ = multiplier.value();
479 if (this->multiplier_number_ == nullptr) {
480 return;
481 }
482 if (this->multiplier_number_->state == multiplier.value()) {
483 return;
484 }
485 auto call = this->multiplier_number_->make_call();
486 call.set_value(multiplier.value());
487 call.perform();
488}
489
491 this->next_prev_ignore_disabled_ = ignore_disabled;
492}
493
494void Sprinkler::set_pump_start_delay(uint32_t start_delay) {
495 this->start_delay_is_valve_delay_ = false;
496 this->start_delay_ = start_delay;
497}
498
499void Sprinkler::set_pump_stop_delay(uint32_t stop_delay) {
500 this->stop_delay_is_valve_delay_ = false;
501 this->stop_delay_ = stop_delay;
502}
503
504void Sprinkler::set_valve_start_delay(uint32_t start_delay) {
505 this->start_delay_is_valve_delay_ = true;
506 this->start_delay_ = start_delay;
507}
508
509void Sprinkler::set_valve_stop_delay(uint32_t stop_delay) {
510 this->stop_delay_is_valve_delay_ = true;
511 this->stop_delay_ = stop_delay;
512}
513
514void Sprinkler::set_pump_switch_off_during_valve_open_delay(bool pump_switch_off_during_valve_open_delay) {
515 this->pump_switch_off_during_valve_open_delay_ = pump_switch_off_during_valve_open_delay;
516}
517
518void Sprinkler::set_valve_open_delay(const uint32_t valve_open_delay) {
519 if (valve_open_delay > 0) {
520 this->valve_overlap_ = false;
521 this->switching_delay_ = valve_open_delay;
522 } else {
523 this->switching_delay_.reset();
524 }
525}
526
527void Sprinkler::set_valve_overlap(uint32_t valve_overlap) {
528 if (valve_overlap > 0) {
529 this->valve_overlap_ = true;
530 this->switching_delay_ = valve_overlap;
531 } else {
532 this->switching_delay_.reset();
533 }
534 this->pump_switch_off_during_valve_open_delay_ = false; // incompatible option
535}
536
537void Sprinkler::set_manual_selection_delay(uint32_t manual_selection_delay) {
538 if (manual_selection_delay > 0) {
539 this->manual_selection_delay_ = manual_selection_delay;
540 } else {
542 }
543}
544
545void Sprinkler::set_valve_run_duration(const optional<size_t> valve_number, const optional<uint32_t> run_duration) {
546 if (!valve_number.has_value() || !run_duration.has_value()) {
547 return;
548 }
549 if (!this->is_a_valid_valve(valve_number.value())) {
550 return;
551 }
552 this->valve_[valve_number.value()].run_duration = run_duration.value();
553 if (this->valve_[valve_number.value()].run_duration_number == nullptr) {
554 return;
555 }
556 if (this->valve_[valve_number.value()].run_duration_number->state == run_duration.value()) {
557 return;
558 }
559 auto call = this->valve_[valve_number.value()].run_duration_number->make_call();
560 if (this->valve_[valve_number.value()].run_duration_number->traits.get_unit_of_measurement_ref() == MIN_STR) {
561 call.set_value(run_duration.value() / 60.0);
562 } else {
563 call.set_value(run_duration.value());
564 }
565 call.perform();
566}
567
568void Sprinkler::set_auto_advance(const bool auto_advance) {
569 if (this->auto_adv_sw_ == nullptr) {
570 return;
571 }
572 if (this->auto_adv_sw_->state == auto_advance) {
573 return;
574 }
575 if (auto_advance) {
576 this->auto_adv_sw_->turn_on();
577 } else {
578 this->auto_adv_sw_->turn_off();
579 }
580}
581
583 this->target_repeats_ = repeat;
584 if (this->repeat_number_ == nullptr) {
585 return;
586 }
587 if (this->repeat_number_->state == repeat.value()) {
588 return;
589 }
590 auto call = this->repeat_number_->make_call();
591 call.set_value(repeat.value_or(0));
592 call.perform();
593}
594
595void Sprinkler::set_queue_enable(bool queue_enable) {
596 if (this->queue_enable_sw_ == nullptr) {
597 return;
598 }
599 if (this->queue_enable_sw_->state == queue_enable) {
600 return;
601 }
602 if (queue_enable) {
603 this->queue_enable_sw_->turn_on();
604 } else {
605 this->queue_enable_sw_->turn_off();
606 }
607}
608
609void Sprinkler::set_reverse(const bool reverse) {
610 if (this->reverse_sw_ == nullptr) {
611 return;
612 }
613 if (this->reverse_sw_->state == reverse) {
614 return;
615 }
616 if (reverse) {
617 this->reverse_sw_->turn_on();
618 } else {
619 this->reverse_sw_->turn_off();
620 }
621}
622
623void Sprinkler::set_standby(const bool standby) {
624 if (this->standby_sw_ == nullptr) {
625 return;
626 }
627 if (this->standby_sw_->state == standby) {
628 return;
629 }
630 if (standby) {
631 this->standby_sw_->turn_on();
632 } else {
633 this->standby_sw_->turn_off();
634 }
635}
636
637uint32_t Sprinkler::valve_run_duration(const size_t valve_number) {
638 if (!this->is_a_valid_valve(valve_number)) {
639 return 0;
640 }
641 if (this->valve_[valve_number].run_duration_number != nullptr) {
642 if (this->valve_[valve_number].run_duration_number->traits.get_unit_of_measurement_ref() == MIN_STR) {
643 return static_cast<uint32_t>(roundf(this->valve_[valve_number].run_duration_number->state * 60));
644 } else {
645 return static_cast<uint32_t>(roundf(this->valve_[valve_number].run_duration_number->state));
646 }
647 }
648 return this->valve_[valve_number].run_duration;
649}
650
651uint32_t Sprinkler::valve_run_duration_adjusted(const size_t valve_number) {
652 uint32_t run_duration = 0;
653
654 if (this->is_a_valid_valve(valve_number)) {
655 run_duration = this->valve_run_duration(valve_number);
656 }
657 run_duration = static_cast<uint32_t>(roundf(run_duration * this->multiplier()));
658 // run_duration must not be less than any of these
659 if ((run_duration < this->start_delay_) || (run_duration < this->stop_delay_) ||
660 (run_duration < this->switching_delay_.value_or(0) * 2)) {
661 return std::max(this->switching_delay_.value_or(0) * 2, std::max(this->start_delay_, this->stop_delay_));
662 }
663 return run_duration;
664}
665
667 if (this->auto_adv_sw_ != nullptr) {
668 return this->auto_adv_sw_->state;
669 }
670 return true;
671}
672
674 if (this->multiplier_number_ != nullptr) {
675 return this->multiplier_number_->state;
676 }
677 return this->multiplier_;
678}
679
681 if (this->repeat_number_ != nullptr) {
682 return static_cast<uint32_t>(roundf(this->repeat_number_->state));
683 }
684 return this->target_repeats_;
685}
686
688 // if there is an active valve and auto-advance is enabled, we may be repeating, so return the count
689 if (this->active_req_.has_request() && this->auto_advance()) {
690 return this->repeat_count_;
691 }
692 return nullopt;
693}
694
696 if (this->queue_enable_sw_ != nullptr) {
697 return this->queue_enable_sw_->state;
698 }
699 return true;
700}
701
703 if (this->reverse_sw_ != nullptr) {
704 return this->reverse_sw_->state;
705 }
706 return false;
707}
708
710 if (this->standby_sw_ != nullptr) {
711 return this->standby_sw_->state;
712 }
713 return false;
714}
715
717 if (this->standby()) {
718 this->log_standby_warning_(LOG_STR("start_from_queue"));
719 return;
720 }
721 if (this->multiplier() == 0) {
722 this->log_multiplier_zero_warning_(LOG_STR("start_from_queue"));
723 return;
724 }
725 if (this->queued_valves_.empty()) {
726 return; // if there is nothing in the queue, don't do anything
727 }
728 if (this->queue_enabled() && this->active_valve().has_value()) {
729 return; // if there is already a valve running from the queue, do nothing
730 }
731
732 this->set_auto_advance(false);
733 this->set_queue_enable(true);
734
735 this->reset_cycle_states_(); // just in case auto-advance is switched on later
736 this->repeat_count_ = 0;
737 this->fsm_kick_(); // will automagically pick up from the queue (it has priority)
738}
739
741 if (this->standby()) {
742 this->log_standby_warning_(LOG_STR("start_full_cycle"));
743 return;
744 }
745 if (this->multiplier() == 0) {
746 this->log_multiplier_zero_warning_(LOG_STR("start_full_cycle"));
747 return;
748 }
749 if (this->auto_advance() && this->active_valve().has_value()) {
750 return; // if auto-advance is already enabled and there is already a valve running, do nothing
751 }
752
753 this->set_queue_enable(false);
754
755 this->prep_full_cycle_();
756 this->repeat_count_ = 0;
757 // if there is no active valve already, start the first valve in the cycle
758 if (!this->active_req_.has_request()) {
759 this->fsm_kick_();
760 }
761}
762
764 if (this->standby()) {
765 this->log_standby_warning_(LOG_STR("start_single_valve"));
766 return;
767 }
768 if (this->multiplier() == 0) {
769 this->log_multiplier_zero_warning_(LOG_STR("start_single_valve"));
770 return;
771 }
772 if (!valve_number.has_value() || (valve_number == this->active_valve())) {
773 return;
774 }
775
776 this->set_auto_advance(false);
777 this->set_queue_enable(false);
778
779 this->reset_cycle_states_(); // just in case auto-advance is switched on later
780 this->repeat_count_ = 0;
781 this->fsm_request_(valve_number.value(), run_duration.value_or(0));
782}
783
785 if (valve_number.has_value()) {
786 if (this->is_a_valid_valve(valve_number.value()) && (this->queued_valves_.size() < this->max_queue_size_)) {
787 SprinklerQueueItem item{valve_number.value(), run_duration.value()};
788 this->queued_valves_.insert(this->queued_valves_.begin(), item);
789 ESP_LOGD(TAG, "Valve %zu placed into queue with run duration of %" PRIu32 " seconds", valve_number.value_or(0),
790 run_duration.value_or(0));
791 }
792 }
793}
794
796 this->queued_valves_.clear();
797 ESP_LOGD(TAG, "Queue cleared");
798}
799
801 if (this->standby()) {
802 this->log_standby_warning_(LOG_STR("next_valve"));
803 return;
804 }
805
806 if (this->state_ == IDLE) {
807 this->reset_cycle_states_(); // just in case auto-advance is switched on later
808 }
809
810 this->manual_valve_ = this->next_valve_number_(
811 this->manual_valve_.value_or(this->active_req_.valve_as_opt().value_or(this->number_of_valves() - 1)),
812 !this->next_prev_ignore_disabled_, true);
813
814 if (!this->manual_valve_.has_value()) {
815 ESP_LOGD(TAG, "next_valve was called but no valve could be started; perhaps next_prev_ignore_disabled allows only "
816 "enabled valves and no valves are enabled?");
817 return;
818 }
819
823 } else {
824 this->fsm_request_(this->manual_valve_.value());
825 }
826}
827
829 if (this->standby()) {
830 this->log_standby_warning_(LOG_STR("previous_valve"));
831 return;
832 }
833
834 if (this->state_ == IDLE) {
835 this->reset_cycle_states_(); // just in case auto-advance is switched on later
836 }
837
838 this->manual_valve_ =
839 this->previous_valve_number_(this->manual_valve_.value_or(this->active_req_.valve_as_opt().value_or(0)),
840 !this->next_prev_ignore_disabled_, true);
841
842 if (!this->manual_valve_.has_value()) {
843 ESP_LOGD(TAG, "previous_valve was called but no valve could be started; perhaps next_prev_ignore_disabled allows "
844 "only enabled valves and no valves are enabled?");
845 return;
846 }
847
851 } else {
852 this->fsm_request_(this->manual_valve_.value());
853 }
854}
855
856void Sprinkler::shutdown(bool clear_queue) {
858 this->active_req_.reset();
859 this->manual_valve_.reset();
860 this->next_req_.reset();
861 for (auto &vo : this->valve_op_) {
862 vo.stop();
863 }
865 if (clear_queue) {
866 this->clear_queued_valves();
867 this->repeat_count_ = 0;
868 }
869}
870
872 if (this->paused_valve_.has_value() || !this->active_req_.has_request()) {
873 return; // we can't pause if we're already paused or if there is no active valve
874 }
875 this->paused_valve_ = this->active_valve();
877 this->shutdown(false);
878 ESP_LOGD(TAG, "Paused valve %zu with %" PRIu32 " seconds remaining", this->paused_valve_.value_or(0),
879 this->resume_duration_.value_or(0));
880}
881
883 if (this->standby()) {
884 this->log_standby_warning_(LOG_STR("resume"));
885 return;
886 }
887
888 if (this->paused_valve_.has_value() && (this->resume_duration_.has_value())) {
889 // Resume only if valve has not been completed yet
890 if (!this->valve_cycle_complete_(this->paused_valve_.value())) {
891 ESP_LOGD(TAG, "Resuming valve %zu with %" PRIu32 " seconds remaining", this->paused_valve_.value_or(0),
892 this->resume_duration_.value_or(0));
893 this->fsm_request_(this->paused_valve_.value(), this->resume_duration_.value());
894 }
895 this->reset_resume();
896 } else {
897 ESP_LOGD(TAG, "No valve to resume!");
898 }
899}
900
902 if (this->paused_valve_.has_value() && (this->resume_duration_.has_value())) {
903 this->resume();
904 } else {
905 this->start_full_cycle();
906 }
907}
908
910 this->paused_valve_.reset();
911 this->resume_duration_.reset();
912}
913
914const char *Sprinkler::valve_name(const size_t valve_number) {
915 if (this->is_a_valid_valve(valve_number)) {
916 return this->valve_[valve_number].controller_switch->get_name().c_str();
917 }
918 return nullptr;
919}
920
927
929 if (!this->valve_overlap_ && this->prev_req_.has_request() && this->prev_req_.has_valve_operator() &&
930 (this->prev_req_.valve_operator()->state() == STARTING || this->prev_req_.valve_operator()->state() == ACTIVE)) {
931 return this->prev_req_.valve_as_opt();
932 }
933 return this->active_req_.valve_as_opt();
934}
935
937
939 if (!this->queued_valves_.empty()) {
940 return this->queued_valves_.back().valve_number;
941 }
942 return nullopt;
943}
944
946
947size_t Sprinkler::number_of_valves() { return this->valve_.size(); }
948
949bool Sprinkler::is_a_valid_valve(const size_t valve_number) { return (valve_number < this->number_of_valves()); }
950
952 if (pump_switch == nullptr) {
953 return false; // we can't do anything if there's nothing to check
954 }
955 // a pump must be considered "in use" if a (distribution) valve it supplies is active. this means:
956 // - at least one SprinklerValveOperator:
957 // - has a valve loaded that depends on this pump
958 // - is in a state that depends on the pump: (ACTIVE and _possibly_ STARTING/STOPPING)
959 // - if NO SprinklerValveOperator is active but there is a run request pending (active_req_.has_request()) and the
960 // controller state is STARTING, valve open delay is configured but NOT pump_switch_off_during_valve_open_delay_
961 for (auto &vo : this->valve_op_) { // first, check if any SprinklerValveOperator has a valve dependent on this pump
962 if ((vo.state() != BYPASS) && (vo.pump_switch() != nullptr)) {
963 // the SprinklerValveOperator is configured with a pump; now check if it is the pump of interest
964 if (vo.pump_switch() == pump_switch) {
965 // now if the SprinklerValveOperator has a pump and it is either ACTIVE, is STARTING with a valve delay or
966 // is STOPPING with a valve delay, its pump can be considered "in use", so just return indicating this now
967 if ((vo.state() == ACTIVE) ||
968 ((vo.state() == STARTING) && this->start_delay_ && this->start_delay_is_valve_delay_) ||
969 ((vo.state() == STOPPING) && this->stop_delay_ && this->stop_delay_is_valve_delay_)) {
970 return true;
971 }
972 }
973 }
974 } // if we end up here, no SprinklerValveOperator was in a "give-away" state indicating that the pump is in use...
976 this->active_req_.has_request() && (this->state_ != STOPPING)) {
977 // ...the controller is configured to keep the pump on during a valve open delay, so just return
978 // whether or not the next valve shares the same pump
979 auto *valve_pump = this->valve_pump_switch(this->active_req_.valve());
980 if (valve_pump == nullptr) {
981 return false; // valve has no pump, so this pump isn't in use by it
982 }
983 return pump_switch == valve_pump;
984 }
985 return false;
986}
987
989 if (pump_switch == nullptr) {
990 return; // we can't do anything if there's nothing to check
991 }
992
993 bool hold_pump_on = false;
994
995 for (auto &controller : this->other_controllers_) { // check if the pump is in use by another controller
996 if (controller != this) { // dummy check
997 if (controller->pump_in_use(pump_switch)) {
998 hold_pump_on = true; // if another controller says it's using this pump, keep it on
999 }
1000 }
1001 }
1002 if (hold_pump_on) {
1003 ESP_LOGD(TAG, "Leaving pump on because another controller instance is using it");
1004 }
1005
1006 if (state) { // ...and now we can set the new state of the switch
1007 pump_switch->turn_on();
1008 } else if (!hold_pump_on && !this->pump_in_use(pump_switch)) {
1009 pump_switch->turn_off();
1010 }
1011}
1012
1014 uint32_t total_time_remaining = 0;
1015
1016 for (size_t valve = 0; valve < this->number_of_valves(); valve++) {
1017 total_time_remaining += this->valve_run_duration_adjusted(valve);
1018 }
1019
1020 if (this->valve_overlap_) {
1021 total_time_remaining -= this->switching_delay_.value_or(0) * (this->number_of_valves() - 1);
1022 } else {
1023 total_time_remaining += this->switching_delay_.value_or(0) * (this->number_of_valves() - 1);
1024 }
1025
1026 return total_time_remaining;
1027}
1028
1030 uint32_t total_time_remaining = 0;
1031 uint32_t valve_count = 0;
1032
1033 for (size_t valve = 0; valve < this->number_of_valves(); valve++) {
1034 if (this->valve_is_enabled_(valve)) {
1035 total_time_remaining += this->valve_run_duration_adjusted(valve);
1036 valve_count++;
1037 }
1038 }
1039
1040 if (valve_count) {
1041 if (this->valve_overlap_) {
1042 total_time_remaining -= this->switching_delay_.value_or(0) * (valve_count - 1);
1043 } else {
1044 total_time_remaining += this->switching_delay_.value_or(0) * (valve_count - 1);
1045 }
1046 }
1047
1048 return total_time_remaining;
1049}
1050
1052 uint32_t total_time_remaining = 0;
1053 uint32_t enabled_valve_count = 0;
1054 uint32_t incomplete_valve_count = 0;
1055
1056 for (size_t valve = 0; valve < this->number_of_valves(); valve++) {
1057 if (this->valve_is_enabled_(valve)) {
1058 enabled_valve_count++;
1059 if (!this->valve_cycle_complete_(valve)) {
1060 if (!this->active_valve().has_value() || (valve != this->active_valve().value())) {
1061 total_time_remaining += this->valve_run_duration_adjusted(valve);
1062 incomplete_valve_count++;
1063 } else {
1064 // to get here, there must be an active valve and this valve must be equal to 'valve'
1065 if (this->active_req_.valve_operator() == nullptr) { // no SVO has been assigned yet so it hasn't started
1066 total_time_remaining += this->valve_run_duration_adjusted(valve);
1067 incomplete_valve_count++;
1068 }
1069 }
1070 }
1071 }
1072 }
1073
1074 if (incomplete_valve_count >= enabled_valve_count) {
1075 incomplete_valve_count--;
1076 }
1077 if (incomplete_valve_count) {
1078 if (this->valve_overlap_) {
1079 total_time_remaining -= this->switching_delay_.value_or(0) * incomplete_valve_count;
1080 } else {
1081 total_time_remaining += this->switching_delay_.value_or(0) * incomplete_valve_count;
1082 }
1083 }
1084
1085 return total_time_remaining;
1086}
1087
1089 uint32_t total_time_remaining = 0;
1090 uint32_t valve_count = 0;
1091
1092 for (auto &valve : this->queued_valves_) {
1093 if (valve.run_duration) {
1094 total_time_remaining += valve.run_duration;
1095 } else {
1096 total_time_remaining += this->valve_run_duration_adjusted(valve.valve_number);
1097 }
1098 valve_count++;
1099 }
1100
1101 if (valve_count) {
1102 if (this->valve_overlap_) {
1103 total_time_remaining -= this->switching_delay_.value_or(0) * (valve_count - 1);
1104 } else {
1105 total_time_remaining += this->switching_delay_.value_or(0) * (valve_count - 1);
1106 }
1107 }
1108
1109 return total_time_remaining;
1110}
1111
1113 if (this->active_req_.has_request()) { // first try to return the value based on active_req_...
1114 if (this->active_req_.valve_operator() != nullptr) {
1115 return this->active_req_.valve_operator()->time_remaining();
1116 }
1117 }
1118 if (this->prev_req_.has_request()) { // try to return the value based on prev_req_...
1119 if (this->prev_req_.valve_operator() != nullptr) {
1120 return this->prev_req_.valve_operator()->time_remaining();
1121 }
1122 }
1123 return nullopt;
1124}
1125
1127 if (!this->time_remaining_active_valve().has_value() && this->state_ == IDLE) {
1128 return nullopt;
1129 }
1130
1131 auto total_time_remaining = this->time_remaining_active_valve().value_or(0);
1132 if (this->auto_advance()) {
1133 total_time_remaining += this->total_cycle_time_enabled_incomplete_valves();
1134 if (this->repeat().value_or(0) > 0) {
1135 total_time_remaining +=
1136 (this->total_cycle_time_enabled_valves() * (this->repeat().value_or(0) - this->repeat_count().value_or(0)));
1137 }
1138 }
1139
1140 if (this->queue_enabled()) {
1141 total_time_remaining += this->total_queue_time();
1142 }
1143 return total_time_remaining;
1144}
1145
1147 if (this->state_ != IDLE) {
1148 return true;
1149 }
1150
1151 for (auto &controller : this->other_controllers_) {
1152 if (controller != this) { // dummy check
1153 if (controller->controller_state() != IDLE) {
1154 return true;
1155 }
1156 }
1157 }
1158 return false;
1159}
1160
1162 if (this->is_a_valid_valve(valve_number)) {
1163 return this->valve_[valve_number].controller_switch;
1164 }
1165 return nullptr;
1166}
1167
1169 if (this->is_a_valid_valve(valve_number)) {
1170 return this->valve_[valve_number].enable_switch;
1171 }
1172 return nullptr;
1173}
1174
1175switch_::Switch *Sprinkler::valve_switch(const size_t valve_number) {
1176 if (this->is_a_valid_valve(valve_number)) {
1177 return this->valve_[valve_number].valve_switch;
1178 }
1179 return nullptr;
1180}
1181
1183 if (this->is_a_valid_valve(valve_number) && this->valve_[valve_number].pump_switch_index.has_value()) {
1184 return this->pump_[this->valve_[valve_number].pump_switch_index.value()];
1185 }
1186 return nullptr;
1187}
1188
1190 if (pump_index < this->pump_.size()) {
1191 return this->pump_[pump_index];
1192 }
1193 return nullptr;
1194}
1195
1196bool Sprinkler::valve_is_enabled_(const size_t valve_number) {
1197 if (this->is_a_valid_valve(valve_number)) {
1198 if (this->valve_[valve_number].enable_switch != nullptr) {
1199 return this->valve_[valve_number].enable_switch->state;
1200 } else {
1201 return true;
1202 }
1203 }
1204 return false;
1205}
1206
1207void Sprinkler::mark_valve_cycle_complete_(const size_t valve_number) {
1208 if (this->is_a_valid_valve(valve_number)) {
1209 ESP_LOGD(TAG, "Marking valve %u complete", valve_number);
1210 this->valve_[valve_number].valve_cycle_complete = true;
1211 }
1212}
1213
1214bool Sprinkler::valve_cycle_complete_(const size_t valve_number) {
1215 if (this->is_a_valid_valve(valve_number)) {
1216 return this->valve_[valve_number].valve_cycle_complete;
1217 }
1218 return false;
1219}
1220
1221optional<size_t> Sprinkler::next_valve_number_(const optional<size_t> first_valve, const bool include_disabled,
1222 const bool include_complete) {
1223 auto valve = first_valve.value_or(0);
1224 size_t start = first_valve.has_value() ? 1 : 0;
1225
1226 if (!this->is_a_valid_valve(valve)) {
1227 valve = 0;
1228 }
1229
1230 for (size_t offset = start; offset < this->number_of_valves(); offset++) {
1231 auto valve_of_interest = valve + offset;
1232 if (!this->is_a_valid_valve(valve_of_interest)) {
1233 valve_of_interest -= this->number_of_valves();
1234 }
1235
1236 if ((this->valve_is_enabled_(valve_of_interest) || include_disabled) &&
1237 (!this->valve_cycle_complete_(valve_of_interest) || include_complete)) {
1238 return valve_of_interest;
1239 }
1240 }
1241 return nullopt;
1242}
1243
1244optional<size_t> Sprinkler::previous_valve_number_(const optional<size_t> first_valve, const bool include_disabled,
1245 const bool include_complete) {
1246 auto valve = first_valve.value_or(this->number_of_valves() - 1);
1247 size_t start = first_valve.has_value() ? 1 : 0;
1248
1249 if (!this->is_a_valid_valve(valve)) {
1250 valve = this->number_of_valves() - 1;
1251 }
1252
1253 for (size_t offset = start; offset < this->number_of_valves(); offset++) {
1254 auto valve_of_interest = valve - offset;
1255 if (!this->is_a_valid_valve(valve_of_interest)) {
1256 valve_of_interest += this->number_of_valves();
1257 }
1258
1259 if ((this->valve_is_enabled_(valve_of_interest) || include_disabled) &&
1260 (!this->valve_cycle_complete_(valve_of_interest) || include_complete)) {
1261 return valve_of_interest;
1262 }
1263 }
1264 return nullopt;
1265}
1266
1268 if (this->reverse()) {
1269 return this->previous_valve_number_(first_valve, false, false);
1270 }
1271 return this->next_valve_number_(first_valve, false, false);
1272}
1273
1275 if (this->active_req_.has_request()) {
1276 this->prev_req_ = this->active_req_;
1277 } else {
1278 this->prev_req_.reset();
1279 }
1280
1281 if (this->next_req_.has_request()) {
1282 if (!this->next_req_.run_duration()) { // ensure the run duration is set correctly for consumption later on
1284 }
1285 return; // there is already a request pending
1286 } else if (this->queue_enabled() && !this->queued_valves_.empty()) {
1287 this->next_req_.set_valve(this->queued_valves_.back().valve_number);
1289 if (this->queued_valves_.back().run_duration) {
1290 this->next_req_.set_run_duration(this->queued_valves_.back().run_duration);
1291 this->queued_valves_.pop_back();
1292 } else if (this->multiplier()) {
1293 this->next_req_.set_run_duration(this->valve_run_duration_adjusted(this->queued_valves_.back().valve_number));
1294 this->queued_valves_.pop_back();
1295 } else {
1296 this->next_req_.reset();
1297 }
1298 } else if (this->auto_advance() && this->multiplier()) {
1299 if (this->next_valve_number_in_cycle_(first_valve).has_value()) {
1300 // if there is another valve to run as a part of a cycle, load that
1301 this->next_req_.set_valve(this->next_valve_number_in_cycle_(first_valve).value_or(0));
1304 this->valve_run_duration_adjusted(this->next_valve_number_in_cycle_(first_valve).value_or(0)));
1305 } else if ((this->repeat_count_++ < this->repeat().value_or(0))) {
1306 ESP_LOGD(TAG, "Repeating - starting cycle %" PRIu32 " of %" PRIu32, this->repeat_count_ + 1,
1307 this->repeat().value_or(0) + 1);
1308 // if there are repeats remaining and no more valves were left in the cycle, start a new cycle
1309 this->prep_full_cycle_();
1310 if (this->next_valve_number_in_cycle_().has_value()) { // this should always succeed here, but just in case...
1311 this->next_req_.set_valve(this->next_valve_number_in_cycle_().value_or(0));
1314 this->valve_run_duration_adjusted(this->next_valve_number_in_cycle_().value_or(0)));
1315 }
1316 }
1317 }
1318}
1319
1321 for (size_t valve_number = 0; valve_number < this->number_of_valves(); valve_number++) {
1322 if (this->valve_is_enabled_(valve_number))
1323 return true;
1324 }
1325 return false;
1326}
1327
1329 if (!req->has_request()) {
1330 return; // we can't do anything if the request contains nothing
1331 }
1332 if (!this->is_a_valid_valve(req->valve())) {
1333 return; // we can't do anything if the valve number isn't valid
1334 }
1335 for (auto &vo : this->valve_op_) { // find the first available SprinklerValveOperator, load it and start it up
1336 if (vo.state() == IDLE) {
1337 auto run_duration = req->run_duration() ? req->run_duration() : this->valve_run_duration_adjusted(req->valve());
1338 ESP_LOGD(TAG, "%s is starting valve %zu for %" PRIu32 " seconds, cycle %" PRIu32 " of %" PRIu32,
1339 LOG_STR_ARG(this->req_as_str_(req->request_is_from())), req->valve(), run_duration,
1340 this->repeat_count_ + 1, this->repeat().value_or(0) + 1);
1341 req->set_valve_operator(&vo);
1342 vo.set_controller(this);
1343 vo.set_valve(&this->valve_[req->valve()]);
1344 vo.set_run_duration(run_duration);
1345 vo.set_start_delay(this->start_delay_, this->start_delay_is_valve_delay_);
1346 vo.set_stop_delay(this->stop_delay_, this->stop_delay_is_valve_delay_);
1347 vo.start();
1348 return;
1349 }
1350 }
1351}
1352
1353void Sprinkler::all_valves_off_(const bool include_pump) {
1354 for (size_t valve_index = 0; valve_index < this->number_of_valves(); valve_index++) {
1355 auto *valve_sw = this->valve_[valve_index].valve_switch;
1356 if ((valve_sw != nullptr) && valve_sw->state) {
1357 valve_sw->turn_off();
1358 }
1359 if (include_pump) {
1360 this->set_pump_state(this->valve_pump_switch(valve_index), false);
1361 }
1362 }
1363 ESP_LOGD(TAG, "All valves stopped%s", include_pump ? ", including pumps" : "");
1364}
1365
1367 this->set_auto_advance(true);
1368
1369 if (!this->any_valve_is_enabled_()) {
1370 for (auto &valve : this->valve_) {
1371 if (valve.enable_switch != nullptr) {
1372 if (!valve.enable_switch->state) {
1373 valve.enable_switch->turn_on();
1374 }
1375 }
1376 }
1377 }
1378 this->reset_cycle_states_();
1379}
1380
1382 for (auto &valve : this->valve_) {
1383 valve.valve_cycle_complete = false;
1384 }
1385}
1386
1387void Sprinkler::fsm_request_(size_t requested_valve, uint32_t requested_run_duration) {
1388 this->next_req_.set_valve(requested_valve);
1389 this->next_req_.set_run_duration(requested_run_duration);
1390 // if state is IDLE or ACTIVE, call fsm_transition_() to start it immediately;
1391 // otherwise, fsm_transition() will pick up next_req_ at the next appropriate transition
1392 this->fsm_kick_();
1393}
1394
1396 if ((this->state_ == IDLE) || (this->state_ == ACTIVE)) {
1397 this->fsm_transition_();
1398 }
1399}
1400
1402 ESP_LOGVV(TAG, "fsm_transition_ called; state is %s", LOG_STR_ARG(this->state_as_str_(this->state_)));
1403 switch (this->state_) {
1404 case IDLE: // the system was off -> start it up
1405 // advances to ACTIVE
1407 break;
1408
1409 case ACTIVE:
1410 // advances to STOPPING or ACTIVE (again)
1412 break;
1413
1414 case STARTING: {
1415 // follows valve open delay interval
1416 uint32_t timer_duration = this->active_req_.run_duration();
1417 if (timer_duration > this->switching_delay_.value_or(0)) {
1418 timer_duration -= this->switching_delay_.value_or(0);
1419 }
1420 this->set_timer_duration_(sprinkler::TIMER_SM, timer_duration);
1422 this->start_valve_(&this->active_req_);
1423 this->state_ = ACTIVE;
1424 if (this->next_req_.has_request()) {
1425 // another valve has been requested, so restart the timer so we pick it up quickly
1428 }
1429 break;
1430 }
1431
1432 case STOPPING:
1433 // stop_delay_ has elapsed so just shut everything off
1434 this->active_req_.reset();
1435 this->manual_valve_.reset();
1436 this->all_valves_off_(true);
1437 this->state_ = IDLE;
1438 break;
1439
1440 default:
1441 break;
1442 }
1443 if (this->next_req_.has_request() && (this->state_ == IDLE)) {
1444 // another valve has been requested, so restart the timer so we pick it up quickly
1447 }
1448 ESP_LOGVV(TAG, "fsm_transition_ complete; new state is %s", LOG_STR_ARG(this->state_as_str_(this->state_)));
1449}
1450
1453
1454 if (this->next_req_.has_request()) { // there is a valve to run...
1455 this->active_req_.set_valve(this->next_req_.valve());
1458 this->next_req_.reset();
1459
1460 uint32_t timer_duration = this->active_req_.run_duration();
1461 if (timer_duration > this->switching_delay_.value_or(0)) {
1462 timer_duration -= this->switching_delay_.value_or(0);
1463 }
1464 this->set_timer_duration_(sprinkler::TIMER_SM, timer_duration);
1466 this->start_valve_(&this->active_req_);
1467 this->state_ = ACTIVE;
1468 }
1469}
1470
1472 if (!this->active_req_.has_request()) { // dummy check...
1474 return;
1475 }
1476
1477 if (!this->timer_active_(sprinkler::TIMER_SM)) { // only flag the valve as "complete" if the timer finished
1478 if ((this->active_req_.request_is_from() == CYCLE) || (this->active_req_.request_is_from() == USER)) {
1480 }
1481 } else {
1482 ESP_LOGD(TAG, "Valve cycle interrupted - NOT flagging valve as complete and stopping current valve");
1483 for (auto &vo : this->valve_op_) {
1484 vo.stop();
1485 }
1486 }
1487
1489
1490 if (this->next_req_.has_request()) { // there is another valve to run...
1491 auto *active_pump = this->valve_pump_switch(this->active_req_.valve());
1492 auto *next_pump = this->valve_pump_switch(this->next_req_.valve());
1493 bool same_pump = (active_pump != nullptr) && (next_pump != nullptr) && (active_pump == next_pump);
1494
1495 this->active_req_.set_valve(this->next_req_.valve());
1498 this->next_req_.reset();
1499
1500 // this->state_ = ACTIVE; // state isn't changing
1501 if (this->valve_overlap_ || !this->switching_delay_.has_value()) {
1502 uint32_t timer_duration = this->active_req_.run_duration();
1503 if (timer_duration > this->switching_delay_.value_or(0)) {
1504 timer_duration -= this->switching_delay_.value_or(0);
1505 }
1506 this->set_timer_duration_(sprinkler::TIMER_SM, timer_duration);
1508 this->start_valve_(&this->active_req_);
1509 } else {
1510 this->set_timer_duration_(
1512 this->switching_delay_.value() * 2 +
1513 (this->pump_switch_off_during_valve_open_delay_ && same_pump ? this->stop_delay_ : 0));
1515 this->state_ = STARTING;
1516 }
1517 } else { // there is NOT another valve to run...
1519 }
1520}
1521
1528
1529void Sprinkler::log_standby_warning_(const LogString *method_name) {
1530 ESP_LOGW(TAG, "%s called but standby is enabled; no action taken", LOG_STR_ARG(method_name));
1531}
1532
1533void Sprinkler::log_multiplier_zero_warning_(const LogString *method_name) {
1534 ESP_LOGW(TAG, "%s called but multiplier is set to zero; no action taken", LOG_STR_ARG(method_name));
1535}
1536
1538 switch (origin) {
1539 case USER:
1540 return LOG_STR("USER");
1541
1542 case CYCLE:
1543 return LOG_STR("CYCLE");
1544
1545 case QUEUE:
1546 return LOG_STR("QUEUE");
1547
1548 default:
1549 return LOG_STR("UNKNOWN");
1550 }
1551}
1552
1554 switch (state) {
1555 case IDLE:
1556 return LOG_STR("IDLE");
1557
1558 case STARTING:
1559 return LOG_STR("STARTING");
1560
1561 case ACTIVE:
1562 return LOG_STR("ACTIVE");
1563
1564 case STOPPING:
1565 return LOG_STR("STOPPING");
1566
1567 case BYPASS:
1568 return LOG_STR("BYPASS");
1569
1570 default:
1571 return LOG_STR("UNKNOWN");
1572 }
1573}
1574
1576 if (this->timer_duration_(timer_index) > 0) {
1577 this->set_timeout(this->timer_[timer_index].name, this->timer_duration_(timer_index),
1578 this->timer_cbf_(timer_index));
1579 this->timer_[timer_index].start_time = millis();
1580 this->timer_[timer_index].active = true;
1581 }
1582 ESP_LOGVV(TAG, "Timer %zu started for %" PRIu32 " sec", static_cast<size_t>(timer_index),
1583 this->timer_duration_(timer_index) / 1000);
1584}
1585
1587 this->timer_[timer_index].active = false;
1588 return this->cancel_timeout(this->timer_[timer_index].name);
1589}
1590
1591bool Sprinkler::timer_active_(const SprinklerTimerIndex timer_index) { return this->timer_[timer_index].active; }
1592
1593void Sprinkler::set_timer_duration_(const SprinklerTimerIndex timer_index, const uint32_t time) {
1594 this->timer_[timer_index].time = 1000 * time;
1595}
1596
1597uint32_t Sprinkler::timer_duration_(const SprinklerTimerIndex timer_index) { return this->timer_[timer_index].time; }
1598
1599std::function<void()> Sprinkler::timer_cbf_(const SprinklerTimerIndex timer_index) {
1600 return this->timer_[timer_index].func;
1601}
1602
1604 this->timer_[sprinkler::TIMER_VALVE_SELECTION].active = false;
1605 ESP_LOGVV(TAG, "Valve selection timer expired");
1606 if (this->manual_valve_.has_value()) {
1607 this->fsm_request_(this->manual_valve_.value());
1608 this->manual_valve_.reset();
1609 }
1610}
1611
1613 this->timer_[sprinkler::TIMER_SM].active = false;
1614 ESP_LOGVV(TAG, "State machine timer expired");
1615 this->fsm_transition_();
1616}
1617
1619 ESP_LOGCONFIG(TAG, "Sprinkler Controller -- %s", this->name_.c_str());
1620 if (this->manual_selection_delay_.has_value()) {
1621 ESP_LOGCONFIG(TAG, " Manual Selection Delay: %" PRIu32 " seconds", this->manual_selection_delay_.value_or(0));
1622 }
1623 if (this->repeat().has_value()) {
1624 ESP_LOGCONFIG(TAG, " Repeat Cycles: %" PRIu32 " times", this->repeat().value_or(0));
1625 }
1626 if (this->start_delay_) {
1627 if (this->start_delay_is_valve_delay_) {
1628 ESP_LOGCONFIG(TAG, " Pump Start Valve Delay: %" PRIu32 " seconds", this->start_delay_);
1629 } else {
1630 ESP_LOGCONFIG(TAG, " Pump Start Pump Delay: %" PRIu32 " seconds", this->start_delay_);
1631 }
1632 }
1633 if (this->stop_delay_) {
1634 if (this->stop_delay_is_valve_delay_) {
1635 ESP_LOGCONFIG(TAG, " Pump Stop Valve Delay: %" PRIu32 " seconds", this->stop_delay_);
1636 } else {
1637 ESP_LOGCONFIG(TAG, " Pump Stop Pump Delay: %" PRIu32 " seconds", this->stop_delay_);
1638 }
1639 }
1640 if (this->switching_delay_.has_value()) {
1641 if (this->valve_overlap_) {
1642 ESP_LOGCONFIG(TAG, " Valve Overlap: %" PRIu32 " seconds", this->switching_delay_.value_or(0));
1643 } else {
1644 ESP_LOGCONFIG(TAG,
1645 " Valve Open Delay: %" PRIu32 " seconds\n"
1646 " Pump Switch Off During Valve Open Delay: %s",
1648 }
1649 }
1650 for (size_t valve_number = 0; valve_number < this->number_of_valves(); valve_number++) {
1651 ESP_LOGCONFIG(TAG,
1652 " Valve %zu:\n"
1653 " Name: %s\n"
1654 " Run Duration: %" PRIu32 " seconds",
1655 valve_number, this->valve_name(valve_number), this->valve_run_duration(valve_number));
1656 }
1657 if (!this->pump_.empty()) {
1658 ESP_LOGCONFIG(TAG, " Total number of pumps: %zu", this->pump_.size());
1659 }
1660 if (!this->valve_.empty()) {
1661 ESP_LOGCONFIG(TAG, " Total number of valves: %zu", this->valve_.size());
1662 }
1663}
1664
1665} // namespace esphome::sprinkler
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
bool cancel_timeout(const std::string &name)
Cancel a timeout function.
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
bool save(const T *src)
Definition preferences.h:21
virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash)=0
uint32_t get_preference_hash()
Get a unique hash for storing preferences/settings for this entity.
void trigger(const Ts &...x)
Inform the parent automation that the event has triggered.
Definition automation.h:204
void stop_action()
Stop any action connected to this trigger.
Definition automation.h:212
NumberCall & set_value(float value)
NumberCall make_call()
Definition number.h:35
void publish_state(float state)
Definition number.cpp:31
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:71
optional< std::function< optional< bool >()> > f_
Definition sprinkler.h:108
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
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:490
void reset_resume()
resets resume state
SprinklerControllerNumber * repeat_number_
Definition sprinkler.h:571
bool valve_is_enabled_(size_t valve_number)
returns true if valve number is enabled
SprinklerState state_
Sprinkler controller state.
Definition sprinkler.h:509
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:539
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:551
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:530
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:518
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:503
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:570
std::vector< SprinklerValveOperator > valve_op_
Sprinkler valve operator objects.
Definition sprinkler.h:554
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.
std::vector< SprinklerTimer > timer_
Valve control timers.
Definition sprinkler.h:557
SprinklerControllerSwitch * controller_sw_
Definition sprinkler.h:564
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:527
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:566
optional< uint32_t > switching_delay_
Valve switching delay.
Definition sprinkler.h:536
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:512
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:548
bool start_delay_is_valve_delay_
Pump start/stop delay interval types.
Definition sprinkler.h:499
optional< size_t > manual_valve_
The number of the manually selected valve currently selected.
Definition sprinkler.h:521
bool pump_switch_off_during_valve_open_delay_
Pump should be off during valve_open_delay interval.
Definition sprinkler.h:493
bool cancel_timer_(SprinklerTimerIndex timer_index)
SprinklerControllerSwitch * queue_enable_sw_
Definition sprinkler.h:565
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:573
std::unique_ptr< ResumeOrStartAction<> > sprinkler_resumeorstart_action_
Definition sprinkler.h:575
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:545
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:578
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:577
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:574
optional< uint32_t > manual_selection_delay_
Manual switching delay.
Definition sprinkler.h:533
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:524
SprinklerValveRunRequest next_req_
The next run request for the controller to consume after active_req_ is complete.
Definition sprinkler.h:515
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:563
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:496
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:579
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:560
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:542
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:567
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:172
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:39
void turn_on()
Turn this switch on.
Definition switch.cpp:21
void turn_off()
Turn this switch off.
Definition switch.cpp:25
bool state
The current reported state of the binary sensor.
Definition switch.h:56
void publish_state(bool state)
Publish a state to the front-end from the back-end.
Definition switch.cpp:57
optional< bool > get_initial_state_with_restore_mode()
Returns the initial state of the switch, after applying restore mode rules.
Definition switch.cpp:43
bool state
Definition fan.h:0
const float HARDWARE
For components that deal with hardware and are very important like GPIO switch.
Definition component.cpp:80
const char *const TAG
Definition spi.cpp:7
const std::string MIN_STR
Definition sprinkler.h:13
ESPPreferences * global_preferences
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:67
std::unique_ptr< Automation<> > valve_turn_off_automation
Definition sprinkler.h:68
SprinklerControllerSwitch * enable_switch
Definition sprinkler.h:61
std::unique_ptr< ShutdownAction<> > valve_shutdown_action
Definition sprinkler.h:66
SprinklerControllerSwitch * controller_switch
Definition sprinkler.h:60
std::unique_ptr< Automation<> > valve_turn_on_automation
Definition sprinkler.h:69
optional< size_t > pump_switch_index
Definition sprinkler.h:64