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