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)

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.