/*
* LD7182 – AI for IoT | Smart Agriculture Crop Recommendation
* Board : wokwi-esp32-devkit-v1
*
* ════════════════════════════════════════════════════════════════
* PIN MAP
* ════════════════════════════════════════════════════════════════
* DHT22 data → GPIO4 (esp:D4)
* LCD SDA → GPIO21 (esp:D21)
* LCD SCL → GPIO22 (esp:D22)
* Pot N → GPIO36 (esp:VP) ADC1_CH0
* Pot P → GPIO39 (esp:VN) ADC1_CH3
* Pot K → GPIO34 (esp:D34) ADC1_CH6
* Pot pH → GPIO35 (esp:D35) ADC1_CH7
* Pot Rainfall → GPIO32 (esp:D32) ADC1_CH4
* LED Green → GPIO26 (esp:D26)
* LED Red → GPIO27 (esp:D27)
* ════════════════════════════════════════════════════════════════
*
* ThingSpeak Fields:
* 1=N 2=P 3=K 4=Temperature 5=Humidity 6=pH 7=Rainfall 8=CropIndex
*
* HOW TO CHANGE SENSORS IN WOKWI:
* Temp/Humidity → Right-click the DHT22 → "Change values"
* Soil params → Rotate the 5 potentiometer knobs
*/
#include <WiFi.h>
#include <WiFiClient.h> // ← required for ESP32 HTTPClient v2
#include <HTTPClient.h>
#include <DHT.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// ── WiFi ──────────────────────────────────────────────────────────────────────
const char* WIFI_SSID = "Wokwi-GUEST";
const char* WIFI_PASSWORD = "";
// ── ThingSpeak ────────────────────────────────────────────────────────────────
const char* TS_API_KEY = "CU98HJ3U9LDRGJ8C";
// ▲ ─────────────────────────────────────────────────── ▲
const char* TS_SERVER = "api.thingspeak.com"; // host only, no http://
const int TS_PORT = 80;
const unsigned long TS_INTERVAL = 16000UL; // 16 s (ThingSpeak min=15 s)
// ── Pins ──────────────────────────────────────────────────────────────────────
#define DHT_PIN 4
#define PIN_N 36 // VP ADC1
#define PIN_P 39 // VN ADC1
#define PIN_K 34 // ADC1
#define PIN_PH 35 // ADC1
#define PIN_RAIN 32 // ADC1 ← was GPIO25(ADC2), now fixed
#define LCD_SDA 21
#define LCD_SCL 22
#define LCD_ADDR 0x27
#define LED_GREEN 26
#define LED_RED 27
// ── Crop labels (sklearn LabelEncoder alphabetical order) ─────────────────────
const char* CROPS[22] = {
"Apple", "Banana", "Blackgram", "Chickpea", "Coconut",
"Coffee", "Cotton", "Grapes", "Jute", "KidneyBeans",
"Lentil", "Maize", "Mango", "MothBeans", "MungBean",
"Muskmelon", "Orange", "Papaya", "PigeonPeas", "Pomegranate",
"Rice", "Watermelon"
};
DHT dht(DHT_PIN, DHT22);
LiquidCrystal_I2C lcd(LCD_ADDR, 16, 2);
WiFiClient wifiClient; // persistent WiFiClient for HTTPClient
unsigned long lastUpload = 0;
unsigned long lastSample = 0;
int lastCropIdx = -99;
// ── ADC helper ────────────────────────────────────────────────────────────────
float mapADC(int raw, float lo, float hi) {
return lo + (constrain(raw, 0, 4095) / 4095.0f) * (hi - lo);
}
// ── Decision Tree (sklearn max_depth=12) ─────────────────────────────────────
int predictCrop(float N, float P, float K,
float temp, float hum, float ph, float rain) {
if (rain <= 30.3935f) return 15; // Muskmelon
if (hum <= 27.6851f) return (K <= 50.0f) ? 9 : 3;
if (P > 107.5f) return (hum <= 87.0046f) ? 7 : 0;
if (hum <= 70.8150f) {
if (N <= 59.5f) {
if (rain <= 82.1035f) {
if (hum <= 60.016f) return 13;
if (rain <= 57.6786f) {
if (P <= 52.0f) return 13;
return (temp <= 30.1687f) ? 10 : 13;
}
if (P <= 53.5f) return 13;
return (ph <= 8.5957f) ? 2 : 13;
}
return (P <= 47.5f) ? 12 : 18;
}
if (rain <= 112.454f) return (temp <= 29.0062f) ? 11 : 2;
return 5;
}
if (P <= 32.5f) {
if (rain <= 79.9668f) return 21;
if (rain <= 125.3923f) return (K <= 25.0f) ? 16 : 19;
return 4;
}
if (hum > 90.0174f) return 17;
if (P <= 65.0f) {
if (N <= 50.0f) return 14;
if (N <= 99.5f) {
if (rain <= 199.9623f) return (K <= 29.5f) ? 11 : (ph <= 6.0012f ? 20 : 8);
return 20;
}
return (K <= 30.5f) ? 6 : 8;
}
return 1;
}
// ═════════════════════════════════════════════════════════════════════════════
// ThingSpeak upload — uses raw TCP via WiFiClient (most reliable on ESP32)
// Pattern: HTTP GET over port 80 using WiFiClient directly
// This avoids the HTTPClient.begin(String) bug on ESP32 Arduino core v2+
// ═════════════════════════════════════════════════════════════════════════════
bool uploadThingSpeak(float N, float P, float K, float temp,
float hum, float ph, float rain, int idx) {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("[TS] No WiFi — skip upload");
return false;
}
// Build the GET request path
char path[256];
snprintf(path, sizeof(path),
"/update?api_key=%s"
"&field1=%.1f&field2=%.1f&field3=%.1f"
"&field4=%.2f&field5=%.2f&field6=%.3f"
"&field7=%.1f&field8=%d",
TS_API_KEY, N, P, K, temp, hum, ph, rain, idx);
Serial.printf("[TS] Connecting to %s:%d\n", TS_SERVER, TS_PORT);
// Connect TCP
if (!wifiClient.connect(TS_SERVER, TS_PORT)) {
Serial.println("[TS] TCP connect FAILED");
return false;
}
// Send HTTP GET
wifiClient.print(String("GET ") + path + " HTTP/1.1\r\n");
wifiClient.print(String("Host: ") + TS_SERVER + "\r\n");
wifiClient.print("Connection: close\r\n\r\n");
Serial.printf("[TS] Sent GET %s\n", path);
// Wait for response (up to 5 s)
unsigned long t0 = millis();
while (!wifiClient.available() && millis() - t0 < 5000) delay(10);
// Read status line e.g. "HTTP/1.1 200 OK"
String statusLine = wifiClient.readStringUntil('\n');
Serial.printf("[TS] Response: %s\n", statusLine.c_str());
// Drain remaining response
while (wifiClient.available()) wifiClient.read();
wifiClient.stop();
// ThingSpeak returns the entry ID (a positive integer) in the body on success
// Status line contains "200" on success
return statusLine.indexOf("200") >= 0;
}
// ── LCD helpers ───────────────────────────────────────────────────────────────
void lcdDraw(int idx, float temp, float hum) {
lcd.setCursor(0, 0);
lcd.print("Crop: ");
lcd.setCursor(5, 0);
char name[12]; strncpy(name, CROPS[idx], 11); name[11] = '\0';
lcd.print(name);
lcd.setCursor(0, 1);
lcd.print("T:");
lcd.print(temp, 1);
lcd.write(223); // ° — direct write, always correct
lcd.print("C H:");
lcd.print((int)hum);
lcd.print("% ");
}
void lcdMsg(const char* r0, const char* r1) {
lcd.clear();
lcd.setCursor(0, 0); lcd.print(r0);
lcd.setCursor(0, 1); lcd.print(r1);
}
// ── WiFi connect ──────────────────────────────────────────────────────────────
void connectWiFi() {
lcdMsg("Connecting WiFi", WIFI_SSID);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.print("[WiFi] Connecting");
for (int i = 0; i < 30 && WiFi.status() != WL_CONNECTED; i++) {
delay(500); Serial.print(".");
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\n[WiFi] Connected: " + WiFi.localIP().toString());
digitalWrite(LED_GREEN, HIGH);
digitalWrite(LED_RED, LOW);
lcdMsg("WiFi Connected!", WiFi.localIP().toString().c_str());
} else {
Serial.println("\n[WiFi] FAILED");
digitalWrite(LED_GREEN, LOW);
digitalWrite(LED_RED, HIGH);
lcdMsg("WiFi Failed!", "Offline Mode");
}
delay(2000);
}
// ── Setup ─────────────────────────────────────────────────────────────────────
void setup() {
Serial.begin(115200);
delay(500);
pinMode(LED_GREEN, OUTPUT); digitalWrite(LED_GREEN, LOW);
pinMode(LED_RED, OUTPUT); digitalWrite(LED_RED, HIGH);
analogReadResolution(12);
analogSetAttenuation(ADC_11db);
Wire.begin(LCD_SDA, LCD_SCL);
delay(100);
lcd.init();
lcd.backlight();
lcdMsg("Smart Agri AI", "LD7182 IoT");
delay(2000);
dht.begin();
delay(500);
connectWiFi();
Serial.println();
Serial.println("╔══════════════════════════════════════════════╗");
Serial.println("║ Smart Agriculture – LD7182 ║");
Serial.println("║ ThingSpeak upload: WiFiClient TCP (fixed) ║");
Serial.println("║ Upload interval : 16 seconds ║");
Serial.println("╚══════════════════════════════════════════════╝");
Serial.println();
}
// ── Loop ──────────────────────────────────────────────────────────────────────
void loop() {
unsigned long now = millis();
if (now - lastSample >= 1000) {
lastSample = now;
// DHT22
float temp = dht.readTemperature();
float hum = dht.readHumidity();
if (isnan(temp) || isnan(hum)) {
Serial.println("[WARN] DHT22 read failed — using 25.0C / 70%");
temp = 25.0f; hum = 70.0f;
}
// Potentiometers (all ADC1)
int rN = analogRead(PIN_N);
int rP = analogRead(PIN_P);
int rK = analogRead(PIN_K);
int rPH = analogRead(PIN_PH);
int rRAIN = analogRead(PIN_RAIN);
float N = mapADC(rN, 0.0f, 140.0f);
float P = mapADC(rP, 5.0f, 145.0f);
float K = mapADC(rK, 5.0f, 205.0f);
float ph = mapADC(rPH, 3.5f, 9.94f);
float rain = mapADC(rRAIN, 20.0f, 300.0f);
int cropIdx = predictCrop(N, P, K, temp, hum, ph, rain);
// Serial log
Serial.println("─────────────────────────────────────────────");
Serial.printf(" Temp=%.1f°C Hum=%.1f%%\n", temp, hum);
Serial.printf(" N=%.1f P=%.1f K=%.1f pH=%.2f Rain=%.1fmm\n",
N, P, K, ph, rain);
Serial.printf(" RAW: VP=%d VN=%d D34=%d D35=%d D32=%d\n",
rN, rP, rK, rPH, rRAIN);
Serial.printf(" CROP → %s (idx=%d)\n", CROPS[cropIdx], cropIdx);
// LCD
lcdDraw(cropIdx, temp, hum);
lastCropIdx = cropIdx;
// LEDs
bool online = (WiFi.status() == WL_CONNECTED);
digitalWrite(LED_GREEN, online ? HIGH : LOW);
digitalWrite(LED_RED, online ? LOW : HIGH);
// ThingSpeak — every 16 s
if (now - lastUpload >= TS_INTERVAL) {
lastUpload = now;
Serial.println(" [TS] Starting upload...");
bool ok = uploadThingSpeak(N, P, K, temp, hum, ph, rain, cropIdx);
Serial.println(ok ? " [TS] ✓ Upload OK!" : " [TS] ✗ Upload FAILED");
if (!ok && !online) connectWiFi();
lcdMsg("ThingSpeak", ok ? "Upload OK!" : "Upload FAIL");
delay(1500);
lcdDraw(cropIdx, temp, hum);
}
}
}