#include #include #include // ================= WIFI ================= #define WIFI_SSID "Angga" #define WIFI_PASSWORD "upinipinupin" // ================= FIREBASE ================= #define DATABASE_URL "https://tomat2-default-rtdb.asia-southeast1.firebasedatabase.app" #define DATABASE_SECRET "EsSGEME2IMqmVvWx2VZLkLSKAnJ4Meg6AblUOyxO" FirebaseData fbdo; FirebaseAuth auth; FirebaseConfig config; // ================= PIN ================= #define SOIL_PIN 34 #define PIR_PIN 27 #define RELAY_PIN 26 #define BUZZER_PIN 25 // ================= KALIBRASI ================= #define SOIL_DRY 2400 #define SOIL_WET 1100 // ================= GLOBAL ================= volatile bool pirFlag = false; volatile unsigned long lastInterrupt = 0; int pirState = 0; unsigned long pirHoldTime = 0; const int pirActiveDuration = 3000; int lastMoist = -1; int lastPir = -1; int lastPump = -1; int lastPumpState = 0; String mode = "auto"; int pumpManual = 0; int minMoist = 60; int maxMoist = 80; unsigned long lastSend = 0; unsigned long lastRead = 0; const int sendInterval = 10000; const int readInterval = 7000; // ================= OFFLINE ================= bool offlineMode = false; unsigned long lastOfflineCheck = 0; const int offlineCheckInterval = 10000; // ================= BUZZER ================= unsigned long lastBuzz = 0; unsigned long buzzerStart = 0; bool buzzerActive = false; const int buzzCooldown = 5000; const int buzzerDuration = 2000; bool sendPirNow = false; // ================= ISR ================= void IRAM_ATTR pirISR() {   if (millis() - lastInterrupt > 200) {     pirFlag = true;     lastInterrupt = millis();   } } // ================= WIFI ================= void connectWiFi() {   static unsigned long lastAttempt = 0;   // sudah connect   if (WiFi.status() == WL_CONNECTED) {     return;   }   // retry tiap 10 detik   if (millis() - lastAttempt < 10000) {     return;   }   lastAttempt = millis();   Serial.println("Connecting WiFi...");   WiFi.disconnect();   WiFi.begin(WIFI_SSID, WIFI_PASSWORD); } // ================= OFFLINE CHECK ================= void checkOfflineMode() {   // cek tiap 10 detik   if (millis() - lastOfflineCheck < offlineCheckInterval) {     return;   }   lastOfflineCheck = millis();   // ===== WIFI PUTUS =====   if (WiFi.status() != WL_CONNECTED) {     if (!offlineMode) {       Serial.println("⚠️ OFFLINE MODE");     }     offlineMode = true;     return;   }   // ===== WIFI NORMAL =====   if (offlineMode) {     Serial.println("✅ BACK ONLINE");   }   offlineMode = false; } // ================= SAFE SEND ================= bool sendData(FirebaseJson &json) {   // jangan kirim saat offline   if (     offlineMode ||     WiFi.status() != WL_CONNECTED   ) {     return false;   }   // firebase belum ready   if (!Firebase.ready()) {     return false;   }   // kirim 1x saja   return Firebase.RTDB.setJSON(     &fbdo,     "/sensor",     &json   ); } // ================= SETUP ================= void setup() {   Serial.begin(115200);   pinMode(PIR_PIN, INPUT);   pinMode(RELAY_PIN, OUTPUT);   pinMode(BUZZER_PIN, OUTPUT);   // relay OFF   digitalWrite(RELAY_PIN, HIGH);   digitalWrite(BUZZER_PIN, LOW);   analogSetAttenuation(ADC_11db);   attachInterrupt(     digitalPinToInterrupt(PIR_PIN),     pirISR,     RISING   );   // ===== WIFI =====   WiFi.mode(WIFI_STA);   WiFi.setSleep(false);   connectWiFi();   WiFi.setAutoReconnect(true);   WiFi.persistent(true);   // ===== FIREBASE =====   config.database_url = DATABASE_URL;   config.signer.tokens.legacy_token =     DATABASE_SECRET;   Firebase.begin(&config, &auth);   Firebase.reconnectWiFi(true);   fbdo.setBSSLBufferSize(2048, 512);   config.timeout.serverResponse = 3000;   // ===== WATCHDOG =====   esp_task_wdt_config_t wdt_config = {     .timeout_ms = 10000,     .idle_core_mask = (1 << portNUM_PROCESSORS) - 1,     .trigger_panic = true   };   esp_task_wdt_init(&wdt_config);   esp_task_wdt_add(NULL);   Serial.println("System Ready"); } // ================= LOOP ================= void loop() {   esp_task_wdt_reset();   // ===== CHECK ONLINE/OFFLINE =====   checkOfflineMode();   // ===== RECONNECT WIFI =====   connectWiFi();   // ================= PIR =================   if (pirFlag) {     pirFlag = false;     Serial.println("⚠️ GERAKAN!");     pirState = 1;     pirHoldTime = millis();     sendPirNow = true;     // ===== BUZZER =====     if (millis() - lastBuzz > buzzCooldown) {       lastBuzz = millis();       buzzerStart = millis();       buzzerActive = true;     }   }   // ================= BUZZER =================   if (buzzerActive) {     static unsigned long lastToggle = 0;     static bool state = false;     if (millis() - lastToggle > 100) {       lastToggle = millis();       state = !state;       digitalWrite(BUZZER_PIN, state);     }     // ===== STOP BUZZER =====     if (millis() - buzzerStart > buzzerDuration) {       buzzerActive = false;       digitalWrite(BUZZER_PIN, LOW);     }   }   // ================= RESET PIR =================   if (     pirState == 1 &&     millis() - pirHoldTime > pirActiveDuration   ) {     pirState = 0;   }   // ================= SENSOR =================   int soilRaw = analogRead(SOIL_PIN);   int moisture = map(     soilRaw,     SOIL_DRY,     SOIL_WET,     0,     100   );   moisture = constrain(moisture, 0, 100);   // ==================================================   // ================= LOGIKA POMPA ===================   // ==================================================   int pumpState = lastPumpState;   // ===== OFFLINE =====   if (offlineMode) {     // paksa AUTO lokal     if (moisture < minMoist) {       pumpState = 1;     } else if (moisture > maxMoist) {       pumpState = 0;     }   } else {     // ===== MODE NORMAL =====     if (mode == "manual") {       pumpState = pumpManual;     } else {       if (moisture < minMoist) {         pumpState = 1;       } else if (moisture > maxMoist) {         pumpState = 0;       }     }   }   lastPumpState = pumpState;   // relay active LOW   digitalWrite(     RELAY_PIN,     pumpState ? LOW : HIGH   );   // ================= DEBUG =================   Serial.printf(     "OFF:%d Moist:%d Pump:%d PIR:%d WiFi:%d\n",     offlineMode,     moisture,     pumpState,     pirState,     WiFi.status()   );   // ==================================================   // ============== FIREBASE DI BAWAH =================   // ==================================================   // ================= READ FIREBASE =================   if (     !offlineMode &&     millis() - lastRead > readInterval   ) {     if (       WiFi.status() == WL_CONNECTED &&       Firebase.ready()     ) {       Firebase.RTDB.getString(         &fbdo,         "/control/mode"       );       if (fbdo.dataType() == "string") {         mode = fbdo.stringData();       }       Firebase.RTDB.getInt(         &fbdo,         "/control/pump"       );       if (fbdo.dataType() == "int") {         pumpManual = fbdo.intData();       }       Firebase.RTDB.getInt(         &fbdo,         "/config/min_moisture"       );       if (fbdo.dataType() == "int") {         minMoist = fbdo.intData();       }       Firebase.RTDB.getInt(         &fbdo,         "/config/max_moisture"       );       if (fbdo.dataType() == "int") {         maxMoist = fbdo.intData();       }     }     lastRead = millis();   }   // ================= PIR REALTIME =================   if (sendPirNow) {     // reset flag dulu     sendPirNow = false;     // hanya kirim kalau online     if (!offlineMode) {       FirebaseJson json;       json.set("moisture", moisture);       json.set("pir", 1);       json.set("pump", pumpState);       if (sendData(json)) {         Serial.println("🚀 PIR SENT");       }     }   }   // ================= PERIODIC SEND =================   if (     !offlineMode &&     millis() - lastSend > sendInterval   ) {     if (       abs(moisture - lastMoist) > 3 ||       pirState != lastPir ||       pumpState != lastPump     ) {       FirebaseJson json;       json.set("moisture", moisture);       json.set("pir", pirState);       json.set("pump", pumpState);       if (sendData(json)) {         Serial.printf(           "SEND → M:%d PIR:%d P:%d\n",           moisture,           pirState,           pumpState         );       }       lastMoist = moisture;       lastPir = pirState;       lastPump = pumpState;     }     lastSend = millis();   }   delay(20); }