This is my code currently below. I’ve added ability to change the LCD display to follow the status of the running zone. I don’t have any internal scheduling yet, and I’m not sure I need too, maybe a default program if HA is off-air and one button assigned to start a manual watering.
Not sure yet if I should use HA automations for scheduling or create of borrow something for Node-Red…
# 8 Zone Version and compiled via ESPhome and Python3.10.4
substitutions:
devicename: irrigation-controller
esphome:
name: $devicename
esp32:
board: nodemcu-32s
framework:
type: arduino
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Esp32-Irrigation"
# password: !secret ap_password
# Enable logging
logger:
# Enable Home Assistant API
api:
reboot_timeout: 0s
services:
- service: pause
then:
- sprinkler.pause: esp32_sprinkler_ctrlr
- service: resume
then:
- sprinkler.resume: esp32_sprinkler_ctrlr
ota:
# I2C bus config
i2c:
sda: 4
scl: 5
scan: true
id: bus_a
# I2C I/O expander config
pcf8574:
- id: 'pcf8574_output_hub'
address: 0x24
pcf8575: false
- id: 'pcf8574_input_hub'
address: 0x22
# 16x2 LCD config
display:
- platform: lcd_pcf8574
dimensions: 16x2
address: 0x27
update_interval: 2s
lambda: |-
switch (id(esp32_sprinkler_ctrlr).active_valve().value_or(-1)) {
case 0: //valve0, zone1, internal valve numbers not zone_valve_swX numbers
it.printf(0, 0, "Front Lawn: %s", id(zone_valve_sw1).state ? "ON" : "OFF");
if (id(zone_valve_sw1).state) {
it.printf(0, 1, "Mins: %2d of", id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60);
it.printf(12, 1, "%2d", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(0) / 60);
} else {
it.printf(0, 1, "Mins Set %2d ", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(0) / 60);
}
break;
case 1: //valve1, zone2,
it.printf(0, 0, "Back Lawn: %s", id(zone_valve_sw2).state ? "ON" : "OFF");
if (id(zone_valve_sw2).state) {
it.printf(0, 1, "Mins: %2d of", id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60);
it.printf(12, 1, "%2d", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(1) / 60);
} else {
it.printf(0, 1, "Mins Set %2d ", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(1) / 60);
}
break;
case 2: //valve2, zone3,
it.printf(0, 0, "Front middle: %s", id(zone_valve_sw3).state ? "ON" : "OFF");
if (id(zone_valve_sw3).state) {
it.printf(0, 1, "Mins: %2d of", id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60);
it.printf(12, 1, "%2d", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(2) / 60);
} else {
it.printf(0, 1, "Mins Set %2d ", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(2) / 60);
}
break;
case 3: //valve3, zone4
it.printf(0, 0, "West Fence: %s", id(zone_valve_sw4).state ? "ON" : "OFF");
if (id(zone_valve_sw4).state) {
it.printf(0, 1, "Mins: %2d of", id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60);
it.printf(12, 1, "%2d", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(3) / 60);
} else {
it.printf(0, 1, "Mins Set %2d ", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(3) / 60);
}
break;
case 4: //valve4, zone5
it.printf(0, 0, "Front South: %s", id(zone_valve_sw5).state ? "ON" : "OFF");
if (id(zone_valve_sw5).state) {
it.printf(0, 1, "Mins: %2d of", id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60);
it.printf(12, 1, "%2d", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(4) / 60);
} else {
it.printf(0, 1, "Mins Set %2d ", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(4) / 60);
}
break;
case 5: //valve5, zone6
it.printf(0, 0, "Back Wall W: %s", id(zone_valve_sw6).state ? "ON" : "OFF");
if (id(zone_valve_sw6).state) {
it.printf(0, 1, "Mins: %2d of", id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60);
it.printf(12, 1, "%2d", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(5) / 60);
} else {
it.printf(0, 1, "Mins Set %2d ", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(5) / 60);
}
break;
case 6: //valve6, zone7
it.printf(0, 0, "Back Wall E: %s", id(zone_valve_sw7).state ? "ON" : "OFF");
if (id(zone_valve_sw7).state) {
it.printf(0, 1, "Mins: %2d of", id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60);
it.printf(12, 1, "%2d", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(6) / 60);
} else {
it.printf(0, 1, "Mins Set %2d ", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(6) / 60);
}
break;
case 7: //valve7, zone8
it.printf(0, 0, "Verandas: %s", id(zone_valve_sw8).state ? "ON" : "OFF");
if (id(zone_valve_sw8).state) {
it.printf(0, 1, "Mins: %2d of", id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60);
it.printf(12, 1, "%2d", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(7) / 60);
} else {
it.printf(0, 1, "Mins Set %2d ", id(esp32_sprinkler_ctrlr).valve_run_duration_adjusted(7) / 60);
}
break;
case -1:
it.printf(0, 0, "Watering OFF");
it.strftime(0, 1,"Time is %H:%M:%S", id(homeassistant_time).now());
break;
default:
break;
}
# Main sprinkler code
sprinkler:
- id: esp32_sprinkler_ctrlr
main_switch: "Master Run/Stop"
auto_advance_switch: "Zones Auto Advance"
reverse_switch: "Zones Reverse"
valve_overlap: 1s
valves:
- valve_switch: "Zone 1"
enable_switch: "Zone 1 Enable"
run_duration: 900s
valve_switch_id: zone_valve_sw1
- valve_switch: "Zone 2"
enable_switch: "Zone 2 Enable"
run_duration: 900s
valve_switch_id: zone_valve_sw2
- valve_switch: "Zone 3"
enable_switch: "Zone 3 Enable"
run_duration: 900s
valve_switch_id: zone_valve_sw3
- valve_switch: "Zone 4"
enable_switch: "Zone 4 Enable"
run_duration: 900s
valve_switch_id: zone_valve_sw4
- valve_switch: "Zone 5"
enable_switch: "Zone 5 Enable"
run_duration: 900s
valve_switch_id: zone_valve_sw5
- valve_switch: "Zone 6"
enable_switch: "Zone 6 Enable"
run_duration: 900s
valve_switch_id: zone_valve_sw6
- valve_switch: "Zone 7"
enable_switch: "Zone 7 Enable"
run_duration: 900s
valve_switch_id: zone_valve_sw7
- valve_switch: "Zone 8"
enable_switch: "Zone 8 Enable"
run_duration: 900s
valve_switch_id: zone_valve_sw8
# Valve control outputs config via I/O expander
switch:
- platform: gpio
id: zone_valve_sw1
pin:
pcf8574: pcf8574_output_hub
# Use pin number 0
number: 0
# One of INPUT or OUTPUT
mode:
output: true
inverted: true
internal: true
- platform: gpio
id: zone_valve_sw2
pin:
pcf8574: pcf8574_output_hub
# Use pin number 1
number: 1
# One of INPUT or OUTPUT
mode:
output: true
inverted: true
internal: true
- platform: gpio
id: zone_valve_sw3
pin:
pcf8574: pcf8574_output_hub
# Use pin number 2
number: 2
# One of INPUT or OUTPUT
mode:
output: true
inverted: true
internal: true
- platform: gpio
id: zone_valve_sw4
pin:
pcf8574: pcf8574_output_hub
# Use pin number 3
number: 3
# One of INPUT or OUTPUT
mode:
output: true
inverted: true
internal: true
- platform: gpio
id: zone_valve_sw5
pin:
pcf8574: pcf8574_output_hub
# Use pin number 4
number: 4
# One of INPUT or OUTPUT
mode:
output: true
inverted: true
internal: true
- platform: gpio
id: zone_valve_sw6
pin:
pcf8574: pcf8574_output_hub
# Use pin number 5
number: 5
# One of INPUT or OUTPUT
mode:
output: true
inverted: true
internal: true
- platform: gpio
id: zone_valve_sw7
pin:
pcf8574: pcf8574_output_hub
# Use pin number 6
number: 6
# One of INPUT or OUTPUT
mode:
output: true
inverted: true
internal: true
- platform: gpio
id: zone_valve_sw8
pin:
pcf8574: pcf8574_output_hub
# Use pin number 7
number: 7
# One of INPUT or OUTPUT
mode:
output: true
inverted: true
internal: true
- platform: template #this switch doesn't work properly. Can pause via HA frontend, but will not resume... Via Services does work...
id: pause_switch
name: "Pause Irrigation Switch"
turn_on_action:
then:
- sprinkler.pause: esp32_sprinkler_ctrlr
turn_off_action:
then:
- sprinkler.resume: esp32_sprinkler_ctrlr
# Configuration to set multiplier via number
number:
- platform: template
id: esp32_ctrlr_multiplier
name: "Run Duration Multiplier"
min_value: 1.0
max_value: 3.0
step: 0.1
mode: box
lambda: "return id(esp32_sprinkler_ctrlr).multiplier();"
set_action:
- sprinkler.set_multiplier:
id: esp32_sprinkler_ctrlr
multiplier: !lambda 'return x;'
# Configuration to set valve run duration via number
- platform: template
id: sprinkler_valve_1_duration
name: "Zone 1 Duration"
icon: mdi:timer
unit_of_measurement: Min
min_value: 1
max_value: 120
step: 1.0
# update_interval: 10s
mode: box
lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(0) / 60;"
set_action:
- sprinkler.set_valve_run_duration:
id: esp32_sprinkler_ctrlr
valve_number: 0
run_duration: !lambda "return x * 60;"
- platform: template
id: sprinkler_valve_2_duration
name: "Zone 2 Duration"
icon: mdi:timer
unit_of_measurement: Min
min_value: 1
max_value: 120
step: 1.0
mode: box
lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(1) / 60;"
set_action:
- sprinkler.set_valve_run_duration:
id: esp32_sprinkler_ctrlr
valve_number: 1
run_duration: !lambda "return x * 60;"
- platform: template
id: sprinkler_valve_3_duration
name: "Zone 3 Duration"
icon: mdi:timer
unit_of_measurement: Min
min_value: 1
max_value: 120
step: 1.0
mode: box
lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(2) / 60;"
set_action:
- sprinkler.set_valve_run_duration:
id: esp32_sprinkler_ctrlr
valve_number: 2
run_duration: !lambda "return x * 60;"
- platform: template
id: sprinkler_valve_4_duration
name: "Zone 4 Duration"
icon: mdi:timer
unit_of_measurement: Min
min_value: 1
max_value: 120
step: 1.0
mode: box
lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(3) / 60;"
set_action:
- sprinkler.set_valve_run_duration:
id: esp32_sprinkler_ctrlr
valve_number: 3
run_duration: !lambda "return x * 60;"
- platform: template
id: sprinkler_valve_5_duration
name: "Zone 5 Duration"
icon: mdi:timer
unit_of_measurement: Min
min_value: 1
max_value: 120
step: 1.0
mode: box
lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(4) / 60;"
set_action:
- sprinkler.set_valve_run_duration:
id: esp32_sprinkler_ctrlr
valve_number: 4
run_duration: !lambda "return x * 60;"
- platform: template
id: sprinkler_valve_6_duration
name: "Zone 6 Duration"
icon: mdi:timer
unit_of_measurement: Min
min_value: 1
max_value: 120
step: 1.0
mode: box
lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(5) / 60;"
set_action:
- sprinkler.set_valve_run_duration:
id: esp32_sprinkler_ctrlr
valve_number: 5
run_duration: !lambda "return x * 60;"
- platform: template
id: sprinkler_valve_7_duration
name: "Zone 7 Duration"
icon: mdi:timer
unit_of_measurement: Min
min_value: 1
max_value: 120
step: 1.0
mode: box
lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(6) / 60;"
set_action:
- sprinkler.set_valve_run_duration:
id: esp32_sprinkler_ctrlr
valve_number: 6
run_duration: !lambda "return x * 60;"
- platform: template
id: sprinkler_valve_8_duration
name: "Zone 8 Duration"
icon: mdi:timer
unit_of_measurement: Min
min_value: 1
max_value: 120
step: 1.0
mode: box
lambda: "return id(esp32_sprinkler_ctrlr).valve_run_duration(7) / 60;"
set_action:
- sprinkler.set_valve_run_duration:
id: esp32_sprinkler_ctrlr
valve_number: 7
run_duration: !lambda "return x * 60;"
#Rain Sensor from my HA
sensor:
- platform: homeassistant
id: rain_today
entity_id: sensor.daily_rain_rate
- platform: wifi_signal
name: "ESP Irrigation WiFi Signal Sensor"
update_interval: 300s
- platform: template
name: "Zone Time Remaining Sensor"
icon: mdi:progress-clock
unit_of_measurement: 'Min'
lambda: "return id(esp32_sprinkler_ctrlr).time_remaining().value_or(0) / 60;"
update_interval: 30s
# - platform: template
# name: "Zone Active Sensor"
## unit_of_measurement: ''
# lambda: "return id(esp32_sprinkler_ctrlr).active_valve().value_or(NAN);" #Valves are numbered from 0-7 internally which is an issue when displaying !
# update_interval: 30s
# Time source config
time:
- platform: homeassistant
id: homeassistant_time
timezone: Australia/Adelaide
#timezone: CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00