ESP32 ve Arduino

ESP32 ArduinoJson MQTT: JSON Payload'larını Doğru Şekilde Gönderin ve Ayrıştırın

Bir ESP32'de ArduinoJson 7 ile sensör ölçümlerini serileştirin ve gelen JSON komutlarını ayrıştırın, bunları PubSubClient ile MQTT üzerinden yayınlayın ve mesajları sessizce düşüren tampon ve bellek tuzaklarından kaçının.

ESP32 ArduinoJson MQTT eğitiminde ArduinoJson ile oluşturulmuş bir JSON belgesi yayınlayan ESP32 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

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_pub ve mosquitto_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:

  1. 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.
  2. | 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.
  3. 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":1 gibi 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.

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