// ====== BLYNK Credentials ======
#define BLYNK_TEMPLATE_ID "TMPL68Pu-2BcK"
#define BLYNK_TEMPLATE_NAME "ESP32 DHT22"
#define BLYNK_AUTH_TOKEN "mze_PEXMXjVXUF7huu--4YbKGIdrTLIH"
#define BLYNK_PRINT Serial
#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <DHT.h>
#include <ESP32Servo.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <math.h>
// ====== Pin & Object Setup ======
#define DHTPIN 26
#define DHTTYPE DHT22
#define MQ135_PIN 32
#define MQ138_PIN 33
#define BUTTON_PIN 4
#define SERVO_PIN 15
DHT dht(DHTPIN, DHTTYPE);
LiquidCrystal_I2C lcd(0x27, 16, 2);
Servo servo;
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// ====== WiFi/Blynk Details ======
char auth[] = BLYNK_AUTH_TOKEN;
char ssid[] = "Wokwi-GUEST";
char pass[] = "";
// ====== KNN Dataset ======
const int NUM_SAMPLES = 75;
float trainingData[NUM_SAMPLES][4] = {
{285, 310, 65, 0}, {290, 320, 62, 0}, {300, 315, 67, 0},
{150, 200, 85, 0}, {160, 210, 82, 0}, {170, 220, 88, 0},
{180, 230, 83, 0}, {190, 240, 90, 0}, {200, 250, 87, 0},
{210, 260, 91, 0}, {220, 270, 89, 0}, {230, 280, 92, 0},
{240, 290, 88, 0}, {250, 300, 86, 0}, {260, 310, 84, 0},
{270, 320, 85, 0}, {280, 330, 82, 0}, {290, 340, 90, 0},
{300, 350, 83, 0}, {310, 360, 91, 0}, {320, 370, 89, 0},
{330, 380, 87, 0}, {340, 390, 88, 0}, {350, 400, 92, 0},
{360, 410, 93, 0}, {370, 420, 94, 0}, {380, 430, 95, 0},
{390, 440, 96, 0}, {310, 330, 68, 0}, {320, 340, 64, 0},
{350, 360, 60, 1}, {360, 370, 61, 1}, {365, 375, 59, 1},
{370, 380, 58, 1}, {380, 390, 57, 1}, {390, 400, 55, 1},
{400, 410, 54, 1}, {410, 420, 53, 1}, {415, 430, 52, 1},
{600, 650, 45, 1}, {610, 660, 42, 1}, {620, 670, 40, 1},
{630, 680, 44, 1}, {640, 690, 41, 1}, {650, 700, 43, 1},
{660, 710, 46, 1}, {670, 720, 44, 1}, {680, 730, 42, 1},
{690, 740, 41, 1}, {700, 750, 40, 1}, {710, 760, 39, 1},
{720, 770, 38, 1}, {730, 780, 37, 1}, {740, 790, 36, 1},
{750, 800, 35, 1}, {760, 810, 34, 1}, {770, 820, 33, 1},
{780, 830, 32, 1}, {790, 840, 30, 1}, {800, 850, 28, 1},
{810, 860, 29, 1}, {820, 870, 31, 1}, {830, 880, 27, 1},
{840, 890, 26, 1}, {420, 440, 51, 1}
};
const int K = 3;
// ====== Global Flags ======
bool shadeDeployed = false;
bool operationCompleted = false;
bool remoteResetTriggered = false;
// ====== Struct KNN Prediction ======
struct KNNResult {
int prediction;
int confidence;
};
// ====== SETUP ======
void setup() {
Serial.begin(115200);
Blynk.begin(auth, ssid, pass);
dht.begin();
lcd.init(); lcd.backlight();
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay(); display.display();
pinMode(BUTTON_PIN, INPUT_PULLUP);
servo.attach(SERVO_PIN);
servo.write(0); // Sunshade OFF at start
lcd.setCursor(0, 0);
lcd.print("Blynk Connected");
lcd.setCursor(0, 1);
lcd.print("System Starting...");
delay(5000);
}
// ====== LOOP ======
void loop() {
Blynk.run();
if (operationCompleted) return;
int rawMQ135 = analogRead(MQ135_PIN);
int rawMQ138 = analogRead(MQ138_PIN);
float mq135 = ((rawMQ135 / 4095.0) * 2000.0);
float mq138 = ((rawMQ138 / 4095.0) * 2000.0);
float humidity = dht.readHumidity();
KNNResult result = knnPredict(mq135, mq138, humidity);
int confidencePercent = (result.confidence * 100) / K;
// ==== LCD DISPLAY ====
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("M1:"); lcd.print(mq135, 1);
lcd.print(" M2:"); lcd.print(mq138, 1);
lcd.setCursor(0, 1);
lcd.print((result.prediction == 1) ? "READY H:" : "NOT READY H:");
lcd.print(humidity, 1);
// ==== OLED BAR DISPLAY ====
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.print("Humidity: "); display.print(humidity, 1); display.print(" %");
int barHeight = map(humidity, 0, 100, 0, 48);
display.fillRect(0, 64 - barHeight, 20, barHeight, SSD1306_WHITE);
display.display();
// ==== BLYNK SEND ====
Blynk.virtualWrite(V0, humidity);
Blynk.virtualWrite(V1, mq135);
Blynk.virtualWrite(V2, mq138);
Blynk.virtualWrite(V3, result.prediction == 1 ? "READY" : "NOT READY");
Serial.print("MQ135: "); Serial.print(mq135, 1);
Serial.print(" | MQ138: "); Serial.print(mq138, 1);
Serial.print(" | Humidity: "); Serial.print(humidity, 1);
Serial.print(" → Prediction: ");
Serial.print(result.prediction == 1 ? "READY" : "NOT READY");
Serial.print(" (Confidence: "); Serial.print(confidencePercent); Serial.println("%)");
// ==== KAWAL SERVO (sensor auto) ====
if (result.prediction == 1 && !shadeDeployed) {
servo.write(90);
shadeDeployed = true;
Serial.println("Sunshade Netting Roll On");
Blynk.logEvent("ready_to_harvest", "Plant is READY to harvest.");
}
// ==== BUTTON KAWAL MANUAL ====
static bool lastButtonState = HIGH;
bool currentButtonState = digitalRead(BUTTON_PIN);
if (shadeDeployed && lastButtonState == HIGH && currentButtonState == LOW) {
servo.write(0);
Serial.println("Button pressed → Sunshade OFF.");
shadeDeployed = false;
delay(5000);
operationCompleted = true;
lcd.clear();
lcd.setCursor(0, 0); lcd.print("System Completed");
lcd.setCursor(0, 1); lcd.print("Sensor OFF");
}
lastButtonState = currentButtonState;
delay(2000);
}
// ====== BLYNK REMOTE BUTTON FUNCTION ======
BLYNK_WRITE(V4) {
int pinValue = param.asInt();
if (pinValue == 1 && shadeDeployed && !remoteResetTriggered) {
servo.write(0);
Serial.println("Remote Blynk pressed → Sunshade OFF.");
lcd.clear();
lcd.setCursor(0, 0); lcd.print("Remote Button");
lcd.setCursor(0, 1); lcd.print("Sunshade OFF");
shadeDeployed = false;
remoteResetTriggered = true;
operationCompleted = true;
delay(5000);
lcd.clear();
lcd.setCursor(0, 0); lcd.print("System Completed");
lcd.setCursor(0, 1); lcd.print("Sensor OFF");
}
}
// ====== KNN Prediction ======
float distance(float x1, float y1, float z1, float x2, float y2, float z2) {
return sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2) + pow(z1 - z2, 2));
}
KNNResult knnPredict(float mq135, float mq138, float hum) {
float dists[NUM_SAMPLES];
int labels[NUM_SAMPLES];
for (int i = 0; i < NUM_SAMPLES; i++) {
dists[i] = distance(mq135, mq138, hum,
trainingData[i][0], trainingData[i][1], trainingData[i][2]);
labels[i] = trainingData[i][3];
}
// Sort by distance
for (int i = 0; i < NUM_SAMPLES - 1; i++) {
for (int j = i + 1; j < NUM_SAMPLES; j++) {
if (dists[j] < dists[i]) {
float tmpDist = dists[i]; dists[i] = dists[j]; dists[j] = tmpDist;
int tmpLabel = labels[i]; labels[i] = labels[j]; labels[j] = tmpLabel;
}
}
}
int countReady = 0;
for (int i = 0; i < K; i++) {
if (labels[i] == 1) countReady++;
}
KNNResult res;
res.prediction = (countReady > K / 2) ? 1 : 0;
res.confidence = countReady;
return res;
}
MQ-138
MQ-135
LCD DISPLAY
AUTOMATIC SUNSHADE NETTING ROLL ON
ROLL OFF SUNSHADE BUTTON
AFTER FINISH HARVEST
DHT22 FOR DATA HUMIDITY