ESP32 ve Arduino

MQTT Projelerinde ESP32 Task Watchdog Sıfırlamalarını Düzeltmek

ESP32 MQTT sketch'leri neden task watchdog sıfırlamalarını tetikler, task_wdt mesajı nasıl okunur ve watchdog'un sessiz kalması için yeniden bağlanma döngüleri, callback'ler ve engelleyen kod nasıl yeniden yapılandırılır.

Bir ESP32 task watchdog MQTT projesinde task_wdt hatası gösteren ESP32 seri monitörü
Bunu kendi MQTT broker’ınızda çalıştırınFree planla özel, yönetilen bir MQTT broker alın ve hazır kod örnekleriyle başlayın.
Ücretsiz deneyin Kod örnekleri Kart gerekmez

Bir MQTT projesindeki ESP32 task watchdog sıfırlaması neredeyse her zaman bir kod parçasının bir görevi watchdog'un izin verdiğinden daha uzun süre engellediği anlamına gelir. Bu makale task_wdt mesajının nasıl okunacağını gösterir, MQTT sketch'lerindeki olağan şüphelileri belirler ve cihazı sıfırlama olmadan bağlı tutan engellemeyen kalıplar sunar.

Task Watchdog Gerçekte Ne Yapar?

ESP32 FreeRTOS çalıştırır. Bir Arduino sketch'inde bile setup() ve loop(), loopTask adlı bir FreeRTOS görevi içinde çalışır; Wi-Fi ve TCP/IP yığını kendi görevlerinde çalışır ve her CPU çekirdeğinin, CPU'ya başka hiçbir şey ihtiyaç duymadığında çalışan bir idle görevi vardır.

Task Watchdog Timer (TWDT) bir dizi abone görevi izler. Abone olan her görev watchdog'u periyodik olarak "beslemelidir". Bunlardan biri zaman aşımı içinde bunu yapamazsa TWDT bir hata yazdırır ve yapılandırmaya bağlı olarak panic durumuna geçip çipi yeniden başlatır.

Hangi görevlerin izlendiği ortamınıza bağlıdır:

Ortam Varsayılan olarak izlenen Tipik zaman aşımı
ESP-IDF Her iki çekirdeğin idle görevleri (menuconfig'te yapılandırılabilir) 5 sn
Arduino-ESP32 Çekirdek 0'daki idle görevi; loopTask yalnızca enableLoopWDT() çağırırsanız 5 sn
AsyncTCP (AsyncMqttClient tarafından kullanılır) Watchdog seçeneği etkin olarak derlendiğinde kendi async_tcp görevi TWDT zaman aşımını devralır

Idle görevi dolaylı bir nedenle izlenir: hiç çalışma fırsatı bulamıyorsa o çekirdekteki başka bir görev CPU'yu bırakmadan tekelinde tutuyordur. Yani bir "IDLE0" mesajı idle görevinin bozuk olduğu anlamına gelmez; çekirdek 0'daki başka bir şeyin boşa döndüğü anlamına gelir.

Ayrıca kesmeler çok uzun süre devre dışı kaldığında veya bir ISR çok uzun çalıştığında tetiklenen ayrı bir Interrupt Watchdog da vardır. Mesajında "Interrupt wdt timeout" ifadesi geçer ve nedenleri farklıdır; çoğunlukla ISR kodundadır. Bu makale task watchdog hakkındadır.

Hata Mesajını Okumak

Tipik bir çökme şöyle görünür:

E (25391) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
E (25391) task_wdt:  - IDLE0 (CPU 0)
E (25391) task_wdt: Tasks currently running:
E (25391) task_wdt: CPU 0: mqttTask
E (25391) task_wdt: CPU 1: loopTask
E (25391) task_wdt: Aborting.

Bunu iki bölümde okuyun. İlk liste kimin aç kaldığını söyler: burada çekirdek 0'ın idle görevi. "Currently running" listesi ise o anda CPU'yu kimin tuttuğunu söyler: çekirdek 0'da mqttTask adlı bir görev. Şüpheliniz budur. Aç kalan görev async_tcp ise sorun bir AsyncTCP veya AsyncMqttClient callback'i içinde çalışan koddadır. loopTask ise loop watchdog etkinken loop()'unuz engellenmiştir.

Backtrace adreslerini dosya adlarına ve satır numaralarına dönüştürmek için seri monitörünüzde exception decoder'ı etkinleştirin (PlatformIO'da monitor_filters = esp32_exception_decoder).

ESP32 Task Watchdog MQTT Çökmelerindeki Olağan Şüpheliler

Yield Olmadan Meşgul Bekleme Döngüleri

Klasik suçlu, içi boş bir bekleme döngüsüdür:

while (WiFi.status() != WL_CONNECTED) { }   // never yields
while (!mqtt.connect(clientId)) { }          // retries at full speed

Çekirdek 1'deki loop() içinde bu çoğu zaman sıfırlamaya yol açmadan geçer, çünkü Arduino core varsayılan olarak idle görevi 1'i izlemez. Aynı kodu çekirdek 0'a sabitlenmiş bir göreve taşıyın veya loop watchdog'unu etkinleştirin; beş saniye sonra sıfırlanır. Döngünün içine bir delay(10) veya vTaskDelay() koymak açlığı giderir ama tasarımı düzeltmez: cihaz beklerken yine başka hiçbir şey yapamaz.

Zaman Aşımından Uzun Süren Engelleyen Bağlantı Çağrıları

PubSubClient'ın connect() fonksiyonu engelleyicidir. Broker'a ulaşılamıyorsa alttaki TCP bağlantısı ve MQTT CONNACK beklemesi birlikte beş saniyeden uzun sürebilir; özellikle zayıf sinyalde el sıkışmanın tek başına saniyeler sürebildiği TLS ile. connect()'i art arda birkaç kez çağıran bir yeniden bağlanma fonksiyonu bunu katlar. Soket zaman aşımını mqtt.setSocketTimeout(4) ile düşürün ve her çağrıda yalnızca bir kez deneyin.

MQTT Callback'lerinde Ağır İşler

AsyncMqttClient ile onMessage, async_tcp görevi içinde çalışır. Orada büyük bir JSON belgesini ayrıştırmak, flash'a yazmak, bir ekranı sürmek veya delay() çağırmak ağ yığınını engeller ve o görevin watchdog'unu tetikleyebilir. PubSubClient ile callback mqtt.loop() içinde çalışır; bu yüzden oradaki ağır iş bunun yerine döngüyü engeller. Her iki durumda da payload'ı kopyalayın, bir bayrak ayarlayın veya bir kuyruğa itin ve işi başka bir yerde yapın.

Uzun Engelleyen Sensör Okumaları ve Flash Yazmaları

Bazı sensör kütüphaneleri meşgul beklemeyle yoklama yapar; bir OTA indirmesi veya büyük bir flash yazması saniyelerce sürebilir. Bunlar watchdog'u besleyen kendi görevlerinde sorun değildir, ancak MQTT'ye hizmet eden aynı görevde sorun olur.

Ağ Kaybından Sonra Yeniden Bağlanma Fırtınaları

Wi-Fi koptuğunda saf bir sketch WiFi.begin() ve mqtt.connect()'i sıkı bir döngüde art arda çağırır. Her çağrı biraz engeller, döngü hiç duraklamaz ve sonunda watchdog tetiklenir. Üstel backoff hem watchdog sorununu hem de broker üzerindeki yükü çözer.

loop() İçin Engellemeyen MQTT Kalıbı

Bu sketch loop()'u her zaman kısa tutar. Her bağlantı denemesi backoff aralığı başına en fazla bir kez yapılır, callback yalnızca veriyi kopyalar ve iş daha sonra loop() içinde çalışır. broker.connectmq.com'un public alanına bağlanır.

#include <WiFi.h>
#include <PubSubClient.h>

const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_WIFI_PASSWORD";
const char* CMD_TOPIC = "connectmq/demo/b52e07/cmd";
const char* TELEMETRY_TOPIC = "connectmq/demo/b52e07/telemetry";

WiFiClient net;
PubSubClient mqtt(net);
String clientId;

volatile bool commandPending = false;
char commandBuf[128];

unsigned long nextMqttAttempt = 0;
unsigned long mqttBackoff = 1000;
unsigned long lastPublish = 0;

void onMessage(char* topic, byte* payload, unsigned int length) {
  size_t n = min((size_t)length, sizeof(commandBuf) - 1);
  memcpy(commandBuf, payload, n);
  commandBuf[n] = '\0';
  commandPending = true;          // no heavy work here
}

void handleCommand(const char* cmd) {
  Serial.printf("Handling command: %s\n", cmd);
  // slow work is fine here, as long as it stays under the watchdog timeout
}

void maintainMqtt() {
  if (WiFi.status() != WL_CONNECTED) return;   // Wi-Fi reconnects on its own
  if (mqtt.connected()) { mqtt.loop(); return; }
  if (millis() < nextMqttAttempt) return;

  if (mqtt.connect(clientId.c_str())) {
    mqtt.subscribe(CMD_TOPIC);
    mqttBackoff = 1000;
  } else {
    nextMqttAttempt = millis() + mqttBackoff;
    mqttBackoff = min(mqttBackoff * 2, 30000UL);
  }
}

void setup() {
  Serial.begin(115200);
  enableLoopWDT();                // watch loop() too, so blocking shows up in testing
  WiFi.mode(WIFI_STA);
  WiFi.setAutoReconnect(true);
  WiFi.begin(WIFI_SSID, WIFI_PASS);

  clientId = "esp32-wdt-" + String((uint32_t)ESP.getEfuseMac(), HEX);
  mqtt.setServer("broker.connectmq.com", 1883);
  mqtt.setSocketTimeout(4);       // keep each blocking connect well under 5 s
  mqtt.setCallback(onMessage);
}

void loop() {
  maintainMqtt();

  if (commandPending) {
    commandPending = false;
    handleCommand(commandBuf);
  }

  if (mqtt.connected() && millis() - lastPublish > 10000) {
    lastPublish = millis();
    char payload[64];
    snprintf(payload, sizeof(payload), "{\"uptime\":%lu,\"heap\":%u}",
             millis() / 1000, ESP.getFreeHeap());
    mqtt.publish(TELEMETRY_TOPIC, payload);
  }
}

Arduino core, loop() her döndüğünde loop watchdog'unu otomatik olarak besler; dolayısıyla loop()'tan bir geçiş zaman aşımının çok altında kaldığı sürece onu etkinleştirmenin hiçbir maliyeti yoktur ve gerilemeleri erkenden yakalar. Geliştirme sırasında açmak, engelleyen çağrıyı sahaya ulaşmadan bulmanın ucuz bir yoludur.

MQTT'yi Kendi FreeRTOS Görevinde Çalıştırmak

Ayrı bir MQTT görevi tercih ediyorsanız onu watchdog'a abone edin ve her yinelemede sıfırlayın. ESP-IDF 5'teki (Arduino-ESP32 3.x tarafından kullanılır) esp_task_wdt API'si şöyle görünür:

#include <esp_task_wdt.h>

void mqttTask(void* arg) {
  esp_task_wdt_add(NULL);         // watch this task
  for (;;) {
    maintainMqtt();               // must return quickly
    esp_task_wdt_reset();         // feed the watchdog
    vTaskDelay(pdMS_TO_TICKS(10)); // always yield
  }
}

void setup() {
  // ... Wi-Fi and mqtt setup as above ...
  xTaskCreatePinnedToCore(mqttTask, "mqttTask", 6144, NULL, 1, NULL, 1);
}

Yükseltmek için bir nedeniniz yoksa önceliği düşük tutun (1, loopTask ile aynıdır); hiç bloklanmayan yüksek öncelikli bir görev altındaki her şeyi aç bırakır. Wi-Fi'ın çalıştığı çekirdekten uzak durmak istiyorsanız onu çekirdek 1'e sabitleyin. MQTT istemcisi birden fazla görevden kullanılıyorsa bir mutex ile koruyun, çünkü PubSubClient thread-safe değildir. ESP32 FreeRTOS MQTT rehberi kuyrukları ve görev tasarımını daha ayrıntılı ele alır.

Watchdog Zaman Aşımını Yükseltmeli misiniz?

Bazen, ama bunu son adım olarak görün. Zaman aşımını yükseltmek engelleyen kodu düzeltmez, gizler; cihaz da gerçek bir kilitlenmeden daha yavaş toparlanır. Meşru durumlar, OTA flash yazması gibi uzun ve kaçınılmaz işlemlerdir. ESP-IDF 5'te TWDT'yi çalışma zamanında yeniden yapılandırabilirsiniz:

esp_task_wdt_config_t cfg = {
  .timeout_ms = 15000,
  .idle_core_mask = (1 << 0),     // keep watching IDLE0
  .trigger_panic = true,
};
esp_task_wdt_reconfigure(&cfg);

Tek bir uzun işlem için daha temiz bir yaklaşım, watchdog'u o işlemin içinden, örneğin bir OTA ilerleme callback'inden beslemek ve genel zaman aşımına dokunmamaktır. Üretimde watchdog'u asla tamamen devre dışı bırakmayın. Sahada bir deadlock'ta takılan cihazı yeniden başlatan tek şey odur.

Sorun Giderme Kontrol Listesi

  1. Hangi görevin aç kaldığını ve hangi görevin çalıştığını okuyun; bu ikili size nereye bakacağınızı söyler.
  2. Tam satırı bulmak için backtrace'i exception decoder ile çözün.
  3. Kodda delay(), yield() veya vTaskDelay() olmayan while döngülerini arayın.
  4. mqtt.setSocketTimeout()'u birkaç saniyeye ayarlayın ve connect() etrafındaki yeniden deneme döngülerini kaldırın.
  5. JSON ayrıştırma, flash yazma ve ekran güncellemelerini MQTT callback'lerinin dışına taşıyın.
  6. Wi-Fi ve MQTT yeniden bağlanmalarına üstel backoff ekleyin.
  7. loop() içindeki engellemelerin erkenden ortaya çıkması için test sırasında enableLoopWDT()'yi etkinleştirin.
  8. Cihazın uptime değerini MQTT üzerinden izleyin: sürekli sıfırlanan bir sayaç watchdog veya brownout kaynaklı yeniden başlatmadır.

Her açılıştan sonra uptime ve esp_reset_reason() değerlerini yayınlamak, görünmez sıfırlamaları veriye dönüştüren basit bir alışkanlıktır. Watchdog sorunu gibi görünen birçok bağlantı sorunu aslında altta yatan ağ sorunlarıdır; cihaz hiç bağlanmıyorsa önce ESP32'nin bir MQTT broker'a neden bağlanamadığını inceleyin, yeniden bağlanma stratejisi için de ESP32 MQTT yeniden bağlanma rehberine bakın.

Cihazlarınızı ConnectMQ'da İzleyin

Yukarıdaki test sketch'i ConnectMQ'nun ücretsiz public MQTT broker'ında çalışır; bu, bir sıfırlamayı masanızda yeniden üretmek için mükemmeldir. Bir filo için özel bir ConnectMQ broker, her cihazın telemetrisini canlı topic gezgininde izlemenizi ve boş heap gibi değerler için eşik uyarı kuralları belirlemenizi sağlar; böylece yeniden başlamaya başlayan bir cihaz hızla göze çarpar. app.connectmq.com adresinde kredi kartı olmadan ücretsiz Serverless planında bir tane oluşturabilirsiniz. Broker'ınızı nerede barındıracağınıza hâlâ karar veriyorsanız bulut MQTT broker rehberi seçenekleri karşılaştırır; ESP32 IoT eğitimi de eksiksiz, engellemeyen bir cihaz projesini baştan sona gösterir.

Sık Sorulan Sorular

IDLE0 ile birlikte "Task watchdog got triggered" ne anlama gelir?

Çekirdek 0'da çalışan bir görev CPU'yu bırakmadan zaman aşımından daha uzun süre meşgul tuttu, bu yüzden idle görevi hiç çalışamadı. "Tasks currently running" satırı olası suçluyu belirtir.

delay() watchdog'u sıfırlar mı?

delay() diğer görevlere sıra verir, bu da idle görevinin çalışmasına izin verir; böylece IDLE açlığını önler. Ancak esp_task_wdt_add() ile abone ettiğiniz bir görevi beslemez; o görevin esp_task_wdt_reset()'i kendisi çağırması gerekir.

AsyncMqttClient neden async_tcp watchdog'unu tetikler?

Callback'leri async_tcp görevi içinde çalışır. Oradaki uzun işler görevi engeller; bu yüzden callback'leri kısa tutun ve payload'ı bir bayrak veya kuyruk aracılığıyla loop()'a ya da ayrı bir göreve aktarın.

Task watchdog'u devre dışı bırakmak güvenli mi?

Üretimde değil. Bir cihazı kilitlenmeden kurtaran mekanizma odur. Engelleyen kodu düzeltin ve zaman aşımını yalnızca gerçekten uzun işlemler için yükseltin.

İlgili yazılar

ESP32 NTP MQTT Zaman Damgası: Her Mesaja Doğru Zaman Ekleyin

ESP32 saatini NTP ile eşitleyin, ISO 8601 ve epoch milisaniye zaman damgalarını biçimlendirin ve bunları MQTT payload'larına ekleyin; böylece ölçümler geç yayınlansa bile gerçek alınma zamanlarını korur.

6 dk okuma

Tüm yazılar →

Özel MQTT broker’ınız birkaç tık uzağınızda

Hesabınızı oluşturun, broker kimlik bilgilerinizi alın ve ilk cihazınızı bağlayın — kurulacak, yamalanacak ya da ölçeklendirilecek sunucu yok.