Smart Garden Irrigation - Complete DIY Guide

Automatic garden irrigation system with smart mobile control and moisture sensors

Automatic irrigation saves water, time, and ensures healthy plants even during vacation. In this detailed guide, I'll show you how to build a smart irrigation system that responds to weather, soil moisture, and can be controlled from anywhere.

Why Smart Irrigation?

Traditional irrigation systems run on timers regardless of actual need. A smart system:

  • Measures soil moisture - waters only when needed
  • Monitors weather - skips irrigation before rain
  • Multiple zones - each part of garden according to need
  • Remote control - monitoring from anywhere
  • Statistics - track water consumption

System Components

Basic Hardware

1. Control Unit

  • ESP32/ESP8266 ($8-16) - WiFi microcontroller
  • Raspberry Pi ($60-100) - more advanced option
  • Arduino + WiFi shield ($32-48) - classic choice

2. Valves and Piping

Electromagnetic valves:
- 24V AC valves ($32-60 each)
- 12V DC valves ($24-40 each)
- Diameter based on flow (3/4" or 1")

Piping:
- PE tubing 16-25mm (5/8" - 1")
- Connectors, elbows, T-fittings
- Drippers or sprinklers

3. Power Supply

  • 24V AC transformer ($20-32)
  • Or 12V DC supply + relay board

4. Sensors

  • Soil moisture ($6-12 each)
  • Rain sensor ($8-16)
  • Water flow meter ($20-40)
  • Temperature/humidity ($8-16)

Budget Examples

Small garden (4 zones):

ESP32: $12
4x 12V valve: $96
8-ch relay module: $8
4x moisture sensor: $32
Power supply: $16
Enclosure, cables: $20
Piping and fittings: $60
Total: ~$244

Medium garden (8 zones):

Raspberry Pi: $80
8x 24V valve: $320
Expander + relays: $32
8x sensors: $64
24V transformer: $32
Flow meter: $32
Installation materials: $80
Total: ~$640

System Planning

1. Zone Division

By plant type:

  • Zone 1: Lawn (daily short watering)
  • Zone 2: Flower beds (2-3x weekly longer)
  • Zone 3: Shrubs (1x weekly heavy)
  • Zone 4: Greenhouse (frequent light watering)

By sun exposure:

  • South side - more frequent watering
  • North/shade - less frequent

2. Flow Calculation

Example calculation:
- Water pressure: 43 PSI
- Tap flow rate: 4 GPM
- Sprinkler: 0.5 GPM
- Max sprinklers/zone: 7

Drippers:
- Dripper: 0.5-1 GPH
- Per zone: up to 50 drippers

3. Water Distribution

Main supply pipe:

  • PE 25-32mm (1-1.25") or 1"
  • Bury 12-16 inches (frost protection)
  • Slope for drainage

Distribution to plants:

  • PE 16-20mm (5/8-3/4")
  • Or micro tubing 4-6mm (1/4")
  • Stakes for securing

Electronics Assembly

ESP32 Solution (Recommended)

Required Components:

- ESP32 DevKit
- 8-channel 5V relay
- 5V/3A power supply
- Terminal blocks
- IP65 enclosure
- Connection wires

Wiring:

ESP32 -> Relay module:
GPIO23 -> IN1 (Zone 1)
GPIO22 -> IN2 (Zone 2)
GPIO21 -> IN3 (Zone 3)
GPIO19 -> IN4 (Zone 4)
VIN -> VCC
GND -> GND

Moisture sensors:
GPIO34 -> Sensor 1 (analog)
GPIO35 -> Sensor 2 (analog)
GPIO32 -> Sensor 3 (analog)
GPIO33 -> Sensor 4 (analog)

Valves:
Relay NO -> Valve (+)
12V Supply -> Valve (-)

ESP32 Code

#include <WiFi.h>
#include <WebServer.h>
#include <ArduinoJson.h>

// WiFi credentials
const char* ssid = "YourWiFi";
const char* password = "YourPassword";

// Pins
const int zone_pins[] = {23, 22, 21, 19};
const int moisture_pins[] = {34, 35, 32, 33};
const int num_zones = 4;

// Settings
int moisture_threshold[] = {30, 40, 35, 45}; // %
int watering_duration[] = {300, 600, 900, 300}; // seconds

WebServer server(80);

void setup() {
  Serial.begin(115200);
  
  // Setup pins
  for (int i = 0; i < num_zones; i++) {
    pinMode(zone_pins[i], OUTPUT);
    digitalWrite(zone_pins[i], LOW);
  }
  
  // WiFi connection
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  
  Serial.println("Connected! IP: " + WiFi.localIP().toString());
  
  // Web server endpoints
  server.on("/", handleRoot);
  server.on("/status", handleStatus);
  server.on("/water", handleWater);
  server.on("/settings", handleSettings);
  
  server.begin();
}

void loop() {
  server.handleClient();
  
  // Check moisture every 30 minutes
  static unsigned long lastCheck = 0;
  if (millis() - lastCheck > 1800000) { // 30 min
    checkMoistureAndWater();
    lastCheck = millis();
  }
}

void checkMoistureAndWater() {
  for (int i = 0; i < num_zones; i++) {
    int moisture = readMoisture(i);
    
    if (moisture < moisture_threshold[i]) {
      Serial.println("Zone " + String(i+1) + " dry (" + 
                     String(moisture) + "%) - watering");
      waterZone(i, watering_duration[i]);
    }
  }
}

int readMoisture(int zone) {
  int raw = analogRead(moisture_pins[zone]);
  // Convert to percentage (calibrate according to sensor)
  // Dry = 4095, Wet = 1000
  int percent = map(raw, 4095, 1000, 0, 100);
  return constrain(percent, 0, 100);
}

void waterZone(int zone, int duration) {
  digitalWrite(zone_pins[zone], HIGH);
  delay(duration * 1000);
  digitalWrite(zone_pins[zone], LOW);
}

void handleRoot() {
  String html = R"(
<!DOCTYPE html>
<html>
<head>
  <title>Smart Irrigation</title>
  <meta name='viewport' content='width=device-width, initial-scale=1'>
  <style>
    body { font-family: Arial; margin: 20px; }
    .zone { border: 1px solid #ddd; padding: 15px; margin: 10px 0; }
    button { padding: 10px 20px; margin: 5px; }
    .moisture { font-weight: bold; }
  </style>
</head>
<body>
  <h1>Smart Irrigation</h1>
  <div id='zones'></div>
  
  <script>
    function updateStatus() {
      fetch('/status')
        .then(r => r.json())
        .then(data => {
          let html = '';
          data.zones.forEach((zone, i) => {
            html += `
              <div class='zone'>
                <h3>Zone ${i+1}</h3>
                <p>Moisture: <span class='moisture'>${zone.moisture}%</span></p>
                <p>Threshold: ${zone.threshold}%</p>
                <p>Status: ${zone.active ? 'Watering' : 'Off'}</p>
                <button onclick='waterZone(${i})'>Water (${zone.duration}s)</button>
              </div>
            `;
          });
          document.getElementById('zones').innerHTML = html;
        });
    }
    
    function waterZone(zone) {
      fetch(`/water?zone=${zone}`)
        .then(() => updateStatus());
    }
    
    setInterval(updateStatus, 5000);
    updateStatus();
  </script>
</body>
</html>
  )";
  
  server.send(200, "text/html", html);
}

void handleStatus() {
  StaticJsonDocument<512> doc;
  JsonArray zones = doc.createNestedArray("zones");
  
  for (int i = 0; i < num_zones; i++) {
    JsonObject zone = zones.createNestedObject();
    zone["moisture"] = readMoisture(i);
    zone["threshold"] = moisture_threshold[i];
    zone["duration"] = watering_duration[i];
    zone["active"] = digitalRead(zone_pins[i]);
  }
  
  String response;
  serializeJson(doc, response);
  server.send(200, "application/json", response);
}

void handleWater() {
  if (server.hasArg("zone")) {
    int zone = server.arg("zone").toInt();
    if (zone >= 0 && zone < num_zones) {
      waterZone(zone, watering_duration[zone]);
      server.send(200, "text/plain", "OK");
      return;
    }
  }
  server.send(400, "text/plain", "Bad Request");
}

Weather Integration

OpenWeatherMap API

#include <HTTPClient.h>

const char* api_key = "YOUR_API_KEY";
const char* city = "Prague,CZ";

bool willRainSoon() {
  HTTPClient http;
  String url = "http://api.openweathermap.org/data/2.5/forecast?q=" + 
               String(city) + "&appid=" + String(api_key);
  
  http.begin(url);
  int httpCode = http.GET();
  
  if (httpCode == 200) {
    String payload = http.getString();
    StaticJsonDocument<2048> doc;
    deserializeJson(doc, payload);
    
    // Check forecast for next 6 hours
    for (int i = 0; i < 2; i++) {
      String weather = doc["list"][i]["weather"][0]["main"];
      if (weather == "Rain") {
        return true;
      }
    }
  }
  
  http.end();
  return false;
}

// In checkMoistureAndWater add:
if (willRainSoon()) {
  Serial.println("Rain expected, skipping irrigation");
  return;
}

Home Assistant Integration

ESPHome Configuration

esphome:
  name: smart-irrigation
  platform: ESP32
  board: esp32dev

wifi:
  ssid: "YourWiFi"
  password: "YourPassword"

api:
  password: "api-password"

ota:
  password: "ota-password"

# Moisture sensors
sensor:
  - platform: adc
    pin: GPIO34
    name: "Zone 1 Moisture"
    unit_of_measurement: "%"
    filters:
      - calibrate_linear:
          - 2.8 -> 0.0
          - 1.1 -> 100.0
    update_interval: 30min
    
  - platform: adc
    pin: GPIO35
    name: "Zone 2 Moisture"
    # ... similar for other zones

# Valve control
switch:
  - platform: gpio
    pin: GPIO23
    name: "Irrigation Zone 1"
    id: zone1
    icon: "mdi:water"
    
  - platform: gpio
    pin: GPIO22
    name: "Irrigation Zone 2"
    id: zone2
    icon: "mdi:water"
    # ... other zones

# Automation
interval:
  - interval: 30min
    then:
      - if:
          condition:
            sensor.in_range:
              id: moisture_zone1
              below: 30.0
          then:
            - switch.turn_on: zone1
            - delay: 5min
            - switch.turn_off: zone1

Home Assistant Automations

automation:
  - alias: "Morning Watering"
    trigger:
      - platform: time
        at: "06:00:00"
    condition:
      - condition: numeric_state
        entity_id: sensor.moisture_zone1
        below: 40
      - condition: numeric_state
        entity_id: weather.home
        attribute: precipitation_probability
        below: 30
    action:
      - service: switch.turn_on
        entity_id: switch.irrigation_zone_1
      - delay: "00:05:00"
      - service: switch.turn_off
        entity_id: switch.irrigation_zone_1
        
  - alias: "Low Moisture Alert"
    trigger:
      - platform: numeric_state
        entity_id: sensor.moisture_zone2
        below: 25
        for: "01:00:00"
    action:
      - service: notify.mobile_app_phone
        data:
          title: "Garden Dried Out!"
          message: "Zone 2 moisture level is critically low"

Mechanical Installation

Valve Installation

  1. Valve box placement

    • Plastic box 12"x16"
    • Drainage at bottom (gravel)
    • Access for maintenance
  2. Valve connection

    Main supply → Main shutoff → Filter → 
    → Manifold → Valve 1 → Zone 1
               → Valve 2 → Zone 2
               → etc.
    
  3. Electrical connection

    • 14 AWG cable in conduit
    • Waterproof connectors
    • Cable labeling

Water Distribution

Main branches:

  1. Dig 12-16 inches deep
  2. Sand bed
  3. Lay PE tubing
  4. Sand backfill
  5. Warning tape
  6. Backfill

Drip irrigation:

Dripper spacing:
- Vegetables: 12"
- Shrubs: 20-40"
- Trees: around trunk

Securing:
- Plastic stakes every 3 feet
- Loop at drippers

Calibration and Tuning

Moisture Sensor Calibration

  1. Dry soil: Measure value
  2. Wet soil: Wait 30 min, measure
  3. Ideal moisture: According to plant type (30-60%)

Watering Time Optimization

Testing:
1. Water zone for 5 minutes
2. Wait 1 hour
3. Check penetration depth
4. Adjust time as needed

Target depth:
- Lawn: 4-6"
- Flower beds: 8-12"
- Shrubs/trees: 16-24"

System Maintenance

Seasonal Maintenance

Spring:

  • Check joint tightness
  • Clean filters
  • Test all zones
  • Calibrate sensors

Fall:

  • Drain system
  • Air blow-out
  • Remove valves (or antifreeze)
  • Winterize electronics

Regular Maintenance

  • Weekly: Visual inspection
  • Monthly: Clean drippers
  • Seasonally: Replace filters
  • Yearly: Check wiring

Advanced Features

Water Consumption Monitoring

// Flow meter on GPIO25
volatile int pulseCount = 0;
float flowRate = 0;
float totalLiters = 0;

void IRAM_ATTR pulseCounter() {
  pulseCount++;
}

void setupFlowMeter() {
  pinMode(25, INPUT_PULLUP);
  attachInterrupt(25, pulseCounter, FALLING);
}

void calculateFlow() {
  // YF-S201: 450 pulses = 1 liter
  flowRate = pulseCount / 450.0 * 60; // L/min
  totalLiters += pulseCount / 450.0;
  pulseCount = 0;
}

Leak Detection

automation:
  - alias: "Water Leak Detection"
    trigger:
      - platform: numeric_state
        entity_id: sensor.flow_rate
        above: 0.5
        for: "00:30:00"
    condition:
      - condition: state
        entity_id: group.all_zones
        state: "off"
    action:
      - service: notify.mobile_app_phone
        data:
          title: "WARNING - Water Leak!"
          message: "Flow detected with no active zone!"

Troubleshooting

Valve Won't Open

  1. Check voltage (multimeter)
  2. Manual valve test
  3. Clean diaphragm
  4. Check solenoid coil

Uneven Watering

  • Check water pressure
  • Clean clogged nozzles
  • Add pressure regulator
  • Split into more zones

Moisture Sensor Not Working

  • Clean electrodes
  • Check depth
  • Replace with capacitive type
  • Add corrosion protection

Cost Comparison with Commercial Systems

SolutionCostFeaturesMaintenance
DIY ESP32$240-600Full controlSelf
Gardena Smart$800-1600LimitedMedium
Rain Bird$1200-2400ProfessionalService
Hunter Hydrawise$1000-2000CloudService

Conclusion

Building your own smart irrigation system isn't complex and provides complete control over your garden. Start simple with a few zones and gradually expand. The investment pays back in water savings and healthier plants.

Tip: Start with one test zone and once you verify functionality, expand to the entire garden.

Related Articles