Irrigation that skips a zone when the soil is already wet
Six soil-moisture probes veto each zone's scheduled run. Over one summer the controller skipped 34 % of scheduled cycles and cut water use by 41 m³ without any visible plant stress on the tomatoes or the hedge line.
# HearthNodes recipe 22 - soil moisture irrigation
alias: "Water - irrigation vetoed by wet soil"
id: hn_water_soil_moisture_irrigation
description: "Six probes veto their zone's scheduled run, duration scaled by deficit"
mode: single
max_exceeded: silent
trigger:
- platform: time
at: "05:10:00"
id: group_a
- platform: time
at: "05:40:00"
id: group_b
- platform: time
at: "06:10:00"
id: group_c
variables:
zones:
- { id: 1, valve: valve.zone_1, probe: sensor.soil_moisture_beds, threshold: 22, base_min: 9 }
- { id: 2, valve: valve.zone_2, probe: sensor.soil_moisture_hedge, threshold: 18, base_min: 14 }
- { id: 3, valve: valve.zone_3, probe: sensor.soil_moisture_pots, threshold: 25, base_min: 6 }
rain_recent: "{{ states('sensor.rain_last_6h_mm') | float(0) }}"
rain_forecast: "{{ states('sensor.rain_forecast_3h_mm') | float(0) }}"
condition:
- condition: numeric_state
entity_id: sensor.rain_last_6h_mm
below: 0.5
- condition: numeric_state
entity_id: sensor.rain_forecast_3h_mm
below: 0.5
- condition: numeric_state
entity_id: sensor.irrigation_pressure_bar
above: 1.5
- condition: state
entity_id: input_boolean.irrigation_suspend
state: "off"
action:
- repeat:
for_each: "{{ zones }}"
sequence:
- variables:
moist: "{{ states(repeat.item.probe) | float(99) }}"
deficit: "{{ [[repeat.item.threshold - moist, 0] | max / repeat.item.threshold, 0.3] | min }}"
minutes: "{{ [[repeat.item.base_min * (1 + deficit * 2), repeat.item.base_min * 0.4] | max, repeat.item.base_min * 1.3] | min | round(0) }}"
- choose:
- conditions:
- condition: template
value_template: "{{ moist < repeat.item.threshold }}"
sequence:
- service: valve.open_valve
target:
entity_id: "{{ repeat.item.valve }}"
- delay: "{{ '00:%02d:00' | format(minutes) }}"
- service: valve.close_valve
target:
entity_id: "{{ repeat.item.valve }}"
- conditions: []
sequence:
- service: logbook.log
data:
name: "irrigation skipped"
message: "zone {{ repeat.item.id }} soil {{ moist }} percent, wet"
- service: sensor.record
data:
entity_id: sensor.water_used_irrigation
note: "compare litres delivered against litres commanded"
# Probe unavailable beyond 48 hours -> that zone drops to 60 percent of nominal.
# Flow meter disagreeing with commanded volume by more than 25 percent skips the
# zone next cycle and names it: usually a split dripper line or a stuck valve.
Trigger · time
time
Zone schedule start times: 05:10, 05:40 and 06:10, one per zone group.
Step-by-step
- valve.zone_[n]Run the zone for a duration scaled by how far below threshold the soil is, clamped between 40 % and 130 % of the nominal time.
open · duration {{ base_minutes * moisture_factor }} - valve.zone_[n]When the zone is skipped, log the probe's reading so the summer's decisions can be reviewed in autumn.
skip · log reason 'soil wet' - sensor.water_used_irrigationLog litres per zone per day so a leaking valve shows up as flow with no valve open.
log · daily litres per zone - input_boolean.irrigation_suspendSuspend the whole schedule for 48 hours if any probe reads above 35 %, which handles the day after a real downpour without needing rain data.
on · 48 h after any probe shows > 35 %
Calibrate each probe against its own soil
A capacitive probe reads differently in sandy loam than in clay. Take a handful of soil from the zone, dry it, and record the probe's raw dry value; then saturate it and record the wet value. Everything between is your scale. Skipping this step means the thresholds are in units that mean nothing.
Water early, and only when the soil says so
05:10–06:10 minimises evaporation and lets foliage dry before evening. Within that window, the probe decides whether the zone runs at all. A schedule that ignores the probe wastes water on the days after rain, which is also when over-watering does the most root damage.
Read the flow meter, not the controller
A controller reporting 'zone 3 ran for 8 minutes' means it sent the command. The flow meter tells you whether 96 litres actually went out. That distinction catches split lines, closed manual valves left over from winter, and drippers clogged by the season's first hard water.
Hardware
| Model | Protocol | Link | Role in this chain |
|---|---|---|---|
| Capacitive soil-moisture probes at 15 cm depth | zigbee |
wireless | Capacitive, not resistive: resistive probes corrode in weeks and their readings drift upward as they fail. |
| Zigbee multi-zone irrigation controller | zigbee |
wired | Six-zone controller with per-zone duration commands; the controller's own rain sensor input is left unconnected so the automation owns the decision. |
| Inline flow meter on the irrigation main | zigbee |
wired | Confirms each zone actually delivered the commanded litres, which is how a blocked dripper line is detected. |
When it fails
If a probe is unavailable for more than 48 hours, its zone drops to conservative mode at 60 % of nominal run time, which keeps plants alive without over-watering. If the flow meter disagrees with the commanded volume by more than 25 %, the zone is skipped on the next cycle and a notification names it — usually a split dripper line or a stuck valve.