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
- 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.
- İhtiyacınız olan kart paketini kurun: ESP32 kartları için "esp32 by Espressif Systems", NodeMCU tarzı kartlar için "esp8266 by ESP8266 Community".
- 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.
- 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()fonksiyonunuconnect()öncesinde çağırın ve dönüş değerini kontrol edin: bellek ayırma başarısız olursafalsedö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
- 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.
publish()false döndürüyor: payload artı topic buffer'ı aşıyor veya istemci bağlı değil.- Callback hiç tetiklenmiyor:
mqtt.loop()çağrısını unuttunuz, abonelik bağlantı kurulmadan önce yapıldı veya mesaj buffer'dan büyük. - 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. - 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.



