Back to blog

Smart Garden Watering with Node-RED and Home Assistant

| 5 min read

After spending one too many summers manually turning on sprinklers during a heatwave (or worse, forgetting to), I decided it was time to automate my garden watering. The result is a Node-RED flow that evaluates weather conditions daily at sunrise and decides whether to water and for how long.

What the System Does

The core idea is simple: water the garden based on three factors:

  1. Maximum temperature since the last watering
  2. Days elapsed since the last watering
  3. Total rainfall (actual + expected) since the last watering

Every morning at sunrise, the system evaluates these conditions and either starts a watering cycle or skips the day. When it waters, it sequences through four zones one at a time to maintain adequate water pressure.

Hardware Setup

My setup uses two types of Gardena smart irrigation products:

┌─────────────────┐
│ Water Main │
└────────┬────────┘
┌───────────────────┼───────────────────┐
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Gardena Smart │ │ Outdoor Tap │
│ Gateway │ └────────┬────────┘
└────────┬────────┘ │
│ ▼
┌────────▼────────┐ ┌─────────────────┐
│ 3-Valve Smart │ │ Bluetooth Valve │
│ Distribution Box│ │ (Gardena) │
└────────┬────────┘ └────────┬────────┘
│ │
┌───────┼───────┐ ▼
│ │ │ ┌─────────────────┐
▼ ▼ ▼ │ Gardena Water │
Lawn Planter Planter │ Stop + Spray │
Left Right └─────────────────┘
(Drip) (Drip) Front Yard

Backyard (via Smart Gateway):

  • Lawn - Standard sprinkler coverage
  • Left Planter - Drip irrigation system
  • Right Planter - Drip irrigation system

Front yard (via Bluetooth valve):

  • Front Yard - Connected to a Gardena water stop with spray nozzle

The zones are watered sequentially rather than simultaneously because running multiple zones would drop the water pressure too much for effective coverage.

The Decision Logic

Every day at sunrise, the system runs through this decision tree:

┌─────────────┐
│ Sunrise │
└──────┬──────┘
┌────────────────────────┐
│ Days since watering >4?│
└────────────┬───────────┘
│ │
Yes No
│ │
▼ ▼
┌──────────┐ ┌─────────────────────┐
│Reset all │ │Check max temperature│
│counters │ └──────────┬──────────┘
└──────────┘ │
┌───────────────────────┐
│ Match temperature tier │
│ & check days threshold │
└───────────┬───────────┘
┌───────────────────────┐
│ Rain < threshold for │
│ this tier? │
└───────────┬───────────┘
│ │
Yes No
│ │
▼ ▼
┌────────────┐ ┌──────────┐
│Start cycle │ │Skip today│
└────────────┘ └──────────┘

Temperature Tiers

The watering duration and conditions depend on how hot it’s been:

Max TempDays Since LastRain ThresholdDuration
> 31°Cany< 20.1mm90 min
26-31°C≥ 2 days< 15.1mm75 min
21-26°C≥ 3 days< 10.1mm60 min
15-21°C≥ 3 days< 10.1mm45 min
Any> 4 days- (safety)reset

Visual representation:

Duration (min)
90 ├────────────────────────────────────────────── > 31°C (extreme heat)
75 ├──────────────────────────────────── 26-31°C (hot)
60 ├────────────────────────── 21-26°C (warm)
45 ├──────────────── 15-21°C (mild)
0 ├──── < 15°C (skip watering)
└────┬─────┬─────┬─────┬─────┬────► Temperature (°C)
15 21 26 31 35

The logic is intentionally conservative: during extreme heat, water every day regardless of how recently you watered. During milder temperatures, wait a few days between cycles.

Rain Tracking

Rain data comes from OpenWeatherMap integration in Home Assistant. The system tracks two values:

  1. Actual rainfall - How much rain has fallen since the last watering
  2. Expected rainfall - Today’s forecast: precipitation amount × probability

For example, if the forecast shows 5mm with 60% probability, the expected rain is 5 × 0.6 = 3mm.

Both values are accumulated between watering sessions and summed to determine if we’ve had “enough” rain. This approach isn’t perfect—sometimes forecasts are wrong—but it’s been reliable enough in practice.

// Simplified rain calculation
const actualRain = msg.payload; // from weather sensor
const currentTotal = parseFloat(global.get('rain_since_watering')) || 0;
const newTotal = currentTotal + actualRain;
global.set('rain_since_watering', newTotal);

Temperature Tracking

Temperature tracking follows a similar pattern, but we only care about the maximum temperature since the last watering:

// Temperature tracking logic
const currentTemp = msg.payload;
const storedMax = parseFloat(global.get('max_temp_since_watering')) || 0;
if (currentTemp > storedMax) {
global.set('max_temp_since_watering', currentTemp);
}

This runs every time the temperature sensor updates. After watering (or after 4+ days), the maximum is reset to start fresh.

The Watering Sequence

When watering is triggered, each zone runs sequentially. Here’s what a 90-minute cycle looks like:

Time ────────────────────────────────────────────────────────────────────►
Sunrise ~6h later
│ │
▼ ▼
┌─────────┐
│Evaluate │
│Conditions│
└────┬────┘
┌─────────────────────────────────────────────────────────────────────┐
│ Notification: "Watering started for 90 minutes" │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ Front Yard │
│ ████████████████████████████████████████████████████████████████ │
│ 0 ──────────────────────────────────────────────────────────► 90min │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ Lawn │
│ ████████████████████████████████████████████████████████████████ │
│ 0 ──────────────────────────────────────────────────────────► 90min │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ Left Planter (drip) │
│ ████████████████████████████████████████████████████████████████ │
│ 0 ──────────────────────────────────────────────────────────► 90min │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ Right Planter (drip) │
│ ████████████████████████████████████████████████████████████████ │
│ 0 ──────────────────────────────────────────────────────────► 90min │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ Notification: "Watering stopped" │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ Reset: days counter, max temp, rain accumulator │
└─────────────────────────────────────────────────────────────────────┘

A full 90-minute cycle across all four zones takes 6 hours to complete. The morning start time ensures everything is done by early afternoon.

Node-RED Flow Overview

The flow uses these key node types:

Triggers:

  • state-change nodes watching sun.sun for sunrise
  • state-change nodes watching temperature and rain sensors

Logic:

  • function nodes for calculations and threshold comparisons
  • switch nodes for routing based on conditions
  • current-state nodes to check sensor values

Actions:

  • call-service nodes to turn valves on/off
  • delay nodes for watering duration
  • api-call-service for mobile notifications

Flow control:

  • link-in and link-out nodes for the reset sequence

Here’s a simplified version of the main evaluation function:

const maxTemp = parseFloat(global.get('max_temp_since_watering')) || 0;
const daysSince = parseInt(global.get('days_since_watering')) || 0;
const totalRain = parseFloat(global.get('rain_since_watering')) || 0;
// Safety reset after 4+ days
if (daysSince > 4) {
return [null, null, null, null, { payload: 'reset' }];
}
// Extreme heat: >31°C, any days, <20.1mm rain
if (maxTemp > 31 && totalRain < 20.1) {
return [{ payload: 90 }, null, null, null, null]; // 90 min
}
// Hot: 26-31°C, 2+ days, <15.1mm rain
if (maxTemp > 26 && maxTemp <= 31 && daysSince >= 2 && totalRain < 15.1) {
return [null, { payload: 75 }, null, null, null]; // 75 min
}
// Warm: 21-26°C, 3+ days, <10.1mm rain
if (maxTemp > 21 && maxTemp <= 26 && daysSince >= 3 && totalRain < 10.1) {
return [null, null, { payload: 60 }, null, null]; // 60 min
}
// Mild: 15-21°C, 3+ days, <10.1mm rain
if (maxTemp > 15 && maxTemp <= 21 && daysSince >= 3 && totalRain < 10.1) {
return [null, null, null, { payload: 45 }, null]; // 45 min
}
// Skip watering
return null;

Each output connects to a different watering duration flow.

Lessons Learned After One Season

After running this system through a full summer, I’ve made several adjustments:

Temperature thresholds needed tweaking. My initial thresholds were too conservative. During a 35°C+ heatwave, even 90 minutes wasn’t enough for some plants. I added a manual boost option for extreme conditions.

Drip systems need longer. The planters with drip irrigation needed roughly 50% longer run times compared to the lawn sprinklers to achieve equivalent saturation. I adjusted the per-zone durations accordingly.

Rain prediction is imperfect. Occasionally the system skips a day expecting rain that never comes, or waters just before an unexpected shower. It’s still better than manual management, but there’s room for improvement.

Notifications are essential. Getting a mobile notification when watering starts lets me catch any issues (forgotten hose still connected, etc.) before 6 hours of watering begins.

Known Limitations

The current system has a few gaps I’m aware of:

No mid-cycle abort. If it starts raining heavily while watering is in progress, the system will continue until the cycle completes. Adding a rain sensor trigger to stop early is on my to-do list.

No soil moisture feedback. The system uses weather data as a proxy for soil conditions, but actual soil moisture sensors would provide ground truth. The Gardena ecosystem doesn’t include these, so it would require additional hardware.

Manual intervention during heatwaves. Extended heat periods sometimes need manual watering boosts beyond what the automation provides. A “heatwave mode” with more aggressive settings could help.

Future Improvements

Some enhancements I’m considering:

  • Rain sensor integration - Abort watering if significant rain is detected during a cycle
  • Soil moisture sensors - Replace weather-based guessing with actual measurements
  • Per-zone adjustments - Different plants have different needs; the planters could use separate logic from the lawn
  • Seasonal presets - Automatically adjust thresholds based on time of year

Conclusion

Automating garden watering with Node-RED and Home Assistant has been one of my more satisfying home automation projects. The system handles 90% of watering decisions correctly, which is enough to keep the garden healthy while freeing me from the daily chore of manual irrigation.

If you’re considering a similar setup, start simple. Begin with one zone and basic temperature-based logic, then add complexity as you learn what your garden actually needs. The beauty of Node-RED is that you can iterate quickly—just drag, connect, and deploy.

For questions or suggestions, feel free to reach out on GitHub.