In this post
The best thing about home automation is that it does exactly what you told it to. That is also the worst thing.
Last week I watched my sprinklers run a full cycle in the rain. The automation controlling them was called Watering (morning & evening, weather-dependent). It had weather conditions in it. It had been running all summer. And it was soaking a garden that was already wet.
This is the story of how that automation got there, and how I rebuilt it.
Version one: a very confident timer
The first sketch was the one everybody writes. Two triggers, three zones, a pile of delays:
triggers:
- trigger: sun
event: sunrise
offset: "00:30:00"
- trigger: sun
event: sunset
offset: "01:00:00"
actions:
- action: switch.turn_on
target:
entity_id: switch.backyard_24v_4ch_switch_2
- delay: "00:30:00"
- action: switch.turn_off
target:
entity_id: switch.backyard_24v_4ch_switch_2
- delay: "00:00:15"
- action: valve.open_valve
target:
entity_id: valve.backyard_24v_4ch_switch_1
- delay: "00:35:00"
- action: valve.close_valve
target:
entity_id: valve.backyard_24v_4ch_switch_1
- delay: "00:00:15"
- action: switch.turn_on
target:
entity_id: switch.front_garden_irrigation_switch_1
- delay: "00:20:00"
- action: switch.turn_off
target:
entity_id: switch.front_garden_irrigation_switch_1
Backyard sprinklers on a 4 channel 24 V board, a drip valve on the same board, and the front garden on its own relay. Thirty minutes, thirty five minutes, twenty minutes, twice a day. That is 85 minutes per cycle and 171 minutes of watering every single day, in July and in November alike.
It worked in the sense that the grass was green. It also ran during a thunderstorm, which is the moment you realise your irrigation system has no idea what a sky is.
Version two: the weather check that was not a weather check
So I added conditions. This is where it gets embarrassing, because on paper this looks fine:
conditions:
- condition: not
conditions:
- condition: state
entity_id: sensor.openweathermap_condition
state: rainy
- condition: not
conditions:
- condition: state
entity_id: sensor.openweathermap_precipitation_kind
state: Rain
- condition: not
conditions:
- condition: or
conditions:
- condition: state
entity_id: sensor.openweathermap_weather
state: overcast clouds
- condition: state
entity_id: sensor.openweathermap_weather
state: light rain
Three problems, and I lived with all of them for months.
It matched exact strings. OpenWeatherMap does not only say light rain. It says moderate rain, heavy intensity rain, shower rain, drizzle, thunderstorm with rain. None of those equal any of my three values, so every one of them watered.
It had no memory. This is the real bug. Conditions are evaluated at the instant the automation fires. If it poured all afternoon and the sky cleared by seven in the evening, the evening cycle saw clear skies and dumped another 85 minutes on saturated soil. A rain check that only looks at the present moment is barely a rain check.
It was still the same fixed 85 minutes. A September garden was getting the July schedule.
The result was a system that looked clever in the automation editor and was dumb in the garden.
Teaching it what “it rained today” means
The fix that mattered was not smarter conditions. It was giving Home Assistant a number it never had: how much rain actually fell today.
The weather integration gives a rain rate in mm/h. On its own that is useless, because it is only true for the moment you read it. But a rate integrated over time is an amount, and both pieces ship with Home Assistant:
- An Integration (Riemann sum) helper on
sensor.openweathermap_rain, method left, time unit hours. Millimetres per hour integrated over hours gives millimetres. That becomessensor.openweathermap_rain_accumulated, a running total. - A Utility Meter helper on top of it with a daily cycle. That becomes
sensor.openweathermap_rain_today, which resets at midnight.
Two helpers created in the UI, no custom component, no external service. Now the automation can ask the only question that matters: has enough rain fallen today that I should stay off?
wet: >
{{ (states('sensor.openweathermap_rain_today') | float(0)) >= 2
or (states('sensor.openweathermap_rain') | float(0)) >= 0.2
or states('sensor.openweathermap_condition') in
['rainy','pouring','lightning-rainy','snowy-rainy','hail','snowy']
or 'rain' in states('sensor.openweathermap_weather') | lower
or 'drizzle' in states('sensor.openweathermap_weather') | lower
or 'thunder' in states('sensor.openweathermap_weather') | lower }}
Three layers: rain that already fell today, rain falling right now, and a substring match that catches every wording the API throws at me. 'rain' in ... beats listing fifteen exact states, and it never misses moderate rain again.
Teaching it what month it is
Fixing rain still left a garden watered in September like it was August. Plants do not want a constant amount of water, they want a seasonal amount. Long days at 35 °C need roughly double what a mild October day needs, and in a Tbilisi winter watering is at best wasted and at worst freezes a pipe.
So the durations became a function of the month, with the zone times cut by about a third from the old values:
variables:
factor: >
{{ {6:1.0, 7:1.0, 8:1.0, 5:0.75, 9:0.75, 4:0.5, 10:0.5}.get(now().month, 0) }}
backyard_s: "{{ (1200 * factor) | int }}"
drip_s: "{{ (1320 * factor) | int }}"
front_s: "{{ (780 * factor) | int }}"
| Months | Level | Per run | Cycles |
|---|---|---|---|
| Jun to Aug | 100% | 55 min | morning and evening |
| May, Sep | 75% | 41 min | evening only |
| Apr, Oct | 50% | 27 min | evening only |
| Nov to Mar | off | none | none |
The delays then use those variables instead of hard coded times:
- action: switch.turn_on
target:
entity_id: switch.backyard_24v_4ch_switch_2
- delay:
seconds: "{{ backyard_s }}"
- action: switch.turn_off
target:
entity_id: switch.backyard_24v_4ch_switch_2
Outside summer only the evening cycle runs. The morning trigger still exists, it just stops itself:
- if:
- condition: trigger
id: morning
- condition: template
value_template: "{{ now().month not in [6,7,8] }}"
then:
- stop: "Morning cycle only runs in June to August"
Morning watering in cool weather leaves leaves wet for hours and invites mildew. In July it is the opposite, the morning run is what carries the plants through the afternoon.
Skips that tell you they happened
A silent skip is indistinguishable from a broken automation. So every skip sends a notification with the numbers behind it:
- if:
- condition: template
value_template: "{{ wet }}"
then:
- action: notify.mobile_app_jabas_iphone_17_pro
data:
title: "Home Automation"
message: >
Watering skipped: rain today {{ rain_today | round(1) }} mm,
now {{ rain_now }} mm/h, {{ states('sensor.openweathermap_weather') }}
- stop: "Rain"
There is also an input_boolean.watering_hold_skip_next toggle for the evenings when I know I do not want water, for example before laying turf. The automation clears it after using it, so a hold is always exactly one cycle.
The dashboard

My sprinklers ran in the rain for months because nothing ever showed me what they were doing. So the rebuild came with a view that answers the obvious questions without opening the automation editor:
- Status chips: next run time, planned minutes, season level, rain today, outside temperature.
- A status card driven by a template sensor that says
Ready, 41 min planned, orWould skip, rain 3.2 mm today, orOn hold, orOff-season. - Zone tiles for the backyard, the drip line and the front garden, which light up while they are running.
- Gauges for rain today, where the needle crosses into red exactly at the 2 mm skip threshold, and for the season level.
- A log of every run and every skip for the last two weeks.
The status sensor is a small template helper, which keeps the logic in one place:
{% set f = {6:1.0,7:1.0,8:1.0,5:0.75,9:0.75,4:0.5,10:0.5}.get(now().month,0) %}
{% if f == 0 %}Off-season (Nov-Mar)
{% elif is_state('input_boolean.watering_hold_skip_next','on') %}On hold
{% elif wet %}Would skip - rain {{ states('sensor.openweathermap_rain_today') }} mm today
{% else %}Ready - {{ ((1200+1320+780) * f / 60) | round(0) | int }} min planned{% endif %}
What changed in numbers
Before: 171 minutes a day, every day, rain included, all year.
Now, in late September: 41 minutes in the evening, skipped entirely when it rains, and nothing at all between November and March. That is roughly a 75% cut in water, and a happier garden, because the old schedule was quietly drowning it in the shoulder seasons.
Three things I would tell past me
- A condition that reads the present moment is not a rain sensor. Integrate the rate, store the total, decide on the total.
- Match loosely on weather text. A substring test catches a dozen variants you would never enumerate by hand.
- Make the automation tell you when it does nothing. The skips are the interesting events. A notification with millimetres in it is how I would have found this bug in week one instead of month three.
The next step is soil moisture probes in the beds, so the garden itself gets a vote instead of a weather station three kilometres away.
If you are building something similar, feel free to reach out on LinkedIn.
Follow along. New posts show up on the RSS feed. I also share my projects on GitHub, MakerWorld and LinkedIn.