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
- Arduino IDE'de "esp32 by Espressif Systems" kart paketini kurun.
- 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.
- 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.
- PlatformIO ile her ikisini
platformio.iniiçindekilib_depsalanı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:
connectToWifi()Wi-Fi bağlantısını başlatır ve hemen döner.- İstasyon bir IP adresi aldığında
onWifiEvent,connectToMqtt()fonksiyonunu çağırır. mqttClient.connect()hemen döner. Daha sonra AsyncTCP görevi CONNACK'i alır ve kütüphaneonMqttConnectfonksiyonunu çağırır.onMqttConnectiç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.- Broker veya ağ bağlantınızı düşürürse
onMqttDisconnecttek seferlik bir timer planlar ve bu timer iki saniye sonraconnectToMqtt()ç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:
- 8883 portunda TLS mi gerekiyor?
WiFiClientSecureile PubSubClient'ı veya espMqttClient'ın güvenli client'ını seçin. Standart AsyncMqttClient uygun değildir. - Cihazdan QoS 1 veya 2 ile yayınlama mı gerekiyor? AsyncMqttClient veya espMqttClient.
- Aynı cihazda bir web sunucusu (ESPAsyncWebServer) mu çalışıyor? AsyncMqttClient, AsyncTCP yığınını sorunsuz şekilde paylaşır.
- ESP8266'yı da mı hedefliyorsunuz? AsyncMqttClient orada ESPAsyncTCP ile çalışır, ancak bellek daha kısıtlıdır. Bkz. ESP8266 MQTT eğitimi.
- 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.



