$ cd ..
// esphome-irrigation-controller.md

Watering Without the Cloud: My ESPHome Irrigation Controller

esphomehome-automationdiyirrigationbuild-log

I’ve been waiting to publish this article since April of this year. I didn’t want to publish it until we’d used the controller for a full season. It’s been rock solid and I’m excited to be able to share it.

My lawn sprinklers used to be run by an Orbit B-hyve sprinkler controller. It was a perfectly fine little cloud-connected controller, but, it was a cloud-connected sprinkler controller. My watering schedule lived on someone else’s servers. Opening a valve in my own front yard was a round trip through a data center in who-knows-where. If Orbit ever decided the B-hyve was end-of-life, I’d own a beige brick and I am fundamentally opposed to vendor cloud services and all the privacy implications thereof.

Just…just no. It doesn’t need to run in a cloud, it doesn’t need to have any connectivity to a vendor’s anything as far as I am concerned and they sure as hell don’t need access to my usage telemetry. No, no, no.

So I replaced it with an ESP32, a relay board, and ESPHome.

The house rule

Everything smart in my house follows an iron rule: the thing it controls has to work just as it always has whether or not my home automation platform (Home Assistant) is up and running.

Switches must switch, doorbells must ring and appliances need to do their thing no matter what. Automation is an enrichment, not a gate.

This irrigation controller follows the same rule. The schedule logic lives in Home Assistant, and the rest of the logic & sequencing runs on the device itself. HA’s only job is to say “start a run now” or show you the state of the device. Once it does, or once I poke the controller directly from its own little web page, the ESP32 does the rest. If my server dies or my internet goes out mid-watering, no problem.

I was going to add a small screen and a rotary encoder to the device so I could have full offline scheduling capability but I couldn’t motivate myself to mitigate such a small risk.

The hardware

The parts list is short:

  • An ESP32 dev board. Any of the classic esp32dev boards work.
  • An 8-channel 5V optoisolated relay module. Eight relays, active-low trigger inputs.
  • A 120VAC->24VAC transformer. Any format is fine.
  • 24VAC→5V DC buck converter. For powering ESP32 and relay (can use a separate 5V DC power supply too).
  • A handful of Wago lever connectors. For splitting output and ahem structural support.
  • 24v irrigation system Irrigation system installs are out of scope for this guide, just the controller.

The wiring

Everything runs off one 24VAC transformer.

The 24VAC valve side A hot leg runs from the Wago up to the relay board, where a single wire daisy-chains across every relay’s COM terminal on the back of the board. Each of the first four relays’ NO (normally open) contacts goes to one valve’s wire, and the common leg ties to all the valve commons. Close a relay, and that valve’s solenoid sees 24VAC and opens. Connecting the COM terminals by soldering a wire between them allows you to continue using the “connect the single wire from the irrigation solenoid to the controller” pattern.

The 5V DC esp32 and relay side One hot/common pair from the transformer feeds the 24V→5V AC-DC converter. Its 5V output lands on a second pair of Wagos, and one set of wires runs from that little 5V “rail” to power the ESP32 while another set runs up to the relay to power that board’s logic pins. I’m being overly cautious with this design, I choose to avoid using my ESP32s as a power bus whenever possible so I run power to my devices independent of the ESP32 itself wherever I can.

From there, four GPIO jumpers run to relay inputs IN1–IN4, the config reserves pins for all eight if they are needed.

Four zones are wired today, four relays sit reserved for future expansion (there’s always future expansion):

ZoneAreaGPIO
1NW — DrivewayGPIO32
2NE — Front YardGPIO33
3SE — Swamp/FirewoodGPIO25
4SW — BackyardGPIO26
5–8ReservedGPIO27 / 14 / 12 / 13

About that “enclosure”

This is the part of the article where I’m supposed to show you a photo of the finished install in its tidy project box, wires dressed and labeled, maybe a little DIN rail if I’m showing off.

There is no photo. There will never be a photo.

The truth is that this entire controller — relay board, ESP32, buck converter, all of it — is hanging from my basement ceiling by Wagos and screw terminals. The ESP32 dangles from its own jumper wires. The zone wires are labeled in Sharpie on scraps of green painter’s tape. It went up as a “let’s just verify this works before I build the enclosure” test, and then it worked, and then it watered the lawn all season, and there is nothing as permanent as a temporary install.

A real enclosure is on the list. The list is long. The sprinklers don’t care.

The software: ESPHome’s sprinkler component

ESPHome ships a native sprinkler component that already knows how to be an irrigation controller — zone sequencing, auto-advance, per-zone run durations, repeat cycles, pause and resume. My entire controller is one YAML file.

sprinkler:
  - id: irrigation_controller
    main_switch:
      name: "Master"
    auto_advance_switch:
      name: "Auto Advance"
    valve_open_delay: 2s
    valves:
      - valve_switch: "Zone 1 - NW - Driveway"
        enable_switch:
          name: "Enable Zone 1"
        run_duration: 600s
        valve_switch_id: zone_1_switch
      # ... zones 2-8

Flip the Master switch — from Home Assistant, from the device’s built-in web page, from anything that can reach it — and the controller runs every enabled zone in order for its configured duration, with a 2-second delay between valve changes to be kind to the plumbing.

A few details of note in the config:

The relays can’t boot open. The GPIO switches are inverted: true for the active-low board and restore_mode: ALWAYS_OFF. A power blip, a crash, a reflash — the device always comes back with every valve closed.

switch:
  - platform: gpio
    id: zone_1_switch
    pin:
      number: GPIO32
      inverted: true   # active-low relay board
    restore_mode: ALWAYS_OFF
    internal: true

It doesn’t mind when HA is unavailable. ESPHome devices can reboot if no API client connects for a while. api: reboot_timeout: 0s turns that off.

Zone durations are real entities. Each zone’s run time is exposed as a number in minutes, wrapping sprinkler.set_valve_run_duration. Runtimes can change via user input.

The device tells you what it’s doing. A template text sensor reports the active zone by name (“Zone 4 - SW - Backyard” or “Idle”), which makes dashboards and notifications easy on the HA side.

The full config is in my new esphome-configs repo and I’ll be sharing more work there as I get it ready to publish.

What Home Assistant adds on top

Home Assistant handles the when and the whether:

  • The schedule. Per-day toggles with a start time; an automation checks them each morning and flips the Master switch. We can adjust the schedule from our phones without touching YAML.
  • Rain skip. A 1 AM automation pulls the forecast and arms a skip flag when predicted rain crosses a threshold. There’s a separate manual skip too that clears itself after the run it skipped.
  • Quick runs. Scripts for “water the front,” “water the back,” “water everything.” The partial ones snapshot the zone-enable switches first and restore them when the run finishes, so a quick front-yard run doesn’t wreck your usual schedule.
  • Notifications. A ping when watering finishes or when a run was skipped, and why.

All of it — automations, scripts, helpers, template sensors, and the dashboard view — is in the homeassistant/ directory next to the device config.

Was it worth it?

For me, without question. I got an overpriced, buggy, vendor controlled internet connected device off my network and out of my home.

My new controller is always-on, secure, private, works online or off and cost about $28 in parts (plus a few buck in misc electrical supplies I always have on hand) while giving me something to do for an afternoon.

From my perspective, what’s not to love?


WhatLink
ESPHomeesphome.io
ESPHome sprinkler componentdocs
The full config (my repo)github.com/jasondostal/esphome-configs
ESP32 dev boardElegoo ESP-WROOM-32 (any board works!)
8-channel 5V optoisolated relay moduleElegoo 8-channel
24VAC irrigation transformer (wall wart)generic brand 24v ac-ac
24V→5V 5A AC-DC converter moduleWalfront AC-DC converter
Wago 221 lever connectorsWago