An ESP32 can replace a surprisingly capable commercial irrigation controller when ESPHome handles the valves locally and Home Assistant handles the scheduling and weather logic. The important part is not simply connecting four relays to four GPIOs. A reliable controller needs safe valve sequencing, pump or master-valve coordination, sensible reboot behaviour, rain shutdown, adjustable run times and a clear boundary between local control and Home Assistant automation.
ESPHome already includes a dedicated sprinkler controller that behaves much more like a Rain Bird or Hunter controller than a collection of unrelated switches. It supports multiple zones, pumps or upstream master valves, full cycles, individual-zone runs, adjustable duration multipliers, repeats, pause/resume, valve queues and timing options designed to avoid pressure problems and water hammer.
This guide builds a four-zone controller around a classic ESP32 and ESPHome, then adds Home Assistant scheduling, rain lockout and soil-moisture logic.
Recommended Architecture
A good smart irrigation system separates responsibilities:
| Function | Best place |
|---|---|
| Actually energise/de-energise valves | ESPHome |
| Zone sequencing | ESPHome sprinkler controller |
| Pump/master-valve timing | ESPHome |
| Immediate rain shutdown | ESPHome local input |
| Maximum valve run duration | ESPHome |
| Weekly schedule | Home Assistant |
| Weather forecast decision | Home Assistant |
| Soil-moisture threshold | Home Assistant or ESPHome |
| Seasonal watering adjustment | Home Assistant → ESPHome multiplier |
| History, dashboards and notifications | Home Assistant |
This keeps the safety-critical water control on the ESP32 while letting Home Assistant use richer data when deciding whether a scheduled cycle should start.
Hardware You Need
- ESP32 development board.
- Four-channel or larger relay/driver board.
- 24 V AC irrigation transformer for conventional sprinkler solenoid valves.
- One irrigation valve per zone.
- Optional pump relay/contactor or master valve.
- Optional rain sensor with dry-contact output.
- Optional capacitive soil-moisture sensors.
- Weatherproof enclosure, fuses and suitable terminals.
A classic ESP32-WROOM DevKit is ideal because it offers plenty of GPIOs and ESPHome support. C3/C6/S3 boards also work if you choose pins appropriate to the exact board.
Do Not Drive Irrigation Valves from ESP32 GPIO
Most residential irrigation valves use a 24 V AC solenoid. The ESP32 GPIO is a 3.3 V logic output and cannot drive the valve directly.
For a conventional 24 V AC system:
24 VAC transformer common
|
+---------------- common to every valve
24 VAC switched side
|
relay contact
|
valve wire
The ESP32 operates the low-voltage input side of the relay module. The relay contacts switch the irrigation transformer output.
For a 12/24 V DC solenoid, a suitable MOSFET driver can be more efficient than a relay, but it needs correct flyback suppression. Do not put an ordinary flyback diode directly across a 24 V AC solenoid; AC coils require an AC-compatible suppression method such as an appropriately specified MOV or RC network if suppression is needed.
Pump and Master Valve Safety
Some irrigation systems use a pump or an upstream master valve that must run whenever any zone is active.
This is exactly what ESPHome’s sprinkler controller pump_switch_id is for. In ESPHome terminology, the “pump” can be a real pump or simply another upstream valve.
A mains-powered pump should not normally be switched directly by a tiny PCB relay unless the relay, wiring and enclosure are genuinely rated for its motor load and inrush current. In a permanent system, let the ESP32 relay drive a correctly sized pump contactor or approved control input.
Example GPIO Assignment
For a normal ESP32-WROOM DevKit:
| Function | ESP32 GPIO |
|---|---|
| Zone 1 relay | GPIO16 |
| Zone 2 relay | GPIO17 |
| Zone 3 relay | GPIO18 |
| Zone 4 relay | GPIO19 |
| Pump/master relay | GPIO23 |
| Rain sensor | GPIO27 |
| Optional soil ADC | GPIO34 |
GPIO34 is input-only, which is fine for an analogue sensor. If your exact ESP32 board uses PSRAM or has board-specific pin reservations, verify its pinout before copying these assignments.
Base ESPHome Configuration
esphome:
name: garden-irrigation
friendly_name: Garden Irrigation
esp32:
board: esp32dev
framework:
type: esp-idf
logger:
api:
ota:
- platform: esphome
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: "Irrigation Fallback"
password: !secret ap_password
captive_portal:
For an outdoor installation, reliable Wi-Fi matters less than reliable local valve control. Do not design the system so a Wi-Fi dropout leaves a raw relay stuck on indefinitely.
Define the Physical Outputs
The relay outputs themselves should remain internal. Home Assistant should normally control the sprinkler controller entities rather than directly overriding the GPIO switches underneath them.
switch:
- platform: gpio
id: zone_1_relay
pin: GPIO16
internal: true
restore_mode: ALWAYS_OFF
- platform: gpio
id: zone_2_relay
pin: GPIO17
internal: true
restore_mode: ALWAYS_OFF
- platform: gpio
id: zone_3_relay
pin: GPIO18
internal: true
restore_mode: ALWAYS_OFF
- platform: gpio
id: zone_4_relay
pin: GPIO19
internal: true
restore_mode: ALWAYS_OFF
- platform: gpio
id: irrigation_pump
pin: GPIO23
internal: true
restore_mode: ALWAYS_OFF
ALWAYS_OFF is deliberate: after a reboot or power failure, the physical outputs start off instead of unexpectedly re-energising a valve.
If your relay board is active-low, use the appropriate inverted pin configuration rather than reversing logic throughout the rest of the project.
For relay wiring and safe output design, see our ESP32 Smart Relay for Home Assistant guide.
Four-Zone ESPHome Sprinkler Controller
sprinkler:
- id: garden_irrigation
main_switch: "Irrigation Cycle"
auto_advance_switch: "Irrigation Auto Advance"
multiplier_number:
name: "Irrigation Multiplier"
id: irrigation_multiplier
initial_value: 1.0
min_value: 0.0
max_value: 2.0
step: 0.1
restore_value: true
repeat_number:
name: "Irrigation Repeat"
initial_value: 0
min_value: 0
max_value: 3
step: 1
restore_value: true
pump_start_pump_delay: 2s
pump_stop_valve_delay: 2s
valves:
- valve_switch: "Front Lawn"
enable_switch: "Enable Front Lawn"
valve_switch_id: zone_1_relay
pump_switch_id: irrigation_pump
run_duration_number:
name: "Front Lawn Run Time"
initial_value: 12
min_value: 1
max_value: 60
step: 1
unit_of_measurement: min
restore_value: true
- valve_switch: "Back Lawn"
enable_switch: "Enable Back Lawn"
valve_switch_id: zone_2_relay
pump_switch_id: irrigation_pump
run_duration_number:
name: "Back Lawn Run Time"
initial_value: 12
min_value: 1
max_value: 60
step: 1
unit_of_measurement: min
restore_value: true
- valve_switch: "Hedges"
enable_switch: "Enable Hedges"
valve_switch_id: zone_3_relay
pump_switch_id: irrigation_pump
run_duration_number:
name: "Hedges Run Time"
initial_value: 8
min_value: 1
max_value: 60
step: 1
unit_of_measurement: min
restore_value: true
- valve_switch: "Vegetable Beds"
enable_switch: "Enable Vegetable Beds"
valve_switch_id: zone_4_relay
pump_switch_id: irrigation_pump
run_duration_number:
name: "Vegetable Beds Run Time"
initial_value: 10
min_value: 1
max_value: 60
step: 1
unit_of_measurement: min
restore_value: true
This creates a much more useful Home Assistant interface than four raw relay switches. You get individual zone controls, zone enable switches, run-time settings, a complete-cycle switch, repeat count and a global multiplier.
Why Use the Irrigation Multiplier?
The multiplier adjusts every configured zone proportionally without rewriting individual times.
| Base zone time | Multiplier | Actual run |
|---|---|---|
| 12 min | 0.5 | 6 min |
| 12 min | 0.8 | 9.6 min |
| 12 min | 1.0 | 12 min |
| 12 min | 1.5 | 18 min |
This is ideal for seasonal adjustment. Home Assistant can reduce the multiplier after cool weather or increase it during a hot dry period while the relative balance between zones stays unchanged.
ESPHome’s sprinkler-number values can persist across reboots when restore_value: true is used, so changing a zone from 12 to 15 minutes in Home Assistant does not have to disappear at the next restart.
Pump Timing Matters
A pump should not normally run against a completely closed irrigation system. ESPHome provides dedicated timing controls for this.
In the example:
pump_start_pump_delay: 2s
pump_stop_valve_delay: 2s
the distribution valve opens first, then the pump starts two seconds later. At shutdown, the pump stops first and the valve remains open for another two seconds.
That avoids running the pump into closed downstream valves. The correct sequence depends on your actual hydraulic system, so do not copy delays blindly if your valves require pressure to close properly.
Valve Overlap vs Valve Open Delay
When moving from one zone to the next, sudden closure can cause water hammer.
ESPHome offers two strategies:
| Option | Behaviour |
|---|---|
valve_overlap | Next zone opens before current zone closes |
valve_open_delay | Current zone closes, then controller waits before opening next zone |
They are alternatives; ESPHome does not allow both on the same controller.
For a normal pressurised municipal-water sprinkler system, a few seconds of overlap can soften pressure transients. For systems where simultaneous valves would reduce pressure too much, an open delay may be better.
Add a Local Rain Sensor
A traditional irrigation rain sensor is often just a normally-open or normally-closed dry contact. Connect it to an ESP32 input with a pull-up and use it as a local safety input.
binary_sensor:
- platform: gpio
id: rain_sensor
name: "Irrigation Rain Sensor"
device_class: moisture
pin:
number: GPIO27
mode:
input: true
pullup: true
inverted: true
filters:
- delayed_on_off: 1s
on_press:
- sprinkler.shutdown: garden_irrigation
The exact inverted setting depends on whether your physical sensor contact closes or opens when wet.
The important point is that active rain can shut the controller down locally. Home Assistant does not need to notice the rain, send an API command and hope Wi-Fi is still working.
Rain Shutdown Is Not the Same as Rain Start Lockout
The local action above stops a cycle if the rain sensor becomes wet. You should also prevent a scheduled cycle from starting while the sensor is already wet.
That second decision is easy in Home Assistant: put the rain-sensor state into the automation conditions before turning on the irrigation-cycle switch.
Add Soil Moisture
A capacitive soil-moisture sensor gives Home Assistant another reason to skip watering. Use a 3.3 V-compatible analogue output and calibrate it against your own soil rather than trusting the percentage printed by somebody else’s project.
sensor:
- platform: adc
pin: GPIO34
name: "Garden Soil Moisture"
id: garden_soil
update_interval: 60s
attenuation: auto
filters:
- calibrate_linear:
- 2.75 -> 0
- 1.35 -> 100
- clamp:
min_value: 0
max_value: 100
unit_of_measurement: "%"
device_class: moisture
state_class: measurement
The example assumes a sensor whose voltage falls as moisture rises. Your own dry/wet voltages will be different, so replace 2.75 V and 1.35 V with actual measurements from your installation.
Our upcoming dedicated Capacitive Soil Moisture Sensor with ESP32, ESPHome and Home Assistant guide will cover corrosion, calibration, mounting depth and long-term drift in detail.
Keep the Moisture Sensor Representative
One sensor in one flower pot should not decide whether a 200 m² lawn needs water.
For useful irrigation control:
- Use a sensor in a representative root zone.
- Do not place it directly beside a dripper.
- Avoid positions permanently shaded or unusually wet.
- Use separate sensors for genuinely different irrigation zones where needed.
- Use threshold hysteresis rather than one exact percentage.
Home Assistant Schedule Example
The simplest useful Home Assistant automation is: at the scheduled time, start the full ESPHome cycle only when the rain sensor is dry and the soil is below a chosen threshold.
alias: Garden Irrigation Morning Cycle
triggers:
- trigger: time
at: "05:30:00"
conditions:
- condition: state
entity_id: binary_sensor.irrigation_rain_sensor
state: "off"
- condition: numeric_state
entity_id: sensor.garden_soil_moisture
below: 35
actions:
- action: switch.turn_on
target:
entity_id: switch.irrigation_cycle
mode: single
Entity IDs are generated from your actual ESPHome/Home Assistant names, so confirm them in Home Assistant rather than assuming they will exactly match the example.
Add Weather Forecast Logic Carefully
Weather-aware irrigation sounds clever, but the best rule is often simple:
Do not water when:
soil is already wet
OR rain sensor is active
OR meaningful rain is expected soon
Use forecast rain as a scheduling decision in Home Assistant rather than a safety interlock inside the ESP32. Online weather data can be wrong or unavailable; a local physical rain sensor remains more appropriate for immediate shutdown.
Similarly, do not build an elaborate evapotranspiration model unless you actually have the inputs and need the precision. A seasonal multiplier plus rain/moisture lockouts is often more reliable than a complicated model fed by poor weather data.
Manual Zone Control
The ESPHome sprinkler component automatically creates individual valve switches. Turning on a zone from Home Assistant runs that zone for its configured duration and disables auto-advance for that manual run.
This is ideal for:
- Testing sprinkler heads.
- Checking for leaks.
- Watering one dry zone.
- Commissioning new pipework.
Do not expose the underlying physical relay switches as separate user controls. Bypassing the sprinkler controller would also bypass its sequencing and pump coordination.
Pause, Resume and Shutdown
ESPHome provides actions for:
sprinkler.pausesprinkler.resumesprinkler.shutdownsprinkler.start_full_cyclesprinkler.start_single_valve- next/previous zone actions
- zone queuing
The queue can be useful for advanced Home Assistant automations: instead of starting a complete cycle, Home Assistant can build a custom sequence of only the zones that actually need water.
A Flow Meter Makes the System Much Smarter
If your irrigation supply has a pulse-output water meter, Home Assistant can detect conditions that a valve-state sensor cannot.
| Valve state | Measured flow | Possible problem |
|---|---|---|
| All valves off | Flow present | Leak or stuck valve |
| Zone on | Zero flow | Closed supply, failed pump or valve |
| Zone on | Much higher than normal | Broken pipe or sprinkler head |
| Zone on | Much lower than normal | Blocked filter/nozzle or low pressure |
See our ESP32 Water Meter for Home Assistant guide for pulse-meter configuration and total water tracking.
Optional Tank-Level Interlock
If irrigation is supplied from a rainwater tank, do not let the pump run dry. A Home Assistant condition can block scheduled irrigation when the tank level is low, while a hard low-level switch wired locally can provide an even stronger pump interlock.
Our ESP32 Water Tank Level Monitor guide covers ultrasonic and hydrostatic level measurement.
Fail-Safe Behaviour
A smart irrigation controller should fail toward water off.
Recommended behaviour:
- Relay GPIOs boot off.
- A restarted ESP32 does not blindly restore an active valve.
- Every manual zone has a finite configured duration.
- Rain input can stop the controller locally.
- Pump never runs intentionally with no valid downstream water path.
- Underlying relay switches are internal.
- Home Assistant being offline prevents smart scheduling, not valve shutdown.
Outdoor Installation
Do not put an ESP32 DevKit and open relay board into an ordinary plastic food box beside the irrigation manifold.
A permanent installation should use:
- A proper weather-resistant enclosure.
- Cable glands.
- Fused transformer output.
- Terminal blocks rather than loose jumpers.
- Separation between mains and extra-low-voltage wiring.
- Surge/transient consideration for long outdoor cables.
- Antenna placement that does not bury the ESP32 inside a metal cabinet.
Long valve wires outdoors can collect electrical transients. Relay isolation does not magically protect every part of the PCB, so enclosure grounding, surge protection and good cable routing matter on a permanent system.
Commissioning Checklist
- Power the ESP32 with all valve outputs disconnected.
- Confirm every physical relay starts OFF.
- Test each relay with no valve connected.
- Connect and test one valve at a time.
- Verify zone names match the actual pipework.
- Test pump/master-valve timing.
- Run a complete automatic cycle while watching transitions.
- Trigger the rain sensor during watering and confirm immediate shutdown.
- Restart the ESP32 during a test run and confirm outputs return safely off.
- Only then enable Home Assistant schedules.
Common Problems
| Symptom | Likely cause / first check |
|---|---|
| Valve never opens | Check 24 VAC at valve and relay contact wiring; GPIO cannot power it directly |
| Relay LED changes but valve does nothing | Transformer/common wiring or contact terminal error |
| Valve stays on after ESP reboot | Wrong relay restore/polarity configuration or hardware wiring |
| Pump starts before water path is open | Configure pump start/stop delays |
| Pipes bang when changing zones | Consider valve overlap or different hydraulic sequencing |
| All valves run manually but full cycle skips one | Zone’s enable switch is off |
| Run times reset unexpectedly | Use sprinkler number entities with restore enabled |
| Rain sensor stops cycle but morning schedule restarts it | Add rain condition to Home Assistant start automation |
| Soil percentage is backwards | Swap/calibrate dry and wet ADC reference points |
| Wi-Fi loss breaks active watering | Do not put valve sequencing in HA delays; keep it in ESPHome |
Why ESPHome’s Sprinkler Component Is Better Than Four Relay Automations
You could create four switches in Home Assistant and write an automation containing:
zone 1 on
delay 10 min
zone 1 off
zone 2 on
delay 10 min
...
That looks simple but pushes hydraulic sequencing and output state into a network-dependent automation. A Home Assistant restart, automation reload or logic error can leave behaviour harder to reason about.
ESPHome’s sprinkler controller already knows that zones belong to one controller. It manages the run timers, cycle progression, pump/master output and transitions locally. Home Assistant only needs to say “start the irrigation cycle” or “run zone 3”.
Recommended Final Design
For a typical four-zone home installation, I would build it like this:
- ESP32-WROOM controller.
- Four isolated/appropriate relay channels for 24 V AC valves.
- One additional relay for a master valve or contactor input if required.
- ESPHome
sprinklercontroller for all sequencing. - Per-zone run-time number entities.
- One global seasonal multiplier.
- Local rain sensor that can immediately call
sprinkler.shutdown. - Home Assistant schedule with rain and moisture conditions.
- Optional pulse water meter for leak/flow verification.
- Optional tank-level interlock when using stored water.
That gives you the useful flexibility of a smart irrigation platform without making a cloud service or Home Assistant connection responsible for keeping a physical valve safe.
Related ESP32 Home Automation Guides
- ESP32 Smart Relay for Home Assistant — relay wiring, safe GPIO behaviour and restore modes.
- ESP32 Water Meter for Home Assistant — live flow and total water for leak/irrigation monitoring.
- ESPHome pulse_meter vs pulse_counter — choosing the right pulse method for water meters.
- ESP32 Water Tank Level Monitor — ultrasonic and pressure-sensor tank monitoring.
Official Resources
- ESPHome Sprinkler Controller — zones, pumps, queues, repeats, multipliers and timing control.
- ESPHome GPIO Switch — relay outputs and restore behaviour.
- ESPHome GPIO Binary Sensor — rain switches and dry-contact inputs.
- ESPHome ADC Sensor — analogue soil-moisture measurements.