ESP32 y Arduino

Tamaño del buffer de PubSubClient: cómo solucionar los fallos con mensajes grandes

¿Fallan sin avisar los mensajes MQTT grandes en tu ESP32 o ESP8266? Aprende cómo funciona el buffer de PubSubClient, cómo dimensionarlo con setBufferSize y cómo enviar JSON grandes en streaming sin malgastar RAM.

Diagrama de un paquete MQTT que desborda el pequeño tamaño del buffer de PubSubClient en un microcontrolador
Pruébalo en tu propio broker MQTTConsigue un broker MQTT privado y gestionado con el plan Free y empieza con ejemplos de código listos para usar.
Pruébalo gratis Ejemplos de código Sin tarjeta

Si publish() devuelve false o los mensajes entrantes nunca llegan a tu callback, la causa más probable es el tamaño del buffer de PubSubClient. El buffer de paquetes por defecto de la librería es de solo 256 bytes, y cualquier cosa más grande falla sin ningún mensaje de error. Esta guía explica exactamente qué debe contener el buffer, cómo dimensionarlo con setBufferSize() y cómo enviar payloads JSON grandes en streaming sin reservar buffers grandes.

Los síntomas

Los problemas de buffer desconciertan porque nada se bloquea y la conexión sigue activa. Presta atención a estas señales:

  • mqtt.publish() devuelve false con algunos mensajes pero no con otros, normalmente los más largos.
  • Los mensajes de prueba pequeños llegan a tu callback, pero el JSON real de tu backend nunca lo hace.
  • Añadir un campo más a tu documento JSON detiene de repente la telemetría.
  • Los mensajes de configuración retenidos en un topic son visibles en otros clientes pero nunca llegan al dispositivo.

Todo esto se explica con una sola regla: el paquete MQTT completo, no solo el payload, debe caber en el buffer interno de PubSubClient.

Qué debe contener el buffer

PubSubClient usa un único buffer tanto para los paquetes salientes como para los entrantes. En un paquete PUBLISH, ese buffer contiene:

Parte Tamaño
Cabecera fija (tipo de paquete y flags) 1 byte
Campo de longitud restante De 1 a 4 bytes (2 bytes en la mayoría de los mensajes de hasta 16 KB)
Prefijo de longitud del topic 2 bytes
Cadena del topic Longitud de tu topic
Identificador de paquete 2 bytes, solo en mensajes entrantes con QoS 1
Payload Longitud de tu mensaje

La librería reserva unos bytes de cabecera al construir el paquete, así que en la práctica necesitas longitud del topic + longitud del payload + unos 7 bytes, más 2 adicionales para los mensajes entrantes con QoS 1. Con los 256 bytes por defecto y un topic como connectmq/demo/sensor-4d2a/telemetry (36 caracteres), tu payload debe quedarse por debajo de unos 210 bytes. Un documento JSON con diez lecturas y marcas de tiempo supera ese límite con facilidad.

El mismo límite se aplica a otros paquetes que la librería construye en el buffer, como CONNECT (client ID, credenciales y mensaje Will) y SUBSCRIBE. Una contraseña muy larga más un mensaje Will largo pueden hacer que incluso el paquete CONNECT supere los 256 bytes, lo que se manifiesta como un connect() fallido.

Solución 1: aumenta el buffer con setBufferSize()

Desde PubSubClient 2.8 puedes cambiar el tamaño del buffer en tiempo de ejecución. Los tutoriales antiguos te dicen que edites MQTT_MAX_PACKET_SIZE en PubSubClient.h; sigue funcionando, pero es frágil, porque las actualizaciones de la librería lo sobrescriben y el cambio desaparece sin avisar.

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

WiFiClient net;
PubSubClient mqtt(net);

void setup() {
  Serial.begin(115200);
  // ... conectar el 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());
}

Puntos clave:

  1. setBufferSize() recibe un uint16_t, así que el techo teórico es de 65.535 bytes.
  2. Reasigna memoria y devuelve false si la asignación falla, conservando el buffer anterior. Comprueba siempre el valor de retorno.
  3. Llámalo antes de connect(). Cambiar el tamaño estando conectado funciona, pero es un desperdicio.
  4. getBufferSize() confirma el tamaño actual.

¿Qué tamaño debe tener? Aplica la fórmula anterior a tu mensaje más grande previsto y añade un margen del 10 al 20 por ciento. No pongas 16 KB "por si acaso" en un ESP8266: esa RAM deja de estar disponible para TLS y el siguiente handshake puede fallar. En un ESP32, unos pocos kilobytes no suponen ningún problema.

Solución 2: envía payloads grandes en streaming con beginPublish()

Aumentar el buffer está bien para mensajes grandes ocasionales, pero el streaming evita por completo el coste en RAM. beginPublish() escribe la cabecera y el topic y después te permite escribir el payload directamente en el cliente de red por partes. Solo la cabecera pasa por el buffer, así que un buffer de 256 bytes puede enviar un payload de muchos kilobytes.

El inconveniente: debes conocer de antemano la longitud del payload, porque MQTT la incluye en la cabecera. ArduinoJson lo pone fácil con 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 es un Print, así que esto se envía en streaming
  return mqtt.endPublish() == 1;
}

Con 200 valores float, el payload es mucho mayor que 256 bytes y, aun así, el buffer por defecto basta. beginPublish(), write()/print() y endPublish() están disponibles en PubSubClient 2.7 y posteriores.

Ten en cuenta que el broker sigue viendo un único mensaje MQTT, que debe estar dentro del límite del broker. En el servidor MQTT público gratuito de broker.connectmq.com, el límite es de 64 KB por mensaje.

Recibir mensajes grandes

El streaming solo ayuda con los mensajes salientes. Con los entrantes, PubSubClient lee el paquete completo en el buffer antes de llamar a tu callback. Si el paquete es mayor que el buffer, la librería lo lee y lo descarta, y tu callback nunca se llama. No hay error ni registro.

Así que, para dispositivos que reciben documentos de configuración grandes o manifiestos de firmware:

  • Ajusta el buffer al mensaje entrante más grande más el topic y la sobrecarga.
  • Mantén compactos los mensajes entrantes. Envía solo los campos que necesita el dispositivo en lugar de un objeto completo del backend.
  • Reparte los datos grandes en varios topics, por ejemplo config/network y config/sensors, cada uno muy por debajo del límite.
  • Para payloads realmente grandes, como imágenes de firmware, publica una URL por MQTT y descarga el archivo por HTTPS.

Comprueba qué ocurre con un mensaje deliberadamente demasiado grande:

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

Con el buffer por defecto, el dispositivo lo ignora. Tras setBufferSize(1024), el callback se dispara con una longitud de 400.

Lista de comprobación para depurar

Recorre esta lista cuando desaparezcan mensajes:

  1. Imprime mqtt.getBufferSize() después de la configuración para confirmar el tamaño que crees haber fijado.
  2. Registra la longitud del topic y del payload antes de cada publicación: Serial.printf("%u + %u\n", strlen(topic), strlen(payload));.
  3. Comprueba el valor de retorno de publish() e imprime mqtt.connected() cuando falle, para distinguir los problemas de tamaño de los de conexión.
  4. En ESP8266, vigila ESP.getMaxFreeBlockSize(). Un buffer grande más TLS puede agotar la memoria contigua.
  5. Compara con un cliente de escritorio: si mosquitto_sub recibe el mensaje pero el dispositivo no, el buffer es demasiado pequeño.
  6. Asegúrate de que tu mensaje respeta el límite del broker además del buffer del dispositivo.

Si lo que falla es la propia conexión, la causa está en otra parte; consulta PubSubClient rc=-2 y otros códigos de estado. Para un recorrido completo por la librería, lee nuestro tutorial de PubSubClient. Si necesitas una librería que también reciba los mensajes en streaming, la guía de ArduinoMqttClient muestra un enfoque alternativo, y el tutorial de IoT con ESP32 integra estas técnicas en un proyecto completo.

Límites de mensaje del broker

El buffer del dispositivo es solo la mitad de la historia. Todo broker limita también el tamaño de los mensajes, y los que superan el límite se rechazan o provocan una desconexión:

Entorno Tamaño máximo de mensaje
Broker público de ConnectMQ (broker.connectmq.com) 64 KB
ConnectMQ Serverless (Free) 256 KB
ConnectMQ Serverless (Paid) 1 MB

En dispositivos embebidos, mantenerse en unos pocos kilobytes es una buena práctica de todos modos. Mensajes más pequeños significan menos RAM, registros TLS más rápidos y menos retransmisiones con un Wi-Fi débil.

Envía payloads más grandes con ConnectMQ

Si tu telemetría se ha quedado grande para el broker público, un broker MQTT gestionado te ofrece credenciales privadas, TLS y límites de mensaje más altos. ConnectMQ es un broker MQTT en la nube con MQTT 3.1.1 y 5.0, un cliente MQTT en el navegador y un explorador de topics en vivo donde puedes inspeccionar exactamente lo que envían tus dispositivos. Crea un broker Serverless gratuito en app.connectmq.com, sin tarjeta de crédito.

Preguntas frecuentes

¿Cuál es el tamaño de buffer por defecto de PubSubClient?

256 bytes, definido como MQTT_MAX_PACKET_SIZE. Abarca el paquete completo, incluidos el topic y las cabeceras, así que el payload utilizable es menor.

¿Debo editar MQTT_MAX_PACKET_SIZE o usar setBufferSize()?

Usa setBufferSize() en PubSubClient 2.8 o posterior. Sobrevive a las actualizaciones de la librería y te permite comprobar si la asignación tuvo éxito.

¿Por qué publish() devuelve false con un mensaje grande?

El topic más el payload más la cabecera superan el buffer. Aumenta el buffer o usa beginPublish() para enviar el payload en streaming.

¿Puede PubSubClient recibir mensajes más grandes que su buffer?

No. Los mensajes entrantes demasiado grandes se descartan antes de que se ejecute tu callback. Aumenta el buffer o mantén más pequeños los mensajes entrantes.

Artículos relacionados

Ejemplo de ESP-IDF MQTT: crea un cliente esp-mqtt desde cero

Construye un proyecto ESP-IDF desde una carpeta vacía con el componente esp-mqtt: configuración de Wi-Fi en modo estación, el manejador de eventos MQTT, publicación y suscripción, propiedades de MQTT 5 y TLS con el bundle de certificados.

9 min de lectura

Todos los artículos →

Tu broker MQTT privado, a unos pocos clics

Crea una cuenta, obtén las credenciales de tu broker y conecta tu primer dispositivo, sin servidores que instalar, parchear ni escalar.