I put together this DIY sprinkler controller, hope you find it useful!
Final Product
Objectives
- duplicate common features of commercial sprinkler controllers such as:
- Ability to schedule day-of-week, time-of-day, and duration.
- Ability to test zones
- Rain delay feature
- Works offline
- Home Assistant integration allows:
- System status
- Manual zone controls for testing
- Ability to set schedule
- Super simple hardware
- Inexpensive.
Hardware
I wanted to keep this as SIMPLE as possible, short of buying a commercial product. Because it is simple, it is also CHEAP.
Hardware list:
- ESP8266 8 Channel Relay board ($12.99 from Aliexpress or $18.33 from Amazon)
- 24v DC 1500mA wall-wart power supply ($14.99)
- 3d printed housing (I printed with clear PETG, $18.99)
- M3 screws, suitable for plastic ($11.29 or $12.99)
- (optional) a bit of wire and solder
- (optional) Orbit 57623 3/4" Sprinkler Valve ($17.43)
I used the Amazon products linked above in my prototype, total was about $35. Compare that to a similar commercial product, the Orbit b-hyve 8 Zone at $94 or the outdoor version at $168. Please note that some of these links are amazon affiliate links, which means I get a small commission at no cost to you.
The relay board:
This ESP8266 8 Channel Relay board is just the ticket. Sure, it costs slightly more than an ESP8266 and a separate 8 channel relay PCB, but you donāt have to assemble multiple parts into a project box, so you can build this project in a snap!
Hereās a picture of the board, nice and tidy!
The power supply:
Iām using a 24V DC 1500 mA āwall wartā power supply. It seems to be fully sealed and it has weathered many rain storms without issue.
Iām using the Orbit 57623 Sprinkler Valve which is intended to be operated with 24V AC. The spec sheet indicates it pulls up to 0.30 A inrush current and 0.19 A holding current. My benchtop power supply has it pulling about 0.50 A DC. You can run 2 of these valves simultaneously with some margin.
Iām happy I can power the relay board and the sprinkler valve with one 24V DC power supply. Thatās fewer parts! No separate 5v power supply here!
The housing:
Hereās a link to my CAD file in onshape. You can export an STL for printing directly from that link, just right click on the āpartā in the bottom left and select Export. No user account is needed. The external mounting holes are for #8 wood screws, or zip ties. Itās splashproof, not IP68. Donāt put this where it will get blasted by a lawn sprinkler.
An optional modification to the PCB:
I want this PCB to supply 24V to my sprinkler valve, just by simply hooking it up directly to the screw terminals, just like an off-the-shelf commercial product. To that end, Iāve added some jumper wires inside to supply 24V to the output of the relay.
This is totally optional, but makes for a cleaner install. If you donāt do this, you will just have to find some other way to provide the 24V to your valve, and put the relay in series.
Here you can see the unmodified board. I have labeled the relay outputs NO (normally open) COM (common) and NC (normally closed) in the bottom left.
Wire GND to all NC terminals.
Wire 24V to all NO terminals.
Consider filling the NO screw terminal with hot glue, as it will always be energized and it wonāt be needed for this application.
With this jumper wire configuration:
GND will always be connected to NC terminals.
When the relay is energized, 24V will be connected to COM terminals.
When the relay is not energized, GND will be connected to COM terminals.
Customize your Installation
In ESPHome, create a new device and name it something like āEast Balcony Sprinkler Controllerā. Skip Installation. Select ESP8266. Configuration will be created, skip installation (again).
Once created, open your notepad and copy these unique items:
- API encryption key
- OTA password
- AP password
Now you can delete everything in your new configuration and replace it with the YAML, pasted below. Edit the āsubstitutionsā section to suit your installation. Make sure to paste in the unique keys you just copied. This configuration assumes you have already set up WIFI SSID and WIFI password in your secrets.yaml file.
substitutions: # set up unique values for this hardware instance
name_of_device: east-balcony-sprinkler-cntrl
id_prefix: east_balcony
name_prefix: East Balcony
api_key: "paste_api_key_here"
ota_password: "paste_OTA_password_here"
ap_password: "paste_ap_password_here"
esphome:
name: $name_of_device
friendly_name: ${name_prefix} Sprinkler Controller
esp8266:
board: esp12e
restore_from_flash: True
# Enable logging
logger:
level: DEBUG
# Enable Home Assistant API
# Enable Home Assistant API
api:
encryption:
key: $api_key
ota:
password: $ota_password
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
## uncomment and edit the following line to flash a device with a different name.
## comment the following line out after initially flashing the device, or it won't be found
# use_address: test123.local
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "ESPHome-Sprinkler-Controller"
password: $ap_password
captive_portal:
sprinkler:
- id: ${id_prefix}_sprinklers
main_switch: "Sprinkler Cycle Active"
auto_advance_switch: "Auto Advance"
valve_overlap: 5s
valves:
- valve_switch: "Zone 1 Active"
enable_switch: "Zone 1 Auto"
run_duration_number:
id: zone_1_run_duration
name: "Zone 1 Run Duration"
icon: "mdi:timer-outline"
initial_value: 1
unit_of_measurement: min
valve_switch_id: ${id_prefix}_valve_sw1
- valve_switch: "Zone 2 Active"
enable_switch: "Zone 2 Auto"
run_duration_number:
id: zone_2_run_duration
name: "Zone 2 Run Duration"
icon: "mdi:timer-outline"
initial_value: 1
unit_of_measurement: min
valve_switch_id: ${id_prefix}_valve_sw2
- valve_switch: "Zone 3 Active"
enable_switch: "Zone 3 Auto"
run_duration_number:
id: zone_3_run_duration
name: "Zone 3 Run Duration"
icon: "mdi:timer-outline"
initial_value: 1
unit_of_measurement: min
valve_switch_id: ${id_prefix}_valve_sw3
- valve_switch: "Zone 4 Active"
enable_switch: "Zone 4 Auto"
run_duration_number:
id: zone_4_run_duration
name: "Zone 4 Run Duration"
icon: "mdi:timer-outline"
initial_value: 1
unit_of_measurement: min
valve_switch_id: ${id_prefix}_valve_sw4
- valve_switch: "Zone 5 Active"
enable_switch: "Zone 5 Auto"
run_duration_number:
id: zone_5_run_duration
name: "Zone 5 Run Duration"
icon: "mdi:timer-outline"
initial_value: 1
unit_of_measurement: min
valve_switch_id: ${id_prefix}_valve_sw5
- valve_switch: "Zone 6 Active"
enable_switch: "Zone 6 Auto"
run_duration_number:
id: zone_6_run_duration
name: "Zone 6 Run Duration"
icon: "mdi:timer-outline"
initial_value: 1
unit_of_measurement: min
valve_switch_id: ${id_prefix}_valve_sw6
- valve_switch: "Zone 7 Active"
enable_switch: "Zone 7 Auto"
run_duration_number:
id: zone_7_run_duration
name: "Zone 7 Run Duration"
icon: "mdi:timer-outline"
initial_value: 1
unit_of_measurement: min
valve_switch_id: ${id_prefix}_valve_sw7
- valve_switch: "Zone 8 Active"
enable_switch: "Zone 8 Auto"
run_duration_number:
id: zone_8_run_duration
name: "Zone 8 Run Duration"
icon: "mdi:timer-outline"
initial_value: 1
unit_of_measurement: min
valve_switch_id: ${id_prefix}_valve_sw8
switch:
# relays
- platform: gpio
id: ${id_prefix}_valve_sw1
pin: GPIO16
restore_mode: ALWAYS_OFF
- platform: gpio
id: ${id_prefix}_valve_sw2
pin: GPIO14
restore_mode: ALWAYS_OFF
- platform: gpio
id: ${id_prefix}_valve_sw3
pin: GPIO12
restore_mode: ALWAYS_OFF
- platform: gpio
id: ${id_prefix}_valve_sw4
pin: GPIO13
restore_mode: ALWAYS_OFF
- platform: gpio
id: ${id_prefix}_valve_sw5
pin: GPIO15
restore_mode: ALWAYS_OFF
- platform: gpio
id: ${id_prefix}_valve_sw6
pin: GPIO0
restore_mode: ALWAYS_OFF
- platform: gpio
id: ${id_prefix}_valve_sw7
pin: GPIO4
restore_mode: ALWAYS_OFF
- platform: gpio
id: ${id_prefix}_valve_sw8
pin: GPIO5
restore_mode: ALWAYS_OFF
# day of week toggle switches
- platform: template
id: ${id_prefix}_sunday
name: Sunday
icon: "mdi:calendar-range"
entity_category: config
restore_mode: RESTORE_DEFAULT_ON
optimistic: true
- platform: template
id: ${id_prefix}_monday
name: Monday
icon: "mdi:calendar-range"
entity_category: config
restore_mode: RESTORE_DEFAULT_ON
optimistic: true
- platform: template
id: ${id_prefix}_tuesday
name: Tuesday
icon: "mdi:calendar-range"
entity_category: config
restore_mode: RESTORE_DEFAULT_ON
optimistic: true
- platform: template
id: ${id_prefix}_wednesday
name: Wednesday
icon: "mdi:calendar-range"
entity_category: config
restore_mode: RESTORE_DEFAULT_ON
optimistic: true
- platform: template
id: ${id_prefix}_thursday
name: Thursday
icon: "mdi:calendar-range"
entity_category: config
restore_mode: RESTORE_DEFAULT_ON
optimistic: true
- platform: template
id: ${id_prefix}_friday
name: Friday
icon: "mdi:calendar-range"
entity_category: config
restore_mode: RESTORE_DEFAULT_ON
optimistic: true
- platform: template
id: ${id_prefix}_saturday
name: Saturday
icon: "mdi:calendar-range"
entity_category: config
restore_mode: RESTORE_DEFAULT_ON
optimistic: true
# scheduled time enable switches
- platform: template
id: ${id_prefix}_schedule1_enabled
name: Enable Schedule 1
icon: "mdi:clock-outline"
entity_category: config
restore_mode: RESTORE_DEFAULT_ON
optimistic: true
- platform: template
id: ${id_prefix}_schedule2_enabled
name: Enable Schedule 2
icon: "mdi:clock-outline"
entity_category: config
restore_mode: RESTORE_DEFAULT_ON
optimistic: true
- platform: template
id: ${id_prefix}_schedule3_enabled
name: Enable Schedule 3
icon: "mdi:clock-outline"
entity_category: config
restore_mode: RESTORE_DEFAULT_ON
optimistic: true
# rain delay switches (can be cancelled, auto-resetting)
- platform: template
id: ${id_prefix}_raindelay_24h_enabled
name: Enable 24h Rain Delay
icon: "mdi:weather-cloudy-clock"
restore_mode: RESTORE_DEFAULT_OFF
optimistic: true
turn_on_action:
then:
- delay:
hours: 24
- switch.turn_off: ${id_prefix}_raindelay_24h_enabled
- platform: template
id: ${id_prefix}_raindelay_48h_enabled
name: Enable 48h Rain Delay
icon: "mdi:weather-cloudy-clock"
restore_mode: RESTORE_DEFAULT_OFF
optimistic: true
turn_on_action:
then:
- delay:
hours: 48
- switch.turn_off: ${id_prefix}_raindelay_48h_enabled
datetime:
- platform: template
name: Schedule 1 Start Time
entity_category: config
id: ${id_prefix}_s1t
type: time
optimistic: true
restore_value: true
- platform: template
name: Schedule 2 Start Time
entity_category: config
id: ${id_prefix}_s2t
type: time
optimistic: true
restore_value: true
- platform: template
name: Schedule 3 Start Time
entity_category: config
id: ${id_prefix}_s3t
type: time
optimistic: true
restore_value: true
sensor:
- platform: wifi_signal # Reports the WiFi signal strength/RSSI in dB
name: "WiFi Signal dB"
id: wifi_signal_db
update_interval: 60s
entity_category: "diagnostic"
- platform: copy # Reports the WiFi signal strength in %
source_id: wifi_signal_db
name: "WiFi Signal Percent"
filters:
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
unit_of_measurement: "%"
entity_category: "diagnostic"
- platform: uptime
name: Uptime
entity_category: "diagnostic"
binary_sensor:
- platform: status
name: "Connection Status"
# set up time and check every minute (or second) for scheduled actions
time:
- platform: homeassistant
id: ha_time
on_time:
# Every 1 minute
- seconds: 0
minutes: /1
then:
- script.execute: ${id_prefix}_script1
on_time_sync:
then:
- logger.log: "Synchronized system clock"
script:
# check what day of the week it is currently, and if we have the schedule enabled today
- id: ${id_prefix}_script1
then:
lambda: |-
int dow = id(ha_time).now().day_of_week;
if ((dow == 1 && id(${id_prefix}_sunday).state == true)
|| (dow == 2 && id(${id_prefix}_monday).state == true)
|| (dow == 3 && id(${id_prefix}_tuesday).state == true)
|| (dow == 4 && id(${id_prefix}_wednesday).state == true)
|| (dow == 5 && id(${id_prefix}_thursday).state == true)
|| (dow == 6 && id(${id_prefix}_friday).state == true)
|| (dow == 7 && id(${id_prefix}_saturday).state == true)) {
id(${id_prefix}_script2).execute();
}
# if current time is equal to any of the scheduled start times, start the sprinkler cycle
- id: ${id_prefix}_script2
then:
lambda: |-
int hour = id(ha_time).now().hour;
int minute = id(ha_time).now().minute;
if ((hour == id(${id_prefix}_s1t).hour && minute == id(${id_prefix}_s1t).minute && id(${id_prefix}_schedule1_enabled).state == true && id(${id_prefix}_raindelay_24h_enabled).state == false && id(${id_prefix}_raindelay_48h_enabled).state == false)
|| (hour == id(${id_prefix}_s2t).hour && minute == id(${id_prefix}_s2t).minute && id(${id_prefix}_schedule2_enabled).state == true && id(${id_prefix}_raindelay_24h_enabled).state == false && id(${id_prefix}_raindelay_48h_enabled).state == false)
|| (hour == id(${id_prefix}_s3t).hour && minute == id(${id_prefix}_s3t).minute && id(${id_prefix}_schedule3_enabled).state == true && id(${id_prefix}_raindelay_24h_enabled).state == false && id(${id_prefix}_raindelay_48h_enabled).state == false)) {
id(${id_prefix}_script3).execute();
}
# start the sprinkler cycle
- id: ${id_prefix}_script3
then:
- sprinkler.start_full_cycle: ${id_prefix}_sprinklers
- lambda: ESP_LOGI("main", "Sprinkler cycle has begun!");
Initial Flash with ESPHome
Connect your FTDI to GND, RX to TX and TX to RX. Do not connect 5V to FTDI.
Set your FTDI for 3v programming, not 5v. (thanks to comments below)
Connect an external power supply to the board, you can use your 24VDC power supply connected to the 7-28V input.
Plug the supplied pin jumper between GND and IO0.
Hereās what it looks like:
Steps:
- Load the YAML into your ESPHome web interface.
- Start with the FTDI USB unplugged, and the board external power disconnected.
- Hold the RST button on the board.
- Plug in the external power source (24V DC plug).
- Plug your FTDI USB into your PC.
- In ESPHome, click install via wired connection.
- Release the RST button.
- The web interface should begin the upload. If it fails, try again.
If the installation hangs up and fails, it may be because you are not supplying enough power. You must power the board from an external source, not the from the FTDI.
Once the flash is successful, unplug the FTDI, disconnect external power, and remove the jumper. Plug it back in to 24V DC power and the device should connect to WIFI. Shortly thereafter you should see a notification in Home Assistant that your new device has been discovered. Go ahead and add it!
You can test each relay right away by toggling the zones.
Final Assembly
The housing has a slot for the barrel plug adaptor that comes with the 24V DC power supply listed above. Just add some jumper wires for power, slot the barrel plug into the receptacle, and assemble the lid. The barrel plug adaptor is marked with (+) and (-), donāt mix it up. I recommend M3 stainless flathead screws (amazon).
Making the final connections
If you made the optional jumper wire modification above, then just hook up your irrigation valve to the NC and COM terminals. Polarity does not matter. Then plug in your barrel plug.
Home Assistant Dashboard Setup
Hereās a simple dashboard with all of the controls:
Hereās the YAML configuration for a basic Home Assistant dashboard:
Status Card
type: entities
entities:
- entity: switch.east_balcony_sprinkler_cntrl_sprinkler_cycle_active
name: Sprinkler Cycle Active
- entity: binary_sensor.east_balcony_sprinkler_cntrl_connection_status
name: Connection Status
- entity: sensor.east_balcony_sprinkler_cntrl_wifi_signal_percent
name: WiFi Signal Percent
- entity: sensor.east_balcony_sprinkler_cntrl_uptime
name: Uptime
- entity: update.east_balcony_sprinkler_controller_firmware
name: Firmware
title: East Balcony Sprinkler Status
show_header_toggle: false
Control Card
type: entities
entities:
- entity: switch.east_balcony_sprinkler_cntrl_enable_24h_rain_delay
name: Enable 24h Rain Delay
- entity: switch.east_balcony_sprinkler_cntrl_enable_48h_rain_delay
name: Enable 48h Rain Delay
- entity: switch.east_balcony_sprinkler_cntrl_sprinkler_cycle_active
name: Sprinkler Cycle Active
- entity: switch.east_balcony_sprinkler_cntrl_zone_1_active
name: Zone 1 Active
- entity: switch.east_balcony_sprinkler_cntrl_zone_2_active
name: Zone 2 Active
- entity: switch.east_balcony_sprinkler_cntrl_zone_3_active
name: Zone 3 Active
- entity: switch.east_balcony_sprinkler_cntrl_zone_4_active
name: Zone 4 Active
- entity: switch.east_balcony_sprinkler_cntrl_zone_5_active
name: Zone 5 Active
- entity: switch.east_balcony_sprinkler_cntrl_zone_6_active
name: Zone 6 Active
- entity: switch.east_balcony_sprinkler_cntrl_zone_7_active
name: Zone 7 Active
- entity: switch.east_balcony_sprinkler_cntrl_zone_8_active
name: Zone 8 Active
title: East Balcony Sprinkler Control
show_header_toggle: false
Setup Card
type: entities
entities:
- type: section
label: What zones?
- entity: switch.east_balcony_sprinkler_cntrl_zone_1_auto
name: Zone 1 Auto
- entity: switch.east_balcony_sprinkler_cntrl_zone_2_auto
name: Zone 2 Auto
- entity: switch.east_balcony_sprinkler_cntrl_zone_3_auto
name: Zone 3 Auto
- entity: switch.east_balcony_sprinkler_cntrl_zone_4_auto
name: Zone 4 Auto
- entity: switch.east_balcony_sprinkler_cntrl_zone_5_auto
name: Zone 5 Auto
- entity: switch.east_balcony_sprinkler_cntrl_zone_6_auto
name: Zone 6 Auto
- entity: switch.east_balcony_sprinkler_cntrl_zone_7_auto
name: Zone 7 Auto
- entity: switch.east_balcony_sprinkler_cntrl_zone_8_auto
name: Zone 8 Auto
- type: section
label: What days?
- entity: switch.east_balcony_sprinkler_cntrl_sunday
name: Sunday
- entity: switch.east_balcony_sprinkler_cntrl_monday
name: Monday
- entity: switch.east_balcony_sprinkler_cntrl_tuesday
name: Tuesday
- entity: switch.east_balcony_sprinkler_cntrl_wednesday
name: Wednesday
- entity: switch.east_balcony_sprinkler_cntrl_thursday
name: Thursday
- entity: switch.east_balcony_sprinkler_cntrl_friday
name: Friday
- entity: switch.east_balcony_sprinkler_cntrl_saturday
name: Saturday
- type: section
label: What times?
- entity: switch.east_balcony_sprinkler_cntrl_enable_schedule_1
name: Enable Schedule 1
- entity: time.east_balcony_sprinkler_cntrl_schedule_1_start_time
name: Schedule 1 Start Time
- entity: switch.east_balcony_sprinkler_cntrl_enable_schedule_2
name: Enable Schedule 2
- entity: time.east_balcony_sprinkler_cntrl_schedule_2_start_time
name: Schedule 2 Start Time
- entity: switch.east_balcony_sprinkler_cntrl_enable_schedule_3
name: Enable Schedule 3
- entity: time.east_balcony_sprinkler_cntrl_schedule_3_start_time
name: Schedule 3 Start Time
- type: section
label: What durations?
- entity: number.east_balcony_sprinkler_cntrl_zone_1_run_duration
name: Zone 1 Run Duration
- entity: number.east_balcony_sprinkler_cntrl_zone_2_run_duration
name: Zone 2 Run Duration
- entity: number.east_balcony_sprinkler_cntrl_zone_3_run_duration
name: Zone 3 Run Duration
- entity: number.east_balcony_sprinkler_cntrl_zone_4_run_duration
name: Zone 4 Run Duration
- entity: number.east_balcony_sprinkler_cntrl_zone_5_run_duration
name: Zone 5 Run Duration
- entity: number.east_balcony_sprinkler_cntrl_zone_6_run_duration
name: Zone 6 Run Duration
- entity: number.east_balcony_sprinkler_cntrl_zone_7_run_duration
name: Zone 7 Run Duration
- entity: number.east_balcony_sprinkler_cntrl_zone_8_run_duration
name: Zone 8 Run Duration
title: East Balcony Sprinkler Setup
show_header_toggle: false
Tip: If you install the Expander Card, you can neatly pack away all those setup fields.
Future work:
None of this is necessary, just would be nice:
- Incorporate schedule functionality directly into the ESPHome Sprinkler Component. Currently, the Sprinkler Component only supports scheduling and storing the run Duration on the device. It must hear from Home Assistant to kick off an irrigation cycle, therefore it requires a reliable wifi connection. It relies on the user to schedule irrigation via something like Schedule or Scheduler Card. I have hacked together āon deviceā scheduling here, but it would be awesome if someone could build this functionality into ESPHome Sprinkler Component natively.
- Try out the 2, 4 and 16 channel boards.
- The 4 channel version of the board also accepts 120 or 220 V AC with its built in power supply, great for mains AC projects if you donāt need 24V.
- The 16 channel version looks like it has some GPIO expanders, because the ESP8266 does not have enough pins. It also has spare GPIO pins. (see comment below, this has been done)
I hope you enjoyed this show and tell.