#include <WiFi.h>
#include <DHT.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <LiquidCrystal_I2C.h>
#include <ArduinoJson.h>
// ======== AgroSense Smart Farming System ========
// Multi-Sensor Node for Crop Monitoring
// Pin Definitions
#define DHT_PIN 4 // Air temp/humidity
#define SOIL_TEMP_PIN 2 // Soil temperature
#define LIGHT_PIN A0 // Light sensor
#define SOIL_MOISTURE_X A6 // Simulated as joystick X
#define SOIL_PH_Y A7 // Simulated as joystick Y
#define BUZZER_PIN 25 // Alert buzzer
#define LED_HEALTHY 18 // Green LED
#define LED_ALERT 19 // Red LED
#define LED_STATUS 5 // Blue LED
// Sensor Objects
DHT dht(DHT_PIN, DHT22);
OneWire oneWire(SOIL_TEMP_PIN);
DallasTemperature soilTemp(&oneWire);
// LCD with correct I2C address for Wokwi
LiquidCrystal_I2C lcd(0x27, 20, 4);
// Crop Configuration
struct CropProfile {
String name;
float optimalSoilMoisture;
float minSoilMoisture;
float maxSoilMoisture;
float optimalPH;
float minPH;
float maxPH;
float optimalTemp;
int minLightLevel;
};
// Predefined crop profiles
CropProfile crops[] = {
{"Tomato", 65.0, 50.0, 80.0, 6.2, 5.5, 7.0, 24.0, 300},
{"Lettuce", 75.0, 60.0, 85.0, 6.5, 6.0, 7.5, 18.0, 200},
{"Pepper", 60.0, 45.0, 75.0, 6.8, 6.0, 7.5, 26.0, 350},
{"Carrot", 55.0, 40.0, 70.0, 6.5, 6.0, 7.0, 20.0, 250}
};
int currentCropIndex = 0;
unsigned long lastSensorRead = 0;
unsigned long lastDisplayUpdate = 0;
unsigned long alertStartTime = 0;
bool alertActive = false;
int displayPage = 0;
// Sensor readings
float airTemp, airHumidity, soilTemperature;
float soilMoisture, soilPH, lightLevel;
int healthScore = 100;
// WiFi credentials (for simulation)
const char* ssid = "AgroSense_Network";
const char* password = "farming2025";
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("=== AgroSense Smart Farming System Starting ===");
// Initialize I2C LCD first
Wire.begin(21, 22); // SDA=21, SCL=22 for ESP32
lcd.init();
lcd.backlight();
lcd.clear();
// Test LCD immediately
lcd.setCursor(0, 0);
lcd.print("AgroSense v1.0");
lcd.setCursor(0, 1);
lcd.print("System Starting...");
Serial.println("LCD Initialized...");
// Initialize sensors
dht.begin();
soilTemp.begin();
// Initialize pins
pinMode(LED_HEALTHY, OUTPUT);
pinMode(LED_ALERT, OUTPUT);
pinMode(LED_STATUS, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
// Startup sequence with LED test
digitalWrite(LED_STATUS, HIGH);
delay(500);
digitalWrite(LED_HEALTHY, HIGH);
delay(500);
digitalWrite(LED_ALERT, HIGH);
delay(500);
digitalWrite(LED_ALERT, LOW);
digitalWrite(LED_HEALTHY, LOW);
// Update LCD
lcd.setCursor(0, 2);
lcd.print("Sensors: OK");
delay(1000);
// WiFi simulation
lcd.setCursor(0, 3);
lcd.print("WiFi: Connecting");
delay(1500);
lcd.setCursor(0, 3);
lcd.print("WiFi: Connected ");
Serial.println("Current Crop: " + crops[currentCropIndex].name);
delay(2000);
lcd.clear();
// Show initial crop info
displayCropInfo();
delay(2000);
}
void loop() {
// Read sensors every 2 seconds
if (millis() - lastSensorRead >= 2000) {
readAllSensors();
calculateHealthScore();
checkAlerts();
sendDataToCloud();
lastSensorRead = millis();
}
// Update display every 4 seconds
if (millis() - lastDisplayUpdate >= 4000) {
updateDisplay();
lastDisplayUpdate = millis();
}
// Handle alerts
if (alertActive && millis() - alertStartTime < 3000) {
// Blink alert LED
digitalWrite(LED_ALERT, millis() % 500 < 250);
// Beep occasionally
if (millis() % 1000 < 100) {
tone(BUZZER_PIN, 1000, 100);
}
} else if (alertActive) {
alertActive = false;
digitalWrite(LED_ALERT, LOW);
noTone(BUZZER_PIN);
}
// Crop switching simulation (every 25 seconds for demo)
if (millis() % 25000 < 100) {
currentCropIndex = (currentCropIndex + 1) % 4;
Serial.println("Switched to crop: " + crops[currentCropIndex].name);
displayCropInfo();
delay(2000);
}
// Status LED heartbeat
digitalWrite(LED_STATUS, millis() % 1000 < 50);
delay(100);
}
void readAllSensors() {
// Air temperature and humidity
airTemp = dht.readTemperature();
airHumidity = dht.readHumidity();
// Soil temperature
soilTemp.requestTemperatures();
soilTemperature = soilTemp.getTempCByIndex(0);
// Light level (0-1023, converted to approximate lux)
int lightRaw = analogRead(LIGHT_PIN);
lightLevel = map(lightRaw, 0, 1023, 0, 1000);
// Soil moisture (simulated with joystick X)
int moistureRaw = analogRead(SOIL_MOISTURE_X);
soilMoisture = map(moistureRaw, 0, 1023, 0, 100);
// Soil pH (simulated with joystick Y)
int phRaw = analogRead(SOIL_PH_Y);
soilPH = map(phRaw, 0, 1023, 40, 90) / 10.0; // 4.0 to 9.0 pH
// Handle sensor errors
if (isnan(airTemp) || isnan(airHumidity)) {
airTemp = 25.0;
airHumidity = 60.0;
}
if (soilTemperature == DEVICE_DISCONNECTED_C) {
soilTemperature = 22.0;
}
// Debug output
if (millis() % 5000 < 100) {
Serial.println("Sensors - Temp:" + String(airTemp) + " Humidity:" + String(airHumidity) +
" Soil:" + String(soilMoisture) + "% pH:" + String(soilPH));
}
}
void calculateHealthScore() {
CropProfile& crop = crops[currentCropIndex];
int score = 100;
// Soil moisture scoring (highest priority)
if (soilMoisture < crop.minSoilMoisture) {
score -= 30;
} else if (soilMoisture > crop.maxSoilMoisture) {
score -= 25;
}
// pH scoring
if (soilPH < crop.minPH || soilPH > crop.maxPH) {
score -= 20;
}
// Temperature scoring
if (abs(airTemp - crop.optimalTemp) > 5) {
score -= 15;
}
// Light scoring
if (lightLevel < crop.minLightLevel) {
score -= 20;
}
// Humidity scoring (general range)
if (airHumidity < 40 || airHumidity > 80) {
score -= 10;
}
healthScore = max(0, score);
// LED indicators based on health score
if (healthScore >= 80) {
digitalWrite(LED_HEALTHY, HIGH);
digitalWrite(LED_ALERT, LOW);
} else if (healthScore >= 60) {
digitalWrite(LED_HEALTHY, millis() % 1000 < 500); // Slow blink
digitalWrite(LED_ALERT, LOW);
} else {
digitalWrite(LED_HEALTHY, LOW);
// LED_ALERT handled in alert system
}
}
void checkAlerts() {
CropProfile& crop = crops[currentCropIndex];
bool needsAlert = false;
String alertMsg = "";
if (soilMoisture < crop.minSoilMoisture) {
needsAlert = true;
alertMsg = "IRRIGATION NEEDED";
} else if (soilMoisture > crop.maxSoilMoisture) {
needsAlert = true;
alertMsg = "OVERWATERING RISK";
} else if (soilPH < crop.minPH) {
needsAlert = true;
alertMsg = "pH TOO LOW";
} else if (soilPH > crop.maxPH) {
needsAlert = true;
alertMsg = "pH TOO HIGH";
} else if (lightLevel < crop.minLightLevel) {
needsAlert = true;
alertMsg = "LIGHT INSUFFICIENT";
}
if (needsAlert && !alertActive) {
alertActive = true;
alertStartTime = millis();
Serial.println("🚨 ALERT: " + alertMsg + " for " + crop.name);
}
}
void updateDisplay() {
lcd.clear();
switch(displayPage) {
case 0: // Crop and health overview
lcd.setCursor(0, 0);
lcd.print("Crop: " + crops[currentCropIndex].name);
lcd.setCursor(0, 1);
lcd.print("Health Score: " + String(healthScore) + "%");
lcd.setCursor(0, 2);
lcd.print("Soil: " + String(soilMoisture, 1) + "% pH: " + String(soilPH, 1));
lcd.setCursor(0, 3);
if (alertActive) {
lcd.print(">>> ALERT ACTIVE <<<");
} else {
lcd.print("Status: Normal");
}
break;
case 1: // Environmental conditions
lcd.setCursor(0, 0);
lcd.print("Environmental Data");
lcd.setCursor(0, 1);
lcd.print("Air: " + String(airTemp, 1) + "C RH:" + String(airHumidity, 1) + "%");
lcd.setCursor(0, 2);
lcd.print("Soil Temp: " + String(soilTemperature, 1) + "C");
lcd.setCursor(0, 3);
lcd.print("Light: " + String((int)lightLevel) + " lux");
break;
case 2: // AI Recommendations
lcd.setCursor(0, 0);
lcd.print("AI Recommendations");
lcd.setCursor(0, 1);
if (healthScore >= 85) {
lcd.print("Excellent conditions");
lcd.setCursor(0, 2);
lcd.print("Continue monitoring");
} else if (soilMoisture < crops[currentCropIndex].minSoilMoisture) {
lcd.print("Water immediately");
lcd.setCursor(0, 2);
lcd.print("Target: " + String(crops[currentCropIndex].optimalSoilMoisture, 0) + "% moisture");
} else if (soilPH < crops[currentCropIndex].minPH) {
lcd.print("Add lime to soil");
lcd.setCursor(0, 2);
lcd.print("Target pH: " + String(crops[currentCropIndex].optimalPH, 1));
} else if (lightLevel < crops[currentCropIndex].minLightLevel) {
lcd.print("Increase lighting");
lcd.setCursor(0, 2);
lcd.print("Need: " + String(crops[currentCropIndex].minLightLevel) + "+ lux");
} else {
lcd.print("Monitor conditions");
lcd.setCursor(0, 2);
lcd.print("Adjust as needed");
}
lcd.setCursor(0, 3);
lcd.print("Score: " + String(healthScore) + "% Health");
break;
}
displayPage = (displayPage + 1) % 3;
}
void displayCropInfo() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("=== CROP SELECTED ===");
lcd.setCursor(0, 1);
lcd.print("Type: " + crops[currentCropIndex].name);
lcd.setCursor(0, 2);
lcd.print("Optimal: " + String(crops[currentCropIndex].optimalSoilMoisture, 0) + "% moist");
lcd.setCursor(0, 3);
lcd.print("pH: " + String(crops[currentCropIndex].optimalPH, 1) + " Temp: " + String(crops[currentCropIndex].optimalTemp, 0) + "C");
}
void sendDataToCloud() {
// Simulate sending data to cloud/server
DynamicJsonDocument doc(1024);
doc["deviceId"] = "AgroSense_Node_001";
doc["cropType"] = crops[currentCropIndex].name;
doc["timestamp"] = millis();
doc["location"] = "Zone_A";
JsonObject sensors = doc.createNestedObject("sensors");
sensors["airTemp"] = round(airTemp * 10) / 10.0;
sensors["airHumidity"] = round(airHumidity * 10) / 10.0;
sensors["soilTemp"] = round(soilTemperature * 10) / 10.0;
sensors["soilMoisture"] = round(soilMoisture * 10) / 10.0;
sensors["soilPH"] = round(soilPH * 100) / 100.0;
sensors["lightLevel"] = (int)lightLevel;
doc["healthScore"] = healthScore;
doc["alertActive"] = alertActive;
String jsonString;
serializeJson(doc, jsonString);
// Only print occasionally to avoid spam
if (millis() % 10000 < 100) {
Serial.println("📊 Cloud Data: " + jsonString);
}
}