Disusun Oleh:
| 1. Fatkhur Rozaq | (24051204026) |
| 2. Ahmad Arkanul Falah Al Khalimi | (24051204033) |
| 3. Abdan Isa Ghaffara | (24051204235) |
| 4. Muhammad Naufal Muzaky | (24051204233) |
| 5. Orlinezar Raveda Ar-Rizky | (24051204222) |
BAB I
PENDAHULUAN
1.1 Latar Belakang
Banjir merupakan salah satu bencana yang sering terjadi di Indonesia. Kerugian akibat banjir dapat ditekan apabila kenaikan muka air diketahui lebih awal dan tindakan pengendalian, misalnya membuka pintu air, dilakukan tepat waktu. Pemantauan secara manual membutuhkan petugas yang berjaga terus-menerus, sehingga informasi sering terlambat sampai kepada pihak yang membutuhkan. Internet of Things (IoT) menawarkan cara yang lebih praktis, yaitu sensor mengukur ketinggian air secara otomatis, mikrokontroler memutuskan tingkat kewaspadaan, dan hasilnya dikirim ke layar pemantau melalui jaringan.
Laporan ini membahas Modul 6, yaitu penggunaan protokol MQTT melalui WebSocket untuk membangun aplikasi IoT berbasis HTML dan JavaScript (Modul 6, t.t.). Contoh pada modul, seperti pengiriman data GPS dan palang parkir berbasis QR Code, memperlihatkan alur perintah dari halaman web menuju ESP32 (ahocool-aisi555, 2025). Pada proyek ini alur tersebut dikembangkan menjadi komunikasi dua arah. ESP32 berperan sebagai publisher data sensor sekaligus subscriber perintah, sedangkan dashboard web berperan sebagai subscriber data sekaligus publisher perintah.
Sistem yang dirancang memakai sensor ultrasonik HC-SR04 untuk mengukur jarak permukaan air dari sensor. Tinggi muka air dihitung dari selisih tinggi pemasangan sensor dan jarak terukur, lalu diklasifikasikan menjadi tiga status, yaitu AMAN, WASPADA, dan BAHAYA. Setiap status memicu indikator lokal berupa LED tiga warna, buzzer, dan LCD I2C, serta menggerakkan servo yang mewakili pintu air. Dashboard web menampilkan tangki ketinggian air, grafik, status, dan tombol kontrol untuk berpindah antara mode otomatis dan manual, mengatur sudut pintu air, serta membisukan alarm. Seluruh rangkaian disimulasikan pada platform Wokwi sehingga dapat diuji tanpa perangkat keras fisik.
Dari sisi arsitektur komputer, program pada ESP32 sengaja dibuat tanpa fungsi delay() yang panjang. Pembacaan sensor, pengiriman data, dan bunyi alarm diatur dengan millis() agar fungsi loop() terus berjalan dan koneksi MQTT tetap terlayani. Pola ini memperlihatkan bagaimana pembagian waktu prosesor pada mikrokontroler memengaruhi ketanggapan sistem.
1.2 Rumusan Masalah
1.Bagaimana merancang rangkaian ESP32 dengan sensor ultrasonik, servo, LCD I2C, LED, dan buzzer pada platform simulasi Wokwi?
2.Bagaimana ESP32 menentukan status AMAN, WASPADA, dan BAHAYA dari ketinggian air serta mengendalikan pintu air secara otomatis?
3.Bagaimana membangun dashboard web yang menerima data sensor dan mengirim perintah menggunakan pustaka Paho MQTT melalui WebSocket?
4.Bagaimana menguji sistem dan apa saja keterbatasannya?
1.3 Tujuan Laporan
1.Merancang dan mensimulasikan rangkaian sistem peringatan dini banjir berbasis ESP32 pada platform Wokwi.
2.Menjelaskan logika penentuan status ketinggian air, termasuk penggunaan histeresis, dan pengendalian pintu air otomatis.
3.Menjelaskan mekanisme komunikasi dua arah antara ESP32 dan dashboard web melalui MQTT dan WebSocket.
4.Menguji sistem berdasarkan skenario ketinggian air dan menganalisis kelebihan serta keterbatasannya.
1.4 Manfaat Penulisan
Secara akademis, laporan ini memperjelas penerapan protokol MQTT melalui WebSocket pada kasus nyata, terutama pola komunikasi dua arah dan penulisan program mikrokontroler yang tidak memblokir. Secara praktis, rancangan ini dapat menjadi acuan untuk membuat prototipe pemantauan tinggi muka air pada sungai, waduk, atau saluran drainase, serta sebagai bahan pembelajaran integrasi aplikasi web dan perangkat tertanam.
BAB II
TINJAUAN PUSTAKA
2.1 Dasar Teori
2.1.1 Internet of Things (IoT)
Internet of Things adalah konsep yang menghubungkan perangkat fisik seperti sensor dan aktuator ke jaringan sehingga datanya dapat dipantau dan perangkatnya dapat dikendalikan dari jarak jauh. Pada proyek ini sensor ketinggian air, mikrokontroler, broker, dan dashboard web membentuk satu rantai IoT sederhana dari pengukuran hingga tindakan.
2.1.2 Mikrokontroler ESP32
ESP32 adalah mikrokontroler 32-bit dengan modul Wi-Fi internal sehingga dapat terhubung ke jaringan tanpa modul tambahan. Pada sistem ini ESP32 membaca sensor, menentukan status, menggerakkan aktuator, dan berkomunikasi dengan broker MQTT. Program ditulis dengan Arduino IDE menggunakan bahasa C++.
2.1.3 Sensor Ultrasonik HC-SR04
HC-SR04 mengukur jarak dengan memancarkan gelombang ultrasonik lalu menghitung lama waktu pantulan kembali. Mikrokontroler memberi pulsa 10 mikrodetik pada pin TRIG, kemudian mengukur lebar pulsa pada pin ECHO. Pada program, jarak dihitung dengan rumus jarak (cm) = durasi (µs) × 0,0343 ÷ 2, dengan 0,0343 cm/µs sebagai kecepatan suara dan pembagi dua karena gelombang menempuh jarak pergi dan pulang. Fungsi pulseIn() diberi batas waktu 30 ms agar program tidak menunggu terlalu lama bila tidak ada pantulan.
2.1.4 Servo Motor
Servo adalah aktuator yang sudut porosnya dapat ditentukan oleh program melalui sinyal PWM. Pada sistem ini servo disambungkan ke GPIO 13 dan berperan sebagai pintu air. Sudut 0° berarti pintu tertutup, 45° terbuka setengah, dan 90° terbuka penuh. Pustaka ESP32Servo diatur pada frekuensi 50 Hz dengan lebar pulsa 500 sampai 2400 mikrodetik.
2.1.5 LCD I2C, LED, dan Buzzer
LCD I2C berukuran 20 kolom × 4 baris dengan alamat 0x27 menampilkan level air, status, sudut pintu, dan mode kerja. Antarmuka I2C hanya memerlukan dua jalur data, yaitu SDA (GPIO 21) dan SCL (GPIO 22). Tiga LED hijau, kuning, dan merah menunjukkan status secara visual, sedangkan buzzer pada GPIO 14 memberi peringatan suara dengan pola yang berbeda untuk setiap tingkat bahaya.
2.1.6 WebSocket
WebSocket adalah protokol komunikasi yang memungkinkan browser dan server bertukar data secara dua arah. Menurut materi Modul 6 (t.t.), WebSocket memiliki empat karakteristik utama. Pertama, bersifat full duplex sehingga data dapat mengalir dua arah secara bersamaan. Kedua, memakai koneksi persisten yang tetap terbuka hingga ditutup secara eksplisit, berbeda dengan HTTP yang biasanya ditutup setelah setiap pasangan permintaan dan respons. Ketiga, mendukung data real-time tanpa menunggu permintaan klien. Keempat, beroperasi di atas koneksi TCP. Contoh penerapannya pada ESP32 dapat dilihat pada Random Nerd Tutorials (t.t.).
2.1.7 MQTT
MQTT (Message Queuing Telemetry Transport) adalah protokol pesan ringan berpola publish/subscribe. Komunikasi berlangsung melalui perantara bernama broker. Publisher mengirim pesan ke sebuah topic, sedangkan subscriber mendaftar pada topic tersebut untuk menerima pesan. Tiga fitur MQTT dimanfaatkan pada proyek ini. Pertama, wildcard # yang membuat ESP32 cukup berlangganan satu pola cmd/# untuk menerima semua perintah. Kedua, retained message yang menyimpan pesan terakhir pada broker, dipakai untuk status online. Ketiga, Last Will and Testament (LWT), yaitu pesan yang diterbitkan broker secara otomatis bila klien terputus tiba-tiba, dipakai untuk menandai ESP32 offline.
2.1.8 MQTT melalui WebSocket dan Pustaka Paho
Browser tidak dapat membuka koneksi TCP mentah ke broker MQTT, sehingga halaman web memakai MQTT melalui WebSocket. Pada proyek ini browser terhubung ke broker.hivemq.com melalui port 8884 dengan SSL, sedangkan ESP32 terhubung ke broker yang sama melalui port 1883. Kedua sisi bertemu pada topic yang sama. Cara ini juga membantu bila port MQTT biasa diblokir firewall karena WebSocket memakai port HTTP (Modul 6, t.t.).
Paho adalah proyek Eclipse Foundation yang menyediakan pustaka klien MQTT open source. MQTT dibuat oleh Andy Stanford-Clark (IBM) dan Arlen Nipper pada tahun 1999 untuk aplikasi industri. IBM menyumbangkan kode klien MQTT ke Eclipse pada tahun 2013, dan pustaka Paho tersedia untuk Java, C, JavaScript, serta Python (Modul 6, t.t.). Pada sisi ESP32 dipakai pustaka PubSubClient (O'Leary, t.t.). Pola penulisan klien Paho JavaScript dengan fungsi callback onSuccess, onFailure, dan onMessageArrived mengikuti contoh pada Steve's Internet Guide (t.t.).
2.1.9 Platform Simulasi Wokwi
Wokwi adalah simulator elektronika berbasis web yang mendukung ESP32 beserta berbagai komponen virtual. Wokwi menyediakan jaringan Wi-Fi virtual dengan SSID Wokwi-GUEST sehingga program dapat terhubung ke internet dan broker publik tanpa perangkat keras (Wokwi, t.t.).
2.2 Komponen
Berikut adalah komponen perangkat keras (virtual) dan perangkat lunak yang digunakan pada sistem.
Tabel 1. Komponen Perangkat Keras
| No | Komponen | Fungsi dalam Sistem |
|---|---|---|
| 1 | ESP32 DevKit C V4 | Mikrokontroler utama: membaca sensor, menentukan status, dan berkomunikasi dengan broker MQTT. |
| 2 | Sensor HC-SR04 | Mengukur jarak permukaan air dari sensor. |
| 3 | Servo motor | Simulasi pintu air (0°, 45°, 90°). |
| 4 | LCD I2C 20×4 | Menampilkan level, status, sudut pintu, dan mode. |
| 5 | LED hijau, kuning, merah | Indikator visual status AMAN, WASPADA, BAHAYA. |
| 6 | Resistor 220 Ω (3 buah) | Pembatas arus untuk setiap LED. |
| 7 | Buzzer | Alarm suara dengan pola berbeda pada WASPADA dan BAHAYA. |
| 8 | Kabel jumper virtual dan Wi-Fi Wokwi-GUEST | Menghubungkan komponen dan memberi akses internet ke ESP32. |
Tabel 2. Perangkat Lunak dan Pustaka
| Perangkat / Pustaka | Peran |
|---|---|
| Wokwi | Simulasi rangkaian dan program ESP32. |
| WiFi.h | Menghubungkan ESP32 ke jaringan Wi-Fi. |
| PubSubClient.h | Koneksi MQTT, publish, subscribe, dan Last Will pada ESP32. |
| ESP32Servo.h | Mengendalikan servo pada ESP32. |
| LiquidCrystal_I2C.h | Mengendalikan LCD melalui I2C. |
| HTML, CSS, JavaScript | Membangun dashboard, grafik, dan logika kontrol. |
| Paho MQTT JavaScript 1.0.1 | Klien MQTT pada browser melalui WebSocket Secure. |
| Broker HiveMQ | Broker MQTT publik (broker.hivemq.com) sebagai perantara pesan. |

BAB III
PEMBAHASAN
3.1 Penjelasan Sistem
3.1.1 Arsitektur dan Alur Kerja
Sistem terdiri dari dua bagian yang dihubungkan oleh broker MQTT, yaitu program ESP32 dan dashboard web index.html. Berbeda dengan contoh pada modul yang hanya mengirim perintah dari web ke ESP32, kedua bagian di sini saling mengirim dan menerima pesan. ESP32 menerbitkan data sensor ke topic data dan berlangganan pada topic cmd/#, sedangkan dashboard berlangganan pada topic data dan status serta menerbitkan perintah ke topic cmd. Arsitekturnya ditunjukkan pada Gambar 2 dan alur kerjanya dirangkum pada Tabel 3.

Tabel 3. Alur Kerja Sistem
| Tahap | Komponen | Proses | Keluaran |
|---|---|---|---|
| 1 | HC-SR04 | Memancarkan gelombang ultrasonik dan mengukur waktu pantulan setiap 500 ms. | Jarak (cm) |
| 2 | ESP32 | Merata-ratakan 5 pembacaan terakhir, lalu menghitung level = 200 − jarak. | Level air (cm) |
| 3 | ESP32 | Menentukan status dengan histeresis, lalu mengatur servo, LED, buzzer, dan LCD. | Status dan aksi lokal |
| 4 | ESP32 → Broker | Menerbitkan data JSON ke topic data setiap 1 detik atau saat status berubah. | Pesan MQTT |
| 5 | Broker → Dashboard | Meneruskan pesan melalui WebSocket Secure port 8884. | Data tiba di browser |
| 6 | Dashboard | Memperbarui tangki, status, dan grafik. Pengguna dapat mengirim perintah. | Tampilan dan perintah |
| 7 | Dashboard → ESP32 | Perintah mode, sudut pintu, atau bisu dikirim ke topic cmd/..., lalu diterapkan ESP32. | Aktuator berubah |
3.1.2 Rancangan Rangkaian
Rangkaian dibuat pada Wokwi dengan koneksi pin seperti pada Tabel 4. Pin ECHO HC-SR04 pada perangkat nyata mengeluarkan sinyal 5 V, sedangkan pin GPIO ESP32 bekerja pada 3,3 V. Pada simulasi hal ini tidak menjadi masalah, tetapi pada perangkat fisik sebaiknya dipasang pembagi tegangan pada jalur ECHO.
Tabel 4. Konfigurasi Pin
| Komponen | Pin Komponen | Pin ESP32 | Fungsi |
|---|---|---|---|
| HC-SR04 | TRIG | GPIO 5 | Mengirim pulsa pemicu. |
| HC-SR04 | ECHO | GPIO 18 | Menerima pulsa pantulan. |
| HC-SR04 | VCC / GND | 5V / GND | Catu daya sensor. |
| LCD I2C | SDA | GPIO 21 | Jalur data I2C. |
| LCD I2C | SCL | GPIO 22 | Jalur clock I2C. |
| LCD I2C | VCC / GND | 5V / GND | Catu daya LCD. |
| Servo | PWM | GPIO 13 | Sinyal kendali sudut pintu air. |
| Servo | V+ / GND | 5V / GND | Catu daya servo. |
| Buzzer | Pin 2 / Pin 1 | GPIO 14 / GND | Sinyal alarm suara. |
| LED hijau | Anoda (via 220 Ω) | GPIO 25 | Indikator AMAN. |
| LED kuning | Anoda (via 220 Ω) | GPIO 26 | Indikator WASPADA. |
| LED merah | Anoda (via 220 Ω) | GPIO 27 | Indikator BAHAYA. |

3.1.3 Struktur Topic dan Format Data MQTT
Seluruh topic memakai awalan unesa/informatika/kel9/banjir agar tidak bertabrakan dengan pengguna lain pada broker publik. Rincian topic ditunjukkan pada Tabel 5, dengan awalan tersebut dihilangkan agar ringkas.
Tabel 5. Daftar Topic MQTT
| Topic | Arah | Payload | Keterangan |
|---|---|---|---|
| /data | ESP32 → Dashboard | JSON | Berisi jarak, level, status, pintu, mode, mute, rssi, dan uptime. |
| /status | ESP32 → Dashboard | online / offline | Bersifat retained. Nilai offline diterbitkan broker lewat Last Will. |
| /cmd/mode | Dashboard → ESP32 | AUTO / MANUAL | Memilih mode kendali pintu air. |
| /cmd/pintu | Dashboard → ESP32 | 0 sampai 90 | Sudut pintu air. Hanya diterima pada mode MANUAL. |
| /cmd/mute | Dashboard → ESP32 | ON / OFF | Membisukan atau mengaktifkan alarm. |
Contoh isi pesan pada topic data adalah {"jarak":80.0,"level":120.0,"status":"WASPADA","pintu":45,"mode":"AUTO","mute":false,"rssi":-50,"uptime":125}. Format JSON dipilih agar dashboard cukup memanggil JSON.parse() untuk membaca seluruh nilai sekaligus.
3.1.4 Logika Status dan Aksi Otomatis
Tinggi muka air dihitung dengan rumus level = 200 − jarak, dengan 200 cm sebagai tinggi pemasangan sensor dari dasar. Nilai level kemudian dibandingkan dengan dua ambang, yaitu 100 cm untuk WASPADA dan 150 cm untuk BAHAYA. Aksi untuk setiap status pada mode AUTO ditunjukkan pada Tabel 6.
Tabel 6. Status dan Aksi pada Mode AUTO
| Status | Level air | LED | Buzzer | Pintu air |
|---|---|---|---|---|
| AMAN | di bawah 100 cm | Hijau | Diam | 0° |
| WASPADA | 100 sampai 149 cm | Kuning | Bunyi 1 kHz, 200 ms setiap 2 detik | 45° |
| BAHAYA | 150 cm ke atas | Merah | Bunyi 2 kHz, putus-putus cepat (150 ms) | 90° |
Agar status tidak berkedip ketika level berada tepat di sekitar ambang, program memakai histeresis sebesar 5 cm. Status naik segera setelah level mencapai ambang, tetapi baru turun bila level berada 5 cm di bawah ambang tersebut. Sebagai contoh, status BAHAYA baru berganti menjadi WASPADA ketika level kurang dari 145 cm. Selain itu, jarak yang dipakai adalah rata-rata dari lima pembacaan terakhir untuk meredam derau pengukuran. Akibatnya, perubahan jarak pada sensor baru tercermin penuh sekitar 2,5 detik kemudian (5 sampel × 500 ms).
Tabel 7. Parameter Konfigurasi Sistem
| Parameter | Nilai pada Program | Keterangan |
|---|---|---|
| Broker MQTT | broker.hivemq.com | Broker publik, memerlukan internet. |
| Port ESP32 | 1883 | Koneksi TCP oleh PubSubClient. |
| Port browser | 8884 | WebSocket Secure (useSSL: true). |
| Wi-Fi | Wokwi-GUEST | Jaringan virtual Wokwi tanpa kata sandi. |
| Tinggi sensor | 200 cm | Jarak sensor ke dasar sungai. |
| Ambang WASPADA / BAHAYA | 100 cm / 150 cm | Batas level penentu status. |
| Histeresis | 5 cm | Pencegah status berkedip. |
| Interval baca / kirim | 500 ms / 1000 ms | Periode sensor dan publikasi data. |
| Reconnect MQTT / Wi-Fi | 5000 ms | Percobaan sambung ulang tanpa memblokir program. |
| Serial Monitor | 115200 baud | Untuk melihat log ESP32. |
3.1.5 Cara Kerja Program ESP32
Program ESP32 menggunakan pustaka WiFi.h, PubSubClient.h, ESP32Servo.h, dan LiquidCrystal_I2C.h. Fungsi-fungsi utamanya dijelaskan sebagai berikut.
1.setup(): mengatur pin, memasang servo pada GPIO 13 dengan frekuensi 50 Hz, menyiapkan LCD, menyambung Wi-Fi, mengatur server dan callback MQTT, lalu melakukan pembacaan awal.
2.bacaJarakCm() dan bacaSensor(): memicu HC-SR04, mengukur durasi pantulan dengan batas waktu 30 ms, mengubahnya menjadi sentimeter, lalu merata-ratakan lima sampel terakhir menjadi jarakCm dan levelCm.
3.hitungStatus() dan perbaruiStatus(): menentukan status baru dengan histeresis. Bila status berubah, program mencatat ke Serial Monitor dan langsung menerbitkan data ke broker.
4.terapkanPintu(): pada mode AUTO sudut pintu mengikuti status (0°, 45°, 90°), sedangkan pada mode MANUAL mengikuti sudutManual dari dashboard. Servo hanya digerakkan bila sudut target berubah.
5.perbaruiAlarm(): mengatur bunyi buzzer dengan millis() tanpa delay(), sehingga client.loop() tetap berjalan selama alarm berbunyi. Alarm otomatis diaktifkan kembali ketika status kembali AMAN.
6.sambungMqtt(): membuat client ID acak, terhubung ke broker dengan Last Will pada topic status, menerbitkan online secara retained, lalu berlangganan cmd/#. Bila gagal, percobaan diulang setiap 5 detik tanpa memblokir program.
7.onPesan(): mengurai topic dan payload perintah. Perintah mode mengubah modeAuto, perintah pintu mengubah sudutManual (dibatasi 0 sampai 90 dan diabaikan pada mode AUTO), dan perintah mute mengubah alarmMute. Setelah itu data terbaru langsung dikirim kembali sebagai konfirmasi.
8.loop(): menjaga Wi-Fi dan MQTT, memanggil client.loop(), membaca sensor setiap 500 ms, mengirim data setiap 1 detik, dan memperbarui alarm pada setiap putaran.
3.1.6 Cara Kerja Dashboard Web
Halaman index.html memuat pustaka Paho MQTT dari CDN dan membuat klien dengan client ID acak berawalan "web-kel9-". Klien terhubung ke broker melalui port 8884 dengan SSL, kemudian berlangganan topic data dan status. Bila koneksi terputus, onConnectionLost menjadwalkan sambungan ulang setelah 3 detik.
Setiap pesan pada topic data diubah dari JSON menjadi objek, lalu fungsi terimaData() memperbarui angka level dan jarak, tinggi air pada gambar tangki, warna status, posisi pintu, mode, dan grafik garis berisi 60 data terakhir. Garis putus-putus pada tangki dan grafik menandai ambang WASPADA dan BAHAYA. Badge di bagian atas menunjukkan status koneksi broker dan status online atau offline ESP32 dari topic status. Bila tidak ada data selama lebih dari 5 detik, status berubah menjadi DATA KEDALUWARSA.
Tombol Mode AUTO dan Mode MANUAL menerbitkan pesan ke cmd/mode. Slider sudut pintu hanya aktif pada mode MANUAL dan mengirim nilai ke cmd/pintu saat slider dilepas, bukan sepanjang geseran, agar tidak membanjiri broker. Tombol Bisukan Alarm menerbitkan ON atau OFF ke cmd/mute. Dashboard tidak mengubah tampilan sendiri setelah perintah dikirim, melainkan menunggu data konfirmasi dari ESP32 sehingga tampilan selalu mencerminkan keadaan perangkat yang sebenarnya.

3.1.7 Prosedur Menjalankan Sistem
1.Buka wokwi.com, buat proyek baru dengan papan ESP32, lalu ganti isi berkas sketch.ino, diagram.json, dan libraries.txt dengan kode pada Subbab 3.2.
2.Jalankan simulasi dengan tombol Start. Buka Serial Monitor dan pastikan muncul pesan Wi-Fi terhubung serta MQTT terhubung.
3.Simpan kode dashboard sebagai index.html, lalu buka dengan browser (klik ganda berkas atau melalui hosting statis seperti GitHub Pages).
4.Pastikan badge pada dashboard berubah menjadi Broker: terhubung dan ESP32: online.
5.Klik sensor HC-SR04 pada Wokwi dan ubah nilai Distance untuk mensimulasikan naik turunnya muka air. Amati LCD, LED, buzzer, servo, dan dashboard.
6.Uji tombol Mode MANUAL, slider sudut pintu, dan tombol Bisukan Alarm pada dashboard.
3.2 Program
Berikut kode program yang digunakan pada sistem, terdiri dari program ESP32 (Kode Sumber 1), konfigurasi rangkaian Wokwi (Kode Sumber 2), daftar pustaka Wokwi (Kode Sumber 3), dan dashboard web (Kode Sumber 4).
Kode Sumber 1. Program ESP32 (sketch.ino, Arduino IDE, C++)
Kelompok 9 - Modul 6 : MQTT melalui WebSocket
Sistem Peringatan Dini Banjir dan Kendali Pintu Air Berbasis ESP32
Sensor : HC-SR04 (jarak permukaan air)
Aktuator : Servo (pintu air), Buzzer, 3 LED, LCD I2C 20x4
Protokol : MQTT (ESP32 -> TCP 1883, Browser -> WebSocket Secure 8884)
*/
#include <WiFi.h>
#include <PubSubClient.h>
#include <ESP32Servo.h>
#include <LiquidCrystal_I2C.h>
// ---------- Konfigurasi WiFi dan MQTT ----------
const char* WIFI_SSID = "Wokwi-GUEST";
const char* WIFI_PASS = "";
const char* MQTT_HOST = "broker.hivemq.com";
const uint16_t MQTT_PORT = 1883;
#define TOPIC_BASE "unesa/informatika/kel9/banjir"
const char* TOPIC_DATA = TOPIC_BASE "/data"; // ESP32 -> Dashboard
const char* TOPIC_STATUS = TOPIC_BASE "/status"; // online / offline (LWT)
const char* TOPIC_CMD_ALL = TOPIC_BASE "/cmd/#"; // Dashboard -> ESP32
const char* TOPIC_MODE = TOPIC_BASE "/cmd/mode"; // AUTO / MANUAL
const char* TOPIC_PINTU = TOPIC_BASE "/cmd/pintu"; // 0 - 90 (derajat)
const char* TOPIC_MUTE = TOPIC_BASE "/cmd/mute"; // ON / OFF
// ---------- Pin ----------
const int PIN_TRIG = 5;
const int PIN_ECHO = 18;
const int PIN_SERVO = 13;
const int PIN_BUZZER = 14;
const int PIN_LED_HIJAU = 25;
const int PIN_LED_KUNING = 26;
const int PIN_LED_MERAH = 27;
// ---------- Parameter sistem ----------
const float TINGGI_SENSOR_CM = 200.0; // jarak sensor ke dasar sungai
const float BATAS_WASPADA_CM = 100.0; // level >= 100 cm -> WASPADA
const float BATAS_BAHAYA_CM = 150.0; // level >= 150 cm -> BAHAYA
const float HISTERESIS_CM = 5.0; // mencegah status berkedip
const int SUDUT_AMAN = 0; // pintu air tertutup
const int SUDUT_WASPADA = 45; // pintu air terbuka setengah
const int SUDUT_BAHAYA = 90; // pintu air terbuka penuh
const unsigned long INTERVAL_BACA_MS = 500;
const unsigned long INTERVAL_KIRIM_MS = 1000;
const unsigned long INTERVAL_RECONNECT_MS = 5000;
enum Status { AMAN, WASPADA, BAHAYA };
const char* NAMA_STATUS[] = { "AMAN", "WASPADA", "BAHAYA" };
// ---------- Objek ----------
Servo pintuAir;
LiquidCrystal_I2C lcd(0x27, 20, 4);
WiFiClient espClient;
PubSubClient client(espClient);
// ---------- Variabel keadaan ----------
const int JUMLAH_SAMPEL = 5;
float sampel[JUMLAH_SAMPEL];
int indeksSampel = 0;
int sampelTerisi = 0;
float jarakCm = TINGGI_SENSOR_CM;
float levelCm = 0;
Status status = AMAN;
bool modeAuto = true;
bool alarmMute = false;
int sudutPintu = -1; // -1 = belum pernah digerakkan
int sudutManual = 0;
unsigned long tBaca = 0, tKirim = 0, tReconnect = 0, tWifi = 0;
void kirimTelemetri(); // deklarasi awal (dipakai sebelum didefinisikan)
// ================= Sensor =================
float bacaJarakCm() {
digitalWrite(PIN_TRIG, LOW);
delayMicroseconds(2);
digitalWrite(PIN_TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(PIN_TRIG, LOW);
unsigned long durasi = pulseIn(PIN_ECHO, HIGH, 30000UL);
if (durasi == 0) return -1; // tidak ada pantulan
return durasi * 0.0343f / 2.0f;
}
void bacaSensor() {
float j = bacaJarakCm();
if (j < 0) {
Serial.println("[SENSOR] Tidak ada pantulan, data dilewati");
return;
}
sampel[indeksSampel] = j;
indeksSampel = (indeksSampel + 1) % JUMLAH_SAMPEL;
if (sampelTerisi < JUMLAH_SAMPEL) sampelTerisi++;
float total = 0;
for (int i = 0; i < sampelTerisi; i++) total += sampel[i];
jarakCm = total / sampelTerisi; // rata-rata bergerak
levelCm = constrain(TINGGI_SENSOR_CM - jarakCm, 0, TINGGI_SENSOR_CM);
}
// ================= Logika status =================
Status hitungStatus(float level, Status sekarang) {
switch (sekarang) {
case AMAN:
if (level >= BATAS_BAHAYA_CM) return BAHAYA;
if (level >= BATAS_WASPADA_CM) return WASPADA;
return AMAN;
case WASPADA:
if (level >= BATAS_BAHAYA_CM) return BAHAYA;
if (level < BATAS_WASPADA_CM - HISTERESIS_CM) return AMAN;
return WASPADA;
default: // BAHAYA
if (level < BATAS_BAHAYA_CM - HISTERESIS_CM)
return (level >= BATAS_WASPADA_CM) ? WASPADA : AMAN;
return BAHAYA;
}
}
int sudutOtomatis(Status s) {
if (s == BAHAYA) return SUDUT_BAHAYA;
if (s == WASPADA) return SUDUT_WASPADA;
return SUDUT_AMAN;
}
void perbaruiStatus() {
Status baru = hitungStatus(levelCm, status);
if (baru != status) {
Serial.printf("[STATUS] %s -> %s (level %.1f cm)\n",
NAMA_STATUS[status], NAMA_STATUS[baru], levelCm);
status = baru;
if (status == AMAN) alarmMute = false; // alarm aktif lagi untuk kejadian berikutnya
kirimTelemetri(); // kabari dashboard seketika
}
}
// ================= Aktuator =================
void terapkanPintu() {
int target = modeAuto ? sudutOtomatis(status) : sudutManual;
if (target != sudutPintu) {
sudutPintu = target;
pintuAir.write(sudutPintu);
Serial.printf("[PINTU] Sudut %d derajat (%s)\n", sudutPintu, modeAuto ? "AUTO" : "MANUAL");
}
}
void perbaruiLed() {
digitalWrite(PIN_LED_HIJAU, status == AMAN);
digitalWrite(PIN_LED_KUNING, status == WASPADA);
digitalWrite(PIN_LED_MERAH, status == BAHAYA);
}
// Alarm tanpa delay() agar loop() tetap berjalan dan MQTT tetap terlayani
void perbaruiAlarm() {
static bool bunyi = false;
static unsigned long tUbah = 0;
if (alarmMute || status == AMAN) {
if (bunyi) { noTone(PIN_BUZZER); bunyi = false; }
return;
}
unsigned long durasiOn = (status == WASPADA) ? 200 : 150;
unsigned long durasiOff = (status == WASPADA) ? 1800 : 150;
int nada = (status == WASPADA) ? 1000 : 2000;
unsigned long batas = bunyi ? durasiOn : durasiOff;
if (millis() - tUbah >= batas) {
tUbah = millis();
bunyi = !bunyi;
if (bunyi) tone(PIN_BUZZER, nada);
else noTone(PIN_BUZZER);
}
}
void tulisBaris(int baris, const char* teks) {
char buf[21];
snprintf(buf, sizeof(buf), "%-20s", teks);
lcd.setCursor(0, baris);
lcd.print(buf);
}
void perbaruiLcd() {
char b[24];
tulisBaris(0, "EWS BANJIR - KEL 9");
snprintf(b, sizeof(b), "Level : %.1f cm", levelCm);
tulisBaris(1, b);
snprintf(b, sizeof(b), "Status: %s", NAMA_STATUS[status]);
tulisBaris(2, b);
snprintf(b, sizeof(b), "Pintu:%3d %s%s", sudutPintu,
modeAuto ? "AUTO" : "MANUAL", alarmMute ? " MUTE" : "");
tulisBaris(3, b);
}
// ================= WiFi dan MQTT =================
void sambungWifi() {
Serial.print("Menghubungkan ke WiFi ");
Serial.println(WIFI_SSID);
tulisBaris(1, "Menyambung WiFi...");
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS, 6);
int hitung = 0;
while (WiFi.status() != WL_CONNECTED && hitung < 40) {
delay(500);
Serial.print(".");
hitung++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWiFi terhubung, IP: " + WiFi.localIP().toString());
} else {
Serial.println("\n[ERROR] WiFi gagal, akan dicoba lagi di loop()");
}
}
void jagaWifi() {
if (WiFi.status() == WL_CONNECTED) return;
if (millis() - tWifi < INTERVAL_RECONNECT_MS) return;
tWifi = millis();
Serial.println("WiFi terputus, mencoba menyambung ulang...");
WiFi.disconnect();
WiFi.begin(WIFI_SSID, WIFI_PASS, 6);
}
void kirimTelemetri() {
if (!client.connected()) return;
char buf[200];
snprintf(buf, sizeof(buf),
"{\"jarak\":%.1f,\"level\":%.1f,\"status\":\"%s\",\"pintu\":%d,"
"\"mode\":\"%s\",\"mute\":%s,\"rssi\":%d,\"uptime\":%lu}",
jarakCm, levelCm, NAMA_STATUS[status], sudutPintu,
modeAuto ? "AUTO" : "MANUAL", alarmMute ? "true" : "false",
WiFi.RSSI(), millis() / 1000UL);
client.publish(TOPIC_DATA, buf);
}
void onPesan(char* topic, byte* payload, unsigned int length) {
char isi[32];
unsigned int n = (length < sizeof(isi) - 1) ? length : sizeof(isi) - 1;
memcpy(isi, payload, n);
isi[n] = '\0';
String t(topic);
String p(isi);
p.trim();
p.toUpperCase();
Serial.printf("[MQTT] %s = %s\n", topic, p.c_str());
if (t == TOPIC_MODE) {
if (p == "AUTO") {
modeAuto = true;
} else if (p == "MANUAL") {
modeAuto = false;
sudutManual = (sudutPintu < 0) ? 0 : sudutPintu;
}
} else if (t == TOPIC_PINTU) {
if (modeAuto) {
Serial.println("[MQTT] Perintah pintu diabaikan: mode AUTO");
} else {
sudutManual = constrain(p.toInt(), 0, 90);
}
} else if (t == TOPIC_MUTE) {
alarmMute = (p == "ON" || p == "1" || p == "TRUE");
}
terapkanPintu();
perbaruiLcd();
kirimTelemetri();
}
void sambungMqtt() {
if (client.connected() || WiFi.status() != WL_CONNECTED) return;
if (millis() - tReconnect < INTERVAL_RECONNECT_MS) return;
tReconnect = millis();
String id = "ESP32-EWS-" + String((uint32_t)esp_random(), HEX);
Serial.print("Menghubungkan ke MQTT broker... ");
// Last Will: broker menerbitkan "offline" bila ESP32 terputus tiba-tiba
if (client.connect(id.c_str(), nullptr, nullptr, TOPIC_STATUS, 1, true, "offline")) {
Serial.println("terhubung");
client.publish(TOPIC_STATUS, "online", true);
client.subscribe(TOPIC_CMD_ALL);
kirimTelemetri();
} else {
Serial.printf("gagal, rc=%d\n", client.state());
}
}
// ================= setup dan loop =================
void setup() {
Serial.begin(115200);
delay(500);
Serial.println("\n--- EWS Banjir Kelompok 9 ---");
pinMode(PIN_TRIG, OUTPUT);
pinMode(PIN_ECHO, INPUT);
pinMode(PIN_LED_HIJAU, OUTPUT);
pinMode(PIN_LED_KUNING, OUTPUT);
pinMode(PIN_LED_MERAH, OUTPUT);
pinMode(PIN_BUZZER, OUTPUT);
ESP32PWM::allocateTimer(0);
ESP32PWM::allocateTimer(1);
pintuAir.setPeriodHertz(50);
pintuAir.attach(PIN_SERVO, 500, 2400);
lcd.init();
lcd.backlight();
lcd.clear();
tulisBaris(0, "EWS BANJIR - KEL 9");
sambungWifi();
client.setServer(MQTT_HOST, MQTT_PORT);
client.setCallback(onPesan);
client.setBufferSize(512);
bacaSensor();
perbaruiStatus();
terapkanPintu();
perbaruiLed();
perbaruiLcd();
}
void loop() {
jagaWifi();
sambungMqtt();
client.loop();
unsigned long sekarang = millis();
if (sekarang - tBaca >= INTERVAL_BACA_MS) {
tBaca = sekarang;
bacaSensor();
perbaruiStatus();
terapkanPintu();
perbaruiLed();
perbaruiLcd();
}
if (sekarang - tKirim >= INTERVAL_KIRIM_MS) {
tKirim = sekarang;
kirimTelemetri();
}
perbaruiAlarm();
}
Kode Sumber 2. Konfigurasi Rangkaian Wokwi (diagram.json)
"version": 1,
"author": "Kelompok 9",
"editor": "wokwi",
"parts": [
{"type": "board-esp32-devkit-c-v4", "id": "esp", "top": 0, "left": 0, "attrs": {}},
{"type": "wokwi-hc-sr04", "id": "ultra1", "top": -40, "left": -290, "attrs": {"distance": "150"}},
{"type": "wokwi-lcd2004", "id": "lcd1", "top": -275, "left": 120, "attrs": {"pins": "i2c"}},
{"type": "wokwi-servo", "id": "servo1", "top": 40, "left": 230, "attrs": {}},
{"type": "wokwi-buzzer", "id": "bz1", "top": 170, "left": 240, "attrs": {"volume": "0.1"}},
{"type": "wokwi-led", "id": "led1", "top": 250, "left": 20, "attrs": {"color": "green"}},
{"type": "wokwi-led", "id": "led2", "top": 250, "left": 80, "attrs": {"color": "yellow"}},
{"type": "wokwi-led", "id": "led3", "top": 250, "left": 140, "attrs": {"color": "red"}},
{"type": "wokwi-resistor", "id": "r1", "top": 320, "left": 10, "rotate": 90, "attrs": {"value": "220"}},
{"type": "wokwi-resistor", "id": "r2", "top": 320, "left": 70, "rotate": 90, "attrs": {"value": "220"}},
{"type": "wokwi-resistor", "id": "r3", "top": 320, "left": 130, "rotate": 90, "attrs": {"value": "220"}}
],
"connections": [
["esp:5V", "ultra1:VCC", "red", []],
["esp:GND.1", "ultra1:GND", "black", []],
["esp:5", "ultra1:TRIG", "green", []],
["esp:18", "ultra1:ECHO", "orange", []],
["esp:5V", "lcd1:VCC", "red", []],
["esp:GND.2", "lcd1:GND", "black", []],
["esp:21", "lcd1:SDA", "blue", []],
["esp:22", "lcd1:SCL", "purple", []],
["esp:5V", "servo1:V+", "red", []],
["esp:GND.2", "servo1:GND", "black", []],
["esp:13", "servo1:PWM", "gold", []],
["esp:14", "bz1:2", "orange", []],
["esp:GND.2", "bz1:1", "black", []],
["esp:25", "r1:1", "green", []],
["r1:2", "led1:A", "green", []],
["led1:C", "esp:GND.1", "black", []],
["esp:26", "r2:1", "green", []],
["r2:2", "led2:A", "green", []],
["led2:C", "esp:GND.1", "black", []],
["esp:27", "r3:1", "green", []],
["r3:2", "led3:A", "green", []],
["led3:C", "esp:GND.1", "black", []]
],
"dependencies": {}
}
Kode Sumber 3. Daftar Pustaka Wokwi (libraries.txt)
ESP32Servo
LiquidCrystal I2C
Kode Sumber 4. Dashboard Web (index.html)
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>EWS Banjir - Kelompok 9</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js"></script>
<style>
:root { --aman:#2e9e5b; --waspada:#e0a800; --bahaya:#d93636; --abu:#6b7280; }
* { box-sizing: border-box; }
body { margin:0; font-family: Arial, sans-serif; background:#eef2f6; color:#1f2937; }
header { background:#0f3d5e; color:#fff; padding:14px 20px; display:flex;
justify-content:space-between; align-items:center; flex-wrap:wrap; gap:8px; }
header h1 { margin:0; font-size:18px; }
.badge { padding:4px 12px; border-radius:14px; font-size:13px; color:#fff; background:var(--abu); }
main { display:grid; grid-template-columns:repeat(auto-fit,minmax(300px,1fr)); gap:14px; padding:14px; }
.card { background:#fff; border-radius:10px; padding:16px; box-shadow:0 1px 4px rgba(0,0,0,.12); }
.card h2 { margin:0 0 10px; font-size:15px; color:#0f3d5e; }
.lebar { grid-column:1 / -1; }
.tangki { display:flex; gap:16px; align-items:center; }
.angka { font-size:40px; font-weight:bold; }
.kecil { font-size:13px; color:var(--abu); margin:3px 0; }
#status { display:inline-block; padding:6px 16px; border-radius:6px; color:#fff;
font-weight:bold; background:var(--abu); margin:8px 0; }
button { padding:8px 14px; border:0; border-radius:6px; background:#0f3d5e; color:#fff;
cursor:pointer; font-size:14px; margin:4px 4px 4px 0; }
button.aktif { background:var(--aman); }
button:disabled { background:#b6bcc6; cursor:not-allowed; }
input[type=range] { width:100%; }
canvas { width:100%; height:240px; display:block; }
#log { height:150px; overflow-y:auto; font-family:monospace; font-size:12px;
background:#f4f6f8; padding:8px; border-radius:6px; }
</style>
</head>
<body>
<header>
<h1>Sistem Peringatan Dini Banjir - Kelompok 9</h1>
<div>
<span id="badgeBroker" class="badge">Broker: menghubungkan...</span>
<span id="badgePerangkat" class="badge">ESP32: tidak diketahui</span>
</div>
</header>
<main>
<section class="card">
<h2>Tinggi Muka Air</h2>
<div class="tangki">
<svg width="110" height="220" viewBox="0 0 110 220">
<rect x="15" y="10" width="80" height="200" fill="#e8eef5" stroke="#334155" stroke-width="2"/>
<rect id="air" x="16" y="210" width="78" height="0" fill="#3b82f6"/>
<line x1="15" x2="95" y1="110" y2="110" stroke="#e0a800" stroke-dasharray="4"/>
<line x1="15" x2="95" y1="60" y2="60" stroke="#d93636" stroke-dasharray="4"/>
</svg>
<div>
<div class="angka"><span id="level">--</span> <small>cm</small></div>
<div class="kecil">Jarak sensor ke air: <b id="jarak">--</b> cm</div>
<div id="status">MENUNGGU DATA</div>
<div class="kecil">Garis kuning: 100 cm (waspada)</div>
<div class="kecil">Garis merah: 150 cm (bahaya)</div>
</div>
</div>
</section>
<section class="card">
<h2>Kontrol Pintu Air</h2>
<div class="kecil">Posisi pintu saat ini: <b id="pintu">--</b>°</div>
<div class="kecil">Mode: <b id="mode">--</b> | Alarm: <b id="alarm">--</b></div>
<div>
<button id="btnAuto">Mode AUTO</button>
<button id="btnManual">Mode MANUAL</button>
<button id="btnMute">Bisukan Alarm</button>
</div>
<label class="kecil">Sudut pintu manual: <b id="nilaiSlider">0</b>°</label>
<input type="range" id="slider" min="0" max="90" step="5" value="0" disabled>
<div class="kecil">Slider aktif hanya pada mode MANUAL.</div>
<div class="kecil">Update terakhir: <span id="waktu">-</span> | RSSI: <span id="rssi">-</span> dBm</div>
</section>
<section class="card lebar">
<h2>Grafik Level Air (60 data terakhir)</h2>
<canvas id="grafik" width="900" height="240"></canvas>
</section>
<section class="card lebar">
<h2>Log Kejadian</h2>
<div id="log"></div>
</section>
</main>
<script>
// ---------- Konfigurasi ----------
const BROKER = "broker.hivemq.com";
const PORT = 8884; // WebSocket Secure
const BASE = "unesa/informatika/kel9/banjir";
const T_DATA = BASE + "/data", T_STATUS = BASE + "/status";
const T_MODE = BASE + "/cmd/mode", T_PINTU = BASE + "/cmd/pintu", T_MUTE = BASE + "/cmd/mute";
const MAKS_LEVEL = 200, BATAS_WASPADA = 100, BATAS_BAHAYA = 150, MAKS_TITIK = 60;
const WARNA = { AMAN:"#2e9e5b", WASPADA:"#e0a800", BAHAYA:"#d93636" };
const $ = (id) => document.getElementById(id);
let data = [], terakhirMasuk = 0, statusLama = "", muteSekarang = false, modeSekarang = "AUTO";
const client = new Paho.MQTT.Client(BROKER, PORT, "/mqtt", "web-kel9-" + Math.floor(Math.random() * 100000));
client.onConnectionLost = (e) => {
badge("badgeBroker", "Broker: terputus", "#d93636");
catat("Koneksi broker terputus: " + (e.errorMessage || "-"));
setTimeout(hubungkan, 3000);
};
client.onMessageArrived = (m) => {
if (m.destinationName === T_DATA) { try { terimaData(JSON.parse(m.payloadString)); } catch (e) { catat("Data tidak valid"); } }
else if (m.destinationName === T_STATUS) {
const on = m.payloadString === "online";
badge("badgePerangkat", "ESP32: " + (on ? "online" : "offline"), on ? "#2e9e5b" : "#d93636");
catat("Perangkat " + m.payloadString);
}
};
function hubungkan() {
badge("badgeBroker", "Broker: menghubungkan...", "#6b7280");
client.connect({
useSSL: true, timeout: 5, keepAliveInterval: 30,
onSuccess: () => {
badge("badgeBroker", "Broker: terhubung", "#2e9e5b");
client.subscribe(T_DATA); client.subscribe(T_STATUS);
catat("Terhubung ke " + BROKER + ":" + PORT);
},
onFailure: (e) => {
badge("badgeBroker", "Broker: gagal", "#d93636");
catat("Gagal terhubung: " + e.errorMessage);
setTimeout(hubungkan, 5000);
}
});
}
function kirim(topik, isi) {
if (!client.isConnected()) { catat("Belum terhubung, perintah dibatalkan"); return; }
const pesan = new Paho.MQTT.Message(String(isi));
pesan.destinationName = topik;
client.send(pesan);
catat("Kirim " + topik.replace(BASE + "/", "") + " = " + isi);
}
function terimaData(d) {
terakhirMasuk = Date.now();
$("level").textContent = d.level.toFixed(1);
$("jarak").textContent = d.jarak.toFixed(1);
$("pintu").textContent = d.pintu;
$("mode").textContent = d.mode;
$("alarm").textContent = d.mute ? "DIBISUKAN" : "AKTIF";
$("rssi").textContent = d.rssi;
$("waktu").textContent = new Date().toLocaleTimeString("id-ID");
$("status").textContent = d.status;
$("status").style.background = WARNA[d.status] || "#6b7280";
const tinggi = Math.max(0, Math.min(MAKS_LEVEL, d.level));
$("air").setAttribute("height", tinggi);
$("air").setAttribute("y", 210 - tinggi);
$("air").setAttribute("fill", WARNA[d.status] || "#3b82f6");
modeSekarang = d.mode; muteSekarang = d.mute;
$("btnAuto").classList.toggle("aktif", d.mode === "AUTO");
$("btnManual").classList.toggle("aktif", d.mode === "MANUAL");
$("btnMute").textContent = d.mute ? "Aktifkan Alarm" : "Bisukan Alarm";
$("slider").disabled = (d.mode !== "MANUAL");
if (d.mode === "AUTO") { $("slider").value = d.pintu; $("nilaiSlider").textContent = d.pintu; }
if (d.status !== statusLama) { catat("Status: " + d.status + " (level " + d.level.toFixed(1) + " cm)"); statusLama = d.status; }
data.push(d.level); if (data.length > MAKS_TITIK) data.shift();
gambarGrafik();
}
function gambarGrafik() {
const c = $("grafik"), g = c.getContext("2d"), W = c.width, H = c.height, pad = 34;
g.clearRect(0, 0, W, H);
const y = (v) => H - pad - (v / MAKS_LEVEL) * (H - 2 * pad);
g.font = "11px Arial"; g.fillStyle = "#6b7280"; g.strokeStyle = "#e5e7eb";
for (let v = 0; v <= MAKS_LEVEL; v += 50) {
g.beginPath(); g.moveTo(pad, y(v)); g.lineTo(W - 10, y(v)); g.stroke();
g.fillText(v, 4, y(v) + 4);
}
[[BATAS_WASPADA, "#e0a800"], [BATAS_BAHAYA, "#d93636"]].forEach(([v, w]) => {
g.strokeStyle = w; g.setLineDash([6, 4]);
g.beginPath(); g.moveTo(pad, y(v)); g.lineTo(W - 10, y(v)); g.stroke();
});
g.setLineDash([]);
if (data.length < 2) return;
g.strokeStyle = "#2563eb"; g.lineWidth = 2; g.beginPath();
data.forEach((v, i) => {
const x = pad + (i / (MAKS_TITIK - 1)) * (W - pad - 10);
i ? g.lineTo(x, y(v)) : g.moveTo(x, y(v));
});
g.stroke(); g.lineWidth = 1;
}
function badge(id, teks, warna) { $(id).textContent = teks; $(id).style.background = warna; }
function catat(teks) {
const baris = document.createElement("div");
baris.textContent = new Date().toLocaleTimeString("id-ID") + " " + teks;
$("log").prepend(baris);
while ($("log").children.length > 40) $("log").lastChild.remove();
}
// ---------- Kontrol ----------
$("btnAuto").onclick = () => kirim(T_MODE, "AUTO");
$("btnManual").onclick = () => kirim(T_MODE, "MANUAL");
$("btnMute").onclick = () => kirim(T_MUTE, muteSekarang ? "OFF" : "ON");
$("slider").oninput = (e) => { $("nilaiSlider").textContent = e.target.value; };
$("slider").onchange = (e) => kirim(T_PINTU, e.target.value);
// Tandai data kedaluwarsa bila ESP32 berhenti mengirim > 5 detik
setInterval(() => {
if (terakhirMasuk && Date.now() - terakhirMasuk > 5000) {
$("status").textContent = "DATA KEDALUWARSA";
$("status").style.background = "#6b7280";
}
}, 1000);
gambarGrafik();
hubungkan();
</script>
</body>
</html>
3.3 Analisis Kelebihan dan Keterbatasan
3.3.1 Kelebihan
•Komunikasi dua arah: data sensor mengalir ke dashboard dan perintah mengalir ke ESP32 melalui satu broker yang sama.
•Program tidak memblokir. Alarm, pembacaan sensor, dan pengiriman data diatur dengan millis() sehingga MQTT tetap terlayani saat buzzer berbunyi.
•Histeresis dan rata-rata bergerak membuat status lebih stabil dan tidak mudah berkedip akibat derau sensor.
•Last Will dan pemeriksaan data kedaluwarsa membuat pengguna mengetahui bila perangkat berhenti bekerja.
•Mode MANUAL memungkinkan operator mengambil alih pintu air, sedangkan mode AUTO menjaga sistem tetap bekerja tanpa operator.
•Dashboard hanya berupa satu berkas HTML sehingga mudah dijalankan dan dibagikan.
3.3.2 Keterbatasan
•Broker bersifat publik dan tanpa autentikasi. Siapa pun yang mengetahui nama topic dapat mengirim perintah mode atau sudut pintu, sehingga sistem belum aman untuk pengendalian pintu air yang sebenarnya.
•ESP32 hanya membatasi rentang sudut 0 sampai 90 dan tidak memverifikasi asal perintah.
•Data pengujian berasal dari simulasi, sedangkan sensor ultrasonik pada kondisi nyata dipengaruhi gelombang air, suhu udara, dan benda mengambang.
•Grafik hanya menyimpan 60 data terakhir pada memori browser dan hilang ketika halaman dimuat ulang, karena belum ada penyimpanan riwayat.
•Pesan MQTT memakai QoS 0 sehingga pengiriman tidak dijamin bila jaringan tidak stabil.
•Peringatan hanya muncul pada dashboard dan perangkat lokal, belum ada notifikasi ke ponsel atau pesan singkat.
•Fungsi pulseIn() menahan program hingga 30 ms pada setiap pembacaan, dan pada perangkat nyata jalur ECHO memerlukan pembagi tegangan.
BAB IV
PENUTUP
4.1 Kesimpulan
Berdasarkan pembahasan pada bab sebelumnya, dapat disimpulkan sebagai berikut.
1.Sistem peringatan dini banjir berhasil dirancang dengan mengintegrasikan sensor HC-SR04, ESP32, LCD I2C, LED, buzzer, dan servo pada Wokwi, serta dashboard web yang berkomunikasi melalui MQTT dan WebSocket menggunakan Paho.
2.ESP32 menghitung level air dari selisih tinggi sensor dan jarak terukur, menentukan status AMAN, WASPADA, atau BAHAYA dengan histeresis 5 cm, lalu menggerakkan pintu air ke 0°, 45°, atau 90° pada mode AUTO.
3.Komunikasi dua arah dicapai dengan lima topic: data dan status dari ESP32 ke dashboard, serta cmd/mode, cmd/pintu, dan cmd/mute dari dashboard ke ESP32.
4.Sistem sesuai untuk demonstrasi integrasi web dan IoT, tetapi belum cukup aman untuk pengendalian pintu air nyata karena memakai broker publik tanpa autentikasi.
4.2 Saran
1.Gunakan broker privat dengan autentikasi, TLS, dan pembatasan akses topic (ACL), serta verifikasi perintah di sisi ESP32.
2.Tambahkan penyimpanan riwayat ketinggian air pada basis data agar grafik tidak hilang saat halaman dimuat ulang.
3.Tambahkan notifikasi ke ponsel, misalnya melalui bot pesan, ketika status mencapai BAHAYA.
4.Gunakan sensor tahan air dan lakukan kalibrasi pada kondisi nyata, serta pasang pembagi tegangan pada jalur ECHO.
5.Gunakan QoS 1 untuk pesan perintah penting dan tambahkan sensor posisi untuk memastikan pintu air benar-benar mencapai sudut yang diminta.
DAFTAR PUSTAKA
ahocool-aisi555. (2025). boomgate: Workshop IOT Informatika UNESA 2025 [Repositori kode]. GitHub. https://github.com/ahocool-aisi555/boomgate Modul 6. (t.t.). MQTT melalui WebSocket: Memanfaatkan JavaScript untuk membuat aplikasi IoT - HTML [Materi perkuliahan]. modul6.pptx.
O'Leary, N. (t.t.). PubSubClient: A client library for MQTT messaging [Repositori kode]. GitHub. https://github.com/knolleary/pubsubclient Random Nerd Tutorials. (t.t.). ESP32 WebSocket Server: Display Sensor Readings. https://randomnerdtutorials.com/esp32-websocket-server-sensor/ Steve's Internet Guide. (t.t.). Using the JavaScript MQTT client with WebSockets. http://www.steves-internet-guide.com/using-javascript-mqtt-client-websockets/ Wokwi. (t.t.). Wokwi: World's most advanced ESP32 simulator. https://wokwi.com
LAMPIRAN
Link Video: https://drive.google.com/drive/folders/11fHlgYJsTspVbom2_wbqFp3pKXwhhOCE?usp=sharing
Tidak ada komentar:
Posting Komentar