LocalFirst Home
< Back to all guides
by Renan

Build a Local AI Irrigation System With Soil Sensors and Weather RAG

Combine calibrated soil sensors, local climate history and guarded Home Assistant automations for private, water-aware irrigation.

Build a Local AI Irrigation System With Soil Sensors and Weather RAG

Combine calibrated soil sensors, local climate history and guarded Home Assistant automations for private, water-aware irrigation.

A soil sensor and a language model do not make an irrigation controller. Sensors drift, forecasts change, and a persuasive paragraph still cannot tell you how much water actually crossed the valve.

A useful local AI irrigation system needs three distinct layers: measured soil and water data, a structured control policy with hard limits, and an optional reasoning layer that explains or proposes adjustments. The model can help interpret history. It should not be the component with unrestricted access to the valve.

The Architecture

Local irrigation architecture with soil and weather sensors, time-series storage, climate RAG, deterministic safety rules and a fail-closed valve
Store measurements as time series, use RAG for supporting context, and put hard rules between the model and the valve. Open full-size image

The data path:

Soil moisture + temperature + rain + flow
  -> ESPHome or MQTT
  -> Home Assistant
  -> local time-series database

Historical climate + plant notes + watering restrictions
  -> local document store and retrieval
  -> local model produces recommendation

Recommendation + live sensor state
  -> deterministic policy
  -> relay
  -> normally closed valve

This borrows the right lesson from local energy management: forecasts and optimization are useful, but the actuator still needs boundaries. A bad prediction should waste neither a battery nor a week of water.

RAG Is Not a Weather Forecast

Retrieval-Augmented Generation retrieves relevant material and places it in the model’s context. It does not create a trustworthy forecast from old documents.

Use two storage paths:

Structured time-series data

Keep numeric observations in SQLite, PostgreSQL, InfluxDB, or another local database:

timestamp
zone_id
soil_moisture_percent
soil_temperature_c
air_temperature_c
rain_mm
irrigation_liters
valve_seconds

Query those values with SQL or a time-series API. Do not embed every five-minute moisture reading into a vector database and ask an LLM to perform arithmetic by vibes.

Retrieved documents

Use RAG for:

  • plant and soil notes;
  • irrigation-zone descriptions;
  • local watering restrictions;
  • valve and sensor manuals;
  • seasonal observations;
  • summaries of historical climate periods;
  • the household’s previous maintenance notes.

For U.S. locations, NOAA’s Climate Data Online API provides programmatic access to station and climate datasets. Download the historical slices you need and cache them locally. Current forecast data is a separate input and should carry a freshness timestamp.

The model can answer: “Last August, this zone dried quickly after three days above 90 F, and the clay bed recovered slowly after deep watering.” The database should calculate the actual averages behind that sentence.

Build the Sensor Layer First

Use an ESP32 with ESPHome or a small MQTT publisher. For each irrigation zone, collect:

  • capacitive soil moisture;
  • optional soil temperature;
  • local air temperature and humidity;
  • rain gauge or rain switch;
  • valve state;
  • flow rate;
  • cumulative water volume.

Avoid exposed resistive probes for permanent installation. Their electrodes corrode and the readings drift. Capacitive probes are not magically accurate, but they usually survive the job better when sealed and installed correctly.

One sensor does not represent an entire bed. Place probes near the active root zone, away from the emitter itself, and use at least two sensors for a valuable or uneven zone.

Calibrate Moisture in the Actual Soil

Raw analog values are not universal percentages.

Record:

dry_reference = reading from soil at the dry limit you accept
wet_reference = reading after deep watering and drainage

Then map the raw reading:

moisture_percent =
  clamp(
    (raw - dry_reference) /
    (wet_reference - dry_reference) * 100,
    0,
    100
  )

Depending on the sensor, the electrical direction may be reversed. Check the measurements instead of copying the subtraction order from a stranger’s YAML.

Recalibrate after moving the probe, changing soil mix, or replacing hardware. Store the calibration date and raw values in Home Assistant attributes or a local configuration file.

The Deterministic Irrigation Policy

The model may recommend watering. The policy decides whether watering is allowed.

Representative rules:

zone_raised_bed:
  moisture_start_below: 32
  moisture_stop_above: 48
  max_runtime_minutes: 12
  max_daily_liters: 45
  minimum_hours_between_runs: 18
  block_if_rain_detected: true
  require_flow_after_seconds: 10
  close_on_sensor_unavailable: true

Use a normally closed valve so loss of power stops water. Add a physical manual shutoff. A relay stuck on, split hose, frozen pipe, or missing sensor must not become an all-night irrigation experiment.

The EPA’s WaterSense program recognizes both weather-based and soil-moisture-based irrigation control. Its controller guidance emphasizes matching irrigation to weather, landscape conditions, and measured moisture instead of using a fixed clock alone.

Add the Local Recommendation Layer

Run the recommendation on a schedule, perhaps once in the early morning:

1. Query the last 72 hours of soil and flow data.
2. Query the latest forecast and its retrieval timestamp.
3. Retrieve plant notes, restrictions and matching seasonal summaries.
4. Ask the local model for a structured recommendation.
5. Validate the output against a schema.
6. Pass the proposal to the deterministic policy.
7. Notify the household of the result.

Expected model output:

{
  "zone": "raised_bed",
  "recommendation": "water",
  "requested_minutes": 8,
  "reason_codes": [
    "soil_below_threshold",
    "no_recent_rain",
    "high_evaporation_period"
  ],
  "confidence": "medium"
}

Reject free-form actions. If the JSON is invalid, the model is unavailable, or the zone name is unknown, do nothing and record the failure.

Forecast Freshness and Offline Behavior

Every external weather response needs:

forecast_retrieved_at
forecast_valid_until
source_station_or_grid

When the internet fails:

  • continue reading local sensors;
  • keep enforcing maximum runtime and volume;
  • use a conservative soil-only rule if the household approved it;
  • announce that forecast context is stale;
  • never reuse yesterday’s rain forecast as if it were current.

A local weather station improves resilience, but it still needs calibration and maintenance. Historical climate data helps explain seasonal patterns. Neither replaces a current measurement in the bed.

Use Flow as the Truth Sensor

Valve state tells you what Home Assistant requested. Flow tells you what the plumbing did.

Failure logic:

Valve opens
AND no flow after 10 seconds
  -> close valve
  -> report blocked supply, failed valve or failed flow meter

Valve closes
AND flow continues for 10 seconds
  -> cut relay power
  -> trigger leak alert
  -> request manual shutoff

Track liters per zone. If a normal eight-minute run suddenly uses twice as much water, a hose or emitter may have failed.

Voice Notifications Without Giving Voice Control the Valve

The local voice assistant can say:

The raised bed is below its moisture threshold.
Rain data is current.
Eight minutes of irrigation is scheduled for 5:30 AM.

It can accept:

  • “skip the garden today”;
  • “pause irrigation for three days”;
  • “how much water did the herb bed use this week?”;
  • “why was watering blocked?”

Giving a language model an “open any valve for any duration” tool is needlessly risky. Map voice to narrow scripts with maximums:

script.irrigation_skip_today
script.irrigation_pause_72h
script.irrigation_status
script.irrigation_manual_test_30s

The voice architecture in Offline Voice Control for Home Assistant With Gemma 4 uses the same allowlist principle.

Network and Power Layout

Place the components deliberately:

Home Assistant:  10.20.0.20
Local AI/RAG:    10.20.0.30
ESP32 gateway:   10.40.0.31
Weather station: 10.40.0.32

Allow the ESP32 to publish only to Home Assistant or the local MQTT broker. The local model needs read access to the prepared context and no direct network route to the irrigation relay. Home Assistant calls a narrow policy service that owns the actuator.

Use a small UPS for the server, router, and controller if irrigation state matters during short outages. The valve should still fail closed when the field power disappears.

Test With a Bucket Before a Garden

Commission the system without trusting it:

  1. Put the output into a measured bucket.
  2. Run the 30-second manual test.
  3. Verify expected flow.
  4. Disconnect a soil sensor.
  5. Block the water supply.
  6. Simulate a stuck flow signal.
  7. disconnect the internet.
  8. stop the model service.
  9. restart Home Assistant mid-run.
  10. verify the valve closes at the hard runtime limit.

Watch it for several weeks in recommendation-only mode. Compare suggestions with soil measurements and plant condition. Only then allow automatic watering within tight limits.

Where the AI Actually Helps

The model is good at connecting messy context: plant notes, seasonal history, forecast summaries, and previous maintenance events. It can explain why a zone behaves differently and produce a useful morning report.

The moisture threshold, daily volume ceiling, flow fault, and maximum valve time should remain plain code. Let the model explain why irrigation was skipped; let the controller decide when the valve must close.

Keep reading

Related guides

View all guides