Published on

Communication & State Thinking on ESP32

Authors

πŸ“˜ Foundation Article 7: Communication & State Thinking on ESP32

Track: C++ Firmware Engineering Foundations for ESP32 Environment: ESP32 + VSCode + Arduino Community Edition Project Base: IndustrialNode/IndustrialNode.ino Use Case: Generic Sensor + Relay Node



1️⃣ Komunikasi: Sumber Coupling Paling Sering

Firmware kecil stabil tanpa komunikasi.

Masalah mulai muncul saat:

  • WiFi ditambahkan
  • MQTT ditambahkan
  • OTA ditambahkan
  • Remote config ditambahkan

Contoh klasik:

void callback(char* topic, byte* payload, unsigned int length) {
    if (payload[0] == '1') {
        digitalWrite(5, HIGH);
    } else {
        digitalWrite(5, LOW);
    }
}

Masalah fundamental:

  • Callback menyentuh hardware langsung.
  • Tidak ada service boundary.
  • Tidak ada domain guard.
  • Tidak ada state awareness.
  • Tidak ada arbitration dengan control loop.

Sekarang komunikasi menjadi:

Jalur pintas melanggar seluruh arsitektur.

Layering runtuh di satu callback.


Failure Model β€” Callback Menjadi God Function

Ketika fitur bertambah, callback berubah menjadi:

callback()
 β”œβ”€ parse payload
 β”œβ”€ set relay
 β”œβ”€ update threshold
 β”œβ”€ save config
 β”œβ”€ publish ack
 β”œβ”€ trigger OTA
 └─ reset device

Sekarang:

  • Business logic bercampur.
  • Transport bercampur.
  • Hardware bercampur.
  • State tidak eksplisit.
  • Debug menjadi sulit.

Komunikasi adalah sumber coupling paling sering karena ia menyentuh banyak domain sekaligus.


2️⃣ Communication Bukan Sekadar Publish/Subscribe

Engineer sering melihat komunikasi sebagai:

subscribe β†’ callback β†’ selesai

Padahal komunikasi memiliki lifecycle kompleks.


Lifecycle Nyata (ESP32 + WiFi + MQTT)

INIT
↓
WIFI_CONNECTING
↓
WIFI_CONNECTED
↓
MQTT_CONNECTING
↓
MQTT_CONNECTED
↓
ERROR
↓
BACKOFF
↓
RETRY

Ini bukan teori. Ini realitas runtime.

Jika lifecycle tidak eksplisit:

  • Reconnect tersebar.
  • Flag global bermunculan.
  • Behavior tidak deterministik.
  • Race dengan control loop muncul.

3️⃣ Visual: State Machine Konseptual

Image

Image

Image

Image

Model konseptual yang sehat:

[INIT]
   ↓
[WIFI_CONNECTING]
   ↓
[WIFI_CONNECTED]
   ↓
[MQTT_CONNECTING]
   ↓
[MQTT_CONNECTED]
   ↓
[ERROR_BACKOFF]
   β†Ί

Tanpa state machine eksplisit, developer sering menulis:

if (!wifiConnected) ...
if (!mqttConnected) ...
if (retryCount > 5) ...

Ini bukan state machine. Ini kondisi implisit yang saling tumpang tindih.


4️⃣ Event-Driven Thinking

Komunikasi menghasilkan event, bukan aksi langsung.

Daripada:

callback() β†’ relay.set()

Lebih aman:

MQTT Callback β†’ Event β†’ Service β†’ Driver

Pattern Sehat

void mqttCallback(...) {
    commManager.handleIncoming(payload);
}

Lalu:

// di svc_CommManager
void CommManager::handleIncoming(...) {
    eventQueue.push(Command::RelayOn);
}

Kemudian:

// di ControlService
void ControlService::processEvent(Command cmd) {
    if (cmd == Command::RelayOn) {
        relay_.set(true);
    }
}

Sekarang:

  • Transport tidak menyentuh hardware.
  • Domain memutuskan aksi.
  • Layering tetap utuh.

5️⃣ Sensor + Relay Node dengan Komunikasi

Mari kita lihat sistem kita.

Tanpa komunikasi:

loop()
  ↓
app_ControlApp
  ↓
svc_ControlService
  ↓
drv_RelayDriver

Dengan komunikasi yang benar:

MQTT Task
   ↓
svc_CommManager (state owner)
   ↓
Event Queue
   ↓
svc_ControlService
   ↓
drv_RelayDriver

Komunikasi tidak boleh memotong langsung ke driver.


Kenapa Ini Penting?

Karena komunikasi:

  • Asynchronous.
  • Bisa delay.
  • Bisa retry.
  • Bisa duplicate (retain).
  • Bisa reconnect.
  • Bisa timeout.

Jika callback langsung menyentuh hardware:

Determinism kontrol rusak.


6️⃣ Reconnect Chaos Tanpa Struktur

Reconnect sering ditulis seperti ini:

if (WiFi.status() != WL_CONNECTED) {
    WiFi.begin(ssid, password);
}

if (!client.connected()) {
    reconnectMqtt();
}

Dan di tempat lain:

retryCount++;
if (retryCount > 5) ESP.restart();

Masalah:

  • Logic tersebar.
  • Tidak ada single state owner.
  • Tidak ada transisi formal.
  • Tidak ada guard antar state.

Ini bukan reconnect strategy. Ini reconnect reaksi spontan.


Dampak Nyata

Kasus nyata di lapangan:

  • Broker restart β†’ reconnect spam.
  • WiFi drop β†’ MQTT reconnect tetap jalan.
  • TLS fail β†’ retry tanpa backoff.
  • Backoff tidak konsisten.

Hasilnya:

  • Heap spike.
  • Fragmentasi meningkat.
  • Watchdog reset.
  • Sistem tidak deterministik.

Reconnect tanpa struktur = chaos sistemik.


7️⃣ Transport β‰  Business Logic

Transport layer adalah:

  • WiFi.begin()
  • WiFi.status()
  • client.connect()
  • client.publish()
  • client.subscribe()

Business logic adalah:

  • Threshold sensor.
  • Mode manual / auto.
  • Safety guard.
  • Fail-safe behavior.

Jika business logic tahu:

if (topic == "node/relay")

Maka domain Anda sudah tergantung pada MQTT topic.

Itu coupling.


Model yang Sehat

Transport menerima payload β†’ ubah menjadi Command domain.

Contoh:

Command cmd = parser.parse(payload);
controlService.processCommand(cmd);

ControlService tidak tahu MQTT. Ia hanya tahu Command.

Transport adalah adapter.

Bukan decision maker.


8️⃣ Implicit State vs Explicit State

Implicit state:

bool wifiConnected;
bool mqttConnected;
bool reconnecting;
bool updating;

Masalah:

  • Kombinasi flag bisa tidak valid.
  • Tidak jelas state aktif sebenarnya.
  • Debug sulit.

Explicit State

enum class CommState {
    INIT,
    WIFI_CONNECTING,
    WIFI_CONNECTED,
    MQTT_CONNECTING,
    MQTT_CONNECTED,
    ERROR_BACKOFF
};

Sekarang:

  • Hanya satu state aktif.
  • Transisi jelas.
  • Logging jelas.
  • Telemetry jelas.
  • Reconnect terstruktur.

Contoh Transisi Terkontrol

void CommManager::update() {
    switch (state_) {
        case CommState::INIT:
            startWifi();
            state_ = CommState::WIFI_CONNECTING;
            break;

        case CommState::WIFI_CONNECTED:
            startMqtt();
            state_ = CommState::MQTT_CONNECTING;
            break;

        case CommState::ERROR_BACKOFF:
            if (backoffExpired()) {
                state_ = CommState::WIFI_CONNECTING;
            }
            break;

        default:
            break;
    }
}

Sekarang reconnect bukan reaksi. Ia transisi formal.


9️⃣ Network Latency & Control Stability

Kita kembali ke sistem sensor + relay.

Loop kontrol 100ms.

Jika MQTT publish blocking 300ms:

  • Loop tertunda.
  • Relay update terlambat.
  • Sampling tidak stabil.
  • Jitter meningkat.

Masalah bukan MQTT.

Masalahnya:

Komunikasi masuk ke jalur kontrol.


Separation of Critical Path

Critical path:

Sensor β†’ Control β†’ Relay

Non-critical path:

Telemetry β†’ MQTT publish

Jika kedua path bercampur, determinism hilang.


Prinsip Penting

Kontrol tidak boleh tergantung network.

Jika broker mati:

  • Kontrol tetap stabil.
  • Relay tetap bekerja.
  • Sensor tetap sampling.

Network adalah fitur tambahan, bukan pusat sistem.


πŸ”Ÿ Communication sebagai Layer Terpisah

Dalam struktur kita:

IndustrialNode/
β”œβ”€β”€ app_ControlApp
β”œβ”€β”€ svc_ControlService
β”œβ”€β”€ svc_CommManager
β”œβ”€β”€ drv_RelayDriver

svc_CommManager bertanggung jawab atas:

  • WiFi lifecycle
  • MQTT lifecycle
  • State komunikasi
  • Event publish
  • Command parsing

Ia tidak:

  • Mengontrol relay.
  • Mengubah threshold.
  • Menentukan rule.

Ia hanya:

Communication state owner + adapter.


Alur Sehat Final

MQTT Callback
   ↓
svc_CommManager (parse + validate)
   ↓
Command
   ↓
svc_ControlService
   ↓
drv_RelayDriver

Semua rule tetap di domain.


11️⃣ Mental Model yang Harus Dikunci

Setelah artikel ini, engineer harus memahami:


1️⃣ Komunikasi memiliki lifecycle

Ia bukan sekadar publish/subscribe.


2️⃣ Callback bukan tempat business logic

Callback hanya entry point.


3️⃣ State harus eksplisit

Flag tersebar = chaos.


4️⃣ Reconnect harus terpusat

Satu state owner. Satu mekanisme transisi.


5️⃣ Transport harus dipisahkan dari domain

Topic MQTT β‰  business rule.


6️⃣ Network latency memengaruhi determinism

Critical path harus terisolasi.


7️⃣ Event-driven lebih aman daripada shared mutable

Message passing > global flag.


Penutup

Komunikasi adalah titik di mana firmware sederhana mulai runtuh.

Bukan karena WiFi buruk. Bukan karena MQTT salah.

Tetapi karena:

  • State implisit.
  • Callback menyentuh hardware.
  • Reconnect logic tersebar.
  • Transport bercampur domain.

Communication discipline adalah jembatan antara:

  • RTOS model
  • Memory discipline
  • OOP boundary
  • Layering architecture

Catatan Penyusunan Artikel ini disusun sebagai materi edukasi dan referensi umum berdasarkan berbagai sumber pustaka, praktik lapangan, serta bantuan alat penulisan. Pembaca disarankan untuk melakukan verifikasi lanjutan dan penyesuaian sesuai dengan kondisi serta kebutuhan masing-masing sistem.