// ===================== KONFIGURASI BLYNK =====================
#define BLYNK_TEMPLATE_ID "TMPL6NypHt4sx"
#define BLYNK_TEMPLATE_NAME "SWARFS"
#define BLYNK_AUTH_TOKEN "PQUb-i3lkXqejChco-olCxAP5hqMQQto"
#define BLYNK_PRINT Serial
#include <WiFi.h>
#include <HTTPClient.h>
#include <BlynkSimpleEsp32.h>
#include "time.h"
#include <vector>
#include <ArduinoJson.h>
#include <Preferences.h>
// ===================== MODE =====================
#define WOKWI_MODE true
// ===================== PIN =====================
const int ledPin = 5;
const int triggerPin = 27;
const int echoPin = 26;
// ===================== WiFi & Blynk =====================
char ssid[] = "Wokwi-GUEST";
char password[] = "";
char auth[] = BLYNK_AUTH_TOKEN;
// ===================== Variabel sensor & interval =====================
long duration;
float Jarak=0, TinggiAir=0, Elevasi=0;
float TinggiSensor = 400;
float ElevasiDasar = 5000;
unsigned long lastSend = 0;
unsigned long intervalMs = 5000;
// ===================== Variabel threshold level =====================
float levelAmanMax = 200;
float levelWaspadaMax = 300;
// ===================== Google Sheets =====================
String googleScriptURL = "ISI_URL_WEB_APP_GOOGLE_SCRIPT";
// ===================== NTP =====================
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 7*3600;
const int daylightOffset_sec = 0;
// ===================== Variabel LED Blink =====================
unsigned long lastBlink = 0;
bool ledState = false;
// ===================== Variabel periode rata-rata =====================
unsigned long periodeRata2 = 60; // menit
// ===================== Simpan sample untuk tren =====================
std::vector<float> tinggiSamples;
// ===================== Virtual Pin BMKG =====================
String bmkgStationID = "5002333"; // default wilayah (Jakarta)
String cuacaSekarang = "";
// ===================== Preferences =====================
Preferences prefs;
// ===================== Counter data =====================
unsigned int dataCounter = 0;
// ===================== Helper =====================
void clearKecepatanTren(){
int pins[] = {V8, V9, V16, V17, V18};
for(int i=0;i<5;i++){
Blynk.virtualWrite(pins[i], "⏳");
Blynk.setProperty(pins[i], "color", "#6B7280");
}
}
void resetForecastV31V33() {
Blynk.virtualWrite(V31, "⏳Menunggu data…"); Blynk.setProperty(V31,"color","#6B7280");
Blynk.virtualWrite(V32, "⏳Menunggu data…"); Blynk.setProperty(V32,"color","#6B7280");
Blynk.virtualWrite(V33, "⏳Menunggu data…"); Blynk.setProperty(V33,"color","#6B7280");
}
// ===================== Fungsi tampilkan konfigurasi =====================
void tampilkanNilaiKonfigurasi() {
Serial.println("=== Nilai Konfigurasi Saat Ini ===");
Serial.printf("Interval : %lu detik\n", intervalMs/1000);
Serial.printf("Tinggi Sensor : %.2f cm\n", TinggiSensor);
Serial.printf("Elevasi Dasar : %.2f cm\n", ElevasiDasar);
Serial.printf("Level Aman : %.2f cm\n", levelAmanMax);
Serial.printf("Level Waspada : %.2f cm\n", levelWaspadaMax);
Serial.printf("Periode Rata-rata: %lu menit\n", periodeRata2);
Serial.printf("ID Stasiun BMKG : %s\n", bmkgStationID.c_str());
Serial.println("=================================");
}
// ===================== Fungsi sensor ultrasonik =====================
float bacaTinggiAir() {
digitalWrite(triggerPin, LOW); delayMicroseconds(2);
digitalWrite(triggerPin, HIGH); delayMicroseconds(10);
digitalWrite(triggerPin, LOW);
duration = pulseIn(echoPin, HIGH, 30000);
if(duration == 0) return 0;
Jarak = duration * 0.034 / 2;
TinggiAir = TinggiSensor - Jarak;
if(TinggiAir < 0) TinggiAir = 0;
Elevasi = ElevasiDasar + TinggiAir;
return TinggiAir;
}
// ===================== Fungsi Ambil Cuaca BMKG =====================
void ambilCuacaBMKG() {
String payload;
if (WOKWI_MODE) {
payload = R"([
{"jam":"0","cuaca":"Cerah","temp":"25"},
{"jam":"3","cuaca":"Berawan","temp":"24"},
{"jam":"6","cuaca":"Ringan","temp":"23"},
{"jam":"9","cuaca":"Cerah","temp":"27"},
{"jam":"12","cuaca":"Ringan","temp":"28"},
{"jam":"15","cuaca":"Lebat","temp":"30"},
{"jam":"18","cuaca":"Cerah","temp":"27"},
{"jam":"21","cuaca":"Berawan","temp":"26"}
])";
} else {
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
String url = "https://ibnux.github.io/BMKG-importer/cuaca/" + bmkgStationID + ".json";
http.begin(url);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) payload = http.getString();
http.end();
}
}
if(payload.length()>0){
DynamicJsonDocument doc(4096);
if(deserializeJson(doc,payload)==DeserializationError::Ok && doc.is<JsonArray>()){
struct tm timeinfo; getLocalTime(&timeinfo);
int jamSekarang = timeinfo.tm_hour;
for(int i=0;i<doc.size();i++){
int jamMulai = doc[i]["jam"].as<int>();
int jamAkhir = (jamMulai+3)%24;
if((jamSekarang>=jamMulai && jamSekarang<jamAkhir) ||
(jamAkhir<jamMulai && (jamSekarang>=jamMulai || jamSekarang<jamAkhir))){
String cuaca = doc[i]["cuaca"].as<String>();
String temp = doc[i]["temp"].as<String>();
String icon = (cuaca.indexOf("Cerah")>=0)?"☀️":(cuaca.indexOf("Lebat")>=0)?"⛈️":(cuaca.indexOf("Hujan")>=0 || cuaca.indexOf("Ringan")>=0)?"🌦️":(cuaca.indexOf("Berawan")>=0)?"⛅":"❓";
cuacaSekarang = icon + " " + cuaca + " | " + temp + "°C";
Blynk.virtualWrite(V19, cuacaSekarang);
Serial.printf("[BMKG] %s\n", cuacaSekarang.c_str());
break;
}
}
} else Serial.println("[BMKG] Gagal parsing JSON");
}
}
// ===================== Koreksi durasi berdasarkan cuaca =====================
float koreksiDurasiCuaca(String cuaca, float menit){
if(cuaca.indexOf("Lebat")>=0 || cuaca.indexOf("Hujan")>=0) return menit * 0.7;
else if(cuaca.indexOf("Ringan")>=0) return menit * 0.85;
else return menit;
}
// ===================== Forecast V16–V18 =====================
void updateV16V18(float tinggiAir, float kecepatan, String cuaca, String &teks16, String &teks17, String &teks18, bool gunakanCuaca=false){
teks16="-"; teks17="-"; teks18="-";
auto durasiMenit = [&](float from, float to, float kecepatan){
if(abs(kecepatan)<0.1) return -1;
float menit = (to - from) / kecepatan * 60.0;
if(gunakanCuaca) menit = koreksiDurasiCuaca(cuaca, menit);
if(menit < 0) return -1;
return (int)round(menit);
};
if(kecepatan > 0.5){
if(tinggiAir <= levelAmanMax){
teks16 = "⬆️ " + String(durasiMenit(tinggiAir, levelWaspadaMax, kecepatan)) + " menit → ❌";
teks17 = "⬆️ " + String(durasiMenit(tinggiAir, levelAmanMax, kecepatan)) + " menit → ⚠️";
teks18 = "⬆️ ✅";
} else if(tinggiAir <= levelWaspadaMax){
teks16 = "⬆️ " + String(durasiMenit(tinggiAir, levelWaspadaMax, kecepatan)) + " menit → ❌";
teks17 = "⬆️ ⚠️";
teks18 = "-";
} else { teks16 = "⬆️ ❌"; teks17="-"; teks18="-"; }
} else if(kecepatan < -0.5){
if(tinggiAir > levelWaspadaMax){
teks16 = "⬇️ ❌";
teks17 = "⬇️ " + String(durasiMenit(tinggiAir, levelWaspadaMax, kecepatan)) + " menit → ⚠️";
teks18 = "⬇️ " + String(durasiMenit(tinggiAir, levelAmanMax, kecepatan)) + " menit → ✅";
} else if(tinggiAir > levelAmanMax){
teks16="-";
teks17="⬇️ ⚠️";
teks18="⬇️ " + String(durasiMenit(tinggiAir, levelAmanMax, kecepatan)) + " menit → ✅";
} else { teks16="-"; teks17="-"; teks18="⬇️ ✅"; }
} else { teks16="-"; teks17="-"; teks18="-"; }
auto warna = [](String teks){
if(teks=="-") return "#808080";
else if(teks.indexOf("✅")>=0) return "#4AB765";
else if(teks.indexOf("⚠️")>=0) return "#F5B800";
else return "#EC4751";
};
Blynk.virtualWrite(V16, teks16); Blynk.setProperty(V16,"color",warna(teks16));
Blynk.virtualWrite(V17, teks17); Blynk.setProperty(V17,"color",warna(teks17));
Blynk.virtualWrite(V18, teks18); Blynk.setProperty(V18,"color",warna(teks18));
}
// ===================== Fungsi tampilkan Forecast di Serial =====================
void tampilkanForecastSerialVertikal(String teks16, String teks17, String teks18){
Serial.println("===== TIME FORECAST =====");
Serial.println(teks16);
Serial.println(teks17);
Serial.println(teks18);
Serial.println("=============================");
}
// ===================== Forecast V31–V33 =====================
String nilaiV31, nilaiV32, nilaiV33;
String prediksiLevelV32Gabungan(float tinggiAir, float kecepatan, String cuaca){
String teks16="-", teks17="-", teks18="-";
updateV16V18(tinggiAir, kecepatan, cuaca, teks16, teks17, teks18, true);
return teks16 + " | " + teks17 + " | " + teks18;
}
void tampilkanForecastTerintegrasiSerial(String v31, String v32, String v33){
Serial.println("===== FORECAST TERINTEGRASI =====");
Serial.println(v31);
Serial.println(v32);
Serial.println(v33);
Serial.println("================================");
}
void kirimForecastV31V33(float tinggiAir, float kecepatan, String cuaca, String tren){
String status="", prediksi="", trenCuaca="";
if(tinggiAir <= levelAmanMax) { status="✅ Aman"; Blynk.setProperty(V31,"color","#4AB765"); }
else if(tinggiAir <= levelWaspadaMax){ status="⚠️ Waspada"; Blynk.setProperty(V31,"color","#F5B800"); }
else { status="❌ Bahaya"; Blynk.setProperty(V31,"color","#EC4751"); }
prediksi = prediksiLevelV32Gabungan(tinggiAir, kecepatan, cuaca);
trenCuaca = tren + " " + cuaca;
nilaiV31 = "Status : " + status;
nilaiV32 = "Prediksi : " + prediksi;
nilaiV33 = "Kondisi : " + trenCuaca;
Blynk.virtualWrite(V31, nilaiV31);
Blynk.virtualWrite(V32, nilaiV32);
Blynk.virtualWrite(V33, nilaiV33);
tampilkanForecastTerintegrasiSerial(nilaiV31, nilaiV32, nilaiV33);
}
// ===================== Serial Monitor lengkap =====================
void tampilkanSerialMonitor(float Jarak, float TinggiAir, float Elevasi, float kecepatan, String tren, String cuacaSekarang){
struct tm timeinfo; getLocalTime(&timeinfo);
char waktu[25]; strftime(waktu,sizeof(waktu),"%Y-%m-%d %H:%M:%S",&timeinfo);
Serial.println("========== DATA AIR ==========");
Serial.printf("Waktu : %s\n", waktu);
Serial.printf("Jarak Sensor : %.2f cm\n", Jarak);
Serial.printf("Tinggi Air : %.2f cm\n", TinggiAir);
Serial.printf("Elevasi : %.2f m\n", Elevasi/100);
Serial.printf("Cuaca : %s\n", cuacaSekarang.c_str());
Serial.println("==============================");
}
// ===================== Serial Analisis Dinamika Air =====================
void tampilkanAnalisisDinamikaAir(float kecepatan, String tren){
struct tm timeinfo; getLocalTime(&timeinfo);
char waktu[25]; strftime(waktu,sizeof(waktu),"%Y-%m-%d %H:%M:%S",&timeinfo);
Serial.println("===== ANALISIS DINAMIKA AIR =====");
Serial.printf("Waktu : %s\n", waktu);
Serial.printf("Kecepatan Air : %.2f cm/jam\n", kecepatan);
Serial.printf("Tren : %s\n", tren.c_str());
Serial.println("=================================");
}
// ===================== Sinkronisasi NTP =====================
bool waktuSinkron = false;
void sinkronisasiWaktu() {
struct tm timeinfo;
if (getLocalTime(&timeinfo)) waktuSinkron = true;
else {
Serial.println("[NTP] Belum sinkron, coba lagi...");
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
waktuSinkron = false;
}
}
// ===================== Handler BLYNK_WRITE =====================
BLYNK_WRITE(V10){ intervalMs = param.asInt()*1000; Serial.printf("[BLYNK] V10 diubah menjadi: %d\n", param.asInt()); tampilkanNilaiKonfigurasi(); }
BLYNK_WRITE(V11){ TinggiSensor = param.asInt(); Serial.printf("[BLYNK] V11 diubah menjadi: %d\n", param.asInt()); tampilkanNilaiKonfigurasi(); }
BLYNK_WRITE(V12){ ElevasiDasar = param.asInt(); Serial.printf("[BLYNK] V12 diubah menjadi: %d\n", param.asInt()); tampilkanNilaiKonfigurasi(); }
BLYNK_WRITE(V13){ levelAmanMax = param.asInt(); Serial.printf("[BLYNK] V13 diubah menjadi: %d\n", param.asInt()); tampilkanNilaiKonfigurasi(); }
BLYNK_WRITE(V14){ levelWaspadaMax = param.asInt(); Serial.printf("[BLYNK] V14 diubah menjadi: %d\n", param.asInt()); tampilkanNilaiKonfigurasi(); }
BLYNK_WRITE(V15){ periodeRata2 = param.asInt(); Serial.printf("[BLYNK] V15 diubah menjadi: %d\n", param.asInt()); tampilkanNilaiKonfigurasi(); }
BLYNK_WRITE(V21){ bmkgStationID = String(param.asStr()); Serial.printf("[BLYNK] V21 diubah menjadi: %s\n", bmkgStationID.c_str()); tampilkanNilaiKonfigurasi(); }
// ===================== Setup =====================
void setup(){
Serial.begin(115200);
pinMode(ledPin,OUTPUT); pinMode(triggerPin,OUTPUT); pinMode(echoPin,INPUT);
prefs.begin("bmkg", true);
String saved = prefs.getString("wilayahID","");
prefs.end();
if(saved.length()>0) bmkgStationID=saved;
Blynk.begin(auth,ssid,password);
while (!Blynk.connected()) {
Serial.println("menunggu koneksi Blynk...");
delay(500);
}
Serial.println("terhubung ke Blynk!");
clearKecepatanTren();
resetForecastV31V33();
configTime(gmtOffset_sec,daylightOffset_sec,ntpServer);
sinkronisasiWaktu();
for(int v=10; v<=15; v++) Blynk.setProperty(v,"color","#000000");
Blynk.setProperty(V21,"color","#000000");
Serial.println("=== Nilai Awal Konfigurasi ===");
tampilkanNilaiKonfigurasi();
Serial.println("=== Sistem Monitoring Air Siap ===");
}
// ===================== Loop =====================
unsigned long lastNTP = 0;
unsigned long ntpInterval = 5 * 60 * 1000; // 5 menit
void loop() {
Blynk.run();
unsigned long now = millis();
if(now - lastNTP >= ntpInterval){
lastNTP = now;
sinkronisasiWaktu();
}
if(now - lastBlink >= 500){
lastBlink = now;
digitalWrite(ledPin, (ledState=!ledState));
}
if(now - lastSend >= intervalMs){
lastSend = now;
float tinggiAir = bacaTinggiAir();
ambilCuacaBMKG();
Blynk.virtualWrite(V0,Jarak);
Blynk.virtualWrite(V1,tinggiAir);
Blynk.virtualWrite(V2,Elevasi/100);
// ===== Level Air =====
if(tinggiAir <= levelAmanMax){
Blynk.setProperty(V1,"color","#4AB765"); Blynk.setProperty(V2,"color","#4AB765");
Blynk.setProperty(V5,"color","#4AB765"); Blynk.virtualWrite(V5,255); Blynk.virtualWrite(V6,0);
} else if(tinggiAir <= levelWaspadaMax){
Blynk.setProperty(V1,"color","#F5B800"); Blynk.setProperty(V2,"color","#F5B800");
Blynk.setProperty(V5,"color","#F5B800"); Blynk.virtualWrite(V5,255); Blynk.virtualWrite(V6,1);
} else{
Blynk.setProperty(V1,"color","#EC4751"); Blynk.setProperty(V2,"color","#EC4751");
Blynk.virtualWrite(V5,255); Blynk.virtualWrite(V6,2);
Blynk.logEvent("bahaya","Level air mencapai level BAHAYA!");
}
// ===== Simpan sample =====
tinggiSamples.push_back(tinggiAir);
unsigned long kapasitas = (unsigned long)round(periodeRata2/(intervalMs/1000.0));
if(kapasitas < 1) kapasitas = 1;
if(tinggiSamples.size() > kapasitas)
tinggiSamples.erase(tinggiSamples.begin(), tinggiSamples.begin() + (tinggiSamples.size() - kapasitas));
// ===== Hitung kecepatan =====
float kecepatan = 0;
if(tinggiSamples.size()>=2){
float sumAwal=0, sumAkhir=0;
int n = tinggiSamples.size()>=5?5:tinggiSamples.size();
for(int i=0;i<n;i++) sumAwal+=tinggiSamples[i];
for(int i=tinggiSamples.size()-n;i<tinggiSamples.size();i++) sumAkhir+=tinggiSamples[i];
kecepatan = ((sumAkhir/n - sumAwal/n)/(tinggiSamples.size()*(intervalMs/1000.0)))*3600.0;
}
// ===== Tren =====
String tren, warna;
if(kecepatan>5.0){ tren="⬆️ Naik Cepat"; warna="#EC4751"; }
else if(kecepatan>1.0){ tren="⬆️ Naik Sedang"; warna="#F58D42"; }
else if(kecepatan>0.5){ tren="⬆️ Naik Lambat"; warna="#F5B800"; }
else if(kecepatan<-5.0){ tren="⬇️ Turun Cepat"; warna="#4AB765"; }
else if(kecepatan<-1.0){ tren="⬇️ Turun Sedang"; warna="#6CCF8C"; }
else if(kecepatan<-0.5){ tren="⬇️ Turun Lambat"; warna="#A8E6A1"; }
else{ tren="➡️ Stabil"; warna="#0000FF"; }
// ===== V8–V9 (kecepatan & tren) =====
dataCounter++; // increment counter
if(dataCounter < 10){
// Data belum cukup → V8 abu-abu
Blynk.virtualWrite(V8, 0);
Blynk.virtualWrite(V9, "⏳");
Blynk.setProperty(V8,"color","#808080");
Blynk.setProperty(V9,"color","#6B7280");
} else {
// Data cukup → V8 mengikuti kecepatan dan warna tren
Blynk.virtualWrite(V8, kecepatan);
Blynk.virtualWrite(V9, tren);
Blynk.setProperty(V8,"color",warna);
Blynk.setProperty(V9,"color",warna);
// ===== V16–V18 =====
String teks16, teks17, teks18;
updateV16V18(tinggiAir,kecepatan,cuacaSekarang,teks16,teks17,teks18,false);
tampilkanForecastSerialVertikal(teks16, teks17, teks18);
// ===== V31–V33 =====
kirimForecastV31V33(tinggiAir,kecepatan,cuacaSekarang,tren);
// ===== Analisis Dinamika Air =====
tampilkanAnalisisDinamikaAir(kecepatan, tren);
}
// ===== Serial monitor lengkap =====
tampilkanSerialMonitor(Jarak, TinggiAir, Elevasi, kecepatan, tren, cuacaSekarang);
// ===== Google Sheet =====
if(WiFi.status() == WL_CONNECTED && googleScriptURL.length()>0){
HTTPClient http;
String url = googleScriptURL + "?tinggi=" + String(tinggiAir) + "&kecepatan=" + String(kecepatan) + "&elevasi=" + String(Elevasi/100) + "&tren=" + tren;
http.begin(url);
int code = http.GET();
http.end();
}
}
}
Loading
esp32-devkit-c-v4
esp32-devkit-c-v4