Smart Garden Irrigation System - Complete DIY Guide

Transform Your Home Into a Smart Paradise - 2025 Guide
Discover the latest smart home technology that will revolutionize your daily routine.
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
- Different zones - each garden area 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
Solenoid valves:
- 24V AC valves ($32-60/piece)
- 12V DC valves ($24-40/piece)
- Diameter by flow rate (3/4" or 1")
Piping:
- PE hose 16-25mm
- Connectors, elbows, T-joints
- Drippers or sprinklers
3. Power Supply
- 24V AC transformer ($20-32)
- Or 12V DC supply + relay board
4. Sensors
- Soil moisture ($6-12/piece)
- Rain sensor ($8-16)
- Water flow ($20-40)
- Temperature/humidity ($8-16)
Example Budget
Small garden (4 zones):
ESP32: $12
4x 12V valve: $96
8-channel relay module: $8
4x moisture sensor: $32
Power supply: $16
Box, 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)
- Zone 2: Flower beds (2-3x weekly longer)
- Zone 3: Shrubs (1x weekly heavy)
- Zone 4: Greenhouse (frequent light)
By sun exposure:
- South side - more frequent watering
- North/shade - less frequent
2. Flow Calculation
Example calculation:
- Water pressure: 3 bars
- Faucet flow: 15 l/min
- Sprinkler: 2 l/min
- Max sprinklers/zone: 7
Drippers:
- Dripper: 2-4 l/hour
- Per zone: up to 50 drippers
3. Water Distribution
Main supply piping:
- PE 25-32mm or 1"
- Bury 30-40cm (frost protection)
- Slope for drainage
Distribution to plants:
- PE 16-20mm
- Or micro tubing 4-6mm
- Stake anchoring
Electronics Assembly
ESP32 Solution (Recommended)
Required Components:
- ESP32 DevKit
- 8-channel 5V relay
- 5V/3A power supply
- Terminal blocks
- IP65 enclosure
- Connection cables
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 per 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 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;
}
// Add to checkMoistureAndWater:
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 controls
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"
# ... additional 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: zone1Home Assistant Automation
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 is dry!"
message: "Zone 2 moisture is only {{ states('sensor.moisture_zone2') }}%"Mechanical Installation
Valve Installation
Valve box placement
- Plastic box 30x40cm
- Drainage at bottom (gravel)
- Access for maintenance
Valve connections
Main supply → Main shutoff → Filter → → Manifold → Valve 1 → Zone 1 → Valve 2 → Zone 2 → etc.Electrical connections
- CYKY 3x1.5 cable in conduit
- Waterproof connectors
- Cable labeling
Water Distribution
Main branches:
- Dig 30-40cm deep
- Sand bed
- Place PE pipe
- Cover with sand
- Warning tape
- Backfill
Drip lines:
Dripper spacing:
- Vegetables: 30cm
- Shrubs: 50-100cm
- Trees: around trunk
Anchoring:
- Plastic stakes every 1m
- Loop at drippers
Calibration and Tuning
Moisture Sensor Calibration
- Dry soil: Measure value
- Watered soil: Wait 30 min, measure
- Ideal moisture: Per 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: 10-15cm
- Beds: 20-30cm
- Shrubs/trees: 40-60cm
System Maintenance
Seasonal Maintenance
Spring:
- Check joint tightness
- Clean filters
- Test all zones
- Calibrate sensors
Fall:
- Drain system
- Blow out with air
- Remove valves (or use glycerin)
- Winterize electronics
Regular Maintenance
- Weekly: Visual inspection
- Monthly: Clean drippers
- Seasonal: Replace filters
- Yearly: Check wiring
Advanced Features
Water Usage 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
- Check voltage (multimeter)
- Manual valve test
- Clean diaphragm
- 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
Price Comparison with Commercial Systems
| Solution | Price | Features | Maintenance |
|---|---|---|---|
| DIY ESP32 | $240-600 | Full control | Self |
| Gardena Smart | $800-1600 | Limited | Medium |
| Rain Bird | $1200-2400 | Professional | Service |
| Hunter Hydrawise | $1000-2000 | Cloud | Service |
Conclusion
Building your own smart irrigation system isn't complex and provides full 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 whole garden.
Related Articles
Smart Home Upgrade Package 2025
Transform your home with the latest smart technology
Transform Your Home Into a Smart Paradise - 2025 Guide
Discover the latest smart home technology that will revolutionize your daily routine.

