ESP32 ve Arduino

ESP32'de Async MQTT Client: AsyncMqttClient ile Engellemeyen MQTT

AsyncMqttClient'ın ESP32 firmware'ini nasıl duyarlı tuttuğunu öğrenin: olay güdümlü callback'ler, FreeRTOS timer'larıyla otomatik yeniden bağlanma, QoS 1 ve 2 ile yayınlama ve parçalı payload işleme.

Mesajları engellemeden gönderen bir ESP32 async MQTT client kurulumunu olay oklarıyla gösteren ESP32 kartı
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

ESP32'de async bir MQTT client, MQTT trafiği arka planda işlenirken firmware'inizin sensör okumaya, ekran sürmeye ve web sayfası sunmaya devam etmesini sağlar. Bu rehberde olay callback'leri, otomatik yeniden bağlanma ve gerçek bir broker'a QoS 1 ile yayınlama içeren eksiksiz bir AsyncMqttClient sketch'i oluşturacaksınız. Ayrıca kütüphanenin tuzaklarını öğrenerek ne zaman doğru seçim olduğuna karar verebileceksiniz.

Neden Async Bir MQTT Client Kullanmalı?

PubSubClient gibi engelleyen (blocking) kütüphaneler iyi çalışır, ancak loop() fonksiyonunu sık sık çağırmanıza dayanırlar ve ağ kötü olduğunda connect() sketch'inizi soket zaman aşımı süresine kadar dondurabilir. Aynı zamanda bir motor süren, bir LED şeridi yenileyen veya bir yapılandırma sayfası barındıran bir cihazda bu duraklamalar fark edilir.

Marvin Roger'ın geliştirdiği AsyncMqttClient farklı bir yaklaşım benimser. Ağ işlemlerini kendi FreeRTOS görevinde (task) çalıştıran AsyncTCP kütüphanesinin üzerine oturur. Kodunuz olaylar (bağlandı, bağlantı kesildi, mesaj alındı, yayın onaylandı) için callback'ler kaydeder ve kütüphane bir şey olduğunda bunları çağırır. Çağrılacak bir loop() yoktur.

Özellik PubSubClient AsyncMqttClient
Programlama modeli Yoklama (polling), loop() çağrısı Olay callback'leri
connect() davranışı CONNACK veya zaman aşımına kadar bekler Hemen döner
Yayınlama QoS 0 0, 1 ve 2
Abonelik QoS 0 ve 1 0, 1 ve 2
Büyük gelen payload'lar Buffer'a sığmalı Parçalar halinde iletilir
ESP32'de TLS Evet, WiFiClientSecure ile Standart AsyncTCP ile pratik değil
Bağımlılıklar Yok AsyncTCP

Son satır önemlidir. AsyncMqttClient'ın TLS desteği, AsyncTCP'nin SSL etkin bir derlemesine bağlıdır ve bu, standart ESP32 AsyncTCP kütüphanesinde mevcut değildir. Pratikte bu kütüphane en iyi, güvenilir bir ağ içinde 1883 portunda düz MQTT için veya prototipleme için uygundur. Olay güdümlü bir API ile TLS'e ihtiyacınız varsa, aktif olarak geliştirilen espMqttClient kütüphanesi ESP32'de TLS desteğiyle çok benzer bir callback tarzı sunar. Artıları ve eksileri sonda ele alıyoruz.

Kütüphaneleri Kurun

  1. Arduino IDE'de "esp32 by Espressif Systems" kart paketini kurun.
  2. ESP32 için AsyncTCP kurun. Library Manager'da bakımı yapılan birkaç fork bulunur; ESP32 core sürümünüzle uyumlu olanı seçin.
  3. AsyncMqttClient kurun. Library Manager'da görünmüyorsa proje deposundan sürüm ZIP dosyasını indirin ve Sketch → Include Library → Add .ZIP Library menüsünü kullanın.
  4. PlatformIO ile her ikisini platformio.ini içindeki lib_deps alanına ekleyin.
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
lib_deps =
    marvinroger/AsyncMqttClient
    esp32async/AsyncTCP

Eksiksiz ESP32 Async MQTT Client Sketch'i

Bu örnek, ConnectMQ'nun herkese açık MQTT test broker'ı olan broker.connectmq.com adresine 1883 portundan bağlanır. Wi-Fi ve MQTT'yi FreeRTOS timer'larıyla yeniden bağlar, her on saniyede bir QoS 1 ile telemetri yayınlar ve bir komut geldiğinde karttaki LED'i açıp kapatır.

#include <WiFi.h>
#include <AsyncMqttClient.h>

extern "C" {
  #include "freertos/FreeRTOS.h"
  #include "freertos/timers.h"
}

#define WIFI_SSID     "YOUR_WIFI"
#define WIFI_PASSWORD "YOUR_WIFI_PASSWORD"

#define MQTT_HOST "broker.connectmq.com"
#define MQTT_PORT 1883

#define LED_PIN 2

static const char* DEVICE_ID = "async-4b81d2";   // bunu benzersiz yapın
String topicTelemetry = String("connectmq/demo/") + DEVICE_ID + "/telemetry";
String topicCommand   = String("connectmq/demo/") + DEVICE_ID + "/led/set";
String topicStatus    = String("connectmq/demo/") + DEVICE_ID + "/status";

AsyncMqttClient mqttClient;
TimerHandle_t mqttReconnectTimer;
TimerHandle_t wifiReconnectTimer;

unsigned long lastPublish = 0;

void connectToWifi() {
  Serial.println("Connecting to Wi-Fi...");
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
}

void connectToMqtt() {
  Serial.println("Connecting to MQTT...");
  mqttClient.connect();
}

void onWifiEvent(WiFiEvent_t event) {
  switch (event) {
    case ARDUINO_EVENT_WIFI_STA_GOT_IP:
      Serial.print("Wi-Fi connected, IP ");
      Serial.println(WiFi.localIP());
      connectToMqtt();
      break;
    case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
      Serial.println("Wi-Fi lost");
      xTimerStop(mqttReconnectTimer, 0);   // Wi-Fi kapalıyken MQTT yok
      xTimerStart(wifiReconnectTimer, 0);
      break;
    default:
      break;
  }
}

void onMqttConnect(bool sessionPresent) {
  Serial.printf("MQTT connected, session present: %d\n", sessionPresent);
  mqttClient.subscribe(topicCommand.c_str(), 1);
  mqttClient.publish(topicStatus.c_str(), 1, true, "online");
}

void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) {
  Serial.printf("MQTT disconnected, reason %d\n", static_cast<int>(reason));
  if (WiFi.isConnected()) {
    xTimerStart(mqttReconnectTimer, 0);
  }
}

void onMqttSubscribe(uint16_t packetId, uint8_t qos) {
  Serial.printf("Subscribe ack, packetId %u, granted QoS %u\n", packetId, qos);
}

void onMqttPublish(uint16_t packetId) {
  Serial.printf("Publish ack (PUBACK), packetId %u\n", packetId);
}

void onMqttMessage(char* topic, char* payload,
                   AsyncMqttClientMessageProperties properties,
                   size_t len, size_t index, size_t total) {
  // payload null ile sonlanmaz ve daha büyük bir mesajın bir parçası olabilir
  if (index != 0 || len != total) {
    Serial.println("Fragmented message ignored in this demo");
    return;
  }
  String msg;
  msg.reserve(len);
  for (size_t i = 0; i < len; i++) msg += payload[i];

  Serial.printf("Message on %s (QoS %u): %s\n", topic, properties.qos, msg.c_str());

  if (msg == "ON")  digitalWrite(LED_PIN, HIGH);
  if (msg == "OFF") digitalWrite(LED_PIN, LOW);
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);

  mqttReconnectTimer = xTimerCreate("mqttTimer", pdMS_TO_TICKS(2000), pdFALSE,
                                    nullptr, reinterpret_cast<TimerCallbackFunction_t>(connectToMqtt));
  wifiReconnectTimer = xTimerCreate("wifiTimer", pdMS_TO_TICKS(2000), pdFALSE,
                                    nullptr, reinterpret_cast<TimerCallbackFunction_t>(connectToWifi));

  WiFi.onEvent(onWifiEvent);

  mqttClient.onConnect(onMqttConnect);
  mqttClient.onDisconnect(onMqttDisconnect);
  mqttClient.onSubscribe(onMqttSubscribe);
  mqttClient.onPublish(onMqttPublish);
  mqttClient.onMessage(onMqttMessage);

  mqttClient.setServer(MQTT_HOST, MQTT_PORT);
  mqttClient.setClientId(DEVICE_ID);              // pointer geçerli kalmalı
  mqttClient.setKeepAlive(30);
  mqttClient.setWill(topicStatus.c_str(), 1, true, "offline");

  connectToWifi();
}

void loop() {
  // Uygulama kodunuz burada serbestçe çalışır; MQTT arka planda işlenir.
  if (mqttClient.connected() && millis() - lastPublish > 10000) {
    lastPublish = millis();
    char payload[96];
    snprintf(payload, sizeof(payload), "{\"uptime\":%lu,\"rssi\":%d,\"heap\":%u}",
             millis() / 1000, WiFi.RSSI(), (unsigned)ESP.getFreeHeap());
    uint16_t id = mqttClient.publish(topicTelemetry.c_str(), 1, false, payload);
    Serial.printf("Queued publish, packetId %u\n", id);
  }
}

Trafiği izleyin ve bilgisayarınızdan komut gönderin:

mosquitto_sub -h broker.connectmq.com -p 1883 -i watch-4b81d2 -t "connectmq/demo/async-4b81d2/#" -v -q 1
mosquitto_pub -h broker.connectmq.com -p 1883 -i cmd-4b81d2 -t "connectmq/demo/async-4b81d2/led/set" -m ON -q 1

Olay Akışı Nasıl İşler?

Olayların sırasını anlamak hataların çoğunu önler:

  1. connectToWifi() Wi-Fi bağlantısını başlatır ve hemen döner.
  2. İstasyon bir IP adresi aldığında onWifiEvent, connectToMqtt() fonksiyonunu çağırır.
  3. mqttClient.connect() hemen döner. Daha sonra AsyncTCP görevi CONNACK'i alır ve kütüphane onMqttConnect fonksiyonunu çağırır.
  4. onMqttConnect içinde abone olur ve "online" durumunuzu yayınlarsınız. Burası doğru yerdir, çünkü clean session'daki abonelikler her yeniden bağlanmadan sonra yenilenmelidir.
  5. Broker veya ağ bağlantınızı düşürürse onMqttDisconnect tek seferlik bir timer planlar ve bu timer iki saniye sonra connectToMqtt() çağırır.

publish(), QoS 1 ve 2 mesajlar için bir paket ID'si, kuyruğa alınmış bir QoS 0 mesaj için 1, mesaj gönderilemediğinde ise 0 döndürür. Eşleşen onMqttPublish(packetId), broker teslimatı onayladığında tetiklenir; böylece önemli mesajların gerçekten ulaşıp ulaşmadığını takip edebilirsiniz.

Production'da Can Yakan Tuzaklar

Callback'ler Başka Bir Görevde Çalışır

Callback'leriniz loop() içinde değil, AsyncTCP görevinde çalışır. Orada delay() çağırmayın veya yavaş G/Ç işlemleri yapmayın; bu tüm ağ trafiğini engeller ve görev watchdog'unu tetikleyebilir. Veriyi bir değişkene veya kuyruğa kopyalayın ve loop() içinde işleyin. loop() ile bir callback bir değişkeni paylaşıyorsa onu koruyun; örneğin bir FreeRTOS kuyruğuyla veya basit durumlar için volatile bir bayrakla.

String Ömürleri

setClientId(), setCredentials(), host adıyla setServer() ve setWill() kopya değil pointer saklar. Geçici bir String nesnesinin c_str() değerini vermek, yeniden bağlanırken anlamsız verilerin gönderilmesine yol açar. Örnekte olduğu gibi global sabitler veya global String nesneleri kullanın.

Parçalı Payload'lar

Büyük gelen mesajlar, artan index ve aynı total değeriyle birkaç onMessage çağrısında gelir. Birkaç yüz bayttan büyük payload'lar bekliyorsanız parçaları total boyutunda bir buffer'da biriktirin ve index + len == total olduğunda işleyin.

Yerleşik Yeniden Bağlanma Yok

Kütüphane kendi kendine yeniden bağlanmaz. Yukarıdaki timer deseni standart yaklaşımdır. Wi-Fi düştüğünde MQTT timer'ını mutlaka durdurun; aksi halde başarılı olamayacak bağlantı denemeleriyle ortalığı doldurursunuz.

Kaçırılan Komutlar İçin Kalıcı Oturumlar

Varsayılan olarak client clean session ile bağlanır; bu yüzden cihaz çevrimdışıyken gönderilen komutlar kaybolur. Bağlanmadan önce mqttClient.setCleanSession(false) çağırın ve QoS 1 ile abone olun: broker bu durumda oturumu saklar ve cihaz geri dönene kadar QoS 1 mesajları onun için kuyrukta tutar. Oturum sonsuza kadar yaşamaz; broker'lar bir süre sonra oturumun süresini doldurur (herkese açık broker'da oturumlar en fazla 30 dakika sonra sona erer). Broker'ın sizi hatırlayıp hatırlamadığını görmek için onMqttConnect içinde sessionPresent değerini kontrol edin ve hatırlamadıysa yeniden abone olun.

Broker Limitleri Yine Geçerlidir

Async, sınırsız demek değildir. Herkese açık broker client başına saniyede 10 mesaja, client başına 10 aboneliğe ve mesaj başına 64 KB'a izin verir. publish() çağıran sıkı bir döngü bu limitlere hızla ulaşır.

Async ve Engelleyen Client'lar Arasında Seçim

Karar vermek için bu kontrol listesini kullanın:

  1. 8883 portunda TLS mi gerekiyor? WiFiClientSecure ile PubSubClient'ı veya espMqttClient'ın güvenli client'ını seçin. Standart AsyncMqttClient uygun değildir.
  2. Cihazdan QoS 1 veya 2 ile yayınlama mı gerekiyor? AsyncMqttClient veya espMqttClient.
  3. Aynı cihazda bir web sunucusu (ESPAsyncWebServer) mu çalışıyor? AsyncMqttClient, AsyncTCP yığınını sorunsuz şekilde paylaşır.
  4. ESP8266'yı da mı hedefliyorsunuz? AsyncMqttClient orada ESPAsyncTCP ile çalışır, ancak bellek daha kısıtlıdır. Bkz. ESP8266 MQTT eğitimi.
  5. Başlangıç seviyesinde bir proje için en basit kodu mu istiyorsunuz? PubSubClient eğitimi daha yumuşak bir başlangıçtır.

Mesajlarınızda asıl mesele teslimat garantileriyse, yalnızca QoS 2 için kütüphane değiştirmeden önce MQTT QoS seviyeleri açıklamalı yazısını okuyun; pek çok sensör iş yükü için QoS 0 veya 1 en iyisidir. Daha kapsamlı ESP32 IoT eğitimi, MQTT'nin eksiksiz bir sensör projesine nasıl oturduğunu gösterir.

ConnectMQ ile Production Kurulumu

Laboratuvar dışındaki dağıtımlarda cihazı herkese açık alan yerine özel bir bulut MQTT broker'ına bağlayın. ConnectMQ'da portalda bir broker oluşturursunuz ve bir kullanıcı adı, parola ve client ID alırsınız. AsyncMqttClient ile bunları şöyle ayarlarsınız; client ID'lerin verilen kimlik bilgilerine bağlı olduğunu unutmayın:

mqttClient.setServer("broker.connectmq.com", 1883);
mqttClient.setClientId("YOUR_CLIENT_ID");
mqttClient.setCredentials("YOUR_USERNAME", "YOUR_PASSWORD");

1883 portunda kimlik bilgileri şifrelenmeden iletildiği için güvenilir bir ağın dışındaki cihazlarda TLS destekli bir kütüphaneyi tercih edin.

ConnectMQ ile Başlayın

ConnectMQ; MQTT 3.1.1 ve 5.0, TLS, WebSocket, canlı bir topic gezgini ve portalda eşik uyarıları sunan barındırılan bir MQTT broker'dır; böylece ESP32'nizin yayınlarını gerçek zamanlı izleyebilirsiniz. app.connectmq.com adresinde kredi kartı olmadan ücretsiz bir Serverless broker oluşturun ve ilk async cihazınızı dakikalar içinde bağlayın.

Sık Sorulan Sorular

AsyncMqttClient ESP32'de TLS ile çalışır mı?

ESP32 için SSL derlemesi olmayan standart AsyncTCP kütüphanesiyle çalışmaz. 8883 portuna ihtiyacınız olduğunda WiFiClientSecure ile PubSubClient'ı veya espMqttClient gibi bakımı yapılan bir async kütüphaneyi kullanın.

Yeniden bağlanmak için neden FreeRTOS timer'larına ihtiyacım var?

AsyncMqttClient'ta otomatik yeniden bağlanma yoktur. Tek seferlik timer'lar, loop() fonksiyonunu veya ağ görevini engellemeden kısa bir gecikmeden sonra yeniden denemenizi sağlar.

onMqttMessage içinde delay() çağırabilir miyim?

Hayır. Callback'ler AsyncTCP görevinde çalışır ve orada engelleme yapmak tüm ağ trafiğini durdurur. Mesajı saklayın ve loop() içinde işleyin.

AsyncMqttClient hâlâ geliştiriliyor mu?

Orijinal kütüphane bir süredir aktif olarak geliştirilmiyor, ancak yaygın şekilde kullanılmaya devam ediyor. Uzun ömürlü yeni bir ürüne başlamadan önce depo durumunu kontrol edin ve bakımı yapılan bir alternatif olarak espMqttClient'ı değerlendirin.

İ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.