ESP32 MQTT yeniden bağlanma mantığı, masanızda çalışan bir demoyu aylarca kesintisiz çalışan bir cihazdan ayıran şeydir. Bu rehberde çoğu örnekte bulunan engelleyen reconnect() döngüsünü; Wi-Fi ve MQTT'yi ayrı ayrı yöneten, jitter ile üstel olarak bekleme süresini artıran, otomatik olarak yeniden abone olan ve çevrimdışıyken ölçümleri tamponlayan engellemeyen bir durum makinesiyle değiştireceksiniz. Sonunda bunu en dürüst yoldan doğrulayacaksınız: ESP32 broker.connectmq.com'a bağlıyken router'ınızı yeniden başlatarak.
Klasik Yeniden Bağlanma Döngüsü Neden Başarısız Olur?
Neredeyse her PubSubClient örneği şunun bir versiyonunu içerir:
void reconnect() {
while (!client.connected()) {
if (client.connect("esp32")) {
client.subscribe("cmd");
} else {
delay(5000);
}
}
}
Ağ gidene kadar çalışır. Sonra birkaç sorun aynı anda ortaya çıkar:
| Sorun | Pratikte ne olur |
|---|---|
Sonsuz while döngüsü |
Başka hiçbir şey çalışmaz: butonlar, sensörler, ekranlar ve motor kontrolü broker geri gelene kadar donar |
delay(5000) |
Yukarıdakiyle aynı; ayrıca bazı yapılandırmalarda task watchdog tetiklenebilir |
| Wi-Fi kontrolü yok | Sketch, Wi-Fi'ın kendisi kapalıyken MQTT bağlantılarını art arda dener; her biri DNS veya TCP zaman aşımını bekler |
| Sabit yeniden deneme aralığı | Aynı kesintiden kurtulan yüzlerce cihaz aynı anda yeniden bağlanır ve broker'a aynı anda yüklenir |
| Sabit kodlanmış client ID | Aynı firmware'i çalıştıran iki kart birbirini sonsuza kadar bağlantıdan düşürür |
Çözüm, bağlantıyı bir arka plan meselesi olarak ele almaktır. loop() ne olursa olsun birkaç milisaniyede bir çalışmalı, bağlantı denemeleri ise yalnızca zamanı geldiğinde yapılmalıdır.
PubSubClient Durum Kodlarını Anlayın
Herhangi bir iyileştirmeye başlamadan önce her başarısızlıkta client.state() değerini loglayın. Kodlar sorunun ağda mı, broker'da mı yoksa kimlik bilgilerinizde mi olduğunu söyler.
| Kod | Sabit | Anlamı | Tipik neden |
|---|---|---|---|
| -4 | MQTT_CONNECTION_TIMEOUT |
Keep-alive süresi içinde yanıt yok | Ağ takıldı, zayıf Wi-Fi |
| -3 | MQTT_CONNECTION_LOST |
Bağlantı koptu | Router yeniden başlatma, broker yeniden başlatma, yinelenen client ID |
| -2 | MQTT_CONNECT_FAILED |
TCP bağlantısı başarısız | Yanlış host veya port, DNS hatası, internet yok |
| -1 | MQTT_DISCONNECTED |
Düzgün şekilde bağlantı kesildi | disconnect() çağırdınız |
| 1 | MQTT_CONNECT_BAD_PROTOCOL |
Protokol desteklenmiyor | 3.1.1 broker'larında nadir |
| 2 | MQTT_CONNECT_BAD_CLIENT_ID |
Client ID reddedildi | Boş veya geçersiz ID |
| 3 | MQTT_CONNECT_UNAVAILABLE |
Sunucu kullanılamıyor | Broker aşırı yüklü veya yeniden başlıyor |
| 4 | MQTT_CONNECT_BAD_CREDENTIALS |
Hatalı kullanıcı adı/parola | Yazım hatası, değiştirilmiş parola |
| 5 | MQTT_CONNECT_UNAUTHORIZED |
Yetkisiz | Kimlik bilgileri bu client ID için izinli değil |
4 ve 5 kodları geçici değildir. Birkaç saniyede bir yeniden denemek bunları düzeltmez, yalnızca başarısız giriş sayısını artırır. ConnectMQ dahil pek çok broker tekrarlanan başarısız girişlerden sonra IP adreslerini engeller; bu yüzden hatalı kimlik bilgileriyle döngüye giren bir cihaz kendini dışarıda bırakabilir. Aşağıdaki firmware bu kodlarda çok daha uzun bekler. İlk denemeden itibaren -2 alıyorsanız sorun yeniden bağlanma değildir; önce ESP32'nin MQTT broker'a neden bağlanamadığı rehberini inceleyin.
Engellemeyen ESP32 MQTT Yeniden Bağlanma Tasarımı
Firmware üç katmanı birbirinden ayırır:
- Wi-Fi. Radyoyu ESP32'nin kendi otomatik yeniden bağlanma özelliğine bırakın ve bağlantının ne zaman kurulduğunu öğrenmek için Wi-Fi olaylarını dinleyin.
WiFi.begin()'i sıkı bir döngüde çağırmayın. - MQTT. Yalnızca Wi-Fi'ın bir IP adresi olduğunda ve backoff zamanlayıcısı dolduğunda bağlanmayı deneyin. Başarılı olursa backoff'u sıfırlayın, bir
onlinedurumu yayınlayın ve yeniden abone olun. Başarısız olursa gecikmeyi bir üst sınıra kadar ikiye katlayın ve rastgele jitter ekleyin. - Uygulama. Bağlantı durumundan bağımsız olarak her döngüde sensörleri okuyun ve aktüatörleri yönetin. Bağlıyken yayınlayın; değilse ölçümleri küçük bir halka tampona koyun ve yeniden bağlandıktan sonra boşaltın.
Jitter'lı üstel backoff, paylaşılan bir servise karşı yeniden denemenin standart kalıbıdır. Jitter beklenenden daha önemlidir: bölgesel bir kesintiden sonra binlerce yeniden bağlanmayı tek bir ani yük yerine bir zaman penceresine yayar.
Tam Sketch
#include <WiFi.h>
#include <PubSubClient.h>
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
String clientId; // MAC adresinden oluşturulur
String base; // connectmq/demo/<clientId>
WiFiClient net;
PubSubClient mqtt(net);
// ---- Backoff durumu ----
const uint32_t BACKOFF_MIN = 1000; // 1 sn
const uint32_t BACKOFF_MAX = 60000; // 60 sn
const uint32_t BACKOFF_AUTH = 300000; // kimlik bilgisi hataları için 5 dk
uint32_t backoff = BACKOFF_MIN;
unsigned long nextAttempt = 0;
volatile bool wifiUp = false;
uint32_t reconnects = 0;
// ---- Çevrimdışı halka tampon ----
const int BUF_SIZE = 20;
String pending[BUF_SIZE];
int head = 0, count = 0;
void onWiFiEvent(WiFiEvent_t event) {
switch (event) {
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
wifiUp = true;
Serial.printf("Wi-Fi up, IP %s\n", WiFi.localIP().toString().c_str());
break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
if (wifiUp) Serial.println("Wi-Fi lost");
wifiUp = false;
break;
default:
break;
}
}
void onMessage(char* topic, byte* payload, unsigned int length) {
Serial.printf("cmd on %s: %.*s\n", topic, (int)length, (const char*)payload);
}
void subscribeAll() {
// HER başarılı bağlantıdan sonra çağrılır: abonelikler
// clean-session yeniden bağlantısından sonra korunmaz.
mqtt.subscribe((base + "/cmd/#").c_str(), 1);
}
void flushPending() {
while (count > 0 && mqtt.connected()) {
int tail = (head - count + BUF_SIZE) % BUF_SIZE;
if (!mqtt.publish((base + "/telemetry").c_str(), pending[tail].c_str())) break;
count--;
mqtt.loop();
}
}
void scheduleRetry(int state) {
bool authError = (state == MQTT_CONNECT_BAD_CREDENTIALS || state == MQTT_CONNECT_UNAUTHORIZED);
uint32_t wait = authError ? BACKOFF_AUTH : backoff;
uint32_t jitter = random(0, wait / 4 + 1);
nextAttempt = millis() + wait + jitter;
if (!authError) backoff = min(backoff * 2, BACKOFF_MAX);
Serial.printf("MQTT connect failed (state %d), retry in %lu ms\n", state, (unsigned long)(wait + jitter));
}
void maintainMqtt() {
if (mqtt.connected()) {
mqtt.loop();
return;
}
if (!wifiUp || (long)(millis() - nextAttempt) < 0) return;
String status = base + "/status";
bool ok = mqtt.connect(clientId.c_str(), nullptr, nullptr,
status.c_str(), 1, true, "offline");
if (!ok) {
scheduleRetry(mqtt.state());
return;
}
reconnects++;
backoff = BACKOFF_MIN;
Serial.printf("MQTT connected (#%lu)\n", (unsigned long)reconnects);
mqtt.publish(status.c_str(), "online", true);
subscribeAll();
flushPending();
}
void publishReading(const String& json) {
if (mqtt.connected() && mqtt.publish((base + "/telemetry").c_str(), json.c_str())) return;
pending[head] = json; // çevrimdışıyken tamponla
head = (head + 1) % BUF_SIZE;
if (count < BUF_SIZE) count++; // dolduğunda en eski kaydın üzerine yazılır
}
void setup() {
Serial.begin(115200);
uint8_t mac[6];
WiFi.mode(WIFI_STA);
WiFi.macAddress(mac);
char id[24];
snprintf(id, sizeof(id), "esp32-%02x%02x%02x", mac[3], mac[4], mac[5]);
clientId = id;
base = "connectmq/demo/" + clientId;
randomSeed(esp_random());
WiFi.onEvent(onWiFiEvent);
WiFi.setAutoReconnect(true);
WiFi.persistent(false);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setCallback(onMessage);
mqtt.setKeepAlive(30);
mqtt.setSocketTimeout(5); // takılan bir bağlantının ne kadar engelleyebileceğini sınırlar
}
unsigned long lastSample = 0;
void loop() {
maintainMqtt();
// Uygulama işi bağlı olsun olmasın her zaman çalışır
if (millis() - lastSample >= 10000) {
lastSample = millis();
String json = "{\"uptime\":" + String(millis() / 1000) +
",\"rssi\":" + String(WiFi.RSSI()) +
",\"reconnects\":" + String(reconnects) + "}";
publishReading(json);
}
}
Önemli satırlarla ilgili notlar
- MAC adresinden client ID. Her kart, sketch'i düzenlemeye gerek kalmadan sabit ve benzersiz bir ID alır. Yinelenen ID'ler gizemli
-3döngülerinin bir numaralı nedenidir; çünkü broker yeni oturum bağlandığında eskisini düşürür. Portal kimlik bilgileri kullanıyorsanız portalın verdiği client ID'yi kullanın, çünkü ConnectMQ client ID'leri kimlik bilgilerine bağlar. - Her bağlantıdan sonra
subscribeAll(). PubSubClient clean session ile bağlanır; bu nedenle bağlantı koptuğunda broker abonelikleri unutur. Yeniden abone olmayı unutmak, klasik "yeniden bağlanıyor ama komut almayı bırakıyor" hatasıdır. - Retained
online/offlinedurumu. Last Will herkese cihazın düştüğünü bildirir; retainedonlinemesajı ise toparlanmadan sonra bunu düzeltir. ESP32 MQTT Last Will rehberi bu kalıbı ayrıntılı şekilde ele alır. setSocketTimeout(5). Engellemeyen bir tasarım bile gerçek bağlantı çağrısı sırasında engeller; çünkü PubSubClient'ta DNS sorgusu, TCP el sıkışması ve CONNACK beklemesi senkrondur. Zaman aşımını kısa tutmak bu duraklamayı sınırlar.- Taşmaya dayanıklı zamanlayıcı.
(long)(millis() - nextAttempt) < 0,millis()yaklaşık 49 gün sonra başa döndüğünde de çalışmaya devam eder.
Test Edin: Router'ı Yeniden Başlatın
Test etmediğiniz bir yeniden bağlanma stratejisi bir stratejiden çok bir umuttur. İşte on dakika süren bir test planı:
- Sketch'i yükleyin ve seri monitörü açın.
- Bilgisayarınızda cihazın topic'lerine abone olun (ID'yi seri monitörde yazanla değiştirin):
mosquitto_sub -h broker.connectmq.com -p 1883 -i watch-test01 -v -t "connectmq/demo/esp32-a1b2c3/#"
status onlinemesajını ve her on saniyede bir telemetri mesajını gördüğünüzü doğrulayın.- Router'ınızın gücünü kesip yeniden açın. Bilgisayarınız da bağlantısını kaybeder, bu yüzden bunun yerine seri monitörü izleyin.
- Keep-alive süresinin yaklaşık 1,5 katı içinde (burada 45 saniye) broker Last Will mesajını yayınlar. Dizüstü bilgisayarınız yeniden bağlandığında bunu göreceksiniz.
- Seri logu izleyin:
Wi-Fi lost, ardından router açılırken hiçbir şey (Wi-Fi kapalı olduğu için başarısız MQTT denemesi yok), ardındanWi-Fi up, ardındanMQTT connected (#2). - Abone tarafında
status onlinemesajının geri geldiğini, tamponlanan telemetrinin toplu halde ulaştığını vereconnectsdeğerinin bir arttığını doğrulayın. connectmq/demo/esp32-a1b2c3/cmd/testtopic'ine bir komut gönderin ve cihazın hâlâ aldığını doğrulayın. Bu, yeniden aboneliğin çalıştığını kanıtlar.
Testi, Wi-Fi açıkken broker'a ulaşılamadığı durumda da tekrarlayın; örneğin MQTT_HOST değerini geçici olarak kullanılmayan bir adla değiştirerek. Yeniden deneme aralıklarının yaklaşık bir saniyeden 60 saniyelik üst sınıra kadar büyüdüğünü, jitter nedeniyle de her seferinde biraz farklı değerler aldığını görmelisiniz.
Yeniden Bağlanma Sorunlarını Giderme
- -3 durumuyla birkaç saniyede bir yeniden bağlanıyor. Başka bir cihaz veya unutulmuş bir test istemcisi aynı client ID'yi kullanıyor.
- Bağlı, ama yeniden bağlandıktan sonra komutlar gelmiyor. Yeniden abonelik eksik.
- Zayıf sinyalde -4 durumu. Keep-alive süresini makul ölçüde artırın, anten yerleşimini iyileştirin ve gösterildiği gibi RSSI değerini loglayın.
- Cihaz uzun bir kesintiden sonra yeniden başlatılmadan asla toparlanmıyor. Bir şey sonsuza kadar engelliyor; çoğunlukla bir HTTP çağrısı veya bir sensör kütüphanesi. ESP32 task watchdog ile MQTT yazısında anlatıldığı gibi task watchdog ekleyin.
- Eski mesajların toplu gelişi dashboard'u şaşırtıyor. Tüketicilerin geç gelen veriyi canlı veriden ayırt edebilmesi için tamponlanan ölçümlere bir zaman damgası ekleyin.
Broker Tarafı Ne Zaman Önemlidir?
İyi bir firmware broker'a ulaşılamayabileceğini varsayar; ancak bunun ne sıklıkla olacağı broker'ın nerede çalıştığına bağlıdır. Ev sunucusundaki bir Mosquitto örneği elektrik kesintisinde, işletim sistemi güncellemesinde ve süresi dolan sertifikada çöker. Yönetilen bir MQTT broker bu dertleri servisi sizin için işleten bir ekibe devreder. ConnectMQ ayrıca $SYS/broker/connection/{clientId}/state topic'ini sunar; böylece bir backend bir cihazın bağlantı durumunu izleyebilir. Portalın canlı topic gezgini de test sırasında retained durum topic'lerinin online ve offline arasında değişmesini izlemenizi sağlar. Keep-alive, oturumlar ve Last Will hakkında arka plan bilgisi için bir MQTT broker'ın ne yaptığına dair genel bakış faydalı bir tamamlayıcıdır. Bu alıştırma için herkese açık MQTT test broker'ı ile test etmek yeterlidir; aynı firmware, ESP32 MQTT SSL/TLS yazısında gösterildiği gibi üç sabit ve istemci sınıfı değiştirilerek özel kimlik bilgilerine ve TLS'e taşınır. ESP32 yolculuğunun genelini görmek için ESP32 IoT eğitimine göz atın.
ConnectMQ ile Başlayın
app.connectmq.com adresinde kredi kartı gerekmeden ücretsiz bir Serverless broker oluşturun. Size özel kimlik bilgileri, 8883 portunda TLS ve her cihazın ne zaman bağlanıp bağlantısının kesildiğini gösteren bir portal elde edersiniz; yeniden bağlanma davranışını ince ayarlarken tam olarak ihtiyacınız olan da budur.
Sık Sorulan Sorular
ESP32 bir MQTT bağlantısını ne sıklıkla yeniden denemeli?
Yaklaşık bir saniyeyle başlayın ve her başarısızlıktan sonra bekleme süresini yaklaşık bir dakikalık bir üst sınıra kadar ikiye katlayın, buna rastgele jitter ekleyin. Kimlik bilgisi hatalarında çok daha uzun bekleyin veya durup sorunu raporlayın; çünkü yeniden denemeler başarılı olamaz.
ESP32 MQTT yeniden bağlanmasından sonra tekrar abone olmam gerekir mi?
PubSubClient ile evet. Clean session kullandığı için bağlantı sona erdiğinde broker abonelikleri siler. Her başarılı bağlantıdan sonra abonelik fonksiyonunuzu çağırın.
Wi-Fi koptuğunda WiFi.begin()'i tekrar çağırmalı mıyım?
Genellikle hayır. WiFi.setAutoReconnect(true) ile ESP32 kendi kendine yeniden bağlanır. WiFi.begin()'i tekrar tekrar çağırmak, zaten devam eden bir bağlantı kurma sürecini kesintiye uğratabilir.
ESP32'm neden -3 durumuyla sürekli bağlantıyı kaybediyor?
En yaygın neden iki istemcinin aynı client ID'yi paylaşmasıdır; diğeri her bağlandığında broker eskisini düşürür. Sonraki şüpheliler zayıf Wi-Fi ve bir NAT veya güvenlik duvarının boşta kalma zaman aşımından daha uzun olan keep-alive süresidir.



