// ==== Blynk Template ==== full code with auto-calibration + averaging + startup check
#define BLYNK_TEMPLATE_ID "TMPL2BAzD4VlS"
#define BLYNK_TEMPLATE_NAME "SMART GREEN HOUSE WITH REAL"
// ==== Libraries ====
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <WiFi.h>
#include <BlynkSimpleEsp32.h>
#include "DHT.h"
#include <SD.h>
#include <SPI.h>
#include <time.h>
// ==== LCD ====
LiquidCrystal_I2C lcd(0x27, 20, 4);
// ==== WiFi/Blynk ====
char auth[] = "Kj4HWKiQqd-NX56JDRgHoR0TF3FhkaG-";
char ssid[] = "Wokwi-GUEST";
char pass[] = "";
BlynkTimer timer;
// ==== Pins ====
// Soil moisture (6 sensors)
#define SOIL1_PIN 34
#define SOIL2_PIN 32
#define SOIL3_PIN 33
#define SOIL4_PIN 35
#define SOIL5_PIN 36
#define SOIL6_PIN 39
// Relays
#define RELAY1_PIN 4
#define RELAY2_PIN 25
#define RELAY3_PIN 26
#define RELAY4_PIN 18
#define RELAY5_PIN 2
#define RELAY6_PIN 12
// DHT + buzzer
#define DHTPIN 14
#define DHTTYPE DHT22
#define buzzerPin 5
// SD + Flow
#define SD_CS 13
#define SD_MOSI 23
#define SD_MISO 19
#define SD_SCK 15
#define FLOW_SENSOR_PIN 27
volatile int flowPulseCount = 0;
float flowRate = 0.0;
float totalLitres = 0.0;
DHT dht(DHTPIN, DHTTYPE);
// ==== Calibration values ====
int soilDry[6] = {3500, 3500, 3500, 3500, 3500, 3500};
int soilWet[6] = {1200, 1200, 1200, 1200, 1200, 1200};
int moistureThreshold = 30;
int moistureCutoff = 60;
// States
bool watering[6] = {false,false,false,false,false,false};
unsigned long waterStartTime[6] = {0,0,0,0,0,0};
String sessionFilename = "green house";
bool sdAvailable = false;
// ==== Interrupt for Flow Sensor ====
void IRAM_ATTR pulseCounter() { flowPulseCount++; }
// ==== Flow Calculation ====
void calculateFlow() {
float pulsesPerLitre = 450.0;
if (flowPulseCount > 0) {
flowRate = (flowPulseCount / pulsesPerLitre) * (60.0 / 5.0);
totalLitres += (flowPulseCount / pulsesPerLitre);
flowPulseCount = 0;
} else {
flowRate = 0.0;
}
if (flowRate < 0.1) flowRate = 0.0;
}
// ==== SD Card Check ====
bool checkSDCard() {
SPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS);
if (!SD.begin(SD_CS)) {
lcd.setCursor(0,3); lcd.print("SD DATA ERROR!");
Serial.println("SD card connection error!");
return false;
}
File testFile = SD.open("/test.txt", FILE_WRITE);
if (!testFile) {
lcd.setCursor(0,3); lcd.print("NO MEMORY!");
Serial.println("SD card has no memory or cannot write!");
return false;
}
testFile.close();
SD.remove("/test.txt");
lcd.setCursor(0,3); lcd.print(" SD IS READY ");
return true;
}
// ==== SD Write ====
void writeDataToSD(int soilVals[6], float temp, float hum) {
if (!sdAvailable) return;
if (sessionFilename == "") return;
File dataFile = SD.open(sessionFilename, FILE_APPEND);
if (dataFile) {
if (dataFile.size() == 0) {
dataFile.println("Datetime,Soil1,Soil2,Soil3,Soil4,Soil5,Soil6,Temp,Humidity,FlowRate,TotalLitres");
}
time_t now = time(nullptr);
struct tm* timeinfo = localtime(&now);
char datetime[32];
sprintf(datetime, "%04d/%02d/%02d %02d:%02d:%02d",
timeinfo->tm_year + 1900, timeinfo->tm_mon + 1, timeinfo->tm_mday,
timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec);
dataFile.print(datetime); dataFile.print(",");
for (int i=0;i<6;i++){ dataFile.print(soilVals[i]); dataFile.print(","); }
dataFile.print(temp); dataFile.print(",");
dataFile.print(hum); dataFile.print(",");
dataFile.print(flowRate, 2); dataFile.print(",");
dataFile.println(totalLitres, 2);
dataFile.close();
} else {
Serial.println("Failed to open file for writing");
}
}
// ==== Average analog read ====
int readSoilAvg(int pin) {
long sum = 0;
for (int i=0; i<10; i++) {
sum += analogRead(pin);
delay(5);
}
return sum / 10;
}
// ==== Map soil sensor to % with calibration ====
int readSoilPercent(int analogPin, int index) {
int raw = readSoilAvg(analogPin);
if (raw > soilDry[index]) raw = soilDry[index];
if (raw < soilWet[index]) raw = soilWet[index];
int pct = map(raw, soilDry[index], soilWet[index], 0, 100);
return constrain(pct, 0, 100);
}
void readSensors() {
int soilPct[6];
soilPct[0] = readSoilPercent(SOIL1_PIN, 0);
soilPct[1] = readSoilPercent(SOIL2_PIN, 1);
soilPct[2] = readSoilPercent(SOIL3_PIN, 2);
soilPct[3] = readSoilPercent(SOIL4_PIN, 3);
soilPct[4] = readSoilPercent(SOIL5_PIN, 4);
soilPct[5] = readSoilPercent(SOIL6_PIN, 5);
int relayPins[6] = {RELAY1_PIN, RELAY2_PIN, RELAY3_PIN, RELAY4_PIN, RELAY5_PIN, RELAY6_PIN};
// ---- Cillad Check ----
bool sensorFault[6] = {false,false,false,false,false,false};
for (int i=0;i<6;i++){
int raw = analogRead((i==0)?SOIL1_PIN:(i==1)?SOIL2_PIN:(i==2)?SOIL3_PIN:(i==3)?SOIL4_PIN:(i==4)?SOIL5_PIN:SOIL6_PIN);
if (raw <= 50 || raw >= 4090) {
sensorFault[i] = true;
digitalWrite(relayPins[i], HIGH);
watering[i] = false;
}
}
float temp = dht.readTemperature();
float hum = dht.readHumidity();
// ---- Watering Logic ----
for (int i=0;i<6;i++){
if (!sensorFault[i]) {
if (!watering[i] && soilPct[i] < moistureThreshold) {
digitalWrite(relayPins[i], LOW);
watering[i] = true;
waterStartTime[i] = millis();
}
else if (watering[i] && soilPct[i] >= moistureCutoff) {
digitalWrite(relayPins[i], HIGH);
watering[i] = false;
}
}
}
// ---- Blynk V6 ----
bool anyPumpOn = false;
for (int i=0; i<6; i++) {
if (watering[i]) { anyPumpOn = true; break; }
}
if (anyPumpOn) Blynk.virtualWrite(V6, "💧 Waraab Ayaa Socda");
else Blynk.virtualWrite(V6, "🚫 Waraabid ma jiro");
// ---- Pump Status ----
if (anyPumpOn) Blynk.virtualWrite(V7, "💧 Pump Is ON");
else Blynk.virtualWrite(V7, "🚫 Pump Is OFF");
// ---- Pumped sensors detail ----
String pumpMsg = "";
for (int i = 0; i < 6; i++) {
if (watering[i]) {
if (pumpMsg != "") pumpMsg += ", ";
pumpMsg += "S" + String(i+1);
}
}
if (pumpMsg == "") pumpMsg = "All OFF";
Blynk.virtualWrite(V8, pumpMsg);
// ---- Buzzer Alert ----
bool alert = false;
for (int i=0;i<6;i++){
if (watering[i] && (millis() - waterStartTime[i] > 180000) && soilPct[i] < moistureThreshold) {
alert = true; break;
}
}
digitalWrite(buzzerPin, alert ? HIGH : LOW);
// ---- Soil Status ----
auto soilLetter = [&](int v)->char{
if (v < moistureThreshold) return 'D';
else if (v < 50) return 'N';
else if (v < 70) return 'G';
else return 'V';
};
String soilStatus = "S:";
for (int i=0;i<6;i++){
soilStatus += String(i+1) + (sensorFault[i] ? "E" : String(soilLetter(soilPct[i])));
if (i<5) soilStatus += " ";
}
// ==== Send to Blynk ====
Blynk.virtualWrite(V0, soilPct[0]);
Blynk.virtualWrite(V1, soilPct[1]);
Blynk.virtualWrite(V2, soilPct[2]);
Blynk.virtualWrite(V3, soilPct[3]);
Blynk.virtualWrite(V4, soilPct[4]);
Blynk.virtualWrite(V5, soilPct[5]);
// ==== LCD Display ====
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Smart Green House");
lcd.setCursor(0,1);
lcd.print("S1:"); lcd.print(soilPct[0]); lcd.print("% ");
lcd.print("S2:"); lcd.print(soilPct[1]); lcd.print("% ");
lcd.print("S3:"); lcd.print(soilPct[2]); lcd.print("%");
lcd.setCursor(0,2);
lcd.print("S4:"); lcd.print(soilPct[3]); lcd.print("% ");
lcd.print("S5:"); lcd.print(soilPct[4]); lcd.print("% ");
lcd.print("S6:"); lcd.print(soilPct[5]); lcd.print("%");
lcd.setCursor(0,3);
lcd.print(soilStatus);
// ==== Flow & Logging ====
calculateFlow();
writeDataToSD(soilPct, temp, hum);
}
void setup() {
Serial.begin(115200);
Wire.begin();
lcd.init(); lcd.backlight();
lcd.setCursor(0,0); lcd.print("SMART GREEN HOUSE");
lcd.setCursor(0,1); lcd.print("IoT Green House");
lcd.setCursor(0,2); lcd.print("ENG Apdlla");
delay(2000);
lcd.clear();
lcd.setCursor(0,0); lcd.print("System...");
lcd.setCursor(0,1); lcd.print(" Loading...");
lcd.setCursor(0,2); lcd.print("MAZAARI AGRO Tech");
pinMode(buzzerPin, OUTPUT); digitalWrite(buzzerPin, LOW);
// Relays (idle HIGH = OFF)
pinMode(RELAY1_PIN, OUTPUT); digitalWrite(RELAY1_PIN, HIGH);
pinMode(RELAY2_PIN, OUTPUT); digitalWrite(RELAY2_PIN, HIGH);
pinMode(RELAY3_PIN, OUTPUT); digitalWrite(RELAY3_PIN, HIGH);
pinMode(RELAY4_PIN, OUTPUT); digitalWrite(RELAY4_PIN, HIGH);
pinMode(RELAY5_PIN, OUTPUT); digitalWrite(RELAY5_PIN, HIGH);
pinMode(RELAY6_PIN, OUTPUT); digitalWrite(RELAY6_PIN, HIGH);
// Soil pins
pinMode(SOIL1_PIN, INPUT);
pinMode(SOIL2_PIN, INPUT);
pinMode(SOIL3_PIN, INPUT);
pinMode(SOIL4_PIN, INPUT);
pinMode(SOIL5_PIN, INPUT);
pinMode(SOIL6_PIN, INPUT);
// Flow sensor
pinMode(FLOW_SENSOR_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(FLOW_SENSOR_PIN), pulseCounter, FALLING);
dht.begin();
// ✅ SD check
sdAvailable = checkSDCard();
WiFi.begin(ssid, pass);
Blynk.config(auth);
Blynk.connect();
configTime(3 * 3600, 0, "pool.ntp.org", "time.nist.gov");
while (!time(nullptr)) { delay(500); Serial.print("."); }
time_t now = time(nullptr);
struct tm* timeinfo = localtime(&now);
char filename[32];
sprintf(filename, "/greenhouse_%04d%02d%02d_%02d%02d.csv",
timeinfo->tm_year + 1900, timeinfo->tm_mon + 1, timeinfo->tm_mday,
timeinfo->tm_hour, timeinfo->tm_min);
sessionFilename = String(filename);
// 🔹 Initial check for soil sensors at startup
int relayPins[6] = {RELAY1_PIN, RELAY2_PIN, RELAY3_PIN, RELAY4_PIN, RELAY5_PIN, RELAY6_PIN};
for (int i=0;i<6;i++) {
int soilPct = readSoilPercent((i==0)?SOIL1_PIN:(i==1)?SOIL2_PIN:(i==2)?SOIL3_PIN:(i==3)?SOIL4_PIN:(i==4)?SOIL5_PIN:SOIL6_PIN, i);
if (soilPct < moistureThreshold) {
digitalWrite(relayPins[i], LOW);
watering[i] = true;
waterStartTime[i] = millis();
} else {
digitalWrite(relayPins[i], HIGH);
watering[i] = false;
}
}
timer.setInterval(5000L, readSensors);
}
void loop() {
Blynk.run();
timer.run();
}