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