diff --git a/reciver_lora_banjir.ino b/reciver_lora_banjir.ino new file mode 100644 index 0000000..4daa147 --- /dev/null +++ b/reciver_lora_banjir.ino @@ -0,0 +1,289 @@ +#include +#include +#include +#include +#include + +#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(); + } +} \ No newline at end of file diff --git a/sender_lora_banjir.ino b/sender_lora_banjir.ino new file mode 100644 index 0000000..a51ebf1 --- /dev/null +++ b/sender_lora_banjir.ino @@ -0,0 +1,160 @@ +#include + +// -------------------- Pin Konfigurasi -------------------- +#define LORA_TX 17 +#define LORA_RX 16 +#define M0 25 +#define M1 26 +#define TRIG_PIN 5 +#define ECHO_PIN 18 +#define RAIN_SENSOR_PIN 34 +#define BUZZER_PIN 27 + +// -------------------- Variabel Sensor -------------------- +float ketinggianAir; +int rainPercent; + +// -------------------- Kalibrasi -------------------- +float tinggiSensorDariDasar = 200.0; +float batasAmanKetinggian = 150.0; + +// -------------------- Timing & Filter -------------------- +unsigned long lastSendTime = 0; +const unsigned long sendInterval = 2000; + +#define FILTER_SAMPLES 5 +float distanceSamples[FILTER_SAMPLES]; +int currentSample = 0; + +// -------------------- Buzzer -------------------- +unsigned long lastBuzzerTime = 0; +const unsigned long buzzerInterval = 200; +bool buzzerState = false; + +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); + + Serial.println("🚀 Sender LoRa Flood Detection Ready"); + + pinMode(TRIG_PIN, OUTPUT); + pinMode(ECHO_PIN, INPUT); + pinMode(RAIN_SENSOR_PIN, INPUT); + pinMode(BUZZER_PIN, OUTPUT); + digitalWrite(BUZZER_PIN, LOW); + + for (int i = 0; i < FILTER_SAMPLES; i++) { + distanceSamples[i] = tinggiSensorDariDasar; + } +} + +void loop() { + unsigned long currentMillis = millis(); + + processIncomingMessages(); + + float distance = readUltrasonic(); + ketinggianAir = tinggiSensorDariDasar - distance; + ketinggianAir = constrain(ketinggianAir, 0, tinggiSensorDariDasar); + + rainPercent = readRainSensor();, + + controlBuzzer(currentMillis); + + if (currentMillis - lastSendTime >= sendInterval) { + lastSendTime = currentMillis; + sendSensorData(); + } + + delay(50); +} + +void processIncomingMessages() { + if (Serial2.available()) { + String incoming = Serial2.readStringUntil('\n'); + incoming.trim(); + + Serial.print("📩 Pesan masuk: "); + Serial.println(incoming); + + if (incoming.startsWith("SETTING:")) { + int tStart = incoming.indexOf("TINGGI="); + int bStart = incoming.indexOf("BATAS="); + int endCmd = incoming.indexOf(';'); + + if (tStart != -1 && bStart != -1 && endCmd != -1) { + float newTinggi = incoming.substring(tStart + 7, bStart - 1).toFloat(); + float newBatas = incoming.substring(bStart + 6, endCmd).toFloat(); + + if (newTinggi > 0 && newBatas > 0) { + tinggiSensorDariDasar = newTinggi; + batasAmanKetinggian = newBatas; + + Serial.println("🔧 Kalibrasi diperbarui:"); + Serial.println(" Tinggi Sensor: " + String(tinggiSensorDariDasar) + " cm"); + Serial.println(" Batas Aman: " + String(batasAmanKetinggian) + " cm"); + + Serial2.println("ACK:SETTING_OK;"); + } + } + } + } +} + +float readUltrasonic() { + digitalWrite(TRIG_PIN, LOW); + delayMicroseconds(2); + digitalWrite(TRIG_PIN, HIGH); + delayMicroseconds(10); + digitalWrite(TRIG_PIN, LOW); + + long duration = pulseIn(ECHO_PIN, HIGH, 30000); + + if (duration == 0) { + Serial.println("⚠ Error: Sensor ultrasonik tidak merespon"); + return tinggiSensorDariDasar; + } + + float distance = duration * 0.034 / 2; + distanceSamples[currentSample] = distance; + currentSample = (currentSample + 1) % FILTER_SAMPLES; + + float avgDistance = 0; + for (int i = 0; i < FILTER_SAMPLES; i++) { + avgDistance += distanceSamples[i]; + } + return avgDistance / FILTER_SAMPLES; +} + +int readRainSensor() { + int rawValue = analogRead(RAIN_SENSOR_PIN); + int percent = map(rawValue, 4095, 0, 0, 100); + return constrain(percent, 0, 100); +} + +void controlBuzzer(unsigned long currentTime) { + if (ketinggianAir >= batasAmanKetinggian) { + if (currentTime - lastBuzzerTime >= buzzerInterval) { + lastBuzzerTime = currentTime; + buzzerState = !buzzerState; + digitalWrite(BUZZER_PIN, buzzerState ? HIGH : LOW); + } + } else { + digitalWrite(BUZZER_PIN, LOW); + buzzerState = false; + } +} + +void sendSensorData() { + String dataStr = "DATA:KETINGGIAN=" + String(ketinggianAir, 1) + + ",HUJAN=" + String(rainPercent) + ";"; + + Serial2.println(dataStr); + Serial.println("📤 Mengirim data: " + dataStr); +} \ No newline at end of file