Uygulamalı Eğitimler

PubSubClient ile ESP8266 MQTT Eğitimi

NodeMCU veya Wemos D1 mini'yi PubSubClient ile bir MQTT broker'a bağlayın: eksiksiz sketch, bloklamayan yeniden bağlanma, Last Will ve fingerprint ile TLS.

Güncellendi · 9 dk okuma

PubSubClient ile ESP8266 MQTT Eğitimi

ESP8266, bir sensörü Wi-Fi'ye taşımanın hâlâ en ucuz yollarından biridir ve PubSubClient, ESP8266 sketch'lerinin çoğunun kullandığı MQTT kütüphanesidir. Bu eğitim size NodeMCU veya Wemos D1 mini için eksiksiz bir sketch veriyor: bir MQTT broker'a bağlanıyor, telemetri publish ediyor, komut alıyor, bloklamadan yeniden bağlanıyor ve Last Will ile varlığını duyuruyor. Ardından bağlantıyı sertifika fingerprint'i ile TLS'e yükseltiyor ve bunun yerine ne zaman kök CA kullanmanız gerektiğini açıklıyor.

Test edilen kurulum: Arduino IDE 2.x veya arduino-cli, ESP8266 core 3.1.x, PubSubClient 2.8.

Araç zincirini kurun

  1. Arduino IDE'de Tercihler'i açın ve şu kart yöneticisi URL'sini ekleyin: https://arduino.esp8266.com/stable/package_esp8266com_index.json.
  2. Kart Yöneticisi → esp8266 by ESP8266 Community paketini kurun.
  3. Kütüphane Yöneticisi → Nick O'Leary'nin PubSubClient kütüphanesini kurun.
  4. Kart olarak NodeMCU 1.0 (ESP-12E Module) veya LOLIN(WEMOS) D1 R2 & mini seçin.

arduino-cli ile aynı adımlar şöyledir:

arduino-cli kurulumu
arduino-cli config add board_manager.additional_urls \
  https://arduino.esp8266.com/stable/package_esp8266com_index.json
arduino-cli core update-index
arduino-cli core install esp8266:esp8266
arduino-cli lib install PubSubClient

ESP8266 yalnızca 2,4 GHz Wi-Fi destekler. Router'ınız iki bant için tek bir ortak SSID yayınlıyorsa bu genellikle çalışır; yalnızca 5 GHz yayın yapan bir ağ asla çalışmaz.

Eksiksiz sketch

esp8266_mqtt.ino
#include <ESP8266WiFi.h>
#include <PubSubClient.h>

const char* WIFI_SSID = "your-ssid";
const char* WIFI_PASS = "your-wifi-password";
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* MQTT_USER = "username";
const char* MQTT_PASS = "password";

WiFiClient net;
PubSubClient mqtt(net);

String clientId, baseTopic;
unsigned long lastAttempt = 0, retryDelay = 1000, lastPublish = 0;

void onMessage(char* topic, byte* payload, unsigned int length) {
  String msg;
  for (unsigned int i = 0; i < length; i++) msg += (char)payload[i];
  Serial.printf("[%s] %s\n", topic, msg.c_str());
  if (msg == "on")  digitalWrite(LED_BUILTIN, LOW);    // dahili LED aktif-düşük çalışır
  if (msg == "off") digitalWrite(LED_BUILTIN, HIGH);
}

bool connectMqtt() {
  String status = baseTopic + "/status";
  // connect(id, user, pass, willTopic, willQos, willRetain, willMessage)
  if (!mqtt.connect(clientId.c_str(), MQTT_USER, MQTT_PASS,
                    status.c_str(), 1, true, "offline")) {
    Serial.printf("MQTT connect failed, state=%d\n", mqtt.state());
    return false;
  }
  mqtt.publish(status.c_str(), "online", true);              // retained
  mqtt.subscribe((baseTopic + "/led").c_str(), 1);
  Serial.println("MQTT connected as " + clientId);
  return true;
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);

  WiFi.persistent(false);              // her açılışta flash'a yeniden yazma
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);

  clientId  = "esp8266-" + String(ESP.getChipId(), HEX);
  baseTopic = "testmqtt/esp8266/" + clientId;

  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  mqtt.setCallback(onMessage);
  mqtt.setBufferSize(512);             // varsayılan, topic ve başlık dahil 256 bayttır
  mqtt.setKeepAlive(30);
  mqtt.setSocketTimeout(5);            // başarısız bir connect en fazla ~5 sn bloklar
}

void loop() {
  if (WiFi.status() != WL_CONNECTED) {
    delay(10);                         // core Wi-Fi'yi kendisi yeniden bağlar
    return;
  }

  if (!mqtt.connected()) {
    unsigned long now = millis();
    if (now - lastAttempt >= retryDelay) {
      lastAttempt = now;
      retryDelay = connectMqtt() ? 1000 : min(retryDelay * 2, 60000UL);
    }
    return;
  }

  mqtt.loop();                         // sık çalışmalı: mesajları alır, ping gönderir

  if (millis() - lastPublish >= 10000) {
    lastPublish = millis();
    char payload[96];
    snprintf(payload, sizeof(payload),
             "{\"rssi\":%d,\"heap\":%u,\"uptime\":%lu}",
             WiFi.RSSI(), ESP.getFreeHeap(), millis() / 1000);
    mqtt.publish((baseTopic + "/telemetry").c_str(), payload);
  }
}

Neden böyle yazıldı

  • Bloklamayan yeniden bağlanma. Klasik örnek while (!client.connected()) { ...; delay(5000); } kullanır; bu da broker'a ulaşılamadığı sürece butonlar, sensörler, hatta Wi-Fi yığını dahil her şeyi dondurur. Burada loop() hemen döner ve üstel backoff ile (1 sn, 2 sn, 4 sn … 60 sn'ye kadar) yeniden dener; böylece bir filo yeniden başlayan bir broker'ı bombardımana tutmaz.
  • ESP.getChipId()'den türetilen kalıcı client ID. Aynı ID'ye sahip iki kart birbirini sonsuz bir döngüde atar; client ID en iyi uygulamaları yazısına bakın.
  • Last Will + retained durum. Kartın gücü kesilirse broker, keep-alive süresi dolduğunda (burada 1,5 × 30 sn) .../status topic'ine offline publish eder. Sonradan abone olan herkes güncel durumu hemen görür.
  • Her bağlantıdan sonra abone olun. PubSubClient clean session kullanır, bu yüzden abonelikler yeniden bağlanmadan sonra korunmaz. broker.connectmq.com gibi paylaşımlı test uç noktaları zaten session saklamaz; bu nedenle sketch connectMqtt() içinde her seferinde yeniden abone olur.
  • setBufferSize(512): PubSubClient'ın varsayılan buffer'ı topic dahil paketin tamamı için 256 bayttır. Uzun bir topic artı bir JSON payload bunu hızla aşar ve publish() başka hiçbir ipucu vermeden false döndürür. Paket boyutu hesaplayıcı paketinizin gerçek boyutunu gösterir.
  • setSocketTimeout(5): connect()'in kendisi bloklayıcıdır. Varsayılan 15 saniyelik timeout, watchdog reset'lerini veya donmuş bir arayüzü tetiklemeye yeter.

Sketch'i test edin

Kartı flash'layın ve Seri Monitör'ü 115200 baud'da açın. MQTT connected as esp8266-xxxxxx görmelisiniz. Bunun yerine state=-2 veya başka bir kod görürseniz aşağıdaki hata tablosuna geçin.

Ardından online MQTT istemcisini açın, broker.connectmq.com adresine bağlanın ve testmqtt/esp8266/# topic'ine abone olun. Önce retained online durumu, ardından her 10 saniyede bir telemetri JSON'u gelir. testmqtt/esp8266/<client-id>/led topic'ine on publish edin; LED yanar.

ESP8266'nızı tarayıcıda izleyin

Last Will'i test etmek için kartın fişini çekin ve yaklaşık 45 saniye bekleyin: durum topic'i offline olur. Kartı tekrar takın, online'a döner; yeniden bağlanma yolunuz çalışıyor demektir.

Fingerprint ile TLS eklemek

1883 portu MQTT şifresini düz metin olarak gönderir. ESP8266 core'u BearSSL içerir, dolayısıyla 8883 portunda TLS için yalnızca farklı bir istemci sınıfı gerekir. Sunucuyu doğrulamanın en hafif yolu SHA-1 fingerprint ile sertifika sabitleme (pinning)'dir.

Broker'ın güncel sertifikasının fingerprint'ini bilgisayarınızdan alın:

SHA-1 fingerprint'i okuyun
openssl s_client -connect broker.connectmq.com:8883 \
  -servername broker.connectmq.com </dev/null 2>/dev/null \
  | openssl x509 -noout -fingerprint -sha1 \
  | sed 's/.*=//; s/:/ /g'

Ardından sketch'te üç şeyi değiştirin:

TLS değişiklikleri
#include <WiFiClientSecure.h>

const uint16_t MQTT_PORT = 8883;
const char* FINGERPRINT = "AA BB CC DD EE FF 00 11 22 33 44 55 66 77 88 99 AA BB CC DD";

BearSSL::WiFiClientSecure net;         // WiFiClient'ın yerine geçer
PubSubClient mqtt(net);

// setup() içinde, mqtt.setServer()'dan önce:
net.setFingerprint(FINGERPRINT);
mqtt.setSocketTimeout(10);             // 80 MHz'lik bir çipte TLS el sıkışması yavaştır

El sıkışma, düz bir TCP bağlantısından belirgin şekilde uzun sürer ve ESP8266'nın sınırlı RAM'inin önemli bir kısmını kullanır. Telemetride ESP.getFreeHeap() değerini izleyin; bellek daralırsa BearSSL net.setBufferSizes(1024, 1024) ile daha küçük buffer'lar kullanabilir, ancak yalnızca broker TLS max fragment length uzantısını destekliyorsa. Önce BearSSL::WiFiClientSecure::probeMaxFragmentLength(MQTT_HOST, 8883, 1024) ile kontrol edin.

Fingerprint mi, kök CA mı?

YöntemNasılArtı / eksi
setFingerprint()Sunucu sertifikasının SHA-1'ini sabitlerEn küçük ve en hızlı yöntem, ama her sertifika yenilemesinde bozulur
setTrustAnchors()Kök CA'yı sabitler (BearSSL::X509List)Yenilemelerden etkilenmez; configTime() ile doğru saat gerektirir
setInsecure()Doğrulama yokŞifreli ama her sunucuyu kabul eder; yalnızca hata ayıklama için

Genel CA'ların sertifikaları düzenli olarak, çoğu zaman birkaç ayda bir yenilenir. Fingerprint sabitlenmiş bir filo, yenileme gününde state=-2 ile bağlanmayı bırakır ve her cihazı yeniden flash'lamanız gerekir. Uzun ömürlü her şey için bunun yerine kök CA'yı sabitleyin:

Fingerprint yerine kök CA
#include <time.h>
static const char ROOT_CA[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
...broker'ınızın sertifikasını imzalayan CA'nın kök sertifikası...
-----END CERTIFICATE-----
)EOF";
BearSSL::X509List trustAnchor(ROOT_CA);

// setup() içinde, Wi-Fi bağlandıktan sonra:
configTime(0, 0, "pool.ntp.org", "time.nist.gov");
while (time(nullptr) < 1700000000) delay(200);   // NTP'yi bekle
net.setTrustAnchors(&trustAnchor);

Geçerli bir saat olmadan BearSSL her sertifikayı süresi dolmuş veya henüz geçerli değil sayar. İstemci sertifikaları dahil TLS ile ilgili daha fazla ayrıntı ESP32 TLS rehberinde; BearSSL API'si farklı olsa da kavramlar aynıdır.

Hata kodları

mqtt.state() şu değerleri döndürür:

stateSabitTipik sebep
-4MQTT_CONNECTION_TIMEOUTKeep-alive süresi içinde ping yanıtı yok; zayıf Wi-Fi
-3MQTT_CONNECTION_LOSTAğ koptu veya broker socket'i kapattı
-2MQTT_CONNECT_FAILEDTCP/TLS başarısız: yanlış host veya port, DNS, firewall, eskimiş fingerprint
-1MQTT_DISCONNECTEDTemiz bağlantı kesme veya hiç bağlanmamış
1MQTT_CONNECT_BAD_PROTOCOLBroker MQTT 3.1.1'i reddediyor
2MQTT_CONNECT_BAD_CLIENT_IDClient ID reddedildi
3MQTT_CONNECT_UNAVAILABLEBroker kullanılamıyor
4MQTT_CONNECT_BAD_CREDENTIALSYanlış kullanıcı adı veya şifre
5MQTT_CONNECT_UNAUTHORIZEDKimlik doğrulandı ama bağlanma izni yok

Pozitif değerler broker'dan gelen CONNACK dönüş kodlarıdır; bunları reason code aracında arayın. Negatif değerler kütüphanenin kendisinden gelir ve neredeyse her zaman bir ağ sorununa işaret eder. ESP32 broker bağlantısını test etme yazısındaki adım adım yöntem ESP8266'da da aynen çalışır.

Sonraki adımlar

  • Daha yeni bir çipe mi geçiyorsunuz? ESP32 Arduino eğitimi aynı PubSubClient API'sini kullanır.
  • QoS 1 publish veya farklı bir API tarzı mı gerekiyor? arduino-mqtt kütüphanesi ile karşılaştırın.
  • Daha büyük payload'lar mı? MQTT ile ArduinoJson buffer'ları doğru boyutlandırmayı gösteriyor.

Güvenilir bir ESP8266 MQTT düğümü birkaç alışkanlığa dayanır: loop()'u asla bloklamayın, backoff ile yeniden bağlanın, her bağlantıda yeniden abone olun, buffer'ı gerçek paketlerinize göre boyutlandırın ve bir sonraki sertifika yenilemesinden sonra da çalışması gereken cihazlarda fingerprint yerine kök CA sabitleyin.

Sıkça sorulan sorular

ESP8266'mın TLS bağlantısı neden birkaç hafta sonra çalışmayı bırakıyor?

Sertifikayı setFingerprint() ile sabitlediyseniz, broker sertifikasını her yenilediğinde fingerprint değişir ve el sıkışma state -2 ile başarısız olur. Bunun yerine kök CA'yı setTrustAnchors() ile sabitleyin; yeni sertifikayı aynı CA imzaladığı sürece yenilemelerden etkilenmez.

PubSubClient, ESP8266'da QoS 1 ile publish edebilir mi?

Hayır. PubSubClient yalnızca QoS 0 ile publish eder. QoS 0 veya 1 ile abone olabilir, yani gelen mesajlar onaylanabilir; ancak giden mesajlar fire-and-forget'tır. QoS 1 publish gerekiyorsa arduino-mqtt gibi bir kütüphane kullanın veya uygulama seviyesinde bir onay mekanizması ekleyin.

PubSubClient state -2 ne anlama gelir?

MQTT_CONNECT_FAILED: broker'a TCP veya TLS bağlantısı açılamamıştır. Sorun MQTT'nin altındadır: Wi-Fi, DNS, yanlış port, bir firewall ya da eskimiş bir fingerprint veya 8883 portunda düz WiFiClient kullanımı gibi bir TLS el sıkışma hatası.