Pendahuluan

Di artikel WiFi ESP32 dan node deep sleep DHT22, kita masih menulis ssid dan password langsung di sketch. Itu cepat untuk belajar, tapi tidak layak produksi: setiap ganti WiFi atau deploy ke pelanggan lain, kamu harus edit kode, compile ulang, dan upload via USB.

Artikel ini mengajarkan dua fondasi firmware ESP32 yang wajib untuk proyek lapangan:

  1. WiFiManager — portal konfigurasi WiFi lewat hotspot captive (tanpa Serial Monitor)
  2. NVS Preferences — simpan pengaturan (topic MQTT, interval, flag) di flash secara persisten

Kita gabungkan keduanya dalam satu sketch: baca DHT22, publish MQTT JSON (topic Seri 1), tanpa satu pun kredensial WiFi di source code.

Prasyarat: Paham koneksi WiFi, DHT22, dan publish MQTT. Familiar dengan deep sleep membantu untuk node baterai nanti.

Masalah Hardcode WiFi

SkenarioTanpa WiFiManagerDengan WiFiManager
Pindah rumah / kantorEdit sketch + upload USBBuka portal, pilih WiFi baru
Deploy ke banyak unitSatu firmware per lokasiSatu firmware universal
Password WiFi di GitHubRisiko bocorTidak ada password di repo
Node di atap / kebunHarus bawa laptopSetup lewat HP saja

Di artikel WiFi ESP32 (#4) sudah disebutkan: gunakan WiFiManager atau file konfigurasi terpisah — ini janji yang kita penuhi di artikel Seri 2. Kali ini kita implementasi lengkapnya.

NVS (Non-Volatile Storage) & Preferences

ESP32 punya partisi flash bernama NVS untuk menyimpan key-value yang tetap ada setelah reboot atau deep sleep. Di Arduino, library Preferences adalah wrapper resmi:

  • prefs.begin("namespace") — buka namespace (misalnya "kindo")
  • prefs.putString("mqtt_topic", ...) — simpan string
  • prefs.getString("mqtt_topic", default) — baca dengan nilai default
  • prefs.clear() — hapus semua key di namespace

WiFiManager sendiri sudah menyimpan kredensial WiFi ke NVS internal. Kita pakai Preferences tambahan untuk parameter aplikasi: topic MQTT, durasi deep sleep (opsional), nama perangkat.

WiFiManager: Alur Portal Captive

  1. ESP32 tidak menemukan WiFi tersimpan → buat AP KindoESP32-Setup
  2. HP/laptop connect ke AP tersebut → browser terbuka halaman konfigurasi
  3. Pilih SSID rumah, masukkan password, isi field custom (topic MQTT)
  4. ESP32 simpan ke flash, reboot, connect ke WiFi rumah
  5. Boot berikutnya langsung connect — portal tidak muncul lagi

Reset konfigurasi: Tahan tombol BOOT (GPIO 0) saat boot, atau panggil wm.resetSettings() + prefs.clear() di kode maintenance.

Komponen & Wiring

Sama seperti tutorial DHT22 Seri 1 — sensor digital di GPIO 4:

ESP32 DevKit          DHT22
─────────────         ─────
3.3V          ─────── VCC
GND           ─────── GND
GPIO 4        ─────── DATA
                      │
                   [10kΩ] pull-up ke 3.3V (modul breakout biasanya sudah ada)
  • ESP32 DevKit (USB untuk upload pertama)
  • Sensor DHT22 + kabel jumper
  • HP Android/iOS untuk portal WiFiManager

Install Library

Di Arduino IDE 2.x → Sketch → Include Library → Manage Libraries:

  • WiFiManager oleh tzapu (versi 2.x)
  • DHT sensor library oleh Adafruit + dependency Adafruit Unified Sensor
  • PubSubClient oleh Nick O'Leary

Board: esp32 by Espressif (v3.x). Library Preferences dan WiFi sudah built-in.

Broker latihan: test.mosquitto.org:1883 (sama Seri 1).

Topic default: kodingindonesia/esp32/dht22/data — payload JSON {"suhu":28.5,"kelembaban":65.2} (bisa diubah lewat portal).

Broker bukan website: test.mosquitto.org tidak dibuka di browser. Pakai MQTT Explorer atau mosquitto_sub. Detail di artikel MQTT.

Kode Lengkap: WiFiManager + NVS + DHT22 + MQTT

Tidak ada const char* ssid / password di bawah. Ganti default topic jika perlu; sisanya diatur lewat portal.

#include <WiFi.h>
#include <WiFiManager.h>
#include <Preferences.h>
#include <PubSubClient.h>
#include <DHT.h>

#define DHT_PIN  4
#define DHT_TYPE DHT22
#define BTN_RESET_WIFI 0   // BOOT — tahan saat power-on untuk reset WiFi

const char* NS_KINDO = "kindo";
const char* DEFAULT_TOPIC = "kodingindonesia/esp32/dht22/data";
const char* MQTT_HOST = "test.mosquitto.org";
const int   MQTT_PORT = 1883;

DHT dht(DHT_PIN, DHT_TYPE);
WiFiClient espClient;
PubSubClient mqttClient(espClient);
Preferences prefs;

String topicSensor;

WiFiManagerParameter paramTopic(
  "mqtt_topic", "MQTT topic sensor", DEFAULT_TOPIC, 64);

bool tombolResetDitekan() {
  pinMode(BTN_RESET_WIFI, INPUT_PULLUP);
  return digitalRead(BTN_RESET_WIFI) == LOW;
}

void muatPengaturan() {
  prefs.begin(NS_KINDO, true);
  topicSensor = prefs.getString("mqtt_topic", DEFAULT_TOPIC);
  prefs.end();
}

void simpanTopicDariPortal() {
  prefs.begin(NS_KINDO, false);
  prefs.putString("mqtt_topic", paramTopic.getValue());
  prefs.end();
  topicSensor = String(paramTopic.getValue());
}

bool setupWiFiManager() {
  WiFiManager wm;
  wm.setConfigPortalTimeout(180);
  wm.addParameter(&paramTopic);

  if (tombolResetDitekan()) {
    Serial.println("Reset WiFi + NVS (tombol BOOT)");
    wm.resetSettings();
    prefs.begin(NS_KINDO, false);
    prefs.clear();
    prefs.end();
  }

  muatPengaturan();
  paramTopic.setValue(topicSensor.c_str(), 64);

  Serial.println("WiFiManager: autoConnect...");
  if (!wm.autoConnect("KindoESP32-Setup")) {
    Serial.println("Portal gagal / timeout");
    return false;
  }

  simpanTopicDariPortal();
  Serial.println("WiFi OK — SSID: " + WiFi.SSID());
  return true;
}

bool koneksiMQTT() {
  mqttClient.setServer(MQTT_HOST, MQTT_PORT);
  mqttClient.setBufferSize(512);

  String clientId = "ESP32-NVS-" + String(random(0xffff), HEX);
  if (mqttClient.connect(clientId.c_str())) {
    Serial.println("MQTT terhubung");
    return true;
  }
  Serial.print("MQTT gagal, rc=");
  Serial.println(mqttClient.state());
  return false;
}

void publishDHT() {
  float suhu = dht.readTemperature();
  float kelembaban = dht.readHumidity();

  if (isnan(suhu) || isnan(kelembaban)) {
    Serial.println("DHT22 gagal — cek wiring");
    return;
  }

  char payload[96];
  snprintf(payload, sizeof(payload),
    "{\"suhu\":%.1f,\"kelembaban\":%.1f}", suhu, kelembaban);

  mqttClient.loop();
  if (mqttClient.publish(topicSensor.c_str(), payload, false)) {
    Serial.print("Publish OK → ");
    Serial.println(payload);
  } else {
    Serial.println("Publish gagal");
  }
}

void setup() {
  Serial.begin(115200);
  delay(500);

  dht.begin();
  delay(2000);

  if (!setupWiFiManager()) {
    delay(3000);
    ESP.restart();
  }

  if (!koneksiMQTT()) {
    Serial.println("MQTT gagal — restart 5 detik");
    delay(5000);
    ESP.restart();
  }

  publishDHT();
}

void loop() {
  mqttClient.loop();
  delay(10000);
  publishDHT();
}

Penjelasan Bagian Kritis

  • wm.autoConnect("KindoESP32-Setup") — blocking sampai WiFi tersimpan atau timeout 180 detik
  • WiFiManagerParameter — field custom di portal; nilainya kita simpan ke NVS via prefs.putString
  • wm.resetSettings() — hapus kredensial WiFi tersimpan (dipicu tombol BOOT)
  • prefs.begin(NS_KINDO, true) — mode read-only saat boot normal
  • mqttClient.loop() — wajib sebelum publish() (konsisten Seri 1)
  • setBufferSize(512) — cukup untuk payload JSON DHT22

Uji Coba (Step-by-Step)

  1. Upload sketch, buka Serial Monitor 115200
  2. Pertama kali: ESP32 membuat AP KindoESP32-Setup
  3. Di HP: Settings → WiFi → connect KindoESP32-Setup
  4. Portal terbuka otomatis (atau buka 192.168.4.1)
  5. Pilih WiFi rumah, password, cek field MQTT topic sensor
  6. Simpan — ESP32 reboot dan connect
  7. Serial: WiFi OKMQTT terhubungPublish OK
  8. Di MQTT Explorer / mosquitto_sub, subscribe topic yang kamu set
  9. Reboot ESP32 (tanpa upload) — harus langsung connect tanpa portal
mosquitto_sub -h test.mosquitto.org -t "kodingindonesia/esp32/dht22/data" -v

Pro tip: Gunakan topic unik per perangkat, misalnya kodingindonesia/anton/esp32/dht22/data, agar tidak bentrok di broker publik.

Gabung dengan Deep Sleep (#11)

Sketch di atas cocok untuk node USB/adaptor. Untuk baterai, pindahkan logika publishDHT() ke dalam setup() seperti artikel deep sleep, lalu tidur lagi. WiFiManager hanya perlu dijalankan saat pertama kali atau setelah reset — jangan buka portal tiap bangun (boros baterai).

// Pola deep sleep + WiFiManager (pseudocode)
prefs.begin("kindo", true);
bool wifiConfigured = prefs.getBool("wifi_ok", false);
prefs.end();

if (!wifiConfigured || tombolResetDitekan()) {
  setupWiFiManager();  // portal sekali
  prefs.begin("kindo", false);
  prefs.putBool("wifi_ok", true);
  prefs.end();
} else {
  WiFi.begin();  // credentials sudah di NVS internal WiFi stack
  // ... timeout connect ...
}

Tips & Troubleshooting

  • Portal tidak muncul: Pastikan tidak ada WiFi tersimpan — reset dengan tahan BOOT saat boot, atau wm.resetSettings()
  • Captive portal tidak redirect (Android): Buka manual http://192.168.4.1
  • WiFi connect loop: Cek 2.4 GHz — ESP32 tidak support jaringan WiFi 5 GHz saja; dekatkan ke router
  • Topic MQTT kosong: Cek prefs.getString default; isi ulang lewat portal
  • DHT22 NaN: delay(2000) setelah dht.begin(); GPIO 4 + pull-up
  • Compile error WiFiManager: Update library tzapu ke 2.x; board esp32 v3.x
  • NVS penuh: Jarang di hobby project; prefs.clear() pada namespace kindo jika perlu factory reset

Keamanan & Produksi

  • Jangan commit file secrets.h dengan password — WiFiManager menghilangkan kebutuhan itu untuk WiFi
  • Portal default tidak pakai password AP — untuk deploy komersial, set wm.setAPStaticIPConfig + password AP atau gunakan MQTT dengan auth di broker sendiri (artikel #16)
  • Segera pindah dari test.mosquitto.org ke broker pribadi untuk data produksi

Langkah Selanjutnya (Seri 2)

  • Sensor BME280 via I2C — lebih akurat dari DHT22, plus tekanan udara
  • Artikel #15: OTA update — butuh WiFiManager (#12) agar firmware bisa di-update tanpa kabel setelah deploy
  • Broker Mosquitto pribadi + autentikasi — simpan host/user/pass di NVS dengan pola sama
  • Kembali ke node deep sleep untuk gabungkan hemat baterai + konfigurasi lapangan

Dengan NVS dan WiFiManager, ESP32 kamu siap dipasang di lokasi pelanggan tanpa membawa laptop setiap kali jaringan berubah. Lanjutkan Seri 2 di halaman artikel Koding Indonesia.