778 lines
30 KiB
C++
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();
|
|
}
|
|
}
|
|
}
|