/*
Greenhouse IoT System - Core Driver
Platform: ESP32 (Wokwi Simulation)
Features:
- Auto Watering (Based on Soil Moisture & Water Tank Level)
- Auto Ventilation (Based on Air Temperature)
- Tank Level Display (LED Bar)
- Status Display (I2C LCD)
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <DHT.h>
// ================= Pin Definitions =================
#define PIN_DHT 15
#define PIN_TRIG 5
#define PIN_ECHO 18
#define PIN_RELAY_PUMP 4
#define PIN_SOIL 34 // Analog Input
#define PIN_STEP 19
#define PIN_DIR 23
// LED Bar Pins (Low Level to High Level)
const int ledPins[5] = {13, 12, 14, 27, 26};
// ================= Configuration =================
#define DHTTYPE DHT22
const float DIST_TANK_HEIGHT = 20.0; // cm (Tank depth)
const float DIST_EMPTY_THRESH = 15.0; // cm (Distance > 15cm means water is low)
// Thresholds
const int SOIL_DRY_THRESH = 30; // % (Start watering if < 30%)
const int SOIL_WET_THRESH = 70; // % (Stop watering if > 70%)
const float TEMP_HOT_THRESH = 30.0; // C (Open fan/window if > 30C)
const float TEMP_COOL_THRESH = 25.0; // C (Close fan/window if < 25C)
// ================= Global Objects =================
DHT dht(PIN_DHT, DHTTYPE);
LiquidCrystal_I2C lcd(0x27, 16, 2);
// ================= Variables =================
float airTemp = 0;
float airHum = 0;
int soilMoisturePercent = 0;
float waterLevelCm = 0; // Calculated water height
float distanceCm = 0; // Raw ultrasonic distance
bool isWatering = false;
bool isVentOpen = false;
bool tankEmpty = false;
// Timer variables (Non-blocking)
unsigned long lastSensorTime = 0;
unsigned long lastScreenTime = 0;
const long SENSOR_INTERVAL = 2000; // Read sensors every 2s
const long SCREEN_INTERVAL = 1000; // Update screen every 1s
// ================= Setup =================
void setup() {
Serial.begin(115200);
// Pin Modes
pinMode(PIN_TRIG, OUTPUT);
pinMode(PIN_ECHO, INPUT);
pinMode(PIN_RELAY_PUMP, OUTPUT);
pinMode(PIN_STEP, OUTPUT);
pinMode(PIN_DIR, OUTPUT);
digitalWrite(PIN_RELAY_PUMP, LOW); // Assume HIGH trigger or init state
for(int i=0; i<5; i++) {
pinMode(ledPins[i], OUTPUT);
digitalWrite(ledPins[i], LOW);
}
// Init Components
dht.begin();
lcd.init();
lcd.backlight();
lcd.setCursor(0,0);
lcd.print("Greenhouse Sys");
lcd.setCursor(0,1);
lcd.print("Initializing...");
delay(1500);
lcd.clear();
}
// ================= Main Loop =================
void loop() {
unsigned long currentMillis = millis();
// 1. Task: Read Sensors
if (currentMillis - lastSensorTime >= SENSOR_INTERVAL) {
lastSensorTime = currentMillis;
readSensors();
controlSystem(); // Run logic after reading
}
// 2. Task: Update LCD
if (currentMillis - lastScreenTime >= SCREEN_INTERVAL) {
lastScreenTime = currentMillis;
updateDisplay();
}
}
// ================= Functions =================
void readSensors() {
// --- DHT22 ---
airTemp = dht.readTemperature();
airHum = dht.readHumidity();
// --- Soil Moisture (Simulated) ---
// Potentiometer gives 0-4095. Map to 0-100%
int rawSoil = analogRead(PIN_SOIL);
soilMoisturePercent = map(rawSoil, 0, 4095, 0, 100);
// --- Ultrasonic (Water Level) ---
digitalWrite(PIN_TRIG, LOW);
delayMicroseconds(2);
digitalWrite(PIN_TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(PIN_TRIG, LOW);
long duration = pulseIn(PIN_ECHO, HIGH);
distanceCm = duration * 0.034 / 2;
// Calculate Water Height (Tank Height - Distance to water surface)
waterLevelCm = DIST_TANK_HEIGHT - distanceCm;
if (waterLevelCm < 0) waterLevelCm = 0;
// Safety Check
if (distanceCm >= DIST_EMPTY_THRESH) {
tankEmpty = true;
} else {
tankEmpty = false;
}
// Update LED Bar (Visual Water Level)
// Map water level (0 to 20cm) to 0 to 5 LEDs
int ledLevel = map((int)waterLevelCm, 0, (int)DIST_TANK_HEIGHT, 0, 5);
for(int i=0; i<5; i++) {
if (i < ledLevel) digitalWrite(ledPins[i], HIGH);
else digitalWrite(ledPins[i], LOW);
}
}
void controlSystem() {
// --- Watering Logic ---
// If Tank is Empty -> STOP PUMP immediately (Protection)
if (tankEmpty) {
digitalWrite(PIN_RELAY_PUMP, LOW); // OFF (Adjust if Relay is Active LOW)
isWatering = false;
}
else {
// Tank has water, check Soil
if (!isWatering && soilMoisturePercent < SOIL_DRY_THRESH) {
digitalWrite(PIN_RELAY_PUMP, HIGH); // Turn ON Pump
isWatering = true;
}
else if (isWatering && soilMoisturePercent > SOIL_WET_THRESH) {
digitalWrite(PIN_RELAY_PUMP, LOW); // Turn OFF Pump
isWatering = false;
}
}
// --- Ventilation Logic (Stepper) ---
// Simple logic: If hot, open window (move stepper). If cool, close.
// Note: A4988 needs pulses.
if (!isVentOpen && airTemp > TEMP_HOT_THRESH) {
// Open Window (Rotate CW)
moveStepper(true, 200); // 200 steps
isVentOpen = true;
}
else if (isVentOpen && airTemp < TEMP_COOL_THRESH) {
// Close Window (Rotate CCW)
moveStepper(false, 200); // 200 steps back
isVentOpen = false;
}
}
// Helper to move stepper (Blocking slightly, but short duration for demo)
// For best IoT performance, use AccelStepper non-blocking in future
void moveStepper(bool dir, int steps) {
digitalWrite(PIN_DIR, dir ? HIGH : LOW);
for(int i=0; i<steps; i++) {
digitalWrite(PIN_STEP, HIGH);
delayMicroseconds(1000); // Speed control
digitalWrite(PIN_STEP, LOW);
delayMicroseconds(1000);
}
}
void updateDisplay() {
// Rotate display info
static int page = 0;
page = !page; // Toggle 0 and 1
lcd.setCursor(0, 0);
if (page == 0) {
// Page 1: Environment
lcd.print("T:"); lcd.print((int)airTemp); lcd.print("C ");
lcd.print("H:"); lcd.print((int)airHum); lcd.print("% ");
lcd.setCursor(0, 1);
lcd.print("Soil:"); lcd.print(soilMoisturePercent); lcd.print("% ");
lcd.print(isWatering ? "P:ON " : "P:OFF");
} else {
// Page 2: System Status
lcd.print("Tank Lvl: ");
if(tankEmpty) lcd.print("EMPTY!");
else { lcd.print((int)waterLevelCm); lcd.print("cm "); }
lcd.setCursor(0, 1);
lcd.print("Vent: ");
lcd.print(isVentOpen ? "OPEN " : "SHUT ");
}
// Debug to Serial
Serial.printf("T:%.1f H:%.1f Soil:%d%% Tank:%.1fcm Pump:%d Vent:%d\n",
airTemp, airHum, soilMoisturePercent, waterLevelCm, isWatering, isVentOpen);
}