Back to blog
FILE 0xAC·A TINY GARDEN SPRINKLER WATCHER THAT ONLY TEXTS WHEN IT MATT

A tiny garden sprinkler watcher that only texts when it matters

May 22, 2026 · homelab, automation, garden

I turned the garden sprinkler off because rain was forecast for a week. The question is: when do I turn it back on? I do not want to check a weather app every morning. I want exactly one notification, the morning the rain stretch ends.

What was happening

The old version of this is "I forget to turn the sprinkler back on, the garden bakes for three days, I feel bad." Or the inverse: I turn it on too early and waste water during the trailing storms. The notification I want is very specific: "the next 48 hours look dry, you can turn it back on."

What I found

The National Weather Service grid forecast covers my area with periods roughly twelve hours apart. Four periods is ~48 hours of lookahead, which is the right window for "is this rain stretch actually ending?" I don't trust any single period — a 30% chance of a passing afternoon shower isn't a deal-breaker — but four consecutive low-PoP periods with no shower/thunder/drizzle wording is a real dry stretch.

The notification has to be edge-triggered. If I fired a push every time the forecast looked dry, I'd be back to ignoring it within a week.

The fix

A small Python script on cron, twice a day. State lives in a single JSON file:

import json, requests, pathlib, datetime

STATE = pathlib.Path("/var/lib/garden-weather/state.json")
GRID  = "https://api.weather.gov/gridpoints/LIX/116,111/forecast"

state = json.loads(STATE.read_text()) if STATE.exists() else {}
periods = requests.get(GRID, headers={"User-Agent": "garden-watcher"}).json()
periods = periods["properties"]["periods"][:4]

WET_WORDS = ("shower", "thunder", "rain", "drizzle", "storm")
def is_wet(p):
    if p.get("probabilityOfPrecipitation", {}).get("value") or 0 > 30:
        return True
    forecast = (p["shortForecast"] + " " + p["detailedForecast"]).lower()
    return any(w in forecast for w in WET_WORDS)

dry = not any(is_wet(p) for p in periods)

if dry and not state.get("notified_dry_at"):
    send_push("Garden: rain has cleared, safe to turn the sprinkler back on")
    state["notified_dry_at"] = datetime.datetime.utcnow().isoformat()
elif not dry and state.get("notified_dry_at"):
    # Rain is back. Reset so the next dry stretch will fire again.
    state.pop("notified_dry_at", None)

STATE.write_text(json.dumps(state))

Cron is two entries per day, 7:13 and 17:13 local — odd minutes so I'm not fighting other cron jobs on the round numbers.

The state file is what makes this work. Without it, every dry-stretch run sends another push and I learn to ignore them. With it, one dry window produces exactly one notification. When wet weather returns, the flag clears, and the next dry stretch fires fresh.

What I'd do differently

This is the kind of script that's almost not worth writing — until you notice the alternative is checking a weather app every morning for a week and still forgetting half the time. The lesson I keep relearning: small automations that produce exactly one notification at exactly the right time are weirdly satisfying, and they're an order of magnitude more useful than dashboards I have to remember to look at.