When I installed an irrigation system in my yard quite some years ago it was clear to me that I wasn’t going with the “official” controller. It was expensive, didn’t come with WiFi out of the box (that would have cost a hefty premium), thus was difficult or even impossible to integrate and it just wasn’t flexible enough.
Here’s my setup:
- 8 Valves
- 13 sprinklers
- 2 drip hoses
- 5 zones each comprised of one or more sprinklers, hoses or a combination
In addition I wanted an automation to iterate over the zones in a predefined order with predefined timings. Given those requirements, I then settled for 2 Sonoff 4CH Pro R2 flashed with Tasmota which worked flawlessly ever since. However, time has passed and the Smart Home landscape has evolved. This led me to re-evaluate my setup and I came to the conclusion that ESPHome was now easy and yet powerful enough to replace Tasmota on all my devices with it.
The Software
ESPHome is absolutely fantastic both for its simplicity, flexibility and not least its declarative nature.
Even though my architecture up to this point worked quite well it was essentially built as a collection of helpers which were all over the place and most of all lacked a critical feature: I built an automation for each valve to force-stop it after a certain time. Both as a way to just start a valve and have it shut down automatically, but also as sort of a safety if something went wrong with the automations to not have them running indefinitely.
This led to an interesting problem, though: What if Home Assistant triggered a valve but then restarted and lost track of what was going on? I fixed this by introducing another Automation to stop all valves on Home Assistant startup. But what if Home Assistant did not restart but stayed offline? To be honest, this never happened during sprinkler runtime, but there’s always the possibility and I didn’t have an answer for it - until I discovered ESPHome. This made it possible to have most of the logic on the board itself, so connectivity issues don’t pose a threat anymore.
So I went on, installed the configuration from ESPHome’s library and all was well. But then I discovered the sprinkler
controller and it blew my mind! All those years I’ve been creating various Automations, Scripts and other helpers in Home Assistant to provide good control over the individual valves as well as the automated iteration. The sprinkler controller looked like everything I’ve manually built and fine tuned over the years with some additional perks like the option to dynamically set the sprinkler runtime. And what’s more is that it does all of this in a much nicer package with even more possibilities - or so I thought.
Using the built-in sprinkler component
Let’s have a look at my initial attempt:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
| esphome:
name: irrigation
friendly_name: Irrigation
esp32:
board: esp32dev
framework:
type: arduino
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: 'my-key'
ota:
platform: esphome
password: 'my-password'
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Device Specific Config
binary_sensor:
- platform: status
name: 'Status'
sensor:
- platform: wifi_signal
name: "WiFi Signal Sensor"
update_interval: 60s
sprinkler:
# Valve controller
- id: valve_controller
main_switch: 'Alle Sprenger'
auto_advance_switch: 'Alle Sprenger Automatik'
valve_open_delay: 5s
valves:
- valve_switch: 'Sprenger 1'
enable_switch: 'Sprenger 1 aktiv'
run_duration_number:
name: 'Sprenger 1 Dauer'
initial_value: 20
unit_of_measurement: min
min_value: 1
max_value: 60
valve_switch_id: valve_1
- valve_switch: 'Sprenger 2'
enable_switch: 'Sprenger 2 aktiv'
run_duration_number:
name: 'Sprenger 2 Dauer'
initial_value: 20
unit_of_measurement: min
min_value: 1
max_value: 60
valve_switch_id: valve_2
- valve_switch: 'Sprenger 3'
enable_switch: 'Sprenger 3 aktiv'
run_duration_number:
name: 'Sprenger 3 Dauer'
initial_value: 20
unit_of_measurement: min
min_value: 1
max_value: 60
valve_switch_id: valve_3
- valve_switch: 'Sprenger 4'
enable_switch: 'Sprenger 4 aktiv'
run_duration_number:
name: 'Sprenger 4 Dauer'
initial_value: 20
unit_of_measurement: min
min_value: 1
max_value: 60
valve_switch_id: valve_4
- valve_switch: 'Sprenger 5'
enable_switch: 'Sprenger 5 aktiv'
run_duration_number:
name: 'Sprenger 5 Dauer'
initial_value: 20
unit_of_measurement: min
min_value: 1
max_value: 60
valve_switch_id: valve_5
- valve_switch: 'Sprenger 6'
enable_switch: 'Sprenger 6 aktiv'
run_duration_number:
name: 'Sprenger 6 Dauer'
initial_value: 20
unit_of_measurement: min
min_value: 1
max_value: 60
valve_switch_id: valve_6
- valve_switch: 'Sprenger 7'
enable_switch: 'Sprenger 7 aktiv'
run_duration_number:
name: 'Sprenger 7 Dauer'
initial_value: 20
unit_of_measurement: min
min_value: 1
max_value: 60
valve_switch_id: valve_7
- valve_switch: 'Sprenger 8'
enable_switch: 'Sprenger 8 aktiv'
run_duration_number:
name: 'Sprenger 8 Dauer'
initial_value: 20
unit_of_measurement: min
min_value: 1
max_value: 60
valve_switch_id: valve_8
# Zone controller
- id: zone_controller
main_switch: 'Alle Zonen'
auto_advance_switch: 'Alle Zonen Automatik'
valve_open_delay: 5s
valves:
- valve_switch: 'Zone 1'
enable_switch: 'Zone 1 aktiv'
run_duration_number:
name: 'Zone 1 Dauer'
initial_value: 15
unit_of_measurement: min
min_value: 1
max_value: 60
valve_switch_id: zone_1
- valve_switch: 'Zone 2'
enable_switch: 'Zone 2 aktiv'
run_duration_number:
name: 'Zone 2 Dauer'
initial_value: 15
unit_of_measurement: min
min_value: 1
max_value: 60
valve_switch_id: zone_2
- valve_switch: 'Zone 3'
enable_switch: 'Zone 3 aktiv'
run_duration_number:
name: 'Zone 3 Dauer'
initial_value: 10
unit_of_measurement: min
min_value: 1
max_value: 60
valve_switch_id: zone_3
- valve_switch: 'Zone 4'
enable_switch: 'Zone 4 aktiv'
run_duration_number:
name: 'Zone 4 Dauer'
initial_value: 15
unit_of_measurement: min
min_value: 1
max_value: 60
valve_switch_id: zone_4
- valve_switch: 'Zone 5'
enable_switch: 'Zone 5 aktiv'
run_duration_number:
name: 'Zone 5 Dauer'
initial_value: 30
unit_of_measurement: min
min_value: 1
max_value: 60
valve_switch_id: zone_5
switch:
# Individual valves
- platform: gpio
id: valve_1
name: 'Sprenger 1'
icon: 'mdi:sprinkler'
pin: GPIO16
on_turn_on:
- delay: 60min
- switch.turn_off: valve_1
- platform: gpio
id: valve_2
name: 'Sprenger 2'
icon: 'mdi:sprinkler'
pin: GPIO14
on_turn_on:
- delay: 60min
- switch.turn_off: valve_2
- platform: gpio
id: valve_3
name: 'Sprenger 3'
icon: 'mdi:sprinkler'
pin: GPIO12
on_turn_on:
- delay: 60min
- switch.turn_off: valve_3
- platform: gpio
id: valve_4
name: 'Sprenger 4'
icon: 'mdi:sprinkler'
pin: GPIO13
on_turn_on:
- delay: 60min
- switch.turn_off: valve_4
- platform: gpio
id: valve_5
name: 'Sprenger 5'
icon: 'mdi:sprinkler'
pin: GPIO15
on_turn_on:
- delay: 60min
- switch.turn_off: valve_5
- platform: gpio
id: valve_6
name: 'Sprenger 6'
icon: 'mdi:sprinkler'
pin: GPIO0
on_turn_on:
- delay: 60min
- switch.turn_off: valve_6
- platform: gpio
id: valve_7
name: 'Sprenger 7'
icon: 'mdi:sprinkler'
pin: GPIO4
on_turn_on:
- delay: 60min
- switch.turn_off: valve_7
- platform: gpio
id: valve_8
name: 'Sprenger 8'
icon: 'mdi:sprinkler'
pin: GPIO5
on_turn_on:
- delay: 60min
- switch.turn_off: valve_8
# Valves grouped in zones
- platform: template
id: zone_1
name: 'Zone 1'
lambda: |-
return id(valve_1).state || id(valve_8).state;
turn_on_action:
- switch.turn_on: valve_1
- switch.turn_on: valve_8
turn_off_action:
- switch.turn_off: valve_1
- switch.turn_off: valve_8
- platform: template
id: zone_2
name: 'Zone 2'
lambda: |-
return id(valve_2).state;
turn_on_action:
- switch.turn_on: valve_2
turn_off_action:
- switch.turn_off: valve_2
- platform: template
id: zone_3
name: 'Zone 3'
lambda: |-
return id(valve_3).state;
turn_on_action:
- switch.turn_on: valve_3
turn_off_action:
- switch.turn_off: valve_3
- platform: template
id: zone_4
name: 'Zone 4'
lambda: |-
return id(valve_4).state || id(valve_5).state;
turn_on_action:
- switch.turn_on: valve_4
- switch.turn_on: valve_5
turn_off_action:
- switch.turn_off: valve_4
- switch.turn_off: valve_5
- platform: template
id: zone_5
name: 'Zone 5'
lambda: |-
return id(valve_6).state || id(valve_7).state;
turn_on_action:
- switch.turn_on: valve_6
- switch.turn_on: valve_7
turn_off_action:
- switch.turn_off: valve_6
- switch.turn_off: valve_7
|
Below the regular “framework” at the top there’s the sprinkler
controllers. First one for all individual valves, then another for the zones. The valve switches all directly link to their corresponding GPIO switch while the zone switches link to template switches I used to group valves together.
At first this looked really nice but the closer I looked the more it became clear that this was not very well suited for my needs.
The zone switches for example were a necessary “hack” to circumvent the sprinkler
controller itself not being able to operate multiple switches in parallel.
Another drawback was the fact that even with those virtual zone switches you couldn’t stop one valve in a zone without stopping all valves in that zone. Since we often need this feature for various reasons (e.g. having laundry in reach of a particular sprinkler) this wasn’t feasible. There were many minor details about this approach that didn’t quite fit which caused me to give it a complete overhaul.
All manual
Determined to get this up and running with ESPHome I invested many more hours into crafting the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
| esphome:
name: irrigation
friendly_name: Irrigation
esp32:
board: esp32dev
framework:
type: esp-idf
substitutions:
max_runtime: '60'
valve_open_delay: 5s
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: 'my-key'
ota:
platform: esphome
password: 'my-password'
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
binary_sensor:
- platform: status
name: 'Status'
sensor:
- platform: wifi_signal
name: "WiFi Signal Sensor"
update_interval: 60s
script:
# Execution helpers to allow for delays to be cancelled
- id: valve_1_on_off
then:
- switch.turn_on: valve_1
- delay: !lambda "return id(valve_1_duration).state * 1000 * 60;"
- switch.turn_off: valve_1
- id: valve_2_on_off
then:
- switch.turn_on: valve_2
- delay: !lambda "return id(valve_2_duration).state * 1000 * 60;"
- switch.turn_off: valve_2
- id: valve_3_on_off
then:
- switch.turn_on: valve_3
- delay: !lambda "return id(valve_3_duration).state * 1000 * 60;"
- switch.turn_off: valve_3
- id: valve_4_on_off
then:
- switch.turn_on: valve_4
- delay: !lambda "return id(valve_4_duration).state * 1000 * 60;"
- switch.turn_off: valve_4
- id: valve_5_on_off
then:
- switch.turn_on: valve_5
- delay: !lambda "return id(valve_5_duration).state * 1000 * 60;"
- switch.turn_off: valve_5
- id: valve_6_on_off
then:
- switch.turn_on: valve_6
- delay: !lambda "return id(valve_6_duration).state * 1000 * 60;"
- switch.turn_off: valve_6
- id: valve_7_on_off
then:
- switch.turn_on: valve_7
- delay: !lambda "return id(valve_7_duration).state * 1000 * 60;"
- switch.turn_off: valve_7
- id: valve_8_on_off
then:
- switch.turn_on: valve_8
- delay: !lambda "return id(valve_8_duration).state * 1000 * 60;"
- switch.turn_off: valve_8
switch:
# Valves
- platform: gpio
id: valve_1
name: "Sprenger 1"
icon: "mdi:sprinkler"
pin: GPIO32
on_turn_on:
then:
- script.execute: valve_1_on_off
on_turn_off:
then:
- script.stop: valve_1_on_off
- platform: gpio
id: valve_2
name: "Sprenger 2"
icon: "mdi:sprinkler"
pin: GPIO33
on_turn_on:
then:
- script.execute: valve_2_on_off
on_turn_off:
then:
- script.stop: valve_2_on_off
- platform: gpio
id: valve_3
name: "Sprenger 3"
icon: "mdi:sprinkler"
pin: GPIO25
on_turn_on:
then:
- script.execute: valve_3_on_off
on_turn_off:
then:
- script.stop: valve_3_on_off
- platform: gpio
id: valve_4
name: "Sprenger 4"
icon: "mdi:sprinkler"
pin: GPIO26
on_turn_on:
then:
- script.execute: valve_4_on_off
on_turn_off:
then:
- script.stop: valve_4_on_off
- platform: gpio
id: valve_5
name: "Sprenger 5"
icon: "mdi:sprinkler"
pin: GPIO27
on_turn_on:
then:
- script.execute: valve_5_on_off
on_turn_off:
then:
- script.stop: valve_5_on_off
- platform: gpio
id: valve_6
name: "Sprenger 6"
icon: "mdi:sprinkler"
pin: GPIO14
on_turn_on:
then:
- script.execute: valve_6_on_off
on_turn_off:
then:
- script.stop: valve_6_on_off
- platform: gpio
id: valve_7
name: "Sprenger 7"
icon: "mdi:sprinkler"
pin: GPIO12
on_turn_on:
then:
- script.execute: valve_7_on_off
on_turn_off:
then:
- script.stop: valve_7_on_off
- platform: gpio
id: valve_8
name: "Sprenger 8"
icon: "mdi:sprinkler"
pin: GPIO13
on_turn_on:
then:
- script.execute: valve_8_on_off
on_turn_off:
then:
- script.stop: valve_8_on_off
# Valves Enabled
- platform: template
id: valve_1_enabled
name: "Sprenger 1 aktiv"
icon: "mdi:check-circle-outline"
optimistic: True
restore_mode: RESTORE_DEFAULT_ON
- platform: template
id: valve_2_enabled
name: "Sprenger 2 aktiv"
icon: "mdi:check-circle-outline"
optimistic: True
restore_mode: RESTORE_DEFAULT_ON
- platform: template
id: valve_3_enabled
name: "Sprenger 3 aktiv"
icon: "mdi:check-circle-outline"
optimistic: True
restore_mode: RESTORE_DEFAULT_ON
- platform: template
id: valve_4_enabled
name: "Sprenger 4 aktiv"
icon: "mdi:check-circle-outline"
optimistic: True
restore_mode: RESTORE_DEFAULT_ON
- platform: template
id: valve_5_enabled
name: "Sprenger 5 aktiv"
icon: "mdi:check-circle-outline"
optimistic: True
restore_mode: RESTORE_DEFAULT_ON
- platform: template
id: valve_6_enabled
name: "Sprenger 6 aktiv"
icon: "mdi:check-circle-outline"
optimistic: True
restore_mode: RESTORE_DEFAULT_ON
- platform: template
id: valve_7_enabled
name: "Sprenger 7 aktiv"
icon: "mdi:check-circle-outline"
optimistic: True
restore_mode: RESTORE_DEFAULT_ON
- platform: template
id: valve_8_enabled
name: "Sprenger 8 aktiv"
icon: "mdi:check-circle-outline"
optimistic: True
restore_mode: RESTORE_DEFAULT_ON
# Zones
- platform: template
id: zone_1
name: "Zone 1"
icon: "mdi:sprinkler-variant"
lambda: |-
return id(valve_1).state || id(valve_8).state;
turn_on_action:
then:
- if:
condition:
- switch.is_on: valve_1_enabled
then:
- number.set:
id: valve_1_duration
value: !lambda "return id(zone_1_duration).state;"
- switch.turn_on: valve_1
- if:
condition:
- switch.is_on: valve_8_enabled
then:
- number.set:
id: valve_8_duration
value: !lambda "return id(zone_1_duration).state;"
- switch.turn_on: valve_8
- delay: !lambda "return id(zone_1_duration).state * 1000 * 60;"
- switch.turn_off: zone_1
turn_off_action:
then:
- switch.turn_off: valve_1
- switch.turn_off: valve_8
- platform: template
id: zone_2
name: "Zone 2"
icon: "mdi:sprinkler-variant"
lambda: |-
return id(valve_2).state;
turn_on_action:
then:
- if:
condition:
- switch.is_on: valve_2_enabled
then:
- number.set:
id: valve_2_duration
value: !lambda "return id(zone_2_duration).state;"
- switch.turn_on: valve_2
- delay: !lambda 'return id(zone_2_duration).state * 1000 * 60;'
- switch.turn_off: zone_2
turn_off_action:
then:
- switch.turn_off: valve_2
- platform: template
id: zone_3
name: "Zone 3"
icon: "mdi:sprinkler-variant"
lambda: |-
return id(valve_3).state;
turn_on_action:
then:
- if:
condition:
- switch.is_on: valve_3_enabled
then:
- number.set:
id: valve_3_duration
value: !lambda "return id(zone_3_duration).state;"
- switch.turn_on: valve_3
- delay: !lambda 'return id(zone_3_duration).state * 1000 * 60;'
- switch.turn_off: zone_3
turn_off_action:
then:
- switch.turn_off: valve_3
- platform: template
id: zone_4
name: "Zone 4"
icon: "mdi:sprinkler-variant"
lambda: |-
return id(valve_4).state || id(valve_5).state;
turn_on_action:
then:
- if:
condition:
- switch.is_on: valve_4_enabled
then:
- number.set:
id: valve_4_duration
value: !lambda "return id(zone_4_duration).state;"
- switch.turn_on: valve_4
- if:
condition:
- switch.is_on: valve_5_enabled
then:
- number.set:
id: valve_5_duration
value: !lambda "return id(zone_4_duration).state;"
- switch.turn_on: valve_5
- delay: !lambda 'return id(zone_4_duration).state * 1000 * 60;'
- switch.turn_off: zone_4
turn_off_action:
then:
- switch.turn_off: valve_4
- switch.turn_off: valve_5
- platform: template
id: zone_5
name: "Zone 5"
icon: "mdi:sprinkler-variant"
lambda: |-
return id(valve_6).state || id(valve_7).state;
turn_on_action:
then:
- if:
condition:
- switch.is_on: valve_6_enabled
then:
- number.set:
id: valve_6_duration
value: !lambda "return id(zone_5_duration).state;"
- switch.turn_on: valve_6
- if:
condition:
- switch.is_on: valve_7_enabled
then:
- number.set:
id: valve_7_duration
value: !lambda "return id(zone_5_duration).state;"
- switch.turn_on: valve_7
- delay: !lambda 'return id(zone_5_duration).state * 1000 * 60;'
- switch.turn_off: zone_5
turn_off_action:
then:
- switch.turn_off: valve_6
- switch.turn_off: valve_7
# Zones Enabled
- platform: template
id: zone_1_enabled
name: "Zone 1 aktiv"
icon: "mdi:check-circle-outline"
optimistic: True
restore_mode: RESTORE_DEFAULT_ON
- platform: template
id: zone_2_enabled
name: "Zone 2 aktiv"
icon: "mdi:check-circle-outline"
optimistic: True
restore_mode: RESTORE_DEFAULT_ON
- platform: template
id: zone_3_enabled
name: "Zone 3 aktiv"
icon: "mdi:check-circle-outline"
optimistic: True
restore_mode: RESTORE_DEFAULT_ON
- platform: template
id: zone_4_enabled
name: "Zone 4 aktiv"
icon: "mdi:check-circle-outline"
optimistic: True
restore_mode: RESTORE_DEFAULT_ON
- platform: template
id: zone_5_enabled
name: "Zone 5 aktiv"
icon: "mdi:check-circle-outline"
optimistic: True
restore_mode: RESTORE_DEFAULT_ON
# Programm
- platform: template
id: program
name: "Programm"
icon: "mdi:robot"
#lambda: |-
# return id(zone_1).state || id(zone_2).state || id(zone_3).state || id(zone_4).state || id(zone_5).state;
optimistic: True
turn_on_action:
then:
- delay: 1s # Initial delay required for potential immediate shutdown
- if:
condition:
- switch.is_on: program_enabled
then:
- if:
condition:
- switch.is_on: zone_1_enabled
then:
- switch.turn_on: zone_1
- delay: 1s
#- delay: !lambda "return id(zone_1_duration).state * 1000 * 60;"
- wait_until:
condition:
- switch.is_off: zone_1
- delay: $valve_open_delay
- if:
condition:
- switch.is_on: zone_2_enabled
then:
- switch.turn_on: zone_2
- delay: 1s
#- delay: !lambda "return id(zone_2_duration).state * 1000 * 60;"
- wait_until:
condition:
- switch.is_off: zone_2
- delay: $valve_open_delay
- if:
condition:
- switch.is_on: zone_3_enabled
then:
- switch.turn_on: zone_3
- delay: 1s
#- delay: !lambda "return id(zone_3_duration).state * 1000 * 60;"
- wait_until:
condition:
- switch.is_off: zone_3
- delay: $valve_open_delay
- if:
condition:
- switch.is_on: zone_4_enabled
then:
- switch.turn_on: zone_4
- delay: 1s
#- delay: !lambda "return id(zone_4_duration).state * 1000;"
- wait_until:
condition:
- switch.is_off: zone_4
- delay: $valve_open_delay
- if:
condition:
- switch.is_on: zone_5_enabled
then:
- switch.turn_on: zone_5
- delay: 1s
#- delay: !lambda "return id(zone_5_duration).state * 1000;"
- wait_until:
condition:
- switch.is_off: zone_5
- switch.turn_off: program
turn_off_action:
then:
- switch.turn_off: zone_1
- switch.turn_off: zone_2
- switch.turn_off: zone_3
- switch.turn_off: zone_4
- switch.turn_off: zone_5
# Programm Enabled
- platform: template
id: program_enabled
name: "Programm aktiv"
icon: "mdi:check-circle-outline"
optimistic: True
number:
# Sprinkler Durations
- platform: template
name: "Sprenger 1 Dauer"
id: valve_1_duration
icon: "mdi:timer"
optimistic: true
min_value: 1
max_value: $max_runtime
initial_value: 20
step: 1
unit_of_measurement: min
- platform: template
name: "Sprenger 2 Dauer"
id: valve_2_duration
icon: "mdi:timer"
optimistic: true
min_value: 1
max_value: $max_runtime
initial_value: 20
step: 1
unit_of_measurement: min
- platform: template
name: "Sprenger 3 Dauer"
id: valve_3_duration
icon: "mdi:timer"
optimistic: true
min_value: 1
max_value: $max_runtime
initial_value: 20
step: 1
unit_of_measurement: min
- platform: template
name: "Sprenger 4 Dauer"
id: valve_4_duration
icon: "mdi:timer"
optimistic: true
min_value: 1
max_value: $max_runtime
initial_value: 20
step: 1
unit_of_measurement: min
- platform: template
name: "Sprenger 5 Dauer"
id: valve_5_duration
icon: "mdi:timer"
optimistic: true
min_value: 1
max_value: $max_runtime
initial_value: 20
step: 1
unit_of_measurement: min
- platform: template
name: "Sprenger 6 Dauer"
id: valve_6_duration
icon: "mdi:timer"
optimistic: true
min_value: 1
max_value: $max_runtime
initial_value: 20
step: 1
unit_of_measurement: min
- platform: template
name: "Sprenger 7 Dauer"
id: valve_7_duration
icon: "mdi:timer"
optimistic: true
min_value: 1
max_value: $max_runtime
initial_value: 20
step: 1
unit_of_measurement: min
- platform: template
name: "Sprenger 8 Dauer"
id: valve_8_duration
icon: "mdi:timer"
optimistic: true
min_value: 1
max_value: $max_runtime
initial_value: 20
step: 1
unit_of_measurement: min
# Zone Durations
- platform: template
name: "Zone 1 Dauer"
id: zone_1_duration
icon: "mdi:timer"
optimistic: true
min_value: 1
max_value: $max_runtime
initial_value: 15
step: 1
unit_of_measurement: min
- platform: template
name: "Zone 2 Dauer"
id: zone_2_duration
icon: "mdi:timer"
optimistic: true
min_value: 1
max_value: $max_runtime
initial_value: 20
step: 1
unit_of_measurement: min
- platform: template
name: "Zone 3 Dauer"
id: zone_3_duration
icon: "mdi:timer"
optimistic: true
min_value: 1
max_value: $max_runtime
initial_value: 15
step: 1
unit_of_measurement: min
- platform: template
name: "Zone 4 Dauer"
id: zone_4_duration
icon: "mdi:timer"
optimistic: true
min_value: 1
max_value: $max_runtime
initial_value: 15
step: 1
unit_of_measurement: min
- platform: template
name: "Zone 5 Dauer"
id: zone_5_duration
icon: "mdi:timer"
optimistic: true
min_value: 1
max_value: $max_runtime
initial_value: 30
step: 1
unit_of_measurement: min
|
The top section remained basically unchanged, however below it nearly everything is different.
Since I no longer had the “magic” behind the sprinkler
controller, i.e. all the implicit controls like duration and automation, I had to spell it all out explicitly. While being extremely tedious this provided me with exactly the flexibility I was missing from my previous approaches.
Here are the most notable parts:
The new configuration still has GPIO switches to control the individual valves. But instead of controlling them directly I’m now using a script
because without it when you start a valve with a 10 minute runtime, then stop it after 8 minutes and immediately restart it it will only run for another 2 minutes before delay kicks in and stops it. Scripts work differently in that stopping the script will also cancel the delay.
The zones now have conditions to check if a valve is enabled or not, thus allowing a zone to run even if not all valves are involved. Another highlight is that the zone will automatically set the valves’ runtimes to match the zone. There was an intermittent version which set the zone’s runtime according to the maximum runtime of its child valves:
turn_on_action:
then:
- number.set:
id: zone_1_duration
value: !lambda 'return max(id(valve_1_duration).state, id(valve_8_duration).state)'
This would allow a valve to be shut down before the end of the zone’s runtime:
- Valve 1: 10min runtime
- Valve 8: 15min runtime
- Zone 1: max(10min, 15min) = 15min runtime
The automation now also respects each zone’s enabled status. Together with the same mechanism in each of the zones this setup is super flexible and allows for the automation to only open the valves you want it to.
Edit 2024-07-23
Unfortunately I realized that the current status of the “active” switches is not always properly reflected in Home Assistant. Pretty regularly the controller would lose its connection to HA which caused the values for all “active” switches to be set to their default. This in turn resulted in the automatic program to mostly not run at all - not very desirable. I tried different methods like connecting the switch to a GPIO and setting a restore_mode
like this:
- platform: gpio
id: program_enabled
name: "Programm aktiv"
icon: "mdi:check-circle-outline"
pin: GPIO16
restore_mode: RESTORE_DEFAULT_OFF
However, that only resulted in the switch being constantly on with no way of turning it off for some reason.
Solution: Since I wanted to keep it simple, I created an input boolean in Home Assistant which now keeps track of whether the program is active or not.
The Hardware
With the software part figured out it was time to apply it to the real world:
Let’s walk through it left to right:
A the very left bottom there’s the 2-wired 230V cable coming into the box. It feeds to both a 24VAC and a 5VDC transformer. The former powers the valves, the latter is for powering the board.
Next up is the board itself. It has a 5VDC as well as a 7-30VDC input, an ESP32 with pinouts for easy flashing and 8 relays.
Last but not least in the right side is the 9 wired cable (8 valves plus ground) connected to the valve box.
To save on cables and connectors I simply bridged all the relais’ common ports.
Conclusion
It took a lot of planning for the hardware and even more trial and error to get the software exactly how I wanted it to behave. However, all this work has paid off massively because not only is the wiring a little simpler than what I had before but having all of the controls on a single board is tremendously beneficial and SO much cleaner when it comes to controlling it with Home Assistant.