289 lines
8.0 KiB
C++
289 lines
8.0 KiB
C++
#include <WiFi.h>
|
|
#include <HTTPClient.h>
|
|
#include <FirebaseESP32.h>
|
|
#include <WiFiManager.h>
|
|
#include <time.h>
|
|
|
|
#define API_KEY "AIzaSyDCtvZMg-Ma0BCjG3eEpEEgs19EJVBTx90"
|
|
#define DATABASE_URL "https://floodwatch-cabc5-default-rtdb.asia-southeast1.firebasedatabase.app/"
|
|
|
|
String namaSungai = "sungai_bedadung";
|
|
|
|
FirebaseData fbdo;
|
|
FirebaseAuth auth;
|
|
FirebaseConfig config;
|
|
|
|
const char* backendHistoriURL = "https://services-flood.vercel.app/api/histori";
|
|
const char* backendNotifikasiURL = "https://services-flood.vercel.app/api/notifikasi";
|
|
|
|
#define LORA_TX 17
|
|
#define LORA_RX 16
|
|
#define M0 25
|
|
#define M1 26
|
|
|
|
#define LED_PIN 15
|
|
#define BUTTON_PIN 4
|
|
|
|
unsigned long lastDataTime = 0;
|
|
unsigned long lastBackendTime = 0;
|
|
unsigned long lastThresholdCheck = 0;
|
|
unsigned long lastDaruratCheck = 0;
|
|
|
|
const unsigned long dataInterval = 5000;
|
|
const unsigned long backendInterval = 60000;
|
|
const unsigned long thresholdInterval = 10000;
|
|
const unsigned long daruratInterval = 5000;
|
|
|
|
float lastKetinggian = 0;
|
|
float lastKetinggianDarurat = 0;
|
|
float nilaiAmbangBatas = 150.0;
|
|
float tinggiSensorDariDasar = 200.0;
|
|
|
|
const float ambangKenaikanDarurat = 30.0; // Lonjakan ketinggian cepat dalam 5 detik
|
|
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
|
|
pinMode(M0, OUTPUT);
|
|
pinMode(M1, OUTPUT);
|
|
digitalWrite(M0, LOW);
|
|
digitalWrite(M1, LOW);
|
|
Serial2.begin(9600, SERIAL_8N1, LORA_RX, LORA_TX);
|
|
delay(500);
|
|
|
|
pinMode(LED_PIN, OUTPUT);
|
|
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
|
digitalWrite(LED_PIN, LOW);
|
|
|
|
if (digitalRead(BUTTON_PIN) == LOW) {
|
|
WiFiManager wm;
|
|
wm.resetSettings();
|
|
delay(1000);
|
|
ESP.restart();
|
|
}
|
|
|
|
WiFi.mode(WIFI_STA);
|
|
WiFiManager wm;
|
|
wm.setTimeout(180);
|
|
if (!wm.autoConnect("FloodWatch-Receiver")) {
|
|
Serial.println("Failed to connect and hit timeout");
|
|
delay(3000);
|
|
ESP.restart();
|
|
}
|
|
|
|
Serial.println("Connected to WiFi: " + WiFi.SSID());
|
|
digitalWrite(LED_PIN, HIGH);
|
|
|
|
configTime(0, 0, "pool.ntp.org", "time.nist.gov");
|
|
|
|
config.api_key = API_KEY;
|
|
config.database_url = DATABASE_URL;
|
|
auth.user.email = "sungaibedadung@gmail.com";
|
|
auth.user.password = "123456";
|
|
Firebase.begin(&config, &auth);
|
|
Firebase.reconnectWiFi(true);
|
|
|
|
Serial.println("📡 Receiver LoRa Flood Detection Ready");
|
|
}
|
|
|
|
void loop() {
|
|
unsigned long currentMillis = millis();
|
|
|
|
if (Serial2.available()) {
|
|
String incoming = Serial2.readStringUntil('\n');
|
|
incoming.trim();
|
|
|
|
Serial.print("📩 Pesan masuk: ");
|
|
Serial.println(incoming);
|
|
|
|
if (incoming.startsWith("DATA:")) {
|
|
processSensorData(incoming);
|
|
lastDataTime = currentMillis;
|
|
} else if (incoming.startsWith("ACK:")) {
|
|
Serial.println("✅ Sender menerima update kalibrasi");
|
|
}
|
|
}
|
|
|
|
if (currentMillis - lastThresholdCheck >= thresholdInterval) {
|
|
lastThresholdCheck = currentMillis;
|
|
checkFirebaseThreshold();
|
|
}
|
|
|
|
if (currentMillis - lastBackendTime >= backendInterval) {
|
|
lastBackendTime = currentMillis;
|
|
sendToBackend(lastKetinggian);
|
|
}
|
|
|
|
checkKenaikanDarurat(); // 🔥 Tambahan untuk deteksi mendadak
|
|
updateLEDStatus();
|
|
checkResetButton();
|
|
}
|
|
|
|
void processSensorData(String data) {
|
|
int kStart = data.indexOf("KETINGGIAN=");
|
|
int hStart = data.indexOf("HUJAN=");
|
|
int endData = data.indexOf(';', hStart);
|
|
|
|
if (kStart != -1 && hStart != -1 && endData != -1) {
|
|
float ketinggian = data.substring(kStart + 11, hStart - 1).toFloat();
|
|
int hujan = data.substring(hStart + 6, endData).toInt();
|
|
|
|
lastKetinggian = ketinggian;
|
|
|
|
Serial.println("📊 Data Sensor:");
|
|
Serial.println(" Ketinggian: " + String(ketinggian) + " cm");
|
|
Serial.println(" Hujan: " + String(hujan) + "%");
|
|
|
|
updateFirebaseData(ketinggian, hujan);
|
|
}
|
|
}
|
|
|
|
void checkFirebaseThreshold() {
|
|
String pathTinggi = "/" + namaSungai + "/kalibrasi/tinggiSensor";
|
|
String pathAmbang = "/" + namaSungai + "/threshold/nilai";
|
|
bool needUpdate = false;
|
|
|
|
if (Firebase.getFloat(fbdo, pathTinggi) && fbdo.httpCode() == 200) {
|
|
float newTinggi = fbdo.floatData();
|
|
if (newTinggi != tinggiSensorDariDasar) {
|
|
tinggiSensorDariDasar = newTinggi;
|
|
Serial.println("🔄 Tinggi sensor diperbarui: " + String(newTinggi));
|
|
needUpdate = true;
|
|
}
|
|
}
|
|
|
|
if (Firebase.getFloat(fbdo, pathAmbang) && fbdo.httpCode() == 200) {
|
|
float newAmbang = fbdo.floatData();
|
|
if (newAmbang != nilaiAmbangBatas) {
|
|
nilaiAmbangBatas = newAmbang;
|
|
Serial.println("🔄 Ambang batas diperbarui: " + String(newAmbang));
|
|
needUpdate = true;
|
|
}
|
|
}
|
|
|
|
if (needUpdate) {
|
|
String cmd = "SETTING:TINGGI=" + String(tinggiSensorDariDasar, 1) +
|
|
",BATAS=" + String(nilaiAmbangBatas, 1) + ";";
|
|
Serial2.println(cmd);
|
|
Serial.println("📤 Mengirim kalibrasi: " + cmd);
|
|
}
|
|
}
|
|
|
|
void updateFirebaseData(float ketinggian, int hujan) {
|
|
String path = "/" + namaSungai;
|
|
|
|
Firebase.setFloat(fbdo, path + "/ketinggian", ketinggian);
|
|
Firebase.setInt(fbdo, path + "/hujan", hujan);
|
|
}
|
|
|
|
void sendToBackend(float ketinggian) {
|
|
if (WiFi.status() != WL_CONNECTED) {
|
|
Serial.println("⚠ WiFi tidak terhubung");
|
|
return;
|
|
}
|
|
|
|
String level = "AMAN";
|
|
if (ketinggian >= nilaiAmbangBatas) {
|
|
level = "BAHAYA";
|
|
} else if (ketinggian >= nilaiAmbangBatas * 0.7) {
|
|
level = "WASPADA";
|
|
}
|
|
|
|
time_t now;
|
|
time(&now);
|
|
|
|
HTTPClient http;
|
|
http.begin(backendHistoriURL);
|
|
http.addHeader("Content-Type", "application/json");
|
|
|
|
String jsonHistori = "{\"sungai\":\"" + namaSungai +
|
|
"\",\"ketinggian\":" + String(ketinggian, 1) +
|
|
",\"timestamp\":" + String(now) +
|
|
",\"level\":\"" + level + "\"}";
|
|
|
|
int httpCode = http.POST(jsonHistori);
|
|
Serial.printf(httpCode > 0 ? "✅ Histori terkirim (HTTP %d)\n" : "❌ Gagal kirim histori (HTTP %d)\n", httpCode);
|
|
http.end();
|
|
|
|
if (level == "WASPADA" || level == "BAHAYA") {
|
|
http.begin(backendNotifikasiURL);
|
|
http.addHeader("Content-Type", "application/json");
|
|
|
|
String jsonNotif = "{\"sungai\":\"" + namaSungai +
|
|
"\",\"ketinggian\":" + String(ketinggian, 1) +
|
|
",\"timestamp\":" + String(now) +
|
|
",\"level\":\"" + level + "\"}";
|
|
|
|
int notifCode = http.POST(jsonNotif);
|
|
Serial.printf(notifCode > 0 ? "📣 Notifikasi terkirim (HTTP %d)\n" : "⚠ Gagal kirim notifikasi (HTTP %d)\n", notifCode);
|
|
http.end();
|
|
}
|
|
}
|
|
|
|
// 🔥 Deteksi Kenaikan Cepat Air
|
|
void checkKenaikanDarurat() {
|
|
unsigned long now = millis();
|
|
if (now - lastDaruratCheck >= daruratInterval) {
|
|
float delta = lastKetinggian - lastKetinggianDarurat;
|
|
|
|
if (delta >= ambangKenaikanDarurat && lastKetinggian >= nilaiAmbangBatas * 0.7) {
|
|
Serial.println("🚨 Deteksi banjir mendadak! Naik cepat.");
|
|
kirimNotifikasiDarurat();
|
|
}
|
|
|
|
lastKetinggianDarurat = lastKetinggian;
|
|
lastDaruratCheck = now;
|
|
}
|
|
}
|
|
|
|
// 🔔 Kirim Notifikasi Darurat Langsung
|
|
void kirimNotifikasiDarurat() {
|
|
if (WiFi.status() != WL_CONNECTED) return;
|
|
|
|
time_t now;
|
|
time(&now);
|
|
|
|
HTTPClient http;
|
|
http.begin(backendNotifikasiURL);
|
|
http.addHeader("Content-Type", "application/json");
|
|
|
|
String jsonNotif = "{\"sungai\":\"" + namaSungai +
|
|
"\",\"ketinggian\":" + String(lastKetinggian, 1) +
|
|
",\"timestamp\":" + String(now) +
|
|
",\"level\":\"DARURAT\"}";
|
|
|
|
int code = http.POST(jsonNotif);
|
|
Serial.printf(code > 0 ? "🚨 Notif DARURAT dikirim (HTTP %d)\n" : "❌ Gagal kirim notif darurat (HTTP %d)\n", code);
|
|
http.end();
|
|
}
|
|
|
|
void updateLEDStatus() {
|
|
static unsigned long lastBlink = 0;
|
|
static bool ledState = false;
|
|
|
|
if (WiFi.status() == WL_CONNECTED) {
|
|
digitalWrite(LED_PIN, HIGH);
|
|
} else {
|
|
unsigned long now = millis();
|
|
if (now - lastBlink >= 500) {
|
|
lastBlink = now;
|
|
ledState = !ledState;
|
|
digitalWrite(LED_PIN, ledState);
|
|
}
|
|
}
|
|
}
|
|
|
|
void checkResetButton() {
|
|
static unsigned long lastPress = 0;
|
|
|
|
if (digitalRead(BUTTON_PIN) == LOW && millis() - lastPress > 200) {
|
|
lastPress = millis();
|
|
Serial.println("🔄 Tombol reset WiFi ditekan");
|
|
digitalWrite(LED_PIN, LOW);
|
|
|
|
WiFiManager wm;
|
|
wm.resetSettings();
|
|
delay(1000);
|
|
ESP.restart();
|
|
}
|
|
} |