ESP32, ArduinoJson ve MQTT'yi birleştirmek, bir mikrodenetleyiciden yapılandırılmış sensör verisi göndermenin en yaygın yoludur. Bu eğitimde ArduinoJson 7 ile JSON telemetri oluşturacak, bunu PubSubClient ile verimli şekilde yayınlayacak, mesaj callback'inde JSON komutlarını ayrıştıracak ve mesajların hiçbir hata vermeden kaybolmasına neden olan tampon sınırlarını öğreneceksiniz.
MQTT İçin Neden JSON Payload'ları?
MQTT bir payload'ın içinde ne olduğuyla ilgilenmez; baytları taşır. Bu özgürlük bir format seçmeniz gerektiği anlamına gelir. Çoğu ESP32 projesinde JSON kazanır, çünkü her dashboard, Node-RED akışı, Python betiği ve veritabanı içe aktarıcısı onu özel bir kod çözücü olmadan okuyabilir. {"temp":22.4,"hum":51,"rssi":-63} gibi bir mesaj kendini açıklar, yeni alanlarla kolayca genişletilebilir ve hata ayıklarken herhangi bir MQTT istemcisinde okunabilir.
Maliyetleri boyut ve ayrıştırma işidir; tipik sensör hızlarında bir ESP32'de ikisi de ihmal edilebilir düzeydedir. Saniyede yüzlerce mesaj gönderiyorsanız veya sıkı bir pil bütçesiyle çalışıyorsanız, format değiştirmekten çok kompakt anahtarlar işe yarar.
ArduinoJson bu iş için fiili standart kütüphanedir. Sürüm 7, belge kapasitesi seçme ihtiyacını ortadan kaldırdı: JsonDocument heap üzerinde gerektiği kadar büyür. Aşağıdaki tüm kodlar ArduinoJson 7'yi hedefler; sürüm 6 kullanıyorsanız JsonDocument yerine StaticJsonDocument<N> veya DynamicJsonDocument kullanın. Resmi ArduinoJson dokümantasyonu geçiş ayrıntılarını ele alır.
İhtiyacınız Olanlar
- Bir ESP32 kartı ve Arduino IDE veya PlatformIO.
- Kütüphaneler: Nick O'Leary'nin PubSubClient'ı ve Benoit Blanchon'un ArduinoJson'ı (7.x).
- Komut göndermek ve çıktıyı izlemek için bilgisayarınızda
mosquitto_pubvemosquitto_sub.
Örnekler, connectmq/demo/a8e2d1 topic önekiyle 1883 portunda broker.connectmq.com'un public alanını kullanır. Bu öneki kendi rastgele dizginizle değiştirin ve public topic'lerin herkes tarafından okunabildiğini unutmayın.
| Topic | Yön | Payload |
|---|---|---|
connectmq/demo/a8e2d1/telemetry |
ESP32 yayınlar | {"device":"...","temp":22.4,"hum":51,"uptime":120} |
connectmq/demo/a8e2d1/cmd |
ESP32 abone olur | {"action":"set_interval","value":10} |
connectmq/demo/a8e2d1/ack |
ESP32 yayınlar | {"ok":true,"action":"set_interval"} |
Eksiksiz ESP32 ArduinoJson MQTT Sketch'i
Sketch, ayarlanabilir bir aralıkta telemetri yayınlar ve bu aralığı değiştirmek veya bir LED'i açıp kapatmak için JSON komutlarını kabul eder.
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_WIFI_PASSWORD";
const char* TOPIC_TELEMETRY = "connectmq/demo/a8e2d1/telemetry";
const char* TOPIC_CMD = "connectmq/demo/a8e2d1/cmd";
const char* TOPIC_ACK = "connectmq/demo/a8e2d1/ack";
const int LED_PIN = 2;
WiFiClient net;
PubSubClient mqtt(net);
String deviceId;
unsigned long intervalMs = 5000;
unsigned long lastPublish = 0;
void publishAck(const char* action, bool ok, const char* error = nullptr) {
JsonDocument doc;
doc["ok"] = ok;
doc["action"] = action;
if (error) doc["error"] = error;
char buf[128];
size_t n = serializeJson(doc, buf, sizeof(buf));
mqtt.publish(TOPIC_ACK, (const uint8_t*)buf, n, false);
}
void onMessage(char* topic, byte* payload, unsigned int length) {
JsonDocument doc;
DeserializationError err = deserializeJson(doc, payload, length);
if (err) {
publishAck("unknown", false, err.c_str());
return;
}
const char* action = doc["action"] | "";
if (strcmp(action, "set_interval") == 0) {
int seconds = doc["value"] | 0;
if (seconds < 1 || seconds > 3600) {
publishAck(action, false, "value out of range");
return;
}
intervalMs = seconds * 1000UL;
publishAck(action, true);
} else if (strcmp(action, "led") == 0) {
bool on = doc["value"] | false;
digitalWrite(LED_PIN, on ? HIGH : LOW);
publishAck(action, true);
} else {
publishAck(action, false, "unknown action");
}
}
void publishTelemetry() {
JsonDocument doc;
doc["device"] = deviceId;
doc["temp"] = serialized(String(20.0 + random(0, 50) / 10.0, 1));
doc["hum"] = 45 + random(0, 15);
doc["uptime"] = millis() / 1000;
JsonObject network = doc["net"].to<JsonObject>();
network["rssi"] = WiFi.RSSI();
network["ip"] = WiFi.localIP().toString();
size_t len = measureJson(doc);
mqtt.beginPublish(TOPIC_TELEMETRY, len, false);
serializeJson(doc, mqtt); // stream straight into the MQTT packet
mqtt.endPublish();
}
void connectMqtt() {
while (!mqtt.connected()) {
if (mqtt.connect(deviceId.c_str())) {
mqtt.subscribe(TOPIC_CMD);
} else {
Serial.printf("MQTT failed, state=%d\n", mqtt.state());
delay(3000);
}
}
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) delay(250);
deviceId = "esp32-json-" + String((uint32_t)ESP.getEfuseMac(), HEX);
mqtt.setServer("broker.connectmq.com", 1883);
mqtt.setBufferSize(1024); // default is 256 bytes, too small for most JSON
mqtt.setCallback(onMessage);
}
void loop() {
if (!mqtt.connected()) connectMqtt();
mqtt.loop();
if (millis() - lastPublish >= intervalMs) {
lastPublish = millis();
publishTelemetry();
}
}
Çıktıyı izleyin ve bilgisayarınızdan komut gönderin:
mosquitto_sub -h broker.connectmq.com -p 1883 -i pc-a8e2d1 -t 'connectmq/demo/a8e2d1/#' -v
mosquitto_pub -h broker.connectmq.com -p 1883 -i pc-a8e2d1-pub \
-t connectmq/demo/a8e2d1/cmd -m '{"action":"set_interval","value":10}'
mosquitto_pub -h broker.connectmq.com -p 1883 -i pc-a8e2d1-pub \
-t connectmq/demo/a8e2d1/cmd -m '{"action":"led","value":true}'
Serileştirme: JSON'u Bir Yayına Aktarmanın Üç Yolu
Sabit Bir Tampona
serializeJson(doc, buf, sizeof(buf)) bir char dizisine yazar ve yazılan bayt sayısını döndürür. Bu en basit yöntemdir ve yukarıdaki onay mesajı gibi küçük, öngörülebilir mesajlar için iyi çalışır. strlen()'e güvenmek yerine, bayt işaretçisi ve uzunluk alan overload'u kullanarak döndürülen uzunluğu publish()'e verin.
MQTT İstemcisine Akış Olarak
PubSubClient, Arduino'nun Print sınıfından türetilir; bu yüzden ArduinoJson doğrudan ona yazabilir. Önce measureJson(doc) tam boyutu verir, beginPublish() bu boyutla MQTT başlığını yazar, serializeJson(doc, mqtt) gövdeyi akış olarak gönderir ve endPublish() paketi tamamlar. Ara tampona gerek yoktur; bu, büyük belgeler için bellek açısından en verimli seçenektir. Normal publish() çağrıları ve her zaman tampona okunan gelen mesajlar için yine de setBufferSize()'a ihtiyacınız vardır.
Bir String'e
serializeJson(doc, someString) kullanışlıdır ancak her mesaj için heap'te bellek ayırır. Haftalarca çalışan bir cihazda tekrarlanan String ayırmaları belleği parçalayabilir. Uzun süre çalışan firmware'de ilk iki seçeneği tercih edin.
Float Hassasiyetini Kontrol Etmek
ArduinoJson float'ları geri dönüşümü sağlayacak kadar basamakla yazdırır; bu da 22.399999 gibi değerler verebilir. Biçimlendirilmiş bir dizgiyi serialized() ile sarmalamak, yukarıdaki telemetri fonksiyonunda olduğu gibi onu olduğu gibi ekler. Alternatif olarak sayıyı atamadan önce yuvarlayın: doc["temp"] = round(t * 10) / 10.0;.
JSON Komutlarını Güvenle Ayrıştırmak
MQTT callback'i bir bayt işaretçisi ve bir uzunluk alır; payload null ile sonlanmaz. Uzunluğu her zaman verin: deserializeJson(doc, payload, length). Yalnızca (char*)payload ile çağırmak mesajın sonunun ötesini okur.
Üç alışkanlık daha ayrıştırmayı sağlamlaştırır:
- Hatayı kontrol edin.
DeserializationError, girdinin boş, eksik, geçersiz veya çok derin olup olmadığını söyler. Gönderenin komutun başarısız olduğunu bilmesi için, sketch'in yaptığı gibi bunu bir ack topic'inde geri bildirin. |operatörüyle varsayılan değerler kullanın.doc["value"] | 0, alan eksikse veya türü yanlışsa 0 döndürür; böylece hatalı biçimlendirilmiş bir komut cihazı çökertemez veya bir değişkeni başlatılmamış bırakamaz.- Aralıkları doğrulayın. JSON ayrıştırmanın başarılı olması değerlerin anlamlı olduğu anlamına gelmez. Sıfır saniyelik aralıkları veya 5000'lik parlaklık değerlerini açıkça reddedin.
Büyük bir gelen belgeden yalnızca birkaç alana ihtiyacınız varsa, ArduinoJson'ın geri kalanını yok sayması için bir filtre kullanın:
JsonDocument filter;
filter["action"] = true;
filter["value"] = true;
JsonDocument doc;
deserializeJson(doc, payload, length, DeserializationOption::Filter(filter));
Tampon ve Bellek Tuzakları
| Belirti | Neden | Çözüm |
|---|---|---|
publish() false döndürüyor, hiçbir şey gelmiyor |
Paket, PubSubClient'ın 256 baytlık varsayılan tamponundan büyük | connect()'ten önce mqtt.setBufferSize(1024) |
| Gelen JSON callback'e hiç ulaşmıyor | Gelen mesaj tampondan büyük | Aynısı: tamponu beklenen en büyük komuta sığacak şekilde büyütün |
IncompleteInput hataları |
Sonlandırılmamış bir payload'da strlen() ile ayrıştırma |
deserializeJson()'a length verin |
| Günler sonra rastgele yeniden başlatmalar | String tabanlı serileştirmeden kaynaklanan heap parçalanması | Sabit tamponlara serileştirin veya istemciye akış olarak gönderin |
| Değerler uzun ondalıklarla yazdırılıyor | Varsayılan float biçimlendirmesi | serialized() veya atamadan önce yuvarlama |
Broker sınırlarını da aklınızda tutun. broker.connectmq.com'un public alanı mesaj başına 64 KB'a, ücretsiz ConnectMQ Serverless planı ise 256 KB'a kadar kabul eder; bu da bir ESP32 sketch'inin tek mesajda göndermesi gerekenin çok ötesindedir. JSON'unuz büyüyorsa onu .../telemetry ve .../diagnostics gibi topic'lere bölün. PubSubClient tampon boyutu rehberi tampon hesabını ayrıntılı olarak açıklar.
JSON Şemasını Tasarlamak
Birden fazla cihaz ve birden fazla tüketici devreye girdiğinde birkaç kural sorunları önler:
- Bir cihaz ID'si ekleyin veya bunu topic'e koyun; ikisini birden tutarsız şekilde yapmayın.
- Anahtar adlarında birim kullanın (
temp_c,power_w) veya bunları bir kez belgeleyin; cihazlar arasında birimleri karıştırmayın. - Yalnızca cihaz saati eşitlemişse zaman damgası ekleyin. Aksi halde varış zamanını tüketicinin damgalamasına izin verin; bkz. ESP32 MQTT mesajlarına NTP zaman damgası eklemek.
- Bir cihazın kendi mesajlarını asla ayrıştırmaması için komutları ve telemetriyi ayrı topic'lerde tutun.
- Format değişebilecekse payload'ı
"v":1gibi bir alanla sürümlendirin.
Gerçek Sensörlerle Kullanmak
Rastgele değerleri gerçek ölçümlerle değiştirin; sketch'in geri kalanı aynı kalır. ESP32 DHT22 MQTT eğitimi sensör tarafını gösterir; ESP32 IoT eğitimi de aynı JSON kalıbı etrafında eksiksiz bir cihaz projesi kurar.
JSON Telemetrinizi ConnectMQ'ya Gönderin
Ücretsiz public MQTT broker üzerinde test etmek, bir payload formatı üzerinde hızlıca yineleme yapmanın en hızlı yoludur. Veri önemli olduğunda özel bir broker'a geçin: ConnectMQ, portalı JSON mesajlarınızı topic gezgininde canlı gösteren ve sayısal alanlar üzerinde eşik uyarı kuralları oluşturmanızı sağlayan tamamen yönetilen bir MQTT servisidir. app.connectmq.com adresinde kredi kartı gerekmeden ücretsiz Serverless planına kaydolun, ardından sketch'i YOUR_CLIENT_ID, YOUR_USERNAME ve YOUR_PASSWORD ile 8883 portunda WiFiClientSecure'a geçirin.
Sık Sorulan Sorular
PubSubClient JSON'umu neden yayınlayamıyor?
Varsayılan tampon, topic ve başlık dahil 256 bayttır. Bağlanmadan önce daha büyük bir değerle mqtt.setBufferSize() çağırın ve publish()'in dönüş değerini kontrol edin.
ArduinoJson 6 mı, 7 mi kullanmalıyım?
Yeni projeler için sürüm 7'yi kullanın. Sabit kapasiteli belgelerin yerine kendi boyutunu ayarlayan tek bir JsonDocument koyar; bu da kesilmiş çıktının yaygın bir kaynağını ortadan kaldırır.
MQTT üzerinden JSON bir ESP32 için çok mu ağır?
Hayır. Tipik sensör hızlarında ayrıştırma ve serileştirme maliyeti çok küçüktür. Pille çalışıyorsanız veya saniyede çok sayıda mesaj gönderiyorsanız anahtarları kısa, payload'ları odaklı tutun.
Payload null ile sonlanmadığında onu nasıl ayrıştırırım?
Uzunluğu ayrıştırıcıya verin: deserializeJson(doc, payload, length). ArduinoJson böylece mesajın tam olarak baytlarını okur.



