Langsung ke konten
BerandaMembuat Monitoring IoT Secara Menyeluruh

Membuat Monitoring IoT Secara Menyeluruh

TEKNOLAB • SMART LAB • PANDUAN REKAYASA IoT

Tutorial Lengkap Sistem IoT End-to-End

Smart Lab Environmental Guard

ESP32 + Cirkit Designer + MQTT over WSS + Eclipse Mosquitto + Node-RED + InfluxDB

Dikembangkan dari prototipe internal Teknolab (ESP32, simulasi dan pencatatan pengujian), kemudian diperbaiki melalui audit teknis serta pengujian lokal yang terdokumentasi.

Edisi 31 Oktober 2026 • Referensi praktik IoT Teknolab
⚠️ HAL PENTING Tidak ada insinyur yang dapat menjamin firmware, driver, TLS, DNS, dan simulator langsung berjalan di perangkat yang belum diuji. Panduan ini memberikan langkah pasti, hasil yang harus terlihat, dan titik pemeriksaan. Komponen yang tidak bisa diuji di lingkungan penyusun diberi label BELUM TERUJI NYATA.

Peta baca dan cara menggunakan modul

Dokumen ini merupakan panduan implementasi sistem IoT lengkap. Ikuti urutannya. Jangan meloncat dari wiring langsung ke dashboard, karena setiap lapisan memiliki tes lulus sebelum lanjut.

TahapBabHasil yang harus berhasil
1. Persiapan1–3Asumsi, perangkat, sistem operasi dan pemetaan pin jelas
2. Perangkat lokal4–7DHT, potensiometer, 2 tombol, 3 LED, LCD dan state berjalan
3. Internet/MQTT8–11Mosquitto WSS, sertifikat, MQTTX, ESP32 publish JSON valid
4. Backend12–14Node-RED memvalidasi, InfluxDB menyimpan, dashboard menampilkan
5. Pengujian15–1812 tes penerimaan, fail-safe, formulir dokumentasi teknis, laporan verifikasi

Status audit & bukti pengujian yang dilakukan

Jenis tesHasilBatas bukti
39 assertion logika C++PASSStatus, prioritas, ACK, debounce, overflow millis, LED
77 assertion flow Node-RED (versi Teknolab)PASSValidator JSON, routing 2 sumber, field/tags InfluxDB
15 pemeriksaan server statisPASSCompose YAML, ACL, listener, CA template
Kompilasi mock C++ sketsaPASSSintaks & generasi JSON dengan API tiruan, BUKAN Arduino CLI
Arduino-ESP32/Cirkit asliBELUMTidak tersedia toolchain/perangkat & akses simulator nyata untuk pengujian ini
Docker Mosquitto/Influx/Node-RED berjalanBELUMDocker dan broker tidak tersedia di lingkungan audit
Koneksi WSS end-to-end fisikBELUMMembutuhkan domain, sertifikat, jaringan dan ESP32 riil

Hasil tes otomatis di atas tersimpan pada paket pendamping: tests/HASIL_TEST.txt. PASS statis/mock tidak berarti klaim bahwa seluruh sistem sudah lolos uji penerimaan fisik.

1. Audit prototipe awal Teknolab

File dasar yang diperiksa: TESS1.ino, screenshot Cirkit Designer, konfigurasi MQTTX, potongan flow Node-RED, konfigurasi broker EMQX, serta catatan konfigurasi dan pengujian. Tujuan audit adalah mempertahankan fungsi yang berhasil dan memperbaiki titik lemah yang terdeteksi.

Temuan pada starting pointDampakTindakan di revisi
PubSubClient + WiFiClient ke port 1883MQTT TCP biasa, bukan WSSGunakan PsychicMqttClient dengan URI wss:// dan validasi CA
Topic publish dari kode HW menuju jalur SIMData tertukar pada dashboardIdentitas TEKNOLAB_HW/SIM dan path hw/sim dipisah
peilud() dipanggil sebelum state dan ADC diperbaruiPayload dapat memakai status/limit sebelumnyaUrutan loop: baca -> evaluasi -> output -> publish
Tombol memakai while menunggu rilisLoop terhenti jika tombol ditahanDebounce berbasis millis; satu press satu event
Status dan ACK diatur berbagai fungsiPotensi bug ketika threshold/status berubahSatu kelas Alarm dengan prioritas teruji
Kode mengandung kredensial jaringanRisiko kebocoran aksesSecrets.h dengan placeholder; ganti password lama
Gambar Node-RED menunjukkan real-timeBukan bukti historiUji InfluxDB Data Explorer/Flux dan chart histori
formulir dokumentasi teknis mencatat pot GPIO32; program GPIO34Wiring tidak konsisten jika diikuti mentahGunakan profil pin jelas dan verifikasi pin di board
⚠️ KOREKSI Dokumentasi simulator Cirkit terkini menonjolkan ESP32-S3. Board ESP32-WROOM fisik dan ESP32-S3 tidak memiliki GPIO identik. Logic/status bisa sama, tetapi mapping wajib sesuai board yang dipilih.

2. Kontrak sistem dan batas fungsional

Satu laboratorium dipantau dua implementasi: hardware asli dan simulasi Cirkit. Dua klien mengirim telemetry independen, melalui WSS dengan JSON yang memiliki field sama. Node-RED tidak mengubah aturan alarm lokal; Node-RED hanya memvalidasi, menyimpan, dan menampilkan.

2.1 Aturan status sistem

PrioritasStateKondisiLED sebelum ACKSesudah ACK
1SENSOR_ERRORDHT gagal atau angka invalidKuning + merah kedip 250 msACK diabaikan
2CRITICALT ≥ limit + 3 °C ATAU RH ≥ 85 %Merah kedip 250 msMerah stabil
3WARNINGT ≥ limit ATAU RH ≥ 75 %, bukan criticalKuning kedip 500 msKuning stabil
4NORMALSelain kondisi di atasHijau stabilACK = false

ACK di-reset saat berubah ke status lain, termasuk WARNING→CRITICAL, dan jika NORMAL/ERROR. Ini adalah kebijakan latihan, bukan hysteresis. Nilai T_limit berasal dari potensiometer 20–35 °C. Sensor gagal: kirim null/null, sensor_ok=false, status=SENSOR_ERROR.

2.2 Jadwal fungsi

AktivitasPeriodeAlasan
Baca DHT11≥2.000 msDHT11 tidak boleh dibaca berulang tanpa interval
Baca pot/ADC150 msAmbang cepat berubah
Refresh LCD500 msTidak melakukan lcd.clear() tiap loop
Publish MQTT5.000 msKontrak telemetry, sequence naik saat pesan diantrikan
TombolTiap loop + debounce 30 msTidak memakai delay dan while tahan tombol
Alarm lokalTiap loopTetap aktif saat broker/Wi-Fi OFFLINE

3. Daftar alat, perangkat lunak dan data yang harus disiapkan

•Hardware: ESP32 Development Board sesuai profil, DHT11, LCD 16×2 I2C, 2 push button, 3 LED, 1 potensiometer/trimpot, breadboard, kabel jumper, resistor LED 220–330 Ω dan kabel USB DATA.

•Laptop operator: Arduino IDE, ESP32 Boards Manager, library DHT (Adafruit), library LiquidCrystal_I2C yang cocok, PsychicMqttClient, driver USB sesuai CP210x/CH340.

•Server Linux: Docker Engine + Compose plugin, domain/subdomain dengan DNS A record, sertifikat TLS dari CA publik, port TCP 8084 dapat diakses klien.

•MQTTX (GUI) sebagai alat verifikasi, bukan aplikasi backend wajib.

•Node-RED + InfluxDB; Node-RED Dashboard v1 hanya untuk mereplikasi screenshot trial, dengan catatan legacy/deprecated.

•Kredensial yang diperlukan: akun teknolab, akun monitor (read-only), dan token InfluxDB. Jangan menyebarkan kata sandi atau token.

3.1 Nilai konfigurasi yang harus sudah diketahui sebelum mulai

ParameterContoh/Isian operatorHasil cek
FQDN broker WSSbroker-latihan.DOMAIN-ANDADNS resolve ke IP server
Listener publikTCP 8084 + TLSWSS handshake dan CONNECT berhasil
Path Mosquitto langsung/ (URI wss://host:8084/)JANGAN pakai /mqtt hanya karena contoh EMQX
Server Node-REDlocalhost:1880 (SSH tunnel)Editor dapat dibuka
Server InfluxDBlocalhost:8086 (SSH tunnel)UI Data Explorer dapat dibuka
Wi-Fi fisikSSID 2,4 GHz; password validESP32 mendapat IP
Wi-Fi simulasiCirkitWifi open, jika simulator mendukungWi-Fi simulator connected

4. Wiring hardware fisik — langkah terukur

4.1 Pin yang digunakan pada ESP32-WROOM (fisik)

KomponenKoneksi ESP32-WROOMCatatan pemeriksaan
DHT11 DATAGPIO4VCC sesuai modul; GND bersama; pull-up jika DHT bare
LCD SDA/SCLGPIO21 / GPIO22Backpack I2C 0x27; lihat risiko pull-up 5 V
PB1 / PB2GPIO18 / GPIO19Satu kaki ke GPIO, kaki lain ke GND, INPUT_PULLUP
LED hijau/kuning/merahGPIO25 / 26 / 27Resistor seri 220–330 Ω di tiap LED
Pot tengah/wiperGPIO34 ADC1Dua ujung pot: 3.3 V & GND, bukan 5 V
GND semua modulGND ESP32Wajib common ground

4.2 Profil alternatif ESP32-S3 untuk simulasi

SinyalGPIO contoh pada S3Tindakan wajib
DHT114Cocokkan dengan pinout board S3 yang tersedia di Cirkit
LCD SDA / SCL11 / 12Periksa I2C wiring simulator
PB1 / PB28 / 9INPUT_PULLUP dan GND
LED G / Y / R15 / 16 / 17Resistor seri tiap LED
Pot ADC1GPIO1 merupakan jalur ADC pada S3; bukan GPIO34

Profil S3 di atas merupakan rancangan pemetaan contoh yang tetap harus dicocokkan dengan pin fisik yang diekspos oleh komponen Cirkit. Source firmware memilih profil berdasarkan target kompilasi CONFIG_IDF_TARGET_ESP32S3.

4.3 Urutan perakitan yang aman

1Matikan sambungan USB ESP32. Pastikan jenis board dan tegangan kerja komponen; siapkan multimeter.
2Hubungkan semua GND; sambungkan DHT11 VCC mengikuti modul. Bila DHT11 bare, tambahkan pull-up DATA ke supply sesuai datasheet.
3Hubungkan pot ke 3.3 V dan GND; wiper ke GPIO ADC. Ukur: wiper bergerak antara kira-kira 0–3,3 V dan tidak pernah melebihi 3,3 V.
4Rakit tiga LED dengan resistor seri 220–330 Ω. Pastikan polaritas LED yang benar; hindari resistor bernilai sangat kecil.
5Rakit PB1 dan PB2 menyeberangi celah breadboard; satu sisi ke GPIO dan sisi berlawanan ke GND. Verifikasi pembacaan HIGH saat dilepas, LOW saat ditekan.
6Hubungkan LCD I2C. Jika backpack diberi 5 V, periksa apakah SDA/SCL ditarik naik ke 5 V. Gunakan level shifter bidirectional atau konfigurasi aman 3,3 V yang teruji.
7Hubungkan USB DATA. Lakukan pemeriksaan panas/short dan cek 3.3 V pada modul. Jika ada komponen panas, lepaskan segera.
⚠️ RISIKO HARDWARE I2C backpack berbasis PCF8574 sering dipasok 5 V dan mempunyai pull-up SDA/SCL ke VCC. Menghubungkannya langsung ke ESP32 3,3 V dapat berisiko. Jangan hanya melihat LCD menyala lalu menganggap level sinyal sudah aman.

4.4 Pemeriksaan awal perangkat lokal

TesHasil lulusJika gagal
USB/COMPort baru terlihat di Arduino IDEGanti kabel data/driver, jangan mengubah program dulu
DHT11Suhu dan RH valid setelah ≥2 detikCek VCC, pin data, pull-up dan tipe sensor
PotLimit berubah 20–35 saat diputarCek wiper, 3.3 V, GPIO dan ADC1
LEDNORMAL hijau stabilCek resistor seri, polaritas, pin mapping
PB1Satu tekan -> satu pergantian halamanCek arah kaki tombol dan debounce
PB2WARNING/CRITICAL -> ACK stabilACK tidak boleh mengubah state sebenarnya
LCDDua baris tidak flickerCek 0x27 vs 0x3F, SDA/SCL dan catu daya

5. Simulasi Cirkit Designer — dari proyek kosong

8Buka Cirkit Designer dan buat project baru kosong; jangan memakai template yang sudah disediakan untuk memahami setiap sambungan.
9Pilih board yang simulator benar-benar dukung. Dokumentasi terbaru menampilkan ESP32-S3; jangan memaksakan skema GPIO WROOM jika board S3.
10Tambahkan DHT11, LCD 16×2/I2C backpack, 3 LED, resistor seri, 2 push button, trimpot dan jalur catu daya.
11Terapkan mapping pin menurut tabel profil board; beri nama setiap sinyal dan pastikan GND tersambung.
12Mulai dengan sketch uji kecil: Serial, lalu DHT, pot, LED, LCD, tombol. Jangan langsung menambahkan WSS.
13Buka Serial Monitor simulasi; pastikan WiFi.begin("CirkitWifi", "") mendapat status connected jika didukung.
14Uji penambahan library PsychicMqttClient dan sertifikat root CA. STOP GATE bila compile gagal karena library tidak kompatibel; jangan menganggap source WROOM dapat ditempel tanpa penyesuaian.
15Jika berhasil compile, atur TARGET_SIM=1 dan pastikan pada MQTTX pesan SIM berasal dari client ID yang berbeda dari HW.
16Simpan proyek dan salin public link. Tuliskan pin mapping SIM yang benar pada formulir dokumentasi teknis.

Catatan platform: situs Cirkit mendokumentasikan kemampuan Wi-Fi, MQTT, WebSocket dan Arduino sketch pada ESP32-S3, tetapi ini bukan bukti bahwa setiap library pihak ketiga termasuk PsychicMqttClient pasti dapat dijalankan di setiap versi simulaturnya. Tahap kompilasi native Cirkit adalah pengujian wajib.

6. Memahami firmware hasil revisi

Kode revisi di paket berisi SmartLab_Teknolab.ino + SmartLabLogic.h + Secrets.h. Berkas Secrets.h dibuat dari Secrets.template.h. Kode tidak memakai ArduinoJson karena struktur field tetap dan dapat disusun menggunakan snprintf dengan tipe angka/boolean/null yang dijaga; Node-RED memvalidasi JSON kembali.

6.1 Urutan fungsi loop yang benar

Inti alur loop (dari firmware revisi):
void loop() {
  uint32_t now=millis();
  updateLimit(now);          // baca trimpot
  updateSensor(now);         // baca DHT11 setiap >= 2s
  alarmControl.update(sensorOk,temperatureC,humidityPct,limitC,now);
  handleButtons(now);        // debounce 30ms
  updateLeds(now);           // alarm lokal
  updateLcd(now);            // refresh ~500ms
  updateNetwork(now);        // WSS & WiFi
  publishTelemetry(now);     // publish ~5s
}

Perhatikan pembacaan pot dan sensor dilakukan sebelum evaluasi state. Ini menghindari satu periode telemetry memakai batas/status lama yang menjadi salah satu risiko TESS1.ino.

6.2 Fungsi kontrol yang telah diuji

•State priority: SENSOR_ERROR > CRITICAL > WARNING > NORMAL.

•ACK hanya aktif di WARNING/CRITICAL, reset ketika transisi atau error, tidak mematikan pembacaan sensor.

•LED berkedip berdasarkan selisih millis unsigned, tetap benar pada overflow counter.

•Debounce membaca perubahan HIGH→LOW satu kali per tekan, tanpa while/delay.

•Pemetaan ADC 0–4095 ke 20–35 dibatasi agar tidak lewat range.

•Topic HW berbeda dari SIM dan device_id mengikuti identitas proyek serta sumber.

7. Persiapan Arduino IDE dan firmware

17Pada Arduino IDE pilih Tools → Board → Boards Manager, instal ESP32 by Espressif Systems (major 3.x yang cocok dengan library). Pilih board sesuai chip fisik, bukan sekadar gambar.
18Instal DHT sensor library dari Adafruit (beserta Adafruit Unified Sensor jika diminta); instal LiquidCrystal_I2C sesuai backpack; pasang PsychicMqttClient dari repo resmi yang mendukung ESP32 Arduino.
19Salin ketiga file SmartLab_Teknolab.ino, SmartLabLogic.h dan Secrets.template.h ke satu folder sketch. Buat salinan bernama Secrets.h.
20Isi WIFI_SSID, WIFI_PASSWORD, MQTT_WSS_URI, MQTT_USERNAME dan MQTT_PASSWORD pada Secrets.h. Jangan mengubah certificate block bila server memakai Let’s Encrypt chain yang cocok; untuk CA lain masukkan root CA yang benar.
21Atur TARGET_SIM=0 untuk ESP32 fisik (device_id TEKNOLAB_HW), atau TARGET_SIM=1 untuk simulasi (device_id TEKNOLAB_SIM). Isi akun MQTT teknolab di Secrets.h dan pilih target board serta mapping GPIO yang benar.
22Klik Verify. Jika tidak lolos, lihat error pertama (bukan 20 baris terakhir). Periksa nama header, versi core, board target dan ketersediaan PsychicMqttClient.
23Klik Upload ke ESP32, buka Serial Monitor 115200. Pastikan sensor lokal dan LCD tampil terlebih dahulu sebelum mengejar pesan WSS.
24Jika Serial menunjukkan MQTT WSS CONNECTED, cek telemetry pada MQTTX. Jika tidak, periksa waktu NTP, CA chain, DNS, port 8084 dan kredensial secara berurutan.

7.1 Makna hasil publish()

Pada client asynchronous, ID hasil publish bukan bukti pesan sudah masuk InfluxDB. Sukses minimal: pesan terlihat pada MQTTX; sukses backend: node Debug menerima objek JSON; sukses histori: titik terlihat di InfluxDB; sukses dashboard: widget berubah. Keempatnya harus diverifikasi terpisah.

8. MQTT over WSS: transport dan keamanan

IstilahTransportContoh
MQTT TCPMQTT langsung di TCPmqtt://host:1883 (tidak memenuhi syarat HW/SIM WSS)
MQTT TLSMQTT di TLS langsungmqtts://host:8883 (bukan WebSocket)
MQTT over WSSWebSocket secure + MQTT + TLSwss://FQDN:8084/ untuk Mosquitto direct
WebSocket EMQX publikWSS dengan path providerwss://broker.emqx.io:8084/mqtt (contoh trial, bukan Mosquitto lokal)

8.1 Sertifikat & waktu sistem

WSS membutuhkan hostname dan sertifikat TLS yang cocok. FQDN dalam sertifikat harus sama dengan URI; sertifikat harus belum kedaluwarsa; RTC/NTP pada ESP32 harus mengetahui waktu cukup akurat. Program menunggu epoch masuk akal sebelum connect. Root CA template adalah ISRG Root X1, berlaku hanya bila chain server ditandatangani CA tersebut.

Jangan menyiasati masalah dengan setInsecure() atau mematikan SSL Secure pada MQTTX. Jika root salah, ambil chain CA yang benar dari penyedia sertifikat dan uji kembali.

8.2 Alur lapisan komunikasi (bukan hanya mengganti port)

ESP32 → Wi-Fi 2,4 GHz → DNS FQDN → koneksi TCP port 8084 → TLS handshake & verifikasi sertifikat → WebSocket Upgrade → MQTT CONNECT → MQTT PUBLISH JSON. Gagal di satu lapisan menyebabkan payload tidak sampai, walaupun LED lokal tetap menyala.

9. Instal Mosquitto WSS + Node-RED + InfluxDB tanpa CloudPanel

9.1 Persiapan DNS dan firewall

25Gunakan server Linux yang dapat diakses lewat SSH. Pastikan Docker Engine dan docker compose plugin sudah tersedia.
26Siapkan DNS A record, misalnya broker-latihan.DOMAIN-ANDA, ke IP publik server. Jika memakai NAT, arahkan port 8084 TCP ke server.
27Buka 80 TCP sementara untuk validasi sertifikat Certbot HTTP-01. Node-RED 1880 dan InfluxDB 8086 tidak boleh diekspos publik dalam konfigurasi ini.
28Dapatkan sertifikat CA publik untuk subdomain menggunakan Certbot standalone. Pastikan proses lain tidak sedang memakai port 80.
29Salin PEM ke server/mosquitto/certs dengan hak akses yang memungkinkan UID Mosquitto 1883 membaca private key secara aman.
30Buat password file Mosquitto untuk akun teknolab dan monitor; ACL membatasi jalur publikasi proyek dan membuat akun monitor hanya memiliki izin baca.
31Salin .env.example menjadi .env, isi org, bucket, password dan token. Set chmod 600.
32Jalankan docker compose config, lalu docker compose up -d. Periksa status dan log sampai tidak ada error fatal.
Perintah contoh (jalankan pada direktori server/ setelah prasyarat siap):
docker version
docker compose version
cp .env.example .env
chmod 600 .env
# EDIT .env terlebih dahulu; jangan pakai placeholder!
docker compose config
docker compose up -d
docker compose ps
docker compose logs --tail 100 mosquitto

9.2 Listener broker dan ACL

Perhatikan dua listener Mosquitto. Port 1883 hanya berada dalam jaringan Docker dan tidak dipublikasi ke host. Klien hardware dan simulasi memakai WSS 8084. Node-RED boleh menjadi subscriber internal MQTT TCP 1883 karena kedua pengirim HW dan SIM menggunakan WSS sesuai rancangan.

Potongan mosquitto.conf:
# Mosquitto 2.x: listener TCP hanya di jaringan Docker (tidak dipublish ke host).
listener 1883 0.0.0.0
protocol mqtt

# Listener WebSocket Secure: TLS selesai di Mosquitto, bukan NGINX/CloudPanel.
# Endpoint untuk klien WSS: wss://DNS_ANDA:8084/
listener 8084 0.0.0.0
protocol websockets
certfile /mosquitto/certs/fullchain.pem
keyfile /mosquitto/certs/privkey.pem

allow_anonymous false
password_file /mosquitto/config/passwords
acl_file /mosquitto/config/acl
persistence true
persistence_location /mosquitto/data/
log_dest stdout
connection_messages true
ACL yang dipakai:
# Satu akun publikasi untuk proyek Teknolab.
user teknolab
topic readwrite smartlab/teknolab/#
# Node-RED memantau seluruh jalur proyek tanpa hak publish.
user monitor
topic read smartlab/teknolab/#

9.3 Docker Compose

Paket pendamping mencakup docker-compose.yml, mosquitto.conf, acl, template .env dan contoh panduan izin file. Container Node-RED dan InfluxDB di-bind ke 127.0.0.1 sehingga hanya dapat diakses secara langsung dari server atau SSH tunnel.

Potongan docker-compose.yml:
# Paket latihan internal - tidak memakai CloudPanel.
# WSS publik 8084, Node-RED dan InfluxDB hanya loopback server.
services:
  mosquitto:
    image: eclipse-mosquitto:2
    restart: unless-stopped
    ports:
      - '8084:8084/tcp'
    volumes:
      - ./mosquitto/config:/mosquitto/config:ro
      - ./mosquitto/certs:/mosquitto/certs:ro
      - mosquitto_data:/mosquitto/data
    networks: [iotnet]
  influxdb:
    image: influxdb:2.7
    restart: unless-stopped
    ports:
      - '127.0.0.1:8086:8086/tcp'
    environment:
      DOCKER_INFLUXDB_INIT_MODE: setup
      DOCKER_INFLUXDB_INIT_USERNAME: ${INFLUX_USER:?fill env}
      DOCKER_INFLUXDB_INIT_PASSWORD: ${INFLUX_PASSWORD:?fill env}
      DOCKER_INFLUXDB_INIT_ORG: ${INFLUX_ORG:?fill env}
      DOCKER_INFLUXDB_INIT_BUCKET: ${INFLUX_BUCKET:?fill env}
      DOCKER_INFLUXDB_INIT_ADMIN_TOKEN: ${INFLUX_ADMIN_TOKEN:?fill env}
    volumes:
      - influxdb_data:/var/lib/influxdb2
      - influxdb_config:/etc/influxdb2
    networks: [iotnet]
  nodered:
    image: nodered/node-red:4.1
    restart: unless-stopped
    ports:
      - '127.0.0.1:1880:1880/tcp'
    volumes:
      - nodered_data:/data
    depends_on: [mosquitto, influxdb]
    networks: [iotnet]
volumes:
  mosquitto_data:
  influxdb_data:
  influxdb_config:
  nodered_data:
networks:
  iotnet:

9.4 Kendala sertifikat yang harus diuji

GejalaPenyebab seringTindakan terarah
Mosquitto restart loopPEM tidak terbaca oleh UID servicePeriksa docker logs, chown/chmod file cert
TLS error “unknown CA”Root CA tidak cocok dengan chainPeriksa issuer dan gunakan root CA yang tepat
TLS error “hostname mismatch”Terhubung dengan IP, SAN hanya FQDNGunakan FQDN yang tercantum dalam sertifikat
TLS handshake gagal / timeoutFirewall atau NAT port 8084Periksa TCP reachability, bukan ganti topic
MQTT auth refusedUsername/password atau ACL salahTes MQTTX dengan kredensial proyek

10. Verifikasi broker menggunakan MQTTX (sebelum ESP32)

33Buka MQTTX dan buat koneksi pengujian TEKNOLAB_TEST (gunakan Client ID berbeda dari TEKNOLAB_HW atau TEKNOLAB_SIM yang sedang online).
34Pilih protocol wss://, host sesuai FQDN, port 8084, path / untuk broker Mosquitto langsung. Aktifkan SSL/TLS dan validasi sertifikat.
35Isi username teknolab dan password yang dibuat dengan mosquitto_passwd. Gunakan client ID unik misalnya OPERATOR_TEST_001.
36Klik Connect. Target status Connected; bila gagal buka detail error SSL, path, user atau network.
37Subscribe smartlab/teknolab/# QoS 0. Publish satu JSON contoh ke smartlab/teknolab/hw/telemetry untuk menguji broker/Node-RED.
38Uji ACL: akun teknolab boleh publish/subscribe pada smartlab/teknolab/#. Akun monitor hanya boleh subscribe pada namespace tersebut; percobaan publish oleh monitor harus ditolak.
39Jangan menyimpulkan database sudah bekerja hanya dari MQTTX: cek Debug Node-RED dan InfluxDB.
Payload uji buatan operator:
{
  "device_id":"TEKNOLAB_HW", "source":"hardware",
  "temperature_c":28.0, "humidity_pct":67.0,
  "threshold_temp_c":26, "status":"WARNING",
  "ack":false, "sensor_ok":true,
  "uptime_ms":120000, "sequence":24
}
Contoh WARNING di atas tepat karena 28 >= threshold 26 dan 28 < threshold+3 (29). Jangan gunakan payload dengan kondisi status yang tidak konsisten; validator akan menolaknya.

11. Menghubungkan firmware fisik serta simulasi

40Pada Secrets.h pilih URI WSS sesuai Mosquitto lokal, misalnya wss://broker-latihan.DOMAIN-ANDA:8084/ (bukan URL EMQX dengan /mqtt).
41Atur TARGET_SIM=0 pada perangkat fisik sehingga device_id TEKNOLAB_HW dan topic .../teknolab/hw/telemetry.
42Untuk simulasi pilih TARGET_SIM=1 dan board sesuai target Cirkit. Jika simulator mengharuskan ESP32-S3, gunakan pin alternatif, bukan memaksa GPIO34.
43Pastikan simulasi benar-benar mengompilasi library PsychicMqttClient atau library WSS lain yang kompatibel; jangan menganggap dukungan WebSocket generik menjamin API yang sama.
44Di MQTTX subscribe smartlab/teknolab/+/telemetry; jalankan kedua pengirim bergantian dahulu, lalu bersamaan.
45Pastikan dua device_id berbeda dan keduanya memakai source hardware/software. Hanya nilai sensor aktual yang boleh berbeda antara HW dan SIM.
46Putuskan Wi-Fi perangkat; LED, tombol dan LCD wajib tetap bekerja. Sambungkan lagi; status MQTT harus kembali ON dan publish dilanjutkan.

11.1 Source pemrograman MQTT

Pemakaian PsychicMqttClient mengikuti dokumentasi API resminya: setServer mendukung wss://, setCACert, setCredentials, setClientId, setAutoReconnect, setWill, connect, connected, publish. Library menyatakan dukungan Arduino core 2/3 dan semua varian ESP32, tetapi pengembang menyatakan Arduino IDE tidak diuji aktif dan Cirkit tidak disebut sebagai target yang telah diverifikasi. Karena itu tahap kompilasi native tidak boleh dilewati.

12. Node-RED: menerima, memvalidasi, memisahkan dan mengirim data

12.1 Mengakses editor

47Dari laptop operator gunakan SSH tunnel untuk akses aman: ssh -L 1880:127.0.0.1:1880 -L 8086:127.0.0.1:8086 user@SERVER.
48Buka http://localhost:1880. Di Manage Palette instal node-red-dashboard legacy 3.6.6 dan node-red-contrib-influxdb sesuai flow paket. Dashboard lama dipilih semata untuk meniru screenshot trial.
49Impor flow Node-RED latihan versi Teknolab (flow_teknolab_hw_sim.json) melalui Menu > Import > Clipboard > Deploy. File flow perlu disesuaikan dengan konfigurasi broker yang digunakan.
50Buka config node MQTT_INTERNAL: host mosquitto, port 1883, username monitor dan password monitor. Bukan URI wss:// karena koneksi backend internal dibatasi Docker.
51Buka config node INFLUX2: pilih version 2.0, URL http://influxdb:8086, org teknolab, bucket teknolab_lab, masukkan token (jangan ekspor beserta token).
52Deploy. Lihat debug. Untuk tiap JSON valid harus muncul pesan yang telah diparse, team dan src yang benar.
53Buka dashboard localhost:1880/ui dan tampilkan dua panel sumber terpisah: TEKNOLAB_HW dan TEKNOLAB_SIM.
54Jika dashboard hidup tetapi data histori kosong, cek InfluxDB lebih dulu, kemudian query pada node influxdb in dan pengaturan chart.

12.2 Struktur flow yang disarankan

Diagram node:
MQTT IN (smartlab/teknolab/+/telemetry)
   -> JSON (always output object)
   -> Function: Validate Topic + Schema
   -> Switch: teknolab/hw | teknolab/sim
   -> Function 5 output:
        1 suhu -> gauge
        2 kelembapan -> gauge
        3 threshold -> gauge
        4 status/ACK -> text
        5 [fields, tags] -> InfluxDB out (measurement smart_lab)
   -> InfluxDB in (Flux query) -> function format -> ui_chart historis

Node JSON harus disetel “always output object”, bukan toggle. Jika input sudah object lalu node JSON men-stringify kembali, validator yang mengharapkan objek akan menolak pesan.

12.3 Pengendalian kualitas data sebelum tersimpan

Validator mengecek path topic, 10 field, tipe boolean, angka, device_id sesuai topic, source sesuai HW/SIM, status hasil perhitungan terhadap suhu/kelembapan dan limit, serta error null/null. Telemetry salah tidak diteruskan ke dashboard atau InfluxDB. Ini mencegah “dashboard terlihat normal” meskipun payload salah.

Function Validate Telemetry:
// Node-RED Function, 1 output. Tempatkan setelah node JSON (action: always convert to object).
const m = /^smartlab\/(teknolab)\/(hw|sim)\/telemetry$/.exec(msg.topic || '');
let d = msg.payload;
if (!m || !d || typeof d !== 'object' || Array.isArray(d)) {
    node.warn('Format topik / JSON bukan object'); return null;
}
const keys = ['device_id','source','temperature_c','humidity_pct',
 'threshold_temp_c','status','ack','sensor_ok','uptime_ms','sequence'];
if (!keys.every(k => Object.prototype.hasOwnProperty.call(d,k))) {
    node.warn('JSON kehilangan field wajib'); return null;
}
const expectedId = m[1].toUpperCase()+'_'+m[2].toUpperCase();
const expectedSource = m[2] === 'hw' ? 'hardware' : 'software';
if (d.device_id !== expectedId || d.source !== expectedSource) {
    node.warn('Mismatch antara topic dan identitas pada JSON'); return null;
}
if (typeof d.ack !== 'boolean' || typeof d.sensor_ok !== 'boolean' ||
    !Number.isInteger(d.threshold_temp_c) || d.threshold_temp_c < 20 || d.threshold_temp_c > 35 ||
    !Number.isInteger(d.uptime_ms) || d.uptime_ms < 0 ||
    !Number.isInteger(d.sequence) || d.sequence < 1) {
    node.warn('Tipe atau rentang parameter tidak benar'); return null;
}
const valid = d.sensor_ok;
if (valid) {
    if (!Number.isFinite(d.temperature_c) || !Number.isFinite(d.humidity_pct) ||
        d.temperature_c < -40 || d.temperature_c > 80 ||
        d.humidity_pct < 0 || d.humidity_pct > 100) {
        node.warn('Sensor dinyatakan OK tapi angka tidak valid'); return null;
    }
} else if (d.temperature_c !== null || d.humidity_pct !== null || d.ack) {
    node.warn('SENSOR_ERROR wajib null/null, ack=false'); return null;
}
const expectedStatus = !valid ? 'SENSOR_ERROR' :
    (d.temperature_c >= d.threshold_temp_c+3 || d.humidity_pct>=85 ? 'CRITICAL' :
     (d.temperature_c >= d.threshold_temp_c || d.humidity_pct>=75 ? 'WARNING' : 'NORMAL'));
if (d.status !== expectedStatus || ((d.status==='NORMAL'||d.status==='SENSOR_ERROR')&&d.ack)) {
    node.warn('Status / ACK tidak mengikuti prioritas kasus'); return null;
}
msg.team = m[1];
msg.src = m[2];
msg.payload = d;
return msg;
Function Split 5 Outputs:
// Node-RED Function (5 outputs): suhu, RH, limit, status, titik InfluxDB.
const d=msg.payload;
const temp=d.sensor_ok?{topic:msg.topic,payload:d.temperature_c}:null;
const hum=d.sensor_ok?{topic:msg.topic,payload:d.humidity_pct}:null;
const threshold={topic:msg.topic,payload:d.threshold_temp_c};
const status={topic:msg.topic,payload:d.status+' | ACK:'+(d.ack?'YA':'TIDAK')};
const fields={status:d.status,ack:d.ack,sensor_ok:d.sensor_ok,
  threshold_temp_c:d.threshold_temp_c,uptime_ms:Number(d.uptime_ms),
  sequence:Number(d.sequence)};
if(d.sensor_ok){fields.temperature_c=d.temperature_c;fields.humidity_pct=d.humidity_pct;}
const tags={team:msg.team,source:msg.src,device_id:d.device_id};
const influx={payload:[fields,tags]};
return [temp,hum,threshold,status,influx];

13. InfluxDB 2.x dan line protocol

Data dari ESP32 tetap dikirim sebagai JSON lewat MQTT WSS. Node-RED mengubah objek menjadi measurement + tags + fields yang ditulis ke InfluxDB. Ini bukan berarti perangkat harus langsung mengirim teks Line Protocol ke MQTT.

BagianNilai yang dipakaiFungsi
Bucketteknolab_labKumpulan data histori
Measurementsmart_labJenis pengukuran
Tagsteam, source, device_idFilter dua jalur perangkat: hardware dan simulasi
Fieldstemperature_c, humidity_pct, status, ack, threshold, uptime, sequenceNilai data
TimestampWaktu tulis serverUntuk grafik historis; bisa diperluas menjadi waktu capture
Contoh Line Protocol ilustratif (hasil transformasi Node-RED):
smart_lab,team=teknolab,source=hw,device_id=TEKNOLAB_HW temperature_c=28.0,humidity_pct=67.0,threshold_temp_c=26i,status="WARNING",ack=false,sensor_ok=true

13.1 Query data di Data Explorer

55Melalui SSH tunnel buka http://localhost:8086; login akun Influx operator.
56Pilih bucket teknolab_lab, measurement smart_lab, field temperature_c, dan time range Last 1 hour.
57Jalankan query Flux contoh berikut; hasil harus menampilkan titik dengan tag team=teknolab/source=hw.
58Jika kosong, periksa debug Node-RED → node influxdb out, token/organisasi/bucket, dan waktu server. Periksa juga apakah perangkat baru mengirim SENSOR_ERROR sehingga field temperature_c sengaja tidak ditulis.
59Bandingkan device_id TEKNOLAB_SIM dan TEKNOLAB_HW. Histori harus terpisah, tidak saling tertimpa.
Flux query satu sumber:
from(bucket:"teknolab_lab")
  |> range(start:-1h)
  |> filter(fn:(r)=>r._measurement=="smart_lab")
  |> filter(fn:(r)=>r._field=="temperature_c")
  |> filter(fn:(r)=>r.team=="teknolab" and r.source=="hw")
  |> sort(columns:["_time"])

14. Dashboard Node-RED: real-time dan grafik histori

Untuk mengikuti contoh dashboard latihan, flow memakai ui_gauge, ui_text, dan ui_chart dari node-red-dashboard v1. Paket ini sudah deprecated; untuk sistem permanen disarankan migrasi ke FlowFuse Dashboard 2, tetapi format node tidak kompatibel satu-banding-satu.

WidgetSumberFormat output
SuhuFunction output 1msg.payload = Number temperatur
KelembapanFunction output 2msg.payload = Number RH
T LimitFunction output 3msg.payload = angka 20–35
Status/ACKFunction output 4msg.payload = teks ringkas
Grafik historisInfluxDB in → format history → ui_chartTime series benar-benar berasal dari database

Pisahkan dua panel: TEKNOLAB/HW dan TEKNOLAB/SIM. Client fisik dan simulasi harus berbeda device_id dan topic; jangan membuat satu gauge menerima seluruh pesan wildcard tanpa pemisahan, karena nilai dapat saling menimpa.

15. Uji penerimaan — memastikan semua fungsi bekerja

Berikut adalah 12 skenario pengujian yang digunakan untuk memverifikasi spesifikasi sistem. Pada simulasi, nilai sensor dapat diatur untuk menghasilkan trigger. Pada hardware, gunakan nilai asli dan ubah limit secara aman; jangan memalsukan data sensor agar cocok tabel.

IDTrigger (contoh)Hasil WAJIB
T01T=25, RH=60, limit=28NORMAL, LED hijau stabil
T02T=28, RH=60, limit=28WARNING, LED kuning kedip
T03T=31, RH=60, limit=28CRITICAL, LED merah kedip
T04T=25, RH=76, limit=28WARNING karena kelembapan
T05T=25, RH=86, limit=28CRITICAL karena kelembapan
T06T=31, RH=76, limit=28CRITICAL menang terhadap WARNING
T07Tekan PB1 satu kaliLCD pindah halaman sekali, tanpa ganda
T08Tekan PB2 saat WARNINGack=true, status tetap WARNING, kuning stabil
T09Naik WARNING → CRITICALACK reset false, merah berkedip
T10Putus sensor DHT11SENSOR_ERROR, dua LED berkedip, JSON null/null
T11WSS off 30 s, kemudian pulihSensor/LED/LCD tetap; koneksi dan publish kembali
T12HW + SIM bersamaanDua client/topic berbeda; JSON valid, InfluxDB & dashboard terpisah

15.1 Catatan bukti untuk setiap tes

60Catat nilai aktual sensor, limit, state, dan kondisi LED; jangan hanya mencentang berdasarkan source.
61Untuk T07/T08 rekam tekan tombol dan lihat perubahan ACK/halaman; tahan tombol 2 detik untuk memastikan tidak terjadi repeat.
62Untuk T10, catat hasil sensor invalid. Pada payload pertama setelah jaringan tersambung, kedua field sensor harus null bila sensor masih gagal.
63Untuk T11, catat timestamp putus/pulih, status di LCD dan berapa lama sampai MQTTX melihat data lagi.
64Untuk T12, cek dari tiga sisi: MQTTX, InfluxDB Data Explorer, dashboard. Ketiganya harus sesuai.

16. Troubleshooting berdasarkan gejala, bukan coba-coba

GejalaLakukan pemeriksaan pertamaTindakan tepat
Arduino IDE: file PsychicMqttClient.h not foundLibrary path / Library ManagerPasang library resmi versi cocok sebelum mengubah kode
Compile gagal di CirkitBoard target, library custom di simulatorUji kemampuan library; jangan otomatis klaim kompatibel
DHT menghasilkan NaNVCC/DATA/GND, interval 2 detik, pull-upUji DHT tanpa jaringan terlebih dahulu
LCD blankAlamat 0x27/0x3F, I2C SDA SCLI2C scanner dan cek level listrik
Pot tidak berubahGPIO pot pada WROOM vs S3Perbaiki mapping; ukur wiper 0–3.3 V
LED salah statusPrioritas CRITICAL/WARNINGLihat uji T01–T06 dan logika SmartLabLogic.h
PB double-triggerWiring dan debounceSatu input pull-up; edge 30 ms
Wi-Fi gagalSSID 2.4 GHz + password / CirkitWifiJalankan contoh Wi-Fi minimal
WSS timeoutDNS, port 8084, NAT, firewallTes dari MQTTX dan periksa docker logs
WSS cert invalidFQDN SAN, root CA dan jam NTPPerbaiki chain CA, jangan matikan TLS verify
MQTT MQTTX sukses, Node-RED kosongBroker internal/ACL topicCek MQTT node connected + Debug
Node-RED gauge kosongmsg.payload salah tipe / validator rejectPeriksa node JSON action obj dan Function Debug
InfluxDB kosongURL, token, org, bucket, storage writeCek Data Explorer dan node out error
Graph flat sajaCache chart lama, query salah filterKonfirmasi query membaca Influx, bukan inject contoh

17. Formulir dokumentasi implementasi

IdentitasIsi
Identitas perangkat☐ TEKNOLAB_HW ☐ TEKNOLAB_SIM
Pengelola proyek________________________________________
Pemeriksaan sistem____________ / ____________
URL projek Cirkit publik________________________________________
Pin sinyalHW GPIOSIM GPIOCatatan
DHT11_______________________________
LCD SDA/SCL___ / ______ / ____________________
PB1/PB2___ / ______ / ____________________
LED Hijau/Kuning/Merah___ / ___ / ______ / ___ / ____________________
Potensiometer ADC_______________________________
MQTT/JSONHWSIM
WSS URL________________________________________________
Client ID________________________________________________
Telemetry topic________________________________________________
Device ID________________________________________________
QoS / period0 / 5 detik0 / 5 detik

Payload JSON yang harus dicentang: ☐ device_id ☐ source ☐ temperature_c ☐ humidity_pct ☐ threshold_temp_c ☐ status ☐ ack ☐ sensor_ok ☐ uptime_ms ☐ sequence

Bukti WSS connect: __________________ | Bukti InfluxDB: __________________ | Dashboard URL: __________________

Tanda tangan anggota: __________________ / __________________ Jam penyerahan: __________

18. Alur kerja paralel hardware dan simulasi

TahapPerangkat fisik (hardware)Simulasi Cirkit Designer
Fase 1Sepakati GPIO, state, JSON dan pembagian tugasSepakati kontrak; buat project kosong
Fase 2Wiring aman + pemeriksaan 3.3VSkematik virtual + kecocokan pin board
Fase 3DHT/ADC/LCD/LED/PB lokalUji sensor/LED/LCD/PB dalam simulator
Fase 4State machine, ACK, debounceState, ACK, debounce setara
Fase 5WSS hardware + JSON validWSS simulasi + JSON valid
Fase 6Offline/reconnect, cek MQTTXCek 4 panel, Influx & graph
Fase 7Pemeriksaan akhir sensor dan wiringIsi dokumentasi dan salin URL simulasi
Fase 8Simpan dan beri versi kode yang sudah diujiPemeriksaan kelengkapan dokumentasi

Urutan kerja berikut merupakan contoh alur implementasi yang dapat disesuaikan dengan kebutuhan. Hardware dan simulasi dikerjakan paralel, tetapi masing-masing perlu lulus tes yang sama.

19. Laporan hasil audit engineering (apa yang telah dan belum diuji)

Pengujian otomatis terhadap paket dilakukan di lingkungan Linux dengan g++ C++17, Node.js 22, Python 3/PyYAML dan OpenSSL. Docker, Arduino CLI, ESP32 fisik, Cirkit Designer dan broker WSS nyata tidak tersedia pada lingkungan penulis.

ID buktiPengujianHasil aktual
AUTO-C++39 kontrol state/ACK/debounce/rolloverPASS
AUTO-JS139 validator/topic/JSON/Node-RED flowPASS
AUTO-SERVER15 static YAML, ACL dan cert templatePASS
MOCK-SKETCHKompilasi C++ dengan stub API + snprintf JSONPASS, bukan compiler ESP32 asli
TLS PEMOpenSSL parse Root CA ISRG Root X1PASS, root berlaku hingga 2035
ESP32 COMPILEArduino Core + Psychic + LCD + DHTBELUM DIUJI NYATA
CIRKIT RUNLibrary WSS & GPIO sesuai simulatorBELUM DIUJI NYATA
MOSQUITTO LIVEHandshake WSS dan ACL nyataBELUM DIUJI NYATA
INFLUX LIVEHistori masuk lewat Node-REDBELUM DIUJI NYATA
⚠️ KRITERIA “SIAP DIPAKAI” Modul ini sudah lolos audit lokal yang dapat dijalankan, tetapi sistem secara keseluruhan baru boleh dinyatakan siap setelah semua item bertanda BELUM DIUJI NYATA dibuktikan pada server, board dan simulator Anda. Tidak ada jaminan zero trial-and-error tanpa pengujian di lingkungan target.

20. Referensi teknis dan batas kompatibilitas

•PsychicMqttClient API dan metode WSS/TLS: https://github.com/theelims/PsychicMqttClient/blob/main/documentation.md

•ESP-MQTT (Espressif, WS dan WSS): https://docs.espressif.com/projects/esp-mqtt/en/latest/esp32/

•Cirkit ESP32-S3 simulation: https://www.cirkitdesigner.com/docs/platforms/esp32-s3

•Cirkit Wi-Fi virtual: https://www.cirkitdesigner.com/docs/guides/wifi-networking

•Mosquitto configuration: https://mosquitto.org/man/mosquitto-conf-5.html

•Node-RED InfluxDB node: https://flows.nodered.org/node/node-red-contrib-influxdb

•Dashboard v1 deprecated: https://flows.nodered.org/node/node-red-dashboard

•FlowFuse Dashboard 2 (untuk migrasi jangka panjang): https://dashboard.flowfuse.com/

LAMPIRAN A. Firmware lengkap untuk praktik mandiri

Tiga file harus berada dalam folder sketch yang sama: SmartLab_Teknolab.ino, SmartLabLogic.h, Secrets.h. Untuk praktis, salin dari paket ZIP yang menyertai dokumen ini. Blok berikut disajikan agar artikel ini tetap mandiri dan bisa diaudit.

SmartLab_Teknolab.ino:
/* TEKNOLAB - Smart Lab Environmental Guard
   Prototipe Teknolab: state, ACK, tombol, JSON dan MQTT over WSS.
   TARGET_SIM: 0 HW asli; 1 simulator. BOARD S3 memakai pin berbeda dari WROOM.
   Library: DHT sensor library (Adafruit), LiquidCrystal_I2C sesuai modul,
            PsychicMqttClient (theelims), Arduino-ESP32 core 3.x.
   Secrets.h wajib dibuat dari Secrets.template.h di folder sketch.
*/
#include <Arduino.h>
#include <WiFi.h>
#include <Wire.h>
#include <DHT.h>
#include <LiquidCrystal_I2C.h>
#include <PsychicMqttClient.h>
#include <time.h>
#include "SmartLabLogic.h"
#include "Secrets.h"

// EDIT TARGET_SIM SEBELUM UPLOAD / SIMULASI:
#define TARGET_SIM 0    // 0=ESP32 fisik; 1=Cirkit Designer
#if TARGET_SIM
static const char* ACTIVE_WIFI_SSID="CirkitWifi";
static const char* ACTIVE_WIFI_PASSWORD="";
#else
static const char* ACTIVE_WIFI_SSID=WIFI_SSID;
static const char* ACTIVE_WIFI_PASSWORD=WIFI_PASSWORD;
#endif

#define TEAM_SLUG "teknolab"
#define TEAM_NAME "TEKNOLAB"
#if TARGET_SIM
  #define SOURCE_SLUG "sim"
  #define SOURCE_NAME "software"
  #define SOURCE_ID TEAM_NAME "_SIM"
#else
  #define SOURCE_SLUG "hw"
  #define SOURCE_NAME "hardware"
  #define SOURCE_ID TEAM_NAME "_HW"
#endif
#define TOPIC_TELEM "smartlab/" TEAM_SLUG "/" SOURCE_SLUG "/telemetry"
#define TOPIC_AVAIL "smartlab/" TEAM_SLUG "/" SOURCE_SLUG "/availability"

// WROOM klasik berbeda dari ESP32-S3. Cocokkan label GPIO pada board nyata/simulasi.
#if defined(CONFIG_IDF_TARGET_ESP32S3)
static constexpr uint8_t PIN_DHT=4, PIN_PB1=8, PIN_PB2=9;
static constexpr uint8_t PIN_G=15, PIN_Y=16, PIN_R=17;
static constexpr uint8_t PIN_POT=1, PIN_SDA=11, PIN_SCL=12;
#else
static constexpr uint8_t PIN_DHT=4, PIN_PB1=18, PIN_PB2=19;
static constexpr uint8_t PIN_G=25, PIN_Y=26, PIN_R=27;
static constexpr uint8_t PIN_POT=34, PIN_SDA=21, PIN_SCL=22;
#endif

static constexpr uint32_t DHT_MS=2000, POT_MS=150, LCD_MS=500, MQTT_MS=5000;
static constexpr uint32_t WIFI_RETRY_MS=10000;
DHT dht(PIN_DHT, DHT11);
LiquidCrystal_I2C lcd(0x27,16,2);
PsychicMqttClient mqtt;
SmartLab::Alarm alarmControl;
SmartLab::DebouncedButton pbPage, pbAck;
float temperatureC=NAN, humidityPct=NAN;
bool sensorOk=false, mqttStarted=false, lastMqttConnected=false;
uint8_t limitC=28, page=1;
uint32_t tDht=0, tPot=0, tLcd=0, tPublish=0, tWifi=0, sequence=0;

void displayLine(uint8_t row, const String &s) {
  String line=s.substring(0,16);
  while(line.length()<16) line+=' ';
  lcd.setCursor(0,row); lcd.print(line);
}
void updateSensor(uint32_t now) {
  if ((uint32_t)(now-tDht)<DHT_MS) return;
  tDht=now;
  const float t=dht.readTemperature();
  const float h=dht.readHumidity();
  sensorOk=SmartLab::validSensor(t,h);
  temperatureC=sensorOk?t:NAN;
  humidityPct=sensorOk?h:NAN;
}
void updateLimit(uint32_t now) {
  if ((uint32_t)(now-tPot)<POT_MS) return;
  tPot=now;
  limitC=SmartLab::thresholdFromAdc((uint16_t)analogRead(PIN_POT));
}
void handleButtons(uint32_t now) {
  if(pbPage.pressed(digitalRead(PIN_PB1)==HIGH,now)) page=(page==1)?2:1;
  if(pbAck.pressed(digitalRead(PIN_PB2)==HIGH,now)) alarmControl.acknowledge();
}
void updateLeds(uint32_t now) {
  auto l=alarmControl.leds(now);
  digitalWrite(PIN_G,l.green?HIGH:LOW);
  digitalWrite(PIN_Y,l.yellow?HIGH:LOW);
  digitalWrite(PIN_R,l.red?HIGH:LOW);
}
void updateLcd(uint32_t now) {
  if ((uint32_t)(now-tLcd)<LCD_MS) return;
  tLcd=now;
  if(page==1) {
    if(sensorOk) displayLine(0,"T:"+String(temperatureC,1)+" H:"+String(humidityPct,0)+"%");
    else displayLine(0,"SENSOR ERROR");
    displayLine(1,"T_Limit:"+String(limitC)+"C");
  } else {
    String s=(alarmControl.state==SmartLab::State::SensorError)?"ERR":
      ((alarmControl.state==SmartLab::State::Critical)?"CRIT":
      ((alarmControl.state==SmartLab::State::Warning)?"WARN":"NORM"));
    displayLine(0,"ST:"+s+" ACK:"+(alarmControl.ack?"Y":"N"));
    bool wifi=WiFi.status()==WL_CONNECTED;
    displayLine(1,String("WF:")+(wifi?"ON ":"OFF")+" MQ:"+(wifi&&mqtt.connected()?"ON":"OFF"));
  }
}

void updateNetwork(uint32_t now) {
  if(WiFi.status()!=WL_CONNECTED) {
    if((uint32_t)(now-tWifi)>=WIFI_RETRY_MS) {
      tWifi=now; WiFi.reconnect();
    }
    lastMqttConnected=false;
    return;
  }
  // Sertifikat TLS: jangan memulai sebelum NTP memperoleh waktu masuk akal.
  if(!mqttStarted && time(nullptr)>1700000000) {
    mqttStarted=true;
    mqtt.connect(); // PsychicMqttClient mengelola auto-reconnect.
  }
  bool up=mqtt.connected();
  if(up&&!lastMqttConnected) mqtt.publish(TOPIC_AVAIL,1,true,"online");
  lastMqttConnected=up;
}

bool prepareJson(char* out,size_t cap,uint32_t now,uint32_t seq) {
  char t[24],h[24];
  if(sensorOk){ snprintf(t,sizeof(t),"%.1f",temperatureC); snprintf(h,sizeof(h),"%.1f",humidityPct); }
  else {strcpy(t,"null");strcpy(h,"null");}
  int n=snprintf(out,cap,
    "{\"device_id\":\"%s\",\"source\":\"%s\",\"temperature_c\":%s,"
    "\"humidity_pct\":%s,\"threshold_temp_c\":%u,\"status\":\"%s\","
    "\"ack\":%s,\"sensor_ok\":%s,\"uptime_ms\":%lu,\"sequence\":%lu}",
    SOURCE_ID,SOURCE_NAME,t,h,(unsigned)limitC,SmartLab::text(alarmControl.state),
    alarmControl.ack?"true":"false",sensorOk?"true":"false",
    (unsigned long)now,(unsigned long)seq);
  return n>0 && (size_t)n<cap;
}
void publishTelemetry(uint32_t now) {
  if((uint32_t)(now-tPublish)<MQTT_MS) return;
  tPublish=now;
  if(WiFi.status()!=WL_CONNECTED||!mqtt.connected()) return;
  char json[340];
  if(!prepareJson(json,sizeof(json),now,sequence+1)){
    Serial.println("ERROR: JSON buffer terlalu kecil");return;
  }
  // Return value != -1 berarti berhasil diantrikan oleh client, BUKAN konfirmasi tersimpan di DB.
  int id=mqtt.publish(TOPIC_TELEM,0,false,json);
  if(id!=-1){sequence++;Serial.println(json);}
  else Serial.println("ERROR: MQTT publish gagal diantrikan");
}
void setup() {
  Serial.begin(115200);
  pinMode(PIN_PB1,INPUT_PULLUP); pinMode(PIN_PB2,INPUT_PULLUP);
  pinMode(PIN_G,OUTPUT);pinMode(PIN_Y,OUTPUT);pinMode(PIN_R,OUTPUT);
  pinMode(PIN_POT,INPUT);analogReadResolution(12);
  Wire.begin(PIN_SDA,PIN_SCL);lcd.init();lcd.backlight();
  displayLine(0,"TEKNOLAB SMART LAB");displayLine(1,"Starting ...");
  dht.begin();
  WiFi.mode(WIFI_STA);WiFi.setAutoReconnect(true);WiFi.begin(ACTIVE_WIFI_SSID,ACTIVE_WIFI_PASSWORD);
  configTime(0,0,"pool.ntp.org","time.google.com");
  mqtt.setServer(MQTT_WSS_URI);mqtt.setClientId(SOURCE_ID);
  mqtt.setCredentials(MQTT_USERNAME,MQTT_PASSWORD);
  mqtt.setCACert(MQTT_ROOT_CA);mqtt.setKeepAlive(60);mqtt.setAutoReconnect(true);
  mqtt.setWill(TOPIC_AVAIL,1,true,"offline");
  mqtt.onConnect([](bool sessionPresent){(void)sessionPresent;Serial.println("MQTT WSS CONNECTED");});
  mqtt.onDisconnect([](bool sessionPresent){(void)sessionPresent;Serial.println("MQTT WSS DISCONNECTED");});
  uint32_t now=millis();
  tDht=now-DHT_MS;tPot=now-POT_MS;
  tWifi=now-WIFI_RETRY_MS;
}
void loop() {
  uint32_t now=millis();
  updateLimit(now);updateSensor(now);
  alarmControl.update(sensorOk,temperatureC,humidityPct,limitC,now);
  handleButtons(now);updateLeds(now);updateLcd(now);
  updateNetwork(now);publishTelemetry(now);
}

LAMPIRAN B. Logika teruji C++ (SmartLabLogic.h)

SmartLabLogic.h:
#ifndef TEKNOLAB_SMART_LAB_LOGIC_H
#define TEKNOLAB_SMART_LAB_LOGIC_H
#include <stdint.h>
#include <math.h>

namespace SmartLab {
enum class State : uint8_t { SensorError, Normal, Warning, Critical };

inline const char* text(State s) {
  switch (s) {
    case State::SensorError: return "SENSOR_ERROR";
    case State::Normal: return "NORMAL";
    case State::Warning: return "WARNING";
    case State::Critical: return "CRITICAL";
  }
  return "SENSOR_ERROR";
}

inline bool validSensor(float c, float rh) {
  return isfinite(c) && isfinite(rh) && c >= -40.0f && c <= 80.0f && rh >= 0.0f && rh <= 100.0f;
}

inline uint8_t thresholdFromAdc(uint16_t adc) {
  if (adc > 4095) adc = 4095;
  return static_cast<uint8_t>(20 + (static_cast<uint32_t>(adc) * 15 + 2047) / 4095);
}

inline State evaluate(bool ok, float temperatureC, float humidityPct, uint8_t threshold) {
  if (!ok || !validSensor(temperatureC, humidityPct)) return State::SensorError;
  if (temperatureC >= (threshold + 3.0f) || humidityPct >= 85.0f) return State::Critical;
  if (temperatureC >= threshold || humidityPct >= 75.0f) return State::Warning;
  return State::Normal;
}

struct Leds { bool green; bool yellow; bool red; };
struct Alarm {
  State state = State::SensorError;
  bool ack = false;
  uint32_t transitionAtMs = 0;

  void update(bool ok, float temp, float humidity, uint8_t threshold, uint32_t now) {
    State next = evaluate(ok, temp, humidity, threshold);
    if (next != state) {
      state = next;
      ack = false; // Termasuk NORMAL, WARNING->CRITICAL, ERROR.
      transitionAtMs = now;
    }
    if (state == State::Normal || state == State::SensorError) ack = false;
  }
  void acknowledge() {
    if (state == State::Warning || state == State::Critical) ack = true;
  }
  Leds leds(uint32_t now) const {
    if (state == State::Normal) return {true, false, false};
    const uint32_t interval = (state == State::Warning) ? 500u : 250u;
    const bool blink = (((uint32_t)(now - transitionAtMs) / interval) % 2u == 0u);
    if (state == State::Warning) return {false, ack || blink, false};
    if (state == State::Critical) return {false, false, ack || blink};
    return {false, blink, blink};
  }
};

struct DebouncedButton {
  bool lastRawHigh = true;
  bool stableHigh = true;
  uint32_t lastChangedAtMs = 0;
  bool pressed(bool rawHigh, uint32_t now, uint32_t debounceMs = 30) {
    if (rawHigh != lastRawHigh) { lastRawHigh = rawHigh; lastChangedAtMs = now; }
    if ((uint32_t)(now - lastChangedAtMs) >= debounceMs && stableHigh != rawHigh) {
      stableHigh = rawHigh;
      return !stableHigh; // event hanya pada HIGH -> LOW
    }
    return false;
  }
};
}
#endif

LAMPIRAN C. Secrets.h & sertifikat CA

File Secrets.template.h di ZIP memiliki contoh CA publik ISRG Root X1. Ini bukan private key. Ubah SSID, password, URI dan nama user di salinan Secrets.h. Jangan menempel password yang pernah terlihat pada sumber TESS1.ino; ganti kredensial tersebut dahulu.

Ringkasan template (sertifikat lengkap terdapat di ZIP):
#define WIFI_SSID "ISI_SSID_24GHZ"
#define WIFI_PASSWORD "ISI_PASSWORD_WIFI"
#define MQTT_WSS_URI "wss://broker-latihan.DOMAIN-ANDA:8084/"
#define MQTT_USERNAME "teknolab"
#define MQTT_PASSWORD "ISI_PASSWORD_AKUN_TEKNOLAB"
static const char MQTT_ROOT_CA[] = R"ROOTCA(-----BEGIN CERTIFICATE-----
...SALIN PEM ROOT CA VALID YANG SESUAI SERVER...
-----END CERTIFICATE-----
)ROOTCA";

LAMPIRAN D. Konfigurasi backend dan skrip uji

Paket ZIP melampirkan compose YAML, mosquitto.conf, ACL, flow Node-RED JSON, validator JS, splitter JS, run_tests.sh, serta output PASS. Jika mengubah struktur JSON, perbarui validator dan lakukan pengujian ulang sebelum integrasi.

Jalankan pengujian lokal (Linux + g++ + Node.js + PyYAML):
cd TEKNOLAB_SMART_LAB/tests
./run_tests.sh
# TARGET: PASS 39, PASS 139, PASS 15,
# dan satu mock compile+JSON. Output rinci: HASIL_TEST.txt.

LAMPIRAN E. Checklist operator saat menyatakan semua siap

☐ ESP32 fisik compile menggunakan toolchain Arduino-ESP32 asli dan upload sukses.
☐ Cirkit Designer compile di board simulator pilihan, dengan library WSS yang didukung.
☐ DNS FQDN mengarah ke server latihan; TLS issuer/SAN/waktu valid.
☐ MQTTX WSS connect ke Mosquitto 8084 dengan CA verification ON.
☐ Auth teknolab membatasi namespace proyek; akun monitor hanya memiliki izin baca.
☐ ESP32 publish dan SIM publish bersamaan dengan client ID unik.
☐ Validator menerima JSON valid dan menolak JSON salah.
☐ Node-RED gauge update sesuai hardware/simulasi, tidak saling menimpa.
☐ InfluxDB menyimpan data historis; query satu jam menampilkan baris.
☐ Dashboard history berasal dari InfluxDB, bukan data contoh statis.
☐ Offline/reconnect berjalan tanpa menghilangkan fungsi alarm lokal.
☐ formulir dokumentasi teknis terisi dan public link simulasi benar.
☐ Semua 12 Acceptance Test tercatat beserta bukti nyata.
Tutorial Teknolab • Dokumentasi Implementasi & Verifikasi Teknik
Bagikan tutorial iniWhatsApp ↗

Terima kasih sudah belajar bersama Arduino Indonesia.

← Kembali ke beranda

Komentar (0)