/*******************************************************
Smart Agriculture - ESP32 + Blynk IoT (robust build)
- Safe includes + guards for Blynk and DHT
- Works OFFLINE (no Blynk/DHT lib) for local testing
*******************************************************/
// --------- BLYNK: define template/token FIRST (required by some Blynk libs) -----
#define BLYNK_TEMPLATE_ID "TMPL64I0nP7Fo"
#define BLYNK_TEMPLATE_NAME "UTS IoT Smart Agriculture"
#define BLYNK_DEVICE_NAME "ESP32-SmartAg"
// GANTI DENGAN TOKEN DEVICE ANDA (jika token lama terekspos, regenerate di Console)
#define BLYNK_AUTH_TOKEN "EunAaJqfVq2q1GGuaUQa4BEhvugVZjs_"
// enable debug printing via Blynk lib if present
#define BLYNK_PRINT Serial
// ---------------- Robust include + detection ----------------
#include <Arduino.h>
#include <WiFi.h>
// Blynk: try common headers (legacy, SSL variant, Edgent)
#if __has_include(<BlynkSimpleEsp32.h>)
#include <BlynkSimpleEsp32.h>
#define HAS_BLYNK 1
#elif __has_include(<BlynkSimpleEsp32_SSL.h>)
#include <BlynkSimpleEsp32_SSL.h>
#define HAS_BLYNK 1
#elif __has_include(<BlynkEdgent.h>)
#include <BlynkEdgent.h>
#define HAS_BLYNK 1
#else
#define HAS_BLYNK 0
#pragma message("[INFO] No Blynk headers found -> OFFLINE mode")
#endif
// DHT: try DHTesp (ESP-specific) then Adafruit DHT
#if __has_include(<DHTesp.h>)
#include <DHTesp.h>
#define HAS_DHT 1
#define DHT_IMPL_DHTESP 1
#elif __has_include(<DHT.h>)
#include <DHT.h>
#define HAS_DHT 1
#define DHT_IMPL_ADAFRUIT 1
#else
#define HAS_DHT 0
#pragma message("[INFO] No DHT headers found -> DHT disabled")
#endif
// If Adafruit DHT used, include Adafruit_Sensor if available
#if defined(DHT_IMPL_ADAFRUIT) && __has_include(<Adafruit_Sensor.h>)
#include <Adafruit_Sensor.h>
#endif
// ---------------- User WiFi ----------------
const char* ssid = "Wokwi-GUEST";
const char* pass = "";
const int WOKWI_WIFI_CHANNEL = 6;
// optional runtime override for auth when Blynk present
#if HAS_BLYNK
char auth[] = "";
const char* blynk_server = "blynk.cloud";
uint16_t blynk_port = 80; // use 80 in Wokwi
#endif
// ---------------- Pins ----------------
const int PIN_SOIL = 34;
const int PIN_DHT = 21;
const int PIN_PUMP = 16;
const int PIN_ALERT_LED = 2;
const int PIN_LDR = 35;
// Virtual pin integers (safe even if Blynk header missing)
const int VP_SOIL = 0;
const int VP_TEMP = 1;
const int VP_HUM = 2;
const int VP_LIGHT = 3;
const int VP_PUMP_BTN = 4;
const int VP_AUTOMODE = 5;
const int VP_PUMP_STAT = 6;
const int VP_ALERT = 7;
// params
const int SOIL_CRITICAL_PERCENT = 25;
const int SOIL_STOP_PERCENT = 35;
const float TEMP_ALERT_C = 35.0;
const unsigned long PUMP_DURATION_MS_DEFAULT = 30UL * 1000UL;
const unsigned long SEND_INTERVAL_MS = 5000UL;
#if HAS_DHT
#if defined(DHT_IMPL_DHTESP)
DHTesp dht; // uses dht.setup(pin, DHTesp::DHT22)
#elif defined(DHT_IMPL_ADAFRUIT)
#ifndef DHTTYPE
#define DHTTYPE DHT22 // change to DHT11 if you use that
#endif
DHT *dht_ada = nullptr;
#endif
#endif
bool autoMode = true;
bool pumpState = false;
unsigned long pumpStartTime = 0;
unsigned long pumpDurationMs = PUMP_DURATION_MS_DEFAULT;
unsigned long lastSendMillis = 0;
float lastTemp = 0.0, lastHum = 0.0;
bool soil_invert = false;
bool light_invert = false;
// helpers
int adcToPercent(int raw, bool invert) {
if (raw < 0) raw = 0;
if (raw > 4095) raw = 4095;
int perc = invert ? map(raw, 0, 4095, 100, 0) : map(raw, 0, 4095, 0, 100);
if (perc < 0) perc = 0;
if (perc > 100) perc = 100;
return perc;
}
int readAdcPercent(int pin, bool invert, uint8_t samples = 8) {
uint32_t acc = 0;
for (uint8_t i = 0; i < samples; i++) {
acc += analogRead(pin);
delayMicroseconds(200);
}
int avg = acc / samples;
return adcToPercent(avg, invert);
}
// forward decl
void startPump();
void stopPump();
#if HAS_BLYNK
BLYNK_WRITE(VP_AUTOMODE) {
autoMode = (param.asInt() != 0);
Serial.printf("[Blynk] AutoMode = %d\n", autoMode);
}
BLYNK_WRITE(VP_PUMP_BTN) {
int v = param.asInt();
Serial.printf("[Blynk] Manual pump button = %d\n", v);
if (v == 1) startPump(); else stopPump();
}
#endif
void startPump() {
if (!pumpState) {
digitalWrite(PIN_PUMP, HIGH);
pumpState = true;
pumpStartTime = millis();
#if HAS_BLYNK
if (Blynk.connected()) {
Blynk.virtualWrite(VP_PUMP_STAT, 1);
Blynk.logEvent("pump_started", "Pump started (manual/auto)");
}
#else
Serial.println("[NOTIFY] Pump started (manual/auto)");
#endif
Serial.println("Pump STARTED");
}
}
void stopPump() {
if (pumpState) {
digitalWrite(PIN_PUMP, LOW);
pumpState = false;
#if HAS_BLYNK
if (Blynk.connected()) {
Blynk.virtualWrite(VP_PUMP_STAT, 0);
Blynk.logEvent("pump_stopped", "Pump stopped");
}
#else
Serial.println("[NOTIFY] Pump stopped");
#endif
Serial.println("Pump STOPPED");
}
}
void setup() {
Serial.begin(115200);
delay(50);
pinMode(PIN_PUMP, OUTPUT); digitalWrite(PIN_PUMP, LOW);
pinMode(PIN_ALERT_LED, OUTPUT); digitalWrite(PIN_ALERT_LED, LOW);
#if HAS_DHT
#if defined(DHT_IMPL_DHTESP)
dht.setup(PIN_DHT, DHTesp::DHT22);
Serial.println("[INFO] Using DHTesp library");
#elif defined(DHT_IMPL_ADAFRUIT)
dht_ada = new DHT(PIN_DHT, DHTTYPE);
dht_ada->begin();
Serial.println("[INFO] Using Adafruit DHT library");
#endif
#else
Serial.println("[WARN] DHT library missing - temperature/humidity disabled");
#endif
analogReadResolution(12);
analogSetPinAttenuation(PIN_SOIL, ADC_11db);
analogSetPinAttenuation(PIN_LDR, ADC_11db);
#if HAS_BLYNK
Serial.println("\n== WiFi connect ==");
WiFi.persistent(false);
WiFi.setAutoReconnect(true);
WiFi.setSleep(false);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, pass, WOKWI_WIFI_CHANNEL);
unsigned long t0 = millis();
while (WiFi.status() != WL_CONNECTED && millis() - t0 < 20000UL) {
Serial.print(".");
delay(250);
}
Serial.println();
Serial.printf("WiFi status: %d\n", WiFi.status());
if (WiFi.status() == WL_CONNECTED) {
Serial.print("IP: "); Serial.println(WiFi.localIP());
} else {
Serial.println("WiFi not connected. Enable Wokwi internet icon.");
}
Serial.println("== Blynk connect ==");
const char* used_auth = (strlen(auth) == 0) ? BLYNK_AUTH_TOKEN : auth;
Blynk.begin((char*)used_auth, ssid, pass, (char*)blynk_server, blynk_port);
if (Blynk.connected()) {
Serial.println("Blynk connected.");
Blynk.virtualWrite(VP_PUMP_STAT, 0);
Blynk.virtualWrite(VP_ALERT, 0);
Blynk.virtualWrite(VP_AUTOMODE, autoMode ? 1 : 0);
} else {
Serial.println("WARNING: Blynk not connected yet.");
}
#else
Serial.println("[OFFLINE] Blynk library not found. Running without Blynk.");
#endif
Serial.println("Setup complete.");
lastSendMillis = millis();
}
void loop() {
#if HAS_BLYNK
if (WiFi.status() != WL_CONNECTED) {
static unsigned long lastWifiRetry = 0;
if (millis() - lastWifiRetry > 10000UL) {
lastWifiRetry = millis();
WiFi.disconnect(true, true);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, pass, WOKWI_WIFI_CHANNEL);
Serial.println("Retry WiFi...");
}
}
if (!Blynk.connected() && WiFi.status() == WL_CONNECTED) {
static unsigned long lastBlynkRetry = 0;
if (millis() - lastBlynkRetry > 10000UL) {
lastBlynkRetry = millis();
Serial.println("Retry Blynk...");
Blynk.connect(5000);
}
}
Blynk.run();
#endif
unsigned long now = millis();
if (now - lastSendMillis >= SEND_INTERVAL_MS) {
lastSendMillis = now;
int soilPercent = readAdcPercent(PIN_SOIL, soil_invert, 8);
#if HAS_DHT
float temp = NAN, hum = NAN;
#if defined(DHT_IMPL_DHTESP)
temp = dht.getTemperature();
hum = dht.getHumidity();
#elif defined(DHT_IMPL_ADAFRUIT)
temp = dht_ada->readTemperature();
hum = dht_ada->readHumidity();
#endif
if (!isnan(temp)) lastTemp = temp; else temp = lastTemp;
if (!isnan(hum)) lastHum = hum; else hum = lastHum;
#else
float temp = lastTemp;
float hum = lastHum;
#endif
int lightPercent = readAdcPercent(PIN_LDR, light_invert, 8);
#if HAS_BLYNK
if (Blynk.connected()) {
Blynk.virtualWrite(VP_SOIL, soilPercent);
Blynk.virtualWrite(VP_TEMP, temp);
Blynk.virtualWrite(VP_HUM, hum);
Blynk.virtualWrite(VP_LIGHT, lightPercent);
}
#else
Serial.printf("[VWRITE] soil%%=%d, tempC=%.1f, hum%%=%.1f, light%%=%d\n",
soilPercent, temp, hum, lightPercent);
#endif
Serial.printf("Soil %%=%d | Light %%=%d | T=%.1fC H=%.1f%%\n",
soilPercent, lightPercent, temp, hum);
// auto irrigation
if (autoMode) {
if (!pumpState && soilPercent < SOIL_CRITICAL_PERCENT) {
Serial.println("Auto: soil critical -> start pump");
startPump();
}
}
// temp alert
if (temp > TEMP_ALERT_C) {
digitalWrite(PIN_ALERT_LED, HIGH);
#if HAS_BLYNK
if (Blynk.connected()) {
Blynk.virtualWrite(VP_ALERT, 1);
Blynk.logEvent("temp_alert", "Temperature exceeded threshold!");
}
#endif
Serial.println("TEMP ALERT: LED ON & notify");
} else {
digitalWrite(PIN_ALERT_LED, LOW);
#if HAS_BLYNK
if (Blynk.connected()) Blynk.virtualWrite(VP_ALERT, 0);
#endif
}
}
// pump duration & hysteresis
if (pumpState) {
if (millis() - pumpStartTime >= pumpDurationMs) {
Serial.println("Pump duration elapsed -> stop");
stopPump();
} else {
int curPerc = readAdcPercent(PIN_SOIL, soil_invert, 4);
if (curPerc >= SOIL_STOP_PERCENT) {
Serial.println("Soil above stop threshold -> stop pump");
stopPump();
}
}
}
delay(1);
}