ESPHome 2025.5.0
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 {
11namespace sprinkler {
12
13static const char *const TAG = "sprinkler";
14
16SprinklerSwitch::SprinklerSwitch(switch_::Switch *sprinkler_switch) : on_switch_(sprinkler_switch) {}
17SprinklerSwitch::SprinklerSwitch(switch_::Switch *off_switch, switch_::Switch *on_switch, uint32_t pulse_duration)
18 : pulse_duration_(pulse_duration), off_switch_(off_switch), on_switch_(on_switch) {}
19
20bool SprinklerSwitch::is_latching_valve() { return (this->off_switch_ != nullptr) && (this->on_switch_ != nullptr); }
21
23 if ((this->pinned_millis_) && (App.get_loop_component_start_time() > this->pinned_millis_ + this->pulse_duration_)) {
24 this->pinned_millis_ = 0; // reset tracker
25 if (this->off_switch_->state) {
26 this->off_switch_->turn_off();
27 }
28 if (this->on_switch_->state) {
29 this->on_switch_->turn_off();
30 }
31 }
32}
33
35 if (!this->state()) { // do nothing if we're already in the requested state
36 return;
37 }
38 if (this->off_switch_ != nullptr) { // latching valve, start a pulse
39 if (!this->off_switch_->state) {
40 this->off_switch_->turn_on();
41 }
42 this->pinned_millis_ = millis();
43 } else if (this->on_switch_ != nullptr) { // non-latching valve
44 this->on_switch_->turn_off();
45 }
46 this->state_ = false;
47}
48
50 if (this->state()) { // do nothing if we're already in the requested state
51 return;
52 }
53 if (this->off_switch_ != nullptr) { // latching valve, start a pulse
54 if (!this->on_switch_->state) {
55 this->on_switch_->turn_on();
56 }
57 this->pinned_millis_ = millis();
58 } else if (this->on_switch_ != nullptr) { // non-latching valve
59 this->on_switch_->turn_on();
60 }
61 this->state_ = true;
62}
63
65 if ((this->off_switch_ == nullptr) && (this->on_switch_ != nullptr)) { // latching valve is not configured...
66 return this->on_switch_->state; // ...so just return the pump switch state
67 }
68 return this->state_;
69}
70
71void SprinklerSwitch::sync_valve_state(bool latch_state) {
72 if (this->is_latching_valve()) {
73 this->state_ = latch_state;
74 } else if (this->on_switch_ != nullptr) {
75 this->state_ = this->on_switch_->state;
76 }
77}
78
80 float value;
81 if (!this->restore_value_) {
82 value = this->initial_value_;
83 } else {
85 if (!this->pref_.load(&value)) {
86 if (!std::isnan(this->initial_value_)) {
87 value = this->initial_value_;
88 } else {
89 value = this->traits.get_min_value();
90 }
91 }
92 }
93 this->publish_state(value);
94}
95
97 this->set_trigger_->trigger(value);
98
99 this->publish_state(value);
100
101 if (this->restore_value_)
102 this->pref_.save(&value);
103}
104
105void SprinklerControllerNumber::dump_config() { LOG_NUMBER("", "Sprinkler Controller Number", this); }
106
108 : turn_on_trigger_(new Trigger<>()), turn_off_trigger_(new Trigger<>()) {}
109
111 if (!this->f_.has_value())
112 return;
113 auto s = (*this->f_)();
114 if (!s.has_value())
115 return;
116
117 this->publish_state(*s);
118}
119
121 if (this->prev_trigger_ != nullptr) {
122 this->prev_trigger_->stop_action();
123 }
124
125 if (state) {
126 this->prev_trigger_ = this->turn_on_trigger_;
127 this->turn_on_trigger_->trigger();
128 } else {
129 this->prev_trigger_ = this->turn_off_trigger_;
130 this->turn_off_trigger_->trigger();
131 }
132
133 this->publish_state(state);
134}
135
136void SprinklerControllerSwitch::set_state_lambda(std::function<optional<bool>()> &&f) { this->f_ = f; }
138
141
143
144void SprinklerControllerSwitch::dump_config() { LOG_SWITCH("", "Sprinkler Switch", this); }
145
148 : controller_(controller), valve_(valve) {}
149
151 uint32_t now = App.get_loop_component_start_time();
152 if (now >= this->start_millis_) { // dummy check
153 switch (this->state_) {
154 case STARTING:
155 if (now > (this->start_millis_ + this->start_delay_)) {
156 this->run_(); // start_delay_ has been exceeded, so ensure both valves are on and update the state
157 }
158 break;
159
160 case ACTIVE:
161 if (now > (this->start_millis_ + this->start_delay_ + this->run_duration_)) {
162 this->stop(); // start_delay_ + run_duration_ has been exceeded, start shutting down
163 }
164 break;
165
166 case STOPPING:
167 if (now > (this->stop_millis_ + this->stop_delay_)) {
168 this->kill_(); // stop_delay_has been exceeded, ensure all valves are off
169 }
170 break;
171
172 default:
173 break;
174 }
175 } else { // perhaps millis() rolled over...or something else is horribly wrong!
176 this->stop(); // bail out (TODO: handle this highly unlikely situation better...)
177 }
178}
179
181 if (controller != nullptr) {
182 this->controller_ = controller;
183 }
184}
185
187 if (valve != nullptr) {
188 if (this->state_ != IDLE) { // Only kill if not already idle
189 this->kill_(); // ensure everything is off before we let go!
190 }
191 this->state_ = IDLE; // reset state
192 this->run_duration_ = 0; // reset to ensure the valve isn't started without updating it
193 this->start_millis_ = 0; // reset because (new) valve has not been started yet
194 this->stop_millis_ = 0; // reset because (new) valve has not been started yet
195 this->valve_ = valve; // finally, set the pointer to the new valve
196 }
197}
198
199void SprinklerValveOperator::set_run_duration(uint32_t run_duration) {
200 if (run_duration) {
201 this->run_duration_ = run_duration * 1000;
202 }
203}
204
205void SprinklerValveOperator::set_start_delay(uint32_t start_delay, bool start_delay_is_valve_delay) {
206 this->start_delay_is_valve_delay_ = start_delay_is_valve_delay;
207 this->start_delay_ = start_delay * 1000; // because 1000 milliseconds is one second
208}
209
210void SprinklerValveOperator::set_stop_delay(uint32_t stop_delay, bool stop_delay_is_valve_delay) {
211 this->stop_delay_is_valve_delay_ = stop_delay_is_valve_delay;
212 this->stop_delay_ = stop_delay * 1000; // because 1000 milliseconds is one second
213}
214
216 if (!this->run_duration_) { // can't start if zero run duration
217 return;
218 }
219 if (this->start_delay_ && (this->pump_switch() != nullptr)) {
220 this->state_ = STARTING; // STARTING state requires both a pump and a start_delay_
221 if (this->start_delay_is_valve_delay_) {
222 this->pump_on_();
223 } else if (!this->pump_switch()->state()) { // if the pump is already on, wait to switch on the valve
224 this->valve_on_(); // to ensure consistent run time
225 }
226 } else {
227 this->run_(); // there is no start_delay_, so just start the pump and valve
228 }
229 this->stop_millis_ = 0;
230 this->start_millis_ = millis(); // save the time the start request was made
231}
232
234 if ((this->state_ == IDLE) || (this->state_ == STOPPING)) { // can't stop if already stopped or stopping
235 return;
236 }
237 if (this->stop_delay_ && (this->pump_switch() != nullptr)) {
238 this->state_ = STOPPING; // STOPPING state requires both a pump and a stop_delay_
239 if (this->stop_delay_is_valve_delay_) {
240 this->pump_off_();
241 } else {
242 this->valve_off_();
243 }
244 if (this->pump_switch()->state()) { // if the pump is still on at this point, it may be in use...
245 this->valve_off_(); // ...so just switch the valve off now to ensure consistent run time
246 }
247 } else {
248 this->kill_(); // there is no stop_delay_, so just stop the pump and valve
249 }
250 this->stop_millis_ = millis(); // save the time the stop request was made
251}
252
253uint32_t SprinklerValveOperator::run_duration() { return this->run_duration_ / 1000; }
254
256 if (this->start_millis_ == 0) {
257 return this->run_duration(); // hasn't been started yet
258 }
259
260 if (this->stop_millis_) {
261 if (this->stop_millis_ - this->start_millis_ >= this->start_delay_ + this->run_duration_) {
262 return 0; // valve was active for more than its configured duration, so we are done
263 } else {
264 // we're stopped; return time remaining
265 return (this->run_duration_ - (this->stop_millis_ - this->start_millis_)) / 1000;
266 }
267 }
268
269 auto completed_millis = this->start_millis_ + this->start_delay_ + this->run_duration_;
270 if (completed_millis > millis()) {
271 return (completed_millis - millis()) / 1000; // running now
272 }
273 return 0; // run completed
274}
275
277
279 if ((this->controller_ == nullptr) || (this->valve_ == nullptr)) {
280 return nullptr;
281 }
282 if (this->valve_->pump_switch_index.has_value()) {
284 }
285 return nullptr;
286}
287
289 if ((this->valve_ == nullptr) || (this->pump_switch() == nullptr)) { // safety first!
290 return;
291 }
292 if (this->controller_ == nullptr) { // safety first!
293 this->pump_switch()->turn_off(); // if no controller was set, just switch off the pump
294 } else { // ...otherwise, do it "safely"
295 auto state = this->state_; // this is silly, but...
296 this->state_ = BYPASS; // ...exclude me from the pump-in-use check that set_pump_state() does
297 this->controller_->set_pump_state(this->pump_switch(), false);
298 this->state_ = state;
299 }
300}
301
303 if ((this->valve_ == nullptr) || (this->pump_switch() == nullptr)) { // safety first!
304 return;
305 }
306 if (this->controller_ == nullptr) { // safety first!
307 this->pump_switch()->turn_on(); // if no controller was set, just switch on the pump
308 } else { // ...otherwise, do it "safely"
309 auto state = this->state_; // this is silly, but...
310 this->state_ = BYPASS; // ...exclude me from the pump-in-use check that set_pump_state() does
311 this->controller_->set_pump_state(this->pump_switch(), true);
312 this->state_ = state;
313 }
314}
315
317 if (this->valve_ == nullptr) { // safety first!
318 return;
319 }
320 if (this->valve_->valve_switch.state()) {
322 }
323}
324
326 if (this->valve_ == nullptr) { // safety first!
327 return;
328 }
329 if (!this->valve_->valve_switch.state()) {
330 this->valve_->valve_switch.turn_on();
331 }
332}
333
335 this->state_ = IDLE;
336 this->valve_off_();
337 this->pump_off_();
338}
339
341 this->state_ = ACTIVE;
342 this->valve_on_();
343 this->pump_on_();
344}
345
347SprinklerValveRunRequest::SprinklerValveRunRequest(size_t valve_number, uint32_t run_duration,
348 SprinklerValveOperator *valve_op)
349 : valve_number_(valve_number), run_duration_(run_duration), valve_op_(valve_op) {}
350
352bool SprinklerValveRunRequest::has_valve_operator() { return !(this->valve_op_ == nullptr); }
353
355
357
358void SprinklerValveRunRequest::set_valve(size_t valve_number) {
359 this->valve_number_ = valve_number;
360 this->run_duration_ = 0;
361 this->valve_op_ = nullptr;
362 this->has_valve_ = true;
363}
364
366 if (valve_op != nullptr) {
367 this->valve_op_ = valve_op;
368 }
369}
370
372 this->has_valve_ = false;
373 this->origin_ = USER;
374 this->run_duration_ = 0;
375 this->valve_op_ = nullptr;
376}
377
379
381
383 if (this->has_valve_) {
384 return this->valve_number_;
385 }
386 return nullopt;
387}
388
390
392
394Sprinkler::Sprinkler(const std::string &name) {
395 // The `name` is needed to set timers up, hence non-default constructor
396 // replaces `set_name()` method previously existed
397 this->name_ = name;
398 this->timer_.push_back({this->name_ + "sm", false, 0, 0, std::bind(&Sprinkler::sm_timer_callback_, this)});
399 this->timer_.push_back({this->name_ + "vs", false, 0, 0, std::bind(&Sprinkler::valve_selection_callback_, this)});
400}
401
402void Sprinkler::setup() { this->all_valves_off_(true); }
403
405 for (auto &p : this->pump_) {
406 p.loop();
407 }
408 for (auto &v : this->valve_) {
409 v.valve_switch.loop();
410 }
411 for (auto &vo : this->valve_op_) {
412 vo.loop();
413 }
414 if (this->prev_req_.has_request() && this->prev_req_.valve_operator()->state() == IDLE) {
415 this->prev_req_.reset();
416 }
417}
418
420 auto new_valve_number = this->number_of_valves();
421 this->valve_.resize(new_valve_number + 1);
422 SprinklerValve *new_valve = &this->valve_[new_valve_number];
423
424 new_valve->controller_switch = valve_sw;
425 new_valve->controller_switch->set_state_lambda([this, new_valve_number]() -> optional<bool> {
426 if (this->valve_pump_switch(new_valve_number) != nullptr) {
427 return this->valve_switch(new_valve_number)->state() && this->valve_pump_switch(new_valve_number)->state();
428 }
429 return this->valve_switch(new_valve_number)->state();
430 });
431
432 new_valve->valve_turn_off_automation =
433 make_unique<Automation<>>(new_valve->controller_switch->get_turn_off_trigger());
434 new_valve->valve_shutdown_action = make_unique<sprinkler::ShutdownAction<>>(this);
435 new_valve->valve_turn_off_automation->add_actions({new_valve->valve_shutdown_action.get()});
436
437 new_valve->valve_turn_on_automation = make_unique<Automation<>>(new_valve->controller_switch->get_turn_on_trigger());
438 new_valve->valve_resumeorstart_action = make_unique<sprinkler::StartSingleValveAction<>>(this);
439 new_valve->valve_resumeorstart_action->set_valve_to_start(new_valve_number);
440 new_valve->valve_turn_on_automation->add_actions({new_valve->valve_resumeorstart_action.get()});
441
442 if (enable_sw != nullptr) {
443 new_valve->enable_switch = enable_sw;
444 }
445}
446
447void Sprinkler::add_controller(Sprinkler *other_controller) { this->other_controllers_.push_back(other_controller); }
448
450 this->controller_sw_ = controller_switch;
451 controller_switch->set_state_lambda([this]() -> optional<bool> {
452 for (size_t valve_number = 0; valve_number < this->number_of_valves(); valve_number++) {
453 if (this->valve_[valve_number].controller_switch->state) {
454 return true;
455 }
456 }
457 return this->active_req_.has_request();
458 });
459
460 this->sprinkler_turn_off_automation_ = make_unique<Automation<>>(controller_switch->get_turn_off_trigger());
461 this->sprinkler_shutdown_action_ = make_unique<sprinkler::ShutdownAction<>>(this);
463
464 this->sprinkler_turn_on_automation_ = make_unique<Automation<>>(controller_switch->get_turn_on_trigger());
465 this->sprinkler_resumeorstart_action_ = make_unique<sprinkler::ResumeOrStartAction<>>(this);
467}
468
470 this->auto_adv_sw_ = auto_adv_switch;
471}
472
474 this->queue_enable_sw_ = queue_enable_switch;
475}
476
478 this->reverse_sw_ = reverse_switch;
479}
480
482 this->standby_sw_ = standby_switch;
483
484 this->sprinkler_standby_turn_on_automation_ = make_unique<Automation<>>(standby_switch->get_turn_on_trigger());
485 this->sprinkler_standby_shutdown_action_ = make_unique<sprinkler::ShutdownAction<>>(this);
487}
488
490 this->multiplier_number_ = multiplier_number;
491}
492
494 this->repeat_number_ = repeat_number;
495}
496
497void Sprinkler::configure_valve_switch(size_t valve_number, switch_::Switch *valve_switch, uint32_t run_duration) {
498 if (this->is_a_valid_valve(valve_number)) {
499 this->valve_[valve_number].valve_switch.set_on_switch(valve_switch);
500 this->valve_[valve_number].run_duration = run_duration;
501 }
502}
503
504void Sprinkler::configure_valve_switch_pulsed(size_t valve_number, switch_::Switch *valve_switch_off,
505 switch_::Switch *valve_switch_on, uint32_t pulse_duration,
506 uint32_t run_duration) {
507 if (this->is_a_valid_valve(valve_number)) {
508 this->valve_[valve_number].valve_switch.set_off_switch(valve_switch_off);
509 this->valve_[valve_number].valve_switch.set_on_switch(valve_switch_on);
510 this->valve_[valve_number].valve_switch.set_pulse_duration(pulse_duration);
511 this->valve_[valve_number].run_duration = run_duration;
512 }
513}
514
515void Sprinkler::configure_valve_pump_switch(size_t valve_number, switch_::Switch *pump_switch) {
516 if (this->is_a_valid_valve(valve_number)) {
517 for (size_t i = 0; i < this->pump_.size(); i++) { // check each existing registered pump
518 if (this->pump_[i].on_switch() == pump_switch) { // if the "new" pump matches one we already have...
519 this->valve_[valve_number].pump_switch_index = i; // ...save its index in the SprinklerSwitch vector pump_...
520 return; // ...and we are done
521 }
522 } // if we end up here, no pumps matched, so add a new one and set the valve's SprinklerSwitch at it
523 this->pump_.resize(this->pump_.size() + 1);
524 this->pump_.back().set_on_switch(pump_switch);
525 this->valve_[valve_number].pump_switch_index = this->pump_.size() - 1; // save the index to the new pump
526 }
527}
528
529void Sprinkler::configure_valve_pump_switch_pulsed(size_t valve_number, switch_::Switch *pump_switch_off,
530 switch_::Switch *pump_switch_on, uint32_t pulse_duration) {
531 if (this->is_a_valid_valve(valve_number)) {
532 for (size_t i = 0; i < this->pump_.size(); i++) { // check each existing registered pump
533 if ((this->pump_[i].off_switch() == pump_switch_off) &&
534 (this->pump_[i].on_switch() == pump_switch_on)) { // if the "new" pump matches one we already have...
535 this->valve_[valve_number].pump_switch_index = i; // ...save its index in the SprinklerSwitch vector pump_...
536 return; // ...and we are done
537 }
538 } // if we end up here, no pumps matched, so add a new one and set the valve's SprinklerSwitch at it
539 this->pump_.resize(this->pump_.size() + 1);
540 this->pump_.back().set_off_switch(pump_switch_off);
541 this->pump_.back().set_on_switch(pump_switch_on);
542 this->pump_.back().set_pulse_duration(pulse_duration);
543 this->valve_[valve_number].pump_switch_index = this->pump_.size() - 1; // save the index to the new pump
544 }
545}
546
548 SprinklerControllerNumber *run_duration_number) {
549 if (this->is_a_valid_valve(valve_number)) {
550 this->valve_[valve_number].run_duration_number = run_duration_number;
551 }
552}
553
555 if (!divider.has_value()) {
556 return;
557 }
558 if (divider.value() > 0) {
559 this->set_multiplier(1.0 / divider.value());
560 this->set_repeat(divider.value() - 1);
561 } else if (divider.value() == 0) {
562 this->set_multiplier(1.0);
563 this->set_repeat(0);
564 }
565}
566
568 if ((!multiplier.has_value()) || (multiplier.value() < 0)) {
569 return;
570 }
571 this->multiplier_ = multiplier.value();
572 if (this->multiplier_number_ == nullptr) {
573 return;
574 }
575 if (this->multiplier_number_->state == multiplier.value()) {
576 return;
577 }
578 auto call = this->multiplier_number_->make_call();
579 call.set_value(multiplier.value());
580 call.perform();
581}
582
584 this->next_prev_ignore_disabled_ = ignore_disabled;
585}
586
587void Sprinkler::set_pump_start_delay(uint32_t start_delay) {
588 this->start_delay_is_valve_delay_ = false;
589 this->start_delay_ = start_delay;
590}
591
592void Sprinkler::set_pump_stop_delay(uint32_t stop_delay) {
593 this->stop_delay_is_valve_delay_ = false;
594 this->stop_delay_ = stop_delay;
595}
596
597void Sprinkler::set_valve_start_delay(uint32_t start_delay) {
598 this->start_delay_is_valve_delay_ = true;
599 this->start_delay_ = start_delay;
600}
601
602void Sprinkler::set_valve_stop_delay(uint32_t stop_delay) {
603 this->stop_delay_is_valve_delay_ = true;
604 this->stop_delay_ = stop_delay;
605}
606
607void Sprinkler::set_pump_switch_off_during_valve_open_delay(bool pump_switch_off_during_valve_open_delay) {
608 this->pump_switch_off_during_valve_open_delay_ = pump_switch_off_during_valve_open_delay;
609}
610
611void Sprinkler::set_valve_open_delay(const uint32_t valve_open_delay) {
612 if (valve_open_delay > 0) {
613 this->valve_overlap_ = false;
614 this->switching_delay_ = valve_open_delay;
615 } else {
616 this->switching_delay_.reset();
617 }
618}
619
620void Sprinkler::set_valve_overlap(uint32_t valve_overlap) {
621 if (valve_overlap > 0) {
622 this->valve_overlap_ = true;
623 this->switching_delay_ = valve_overlap;
624 } else {
625 this->switching_delay_.reset();
626 }
627 this->pump_switch_off_during_valve_open_delay_ = false; // incompatible option
628}
629
630void Sprinkler::set_manual_selection_delay(uint32_t manual_selection_delay) {
631 if (manual_selection_delay > 0) {
632 this->manual_selection_delay_ = manual_selection_delay;
633 } else {
635 }
636}
637
638void Sprinkler::set_valve_run_duration(const optional<size_t> valve_number, const optional<uint32_t> run_duration) {
639 if (!valve_number.has_value() || !run_duration.has_value()) {
640 return;
641 }
642 if (!this->is_a_valid_valve(valve_number.value())) {
643 return;
644 }
645 this->valve_[valve_number.value()].run_duration = run_duration.value();
646 if (this->valve_[valve_number.value()].run_duration_number == nullptr) {
647 return;
648 }
649 if (this->valve_[valve_number.value()].run_duration_number->state == run_duration.value()) {
650 return;
651 }
652 auto call = this->valve_[valve_number.value()].run_duration_number->make_call();
653 if (this->valve_[valve_number.value()].run_duration_number->traits.get_unit_of_measurement() == MIN_STR) {
654 call.set_value(run_duration.value() / 60.0);
655 } else {
656 call.set_value(run_duration.value());
657 }
658 call.perform();
659}
660
661void Sprinkler::set_auto_advance(const bool auto_advance) {
662 if (this->auto_adv_sw_ == nullptr) {
663 return;
664 }
665 if (this->auto_adv_sw_->state == auto_advance) {
666 return;
667 }
668 if (auto_advance) {
669 this->auto_adv_sw_->turn_on();
670 } else {
671 this->auto_adv_sw_->turn_off();
672 }
673}
674
676 this->target_repeats_ = repeat;
677 if (this->repeat_number_ == nullptr) {
678 return;
679 }
680 if (this->repeat_number_->state == repeat.value()) {
681 return;
682 }
683 auto call = this->repeat_number_->make_call();
684 call.set_value(repeat.value_or(0));
685 call.perform();
686}
687
688void Sprinkler::set_queue_enable(bool queue_enable) {
689 if (this->queue_enable_sw_ == nullptr) {
690 return;
691 }
692 if (this->queue_enable_sw_->state == queue_enable) {
693 return;
694 }
695 if (queue_enable) {
696 this->queue_enable_sw_->turn_on();
697 } else {
698 this->queue_enable_sw_->turn_off();
699 }
700}
701
702void Sprinkler::set_reverse(const bool reverse) {
703 if (this->reverse_sw_ == nullptr) {
704 return;
705 }
706 if (this->reverse_sw_->state == reverse) {
707 return;
708 }
709 if (reverse) {
710 this->reverse_sw_->turn_on();
711 } else {
712 this->reverse_sw_->turn_off();
713 }
714}
715
716void Sprinkler::set_standby(const bool standby) {
717 if (this->standby_sw_ == nullptr) {
718 return;
719 }
720 if (this->standby_sw_->state == standby) {
721 return;
722 }
723 if (standby) {
724 this->standby_sw_->turn_on();
725 } else {
726 this->standby_sw_->turn_off();
727 }
728}
729
730uint32_t Sprinkler::valve_run_duration(const size_t valve_number) {
731 if (!this->is_a_valid_valve(valve_number)) {
732 return 0;
733 }
734 if (this->valve_[valve_number].run_duration_number != nullptr) {
735 if (this->valve_[valve_number].run_duration_number->traits.get_unit_of_measurement() == MIN_STR) {
736 return static_cast<uint32_t>(roundf(this->valve_[valve_number].run_duration_number->state * 60));
737 } else {
738 return static_cast<uint32_t>(roundf(this->valve_[valve_number].run_duration_number->state));
739 }
740 }
741 return this->valve_[valve_number].run_duration;
742}
743
744uint32_t Sprinkler::valve_run_duration_adjusted(const size_t valve_number) {
745 uint32_t run_duration = 0;
746
747 if (this->is_a_valid_valve(valve_number)) {
748 run_duration = this->valve_run_duration(valve_number);
749 }
750 run_duration = static_cast<uint32_t>(roundf(run_duration * this->multiplier()));
751 // run_duration must not be less than any of these
752 if ((run_duration < this->start_delay_) || (run_duration < this->stop_delay_) ||
753 (run_duration < this->switching_delay_.value_or(0) * 2)) {
754 return std::max(this->switching_delay_.value_or(0) * 2, std::max(this->start_delay_, this->stop_delay_));
755 }
756 return run_duration;
757}
758
760 if (this->auto_adv_sw_ != nullptr) {
761 return this->auto_adv_sw_->state;
762 }
763 return true;
764}
765
767 if (this->multiplier_number_ != nullptr) {
768 return this->multiplier_number_->state;
769 }
770 return this->multiplier_;
771}
772
774 if (this->repeat_number_ != nullptr) {
775 return static_cast<uint32_t>(roundf(this->repeat_number_->state));
776 }
777 return this->target_repeats_;
778}
779
781 // if there is an active valve and auto-advance is enabled, we may be repeating, so return the count
782 if (this->active_req_.has_request() && this->auto_advance()) {
783 return this->repeat_count_;
784 }
785 return nullopt;
786}
787
789 if (this->queue_enable_sw_ != nullptr) {
790 return this->queue_enable_sw_->state;
791 }
792 return true;
793}
794
796 if (this->reverse_sw_ != nullptr) {
797 return this->reverse_sw_->state;
798 }
799 return false;
800}
801
803 if (this->standby_sw_ != nullptr) {
804 return this->standby_sw_->state;
805 }
806 return false;
807}
808
810 if (this->standby()) {
811 ESP_LOGD(TAG, "start_from_queue called but standby is enabled; no action taken");
812 return;
813 }
814 if (this->multiplier() == 0) {
815 ESP_LOGD(TAG, "start_from_queue called but multiplier is set to zero; no action taken");
816 return;
817 }
818 if (this->queued_valves_.empty()) {
819 return; // if there is nothing in the queue, don't do anything
820 }
821 if (this->queue_enabled() && this->active_valve().has_value()) {
822 return; // if there is already a valve running from the queue, do nothing
823 }
824
825 this->set_auto_advance(false);
826 this->set_queue_enable(true);
827
828 this->reset_cycle_states_(); // just in case auto-advance is switched on later
829 this->repeat_count_ = 0;
830 this->fsm_kick_(); // will automagically pick up from the queue (it has priority)
831}
832
834 if (this->standby()) {
835 ESP_LOGD(TAG, "start_full_cycle called but standby is enabled; no action taken");
836 return;
837 }
838 if (this->multiplier() == 0) {
839 ESP_LOGD(TAG, "start_full_cycle called but multiplier is set to zero; no action taken");
840 return;
841 }
842 if (this->auto_advance() && this->active_valve().has_value()) {
843 return; // if auto-advance is already enabled and there is already a valve running, do nothing
844 }
845
846 this->set_queue_enable(false);
847
848 this->prep_full_cycle_();
849 this->repeat_count_ = 0;
850 // if there is no active valve already, start the first valve in the cycle
851 if (!this->active_req_.has_request()) {
852 this->fsm_kick_();
853 }
854}
855
857 if (this->standby()) {
858 ESP_LOGD(TAG, "start_single_valve called but standby is enabled; no action taken");
859 return;
860 }
861 if (this->multiplier() == 0) {
862 ESP_LOGD(TAG, "start_single_valve called but multiplier is set to zero; no action taken");
863 return;
864 }
865 if (!valve_number.has_value() || (valve_number == this->active_valve())) {
866 return;
867 }
868
869 this->set_auto_advance(false);
870 this->set_queue_enable(false);
871
872 this->reset_cycle_states_(); // just in case auto-advance is switched on later
873 this->repeat_count_ = 0;
874 this->fsm_request_(valve_number.value(), run_duration.value_or(0));
875}
876
878 if (valve_number.has_value()) {
879 if (this->is_a_valid_valve(valve_number.value()) && (this->queued_valves_.size() < this->max_queue_size_)) {
880 SprinklerQueueItem item{valve_number.value(), run_duration.value()};
881 this->queued_valves_.insert(this->queued_valves_.begin(), item);
882 ESP_LOGD(TAG, "Valve %zu placed into queue with run duration of %" PRIu32 " seconds", valve_number.value_or(0),
883 run_duration.value_or(0));
884 }
885 }
886}
887
889 this->queued_valves_.clear();
890 ESP_LOGD(TAG, "Queue cleared");
891}
892
894 if (this->state_ == IDLE) {
895 this->reset_cycle_states_(); // just in case auto-advance is switched on later
896 }
897
898 this->manual_valve_ = this->next_valve_number_(
899 this->manual_valve_.value_or(this->active_req_.valve_as_opt().value_or(this->number_of_valves() - 1)),
900 !this->next_prev_ignore_disabled_, true);
901
902 if (!this->manual_valve_.has_value()) {
903 ESP_LOGD(TAG, "next_valve was called but no valve could be started; perhaps next_prev_ignore_disabled allows only "
904 "enabled valves and no valves are enabled?");
905 return;
906 }
907
911 } else {
912 this->fsm_request_(this->manual_valve_.value());
913 }
914}
915
917 if (this->state_ == IDLE) {
918 this->reset_cycle_states_(); // just in case auto-advance is switched on later
919 }
920
921 this->manual_valve_ =
922 this->previous_valve_number_(this->manual_valve_.value_or(this->active_req_.valve_as_opt().value_or(0)),
923 !this->next_prev_ignore_disabled_, true);
924
925 if (!this->manual_valve_.has_value()) {
926 ESP_LOGD(TAG, "previous_valve was called but no valve could be started; perhaps next_prev_ignore_disabled allows "
927 "only enabled valves and no valves are enabled?");
928 return;
929 }
930
934 } else {
935 this->fsm_request_(this->manual_valve_.value());
936 }
937}
938
939void Sprinkler::shutdown(bool clear_queue) {
941 this->active_req_.reset();
942 this->manual_valve_.reset();
943 this->next_req_.reset();
944 for (auto &vo : this->valve_op_) {
945 vo.stop();
946 }
948 if (clear_queue) {
949 this->clear_queued_valves();
950 this->repeat_count_ = 0;
951 }
952}
953
955 if (this->paused_valve_.has_value() || !this->active_req_.has_request()) {
956 return; // we can't pause if we're already paused or if there is no active valve
957 }
958 this->paused_valve_ = this->active_valve();
960 this->shutdown(false);
961 ESP_LOGD(TAG, "Paused valve %zu with %" PRIu32 " seconds remaining", this->paused_valve_.value_or(0),
962 this->resume_duration_.value_or(0));
963}
964
966 if (this->standby()) {
967 ESP_LOGD(TAG, "resume called but standby is enabled; no action taken");
968 return;
969 }
970
971 if (this->paused_valve_.has_value() && (this->resume_duration_.has_value())) {
972 // Resume only if valve has not been completed yet
973 if (!this->valve_cycle_complete_(this->paused_valve_.value())) {
974 ESP_LOGD(TAG, "Resuming valve %zu with %" PRIu32 " seconds remaining", this->paused_valve_.value_or(0),
975 this->resume_duration_.value_or(0));
976 this->fsm_request_(this->paused_valve_.value(), this->resume_duration_.value());
977 }
978 this->reset_resume();
979 } else {
980 ESP_LOGD(TAG, "No valve to resume!");
981 }
982}
983
985 if (this->paused_valve_.has_value() && (this->resume_duration_.has_value())) {
986 this->resume();
987 } else {
988 this->start_full_cycle();
989 }
990}
991
993 this->paused_valve_.reset();
994 this->resume_duration_.reset();
995}
996
997const char *Sprinkler::valve_name(const size_t valve_number) {
998 if (this->is_a_valid_valve(valve_number)) {
999 return this->valve_[valve_number].controller_switch->get_name().c_str();
1000 }
1001 return nullptr;
1002}
1003
1010
1012 if (!this->valve_overlap_ && this->prev_req_.has_request() &&
1013 (this->prev_req_.valve_operator()->state() == STARTING || this->prev_req_.valve_operator()->state() == ACTIVE)) {
1014 return this->prev_req_.valve_as_opt();
1015 }
1016 return this->active_req_.valve_as_opt();
1017}
1018
1020
1022 if (!this->queued_valves_.empty()) {
1023 return this->queued_valves_.back().valve_number;
1024 }
1025 return nullopt;
1026}
1027
1029
1030size_t Sprinkler::number_of_valves() { return this->valve_.size(); }
1031
1032bool Sprinkler::is_a_valid_valve(const size_t valve_number) {
1033 return ((valve_number >= 0) && (valve_number < this->number_of_valves()));
1034}
1035
1037 if (pump_switch == nullptr) {
1038 return false; // we can't do anything if there's nothing to check
1039 }
1040 // a pump must be considered "in use" if a (distribution) valve it supplies is active. this means:
1041 // - at least one SprinklerValveOperator:
1042 // - has a valve loaded that depends on this pump
1043 // - is in a state that depends on the pump: (ACTIVE and _possibly_ STARTING/STOPPING)
1044 // - if NO SprinklerValveOperator is active but there is a run request pending (active_req_.has_request()) and the
1045 // controller state is STARTING, valve open delay is configured but NOT pump_switch_off_during_valve_open_delay_
1046 for (auto &vo : this->valve_op_) { // first, check if any SprinklerValveOperator has a valve dependent on this pump
1047 if ((vo.state() != BYPASS) && (vo.pump_switch() != nullptr)) {
1048 // the SprinklerValveOperator is configured with a pump; now check if it is the pump of interest
1049 if ((vo.pump_switch()->off_switch() == pump_switch->off_switch()) &&
1050 (vo.pump_switch()->on_switch() == pump_switch->on_switch())) {
1051 // now if the SprinklerValveOperator has a pump and it is either ACTIVE, is STARTING with a valve delay or
1052 // is STOPPING with a valve delay, its pump can be considered "in use", so just return indicating this now
1053 if ((vo.state() == ACTIVE) ||
1054 ((vo.state() == STARTING) && this->start_delay_ && this->start_delay_is_valve_delay_) ||
1055 ((vo.state() == STOPPING) && this->stop_delay_ && this->stop_delay_is_valve_delay_)) {
1056 return true;
1057 }
1058 }
1059 }
1060 } // if we end up here, no SprinklerValveOperator was in a "give-away" state indicating that the pump is in use...
1062 this->active_req_.has_request() && (this->state_ != STOPPING)) {
1063 // ...the controller is configured to keep the pump on during a valve open delay, so just return
1064 // whether or not the next valve shares the same pump
1065 return (pump_switch->off_switch() == this->valve_pump_switch(this->active_req_.valve())->off_switch()) &&
1066 (pump_switch->on_switch() == this->valve_pump_switch(this->active_req_.valve())->on_switch());
1067 }
1068 return false;
1069}
1070
1072 if (pump_switch == nullptr) {
1073 return; // we can't do anything if there's nothing to check
1074 }
1075
1076 bool hold_pump_on = false;
1077
1078 for (auto &controller : this->other_controllers_) { // check if the pump is in use by another controller
1079 if (controller != this) { // dummy check
1080 if (controller->pump_in_use(pump_switch)) {
1081 hold_pump_on = true; // if another controller says it's using this pump, keep it on
1082 // at this point we know if there exists another SprinklerSwitch that is "on" with its
1083 // off_switch_ and on_switch_ pointers pointing to the same pair of switch objects
1084 }
1085 }
1086 }
1087 if (hold_pump_on) {
1088 // at this point we know if there exists another SprinklerSwitch that is "on" with its
1089 // off_switch_ and on_switch_ pointers pointing to the same pair of switch objects...
1090 pump_switch->sync_valve_state(true); // ...so ensure our state is consistent
1091 ESP_LOGD(TAG, "Leaving pump on because another controller instance is using it");
1092 }
1093
1094 if (state) { // ...and now we can set the new state of the switch
1095 pump_switch->turn_on();
1096 } else if (!hold_pump_on && !this->pump_in_use(pump_switch)) {
1097 pump_switch->turn_off();
1098 } else if (hold_pump_on) { // we must assume the other controller will switch off the pump when done...
1099 pump_switch->sync_valve_state(false); // ...this only impacts latching valves
1100 }
1101}
1102
1104 uint32_t total_time_remaining = 0;
1105
1106 for (size_t valve = 0; valve < this->number_of_valves(); valve++) {
1107 total_time_remaining += this->valve_run_duration_adjusted(valve);
1108 }
1109
1110 if (this->valve_overlap_) {
1111 total_time_remaining -= this->switching_delay_.value_or(0) * (this->number_of_valves() - 1);
1112 } else {
1113 total_time_remaining += this->switching_delay_.value_or(0) * (this->number_of_valves() - 1);
1114 }
1115
1116 return total_time_remaining;
1117}
1118
1120 uint32_t total_time_remaining = 0;
1121 uint32_t valve_count = 0;
1122
1123 for (size_t valve = 0; valve < this->number_of_valves(); valve++) {
1124 if (this->valve_is_enabled_(valve)) {
1125 total_time_remaining += this->valve_run_duration_adjusted(valve);
1126 valve_count++;
1127 }
1128 }
1129
1130 if (valve_count) {
1131 if (this->valve_overlap_) {
1132 total_time_remaining -= this->switching_delay_.value_or(0) * (valve_count - 1);
1133 } else {
1134 total_time_remaining += this->switching_delay_.value_or(0) * (valve_count - 1);
1135 }
1136 }
1137
1138 return total_time_remaining;
1139}
1140
1142 uint32_t total_time_remaining = 0;
1143 uint32_t enabled_valve_count = 0;
1144 uint32_t incomplete_valve_count = 0;
1145
1146 for (size_t valve = 0; valve < this->number_of_valves(); valve++) {
1147 if (this->valve_is_enabled_(valve)) {
1148 enabled_valve_count++;
1149 if (!this->valve_cycle_complete_(valve)) {
1150 if (!this->active_valve().has_value() || (valve != this->active_valve().value())) {
1151 total_time_remaining += this->valve_run_duration_adjusted(valve);
1152 incomplete_valve_count++;
1153 } else {
1154 // to get here, there must be an active valve and this valve must be equal to 'valve'
1155 if (this->active_req_.valve_operator() == nullptr) { // no SVO has been assigned yet so it hasn't started
1156 total_time_remaining += this->valve_run_duration_adjusted(valve);
1157 incomplete_valve_count++;
1158 }
1159 }
1160 }
1161 }
1162 }
1163
1164 if (incomplete_valve_count >= enabled_valve_count) {
1165 incomplete_valve_count--;
1166 }
1167 if (incomplete_valve_count) {
1168 if (this->valve_overlap_) {
1169 total_time_remaining -= this->switching_delay_.value_or(0) * incomplete_valve_count;
1170 } else {
1171 total_time_remaining += this->switching_delay_.value_or(0) * incomplete_valve_count;
1172 }
1173 }
1174
1175 return total_time_remaining;
1176}
1177
1179 uint32_t total_time_remaining = 0;
1180 uint32_t valve_count = 0;
1181
1182 for (auto &valve : this->queued_valves_) {
1183 if (valve.run_duration) {
1184 total_time_remaining += valve.run_duration;
1185 } else {
1186 total_time_remaining += this->valve_run_duration_adjusted(valve.valve_number);
1187 }
1188 valve_count++;
1189 }
1190
1191 if (valve_count) {
1192 if (this->valve_overlap_) {
1193 total_time_remaining -= this->switching_delay_.value_or(0) * (valve_count - 1);
1194 } else {
1195 total_time_remaining += this->switching_delay_.value_or(0) * (valve_count - 1);
1196 }
1197 }
1198
1199 return total_time_remaining;
1200}
1201
1203 if (this->active_req_.has_request()) { // first try to return the value based on active_req_...
1204 if (this->active_req_.valve_operator() != nullptr) {
1205 return this->active_req_.valve_operator()->time_remaining();
1206 }
1207 }
1208 if (this->prev_req_.has_request()) { // try to return the value based on prev_req_...
1209 if (this->prev_req_.valve_operator() != nullptr) {
1210 return this->prev_req_.valve_operator()->time_remaining();
1211 }
1212 }
1213 return nullopt;
1214}
1215
1217 if (!this->time_remaining_active_valve().has_value() && this->state_ == IDLE) {
1218 return nullopt;
1219 }
1220
1221 auto total_time_remaining = this->time_remaining_active_valve().value_or(0);
1222 if (this->auto_advance()) {
1223 total_time_remaining += this->total_cycle_time_enabled_incomplete_valves();
1224 if (this->repeat().value_or(0) > 0) {
1225 total_time_remaining +=
1226 (this->total_cycle_time_enabled_valves() * (this->repeat().value_or(0) - this->repeat_count().value_or(0)));
1227 }
1228 }
1229
1230 if (this->queue_enabled()) {
1231 total_time_remaining += this->total_queue_time();
1232 }
1233 return total_time_remaining;
1234}
1235
1237 if (this->state_ != IDLE) {
1238 return true;
1239 }
1240
1241 for (auto &controller : this->other_controllers_) {
1242 if (controller != this) { // dummy check
1243 if (controller->controller_state() != IDLE) {
1244 return true;
1245 }
1246 }
1247 }
1248 return false;
1249}
1250
1252 if (this->is_a_valid_valve(valve_number)) {
1253 return this->valve_[valve_number].controller_switch;
1254 }
1255 return nullptr;
1256}
1257
1259 if (this->is_a_valid_valve(valve_number)) {
1260 return this->valve_[valve_number].enable_switch;
1261 }
1262 return nullptr;
1263}
1264
1265SprinklerSwitch *Sprinkler::valve_switch(const size_t valve_number) {
1266 if (this->is_a_valid_valve(valve_number)) {
1267 return &this->valve_[valve_number].valve_switch;
1268 }
1269 return nullptr;
1270}
1271
1273 if (this->is_a_valid_valve(valve_number) && this->valve_[valve_number].pump_switch_index.has_value()) {
1274 return &this->pump_[this->valve_[valve_number].pump_switch_index.value()];
1275 }
1276 return nullptr;
1277}
1278
1280 if (pump_index < this->pump_.size()) {
1281 return &this->pump_[pump_index];
1282 }
1283 return nullptr;
1284}
1285
1286bool Sprinkler::valve_is_enabled_(const size_t valve_number) {
1287 if (this->is_a_valid_valve(valve_number)) {
1288 if (this->valve_[valve_number].enable_switch != nullptr) {
1289 return this->valve_[valve_number].enable_switch->state;
1290 } else {
1291 return true;
1292 }
1293 }
1294 return false;
1295}
1296
1297void Sprinkler::mark_valve_cycle_complete_(const size_t valve_number) {
1298 if (this->is_a_valid_valve(valve_number)) {
1299 ESP_LOGD(TAG, "Marking valve %u complete", valve_number);
1300 this->valve_[valve_number].valve_cycle_complete = true;
1301 }
1302}
1303
1304bool Sprinkler::valve_cycle_complete_(const size_t valve_number) {
1305 if (this->is_a_valid_valve(valve_number)) {
1306 return this->valve_[valve_number].valve_cycle_complete;
1307 }
1308 return false;
1309}
1310
1311optional<size_t> Sprinkler::next_valve_number_(const optional<size_t> first_valve, const bool include_disabled,
1312 const bool include_complete) {
1313 auto valve = first_valve.value_or(0);
1314 size_t start = first_valve.has_value() ? 1 : 0;
1315
1316 if (!this->is_a_valid_valve(valve)) {
1317 valve = 0;
1318 }
1319
1320 for (size_t offset = start; offset < this->number_of_valves(); offset++) {
1321 auto valve_of_interest = valve + offset;
1322 if (!this->is_a_valid_valve(valve_of_interest)) {
1323 valve_of_interest -= this->number_of_valves();
1324 }
1325
1326 if ((this->valve_is_enabled_(valve_of_interest) || include_disabled) &&
1327 (!this->valve_cycle_complete_(valve_of_interest) || include_complete)) {
1328 return valve_of_interest;
1329 }
1330 }
1331 return nullopt;
1332}
1333
1334optional<size_t> Sprinkler::previous_valve_number_(const optional<size_t> first_valve, const bool include_disabled,
1335 const bool include_complete) {
1336 auto valve = first_valve.value_or(this->number_of_valves() - 1);
1337 size_t start = first_valve.has_value() ? 1 : 0;
1338
1339 if (!this->is_a_valid_valve(valve)) {
1340 valve = this->number_of_valves() - 1;
1341 }
1342
1343 for (size_t offset = start; offset < this->number_of_valves(); offset++) {
1344 auto valve_of_interest = valve - offset;
1345 if (!this->is_a_valid_valve(valve_of_interest)) {
1346 valve_of_interest += this->number_of_valves();
1347 }
1348
1349 if ((this->valve_is_enabled_(valve_of_interest) || include_disabled) &&
1350 (!this->valve_cycle_complete_(valve_of_interest) || include_complete)) {
1351 return valve_of_interest;
1352 }
1353 }
1354 return nullopt;
1355}
1356
1358 if (this->reverse()) {
1359 return this->previous_valve_number_(first_valve, false, false);
1360 }
1361 return this->next_valve_number_(first_valve, false, false);
1362}
1363
1365 if (this->active_req_.has_request()) {
1366 this->prev_req_ = this->active_req_;
1367 } else {
1368 this->prev_req_.reset();
1369 }
1370
1371 if (this->next_req_.has_request()) {
1372 if (!this->next_req_.run_duration()) { // ensure the run duration is set correctly for consumption later on
1374 }
1375 return; // there is already a request pending
1376 } else if (this->queue_enabled() && !this->queued_valves_.empty()) {
1377 this->next_req_.set_valve(this->queued_valves_.back().valve_number);
1379 if (this->queued_valves_.back().run_duration) {
1380 this->next_req_.set_run_duration(this->queued_valves_.back().run_duration);
1381 this->queued_valves_.pop_back();
1382 } else if (this->multiplier()) {
1383 this->next_req_.set_run_duration(this->valve_run_duration_adjusted(this->queued_valves_.back().valve_number));
1384 this->queued_valves_.pop_back();
1385 } else {
1386 this->next_req_.reset();
1387 }
1388 } else if (this->auto_advance() && this->multiplier()) {
1389 if (this->next_valve_number_in_cycle_(first_valve).has_value()) {
1390 // if there is another valve to run as a part of a cycle, load that
1391 this->next_req_.set_valve(this->next_valve_number_in_cycle_(first_valve).value_or(0));
1394 this->valve_run_duration_adjusted(this->next_valve_number_in_cycle_(first_valve).value_or(0)));
1395 } else if ((this->repeat_count_++ < this->repeat().value_or(0))) {
1396 ESP_LOGD(TAG, "Repeating - starting cycle %" PRIu32 " of %" PRIu32, this->repeat_count_ + 1,
1397 this->repeat().value_or(0) + 1);
1398 // if there are repeats remaining and no more valves were left in the cycle, start a new cycle
1399 this->prep_full_cycle_();
1400 if (this->next_valve_number_in_cycle_().has_value()) { // this should always succeed here, but just in case...
1401 this->next_req_.set_valve(this->next_valve_number_in_cycle_().value_or(0));
1404 this->valve_run_duration_adjusted(this->next_valve_number_in_cycle_().value_or(0)));
1405 }
1406 }
1407 }
1408}
1409
1411 for (size_t valve_number = 0; valve_number < this->number_of_valves(); valve_number++) {
1412 if (this->valve_is_enabled_(valve_number))
1413 return true;
1414 }
1415 return false;
1416}
1417
1419 if (!req->has_request()) {
1420 return; // we can't do anything if the request contains nothing
1421 }
1422 if (!this->is_a_valid_valve(req->valve())) {
1423 return; // we can't do anything if the valve number isn't valid
1424 }
1425 for (auto &vo : this->valve_op_) { // find the first available SprinklerValveOperator, load it and start it up
1426 if (vo.state() == IDLE) {
1427 auto run_duration = req->run_duration() ? req->run_duration() : this->valve_run_duration_adjusted(req->valve());
1428 ESP_LOGD(TAG, "%s is starting valve %zu for %" PRIu32 " seconds, cycle %" PRIu32 " of %" PRIu32,
1429 this->req_as_str_(req->request_is_from()).c_str(), req->valve(), run_duration, this->repeat_count_ + 1,
1430 this->repeat().value_or(0) + 1);
1431 req->set_valve_operator(&vo);
1432 vo.set_controller(this);
1433 vo.set_valve(&this->valve_[req->valve()]);
1434 vo.set_run_duration(run_duration);
1435 vo.set_start_delay(this->start_delay_, this->start_delay_is_valve_delay_);
1436 vo.set_stop_delay(this->stop_delay_, this->stop_delay_is_valve_delay_);
1437 vo.start();
1438 return;
1439 }
1440 }
1441}
1442
1443void Sprinkler::all_valves_off_(const bool include_pump) {
1444 for (size_t valve_index = 0; valve_index < this->number_of_valves(); valve_index++) {
1445 if (this->valve_[valve_index].valve_switch.state()) {
1446 this->valve_[valve_index].valve_switch.turn_off();
1447 }
1448 if (include_pump) {
1449 this->set_pump_state(this->valve_pump_switch(valve_index), false);
1450 }
1451 }
1452 ESP_LOGD(TAG, "All valves stopped%s", include_pump ? ", including pumps" : "");
1453}
1454
1456 this->set_auto_advance(true);
1457
1458 if (!this->any_valve_is_enabled_()) {
1459 for (auto &valve : this->valve_) {
1460 if (valve.enable_switch != nullptr) {
1461 if (!valve.enable_switch->state) {
1462 valve.enable_switch->turn_on();
1463 }
1464 }
1465 }
1466 }
1467 this->reset_cycle_states_();
1468}
1469
1471 for (auto &valve : this->valve_) {
1472 valve.valve_cycle_complete = false;
1473 }
1474}
1475
1476void Sprinkler::fsm_request_(size_t requested_valve, uint32_t requested_run_duration) {
1477 this->next_req_.set_valve(requested_valve);
1478 this->next_req_.set_run_duration(requested_run_duration);
1479 // if state is IDLE or ACTIVE, call fsm_transition_() to start it immediately;
1480 // otherwise, fsm_transition() will pick up next_req_ at the next appropriate transition
1481 this->fsm_kick_();
1482}
1483
1485 if ((this->state_ == IDLE) || (this->state_ == ACTIVE)) {
1486 this->fsm_transition_();
1487 }
1488}
1489
1491 ESP_LOGVV(TAG, "fsm_transition_ called; state is %s", this->state_as_str_(this->state_).c_str());
1492 switch (this->state_) {
1493 case IDLE: // the system was off -> start it up
1494 // advances to ACTIVE
1496 break;
1497
1498 case ACTIVE:
1499 // advances to STOPPING or ACTIVE (again)
1501 break;
1502
1503 case STARTING: {
1504 // follows valve open delay interval
1506 this->active_req_.run_duration() - this->switching_delay_.value_or(0));
1508 this->start_valve_(&this->active_req_);
1509 this->state_ = ACTIVE;
1510 if (this->next_req_.has_request()) {
1511 // another valve has been requested, so restart the timer so we pick it up quickly
1514 }
1515 break;
1516 }
1517
1518 case STOPPING:
1519 // stop_delay_ has elapsed so just shut everything off
1520 this->active_req_.reset();
1521 this->manual_valve_.reset();
1522 this->all_valves_off_(true);
1523 this->state_ = IDLE;
1524 break;
1525
1526 default:
1527 break;
1528 }
1529 if (this->next_req_.has_request() && (this->state_ == IDLE)) {
1530 // another valve has been requested, so restart the timer so we pick it up quickly
1533 }
1534 ESP_LOGVV(TAG, "fsm_transition_ complete; new state is %s", this->state_as_str_(this->state_).c_str());
1535}
1536
1539
1540 if (this->next_req_.has_request()) { // there is a valve to run...
1541 this->active_req_.set_valve(this->next_req_.valve());
1544 this->next_req_.reset();
1545
1547 this->active_req_.run_duration() - this->switching_delay_.value_or(0));
1549 this->start_valve_(&this->active_req_);
1550 this->state_ = ACTIVE;
1551 }
1552}
1553
1555 if (!this->active_req_.has_request()) { // dummy check...
1557 return;
1558 }
1559
1560 if (!this->timer_active_(sprinkler::TIMER_SM)) { // only flag the valve as "complete" if the timer finished
1561 if ((this->active_req_.request_is_from() == CYCLE) || (this->active_req_.request_is_from() == USER)) {
1563 }
1564 } else {
1565 ESP_LOGD(TAG, "Valve cycle interrupted - NOT flagging valve as complete and stopping current valve");
1566 for (auto &vo : this->valve_op_) {
1567 vo.stop();
1568 }
1569 }
1570
1572
1573 if (this->next_req_.has_request()) { // there is another valve to run...
1574 bool same_pump =
1575 this->valve_pump_switch(this->active_req_.valve()) == this->valve_pump_switch(this->next_req_.valve());
1576
1577 this->active_req_.set_valve(this->next_req_.valve());
1580 this->next_req_.reset();
1581
1582 // this->state_ = ACTIVE; // state isn't changing
1583 if (this->valve_overlap_ || !this->switching_delay_.has_value()) {
1585 this->active_req_.run_duration() - this->switching_delay_.value_or(0));
1587 this->start_valve_(&this->active_req_);
1588 } else {
1589 this->set_timer_duration_(
1591 this->switching_delay_.value() * 2 +
1592 (this->pump_switch_off_during_valve_open_delay_ && same_pump ? this->stop_delay_ : 0));
1594 this->state_ = STARTING;
1595 }
1596 } else { // there is NOT another valve to run...
1598 }
1599}
1600
1607
1609 switch (origin) {
1610 case USER:
1611 return "USER";
1612
1613 case CYCLE:
1614 return "CYCLE";
1615
1616 case QUEUE:
1617 return "QUEUE";
1618
1619 default:
1620 return "UNKNOWN";
1621 }
1622}
1623
1625 switch (state) {
1626 case IDLE:
1627 return "IDLE";
1628
1629 case STARTING:
1630 return "STARTING";
1631
1632 case ACTIVE:
1633 return "ACTIVE";
1634
1635 case STOPPING:
1636 return "STOPPING";
1637
1638 case BYPASS:
1639 return "BYPASS";
1640
1641 default:
1642 return "UNKNOWN";
1643 }
1644}
1645
1647 if (this->timer_duration_(timer_index) > 0) {
1648 this->set_timeout(this->timer_[timer_index].name, this->timer_duration_(timer_index),
1649 this->timer_cbf_(timer_index));
1650 this->timer_[timer_index].start_time = millis();
1651 this->timer_[timer_index].active = true;
1652 }
1653 ESP_LOGVV(TAG, "Timer %zu started for %" PRIu32 " sec", static_cast<size_t>(timer_index),
1654 this->timer_duration_(timer_index) / 1000);
1655}
1656
1658 this->timer_[timer_index].active = false;
1659 return this->cancel_timeout(this->timer_[timer_index].name);
1660}
1661
1662bool Sprinkler::timer_active_(const SprinklerTimerIndex timer_index) { return this->timer_[timer_index].active; }
1663
1664void Sprinkler::set_timer_duration_(const SprinklerTimerIndex timer_index, const uint32_t time) {
1665 this->timer_[timer_index].time = 1000 * time;
1666}
1667
1668uint32_t Sprinkler::timer_duration_(const SprinklerTimerIndex timer_index) { return this->timer_[timer_index].time; }
1669
1670std::function<void()> Sprinkler::timer_cbf_(const SprinklerTimerIndex timer_index) {
1671 return this->timer_[timer_index].func;
1672}
1673
1675 this->timer_[sprinkler::TIMER_VALVE_SELECTION].active = false;
1676 ESP_LOGVV(TAG, "Valve selection timer expired");
1677 if (this->manual_valve_.has_value()) {
1678 this->fsm_request_(this->manual_valve_.value());
1679 this->manual_valve_.reset();
1680 }
1681}
1682
1684 this->timer_[sprinkler::TIMER_SM].active = false;
1685 ESP_LOGVV(TAG, "State machine timer expired");
1686 this->fsm_transition_();
1687}
1688
1690 ESP_LOGCONFIG(TAG, "Sprinkler Controller -- %s", this->name_.c_str());
1691 if (this->manual_selection_delay_.has_value()) {
1692 ESP_LOGCONFIG(TAG, " Manual Selection Delay: %" PRIu32 " seconds", this->manual_selection_delay_.value_or(0));
1693 }
1694 if (this->repeat().has_value()) {
1695 ESP_LOGCONFIG(TAG, " Repeat Cycles: %" PRIu32 " times", this->repeat().value_or(0));
1696 }
1697 if (this->start_delay_) {
1698 if (this->start_delay_is_valve_delay_) {
1699 ESP_LOGCONFIG(TAG, " Pump Start Valve Delay: %" PRIu32 " seconds", this->start_delay_);
1700 } else {
1701 ESP_LOGCONFIG(TAG, " Pump Start Pump Delay: %" PRIu32 " seconds", this->start_delay_);
1702 }
1703 }
1704 if (this->stop_delay_) {
1705 if (this->stop_delay_is_valve_delay_) {
1706 ESP_LOGCONFIG(TAG, " Pump Stop Valve Delay: %" PRIu32 " seconds", this->stop_delay_);
1707 } else {
1708 ESP_LOGCONFIG(TAG, " Pump Stop Pump Delay: %" PRIu32 " seconds", this->stop_delay_);
1709 }
1710 }
1711 if (this->switching_delay_.has_value()) {
1712 if (this->valve_overlap_) {
1713 ESP_LOGCONFIG(TAG, " Valve Overlap: %" PRIu32 " seconds", this->switching_delay_.value_or(0));
1714 } else {
1715 ESP_LOGCONFIG(TAG, " Valve Open Delay: %" PRIu32 " seconds", this->switching_delay_.value_or(0));
1716 ESP_LOGCONFIG(TAG, " Pump Switch Off During Valve Open Delay: %s",
1718 }
1719 }
1720 for (size_t valve_number = 0; valve_number < this->number_of_valves(); valve_number++) {
1721 ESP_LOGCONFIG(TAG, " Valve %zu:", valve_number);
1722 ESP_LOGCONFIG(TAG, " Name: %s", this->valve_name(valve_number));
1723 ESP_LOGCONFIG(TAG, " Run Duration: %" PRIu32 " seconds", this->valve_run_duration(valve_number));
1724 if (this->valve_[valve_number].valve_switch.pulse_duration()) {
1725 ESP_LOGCONFIG(TAG, " Pulse Duration: %" PRIu32 " milliseconds",
1726 this->valve_[valve_number].valve_switch.pulse_duration());
1727 }
1728 }
1729 if (!this->pump_.empty()) {
1730 ESP_LOGCONFIG(TAG, " Total number of pumps: %zu", this->pump_.size());
1731 }
1732 if (!this->valve_.empty()) {
1733 ESP_LOGCONFIG(TAG, " Total number of valves: %zu", this->valve_.size());
1734 }
1735}
1736
1737} // namespace sprinkler
1738} // namespace esphome
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.
Definition component.cpp:76
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
Definition component.cpp:72
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_object_id_hash()
void stop_action()
Stop any action connected to this trigger.
Definition automation.h:104
void trigger(Ts... x)
Inform the parent automation that the event has triggered.
Definition automation.h:96
NumberCall & set_value(float value)
NumberCall make_call()
Definition number.h:45
void publish_state(float state)
Definition number.cpp:9
NumberTraits traits
Definition number.h:49
bool has_value() const
Definition optional.h:87
value_type value_or(U const &v) const
Definition optional.h:93
value_type const & value() const
Definition optional.h:89
void set_state_lambda(std::function< optional< bool >()> &&f)
optional< std::function< optional< bool >()> > f_
Definition sprinkler.h:138
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()
bool pump_in_use(SprinklerSwitch *pump_switch)
returns true if the pump the pointer points to is in use
SprinklerSwitch * valve_pump_switch(size_t valve_number)
returns a pointer to a valve's pump switch object
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:518
void reset_resume()
resets resume state
SprinklerControllerNumber * repeat_number_
Definition sprinkler.h:599
bool valve_is_enabled_(size_t valve_number)
returns true if valve number is enabled
SprinklerState state_
Sprinkler controller state.
Definition sprinkler.h:537
void next_valve()
advances to the next valve (numerically)
std::vector< SprinklerSwitch > pump_
Sprinkler valve pump objects.
Definition sprinkler.h:576
uint32_t repeat_count_
Number of times the full cycle has been repeated.
Definition sprinkler.h:567
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:579
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:558
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:546
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)
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:531
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:598
std::vector< SprinklerValveOperator > valve_op_
Sprinkler valve operator objects.
Definition sprinkler.h:582
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::string req_as_str_(SprinklerValveRunRequestOrigin origin)
return the specified SprinklerValveRunRequestOrigin as a string
std::vector< SprinklerTimer > timer_
Valve control timers.
Definition sprinkler.h:585
SprinklerControllerSwitch * controller_sw_
Definition sprinkler.h:592
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)
std::string state_as_str_(SprinklerState state)
return the specified SprinklerState state as a string
optional< uint32_t > target_repeats_
Set the number of times to repeat a full cycle.
Definition sprinkler.h:555
void set_controller_main_switch(SprinklerControllerSwitch *controller_switch)
configure important controller switches
optional< uint32_t > time_remaining_current_operation()
returns the amount of time remaining in seconds for all valves remaining, including the active valve,...
SprinklerControllerSwitch * reverse_sw_
Definition sprinkler.h:594
optional< uint32_t > switching_delay_
Valve switching delay.
Definition sprinkler.h:564
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:540
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
bool start_delay_is_valve_delay_
Pump start/stop delay interval types.
Definition sprinkler.h:527
optional< size_t > manual_valve_
The number of the manually selected valve currently selected.
Definition sprinkler.h:549
bool pump_switch_off_during_valve_open_delay_
Pump should be off during valve_open_delay interval.
Definition sprinkler.h:521
bool cancel_timer_(SprinklerTimerIndex timer_index)
SprinklerControllerSwitch * queue_enable_sw_
Definition sprinkler.h:593
void start_timer_(SprinklerTimerIndex timer_index)
Start/cancel/get status of valve timers.
void previous_valve()
advances to the previous valve (numerically)
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:601
std::unique_ptr< ResumeOrStartAction<> > sprinkler_resumeorstart_action_
Definition sprinkler.h:603
void clear_queued_valves()
clears/removes all valves from the queue
bool any_valve_is_enabled_()
returns true if any valve is enabled
SprinklerSwitch * valve_pump_switch_by_pump_index(size_t pump_index)
returns a pointer to a valve's pump switch object
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:573
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:606
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
SprinklerSwitch * valve_switch(size_t valve_number)
returns a pointer to a valve's switch object
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:605
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:602
optional< uint32_t > manual_selection_delay_
Manual switching delay.
Definition sprinkler.h:561
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:552
SprinklerValveRunRequest next_req_
The next run request for the controller to consume after active_req_ is complete.
Definition sprinkler.h:543
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:591
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:524
void configure_valve_pump_switch_pulsed(size_t valve_number, switch_::Switch *pump_switch_off, switch_::Switch *pump_switch_on, uint32_t pulse_duration)
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:607
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 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()'
void set_pump_state(SprinklerSwitch *pump_switch, bool state)
switches on/off a pump "safely" by checking that the new state will not conflict with another control...
std::vector< Sprinkler * > other_controllers_
Other Sprinkler instances we should be aware of (used to check if pumps are in use)
Definition sprinkler.h:588
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:570
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:595
uint32_t timer_duration_(SprinklerTimerIndex timer_index)
returns time in milliseconds (ms)
void configure_valve_switch_pulsed(size_t valve_number, switch_::Switch *valve_switch_off, switch_::Switch *valve_switch_on, uint32_t pulse_duration, uint32_t run_duration)
switch_::Switch * on_switch()
Definition sprinkler.h:65
void sync_valve_state(bool latch_state)
Definition sprinkler.cpp:71
switch_::Switch * off_switch()
Definition sprinkler.h:64
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:202
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:11
void turn_off()
Turn this switch off.
Definition switch.cpp:15
bool state
The current reported state of the binary sensor.
Definition switch.h:53
void publish_state(bool state)
Publish a state to the front-end from the back-end.
Definition switch.cpp:47
optional< bool > get_initial_state_with_restore_mode()
Returns the initial state of the switch, after applying restore mode rules.
Definition switch.cpp:33
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:18
const char *const TAG
Definition spi.cpp:8
const std::string MIN_STR
Definition sprinkler.h:14
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
ESPPreferences * global_preferences
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:27
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:97
std::unique_ptr< Automation<> > valve_turn_off_automation
Definition sprinkler.h:98
SprinklerControllerSwitch * enable_switch
Definition sprinkler.h:91
std::unique_ptr< ShutdownAction<> > valve_shutdown_action
Definition sprinkler.h:96
SprinklerControllerSwitch * controller_switch
Definition sprinkler.h:90
std::unique_ptr< Automation<> > valve_turn_on_automation
Definition sprinkler.h:99
optional< size_t > pump_switch_index
Definition sprinkler.h:94