projectTA/anggrek.cpp

778 lines
30 KiB
C++

#include <ESP8266WiFi.h>
#include <FirebaseESP8266.h>
#include <DHT.h>
#include <Wire.h>
#include <BH1750.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
// Konfigurasi WiFi
#define WIFI_SSID "didinganteng"
#define WIFI_PASSWORD "didin123"
// Konfigurasi Firebase
#define FIREBASE_HOST "https://sensoranggrek-3d9ac-default-rtdb.firebaseio.com/"
#define FIREBASE_AUTH "suOXlb5gENTmy6PbRNWsbpWiFOeUVDQoWq61GhRu"
#define DHTPIN D4 // Pin data DHT11 terhubung ke D4
#define DHTTYPE DHT11 // Jenis sensor DHT
// Definisi pin relay
#define RELAY_FAN D5 // Kipas pada relay channel 1
#define RELAY_LIGHT D6 // Lampu pada relay channel 2
#define RELAY_PUMP D7 // Water pump pada relay channel 3
#define RELAY_CAMERA_TRIGGER D3 // Relay untuk mengaktifkan kamera pada jadwal tertentu
// PERBAIKAN 1: Ubah ke pin analog jika sensor mendukung output analog
#define SOIL_MOISTURE_PIN A0 // Ganti ke A0 untuk sensor analog
// Nilai threshold kelembapan tanah untuk media tanam anggrek
const int SOIL_DRY_THRESHOLD = 48; // Di bawah nilai ini dianggap kering (perlu penyiraman)
const int SOIL_OPTIMAL_MIN = 50; // Batas bawah optimal
const int SOIL_OPTIMAL_THRESHOLD = 60; // Di atas nilai ini dianggap optimal
const int SOIL_WET_THRESHOLD = 70; // Di atas nilai ini dianggap terlalu basah
// Nilai threshold intensitas cahaya
const int LIGHT_LOW_THRESHOLD = 8000; // Di bawah nilai ini dianggap rendah (perlu lampu)
const int LIGHT_OPTIMAL_MAX = 11000; // Di atas nilai ini dianggap tinggi
// Konfigurasi Watchdog dan Power Management
#define WATCHDOG_TIMEOUT 60 // 60 detik timeout
#define POWER_STABILIZATION_DELAY 500 // Delay stabilisasi daya
#define MAX_SENSOR_INIT_ATTEMPTS 3
#define MAX_WIFI_CONNECT_ATTEMPTS 30
DHT dht(DHTPIN, DHTTYPE);
BH1750 lightMeter; // Inisialisasi objek BH1750
FirebaseData firebaseData;
FirebaseConfig config;
FirebaseAuth auth;
// Untuk NTP (Network Time Protocol)
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 7 * 3600, 60000);
// Variabel untuk sensor dan perangkat
unsigned long pumpStartTime = 0;
unsigned long pumpCycleTime = 0;
bool pumpCycleOn = false;
const unsigned long PUMP_ON_DURATION = 10000; // 10 detik ON
const unsigned long PUMP_OFF_DURATION = 10000; // 10 detik OFF
// Variabel untuk pengaktifan kamera terjadwal
unsigned long cameraTriggerStartTime = 0;
bool cameraTriggerOn = false;
const unsigned long CAMERA_TRIGGER_DURATION = 300000; // 5 menit (5 * 60 * 1000 ms)
// Variabel untuk melacak status perangkat terakhir
bool lastKipasStatus = false;
bool lastLampuStatus = false;
bool lastPumpStatus = false;
bool lastCamStatus = false;
bool lastCameraTriggerStatus = false;
String lastLampReason = "";
// Variabel untuk melacak status sensor
bool dht11Status = false;
bool bh1750Status = false;
bool soilMoistureStatus = false;
// Variabel untuk throttling pengiriman data
unsigned long lastSensorUpdateTime = 0;
unsigned long lastDeviceControlTime = 0;
unsigned long lastFirebaseUpdateTime = 0;
const unsigned long sensorUpdateInterval = 5000; // Update setiap 5 detik
const unsigned long deviceControlInterval = 2000; // Kontrol perangkat setiap 2 detik
// Variabel untuk heartbeat status koneksi
unsigned long lastHeartbeatTime = 0;
const unsigned long heartbeatInterval = 30000; // Update status koneksi setiap 30 detik
// Deklarasi fungsi
void checkRestartStatus();
void updateSensorStatus();
void updateSensorData(float temp, float humidity, float lux, int soilMoisture);
void updateHeartbeat();
void checkWiFiConnection();
void checkRestartCommand();
void controlDevices();
void controlPump();
void controlCameraTrigger(); // Fungsi untuk mengontrol pengaktifan kamera terjadwal
void updateDeviceStatus();
String getCurrentTime();
/// Fungsi pembacaan sensor yang ditingkatkan dengan penanganan error
int readSoilMoisture() {
int rawValue;
int readAttempts = 0;
const int MAX_READ_ATTEMPTS = 3;
// Coba baca sensor beberapa kali jika nilainya tidak valid
do {
rawValue = analogRead(SOIL_MOISTURE_PIN);
readAttempts++;
if (rawValue < 0 || rawValue > 1023) {
Serial.println("Error: Pembacaan sensor kelembapan tidak valid!");
delay(100); // Tunggu sebentar sebelum coba lagi
}
} while ((rawValue < 0 || rawValue > 1023) && readAttempts < MAX_READ_ATTEMPTS);
// Jika setelah beberapa percobaan masih error, catat error dan kembalikan nilai default
if (rawValue < 0 || rawValue > 1023) {
Serial.println("Error: Gagal membaca sensor kelembapan tanah setelah beberapa percobaan!");
Firebase.setString(firebaseData, "/logs/soil_moisture/error", "Pembacaan tidak valid setelah " + String(MAX_READ_ATTEMPTS) + " percobaan");
return -1; // Nilai error
}
// Debugging: Tampilkan nilai raw di Serial Monitor
Serial.print("Nilai Raw Soil Moisture: ");
Serial.println(rawValue);
// Kalibrasi nilai (sesuaikan dengan sensor Anda)
// Contoh kalibrasi:
// Nilai 1023 = sangat kering (0% kelembapan)
// Nilai 300 = sangat basah (100% kelembapan)
int moisturePercentage = map(rawValue, 1023, 300, 0, 100);
// Pastikan nilai dalam rentang 0-100%
moisturePercentage = constrain(moisturePercentage, 0, 100);
// Debugging: Tampilkan nilai hasil konversi
Serial.print("Kelembapan Tanah: ");
Serial.print(moisturePercentage);
Serial.println("%");
return moisturePercentage;
}
// Fungsi Tambahan untuk Penanganan Error
void handleError(const String& errorMessage) {
Serial.println("Error: " + errorMessage);
// Log error ke Firebase
Firebase.setString(firebaseData, "/logs/system/error", errorMessage);
delay(1000);
}
void setupPowerManagement() {
system_update_cpu_freq(80);
WiFi.setSleepMode(WIFI_NONE_SLEEP);
WiFi.setPhyMode(WIFI_PHY_MODE_11N);
}
// PERBAIKAN: Implementasi fungsi checkRestartStatus yang lebih baik
void checkRestartStatus() {
if (Firebase.ready() && Firebase.getBool(firebaseData, "/system/restart")) {
if (firebaseData.dataType() == "boolean" && firebaseData.boolData() == true) {
Serial.println("Restart command detected on startup");
Firebase.setString(firebaseData, "/logs/system", "System baru saja di-restart");
Firebase.setBool(firebaseData, "/system/restart", false);
}
}
Firebase.setString(firebaseData, "/logs/system", "System starting up");
}
void setup() {
setupPowerManagement();
ESP.wdtDisable();
delay(POWER_STABILIZATION_DELAY);
Serial.begin(115200);
// Koneksi WiFi
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.print("Menghubungkan ke WiFi");
int wifiAttempts = 0;
while (WiFi.status() != WL_CONNECTED && wifiAttempts < MAX_WIFI_CONNECT_ATTEMPTS) {
Serial.print(".");
delay(500);
wifiAttempts++;
ESP.wdtFeed();
}
if (WiFi.status() != WL_CONNECTED) {
handleError("Gagal terhubung ke WiFi");
delay(5000);
ESP.restart();
}
Serial.println("\nTerhubung ke WiFi");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
// Konfigurasi Firebase
config.host = FIREBASE_HOST;
config.signer.tokens.legacy_token = FIREBASE_AUTH;
Firebase.begin(&config, &auth);
if (!Firebase.ready()) {
handleError("Gagal inisialisasi Firebase");
delay(5000);
ESP.restart();
}
Firebase.reconnectWiFi(true);
Firebase.setMaxRetry(firebaseData, 3);
Firebase.setMaxErrorQueue(firebaseData, 10);
Firebase.setReadTimeout(firebaseData, 1000);
Firebase.setwriteSizeLimit(firebaseData, "tiny");
// Inisialisasi I2C
Wire.begin(4, 5);
Wire.setClock(100000);
// Inisialisasi Sensor BH1750
int bh1750Attempts = 0;
while (!lightMeter.begin() && bh1750Attempts < MAX_SENSOR_INIT_ATTEMPTS) {
Serial.println("Inisialisasi BH1750 gagal, mencoba ulang...");
delay(500);
bh1750Attempts++;
}
bh1750Status = lightMeter.begin();
Firebase.setString(firebaseData, "/logs/bh1750/status",
bh1750Status ? "Terhubung" : "Gagal terhubung");
// Setup relay pins
pinMode(RELAY_FAN, OUTPUT);
pinMode(RELAY_LIGHT, OUTPUT);
pinMode(RELAY_PUMP, OUTPUT);
pinMode(RELAY_CAMERA_TRIGGER, OUTPUT); // Inisialisasi pin trigger kamera
// PERBAIKAN 3: Tidak perlu set pinMode untuk A0 karena sudah analog input secara default
// Kondisi default relay (HIGH = OFF)
digitalWrite(RELAY_FAN, HIGH);
digitalWrite(RELAY_LIGHT, HIGH);
digitalWrite(RELAY_PUMP, HIGH);
digitalWrite(RELAY_CAMERA_TRIGGER, HIGH); // Matikan relay kamera saat inisialisasi
// Inisialisasi NTP Client
timeClient.begin();
timeClient.setTimeOffset(25200); // GMT+7 (WIB)
while(!timeClient.update()) {
timeClient.forceUpdate();
delay(500);
}
// PERBAIKAN: Panggil checkRestartStatus tanpa mengatur restart ke false lagi
checkRestartStatus();
// Baris berikut dihapus karena sudah diatur dalam checkRestartStatus
// Firebase.setBool(firebaseData, "/system/restart", false);
// Inisialisasi DHT11
dht.begin();
// Periksa DHT11
float testHumidity = dht.readHumidity();
float testTemperature = dht.readTemperature();
if (!isnan(testHumidity) && !isnan(testTemperature)) {
dht11Status = true;
Firebase.setString(firebaseData, "/logs/dht11/message", "Perangkat terhubung");
} else {
dht11Status = false;
Firebase.setString(firebaseData, "/logs/dht11/message", "Sensor tidak terhubung");
}
// PERBAIKAN 4: Pengecekan status soil moisture sensor yang lebih baik
int testSoilMoisture = readSoilMoisture();
if (testSoilMoisture >= 0 && testSoilMoisture <= 100) {
soilMoistureStatus = true;
Firebase.setString(firebaseData, "/logs/soil_moisture/message", "Perangkat terhubung");
Firebase.setInt(firebaseData, "/sensor/kelembapan_tanah", testSoilMoisture);
} else {
soilMoistureStatus = false;
Firebase.setString(firebaseData, "/logs/soil_moisture/message", "Sensor tidak terhubung");
}
// Kirim status awal
Firebase.setBool(firebaseData, "/status/connected", true);
Firebase.setString(firebaseData, "/status/last_seen", getCurrentTime());
// Inisialisasi waktu
lastDeviceControlTime = millis();
lastFirebaseUpdateTime = millis();
lastHeartbeatTime = millis();
ESP.wdtEnable(WATCHDOG_TIMEOUT * 1000);
}
void loop() {
ESP.wdtFeed();
unsigned long currentMillis = millis();
if (currentMillis - lastFirebaseUpdateTime >= sensorUpdateInterval) {
lastFirebaseUpdateTime = currentMillis;
// Baca sensor
float humidity = dht.readHumidity();
float temperature = dht.readTemperature();
float lux = lightMeter.readLightLevel();
int soilMoisture = readSoilMoisture();
if (!isnan(temperature) && !isnan(humidity) && lux >= 0 && soilMoisture != -1) {
updateSensorData(temperature, humidity, lux, soilMoisture);
}
if (currentMillis - lastDeviceControlTime >= deviceControlInterval) {
lastDeviceControlTime = currentMillis;
controlDevices();
controlPump();
controlCameraTrigger();
ESP.wdtFeed();
}
updateDeviceStatus();
updateSensorStatus(); // PERBAIKAN: Menambahkan panggilan ke fungsi updateSensorStatus()
updateHeartbeat();
ESP.wdtFeed();
}
timeClient.update();
// PERBAIKAN: Panggil checkRestartCommand setiap iterasi loop
checkRestartCommand();
delay(50);
}
void controlDevices() {
float temperature = dht.readTemperature();
float humidity = dht.readHumidity();
float lightIntensity = lightMeter.readLightLevel();
if (isnan(temperature) || isnan(humidity)) {
Serial.println("Gagal membaca sensor DHT11");
return;
}
timeClient.update();
int currentHour = timeClient.getHours();
bool shouldFanBeOn = false;
String fanReason = "";
if (temperature > 27) {
shouldFanBeOn = true;
fanReason = "Suhu tinggi";
} else if (humidity < 60) {
shouldFanBeOn = true;
fanReason = "Kelembaban rendah";
}
digitalWrite(RELAY_FAN, shouldFanBeOn ? LOW : HIGH);
if (shouldFanBeOn != lastKipasStatus) {
lastKipasStatus = shouldFanBeOn;
Serial.println("Kipas " + String(shouldFanBeOn ? "ON: " + fanReason : "OFF: Kondisi normal"));
}
// Logika baru untuk lampu berdasarkan sensor cahaya BH1750
bool shouldLightBeOn = false;
String lightReason = "";
// Prioritas 1: Mode malam (jam 17:00 - 06:00)
if (currentHour >= 17 || currentHour < 6) {
shouldLightBeOn = true;
lightReason = "Mode malam";
}
// Prioritas 2: Berdasarkan intensitas cahaya
else if (lightIntensity < LIGHT_LOW_THRESHOLD) {
shouldLightBeOn = true;
lightReason = "Intensitas cahaya rendah";
}
// Update status intensitas cahaya ke Firebase
String lightStatus = "";
if (lightIntensity < LIGHT_LOW_THRESHOLD) {
lightStatus = "Rendah";
} else if (lightIntensity <= LIGHT_OPTIMAL_MAX) {
lightStatus = "Optimal";
} else {
lightStatus = "Tinggi";
}
Firebase.setString(firebaseData, "sensor/status_cahaya", lightStatus);
digitalWrite(RELAY_LIGHT, shouldLightBeOn ? LOW : HIGH);
if (shouldLightBeOn != lastLampuStatus || (shouldLightBeOn && lightReason != lastLampReason)) {
lastLampuStatus = shouldLightBeOn;
lastLampReason = lightReason;
Serial.println("Lampu " + String(shouldLightBeOn ? "ON: " + lightReason : "OFF: Kondisi normal"));
if (shouldLightBeOn) {
Firebase.setString(firebaseData, "devices/lampu_reason", lightReason);
}
}
}
void controlPump() {
int soilMoistureValue = readSoilMoisture();
unsigned long currentMillis = millis();
// Jika pembacaan sensor error (-1), jangan lakukan kontrol pompa
if (soilMoistureValue == -1) {
Serial.println("Tidak dapat mengontrol pompa: error pembacaan sensor");
return;
}
// Tentukan status kelembapan tanah
String soilStatus = "";
if (soilMoistureValue < SOIL_DRY_THRESHOLD) {
soilStatus = "Kering";
} else if (soilMoistureValue < SOIL_OPTIMAL_MIN) {
soilStatus = "Cukup";
} else if (soilMoistureValue <= SOIL_OPTIMAL_THRESHOLD) {
soilStatus = "Optimal";
} else if (soilMoistureValue < SOIL_WET_THRESHOLD) {
soilStatus = "Lembab";
} else {
soilStatus = "Terlalu Basah";
}
// Update status ke Firebase
Firebase.setString(firebaseData, "sensor/status_kelembapan_tanah", soilStatus);
// Fungsi helper untuk menghitung elapsed time dengan penanganan overflow
auto calculateElapsedTime = [](unsigned long startTime, unsigned long currentTime) -> unsigned long {
if (currentTime >= startTime) {
return currentTime - startTime;
} else {
// Overflow terjadi
return (ULONG_MAX - startTime) + currentTime + 1;
}
};
// Logika kontrol pompa yang diperbaiki
if (soilMoistureValue < SOIL_DRY_THRESHOLD) {
// Tanah kering - perlu penyiraman dengan siklus
handleDrySoil(currentMillis, soilStatus, calculateElapsedTime);
}
else if (soilMoistureValue < SOIL_OPTIMAL_MIN) {
// Tanah cukup - penyiraman ringan atau hentikan siklus
handleModeratelySoil(currentMillis, soilStatus, calculateElapsedTime);
}
else {
// Tanah sudah optimal/lembab/terlalu basah - matikan pompa
handleWetSoil(soilStatus);
}
// Update status pompa ke Firebase
updatePumpStatus();
}
// Fungsi helper untuk menangani tanah kering
void handleDrySoil(unsigned long currentMillis, String soilStatus,
std::function<unsigned long(unsigned long, unsigned long)> calculateElapsedTime) {
// Jika pompa belum dalam mode siklus, mulai siklus penyiraman
if (pumpCycleTime == 0) {
pumpCycleTime = currentMillis;
pumpCycleOn = true;
digitalWrite(RELAY_PUMP, LOW); // Nyalakan pompa
Firebase.setString(firebaseData, "devices/pump_reason", "Penyiraman otomatis - " + soilStatus);
Serial.println("Pompa ON - Penyiraman dimulai untuk status: " + soilStatus);
}
// Jika dalam siklus, atur ON/OFF sesuai timing
else {
unsigned long elapsedTime = calculateElapsedTime(pumpCycleTime, currentMillis);
// Jika dalam fase ON dan waktunya sudah lewat, ganti ke OFF
if (pumpCycleOn && (elapsedTime >= PUMP_ON_DURATION)) {
pumpCycleTime = currentMillis;
pumpCycleOn = false;
digitalWrite(RELAY_PUMP, HIGH); // Matikan pompa untuk sementara
Serial.println("Pompa OFF - Jeda sementara dalam siklus penyiraman");
}
// Jika dalam fase OFF dan waktunya sudah lewat, ganti ke ON
else if (!pumpCycleOn && (elapsedTime >= PUMP_OFF_DURATION)) {
pumpCycleTime = currentMillis;
pumpCycleOn = true;
digitalWrite(RELAY_PUMP, LOW); // Nyalakan pompa lagi
Serial.println("Pompa ON - Melanjutkan siklus penyiraman");
}
}
}
// Fungsi helper untuk menangani tanah cukup
void handleModeratelySoil(unsigned long currentMillis, String soilStatus,
std::function<unsigned long(unsigned long, unsigned long)> calculateElapsedTime) {
// Jika pompa sedang dalam siklus penyiraman
if (pumpCycleTime != 0) {
unsigned long elapsedTime = calculateElapsedTime(pumpCycleTime, currentMillis);
// Jika sedang dalam fase ON, biarkan selesai dulu satu siklus ON
if (pumpCycleOn && (elapsedTime >= PUMP_ON_DURATION)) {
// Selesaikan fase ON dan langsung hentikan siklus
digitalWrite(RELAY_PUMP, HIGH); // Matikan pompa
pumpCycleTime = 0; // Reset siklus
pumpCycleOn = false;
Firebase.setString(firebaseData, "devices/pump_reason", "Kelembapan tanah sudah " + soilStatus);
Serial.println("Pompa OFF - Siklus dihentikan, kelembapan tanah sudah " + soilStatus);
}
// Jika dalam fase OFF, langsung hentikan siklus
else if (!pumpCycleOn) {
pumpCycleTime = 0; // Reset siklus
// Pompa sudah OFF, cukup reset status
Firebase.setString(firebaseData, "devices/pump_reason", "Kelembapan tanah sudah " + soilStatus);
Serial.println("Siklus pompa dihentikan - Kelembapan tanah sudah " + soilStatus);
}
// Jika sedang ON tapi belum waktunya OFF, biarkan lanjut
}
// Jika pompa tidak dalam siklus dan sedang OFF, biarkan tetap OFF
else if (digitalRead(RELAY_PUMP) == HIGH) {
// Tidak perlu action, pompa sudah OFF
}
// Jika pompa tidak dalam siklus tapi sedang ON (kondisi aneh), matikan
else {
digitalWrite(RELAY_PUMP, HIGH);
Firebase.setString(firebaseData, "devices/pump_reason", "Kelembapan tanah sudah " + soilStatus);
Serial.println("Pompa OFF - Kelembapan tanah sudah " + soilStatus);
}
}
// Fungsi helper untuk menangani tanah basah
void handleWetSoil(String soilStatus) {
// Tanah sudah optimal/lembab/terlalu basah - matikan pompa
if (digitalRead(RELAY_PUMP) != HIGH) {
digitalWrite(RELAY_PUMP, HIGH); // Matikan pompa
pumpCycleTime = 0; // Reset siklus penyiraman
pumpCycleOn = false;
Firebase.setString(firebaseData, "devices/pump_reason", "Kelembapan tanah sudah " + soilStatus);
Serial.println("Pompa OFF - Kelembapan tanah sudah " + soilStatus);
}
}
// Fungsi helper untuk update status pompa
void updatePumpStatus() {
bool currentPumpStatus = (digitalRead(RELAY_PUMP) == LOW);
if (currentPumpStatus != lastPumpStatus) {
lastPumpStatus = currentPumpStatus;
// Retry mechanism untuk Firebase
if (!Firebase.setBool(firebaseData, "devices/pump", currentPumpStatus)) {
Serial.println("Gagal update status pump ke Firebase, mencoba lagi...");
delay(1000);
Firebase.setBool(firebaseData, "devices/pump", currentPumpStatus);
}
if (!Firebase.setString(firebaseData, "devices/pump_timestamp", getCurrentTime())) {
Serial.println("Gagal update pump timestamp ke Firebase");
}
}
}
void controlCameraTrigger() {
// Update waktu NTP dengan penanganan error
if (!timeClient.update()) {
Serial.println("Gagal update NTP, menggunakan waktu terakhir");
// Tetap lanjutkan dengan waktu terakhir yang tersedia
}
// Dapatkan jam saat ini (format 24 jam)
int currentHour = timeClient.getHours();
int currentMinute = timeClient.getMinutes();
int currentSecond = timeClient.getSeconds();
// Konversi ke total detik dalam satu hari untuk perbandingan yang lebih mudah
int currentTimeInSeconds = currentHour * 3600 + currentMinute * 60 + currentSecond;
// Waktu aktivasi kamera pagi: 06:00:00 (6 jam * 3600 detik)
int morningTriggerTime = 6 * 3600;
// Waktu aktivasi kamera sore: 16:00:00 (16 jam * 3600 detik)
int afternoonTriggerTime = 16 * 3600;
// Cek apakah kamera sedang aktif
if (cameraTriggerOn) {
// Penanganan overflow millis() - gunakan unsigned long dan hitung selisih dengan benar
unsigned long currentTime = millis();
unsigned long elapsedTime;
// Hitung elapsed time dengan mempertimbangkan kemungkinan overflow
if (currentTime >= cameraTriggerStartTime) {
elapsedTime = currentTime - cameraTriggerStartTime;
} else {
// Overflow terjadi
elapsedTime = (ULONG_MAX - cameraTriggerStartTime) + currentTime + 1;
}
// Jika kamera sedang aktif, cek apakah sudah waktunya mematikan
if (elapsedTime >= CAMERA_TRIGGER_DURATION) {
// Matikan relay kamera setelah durasi yang ditentukan
digitalWrite(RELAY_CAMERA_TRIGGER, HIGH); // HIGH = OFF
cameraTriggerOn = false;
// Log ke Serial dan Firebase
Serial.println("Kamera dimatikan setelah 5 menit");
Firebase.setString(firebaseData, "devices/camera_trigger_reason", "Pengaktifan kamera selesai");
}
} else {
// Jika kamera tidak aktif, cek apakah sudah waktunya menyalakan (pagi atau sore)
// Perluas toleransi waktu menjadi 60 detik untuk memastikan tidak terlewat
bool shouldTriggerMorning = (currentTimeInSeconds >= morningTriggerTime &&
currentTimeInSeconds < morningTriggerTime + 60);
bool shouldTriggerAfternoon = (currentTimeInSeconds >= afternoonTriggerTime &&
currentTimeInSeconds < afternoonTriggerTime + 60);
// Tambahan: Cek juga apakah sudah lewat 24 jam sejak aktivasi terakhir
// untuk mencegah kamera tidak diaktifkan karena masalah timing
static unsigned long lastTriggerDay = 0;
unsigned long currentDay = currentTimeInSeconds / 86400; // Hari ke-n sejak epoch
if (shouldTriggerMorning || shouldTriggerAfternoon ||
(currentDay > lastTriggerDay && (currentHour == 6 || currentHour == 16))) {
// Aktifkan relay kamera
digitalWrite(RELAY_CAMERA_TRIGGER, LOW); // LOW = ON
cameraTriggerStartTime = millis();
cameraTriggerOn = true;
lastTriggerDay = currentDay;
// Log informasi ke Serial dan Firebase
String triggerReason;
if (shouldTriggerMorning || (currentHour == 6 && currentDay > lastTriggerDay)) {
Serial.println("Aktivasi kamera pagi (06:00) dimulai");
triggerReason = "Aktivasi kamera pagi";
} else {
Serial.println("Aktivasi kamera sore (16:00) dimulai");
triggerReason = "Aktivasi kamera sore";
}
// Tambahkan timestamp untuk debugging
char timeStr[64];
sprintf(timeStr, "%s - %02d:%02d:%02d", triggerReason.c_str(), currentHour, currentMinute, currentSecond);
Firebase.setString(firebaseData, "devices/camera_trigger_reason", timeStr);
}
}
// Update status aktivasi kamera jika ada perubahan
bool currentCameraTriggerStatus = (digitalRead(RELAY_CAMERA_TRIGGER) == LOW);
if (currentCameraTriggerStatus != lastCameraTriggerStatus) {
lastCameraTriggerStatus = currentCameraTriggerStatus;
// Tambahkan retry untuk Firebase jika gagal
if (!Firebase.setBool(firebaseData, "devices/camera_trigger", currentCameraTriggerStatus)) {
Serial.println("Gagal update status camera_trigger ke Firebase");
// Coba lagi setelah delay singkat
delay(1000);
Firebase.setBool(firebaseData, "devices/camera_trigger", currentCameraTriggerStatus);
}
if (!Firebase.setString(firebaseData, "devices/camera_trigger_timestamp", getCurrentTime())) {
Serial.println("Gagal update timestamp ke Firebase");
}
}
// Tambahkan debugging info setiap 1 jam
static unsigned long lastDebugTime = 0;
if (millis() - lastDebugTime > 3600000) { // 1 jam
lastDebugTime = millis();
Serial.printf("Debug: Waktu sekarang %02d:%02d:%02d, Camera aktif: %s\n",
currentHour, currentMinute, currentSecond,
cameraTriggerOn ? "Ya" : "Tidak");
}
}void updateHeartbeat() {
unsigned long currentMillis = millis();
if (currentMillis - lastHeartbeatTime >= heartbeatInterval) {
lastHeartbeatTime = currentMillis;
Firebase.setBool(firebaseData, "/status/connected", true);
Firebase.setString(firebaseData, "/status/last_seen", getCurrentTime());
Serial.println("Heartbeat: Status koneksi diperbarui");
}
}
void updateSensorStatus() {
float testHumidity = dht.readHumidity();
float testTemperature = dht.readTemperature();
bool currentDht11Status = !isnan(testHumidity) && !isnan(testTemperature);
if (currentDht11Status != dht11Status) {
dht11Status = currentDht11Status;
Firebase.setString(firebaseData, "/logs/dht11/message",
dht11Status ? "Perangkat terhubung" : "Sensor tidak terhubung");
}
float testLux = lightMeter.readLightLevel();
bool currentBh1750Status = testLux >= 0;
if (currentBh1750Status != bh1750Status) {
bh1750Status = currentBh1750Status;
Firebase.setString(firebaseData, "/logs/bh1750/message",
bh1750Status ? "Perangkat terhubung" : "Sensor tidak terhubung");
}
int testSoilMoisture = readSoilMoisture();
bool currentSoilMoistureStatus = testSoilMoisture >= 0 && testSoilMoisture <= 100;
if (currentSoilMoistureStatus != soilMoistureStatus) {
soilMoistureStatus = currentSoilMoistureStatus;
Firebase.setString(firebaseData, "/logs/soil_moisture/message",
soilMoistureStatus ? "Perangkat terhubung" : "Sensor tidak terhubung");
}
}
// Perbarui updateSensorData untuk tidak mengirim data kelembapan tanah dua kali
void updateSensorData(float temp, float humidity, float lux, int soilMoisture) {
Firebase.setFloat(firebaseData, "sensor/suhu", temp);
Firebase.setFloat(firebaseData, "sensor/kelembaban", humidity);
Firebase.setFloat(firebaseData, "sensor/cahaya", lux);
Firebase.setInt(firebaseData, "sensor/kelembapan_tanah", soilMoisture);
// Status kelembapan tanah sekarang ditangani di controlPump()
}
void updateDeviceStatus() {
bool fanStatus = (digitalRead(RELAY_FAN) == LOW);
bool lightStatus = (digitalRead(RELAY_LIGHT) == LOW);
bool pumpStatus = (digitalRead(RELAY_PUMP) == LOW);
bool cameraTriggerStatus = (digitalRead(RELAY_CAMERA_TRIGGER) == LOW);
Firebase.setBool(firebaseData, "devices/kipas", fanStatus);
Firebase.setBool(firebaseData, "devices/lampu", lightStatus);
Firebase.setBool(firebaseData, "devices/pump", pumpStatus);
Firebase.setBool(firebaseData, "devices/camera_trigger", cameraTriggerStatus);
if (fanStatus != lastKipasStatus) {
lastKipasStatus = fanStatus;
Firebase.setString(firebaseData, "devices/kipas_timestamp", getCurrentTime());
}
if (lightStatus != lastLampuStatus) {
lastLampuStatus = lightStatus;
Firebase.setString(firebaseData, "devices/lampu_timestamp", getCurrentTime());
}
if (pumpStatus != lastPumpStatus) {
lastPumpStatus = pumpStatus;
Firebase.setString(firebaseData, "devices/pump_timestamp", getCurrentTime());
}
if (cameraTriggerStatus != lastCameraTriggerStatus) {
lastCameraTriggerStatus = cameraTriggerStatus;
Firebase.setString(firebaseData, "devices/camera_trigger_timestamp", getCurrentTime());
}
}
String getCurrentTime() {
timeClient.update();
return timeClient.getFormattedTime();
}
// PERBAIKAN: Implementasi fungsi checkRestartCommand yang lebih robust
void checkRestartCommand() {
if (Firebase.ready() && Firebase.getBool(firebaseData, "/system/restart")) {
// Check if we got a valid response and the value is true
if (firebaseData.dataType() == "boolean" && firebaseData.boolData() == true) {
Serial.println("Restart command detected, restarting system...");
// Log the restart event
Firebase.setString(firebaseData, "/logs/system", "System sedang di restart");
// Set the restart flag back to false
Firebase.setBool(firebaseData, "/system/restart", false);
delay(1000); // Give Firebase some time to update
ESP.restart();
}
}
}