/* Smart Agriculture - Unified Sketch (ESP32 + Blynk IoT)
- Cleaned & fixed:
* Blynk template/auth macros defined at top (required by modern Blynk lib)
* Conditional include of Blynk header (HAS_BLYNK)
* No duplicate includes
* Virtual pin integers (safe even when no Blynk library)
* Use port 80 for Wokwi
* DHT, ADC, pump hysteresis, manual override via Blynk handlers (guarded)
*/
// ----------------- BLYNK: defines MUST be before including the Blynk header -----------------
#define BLYNK_TEMPLATE_ID "TMPL64I0nP7Fo" // <-- optional: replace if using IoT template
#define BLYNK_TEMPLATE_NAME "UTS IoT Smart Agriculture"
#define BLYNK_DEVICE_NAME "ESP32-SmartAg" // optional
// **IMPORTANT** replace with your device auth token before running.
// If token has been published previously, regenerate the token in Blynk Console.
#define BLYNK_AUTH_TOKEN "YOUR_DEVICE_AUTH_TOKEN"
// Print debug to Serial via Blynk lib
#define BLYNK_PRINT Serial
// Try to include Blynk header if available
#if __has_include(<BlynkSimpleEsp32.h>)
#define HAS_BLYNK 1
#include <BlynkSimpleEsp32.h>
#else
#define HAS_BLYNK 0
#pragma message ("[INFO] BlynkSimpleEsp32.h not found, building in OFFLINE mode.")
#endif
// ----------------- common includes -----------------
#include <WiFi.h>
#include "DHTesp.h"
// ---------------- USER WI-FI -------------------------
const char* ssid = "Wokwi-GUEST";
const char* pass = ""; // kosong di Wokwi
const int WOKWI_WIFI_CHANNEL = 6; // recommended for Wokwi
#if HAS_BLYNK
// optional runtime override; leave empty to use BLYNK_AUTH_TOKEN macro
char auth[] = "";
const char* blynk_server = "blynk.cloud";
uint16_t blynk_port = 80; // use 80 in Wokwi for reliability
#endif
// ---------------- Pin mapping ------------------------
const int PIN_SOIL = 34; // ADC1_CH6 (soil pot)
const int PIN_DHT = 21; // DHT22 DATA
const int PIN_PUMP = 16; // LED/Relay pompa (aktif HIGH)
const int PIN_ALERT_LED = 2; // LED alert suhu
const int PIN_LDR = 35; // ADC1_CH7 (opsional LDR)
// -------- Virtual pin numbers (integer constants) ----
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;
// --------- Event codes --------------------------------
const char* EVT_PUMP_STARTED = "pump_started";
const char* EVT_PUMP_STOPPED = "pump_stopped";
const char* EVT_TEMP_ALERT = "temp_alert";
// -------- parameters ---------------------------------
const int SOIL_CRITICAL_PERCENT = 25; // start irrigation if < 25%
const int SOIL_STOP_PERCENT = 35; // stop if >= 35% (hysteresis)
const float TEMP_ALERT_C = 35.0; // temp alert threshold
const unsigned long PUMP_DURATION_MS_DEFAULT = 30UL * 1000UL; // default pump runtime
const unsigned long SEND_INTERVAL_MS = 5000UL; // sensor publish interval
// -------- globals -----------------------------------
DHTesp dht;
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; // fallback if DHT read fails
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 declarations
void startPump();
void stopPump();
// ---------------- Blynk handlers (only if library present) ----------------
#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
// ---------------- Pump control -----------------------------------------
void startPump() {
if (!pumpState) {
digitalWrite(PIN_PUMP, HIGH); // aktif-HIGH
pumpState = true;
pumpStartTime = millis();
#if HAS_BLYNK
if (Blynk.connected()) {
Blynk.virtualWrite(VP_PUMP_STAT, 1);
Blynk.logEvent(EVT_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(EVT_PUMP_STOPPED, "Pump stopped");
}
#else
Serial.println("[NOTIFY] Pump stopped");
#endif
Serial.println("Pump STOPPED");
}
}
// ---------------- Setup ----------------------------------------------
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);
dht.setup(PIN_DHT, DHTesp::DHT22);
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 (3=CONNECTED)\n", WiFi.status());
if (WiFi.status() == WL_CONNECTED) {
Serial.print("IP: "); Serial.println(WiFi.localIP());
} else {
Serial.println("WiFi not connected. Aktifkan ikon 🌐 Internet di Wokwi.");
}
Serial.println("== Blynk connect ==");
const char* used_auth = (strlen(auth) == 0) ? BLYNK_AUTH_TOKEN : auth;
// Use Blynk.begin with explicit server/port so we use port 80 for Wokwi:
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();
}
// ---------------- Loop -----------------------------------------------
void loop() {
#if HAS_BLYNK
// Auto-reconnect WiFi
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...");
}
}
// Auto-reconnect Blynk
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();
// Periodic sensor read/send
if (now - lastSendMillis >= SEND_INTERVAL_MS) {
lastSendMillis = now;
int soilPercent = readAdcPercent(PIN_SOIL, soil_invert, 8);
float temp = dht.getTemperature();
float hum = dht.getHumidity();
if (!isnan(temp)) lastTemp = temp; else temp = lastTemp;
if (!isnan(hum)) lastHum = hum; else hum = lastHum;
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();
}
}
// Temperature alert
if (temp > TEMP_ALERT_C) {
digitalWrite(PIN_ALERT_LED, HIGH);
#if HAS_BLYNK
if (Blynk.connected()) {
Blynk.virtualWrite(VP_ALERT, 1);
Blynk.logEvent(EVT_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 (hysteresis)");
stopPump();
}
}
}
delay(1);
}