ESP32 ve Arduino

PubSubClient Arduino Kütüphanesi Eğitimi: Kurulum, Callback'ler, Buffer ve Keep Alive

ESP32 ve ESP8266 için pratik bir PubSubClient eğitimi: kütüphaneyi kurun, bir MQTT broker'a bağlanın, callback'leri yönetin, buffer boyutunu ayarlayın, keep alive değerini optimize edin ve gerçek kimlik bilgileriyle TLS'e geçin.

PubSubClient kütüphanesini çalıştıran ve bir bulut broker ile MQTT mesajları alışverişi yapan mikrodenetleyici 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

PubSubClient, Arduino uyumlu kartlar için en yaygın kullanılan MQTT kütüphanesidir ve ağ yığını olan neredeyse her cihazda çalışacak kadar küçüktür. Bu eğitimde onu kuracak, bir ESP32'yi gerçek bir broker'a bağlayacak, yayın yapıp abone olacak ve sahadaki sorunların çoğuna yol açan üç ayarı öğreneceksiniz: buffer boyutu, keep alive ve soket zaman aşımı. Sonunda her projede yeniden kullanabileceğiniz, yeniden bağlanmaya dayanıklı bir sketch'e sahip olacaksınız.

PubSubClient Nedir (ve Ne Değildir)

Nick O'Leary tarafından yazılan PubSubClient, hafif bir MQTT 3.1.1 istemcisidir. Ağ bağlantısının sahibi değildir. Bunun yerine Arduino'nun Client arayüzünü uygulayan herhangi bir nesneyi sarmalar: ESP32 ve ESP8266'da WiFiClient, kablolu bir shield'da EthernetClient veya TLS istediğinizde WiFiClientSecure. Aynı kütüphanenin bu kadar çok kartta çalışmasının nedeni bu tasarımdır.

Ayrıca bilinçli olarak minimal tutulmuştur. Sınırları baştan bilmek saatlerce hata ayıklamadan kurtarır:

Özellik PubSubClient davranışı
Protokol sürümü MQTT 3.1.1 (derleme zamanında isteğe bağlı MQTT 3.1), MQTT 5 yok
Yayın QoS Yalnızca QoS 0
Abonelik QoS QoS 0 veya QoS 1
Varsayılan paket buffer'ı 256 bayt (topic + payload + başlık)
Varsayılan keep alive 15 saniye
Varsayılan soket zaman aşımı 15 saniye
Last Will connect() üzerinde desteklenir
Retained yayın Desteklenir
İş parçacığı modeli Tek iş parçacıklı, loop() fonksiyonunu sık çağırmanız gerekir

Cihazdan MQTT 5 özelliklerine veya QoS 2 yayınına ihtiyacınız varsa farklı bir istemciye bakın. Sensörlerin ve aktüatörlerin büyük çoğunluğu için QoS 0 telemetri artı QoS 1 komut abonelikleri tam olarak istediğiniz şeydir. MQTT QoS seviyeleri rehberimiz bu kombinasyonun neden iyi çalıştığını açıklıyor.

PubSubClient'ı Kurun ve Kartı Hazırlayın

  1. Arduino IDE'de Tools → Manage Libraries menüsünü açın, "PubSubClient" araması yapın ve Nick O'Leary'nin kütüphanesini (sürüm 2.8 veya daha yenisi) kurun.
  2. İhtiyacınız olan kart paketini kurun: ESP32 kartları için "esp32 by Espressif Systems", NodeMCU tarzı kartlar için "esp8266 by ESP8266 Community".
  3. Kartınızı ve portunuzu seçin, ardından araç zincirinin çalıştığını doğrulamak için boş Blink sketch'ini bir kez yükleyin.
  4. Benzersiz bir cihaz ID'si seçin, örneğin pubsub-7f3a9c. Bunu hem MQTT client ID'si hem de topic öneki olarak kullanacaksınız.

İlk test için ConnectMQ tarafından işletilen ücretsiz herkese açık MQTT broker olan broker.connectmq.com adresinin herkese açık alanını kullanacaksınız. Client ID boş olmadığı sürece 1883 portunda anonim bağlantıları kabul eder. Orada yayınlanan her şey herkes tarafından okunabilir; bu yüzden ona asla gerçek kimlik bilgileri veya kişisel veriler göndermeyin.

Eksiksiz PubSubClient Örneği

Aşağıdaki sketch Wi-Fi'a bağlanır, bir Last Will ile broker'a bağlanır, her beş saniyede bir JSON mesajı yayınlar, bir komut topic'ine abone olur ve kodunuzun geri kalanını bloke etmeden yeniden bağlanır.

#include <WiFi.h>          // use <ESP8266WiFi.h> on ESP8266
#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;
const char* DEVICE_ID = "pubsub-7f3a9c";   // make this unique

char topicTelemetry[64];
char topicCommand[64];
char topicStatus[64];

WiFiClient netClient;
PubSubClient mqtt(netClient);

unsigned long lastPublish = 0;
unsigned long lastReconnectAttempt = 0;

void onMessage(char* topic, byte* payload, unsigned int length) {
  // payload is NOT null-terminated: copy it with the length
  char msg[64];
  unsigned int n = length < sizeof(msg) - 1 ? length : sizeof(msg) - 1;
  memcpy(msg, payload, n);
  msg[n] = '\0';

  Serial.printf("Message on %s: %s\n", topic, msg);

  if (strcmp(msg, "ping") == 0) {
    mqtt.publish(topicStatus, "pong");
  }
}

void connectWifi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Connecting to Wi-Fi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.printf("\nIP address: %s\n", WiFi.localIP().toString().c_str());
}

bool connectMqtt() {
  // connect(clientId, user, pass, willTopic, willQos, willRetain, willMessage)
  bool ok = mqtt.connect(DEVICE_ID, nullptr, nullptr,
                         topicStatus, 1, true, "offline");
  if (ok) {
    mqtt.publish(topicStatus, "online", true);
    mqtt.subscribe(topicCommand, 1);
    Serial.println("MQTT connected");
  } else {
    Serial.printf("MQTT connect failed, rc=%d\n", mqtt.state());
  }
  return ok;
}

void setup() {
  Serial.begin(115200);

  snprintf(topicTelemetry, sizeof(topicTelemetry), "connectmq/demo/%s/telemetry", DEVICE_ID);
  snprintf(topicCommand,   sizeof(topicCommand),   "connectmq/demo/%s/cmd", DEVICE_ID);
  snprintf(topicStatus,    sizeof(topicStatus),    "connectmq/demo/%s/status", DEVICE_ID);

  connectWifi();

  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  mqtt.setCallback(onMessage);
  mqtt.setBufferSize(512);   // room for larger JSON payloads
  mqtt.setKeepAlive(30);     // seconds
  mqtt.setSocketTimeout(10); // seconds
}

void loop() {
  if (WiFi.status() != WL_CONNECTED) {
    connectWifi();
  }

  if (!mqtt.connected()) {
    unsigned long now = millis();
    if (now - lastReconnectAttempt > 5000) {
      lastReconnectAttempt = now;
      connectMqtt();
    }
    return;
  }

  mqtt.loop();

  if (millis() - lastPublish > 5000) {
    lastPublish = millis();
    char payload[96];
    snprintf(payload, sizeof(payload),
             "{\"uptime\":%lu,\"rssi\":%d,\"heap\":%u}",
             millis() / 1000, WiFi.RSSI(), ESP.getFreeHeap());
    if (!mqtt.publish(topicTelemetry, payload)) {
      Serial.println("Publish failed");
    }
  }
}

Çıktıyı bilgisayarınızdan Mosquitto komut satırı araçlarıyla izleyin:

mosquitto_sub -h broker.connectmq.com -p 1883 -i watcher-7f3a9c -t "connectmq/demo/pubsub-7f3a9c/#" -v

Ardından karta bir komut gönderin:

mosquitto_pub -h broker.connectmq.com -p 1883 -i sender-7f3a9c -t "connectmq/demo/pubsub-7f3a9c/cmd" -m "ping"

Bir saniye içinde status topic'inde pong mesajının göründüğünü görmelisiniz.

PubSubClient Callback'leri Nasıl Çalışır

Callback imzası sabittir: void callback(char* topic, byte* payload, unsigned int length). Üç ayrıntı önemlidir.

Birincisi, payload kütüphanenin dahili buffer'ına işaret eden bir pointer'dır ve null ile sonlandırılmamıştır. Onu Serial.println((char*)payload) ile bir C dizesi gibi kullanmak, gerçek mesajdan sonra anlamsız karakterler yazdırır. Örnekte olduğu gibi her zaman length kullanarak kopyalayın.

İkincisi, callback mqtt.loop() içinde çalışır. Callback içinden yayın yaparsanız kütüphane aynı buffer'ı yeniden kullanır; dolayısıyla orijinal topic ve payload pointer'ları sonrasında artık geçerli değildir. publish() çağırmadan önce ihtiyacınız olan her şeyi kopyalayın.

Üçüncüsü, callback'leri kısa tutun. Callback içindeki bir delay(2000), loop() fonksiyonunun bağlantıya hizmet etmesini engeller ve bir keep alive zaman aşımını tetikleyebilir. Bir bayrak ayarlayın veya değeri saklayın, ardından ana döngünüzde buna göre işlem yapın.

Buffer Boyutu: En Yaygın PubSubClient Tuzağı

Sürüm 2.8'den itibaren kütüphane başlık dosyasındaki MQTT_MAX_PACKET_SIZE değerini düzenlemek yerine çalışma zamanında setBufferSize() çağırabilirsiniz. Buffer, MQTT paketinin tamamını barındırmalıdır: sabit başlık, topic uzunluğu, topic ve payload. 256 baytlık varsayılan değerle 40 karakterlik bir topic üzerindeki 220 karakterlik bir JSON belgesi basitçe başarısız olur ve publish() başka hiçbir açıklama yapmadan false döndürür. Çok büyük gelen mesajlar ise callback'iniz hiç çağrılmadan sessizce atılır.

Pratik kurallar:

  • Buffer'ı en büyük topic artı payload'ınıza, artı yaklaşık 10 baytlık başlığa göre boyutlandırın.
  • setBufferSize() fonksiyonunu connect() öncesinde çağırın ve dönüş değerini kontrol edin: bellek ayırma başarısız olursa false döndürür.
  • ESP8266'da buffer'ı makul tutun, çünkü RAM TLS ve Wi-Fi ile paylaşılır.

beginPublish() ile büyük payload'ları akış halinde göndermek dahil ayrıntıları PubSubClient buffer boyutu: büyük mesaj hatalarını düzeltme yazısında ele alıyoruz.

Keep Alive ve Soket Zaman Aşımı

Keep alive, broker'a istemcinin ne kadar süre sessiz kalabileceğini söyler. PubSubClient, aralık boyunca başka bir şey gönderilmemişse bir PINGREQ gönderir; ancak bunu yalnızca loop() çağrılıyorsa yapar. Kodunuz keep alive aralığının kabaca bir buçuk katından uzun süre bloke olursa broker oturumu düşürür ve Last Will mesajınız tetiklenir.

  • setKeepAlive(30), iyi bir Wi-Fi bağlantısındaki şebekeden beslenen cihazlar için makul bir varsayılandır.
  • Her paketin enerji veya veriye mal olduğu hücresel bağlantılarda daha yüksek değerlere (60 ila 120 saniye) çıkın.
  • setSocketTimeout(10), connect() ve okuma işlemlerinin ne kadar süre bloke olabileceğini sınırlar. Daha düşük değerler, kararsız bir ağın cihazınızı her seferinde 15 saniye dondurmasını önler.

Bağlantı Durumunu Okumak

connect() başarısız olduğunda mqtt.state() nedenini söyler. Negatif değerler ağ düzeyindeki sorunlardır; pozitif değerler ise broker'ın reddetmeleridir.

state() Anlamı
-4 Bağlantı zaman aşımı, CONNACK zamanında gelmedi
-3 Bağlantı kaybedildi
-2 Ağ bağlantısı başarısız (DNS, port, TLS, yönlendirme)
-1 Bağlantı düzgün şekilde kesildi
0 Bağlı
1 Hatalı protokol sürümü
2 Client ID reddedildi
3 Sunucu kullanılamıyor
4 Hatalı kullanıcı adı veya parola
5 Yetkisiz

Açık ara en yaygın olanı -2'dir. PubSubClient rc=-2 ve diğer durum kodları hakkındaki özel rehberimiz her değeri çözümleriyle birlikte ele alıyor.

TLS'e ve Gerçek Kimlik Bilgilerine Geçiş

Herkese açık broker öğrenmek için mükemmeldir, ancak üretimdeki cihazların şifrelemeye ve kendi kimlik bilgilerine ihtiyacı vardır. ConnectMQ'nun sunduğu yönetilen bir MQTT broker ile portalda bir broker oluşturur ve otomatik oluşturulan bir kullanıcı adı, parola ve client ID alırsınız. Sketch'teki değişiklik küçüktür:

#include <WiFiClientSecure.h>

WiFiClientSecure netClient;   // replaces WiFiClient
PubSubClient mqtt(netClient);

// ISRG Root X1, the Let's Encrypt root used by broker.connectmq.com
const char* ROOT_CA = R"EOF(
-----BEGIN CERTIFICATE-----
...paste ISRG Root X1 PEM here...
-----END CERTIFICATE-----
)EOF";

void setup() {
  // ... Wi-Fi as before ...
  netClient.setCACert(ROOT_CA);
  mqtt.setServer("broker.connectmq.com", 8883);
}

bool connectMqtt() {
  return mqtt.connect("YOUR_CLIENT_ID", "YOUR_USERNAME", "YOUR_PASSWORD",
                      topicStatus, 1, true, "offline");
}

ConnectMQ her client ID'yi verdiği kimlik bilgilerine bağlar; bu nedenle portaldaki client ID'nin tam olarak aynısını kullanın. Özel bir broker'da topic öneklerini dilediğiniz gibi tasarlayabilirsiniz; herkese açık connectmq/demo/... önekine artık gerek yoktur. Daha fazla güçlendirme fikri için MQTT güvenliği en iyi uygulamaları yazısına bakın.

Sorun Giderme Kontrol Listesi

  1. Bağlanıyor, ardından birkaç saniyede bir kopuyor: iki cihaz aynı client ID'yi paylaşıyor. Broker, ikincisi her bağlandığında eski oturumu sonlandırır.
  2. publish() false döndürüyor: payload artı topic buffer'ı aşıyor veya istemci bağlı değil.
  3. Callback hiç tetiklenmiyor: mqtt.loop() çağrısını unuttunuz, abonelik bağlantı kurulmadan önce yapıldı veya mesaj buffer'dan büyük.
  4. Yük altında rastgele kopmalar: loop() içinde uzun süre bloke eden kod; onu parçalara ayırın veya bir zamanlayıcıya taşıyın.
  5. 1883'te çalışıyor, 8883'te başarısız oluyor: yanlış kök sertifika veya geçerlilik tarihlerini kontrol eden kartlarda saatin ayarlanmamış olması (ESP8266'da yaygındır).

Kütüphaneler arasında seçim yapıyorsanız ArduinoMqttClient kütüphanesi rehberi ve ESP32 için async MQTT istemcisi başlıca alternatifleri ele alıyor; ESP32 IoT eğitimi ise tüm parçaları eksiksiz bir sensör projesinde bir araya getiriyor.

PubSubClient Cihazlarınızı ConnectMQ'ya Bağlayın

Sketch'iniz herkese açık broker'a karşı çalıştıktan sonra onu özel bir bulut MQTT broker hizmetine taşımak iki satırlık bir değişikliktir. ConnectMQ size TCP üzerinden MQTT, 8883 portunda TLS ve WebSocket, test için bir tarayıcı MQTT istemcisi, canlı bir topic gezgini ve eşik tabanlı uyarı kuralları sunar. app.connectmq.com adresinde kredi kartı gerektirmeden ücretsiz bir Serverless broker oluşturun; birkaç dakika içinde kimlik bilgilerinize sahip olursunuz.

Sık Sorulan Sorular

PubSubClient MQTT 5'i destekliyor mu?

Hayır. PubSubClient, ConnectMQ dahil tüm yaygın broker'ların MQTT 5 ile birlikte hâlâ desteklediği MQTT 3.1.1'i konuşur. Cihazda MQTT 5 özelliklerine ihtiyacınız varsa farklı bir istemci kütüphanesi seçin.

PubSubClient QoS 1 ile yayın yapabilir mi?

Hayır. publish() her zaman QoS 0 gönderir. QoS 1 ile abone olabilirsiniz; böylece cihaza QoS 1 ile gönderilen komutlar broker tarafından güvenilir şekilde iletilir.

PubSubClient payload'ım neden tuhaf karakterler yazdırıyor?

Payload buffer'ı null ile sonlandırılmamıştır. Bir dize olarak kullanmadan önce length bayt kadar veriyi kendi buffer'ınıza kopyalayın ve sonuna bir \0 sonlandırıcı ekleyin.

PubSubClient'ta maksimum mesaj boyutu nedir?

Buffer boyutu 16 bitlik bir değerdir; dolayısıyla pratik üst sınır 64 KB'ın biraz altındadır, ancak asıl sınır RAM'dir. Kısıtlı kartlarda mesajları küçük tutun ve broker planınızın da bir maksimum mesaj boyutu olduğunu unutmayın.

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