ESP32 und Arduino

PubSubClient-Puffergröße: Fehler bei großen Nachrichten beheben

Große MQTT-Nachrichten scheitern auf Ihrem ESP32 oder ESP8266 stillschweigend? Erfahren Sie, wie der PubSubClient-Puffer funktioniert, wie Sie ihn mit setBufferSize dimensionieren und große JSON-Payloads ohne RAM-Verschwendung streamen.

Diagramm eines MQTT-Pakets, das die zu kleine PubSubClient-Puffergröße auf einem Mikrocontroller sprengt
Probieren Sie es auf Ihrem eigenen MQTT-Broker ausHolen Sie sich im Free-Tarif einen privaten, verwalteten MQTT-Broker und starten Sie mit fertigen Codebeispielen.
Kostenlos testen Codebeispiele Keine Kreditkarte erforderlich

Wenn publish() den Wert false liefert oder eingehende Nachrichten nie Ihren Callback erreichen, ist die PubSubClient-Puffergröße die wahrscheinlichste Ursache. Der Standard-Paketpuffer der Bibliothek umfasst nur 256 Bytes, und alles Größere scheitert ohne Fehlermeldung. Dieser Leitfaden erklärt genau, was der Puffer aufnehmen muss, wie Sie ihn mit setBufferSize() dimensionieren und wie Sie große JSON-Payloads streamen, ganz ohne große Puffer anzulegen.

Die Symptome

Pufferprobleme sind verwirrend, weil nichts abstürzt und die Verbindung bestehen bleibt. Achten Sie auf diese Anzeichen:

  • mqtt.publish() liefert bei manchen Nachrichten false, bei anderen nicht, typischerweise bei den längeren.
  • Kleine Testnachrichten kommen in Ihrem Callback an, das echte JSON aus Ihrem Backend aber nie.
  • Ein zusätzliches Feld im JSON-Dokument stoppt plötzlich die Telemetrie.
  • Retained Konfigurationsnachrichten auf einem Topic sind in anderen Clients sichtbar, erreichen das Gerät aber nie.

All das erklärt eine einzige Regel: Das gesamte MQTT-Paket, nicht nur die Payload, muss in den internen Puffer von PubSubClient passen.

Was der Puffer aufnehmen muss

PubSubClient verwendet einen einzigen Puffer für ausgehende und eingehende Pakete. Bei einem PUBLISH-Paket enthält dieser Puffer:

Bestandteil Größe
Fester Header (Pakettyp und Flags) 1 Byte
Feld Remaining Length 1 bis 4 Bytes (2 Bytes für die meisten Nachrichten bis 16 KB)
Längenpräfix des Topics 2 Bytes
Topic-String Länge Ihres Topics
Paket-ID 2 Bytes, nur bei eingehenden QoS-1-Nachrichten
Payload Länge Ihrer Nachricht

Die Bibliothek reserviert beim Aufbau des Pakets einige Bytes für den Header, in der Praxis brauchen Sie also Topic-Länge + Payload-Länge + etwa 7 Bytes, bei eingehenden QoS-1-Nachrichten 2 weitere. Mit dem 256-Byte-Standard und einem Topic wie connectmq/demo/sensor-4d2a/telemetry (36 Zeichen) muss Ihre Payload unter etwa 210 Bytes bleiben. Ein JSON-Dokument mit zehn Messwerten und Zeitstempeln überschreitet das mühelos.

Dieselbe Grenze gilt für andere Pakete, die die Bibliothek im Puffer aufbaut, darunter CONNECT (Client ID, Zugangsdaten und Will Message) und SUBSCRIBE. Ein sehr langes Passwort plus eine lange Will Message können sogar das CONNECT-Paket über 256 Bytes treiben, was sich als fehlgeschlagenes connect() zeigt.

Lösung 1: Den Puffer mit setBufferSize() vergrößern

Seit PubSubClient 2.8 können Sie den Puffer zur Laufzeit in der Größe ändern. Ältere Tutorials empfehlen, MQTT_MAX_PACKET_SIZE in PubSubClient.h zu ändern; das funktioniert noch, ist aber fragil, weil Bibliotheksupdates die Datei überschreiben und die Änderung stillschweigend verschwindet.

#include <WiFi.h>
#include <PubSubClient.h>

WiFiClient net;
PubSubClient mqtt(net);

void setup() {
  Serial.begin(115200);
  // ... WLAN verbinden ...

  mqtt.setServer("broker.connectmq.com", 1883);
  if (!mqtt.setBufferSize(2048)) {
    Serial.println("Could not allocate MQTT buffer");
  }
  Serial.printf("MQTT buffer size: %u\n", mqtt.getBufferSize());
}

Die wichtigsten Punkte:

  1. setBufferSize() erwartet einen uint16_t, die theoretische Obergrenze liegt also bei 65.535 Bytes.
  2. Die Funktion alloziert den Speicher neu und liefert false, wenn die Allokation scheitert; der bisherige Puffer bleibt dann erhalten. Prüfen Sie immer den Rückgabewert.
  3. Rufen Sie sie vor connect() auf. Eine Größenänderung während der Verbindung funktioniert, ist aber verschwenderisch.
  4. getBufferSize() bestätigt die aktuelle Größe.

Wie groß sollte er sein? Wenden Sie die obige Formel auf Ihre größte erwartete Nachricht an und rechnen Sie 10 bis 20 Prozent Reserve hinzu. Setzen Sie auf einem ESP8266 nicht "für alle Fälle" 16 KB: Dieser RAM fehlt dann für TLS, und der nächste Handshake kann scheitern. Auf einem ESP32 sind ein paar Kilobyte kein Problem.

Lösung 2: Große Payloads mit beginPublish() streamen

Einen größeren Puffer zu setzen ist für gelegentliche große Nachrichten in Ordnung, Streaming vermeidet die RAM-Kosten jedoch vollständig. beginPublish() schreibt Header und Topic und lässt Sie dann die Payload stückweise direkt in den Netzwerk-Client schreiben. Nur der Header läuft durch den Puffer, sodass ein 256-Byte-Puffer eine Payload von vielen Kilobytes senden kann.

Der Haken: Sie müssen die Payload-Länge im Voraus kennen, denn MQTT trägt sie in den Header ein. ArduinoJson macht das mit measureJson() einfach.

#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>

const char* TOPIC = "connectmq/demo/sensor-4d2a/batch";

WiFiClient net;
PubSubClient mqtt(net);

bool publishBatch(const float* values, size_t count) {
  JsonDocument doc;                 // ArduinoJson 7
  doc["device"] = "sensor-4d2a";
  doc["uptime"] = millis() / 1000;
  JsonArray arr = doc["values"].to<JsonArray>();
  for (size_t i = 0; i < count; i++) arr.add(values[i]);

  size_t len = measureJson(doc);
  if (!mqtt.beginPublish(TOPIC, len, false)) {
    Serial.println("beginPublish failed");
    return false;
  }
  serializeJson(doc, mqtt);         // PubSubClient ist ein Print, daher wird gestreamt
  return mqtt.endPublish() == 1;
}

Bei 200 Float-Werten ist die Payload weit größer als 256 Bytes, und doch reicht der Standardpuffer aus. beginPublish(), write()/print() und endPublish() stehen ab PubSubClient 2.7 zur Verfügung.

Beachten Sie, dass der Broker trotzdem eine einzelne MQTT-Nachricht sieht, die innerhalb seines Limits liegen muss. Auf dem öffentlichen MQTT-Broker unter broker.connectmq.com, der für Tests und Prototypen gedacht ist, liegt das Limit bei 64 KB pro Nachricht.

Große Nachrichten empfangen

Streaming hilft nur bei ausgehenden Nachrichten. Eingehende Nachrichten liest PubSubClient vollständig in den Puffer, bevor Ihr Callback aufgerufen wird. Ist das Paket größer als der Puffer, liest die Bibliothek es und verwirft es, und Ihr Callback wird nie aufgerufen. Es gibt weder einen Fehler noch einen Logeintrag.

Für Geräte, die große Konfigurationsdokumente oder Firmware-Manifeste empfangen, gilt daher:

  • Setzen Sie den Puffer auf die größte eingehende Nachricht plus Topic plus Overhead.
  • Halten Sie eingehende Nachrichten kompakt. Senden Sie nur die Felder, die das Gerät braucht, statt eines kompletten Backend-Objekts.
  • Teilen Sie große Datenmengen auf mehrere Topics auf, etwa config/network und config/sensors, jeweils deutlich unter dem Limit.
  • Für wirklich große Payloads wie Firmware-Images veröffentlichen Sie per MQTT eine URL und laden die Datei über HTTPS herunter.

Testen Sie, was mit einer absichtlich zu großen Nachricht passiert:

mosquitto_pub -h broker.connectmq.com -p 1883 -i sizetest-4d2a \
  -t "connectmq/demo/sensor-4d2a/cmd" \
  -m "$(head -c 400 /dev/zero | tr '\0' 'x')"

Mit dem Standardpuffer ignoriert das Gerät sie. Nach setBufferSize(1024) wird der Callback mit einer Länge von 400 ausgelöst.

Checkliste zum Debuggen

Gehen Sie diese Liste durch, wenn Nachrichten verloren gehen:

  1. Geben Sie nach dem Setup mqtt.getBufferSize() aus, um zu bestätigen, dass die gewünschte Größe gesetzt ist.
  2. Protokollieren Sie vor jedem Publish die Länge von Topic und Payload: Serial.printf("%u + %u\n", strlen(topic), strlen(payload));.
  3. Prüfen Sie den Rückgabewert von publish() und geben Sie bei einem Fehler mqtt.connected() aus, um Größenprobleme von Verbindungsproblemen zu unterscheiden.
  4. Beobachten Sie auf dem ESP8266 ESP.getMaxFreeBlockSize(). Ein großer Puffer plus TLS kann den zusammenhängenden Speicher erschöpfen.
  5. Vergleichen Sie mit einem Desktop-Client: Empfängt mosquitto_sub die Nachricht, das Gerät aber nicht, ist der Puffer zu klein.
  6. Stellen Sie sicher, dass Ihre Nachricht sowohl innerhalb des Broker-Limits als auch innerhalb des Gerätepuffers liegt.

Scheitert schon die Verbindung, liegt die Ursache woanders; siehe PubSubClient rc=-2 und weitere State-Codes. Eine vollständige Einführung in die Bibliothek bietet unser PubSubClient-Tutorial. Brauchen Sie eine Bibliothek, die auch eingehende Nachrichten streamt, zeigt der ArduinoMqttClient-Leitfaden einen alternativen Ansatz, und das ESP32-IoT-Tutorial verbindet diese Techniken zu einem kompletten Projekt.

Nachrichtenlimits des Brokers

Der Gerätepuffer ist nur die halbe Wahrheit. Jeder Broker begrenzt ebenfalls die Nachrichtengröße, und Nachrichten über dem Limit werden abgelehnt oder führen zu einem Verbindungsabbruch:

Umgebung Maximale Nachrichtengröße
Öffentlicher MQTT-Broker von ConnectMQ (broker.connectmq.com) 64 KB
ConnectMQ Serverless (Free) 256 KB
ConnectMQ Serverless (Paid) 1 MB

Für Embedded-Geräte ist es ohnehin gute Praxis, im niedrigen Kilobyte-Bereich zu bleiben. Das gilt auch für ESP32-basierte Retrofit-Sensoren an Bestandsmaschinen: Statt großer Sammeldokumente lieber einzelne Werte auf sprechende Topics publizieren, etwa nach der ISA-95-Struktur eines Unified Namespace (UNS) wie acme/werk-muenchen/presswerk/linie-2/presse-1/temperatur. Kleinere Nachrichten bedeuten weniger RAM, schnellere TLS-Records und weniger Neuübertragungen bei schwachem WLAN.

Größere Payloads mit ConnectMQ senden

Ist Ihre Telemetrie dem öffentlichen Broker entwachsen, bietet Ihnen ein verwalteter MQTT-Broker private Zugangsdaten, TLS und höhere Nachrichtenlimits. ConnectMQ ist ein Cloud-MQTT-Broker mit MQTT 3.1.1 und 5.0, einem MQTT-Client im Browser und einem Live-Topic-Explorer, in dem Sie genau prüfen können, was Ihre Geräte senden. Erstellen Sie einen kostenlosen Serverless Broker unter app.connectmq.com, ohne Kreditkarte.

Häufig gestellte Fragen

Wie groß ist der Standardpuffer von PubSubClient?

256 Bytes, definiert als MQTT_MAX_PACKET_SIZE. Er umfasst das gesamte Paket einschließlich Topic und Headern, die nutzbare Payload ist also kleiner.

Sollte ich MQTT_MAX_PACKET_SIZE ändern oder setBufferSize() verwenden?

Verwenden Sie setBufferSize() ab PubSubClient 2.8. Die Einstellung übersteht Bibliotheksupdates, und Sie können prüfen, ob die Allokation erfolgreich war.

Warum liefert publish() bei einer großen Nachricht false?

Topic plus Payload plus Header überschreiten den Puffer. Vergrößern Sie den Puffer oder streamen Sie die Payload mit beginPublish().

Kann PubSubClient Nachrichten empfangen, die größer als sein Puffer sind?

Nein. Übergroße eingehende Nachrichten werden verworfen, bevor Ihr Callback läuft. Vergrößern Sie den Puffer oder halten Sie eingehende Nachrichten kleiner.

Verwandte Artikel

ESP32 NTP MQTT Zeitstempel: genaue Uhrzeit in jeder Nachricht

Synchronisieren Sie die ESP32-Uhr per NTP, formatieren Sie Zeitstempel in ISO 8601 und Epoch-Millisekunden und hängen Sie sie an MQTT-Payloads, damit Messwerte ihren echten Erfassungszeitpunkt behalten, auch wenn sie verspätet veröffentlicht werden.

8 Min. Lesezeit

Alle Artikel →

Ihr privater MQTT-Broker ist nur wenige Klicks entfernt

Konto anlegen, Broker-Zugangsdaten erhalten und das erste Gerät verbinden — ohne Server, die installiert, gepatcht oder skaliert werden müssen.