Luke Angel
A garden bed with a buried soil-moisture probe sending a long low-frequency signal over a privacy fence to a gateway at the house, where Wi-Fi and Zigbee can't reach — the long-range LoRa link that makes the far-corner garden automatable.

Outdoor watering automation — the LoRa-and-rain story

by
#smart-home#esphome#lora#irrigation#sensors

The vegetable garden is at the far corner of the back yard. ~40m from the house, behind a 6-foot privacy fence. WiFi doesn’t reach reliably — measured signal strength below -85 dBm at the planters. Zigbee doesn’t reach at all.

LoRa does. This is what I built.

Why the garden needs LoRa. The garden beds sit about 40 metres from the house, behind a 6-foot privacy fence. Wi-Fi measures below -85 dBm out there — too weak to be reliable — and Zigbee's 2.4 GHz mesh doesn't reach past the fence at all. LoRa on the 915 MHz band, designed for kilometre-scale low-power links, crosses the distance and the obstacles with margin to spare: the soil sensors uplink to a gateway at the house through three wood-frame structures and the fence. The trade is throughput — LoRa carries only a few bytes slowly — but a moisture reading every 30 minutes needs almost none, so range is the only spec that matters here.

The hardware

Soil-moisture sensors (8×):

  • Capacitive soil-moisture probe (not resistive — resistive corrodes in 6-12 months underground).
  • Each connected to a RAK Wireless RAK4631 WisBlock module — ARM Cortex-M4F + Semtech SX1262 LoRa radio (915 MHz US ISM band).
  • Battery: 2× AA lithium (3000 mAh combined). Sensor reports every 30 minutes; expected life 2+ years.
  • Custom enclosure: 3D-printed PETG, IP54 with foam seals around the probe entry. Total per-sensor cost: $35.

LoRa gateway:

  • RAK Wireless RAK7268 indoor gateway. Connects to home WiFi, talks to the sensors over LoRa.
  • ~$200.
  • Range tested: 200m through 3 wood-frame buildings + the privacy fence. Plenty.
  • Acts as a LoRaWAN concentrator. Sensors join as Class A devices (uplink-only, downlink during the receive windows after uplink).

Valves (3×):

  • Orbit B-Hyve Z-Wave-compatible valve. Connects to standard 3/4” hose threads. Battery-powered (4× AA).
  • Z-Wave Plus. Local control via the Z-Stick.
  • $75 each.

Total hardware: $445 for the irrigation system. Compare to professional irrigation controller + sensors: $1500+.

The protocol — LoRaWAN

LoRa is the long-range, low-power, low-throughput wireless protocol that fills the gap where Zigbee can’t reach and WiFi is overkill. Key specs:

  • 915 MHz ISM band (US). No license required. Different from WiFi/Zigbee 2.4 GHz — uncongested.
  • Range: typical 1-5 km in suburban environments, line-of-sight further.
  • Data rate: 0.3-50 kbps depending on spreading factor. Slow.
  • Battery: years on a coin cell for periodic sensor uplinks.
  • Topology: star (sensors → gateway). No mesh.
  • Cost: $5-15 per LoRa module in volume.

LoRaWAN adds a network-layer spec on top of the LoRa PHY:

  • OTAA (Over-The-Air-Activation): sensors join with a DevEUI + AppKey, get session keys.
  • AES-128 encryption at the network layer.
  • Class A (most battery-friendly): uplink, then two short receive windows.
  • Adaptive Data Rate: gateway tells sensors to use slower/faster rates depending on signal quality.

For my use case (8 sensors uplinking ~5 bytes every 30 min), the bandwidth is laughably overprovisioned. The win is the range.

The HA integration

RAK gateway publishes sensor uplinks to MQTT. HA’s MQTT integration picks them up:

mqtt:
  sensor:
    - name: "Garden Bed 1 Moisture"
      state_topic: "rak/sensor/bed1/moisture"
      value_template: "{{ value_json.moisture }}"
      unit_of_measurement: "%"
    - name: "Garden Bed 1 Battery"
      state_topic: "rak/sensor/bed1/battery"
      value_template: "{{ value_json.battery }}"
      unit_of_measurement: "V"

  # × 8 sensors

The watering automation

- alias: "Garden bed 1: water if dry and no rain expected"
  trigger:
    - platform: time
      at: "06:30:00"
  condition:
    - condition: numeric_state
      entity_id: sensor.garden_bed_1_moisture
      below: 35   # below 35% volumetric water content = dry
    - condition: template
      value_template: >
        {% set forecast = state_attr('weather.darksky_home', 'forecast') %}
        {% set rain_next_6h = forecast[0:6] | sum(attribute='precipitation', start=0) %}
        {{ rain_next_6h < 5 }}   # < 5mm forecast in next 6h
    - condition: template
      value_template: >
        {% set last_water = states('input_datetime.bed1_last_water') %}
        {% set hours_since = (now() - as_datetime(last_water)).total_seconds() / 3600 %}
        {{ hours_since > 18 }}   # don't water more than once per 18h
  action:
    - service: switch.turn_on
      data:
        entity_id: switch.bhyve_valve_bed1
    - delay: "00:08:00"   # 8 min run
    - service: switch.turn_off
      data:
        entity_id: switch.bhyve_valve_bed1
    - service: input_datetime.set_datetime
      data:
        entity_id: input_datetime.bed1_last_water
        datetime: "{{ now() }}"

Three conditions all have to hold:

  1. Soil is dry (< 35% VWC).
  2. Rain forecast next 6h < 5mm (no point watering if rain coming).
  3. Haven’t watered in last 18 hours.

If all three pass at 6:30 AM, the valve opens for 8 minutes.

The disasters that taught me

Disaster 1 (April): I set up the soil-moisture threshold at 25% (too dry). The system never watered because the sensors were reading slightly higher. Garden tomatoes wilted by mid-May. Adjusted to 35%.

Disaster 2 (May): Forgot to add the “haven’t watered in last 18h” condition. Forecast cleared mid-day, valve opened, ran 8 minutes. Then forecast cleared again at 4 PM (different forecast cycle), valve opened again. Bed flooded; soil washed off the lettuce roots.

Disaster 3 (May): One of the soil sensors malfunctioned and read 5% constantly. Valve opened every morning for a week. Plants drowned. Added an outlier-detection condition to ignore sensors reading suspiciously low.

The algorithm is now:

IF soil_moisture < 35% 
   AND soil_moisture > 5%   (outlier guard)
   AND rain_forecast_6h < 5mm
   AND last_water_hours_ago > 18
THEN water for 8 minutes

Works.

The watering decision as a gate, and the disaster each condition prevents. A morning check runs four conditions in series, and all four must pass before the valve opens. "Soil below 35%" — set too dry at first (25%), so the bed never watered and the tomatoes wilted. "Soil above 5%" — added after a stuck sensor read 5% all week and drowned the plants; this guard ignores impossible lows. "Less than 5 mm of rain forecast in the next 6 hours" — no point watering ahead of rain. "At least 18 hours since the last watering" — added after two forecast cycles cleared on the same day, opened the valve twice, and washed the soil off the lettuce. Pass all four and the valve runs 8 minutes. Each condition is a scar from a specific dead plant.

What’s next

  • A rain-rate sensor at the house. Currently using Dark Sky forecasts — accurate but lagging real conditions. A tipping-bucket rain gauge ($40, ESP-based with reed switch) would give actual rainfall data.
  • A soil-temperature sensor in the same probe. Capacitive moisture probes can be combined with a 1-Wire DS18B20 for soil temp. Useful for “is it too cold to germinate seeds?” automations.
  • Solar charging for the LoRa sensors. Battery life 2+ years is fine, but in-place solar would make them “install and forget.”
  • A wider deployment: front-yard sprinkler zones (currently on a dumb timer), the kid’s small playground “is the swing wet?” question.

Keep reading

shares tags: #smart-home · #esphome
tools
Aeotec Multisensor 6 — six sensors in one Z-Wave device
Oct 28
tools
Aqara Zigbee door/window sensors at scale
Nov 26
tools
Glass-break and vibration sensors — the second-layer security
Aug 19