Pendahuluan — Kenapa FreeRTOS?

Di Seri 1, sketch ESP32 biasanya satu loop() besar: baca sensor, reconnect WiFi, publish MQTT, handle web server — semuanya berjalan berurutan. Itu cukup untuk belajar, tapi saat proyek membesar (seperti gabungan DHT22 + relay (#9) atau dashboard capstone (#10)), satu operasi blocking bisa menggagalkan yang lain.

FreeRTOS sudah built-in di ESP32 Arduino core. Artikel penutup Jalur E ini memecah firmware jadi task paralel: task sensor, task MQTT/WiFi, dan antrean data di antaranya — pola yang dipakai firmware produksi sebelum kamu masuk Tier 2 Seri 2.

Prasyarat: Paham MQTT dasar (#7), sudah punya sketch gabungan DHT22 + MQTT (#9), dan nyaman dengan broker Mosquitto (#16). Familiar PlatformIO (#29) membantu tapi opsional.

Masalah loop() Tunggal

SkenarioTanpa FreeRTOSDengan FreeRTOS
delay(2000) tunggu DHT22MQTT loop() tidak jalanTask MQTT tetap jalan di core lain
Reconnect WiFi 10 detikSensor tidak terbacaTask sensor tetap periodic
Publish JSON besarSubscribe relay telatTask kontrol terpisah (opsional)

Artikel ini fokus pada sensor → queue → MQTT — fondasi sebelum kamu menambah relay subscribe seperti di #9 atau cloud bridge Firebase (#30).

Arsitektur Task

┌─────────────────┐     xQueueSend      ┌──────────────────┐
│  Task Sensor    │ ──────────────────► │  Task MQTT       │
│  (Core 1)       │   struct SensorData │  (Core 0)        │
│  DHT22 @ GPIO4  │                     │  WiFi + publish  │
└─────────────────┘                     └────────┬─────────┘
                                                 │
                                                 ▼
                                    Broker 192.168.1.50
                                    topic .../dht22/data

ESP32 punya dua core. Pola umum: networking di Core 0, pembacaan sensor di Core 1 — mengurangi kontensi saat TLS atau reconnect WiFi.

Core 0 vs Core 1 — Panduan Singkat

Arduino-ESP32 menjalankan WiFi stack dan sebagian driver di core tertentu. Meskipun detail internal bisa berubah antar versi core, aturan praktis untuk proyek Koding Indonesia:

  • Core 0 — WiFi, MQTT client, reconnect broker, opsional HTTPS/Firebase
  • Core 1 — pembacaan DHT22, debounce GPIO, sampling ADC (#35 nanti)
  • Hindari dua task memanggil WiFi.disconnect() bersamaan — race condition

Jika kamu hanya punya satu task berat (mis. TLS Firebase #30), pertimbangkan stack 12–16 KB dan monitor heap secara berkala di Serial.

Struktur Data & Queue

typedef struct {
  float temperature;
  float humidity;
  uint32_t unix_ts;
} SensorReading;

QueueHandle_t sensorQueue;

void sensorTask(void* param) {
  for (;;) {
    SensorReading r;
    r.temperature = dht.readTemperature();
    r.humidity = dht.readHumidity();
    if (!isnan(r.temperature) && !isnan(r.humidity)) {
      r.unix_ts = 1782977400; // produksi: NTP #34
      xQueueSend(sensorQueue, &r, pdMS_TO_TICKS(100));
    }
    vTaskDelay(pdMS_TO_TICKS(5000));
  }
}

Queue memisahkan produksi data (sensor) dari konsumsi (MQTT). Jika broker lambat, queue bisa menampung beberapa sampel — atau drop jika penuh (kebijakan tim).

Task MQTT & WiFi

void mqttTask(void* param) {
  WiFi.begin("GANTI_NAMA_WIFI", "GANTI_PASSWORD_WIFI");
  while (WiFi.status() != WL_CONNECTED) {
    vTaskDelay(pdMS_TO_TICKS(500));
  }

  mqttClient.setServer("192.168.1.50", 1883);
  mqttClient.setBufferSize(512);

  for (;;) {
    if (!mqttClient.connected()) {
      String clientId = "esp32-" + String(random(0xffff), HEX);
      if (mqttClient.connect(clientId.c_str(), "kindo_esp32", "GANTI_PASSWORD_MQTT")) {
        Serial.println("MQTT OK");
      }
    }
    mqttClient.loop();

    SensorReading r;
    if (xQueueReceive(sensorQueue, &r, pdMS_TO_TICKS(200)) == pdTRUE) {
      String json = "{\"temperature\":" + String(r.temperature, 1) +
        ",\"humidity\":" + String(r.humidity, 1) +
        ",\"unix\":" + String(r.unix_ts) +
        ",\"iso\":\"2026-07-02T14:30:00\"" +
        ",\"source\":\"esp32\"}";
      mqttClient.publish("kodingindonesia/esp32/dht22/data", json.c_str());
    }
    vTaskDelay(pdMS_TO_TICKS(50));
  }
}

Topic kodingindonesia/esp32/dht22/data konsisten dengan seluruh Seri 2. Verifikasi dengan:

mosquitto_sub -h 192.168.1.50 -u kindo_esp32 -P GANTI_PASSWORD_MQTT \
  -t kodingindonesia/esp32/dht22/data -v

setup() — Spawn Task

void setup() {
  Serial.begin(115200);
  dht.begin();
  delay(2000);

  sensorQueue = xQueueCreate(5, sizeof(SensorReading));

  xTaskCreatePinnedToCore(sensorTask, "Sensor", 4096, NULL, 1, NULL, 1);
  xTaskCreatePinnedToCore(mqttTask, "MQTT", 8192, NULL, 1, NULL, 0);
}

void loop() {
  vTaskDelay(portMAX_DELAY); // kerja di task, loop kosong
}

Pro tip: Stack size 8192 untuk task MQTT — SSL/Firebase butuh lebih besar; untuk MQTT plain 4096–8192 biasanya cukup. Pantau uxTaskGetStackHighWaterMark() saat debug.

Membandingkan dengan Sketch #9

Di artikel #9, loop() memanggil dht.read, mqttClient.loop(), dan callback subscribe relay secara serial. Itu valid untuk satu node. FreeRTOS menjadi penting ketika:

  • Interval sensor ketat (mis. setiap 2 detik) tapi MQTT harus responsif untuk relay
  • Ada task ketiga: OLED (#14), OTA check (#15), atau NTP sync (#34)
  • Gateway LoRa (#28) menerima packet sementara WiFi reconnect

Kamu bisa migrasi bertahap: pertahankan logika #9, pindahkan hanya bagian sensor ke task pertama — tanpa rewrite total.

Rencana Migrasi dari Sketch #9 (Langkah demi Langkah)

  1. Salin sketch #9 ke project baru — pastikan MQTT ke broker 192.168.1.50 masih jalan
  2. Ekstrak fungsi baca DHT22 ke sensorTask — interval 5 detik dengan vTaskDelay
  3. Pindahkan WiFi + mqttClient.loop + publish ke mqttTask
  4. Tambahkan queue di antara keduanya — mulai depth 3–5
  5. Kosongkan loop() — hanya vTaskDelay(portMAX_DELAY)
  6. Uji subscribe relay — jika masih dipakai dari #9, tambahkan task ketiga atau gabung di mqttTask dengan prioritas lebih tinggi

Setiap langkah di atas bisa di-commit terpisah di Git — memudahkan rollback jika stack overflow muncul di tengah migrasi.

Prioritas, Mutex & Watchdog

FreeRTOS menyediakan primitif lain untuk produksi:

  • Mutex — lindungi bus I2C (#13) jika OLED dan BME280 dibaca dari task berbeda
  • Semaphore — signal event (tombol GPIO interrupt → task MQTT)
  • Task watchdog (TWDT) — reset ESP32 jika task macet; wajib di firmware lapangan

Untuk lab, watchdog bisa dimatikan dulu. Sebelum deploy ke kebun/ greenhouse capstone (#39), aktifkan TWDT di task yang paling kritis.

Stack Size — Titik Awal yang Aman

TaskStack (word)Catatan
Sensor DHT224096Cukup untuk float + queue send
MQTT plain8192Naikkan jika PubSubClient + String JSON
MQTT + TLS12288+Lihat #17 — uji di lapangan
Firebase HTTPS16384+Pisah task cloud dari MQTT lokal (#30)

Gunakan uxTaskGetStackHighWaterMark(NULL) setelah beberapa menit jalan untuk melihat sisa stack terendah per task — naikkan parameter stack di xTaskCreatePinnedToCore jika margin tipis.

PlatformIO vs Arduino IDE

FreeRTOS berjalan di kedua workflow. Di PlatformIO (#29), pecah file:

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
lib_deps =
  knolleary/PubSubClient @ ^2.8
  adafruit/DHT sensor library @ ^1.4
  • src/main.cpp — setup + spawn task
  • src/sensor_task.cpp — implementasi sensorTask
  • src/mqtt_task.cpp — WiFi + MQTT

Debugging Multi-task

  1. Beri nama task jelas — muncul di panic backtrace
  2. Log dengan prefix: [SENSOR], [MQTT]
  3. Cek heap: ESP.getFreeHeap() setelah spawn task
  4. Jika crash Guru Meditation — naikkan stack task yang bersangkutan
  5. Pastikan hanya satu task yang memanggil WiFi / mqttClient (hindari race)

Untuk timestamp live, ganti contoh statis 1782977400 dengan sinkronisasi NTP (#34) di task MQTT sebelum publish.

Hybrid: FreeRTOS + Firebase

Task MQTT bisa publish ke broker lokal dan push ke Firebase (#30) — tapi jangan blokir task sensor menunggu HTTPS. Pola aman: sensor → queue → task cloud terpisah dengan stack besar (SSL).

Untuk kebanyakan proyek rumahan, pilih satu saluran cloud dulu; FreeRTOS memudahkan penambahan saluran kedua nanti tanpa merombak task sensor.

Di tim yang sudah memakai subscriber Python → MySQL (#18), task MQTT tidak perlu tahu soal database — cukup publish JSON konsisten; worker Python tetap jalan di server terpisah tanpa blocking firmware.

Keamanan & Produksi

  • Jangan commit GANTI_PASSWORD_MQTT — pakai NVS (#12) atau build_flags
  • Broker produksi: aktifkan MQTT TLS (#17)
  • Batasi ukuran queue agar RAM tidak habis saat broker down berjam-jam
  • Log stack watermark sebelum rilis firmware

Estimasi Biaya

ItemBiaya
FreeRTOS (built-in ESP32)Rp 0
Hardware (ESP32 + DHT22 dari seri sebelumnya)Rp 0 tambahan
Waktu belajar abstraksi task±2–4 jam setelah #9 menguasai

Checklist Sebelum Go-Live

  1. Hanya satu task mengelola WiFi/MQTT client?
  2. Stack size cukup (cek high water mark)?
  3. Queue depth sesuai interval publish?
  4. Topic dan JSON konsisten dengan Grafana (#19)?
  5. Watchdog dipertimbangkan untuk node lapangan?

Uji Coba

  1. Upload sketch multi-task — Serial: WiFi OK + MQTT OK
  2. mosquitto_sub menerima JSON setiap ~5 detik
  3. Matikan broker 1 menit — queue penuh, task sensor tidak crash
  4. Nyalakan broker — publish lanjut otomatis
  5. Cek heap stabil setelah 30 menit

FAQ Singkat

FreeRTOS wajib di ESP32?
Arduino core sudah memakainya di balik layar; artikel ini membuat task eksplisit untuk kontrol kamu.
Bisa tetap pakai loop()?
Ya untuk proyek kecil; refactor ke task saat blocking jadi masalah.
Core 0 vs Core 1?
WiFi stack biasanya di Core 0 — ikuti pola pin task di contoh.
Queue penuh — data hilang?
xQueueSend dengan timeout pendek bisa gagal — log di Serial atau naikkan depth queue jika broker sering down.
Bisa pakai FreeRTOS di ESP8266?
ESP8266 single-core — pola task tetap ada tapi tanpa pin core; lihat perbandingan ESP32 (#1) sebelum porting.

Tips & Troubleshooting

  • Guru Meditation Error: Stack overflow — naikkan parameter stack di xTaskCreatePinnedToCore
  • MQTT putus acak: Pastikan mqttClient.loop() dipanggil rutin di task MQTT
  • DHT22 nan: Hanya task sensor yang memanggil dht.read; delay 2 detik setelah dht.begin()
  • Data duplikat di queue: Kurangi frekuensi sensor atau naikkan interval publish
  • Heap menurun: Hindari String besar di loop task — pakai buffer tetap
  • Task tidak start: Cek parameter stack tidak 0; pastikan sensorQueue dibuat sebelum xTaskCreate

Saat debugging di PlatformIO (#29), filter log per task dengan prefix — lebih mudah dibanding satu aliran Serial panjang dari loop tunggal. Simpan log panic backtrace jika Guru Meditation muncul — biasanya menunjuk task mana yang kehabisan stack.

Langkah Selanjutnya — Tier 2 Seri 2

Dengan Jalur E (#29–#31) selesai, Tier 1 inti Seri 2 lengkap — total 22 artikel live setelah deploy artikel ini, termasuk deep sleep (#11), broker sendiri (#16), dan capstone dashboard (#10). Lanjut ke pelengkap Tier 2:

FreeRTOS membuka pintu ke firmware ESP32 yang lebih andal dan siap skala tim — lanjutkan perjalanan di halaman artikel Koding Indonesia.