/***************** KONFIGURASI BLYNK *****************/ #define BLYNK_TEMPLATE_ID "TMPL6IJqYTM5v" #define BLYNK_TEMPLATE_NAME "IOT Hamster" #define BLYNK_AUTH_TOKEN "cyVp3mgGDuB3pu0sGBoJlIGYKao-HKS1" /***************** LIBRARY *****************/ #include #include #include #include #include "esp_system.h" #include "DHT.h" #include #include #include /***************** PIN SENSOR *****************/ #define DHTPIN 4 #define DHTTYPE DHT22 #define DHT_POWER 16 #define RELAY_KIPAS 27 #define RELAY_PUMP 26 #define SERVO_PIN 14 #define WATER_SENSOR_PIN 35 DHT dht(DHTPIN, DHTTYPE); RTC_DS3231 rtc; Servo servo; /***************** VARIABEL *****************/ int lastDay = -1; bool manualKipas = false; bool manualPump = false; float lastSuhu = NAN; bool feedingDoneToday = false; bool servoAutoAktif = false; unsigned long servoAutoMulai = 0; const int JAM_FEEDING = 18; const int MENIT_FEEDING = 0; const unsigned long DURASI_BUKA_SERVO = 300; unsigned long lastFeedingTime = 0; const unsigned long INTERVAL_FEEDING = 60000; // 1 menit (60000 ms) /* ===== PERBAIKAN DHT ===== */ bool pumpAktif = false; unsigned long waktuPompaBerubah = 0; bool dhtValid = false; bool dhtEnabled = true; unsigned long lastPrint = 0; /* ======================== */ /* ===== AUTO RESET DHT NON-BLOCKING (MAX 3 DETIK) ===== */ unsigned long dhtErrorSejak = 0; unsigned long dhtResetMulai = 0; bool dhtSedangReset = false; const unsigned long DHT_TIMEOUT = 3000; // const unsigned long DHT_RESET_OFF = 500; // /* ==================================================== */ /* ===== TAMBAHAN AUTO KIPAS ===== */ unsigned long waktuKipasDimatikanManual = 0; bool tungguAutoKipas = false; /* ============================== */ /* ===== TAMBAHAN AUTO OFF POMPA ===== */ const unsigned long POMPA_AUTO_OFF = 10000; /* ================================= */ /* ===== VARIABEL WATER SENSOR ===== */ int waterValue = 0; int waterPercent = 0; // <-- TAMBAHAN float waterFiltered = 0; bool adaAir = false; #define WATER_EMPTY 100 //air habis #define WATER_FULL 950 //air penuh bool pompaOtomatisAktif = false; /* NAMA DAN PASSWORD WIFI */ char ssid[] = "OPPO Reno5 F"; char pass[] = "87654321"; WiFiClientSecure client; BlynkTimer timer; /***************** STATUS SERVO KE BLYNK (V5) *****************/ void updateServoStatus(int posisi) { if (posisi == 0) { Blynk.virtualWrite(V5, "Tertutup"); } else if (posisi == 90) { Blynk.virtualWrite(V5, "Terbuka"); } else { Blynk.virtualWrite(V5, "Bergerak"); } } /************** CONTROL MANUAL DARI BLYNK (Kipas) **************/ BLYNK_WRITE(V1) { int state = param.asInt(); manualKipas = true; digitalWrite(RELAY_KIPAS, state ? LOW : HIGH); if (state == 0) { waktuKipasDimatikanManual = millis(); tungguAutoKipas = true; } else { tungguAutoKipas = false; } } /************** CONTROL MANUAL DARI BLYNK (Pompa) **************/ BLYNK_WRITE(V2) { int state = param.asInt(); if (waterValue >= WATER_FULL && state == 1) { Blynk.virtualWrite(V2, 0); return; } manualPump = true; pompaOtomatisAktif = false; dhtEnabled = false; digitalWrite(RELAY_PUMP, state ? LOW : HIGH); pumpAktif = state; dhtEnabled = false; waktuPompaBerubah = millis(); if (state == 1) { dhtEnabled = false; String pesan = "šŸ’§ POMPA MANUAL MENYALA\n"; pesan += "Status: Minum Dibuka\n"; pesan += "Level Air: " + String(waterValue); kirimTelegram(pesan); } else { dhtEnabled = true; String pesan = "šŸ’§ POMPA MANUAL DIMATIKAN\n"; pesan += "Status: Minum Ditutup\n"; pesan += "Level Air: " + String(waterValue); kirimTelegram(pesan); } } /************** CONTROL MANUAL DARI BLYNK (SERVO) **************/ BLYNK_WRITE(V8) { if (param.asInt() == 1) { servo.write(90); updateServoStatus(90); kirimLogFeeding("Manual Feeding Dimulai"); delay(1000); servo.write(0); updateServoStatus(0); kirimLogFeeding("Manual Feeding Selesai"); Blynk.virtualWrite(V8, 0); } } /***************** KIRIM DATA KE BLYNK *****************/ void sendToBlynk() { DateTime now = rtc.now(); int jam = now.hour(); int menit = now.minute(); Blynk.virtualWrite(V3, jam); Blynk.virtualWrite(V4, menit); char waktu[10]; sprintf(waktu, "%02d:%02d", jam, menit); Blynk.virtualWrite(V7, waktu); if (dhtValid) { Blynk.virtualWrite(V10, 1); Blynk.virtualWrite(V11, "DHT BERFUNGSI"); } else { Blynk.virtualWrite(V10, 0); Blynk.virtualWrite(V11, "DHT ERROR"); } } /***************** SETUP *****************/ void setup() { Serial.begin(115200); pinMode(DHT_POWER, OUTPUT); digitalWrite(DHT_POWER, HIGH); delay(2000); pinMode(RELAY_KIPAS, OUTPUT); pinMode(RELAY_PUMP, OUTPUT); digitalWrite(RELAY_KIPAS, HIGH); digitalWrite(RELAY_PUMP, HIGH); pinMode(WATER_SENSOR_PIN, INPUT); // === TAMBAHAN === dht.begin(); rtc.begin(); // rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); servo.attach(SERVO_PIN, 500, 2400); servo.write(0); updateServoStatus(0); Serial.println("\nšŸ“¶ WiFi Terhubung"); Blynk.begin(BLYNK_AUTH_TOKEN, ssid, pass); timer.setInterval(2000L, sendToBlynk); timer.setInterval(3000L, bacaDHT); timer.setInterval(500L, bacaWaterSensor); // === TAMBAHAN === // timer.setInterval(300000L, printSuhuSerial); Serial.println("Sistem IoT + Blynk berjalan..."); //Pompa set adc analogSetPinAttenuation(WATER_SENSOR_PIN, ADC_11db); analogReadResolution(12); timer.setInterval(30000L, kirimSuhuTelegram); // tiap 10 detik /***************** WAKTU *****************/ configTime(7 * 3600, 0, "pool.ntp.org"); // WIB = GMT+7 struct tm timeinfo; if (getLocalTime(&timeinfo)) { rtc.adjust(DateTime( timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec )); } } /************** TELEGRAM CONFIG **************/ const char* botToken = "8710332774:AAHdWZwsIB2KbkkuvWBnFwWQgeQ15Io_L_c"; const char* chatID = "5535845901"; void kirimTelegram(String pesan) { client.setInsecure(); // bypass SSL if (!client.connect("api.telegram.org", 443)) { Serial.println("Gagal koneksi ke Telegram"); return; } String url = "/bot" + String(botToken) + "/sendMessage?chat_id=" + String(chatID) + "&text=" + pesan; client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: api.telegram.org\r\n" + "Connection: close\r\n\r\n"); delay(500); while (client.available()) { client.read(); } client.stop(); } /***************** LOOP *****************/ void loop() { Blynk.run(); timer.run(); autoFeedingRTC(); if (dhtValid) { if (!manualKipas) { if (lastSuhu > 30) { digitalWrite(RELAY_KIPAS, LOW); Blynk.virtualWrite(V1, 1); } else { digitalWrite(RELAY_KIPAS, HIGH); Blynk.virtualWrite(V1, 0); } } } else { digitalWrite(RELAY_KIPAS, HIGH); } if (tungguAutoKipas && manualKipas && dhtValid) { if (lastSuhu > 30 && millis() - waktuKipasDimatikanManual >= 5000) { digitalWrite(RELAY_KIPAS, LOW); Blynk.virtualWrite(V1, 1); tungguAutoKipas = false; Serial.println("šŸ”„ Kipas otomatis menyala kembali"); } } if (manualKipas && millis() - waktuKipasDimatikanManual > 10000) { manualKipas = false; } if (millis() - lastPrint >= 300000) { Serial.print("🌔 Suhu: "); Serial.print(lastSuhu); Serial.println(" °C"); lastPrint = millis(); } // ===== AUTO RESET DHT NON-BLOCKING ===== if (!dhtValid) { // mulai hitung error if (dhtErrorSejak == 0 && !dhtSedangReset) { dhtErrorSejak = millis(); } // jika error >= 3 detik → mulai reset if (!dhtSedangReset && millis() - dhtErrorSejak >= DHT_TIMEOUT) { // Serial.println("āš ļø DHT error > 3 detik → reset dimulai"); digitalWrite(DHT_POWER, LOW); // matikan DHT dhtResetMulai = millis(); dhtSedangReset = true; } if (dhtSedangReset && millis() - dhtResetMulai >= DHT_RESET_OFF) { digitalWrite(DHT_POWER, HIGH); // ===== RELEASE SYSTEM LOCK ===== dhtSedangReset = false; dhtErrorSejak = 0; dhtEnabled = true; // šŸ”“ izinkan baca DHT lagi pumpAktif = false; // šŸ”“ reset status pompa Blynk.virtualWrite(V2, 0); Serial.println("šŸ”„ DHT reset selesai, sistem dilepas"); } } else { // jika DHT normal → reset semua flag dhtErrorSejak = 0; dhtSedangReset = false; } // ====================================== // ===== AUTO WATER LEVEL ===== // Jika air habis → aktifkan mode otomatis if (!adaAir && !manualPump) { if (!pumpAktif) { pompaOtomatisAktif = true; } } // Jika tangki penuh → pompa harus mati if (waterPercent >= 80) { digitalWrite(RELAY_PUMP, HIGH); pumpAktif = false; pompaOtomatisAktif = false; manualPump = false; Blynk.virtualWrite(V2, 0); String pesan = "āœ… POMPA OTOMATIS BERHENTI\n"; pesan += "Alasan: Tangki Penuh\n"; pesan += "Level Air: " + String(waterValue); kirimTelegram(pesan); } // Jika mode otomatis aktif dan belum penuh → pompa nyala if (!manualPump && pompaOtomatisAktif && waterValue < WATER_FULL) { if (!pumpAktif) { digitalWrite(RELAY_PUMP, LOW); pumpAktif = true; waktuPompaBerubah = millis(); Blynk.virtualWrite(V2, 1); String pesan = "🚰 POMPA OTOMATIS MENYALA\n"; pesan += "Alasan: Air Habis\n"; pesan += "Level Air: " + String(waterValue); kirimTelegram(pesan); } } // ===== AUTO OFF POMPA + RESET MANUAL ===== if (pumpAktif && !pompaOtomatisAktif && millis() - waktuPompaBerubah >= POMPA_AUTO_OFF) { digitalWrite(RELAY_PUMP, HIGH); pumpAktif = false; Blynk.virtualWrite(V2, 0); String pesan = "ā± POMPA DIMATIKAN OTOMATIS\n"; pesan += "Alasan: Timeout 10 detik\n"; pesan += "Level Air: " + String(waterValue); kirimTelegram(pesan); } // reset manual setelah 10 detik if (manualPump && millis() - waktuPompaBerubah > 10000) { manualPump = false; } // =========================== } /***************** FUNGSI BACA DHT *****************/ void bacaDHT() { float suhuTotal = 0; int berhasil = 0; for (int i = 0; i < 5; i++) { float t = dht.readTemperature(); if (!isnan(t)) { suhuTotal += t; berhasil++; } vTaskDelay(250 / portTICK_PERIOD_MS); } if (berhasil > 0) { lastSuhu = suhuTotal / berhasil; dhtValid = true; Blynk.virtualWrite(V0, lastSuhu); } else { dhtValid = false; Serial.println("āŒ Gagal membaca DHT22"); } } /***************** FUNGSI WATER SENSOR (TAMBAHAN) *****************/ void bacaWaterSensor() { // ===== AMBIL BEBERAPA SAMPLE ===== long totalADC = 0; for (int i = 0; i < 10; i++) { totalADC += analogRead(WATER_SENSOR_PIN); vTaskDelay(2 / portTICK_PERIOD_MS); } // ===== RATA-RATA ADC ===== int rawValue = totalADC / 10; // ===== FILTER SMOOTHING ===== // lebih stabil dan tidak loncat-loncat waterFiltered = (0.8 * waterFiltered) + (0.2 * rawValue); waterValue = (int)waterFiltered; // ===== KALIBRASI SENSOR ===== // sesuaikan hasil Serial Monitor int adcKosong = 250; int adcPenuh = 950; waterPercent = map(waterValue, adcKosong, adcPenuh, 0, 100); waterPercent = constrain(waterPercent, 0, 100); // ===== HYSTERESIS ===== // supaya status tidak berubah-ubah static bool statusAir = false; // baru dianggap ADA AIR jika >5% if (waterPercent >= 5) { statusAir = true; } // baru dianggap KOSONG jika <5% if (waterPercent <= 5) { statusAir = false; } adaAir = statusAir; // ===== STATUS FULL ===== static bool statusFull = false; if (waterPercent >= 100) { statusFull = true; } if (waterPercent <= 90) { statusFull = false; } // ===== KIRIM KE BLYNK ===== static unsigned long lastBlynk = 0; if (millis() - lastBlynk >= 500) { Blynk.virtualWrite(V12, waterPercent); if (statusFull) { Blynk.virtualWrite(V13, "FULL"); } else if (adaAir) { Blynk.virtualWrite(V13, "AIR TERSEDIA"); } else { Blynk.virtualWrite(V13, "KOSONG"); } lastBlynk = millis(); } // ===== SERIAL MONITOR ===== static unsigned long lastSerial = 0; if (millis() - lastSerial >= 500) { // Serial.print("RAW ADC : "); // Serial.print(rawValue); // Serial.print(" | FILTER : "); // Serial.print(waterValue); // Serial.print(" | LEVEL : "); // Serial.print(waterPercent); // Serial.print("% | STATUS : "); if (adaAir) { // Serial.println("ADA AIR"); } else { // Serial.println("KOSONG"); } lastSerial = millis(); } } /***************** LOG Feeding *****************/ void kirimLogFeeding(String status) { DateTime now = rtc.now(); char waktu[20]; sprintf(waktu, "%02d:%02d:%02d", now.hour(), now.minute(), now.second()); String logFeeding = String(waktu) + " - " + status; Blynk.virtualWrite(V14, logFeeding); } /***************** AUTO FEEDING *****************/ void autoFeedingRTC() { // === MULAI FEEDING SETIAP 1 MENIT === if (!servoAutoAktif && millis() - lastFeedingTime >= INTERVAL_FEEDING) { servo.write(80); updateServoStatus(80); servoAutoMulai = millis(); servoAutoAktif = true; lastFeedingTime = millis(); String pesan = "🐹 AUTO FEEDING DIMULAI\n"; pesan += "Interval: 1 Menit\n"; pesan += "Status: Pakan Dibuka\n"; kirimTelegram(pesan); // Blynk.virtualWrite(V9, "Auto Feeding Dimulai"); kirimLogFeeding("Auto Feeding Dimulai"); } // === TUTUP SETELAH DURASI === if (servoAutoAktif && millis() - servoAutoMulai >= DURASI_BUKA_SERVO) { servo.write(0); updateServoStatus(0); servoAutoAktif = false; String pesan = "🐹 AUTO FEEDING SELESAI\n"; pesan += "Status: Pakan Ditutup\n"; kirimTelegram(pesan); // Blynk.virtualWrite(V9, "Auto Feeding Selesai"); kirimLogFeeding("Auto Feeding Selesai"); } } void kirimSuhuTelegram() { if (dhtValid) { String pesan = "🌔 Update Suhu:\n"; pesan += "Suhu saat ini: "; pesan += String(lastSuhu); pesan += " °C"; kirimTelegram(pesan); } }