OC
OceanRemote
Low-code IoT platform
← Back to Course

Complete Smart Irrigation System

Complete Smart Irrigation System

šŸ’§ Complete Smart Irrigation System - Fully Automated Farm Watering

šŸ’§ What You'll Learn:

  • šŸ¤– Build a complete automated irrigation system with ESP32
  • šŸŒ§ļø Integrate rain sensors and weather forecasts to skip unnecessary watering
  • šŸ“… Program time-based schedules (6 AM and 6 PM, Monday-Friday)
  • šŸ’§ Combine soil moisture, rain detection, and scheduling for smart decisions

A complete smart irrigation system combines multiple data sources to decide exactly when and how much to water. This system uses soil moisture, rain detection, weather forecast, and time-based scheduling to ensure your crops get the right amount of water at the right time - automatically.

šŸ”§ Hardware Components

  • ESP32 board ($6-8): The brain of the system
  • Capacitive soil moisture sensor ($8-12): Measures water content at root depth
  • 4-channel relay module ($6-8): Controls water pumps or solenoid valves
  • Rain sensor ($3): Detects rainfall to skip irrigation
  • Power supply: 12V/2A for pumps + 5V/2A for ESP32 (or solar + battery)
  • Water pumps or solenoid valves ($10-30): Controls water flow to irrigation lines

šŸ“Š System Decision Logic

šŸ”€ Irrigation Decision Flow:

Check Rain → Check Weather Forecast → Check Soil Moisture → Check Schedule → Decide to Water

  • Priority 1 - Rain: If raining OR forecast > 5mm → SKIP irrigation
  • Priority 2 - Soil Moisture: If moisture < 30% → WATER even if off-schedule
  • Priority 2 - Soil Wet: If moisture > 70% → SKIP even if scheduled
  • Priority 3 - Schedule: Water only during scheduled times (6 AM and 6 PM, Mon-Fri)
šŸ’” Why Combine Multiple Factors?

Schedule alone wastes water if it rained. Soil moisture alone might water at the wrong time (noon = evaporation). Combining all three gives you optimal efficiency - water only when crops need it, at the best time of day (morning/evening), and never waste water before rain.

šŸ“– Complete Smart Irrigation Code

/*
 * Complete Smart Irrigation System
 * Combines soil moisture, rain detection, weather forecast, and time-based scheduling
 */

#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <time.h>

// ========== WIFI CONFIGURATION ==========
const char* ssid = "YOUR_WIFI";
const char* password = "YOUR_PASSWORD";

// ========== PIN DEFINITIONS ==========
#define SOIL_PIN 32           // Soil moisture sensor (capacitive)
#define RAIN_PIN 33           // Rain sensor (LOW = rain)
#define PUMP_RELAY 4          // Main pump relay
#define ZONE1_RELAY 5         // Zone 1 control
#define ZONE2_RELAY 18        // Zone 2 control

// ========== SOIL CALIBRATION ==========
const int DRY_VALUE = 3800;
const int WET_VALUE = 1500;

// ========== IRRIGATION SCHEDULE ==========
int wateringTimes[] = {6, 18};        // 6 AM and 6 PM
int wateringDays[] = {1,2,3,4,5};     // Monday=1, Tuesday=2, ..., Friday=5
int wateringDuration = 600;            // 10 minutes per zone

// ========== WEATHER API ==========
const char* apiKey = "YOUR_OPENWEATHER_API_KEY";
const char* city = "Nairobi";

struct WeatherData {
    float temp;
    float rain;        // mm of rain forecast
    bool isRaining;
};

// ========== READ SOIL MOISTURE ==========
int readSoilMoisture() {
    int raw = analogRead(SOIL_PIN);
    int moisture = map(raw, DRY_VALUE, WET_VALUE, 0, 100);
    moisture = constrain(moisture, 0, 100);
    return moisture;
}

// ========== GET WEATHER FORECAST ==========
WeatherData getWeatherForecast() {
    WeatherData w = {0, 0, false};
    
    if (WiFi.status() != WL_CONNECTED) return w;
    
    HTTPClient http;
    String url = "http://api.openweathermap.org/data/2.5/weather?q=" + String(city) + "&appid=" + apiKey + "&units=metric";
    http.begin(url);
    int httpCode = http.GET();
    
    if (httpCode == 200) {
        String payload = http.getString();
        DynamicJsonDocument doc(2048);
        deserializeJson(doc, payload);
        
        w.temp = doc["main"]["temp"];
        if (doc.containsKey("rain")) {
            w.rain = doc["rain"]["1h"] | 0;
        }
    }
    http.end();
    
    // Check real-time rain sensor
    w.isRaining = (digitalRead(RAIN_PIN) == LOW);
    
    return w;
}

// ========== CHECK IF SHOULD WATER ==========
bool shouldWater(int moisture, WeatherData weather) {
    // Priority 1: Actively raining
    if (weather.isRaining) {
        Serial.println("šŸŒ§ļø Rain detected - skipping irrigation");
        return false;
    }
    
    // Priority 2: Heavy rain forecast
    if (weather.rain > 5) {
        Serial.printf("šŸŒ§ļø %.1fmm rain forecast - skipping irrigation\n", weather.rain);
        return false;
    }
    
    // Priority 3: Emergency - soil critically dry
    if (moisture < 25) {
        Serial.println("🚨 EMERGENCY: Soil critically dry! Watering now.");
        return true;
    }
    
    // Priority 4: Soil too wet
    if (moisture > 75) {
        Serial.println("āœ… Soil too wet - skipping irrigation");
        return false;
    }
    
    // Priority 5: Check schedule
    time_t now = time(nullptr);
    struct tm *timeinfo = localtime(&now);
    int currentHour = timeinfo->tm_hour;
    int currentDay = timeinfo->tm_wday;  // 0=Sunday, 1=Monday...
    
    // Convert to Monday=1 format
    int weekday = (currentDay == 0) ? 7 : currentDay;
    
    // Check if watering day
    bool isWateringDay = false;
    for (int d : wateringDays) {
        if (weekday == d) {
            isWateringDay = true;
            break;
        }
    }
    
    // Check if watering time (within 2 hours of scheduled time)
    bool isWateringTime = false;
    for (int t : wateringTimes) {
        if (abs(currentHour - t) <= 1) {
            isWateringTime = true;
            break;
        }
    }
    
    if (isWateringDay && isWateringTime) {
        Serial.printf("āœ… Scheduled watering - Hour: %d, Weekday: %d\n", currentHour, weekday);
        return true;
    }
    
    // Soil drying but not scheduled - wait for next window
    if (moisture < 35) {
        Serial.printf("āš ļø Soil drying ( %d%%) but outside watering hours - waiting\n", moisture);
    }
    
    return false;
}

// ========== CALCULATE DURATION ==========
int calculateDuration(int moisture) {
    // Base duration: 10 minutes
    int duration = wateringDuration;
    
    // Adjust for dryness: more dry = more water
    if (moisture < 30) {
        duration = duration * 1.5;  // 15 minutes for dry soil
    } else if (moisture < 40) {
        duration = duration * 1.2;  // 12 minutes for slightly dry
    } else if (moisture > 60) {
        duration = duration * 0.7;  // 7 minutes for wet soil
    }
    
    return constrain(duration, 300, 1200);  // 5-20 minutes max
}

// ========== WATER A ZONE ==========
void waterZone(int relayPin, const char* zoneName, int seconds) {
    Serial.printf("šŸ’§ Watering %s for %d seconds...\n", zoneName, seconds);
    digitalWrite(relayPin, LOW);   // Pump ON (active LOW)
    delay(seconds * 1000);
    digitalWrite(relayPin, HIGH);  // Pump OFF
    Serial.printf("āœ… %s complete\n", zoneName);
}

// ========== SEND DECISION TO CLOUD ==========
void logDecision(int moisture, bool watered, int duration, WeatherData weather) {
    if (WiFi.status() != WL_CONNECTED) return;
    
    HTTPClient http;
    http.begin("https://api.oceanremote.net/device/state");
    http.addHeader("Content-Type", "application/x-www-form-urlencoded");
    
    String data = "token=YOUR_TOKEN";
    data += "&moisture=" + String(moisture);
    data += "&watered=" + String(watered ? "YES" : "NO");
    data += "&duration=" + String(duration);
    data += "&raining=" + String(weather.isRaining ? "YES" : "NO");
    data += "&rain_forecast=" + String(weather.rain);
    
    http.POST(data);
    http.end();
}

// ========== SETUP ==========
void setup() {
    Serial.begin(115200);
    
    // Configure pins
    pinMode(SOIL_PIN, INPUT);
    pinMode(RAIN_PIN, INPUT_PULLUP);
    pinMode(PUMP_RELAY, OUTPUT);
    pinMode(ZONE1_RELAY, OUTPUT);
    pinMode(ZONE2_RELAY, OUTPUT);
    
    // Start with all pumps OFF
    digitalWrite(PUMP_RELAY, HIGH);
    digitalWrite(ZONE1_RELAY, HIGH);
    digitalWrite(ZONE2_RELAY, HIGH);
    
    // Connect to WiFi
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("\nāœ… WiFi connected!");
    
    // Sync time
    configTime(0, 0, "pool.ntp.org", "time.nist.gov");
    
    Serial.println("╔══════════════════════════════════════════════════════════════╗");
    Serial.println("ā•‘           šŸ’§ SMART IRRIGATION SYSTEM v2.0                    ā•‘");
    Serial.println("ā•‘           Soil + Rain + Weather + Schedule                  ā•‘");
    Serial.println("ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•\n");
}

// ========== MAIN LOOP ==========
void loop() {
    // Read sensors
    int moisture = readSoilMoisture();
    WeatherData weather = getWeatherForecast();
    
    // Display status
    Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    Serial.printf("šŸ’§ Soil moisture: %d%%\n", moisture);
    Serial.printf("šŸŒ§ļø Raining: %s\n", weather.isRaining ? "YES" : "NO");
    Serial.printf("šŸ“Š Rain forecast: %.1f mm\n", weather.rain);
    
    // Make decision
    bool water = shouldWater(moisture, weather);
    int duration = 0;
    
    if (water) {
        duration = calculateDuration(moisture);
        Serial.printf("šŸ’§ DECISION: WATER for %d seconds\n", duration);
        
        // Water zones
        waterZone(ZONE1_RELAY, "Zone 1 (Tomatoes)", duration);
        delay(2000);
        waterZone(ZONE2_RELAY, "Zone 2 (Peppers)", duration);
        
    } else {
        Serial.println("āœ… DECISION: SKIP irrigation");
    }
    
    // Log to cloud
    logDecision(moisture, water, duration, weather);
    
    Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
    
    // Wait 15 minutes before next check
    delay(900000);
}
    
šŸ“– Case Study - Tanzanian Farm Automates Irrigation:

A 5-hectare farm installed this complete smart irrigation system:

  • šŸ’§ Before: Manual irrigation, 2 hours daily, wasted water
  • šŸ¤– After: System waters only when needed (soil < 35%)
  • šŸŒ§ļø Rain integration: System skipped irrigation 12 times in rainy season
  • šŸ’° Savings: 40% water reduction = $150/month saved

"The system waters only when needed, and the rain detection saves us every time it rains." - Farm Manager, Tanzania

šŸŽÆ Key Takeaways:
  • āœ… Combine soil moisture, rain, weather forecast, and schedule for optimal results
  • āœ… Rain priority: If raining OR forecast > 5mm → skip irrigation
  • āœ… Emergency override: Moisture < 25% waters even off-schedule
  • āœ… Best watering times: Early morning (6 AM) and evening (6 PM) to reduce evaporation
  • āœ… Log all decisions to OceanRemote for monitoring and improvement
šŸ’” Key Takeaways:
  • Apply these concepts directly to your farm or project.
  • Take notes on important details for the quiz.
  • Use the button below to track your progress.