ESP32 et Arduino

Taille du buffer PubSubClient : corriger les échecs des gros messages

Vos gros messages MQTT échouent en silence sur ESP32 ou ESP8266 ? Découvrez comment fonctionne le buffer PubSubClient, comment le dimensionner avec setBufferSize et comment envoyer de gros payloads JSON en flux sans gaspiller de RAM.

Schéma d'un paquet MQTT qui déborde d'un buffer PubSubClient trop petit sur un microcontrôleur
Essayez-le sur votre propre broker MQTTObtenez un broker MQTT privé et managé avec l’offre Free et partez d’exemples de code prêts à l’emploi.
Essayer gratuitement Exemples de code Aucune carte bancaire requise

Si publish() renvoie false ou si les messages entrants n'atteignent jamais votre callback, la taille du buffer PubSubClient est la cause la plus probable. Le buffer de paquets par défaut de la bibliothèque ne fait que 256 octets, et tout ce qui dépasse échoue sans message d'erreur. Ce guide explique précisément ce que le buffer doit contenir, comment le dimensionner avec setBufferSize() et comment envoyer de gros payloads JSON en flux sans allouer de gros buffers.

Les symptômes

Les problèmes de buffer sont déroutants, car rien ne plante et la connexion reste active. Surveillez ces signes :

  • mqtt.publish() renvoie false pour certains messages mais pas pour d'autres, en général les plus longs.
  • Les petits messages de test arrivent dans votre callback, mais le vrai JSON envoyé par votre backend n'arrive jamais.
  • Ajouter un seul champ à votre document JSON interrompt soudain la télémétrie.
  • Les messages de configuration retenus sur un topic sont visibles dans d'autres clients mais n'atteignent jamais l'appareil.

Tout cela s'explique par une seule règle : c'est le paquet MQTT entier, et pas seulement le payload, qui doit tenir dans le buffer interne de PubSubClient.

Ce que le buffer doit contenir

PubSubClient utilise un seul buffer pour les paquets sortants et entrants. Pour un paquet PUBLISH, ce buffer contient :

Partie Taille
En-tête fixe (type de paquet et drapeaux) 1 octet
Champ de longueur restante 1 à 4 octets (2 octets pour la plupart des messages jusqu'à 16 Ko)
Préfixe de longueur du topic 2 octets
Chaîne du topic Longueur de votre topic
Identifiant de paquet 2 octets, uniquement pour les messages QoS 1 entrants
Payload Longueur de votre message

La bibliothèque réserve quelques octets d'en-tête lors de la construction du paquet : en pratique, il vous faut donc longueur du topic + longueur du payload + environ 7 octets, plus 2 octets pour les messages QoS 1 entrants. Avec le défaut de 256 octets et un topic comme connectmq/demo/sensor-4d2a/telemetry (36 caractères), votre payload doit rester sous environ 210 octets. Un document JSON avec dix mesures et leurs horodatages dépasse facilement cette limite.

La même limite s'applique aux autres paquets que la bibliothèque construit dans le buffer, notamment CONNECT (client ID, identifiants et message Will) et SUBSCRIBE. Un mot de passe très long combiné à un long message Will peut faire dépasser 256 octets même au paquet CONNECT, ce qui se traduit par un connect() en échec.

Solution 1 : agrandir le buffer avec setBufferSize()

Depuis PubSubClient 2.8, vous pouvez redimensionner le buffer à l'exécution. Les anciens tutoriels vous disent de modifier MQTT_MAX_PACKET_SIZE dans PubSubClient.h ; cela fonctionne toujours mais reste fragile, car les mises à jour de la bibliothèque écrasent le fichier et la modification disparaît sans bruit.

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

WiFiClient net;
PubSubClient mqtt(net);

void setup() {
  Serial.begin(115200);
  // ... connexion Wi-Fi ...

  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());
}

Points clés :

  1. setBufferSize() prend un uint16_t : le plafond théorique est donc de 65 535 octets.
  2. Elle réalloue la mémoire et renvoie false si l'allocation échoue, en conservant le buffer précédent. Vérifiez toujours la valeur de retour.
  3. Appelez-la avant connect(). Redimensionner en cours de connexion fonctionne mais gaspille des ressources.
  4. getBufferSize() confirme la taille actuelle.

Quelle taille choisir ? Appliquez la formule ci-dessus à votre plus gros message attendu, puis ajoutez une marge de 10 à 20 %. Ne réglez pas 16 Ko « au cas où » sur un ESP8266 : cette RAM n'est alors plus disponible pour le TLS, et la poignée de main suivante risque d'échouer. Sur un ESP32, quelques kilo-octets ne posent aucun problème.

Solution 2 : envoyer les gros payloads en flux avec beginPublish()

Agrandir le buffer convient pour des gros messages occasionnels, mais le streaming évite entièrement le coût en RAM. beginPublish() écrit l'en-tête et le topic, puis vous laisse écrire le payload directement sur le client réseau, morceau par morceau. Seul l'en-tête passe par le buffer : un buffer de 256 octets peut donc envoyer un payload de plusieurs kilo-octets.

La contrepartie : vous devez connaître la longueur du payload à l'avance, car MQTT l'inscrit dans l'en-tête. ArduinoJson facilite cela avec measureJson().

#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 est un Print, donc ceci est envoyé en flux
  return mqtt.endPublish() == 1;
}

Avec 200 valeurs flottantes, le payload dépasse largement 256 octets, et pourtant le buffer par défaut suffit. beginPublish(), write()/print() et endPublish() sont disponibles à partir de PubSubClient 2.7.

Notez que le broker voit toujours un seul message MQTT, qui doit respecter la limite du broker. Sur le serveur MQTT public gratuit broker.connectmq.com, réservé aux tests, la limite est de 64 Ko par message.

Recevoir de gros messages

Le streaming n'aide que pour les messages sortants. Pour les messages entrants, PubSubClient lit l'intégralité du paquet dans le buffer avant d'appeler votre callback. Si le paquet est plus grand que le buffer, la bibliothèque le lit puis l'abandonne, et votre callback n'est jamais appelé. Aucune erreur, aucun journal.

Pour les appareils qui reçoivent de gros documents de configuration ou des manifestes de firmware :

  • Réglez le buffer sur la taille du plus gros message entrant, plus le topic et les en-têtes.
  • Gardez des messages entrants compacts. N'envoyez que les champs dont l'appareil a besoin plutôt qu'un objet backend complet.
  • Répartissez les grosses données sur plusieurs topics, par exemple config/network et config/sensors, chacun bien en dessous de la limite.
  • Pour les payloads vraiment volumineux comme les images de firmware, publiez une URL via MQTT et téléchargez le fichier en HTTPS.

Testez ce qui se passe avec un message volontairement trop gros :

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')"

Avec le buffer par défaut, l'appareil l'ignore. Après setBufferSize(1024), le callback se déclenche avec une longueur de 400.

Liste de vérification pour le débogage

Parcourez cette liste quand des messages disparaissent :

  1. Affichez mqtt.getBufferSize() après l'initialisation pour confirmer la taille que vous pensez avoir définie.
  2. Journalisez la longueur du topic et du payload avant chaque publication : Serial.printf("%u + %u\n", strlen(topic), strlen(payload));.
  3. Vérifiez la valeur de retour de publish() et affichez mqtt.connected() en cas d'échec, pour distinguer les problèmes de taille des problèmes de connexion.
  4. Sur ESP8266, surveillez ESP.getMaxFreeBlockSize(). Un gros buffer combiné au TLS peut épuiser la mémoire contiguë.
  5. Comparez avec un client de bureau : si mosquitto_sub reçoit le message mais pas l'appareil, le buffer est trop petit.
  6. Assurez-vous que votre message respecte à la fois la limite de votre broker et le buffer de l'appareil.

Si c'est la connexion elle-même qui échoue, la cause est ailleurs ; consultez PubSubClient rc=-2 et les autres codes d'état. Pour une présentation complète de la bibliothèque, lisez notre tutoriel PubSubClient. Si vous avez besoin d'une bibliothèque qui traite aussi les messages entrants en flux, le guide ArduinoMqttClient présente une approche alternative, et le tutoriel IoT ESP32 réunit ces techniques dans un projet complet.

Limites de taille des messages côté broker

Le buffer de l'appareil n'est que la moitié de l'histoire. Chaque broker plafonne aussi la taille des messages, et les messages au-delà de la limite sont rejetés ou provoquent une déconnexion :

Environnement Taille maximale d'un message
Serveur MQTT public gratuit ConnectMQ (broker.connectmq.com) 64 Ko
ConnectMQ Serverless (Free) 256 Ko
ConnectMQ Serverless (Paid) 1 Mo

Pour les appareils embarqués, rester dans les quelques kilo-octets est de toute façon une bonne pratique. Des messages plus petits signifient moins de RAM, des enregistrements TLS plus rapides et moins de retransmissions sur un Wi-Fi faible.

Envoyez des payloads plus gros avec ConnectMQ

Si votre télémétrie a dépassé les capacités du serveur MQTT public gratuit, un broker MQTT managé vous apporte des identifiants privés, le TLS et des limites de messages plus élevées. ConnectMQ est un broker MQTT cloud compatible MQTT 3.1.1 et 5.0, avec un client MQTT dans le navigateur et un explorateur de topics en direct pour inspecter précisément ce qu'envoient vos appareils. Créez un broker Serverless gratuit sur app.connectmq.com, sans carte bancaire.

Questions fréquentes

Quelle est la taille par défaut du buffer PubSubClient ?

256 octets, définis par MQTT_MAX_PACKET_SIZE. Elle couvre le paquet entier, topic et en-têtes compris : le payload utilisable est donc plus petit.

Faut-il modifier MQTT_MAX_PACKET_SIZE ou utiliser setBufferSize() ?

Utilisez setBufferSize() avec PubSubClient 2.8 ou ultérieur. Ce réglage survit aux mises à jour de la bibliothèque et vous permet de vérifier si l'allocation a réussi.

Pourquoi publish() renvoie-t-il false avec un gros message ?

Le topic, le payload et l'en-tête dépassent ensemble la taille du buffer. Agrandissez le buffer ou utilisez beginPublish() pour envoyer le payload en flux.

PubSubClient peut-il recevoir des messages plus grands que son buffer ?

Non. Les messages entrants trop volumineux sont abandonnés avant l'exécution de votre callback. Agrandissez le buffer ou réduisez la taille des messages entrants.

Articles liés

Exemple MQTT ESP-IDF : construire un client esp-mqtt à partir de zéro

Construisez un projet ESP-IDF à partir d'un dossier vide avec le composant esp-mqtt : configuration du Wi-Fi en mode station, gestionnaire d'événements MQTT, publication et abonnement, propriétés MQTT 5 et TLS avec le bundle de certificats.

9 min de lecture

Horodatage NTP ESP32 MQTT : ajouter une heure précise à chaque message

Synchronisez l'horloge de l'ESP32 avec NTP, formatez des horodatages ISO 8601 et epoch en millisecondes, et joignez-les aux payloads MQTT pour que les mesures conservent leur véritable heure de capture même lorsqu'elles sont publiées en retard.

8 min de lecture

Tous les articles →

Votre broker MQTT privé, à quelques clics

Créez un compte, récupérez les identifiants de votre broker et connectez votre premier appareil — aucun serveur à installer, mettre à jour ou dimensionner.