Thanks @BrianHanifin, @raberrio, @VikingBlod @joaquin68 and everyone in this thread for putting this together!
I took the code from @joaquin68 here, modified to use 5 zones on an ESP32 (I tried with 5 zones on ESP8266 but it kept crashing - memory issue maybe? - the ESP32 works like a treat though on the same code). The irrigation.h include doesn’t need to be modified beyond language changes.
I’m using 5 zones and made a more minimalist interface (inspired by @VikingBlod and @bremby) for use on my phone that I thought I’d share.
I also made some changes to the code to get some visual feedback for disabling the irrigation schedule and moved the check for disabling to the scheduler rather than the switch itself so that they can still be turned on manually if they are ‘disabled’.
Here’s my card working. It includes two custom (although fairly common and amazing) mods by @thomasloven to make it compact and pretty - template-entity-row and fold-entity-row. The only issue is that adding buttons with tap-action defined seems to break the visual editor at the moment (I opened a front-end issue), so I needed to set up the card, then add in the tap_action in the raw configuration editor. Despite this, the buttons function perfectly, however.
Below I will document my changes for anyone interested. I also noticed that during testing you need to be careful to make sure that your list of days and times need to be in order for things to work as expected. You need to start at Sunday and times must be earliest to latest for the parsing and schedule to work properly.
This is my card config to achieve the above:
type: entities
title: Irrigation Controls
show_header_toggle: false
entities:
- type: custom:template-entity-row
icon: mdi:sprinkler-variant
name: Irrigation System Status
entity: binary_sensor.irrigation_controller_status
state: >-
{% if is_state('binary_sensor.irrigation_controller_status', 'off')
%}OFFLINE {% else %} {% if is_state('switch.irrigation_zone_5', 'on') or
is_state('switch.irrigation_zone_4', 'on') or
is_state('switch.irrigation_zone_3', 'on') or
is_state('switch.irrigation_zone_2', 'on') or
is_state('switch.irrigation_zone_1', 'on') or
is_state('script.water_everything', 'on') or
is_state('script.water_front_yard', 'on') or
is_state('script.water_back_yard', 'on') %}ACTIVE{% else %}IDLE {% endif
%} {% endif %}
active: >-
{% if is_state('switch.irrigation_zone_5', 'on') or
is_state('switch.irrigation_zone_4', 'on') or
is_state('switch.irrigation_zone_3', 'on') or
is_state('switch.irrigation_zone_2', 'on') or
is_state('switch.irrigation_zone_1', 'on') or
is_state('script.water_everything', 'on') or
is_state('script.water_front_yard', 'on') or
is_state('script.water_back_yard', 'on') %}true{% endif %}
secondary: >-
{% if is_state('switch.irrigation_zone_1', 'on') %}Zone 1 has
{{states('sensor.zone_1_time_remaining')}} min remaining{% endif %} {% if
is_state('switch.irrigation_zone_2', 'on') %}Zone 2 has
{{states('sensor.zone_2_time_remaining')}} min remaining{% endif %} {% if
is_state('switch.irrigation_zone_3', 'on') %}Zone 3 has
{{states('sensor.zone_3_time_remaining')}} min remaining{% endif %} {% if
is_state('switch.irrigation_zone_4', 'on') %}Zone 4 has
{{states('sensor.zone_4_time_remaining')}} min remaining{% endif %} {% if
is_state('switch.irrigation_zone_5', 'on') %}Zone 5 has
{{states('sensor.zone_5_time_remaining')}} min remaining{% endif %}
- type: divider
- entity: switch.irrigation_zone_1
secondary_info: last-changed
name: Zone 1
- type: custom:fold-entity-row
head:
entity: sensor.zone_1_next_watering
name: Zone 1 Next Run
entities:
- input_text.irrigation_zone1_days
- input_text.irrigation_zone1_times
- input_number.irrigation_zone1_duration
- sensor.zone_1_time_remaining
- type: buttons
entities:
- entity: input_boolean.irrigation_disable_schedule_zone1
name: Disable Zone 1 Schedule
- entity: switch.irrigation_zone_2
secondary_info: last-changed
name: Zone 2
- type: custom:fold-entity-row
head:
entity: sensor.zone_2_next_watering
name: Zone 2 Next Run
entities:
- input_text.irrigation_zone2_days
- input_text.irrigation_zone2_times
- input_number.irrigation_zone2_duration
- sensor.zone_2_time_remaining
- type: buttons
entities:
- entity: input_boolean.irrigation_disable_schedule_zone2
name: Disable Zone 2 Schedule
- entity: switch.irrigation_zone_3
secondary_info: last-changed
name: Zone 3
- type: custom:fold-entity-row
head:
entity: sensor.zone_3_next_watering
name: Zone 3 Next Run
entities:
- input_text.irrigation_zone3_days
- input_text.irrigation_zone3_times
- input_number.irrigation_zone3_duration
- sensor.zone_3_time_remaining
- type: buttons
entities:
- entity: input_boolean.irrigation_disable_schedule_zone3
name: Disable Zone 3 Schedule
- entity: switch.irrigation_zone_4
secondary_info: last-changed
name: Zone 4
- type: custom:fold-entity-row
head:
entity: sensor.zone_4_next_watering
name: Zone 4 Next Run
entities:
- input_text.irrigation_zone4_days
- input_text.irrigation_zone4_times
- input_number.irrigation_zone4_duration
- sensor.zone_4_time_remaining
- type: buttons
entities:
- entity: input_boolean.irrigation_disable_schedule_zone4
name: Disable Zone 4 Schedule
- entity: switch.irrigation_zone_5
secondary_info: last-changed
name: Zone 5
- type: custom:fold-entity-row
head:
entity: sensor.zone_5_next_watering
name: Zone 5 Next Run
entities:
- input_text.irrigation_zone5_days
- input_text.irrigation_zone5_times
- input_number.irrigation_zone5_duration
- sensor.zone_5_time_remaining
- type: buttons
entities:
- entity: input_boolean.irrigation_disable_schedule_zone5
name: Disable Zone 5 Schedule
- type: divider
- type: buttons
entities:
- entity: script.water_everything
name: All Zones
tap_action:
action: toggle
- entity: script.water_front_yard
name: Front Yard
tap_action:
action: toggle
- entity: script.water_back_yard
name: Back Yard
tap_action:
action: toggle
- type: divider
- type: buttons
entities:
- entity: scene.once_daily_irrigation
name: Once Daily Irrigation
tap_action:
action: call-service
service: scene.turn_on
service_data:
entity_id: scene.once_daily_irrigation
- entity: scene.twice_daily_irrigation
name: Twice Daily Irrigation
tap_action:
action: call-service
service: scene.turn_on
service_data:
entity_id: scene.twice_daily_irrigation
- type: divider
- type: buttons
entities:
- entity: switch.irrigation_disable_schedule_all_zones
name: Disable Schedules
icon: mdi:timer-off-outline
- entity: switch.stop_active_irrigation_zones
name: Stop Irrigating
icon: mdi:close-octagon-outline
At the top, you have a status panel that just shows whether irrigation is active, idle or offline based on templates. (looking at this now, I might add another status if all zone schedules are disabled). I included my scripts in there to prevent the status from flashing between zones and also to remind me if I turn off a valve and the script is still running.
For each zone, I set a toggle then a folded row to hold the configuration and disable button for that specific zone.
The disable button is a helper input_boolean input_boolean.irrigation_disable_schedule_zone1
that I use for the ESP to read and disable the schedule (not the zone switch). This also allows me to change the text to “Schedule Disabled” based upon that. To achieve this, I imported these to the ESP and changed the scheduler to check this before initiating a zone.
Import the input_boolean disable switch and also use it also to update the next runtime text:
binary_sensor:
# ============================================================================= #
# Bring input_boolean buttons from HA to disable schedules
- platform: homeassistant
id: ui_disable_zone1
entity_id: input_boolean.irrigation_disable_schedule_zone1
on_state:
then:
# Update the next scheduled run time.
- text_sensor.template.publish:
id: irrigation_zone1_next
state: !lambda |-
if (id(ui_disable_zone1).state) {
return {"Schedule Disabled"};
} else {
return update_next_runtime(id(irrigation_zone1_times).state, id(irrigation_zone1_days).state);
}
# and repeat for each zone
My ESP’s time:
block looks like this now to take advantage of the disabled schedule input:
time:
- platform: homeassistant
id: homeassistant_time
# Time based automations.
on_time:
- seconds: 0
minutes: /1
then:
- if:
condition:
- binary_sensor.is_off: ui_disable_zone1
then:
- lambda: |-
if (scheduled_runtime(id(irrigation_zone1_next).state.c_str())) {
id(irrigation_zone1).turn_on(); }
# and repeat for each zone...
Also make sure the disabled text doesn’t get updated when you change the days, times, or turn off a manual irrigation run:
DAYS:
# ============================================================================= #
# Store day lists.
- platform: template
name: Zone 1 Days
id: irrigation_zone1_days
on_value:
then:
# Update the next scheduled run time.
- text_sensor.template.publish:
id: irrigation_zone1_next
state: !lambda |-
if (id(ui_disable_zone1).state) {
return {"Schedule Disabled"};
}
else {
return update_next_runtime(id(irrigation_zone1_times).state, id(irrigation_zone1_days).state);
}
# and repeat for each zone...
TIMES:
# ============================================================================= #
# Store time lists.
- platform: template
name: Zone 1 Schedule
id: irrigation_zone1_times
on_value:
then:
# Update the next scheduled run time.
- text_sensor.template.publish:
id: irrigation_zone1_next
state: !lambda |-
if (id(ui_disable_zone1).state) {
return {"Schedule Disabled"};
} else {
return update_next_runtime(id(irrigation_zone1_times).state, id(irrigation_zone1_days).state);
}
Update at end of a manual run with the changes in on_turn_off for your gpio switch:
- platform: gpio
id: relay1
name: "Irrigation Zone 1 Relay"
.
.
.
on_turn_off:
then:
- sensor.template.publish:
id: irrigation_zone1_remaining
state: "0"
# Update the next scheduled run time.
- text_sensor.template.publish:
id: irrigation_zone1_next
state: !lambda |-
if (id(ui_disable_zone1).state) {
return {"Schedule Disabled"};
} else {
return update_next_runtime(id(irrigation_zone1_times).state, id(irrigation_zone1_days).state);
}
It’s all the same lambda, just in the three spots that update the next run sensor.
Next, with the “All Zones” / “Front Yard” / “Back Yard” buttons, I set my zone cycle scripts to use the set values for the zones using delay templates, like this:
alias: Water Everything
mode: single
icon: mdi:sprinkler
sequence:
- service: switch.turn_on
target:
entity_id: switch.irrigation_zone_1
- delay:
minutes: '{{ states(''input_number.irrigation_zone1_duration'') | int }}'
- service: switch.turn_off
target:
entity_id: switch.irrigation_zone_1
- service: switch.turn_on
target:
entity_id: switch.irrigation_zone_2
- delay:
minutes: '{{ states(''input_number.irrigation_zone2_duration'') | int }}'
- service: switch.turn_off
target:
entity_id: switch.irrigation_zone_2
# and repeat for each zone...
For a stop irrigation switch, I added a toggle like @bremby did here but with a shorter turn off delay of 2s instead of 10.
Then, for the disable all schedules toggle, I created a switch that toggles all the input_booleans
switch:
- platform: template
switches:
irrigation_disable_schedule_all_zones:
friendly_name: Disable All Irrigation Schedules
icon_template: mdi:timer-off-outline
turn_on:
service: input_boolean.turn_on
target:
entity_id:
- input_boolean.irrigation_disable_schedule_zone1
- input_boolean.irrigation_disable_schedule_zone2
- input_boolean.irrigation_disable_schedule_zone3
- input_boolean.irrigation_disable_schedule_zone4
- input_boolean.irrigation_disable_schedule_zone5
turn_off:
service: input_boolean.turn_off
target:
entity_id:
- input_boolean.irrigation_disable_schedule_zone1
- input_boolean.irrigation_disable_schedule_zone2
- input_boolean.irrigation_disable_schedule_zone3
- input_boolean.irrigation_disable_schedule_zone4
- input_boolean.irrigation_disable_schedule_zone5
And lastly, I created scenes with my usual times, days and durations to easily toggle between once and twice daily irrigation, but could be whatever you wanted - spring, summer, fall…
- id: '1234565278981'
name: Twice Daily Irrigation
icon: mdi:numeric-2-box-multiple-outline
entities:
input_text.irrigation_zone1_days:
editable: false
min: 0
max: 100
pattern:
mode: text
friendly_name: Zone 1 Days
icon: mdi:calendar-week
state: Sun,Mon,Tue,Wed,Thu,Fri,Sat
input_text.irrigation_zone1_times:
editable: false
min: 0
max: 100
pattern:
mode: text
friendly_name: Zone 1 Times
icon: mdi:chart-timeline
state: 05:00,20:00
input_text.irrigation_zone2_days:
editable: false
min: 0
max: 100
pattern:
mode: text
friendly_name: Zone 2 Days
icon: mdi:calendar-week
state: Sun,Mon,Tue,Wed,Thu,Fri,Sat
input_text.irrigation_zone2_times:
editable: false
min: 0
max: 100
pattern:
mode: text
friendly_name: Zone 2 Times
icon: mdi:chart-timeline
state: 05:10,20:10
# and repeat for each zone...
Again, thanks to everyone in this thread! Next step is just some logic to disable all zones based upon rain today or tomorrow. Will probably start with ideas from these threads here or here
EDIT - also thinking that instead of just disabling the irrigation, I can use rainfall thresholds to set a scene with different durations as well… Just a thought I need to write down before I forget
Cheers!