#include #include #include #include #include #include #include // Konfigurasi WiFi const char* ssid = "Combat2024"; const char* password = "12345678"; // Konfigurasi Firebase #define FIREBASE_HOST "https://jago-9a9a6-default-rtdb.firebaseio.com/" #define FIREBASE_AUTH "Q3i1jDmc3Xh9UiUTaGUmOw4tVztV5TJ3INU9RoWN" // NTP Client untuk mendapatkan waktu WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP, "pool.ntp.org"); FirebaseData firebaseData; FirebaseConfig config; FirebaseAuth auth; #define DHTPIN D5 #define RELAY_TEMP D3 // Relay untuk suhu (kipas) #define RELAY_HUMIDITY D6 // Relay untuk kelembapan (lampu) #define DHTTYPE DHT11 DHT dht(DHTPIN, DHTTYPE); LiquidCrystal_I2C lcd(0x27, 16, 2); // Pastikan alamat sesuai dengan modul yang digunakan int ageInWeeks = 1; // Default, nanti diambil dari Firebase // Variabel untuk mode kontrol bool autoModeFan = true; // Default: mode otomatis untuk kipas bool autoModeLight = true; // Default: mode otomatis untuk lampu bool fanStatus = false; // Status kipas (ON/OFF) bool lightStatus = false; // Status lampu (ON/OFF) // Variabel untuk rentang suhu dan kelembapan dinamis struct Range { float min; float max; float target; }; Range tempRanges[4] = { {33, 35, 34}, // Minggu 1 {30, 33, 31.5}, // Minggu 2 {28, 30, 29}, // Minggu 3 {25, 28, 26.5} // Minggu 4 }; Range humidityRanges[4] = { {60, 70, 65}, // Minggu 1 {60, 65, 62.5}, // Minggu 2 {60, 65, 62.5}, // Minggu 3 {55, 60, 57.5} // Minggu 4 }; // Variabel untuk pengaturan tampilan LCD unsigned long lastDisplayToggle = 0; bool showMainDisplay = true; // Untuk mengganti tampilan LCD // Variabel untuk reconnect unsigned long lastReconnectAttempt = 0; const unsigned long reconnectInterval = 30000; // 30 detik // Variabel untuk menyimpan status koneksi WiFi sebelumnya bool previousWiFiStatus = false; // Fungsi untuk logging ke Firebase dengan timestamp void logToFirebase(const String& path, const String& message) { String timestamp = getCurrentTimestamp(); String logMessage = timestamp + ": " + message; // Kirim log ke Firebase, menggunakan setString untuk memperbarui entri Firebase.setString(firebaseData, path, logMessage); } // Fungsi untuk mendapatkan waktu dari NTP String getCurrentTimestamp() { timeClient.update(); return String(timeClient.getEpochTime()); } // Variabel untuk memeriksa koneksi WiFi unsigned long lastWiFiCheck = 0; const unsigned long wifiCheckInterval = 2000; // 2 detik // Fungsi untuk memeriksa dan memulihkan koneksi WiFi void checkWiFiConnection() { if (WiFi.status() != WL_CONNECTED) { unsigned long currentMillis = millis(); // Coba reconnect setiap interval tertentu if (currentMillis - lastReconnectAttempt >= reconnectInterval) { logToFirebase("/logs/wifi", "WiFi Disconnected. Attempting to Reconnect"); // Matikan WiFi dan nyalakan kembali WiFi.disconnect(); delay(1000); WiFi.begin(ssid, password); lastReconnectAttempt = currentMillis; } // Tambahkan log ketika WiFi terputus if (previousWiFiStatus) { logToFirebase("/logs/wifi", "WiFi Connection Failed"); previousWiFiStatus = false; } } else { // Jika WiFi terhubung, periksa apakah statusnya berubah if (!previousWiFiStatus) { logToFirebase("/logs/wifi", "WiFi Connected Successfully"); Serial.println("\nWiFi connected"); previousWiFiStatus = true; } } } void setup() { Serial.begin(115200); // Inisialisasi pin relay pinMode(RELAY_TEMP, OUTPUT); pinMode(RELAY_HUMIDITY, OUTPUT); digitalWrite(RELAY_TEMP, HIGH); // Pastikan relay mulai dalam keadaan OFF digitalWrite(RELAY_HUMIDITY, HIGH); // Inisialisasi LCD dengan deteksi kesalahan logToFirebase("/logs/lcd", "LCD Initialization Started"); // Coba inisialisasi LCD dengan timeout bool lcdInitSuccess = false; Wire.begin(); // Periksa apakah LCD merespons Wire.beginTransmission(0x27); // Alamat I2C LCD byte error = Wire.endTransmission(); if (error == 0) { // LCD ditemukan, coba inisialisasi lcd.init(); lcd.backlight(); lcd.clear(); lcd.begin(16,2); lcd.print("Initializing..."); lcdInitSuccess = true; } if (lcdInitSuccess) { logToFirebase("/logs/lcd", "LCD Ready and Initialized"); Serial.println("LCD initialized successfully"); } else { logToFirebase("/logs/lcd", "LCD Initialization Failed - Check Connections"); Serial.println("LCD initialization failed! Check wiring (GND, VCC, SDA, SCL)"); } // Inisialisasi WiFi WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); // Tunggu koneksi WiFi int attempts = 0; while (WiFi.status() != WL_CONNECTED && attempts < 20) { delay(500); Serial.print("."); attempts++; } if (WiFi.status() == WL_CONNECTED) { logToFirebase("/logs/wifi", "WiFi Connected Successfully"); Serial.println("\nWiFi connected"); // Inisialisasi NTP Client timeClient.begin(); timeClient.setTimeOffset(25200); // Offset untuk WIB (GMT+7) } else { logToFirebase("/logs/wifi", "WiFi Connection Failed"); Serial.println("\nWiFi connection failed"); } // Inisialisasi DHT dht.begin(); logToFirebase("/logs/sensor", "DHT Sensor Initialized"); // Konfigurasi Firebase config.host = FIREBASE_HOST; config.signer.tokens.legacy_token = FIREBASE_AUTH; Firebase.begin(&config, &auth); Firebase.reconnectWiFi(true); // Inisialisasi variabel kontrol di Firebase Firebase.setBool(firebaseData, "/control/fan/auto", autoModeFan); Firebase.setBool(firebaseData, "/control/light/auto", autoModeLight); Firebase.setBool(firebaseData, "/control/fan/status", fanStatus); Firebase.setBool(firebaseData, "/control/light/status", lightStatus); // Inisialisasi rentang di Firebase jika belum ada for (int i = 0; i < 4; i++) { String tempPath = "/ranges/week" + String(i+1) + "/temperature"; String humidityPath = "/ranges/week" + String(i+1) + "/humidity"; // Inisialisasi rentang suhu Firebase.setFloat(firebaseData, tempPath + "/min", tempRanges[i].min); Firebase.setFloat(firebaseData, tempPath + "/max", tempRanges[i].max); Firebase.setFloat(firebaseData, tempPath + "/target", tempRanges[i].target); // Inisialisasi rentang kelembapan Firebase.setFloat(firebaseData, humidityPath + "/min", humidityRanges[i].min); Firebase.setFloat(firebaseData, humidityPath + "/max", humidityRanges[i].max); Firebase.setFloat(firebaseData, humidityPath + "/target", humidityRanges[i].target); } } void loop() { unsigned long currentMillis = millis(); // Periksa koneksi WiFi setiap 2 detik if (currentMillis - lastWiFiCheck >= wifiCheckInterval) { checkWiFiConnection(); lastWiFiCheck = currentMillis; } // Periksa koneksi LCD secara berkala checkLCDConnection(); // Update NTP Client timeClient.update(); // Baca sensor suhu dan kelembapan float temperature = dht.readTemperature(); float humidity = dht.readHumidity(); // Periksa pembacaan sensor if (isnan(temperature) || isnan(humidity)) { logToFirebase("/logs/sensor", "Failed to Read from DHT Sensor"); lcd.clear(); lcd.setCursor(0, 0); lcd.print("Sensor Error!"); lcd.setCursor(0, 1); lcd.print("Check DHT sensor"); delay(2000); return; } else { // Log pembacaan sensor berhasil (setiap 2 detik) static unsigned long lastSensorLog = 0; if (millis() - lastSensorLog > 2000) { // Log setiap 2 detik logToFirebase("/logs/sensor", "DHT Sensor Reading Successful"); lastSensorLog = millis(); } } // Ambil umur ayam dari Firebase if (Firebase.getInt(firebaseData, "/chicken_age")) { ageInWeeks = firebaseData.intData(); Serial.print("Age: "); Serial.println(ageInWeeks); } else { Serial.print("Failed to get age from Firebase: "); Serial.println(firebaseData.errorReason()); // Inisialisasi jika data belum ada if (firebaseData.errorReason() == "path not exist") { Firebase.setInt(firebaseData, "/chicken_age", 1); logToFirebase("/logs/system", "Initialized /chicken_age with default value 1"); } } // Periksa mode kontrol (manual atau otomatis) checkControlMode(); // Ganti tampilan LCD setiap 5 detik if (currentMillis - lastDisplayToggle >= 5000) { showMainDisplay = !showMainDisplay; lastDisplayToggle = currentMillis; } // Update tampilan LCD berdasarkan mode if (showMainDisplay) { updateMainDisplay(temperature, humidity); } else { updateModeDisplay(); } // Kontrol suhu dan kelembapan berdasarkan mode if (autoModeFan) { // Mode otomatis untuk kipas (menggunakan fuzzy logic) controlTemperature(temperature); } else { // Mode manual untuk kipas if (fanStatus) { digitalWrite(RELAY_TEMP, LOW); // Kipas ON } else { digitalWrite(RELAY_TEMP, HIGH); // Kipas OFF } } if (autoModeLight) { // Mode otomatis untuk lampu (menggunakan fuzzy logic) controlHumidity(humidity, temperature); } else { // Mode manual untuk lampu if (lightStatus) { digitalWrite(RELAY_HUMIDITY, LOW); // Lampu ON } else { digitalWrite(RELAY_HUMIDITY, HIGH); // Lampu OFF } } // Kirim data ke Firebase Firebase.setFloat(firebaseData, "/sensor/temperature", temperature); Firebase.setFloat(firebaseData, "/sensor/Humidity", humidity); Firebase.setBool(firebaseData, "/relay/Kipas", digitalRead(RELAY_TEMP) == LOW); Firebase.setBool(firebaseData, "/relay/Lampu", digitalRead(RELAY_HUMIDITY) == LOW); // Perbarui rentang dari Firebase updateRangesFromFirebase(); delay(1000); // Tunggu 1 detik antara siklus } // Fungsi untuk tampilan utama (Temp, Hum, Age) void updateMainDisplay(float temperature, float humidity) { lcd.clear(); lcd.setCursor(0, 0); lcd.print("Temp:"); lcd.print(temperature, 1); lcd.print((char)223); // Simbol derajat lcd.print("C"); lcd.setCursor(0, 1); lcd.print("Hum:"); lcd.print(humidity, 1); lcd.print("% Age:"); lcd.print(ageInWeeks); lcd.print("w"); } // Fungsi untuk tampilan mode (Auto/Manual) void updateModeDisplay() { lcd.clear(); lcd.setCursor(0, 0); lcd.print("Fan: "); lcd.print(autoModeFan ? "AUTO" : "MANUAL"); lcd.print(" "); lcd.print(digitalRead(RELAY_TEMP) == LOW ? "ON" : "OFF"); lcd.setCursor(0, 1); lcd.print("Light: "); lcd.print(autoModeLight ? "AUTO" : "MANUAL"); lcd.print(" "); lcd.print(digitalRead(RELAY_HUMIDITY) == LOW ? "ON" : "OFF"); } // Definisi fungsi fuzzy logic untuk kontrol suhu void controlTemperature(float temp) { float minTemp = tempRanges[ageInWeeks-1].min; float maxTemp = tempRanges[ageInWeeks-1].max; float targetTemp = tempRanges[ageInWeeks-1].target; // Menghitung derajat keanggotaan untuk berbagai kondisi suhu float veryLow = 0; // Suhu sangat rendah (kondisi kritis) float tooLow = 0; // Suhu terlalu rendah float optimal = 0; // Suhu optimal float tooHigh = 0; // Suhu terlalu tinggi float veryHigh = 0; // Suhu sangat tinggi (kondisi kritis) // Keanggotaan "sangat rendah" - kondisi kritis if (temp < minTemp - 2) { veryLow = 1.0; // Sangat rendah, kondisi kritis } else if (temp >= minTemp - 2 && temp < minTemp - 1) { veryLow = (minTemp - 1 - temp); // Agak sangat rendah veryLow = constrain(veryLow, 0, 1); } // Keanggotaan "terlalu rendah" if (temp >= minTemp - 1.5 && temp < minTemp) { tooLow = (minTemp - temp) / 1.5; // Agak rendah tooLow = constrain(tooLow, 0, 1); } // Keanggotaan "optimal" if (temp >= minTemp && temp <= maxTemp) { // Semakin dekat ke targetTemp, semakin optimal optimal = 1.0 - abs(temp - targetTemp) / ((maxTemp - minTemp) / 2.0); optimal = constrain(optimal, 0, 1); } // Keanggotaan "terlalu tinggi" if (temp > maxTemp && temp <= maxTemp + 1.5) { tooHigh = (temp - maxTemp) / 1.5; // Agak tinggi tooHigh = constrain(tooHigh, 0, 1); } // Keanggotaan "sangat tinggi" - kondisi kritis if (temp > maxTemp + 2) { veryHigh = 1.0; // Sangat tinggi, kondisi kritis } else if (temp > maxTemp + 1 && temp <= maxTemp + 2) { veryHigh = (temp - (maxTemp + 1)) / 1.0; // Agak sangat tinggi veryHigh = constrain(veryHigh, 0, 1); } // Simpan status suhu untuk digunakan oleh fungsi controlHumidity bool isTempVeryCold = (veryLow > 0.3); bool isTempTooCold = (tooLow > 0.3); bool isTempVeryHot = (veryHigh > 0.3); // Kirim nilai fuzzy ke Firebase untuk monitoring Firebase.setFloat(firebaseData, "/fuzzy/temperature/veryLow", veryLow); Firebase.setFloat(firebaseData, "/fuzzy/temperature/tooLow", tooLow); Firebase.setFloat(firebaseData, "/fuzzy/temperature/optimal", optimal); Firebase.setFloat(firebaseData, "/fuzzy/temperature/tooHigh", tooHigh); Firebase.setFloat(firebaseData, "/fuzzy/temperature/veryHigh", veryHigh); // Keputusan berdasarkan derajat keanggotaan dan prioritas // Prioritas: Sangat tinggi > Sangat rendah > Terlalu tinggi > Optimal > Terlalu rendah if (veryHigh > 0.3) { // Jika suhu sangat tinggi (kondisi kritis) digitalWrite(RELAY_TEMP, LOW); // Kipas ON pada kecepatan maksimal // Log status ke Firebase Firebase.setString(firebaseData, "/status/temperature", "Sangat Tinggi - Kipas ON (Kritis)"); Firebase.setBool(firebaseData, "/emergency/high_temperature", true); } else if (veryLow > 0.3) { // Jika suhu sangat rendah (kondisi kritis) digitalWrite(RELAY_TEMP, HIGH); // Kipas OFF // Nyalakan lampu untuk membantu meningkatkan suhu // Ini akan di-override oleh controlHumidity jika diperlukan digitalWrite(RELAY_HUMIDITY, LOW); // Lampu ON untuk membantu meningkatkan suhu // Log status ke Firebase Firebase.setString(firebaseData, "/status/temperature", "Sangat Rendah - Kipas OFF, Lampu ON"); Firebase.setBool(firebaseData, "/emergency/low_temperature", true); Firebase.setBool(firebaseData, "/emergency/high_temperature", false); } else if (tooHigh > 0.3) { // Jika suhu terlalu tinggi digitalWrite(RELAY_TEMP, LOW); // Kipas ON // Log status ke Firebase Firebase.setString(firebaseData, "/status/temperature", "Tinggi - Kipas ON"); Firebase.setBool(firebaseData, "/emergency/low_temperature", false); Firebase.setBool(firebaseData, "/emergency/high_temperature", false); } else if (optimal > 0.7) { // Jika suhu sangat optimal digitalWrite(RELAY_TEMP, HIGH); // Kipas OFF // Log status ke Firebase Firebase.setString(firebaseData, "/status/temperature", "Optimal - Kipas OFF"); Firebase.setBool(firebaseData, "/emergency/low_temperature", false); Firebase.setBool(firebaseData, "/emergency/high_temperature", false); } else if (tooLow > 0.3) { // Jika suhu terlalu rendah digitalWrite(RELAY_TEMP, HIGH); // Kipas OFF // Log status ke Firebase Firebase.setString(firebaseData, "/status/temperature", "Rendah - Kipas OFF"); Firebase.setBool(firebaseData, "/emergency/low_temperature", true); Firebase.setBool(firebaseData, "/emergency/high_temperature", false); } else { // Kasus lain, gunakan pendekatan proporsional // Jika suhu mendekati batas atas rentang, nyalakan kipas float distanceToMax = maxTemp - temp; if (distanceToMax < 1.0) { digitalWrite(RELAY_TEMP, LOW); // Kipas ON // Log status ke Firebase Firebase.setString(firebaseData, "/status/temperature", "Mendekati Tinggi - Kipas ON"); } else { digitalWrite(RELAY_TEMP, HIGH); // Kipas OFF // Log status ke Firebase Firebase.setString(firebaseData, "/status/temperature", "Normal - Kipas OFF"); } Firebase.setBool(firebaseData, "/emergency/low_temperature", false); Firebase.setBool(firebaseData, "/emergency/high_temperature", false); } } // Definisi fungsi fuzzy logic untuk kontrol kelembapan void controlHumidity(float humidity, float temperature) { float minHumidity = humidityRanges[ageInWeeks-1].min; float maxHumidity = humidityRanges[ageInWeeks-1].max; float targetHumidity = humidityRanges[ageInWeeks-1].target; float minTemp = tempRanges[ageInWeeks-1].min; float maxTemp = tempRanges[ageInWeeks-1].max; // Periksa apakah suhu terlalu rendah (kondisi darurat) bool isTemperatureEmergency = false; if (temperature < minTemp - 1.5) { isTemperatureEmergency = true; } // Menghitung derajat keanggotaan untuk berbagai kondisi kelembapan float veryLow = 0; // Kelembapan sangat rendah (kondisi kritis) float tooLow = 0; // Kelembapan terlalu rendah float optimal = 0; // Kelembapan optimal float tooHigh = 0; // Kelembapan terlalu tinggi float veryHigh = 0; // Kelembapan sangat tinggi (kondisi kritis) // Keanggotaan "sangat rendah" - kondisi kritis if (humidity < minHumidity - 5) { veryLow = 1.0; // Sangat rendah, kondisi kritis } else if (humidity >= minHumidity - 5 && humidity < minHumidity - 3) { veryLow = (minHumidity - 3 - humidity) / 2.0; // Agak sangat rendah veryLow = constrain(veryLow, 0, 1); } // Keanggotaan "terlalu rendah" if (humidity >= minHumidity - 3 && humidity < minHumidity) { tooLow = (minHumidity - humidity) / 3.0; // Agak rendah tooLow = constrain(tooLow, 0, 1); } // Keanggotaan "optimal" if (humidity >= minHumidity && humidity <= maxHumidity) { // Semakin dekat ke targetHumidity, semakin optimal optimal = 1.0 - abs(humidity - targetHumidity) / ((maxHumidity - minHumidity) / 2.0); optimal = constrain(optimal, 0, 1); } // Keanggotaan "terlalu tinggi" if (humidity > maxHumidity && humidity <= maxHumidity + 3) { tooHigh = (humidity - maxHumidity) / 3.0; // Agak tinggi tooHigh = constrain(tooHigh, 0, 1); } // Keanggotaan "sangat tinggi" - kondisi kritis if (humidity > maxHumidity + 5) { veryHigh = 1.0; // Sangat tinggi, kondisi kritis } else if (humidity > maxHumidity + 3 && humidity <= maxHumidity + 5) { veryHigh = (humidity - (maxHumidity + 3)) / 2.0; // Agak sangat tinggi veryHigh = constrain(veryHigh, 0, 1); } // Kirim nilai fuzzy ke Firebase untuk monitoring Firebase.setFloat(firebaseData, "/fuzzy/humidity/veryLow", veryLow); Firebase.setFloat(firebaseData, "/fuzzy/humidity/tooLow", tooLow); Firebase.setFloat(firebaseData, "/fuzzy/humidity/optimal", optimal); Firebase.setFloat(firebaseData, "/fuzzy/humidity/tooHigh", tooHigh); Firebase.setFloat(firebaseData, "/fuzzy/humidity/veryHigh", veryHigh); // Keputusan berdasarkan derajat keanggotaan dan prioritas // Jika suhu dalam kondisi darurat, prioritaskan pemanasan if (isTemperatureEmergency) { digitalWrite(RELAY_HUMIDITY, LOW); // Lampu ON untuk membantu meningkatkan suhu // Log status ke Firebase Firebase.setString(firebaseData, "/status/humidity", "Suhu Darurat - Lampu ON untuk Pemanasan"); Firebase.setBool(firebaseData, "/emergency/low_humidity", false); // Reset emergency flag Firebase.setBool(firebaseData, "/emergency/high_humidity", false); // Reset emergency flag return; // Keluar dari fungsi, prioritaskan penanganan suhu } // Prioritas: Kelembapan sangat tinggi > Kelembapan sangat rendah > Terlalu tinggi > Optimal > Terlalu rendah if (veryHigh > 0.3) { // Jika kelembapan sangat tinggi (kondisi kritis) digitalWrite(RELAY_HUMIDITY, LOW); // Lampu ON untuk mengurangi kelembapan // Log status ke Firebase Firebase.setString(firebaseData, "/status/humidity", "Sangat Tinggi - Lampu ON (Kritis)"); Firebase.setBool(firebaseData, "/emergency/high_humidity", true); Firebase.setBool(firebaseData, "/emergency/low_humidity", false); } else if (veryLow > 0.3) { // Jika kelembapan sangat rendah (kondisi kritis) digitalWrite(RELAY_HUMIDITY, HIGH); // Lampu OFF untuk mempertahankan kelembapan // Log status ke Firebase Firebase.setString(firebaseData, "/status/humidity", "Sangat Rendah - Lampu OFF"); Firebase.setBool(firebaseData, "/emergency/low_humidity", true); Firebase.setBool(firebaseData, "/emergency/high_humidity", false); } else if (tooHigh > 0.4) { // Jika kelembapan terlalu tinggi digitalWrite(RELAY_HUMIDITY, LOW); // Lampu ON untuk mengurangi kelembapan // Log status ke Firebase Firebase.setString(firebaseData, "/status/humidity", "Tinggi - Lampu ON"); Firebase.setBool(firebaseData, "/emergency/low_humidity", false); Firebase.setBool(firebaseData, "/emergency/high_humidity", false); } else if (optimal > 0.6) { // Jika kelembapan optimal // Periksa juga suhu - jika suhu rendah, nyalakan lampu meskipun kelembapan optimal if (temperature < minTemp) { digitalWrite(RELAY_HUMIDITY, LOW); // Lampu ON untuk membantu meningkatkan suhu // Log status ke Firebase Firebase.setString(firebaseData, "/status/humidity", "Optimal, Suhu Rendah - Lampu ON"); } // Jika kelembapan mendekati batas atas rentang optimal, nyalakan lampu else if (humidity > targetHumidity + ((maxHumidity - minHumidity) / 4)) { digitalWrite(RELAY_HUMIDITY, LOW); // Lampu ON // Log status ke Firebase Firebase.setString(firebaseData, "/status/humidity", "Optimal Tinggi - Lampu ON"); } else { digitalWrite(RELAY_HUMIDITY, HIGH); // Lampu OFF // Log status ke Firebase Firebase.setString(firebaseData, "/status/humidity", "Optimal - Lampu OFF"); } Firebase.setBool(firebaseData, "/emergency/low_humidity", false); Firebase.setBool(firebaseData, "/emergency/high_humidity", false); } else if (tooLow > 0.3) { // Jika kelembapan terlalu rendah // Periksa juga suhu - jika suhu sangat rendah, prioritaskan pemanasan if (temperature < minTemp - 1) { digitalWrite(RELAY_HUMIDITY, LOW); // Lampu ON untuk membantu meningkatkan suhu // Log status ke Firebase Firebase.setString(firebaseData, "/status/humidity", "Rendah, Suhu Sangat Rendah - Lampu ON"); } else { digitalWrite(RELAY_HUMIDITY, HIGH); // Lampu OFF untuk mempertahankan kelembapan // Log status ke Firebase Firebase.setString(firebaseData, "/status/humidity", "Rendah - Lampu OFF"); } Firebase.setBool(firebaseData, "/emergency/low_humidity", true); Firebase.setBool(firebaseData, "/emergency/high_humidity", false); } else { // Kasus lain, gunakan pendekatan proporsional // Periksa juga suhu - jika suhu rendah, nyalakan lampu if (temperature < minTemp) { digitalWrite(RELAY_HUMIDITY, LOW); // Lampu ON untuk membantu meningkatkan suhu // Log status ke Firebase Firebase.setString(firebaseData, "/status/humidity", "Normal, Suhu Rendah - Lampu ON"); } // Jika kelembapan mendekati batas atas, nyalakan lampu else if (humidity > maxHumidity - 1.5) { digitalWrite(RELAY_HUMIDITY, LOW); // Lampu ON // Log status ke Firebase Firebase.setString(firebaseData, "/status/humidity", "Mendekati Tinggi - Lampu ON"); } else { digitalWrite(RELAY_HUMIDITY, HIGH); // Lampu OFF // Log status ke Firebase Firebase.setString(firebaseData, "/status/humidity", "Normal - Lampu OFF"); } Firebase.setBool(firebaseData, "/emergency/low_humidity", false); Firebase.setBool(firebaseData, "/emergency/high_humidity", false); } } // Fungsi untuk memeriksa mode kontrol dari Firebase void checkControlMode() { // Periksa mode kipas (otomatis/manual) if (Firebase.getBool(firebaseData, "/control/fan/auto")) { bool newAutoModeFan = firebaseData.boolData(); if (newAutoModeFan != autoModeFan) { autoModeFan = newAutoModeFan; } } // Periksa mode lampu (otomatis/manual) if (Firebase.getBool(firebaseData, "/control/light/auto")) { bool newAutoModeLight = firebaseData.boolData(); if (newAutoModeLight != autoModeLight) { autoModeLight = newAutoModeLight; } } // Jika dalam mode manual, ambil status dari Firebase if (!autoModeFan) { if (Firebase.getBool(firebaseData, "/control/fan/status")) { bool newFanStatus = firebaseData.boolData(); if (newFanStatus != fanStatus) { fanStatus = newFanStatus; } } } if (!autoModeLight) { if (Firebase.getBool(firebaseData, "/control/light/status")) { bool newLightStatus = firebaseData.boolData(); if (newLightStatus != lightStatus) { lightStatus = newLightStatus; } } } } // Tambahkan fungsi untuk memeriksa LCD secara berkala void checkLCDConnection() { static unsigned long lastLCDCheck = 0; static bool lastLCDStatus = true; // Periksa LCD setiap 30 detik if (millis() - lastLCDCheck > 30000) { Wire.beginTransmission(0x27); // Alamat I2C LCD byte error = Wire.endTransmission(); bool currentLCDStatus = (error == 0); // Jika status berubah, kirim log if (currentLCDStatus != lastLCDStatus) { if (currentLCDStatus) { // LCD terdeteksi kembali, jalankan proses inisialisasi lengkap logToFirebase("/logs/lcd", "LCD Connection Restored"); logToFirebase("/logs/lcd", "LCD Initialization Started"); // Coba inisialisasi LCD lcd.init(); // Panggil init() tanpa memeriksa nilai kembali lcd.backlight(); lcd.clear(); lcd.begin(16,2); lcd.print("Reconnected..."); // Anggap inisialisasi berhasil jika kita sampai di sini logToFirebase("/logs/lcd", "LCD Ready and Initialized"); Serial.println("LCD reinitialized successfully"); } else { logToFirebase("/logs/lcd", "LCD Connection Lost - Check Wiring"); } lastLCDStatus = currentLCDStatus; } lastLCDCheck = millis(); } } // Fungsi untuk memperbarui rentang dari Firebase void updateRangesFromFirebase() { static unsigned long lastUpdate = 0; if (millis() - lastUpdate > 5000) { // Update setiap 5 detik String path = "/ranges/week" + String(ageInWeeks); // Ambil rentang suhu if (Firebase.getFloat(firebaseData, path + "/temperature/min")) { tempRanges[ageInWeeks-1].min = firebaseData.floatData(); } if (Firebase.getFloat(firebaseData, path + "/temperature/max")) { tempRanges[ageInWeeks-1].max = firebaseData.floatData(); } if (Firebase.getFloat(firebaseData, path + "/temperature/target")) { tempRanges[ageInWeeks-1].target = firebaseData.floatData(); } // Ambil rentang kelembapan if (Firebase.getFloat(firebaseData, path + "/humidity/min")) { humidityRanges[ageInWeeks-1].min = firebaseData.floatData(); } if (Firebase.getFloat(firebaseData, path + "/humidity/max")) { humidityRanges[ageInWeeks-1].max = firebaseData.floatData(); } if (Firebase.getFloat(firebaseData, path + "/humidity/target")) { humidityRanges[ageInWeeks-1].target = firebaseData.floatData(); } lastUpdate = millis(); } }