ESP32 e Arduino

Tamanho do Buffer do PubSubClient: Resolvendo Falhas com Mensagens Grandes

Mensagens MQTT grandes falhando em silêncio no seu ESP32 ou ESP8266? Veja como o buffer do PubSubClient funciona, como dimensioná-lo com setBufferSize e como enviar payloads JSON grandes em stream sem desperdiçar RAM.

Diagrama de um pacote MQTT estourando um buffer pequeno do PubSubClient em um microcontrolador
Rode isso no seu próprio broker MQTTTenha um broker MQTT privado e gerenciado no plano Free e comece com exemplos de código prontos.
Teste grátis Exemplos de código Sem cartão de crédito

Se publish() retorna false ou as mensagens recebidas nunca chegam ao seu callback, o tamanho do buffer do PubSubClient é a causa mais provável. O buffer de pacotes padrão da biblioteca tem apenas 256 bytes, e qualquer coisa maior falha sem nenhuma mensagem de erro. Este guia explica exatamente o que o buffer precisa comportar, como dimensioná-lo com setBufferSize() e como enviar payloads JSON grandes em stream sem alocar buffers grandes.

Os Sintomas

Problemas de buffer confundem porque nada trava e a conexão continua ativa. Fique atento a estes sinais:

  • mqtt.publish() retorna false para algumas mensagens e não para outras, normalmente as mais longas.
  • Mensagens pequenas de teste chegam ao callback, mas o JSON real do seu backend nunca chega.
  • Adicionar mais um campo ao documento JSON de repente interrompe a telemetria.
  • Mensagens de configuração retidas em um topic aparecem em outros clientes, mas nunca chegam ao dispositivo.

Tudo isso é explicado por uma única regra: o pacote MQTT inteiro, e não apenas o payload, precisa caber no buffer interno do PubSubClient.

O Que o Buffer Precisa Comportar

O PubSubClient usa um único buffer tanto para os pacotes enviados quanto para os recebidos. Em um pacote PUBLISH, esse buffer contém:

Parte Tamanho
Cabeçalho fixo (tipo de pacote e flags) 1 byte
Campo remaining length 1 a 4 bytes (2 bytes para a maioria das mensagens de até 16 KB)
Prefixo de tamanho do topic 2 bytes
String do topic Tamanho do seu topic
Identificador do pacote 2 bytes, apenas para mensagens QoS 1 recebidas
Payload Tamanho da sua mensagem

A biblioteca reserva alguns bytes de espaço de cabeçalho ao montar o pacote, então na prática você precisa de tamanho do topic + tamanho do payload + cerca de 7 bytes, mais 2 para mensagens QoS 1 recebidas. Com o padrão de 256 bytes e um topic como connectmq/demo/sensor-4d2a/telemetry (36 caracteres), o payload precisa ficar abaixo de aproximadamente 210 bytes. Um documento JSON com dez leituras e timestamps ultrapassa isso facilmente.

O mesmo limite vale para outros pacotes que a biblioteca monta no buffer, incluindo o CONNECT (client ID, credenciais e mensagem will) e o SUBSCRIBE. Uma senha muito longa somada a uma mensagem will longa pode levar até o pacote CONNECT a mais de 256 bytes, o que aparece como uma falha em connect().

Solução 1: Aumente o Buffer com setBufferSize()

Desde o PubSubClient 2.8, você pode redimensionar o buffer em tempo de execução. Tutoriais antigos mandam editar MQTT_MAX_PACKET_SIZE em PubSubClient.h; isso ainda funciona, mas é frágil, porque as atualizações da biblioteca sobrescrevem o arquivo e a alteração desaparece sem aviso.

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

WiFiClient net;
PubSubClient mqtt(net);

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

Pontos-chave:

  1. setBufferSize() recebe um uint16_t, então o teto teórico é de 65.535 bytes.
  2. Ele realoca memória e retorna false se a alocação falhar, mantendo o buffer anterior. Sempre verifique o valor de retorno.
  3. Chame-o antes de connect(). Redimensionar com a conexão ativa funciona, mas é desperdício.
  4. getBufferSize() confirma o tamanho atual.

Qual deve ser o tamanho? Use a fórmula acima para a maior mensagem esperada e acrescente uma margem de 10 a 20 por cento. Não defina 16 KB "por via das dúvidas" em um ESP8266: essa RAM fica indisponível para o TLS, e o próximo handshake pode falhar. Em um ESP32, alguns kilobytes não são problema.

Solução 2: Envie Payloads Grandes em Stream com beginPublish()

Aumentar o buffer resolve bem para mensagens grandes ocasionais, mas o streaming elimina totalmente o custo de RAM. beginPublish() grava o cabeçalho e o topic e depois permite gravar o payload diretamente no cliente de rede, em partes. Só o cabeçalho passa pelo buffer, então um buffer de 256 bytes consegue enviar um payload de muitos kilobytes.

O detalhe: você precisa saber o tamanho do payload de antemão, porque o MQTT o coloca no cabeçalho. O ArduinoJson facilita isso com 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);         // O PubSubClient é um Print, então isto é enviado em stream
  return mqtt.endPublish() == 1;
}

Com 200 valores float, o payload é bem maior que 256 bytes, e mesmo assim o buffer padrão basta. beginPublish(), write()/print() e endPublish() estão disponíveis a partir do PubSubClient 2.7.

Observe que o broker ainda vê uma única mensagem MQTT, e ela precisa estar dentro do limite do broker. No servidor MQTT público em broker.connectmq.com, pensado para testes, o limite é de 64 KB por mensagem.

Recebendo Mensagens Grandes

O streaming só ajuda nas mensagens enviadas. Nas mensagens recebidas, o PubSubClient lê o pacote inteiro para o buffer antes de chamar o seu callback. Se o pacote for maior que o buffer, a biblioteca o lê e o descarta, e o seu callback nunca é chamado. Não há erro nem log.

Por isso, em dispositivos que recebem documentos de configuração grandes ou manifestos de firmware:

  • Defina o buffer com o tamanho da maior mensagem recebida, mais o topic, mais o overhead.
  • Mantenha as mensagens recebidas compactas. Envie apenas os campos de que o dispositivo precisa, em vez de um objeto completo do backend.
  • Divida dados grandes entre vários topics, por exemplo config/network e config/sensors, cada um bem abaixo do limite.
  • Para payloads realmente grandes, como imagens de firmware, publique uma URL via MQTT e baixe o arquivo via HTTPS.

Teste o que acontece com uma mensagem propositalmente grande demais:

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

Com o buffer padrão, o dispositivo a ignora. Depois de setBufferSize(1024), o callback é disparado com um tamanho de 400.

Checklist de Depuração

Percorra esta lista quando as mensagens sumirem:

  1. Imprima mqtt.getBufferSize() após o setup para confirmar o tamanho que você acha que definiu.
  2. Registre o tamanho do topic e do payload antes de cada publicação: Serial.printf("%u + %u\n", strlen(topic), strlen(payload));.
  3. Verifique o valor de retorno de publish() e imprima mqtt.connected() quando falhar, para separar problemas de tamanho de problemas de conexão.
  4. No ESP8266, acompanhe ESP.getMaxFreeBlockSize(). Um buffer grande somado ao TLS pode esgotar a memória contígua.
  5. Compare com um cliente de desktop: se o mosquitto_sub recebe a mensagem e o dispositivo não, o buffer é pequeno demais.
  6. Garanta que a mensagem esteja dentro do limite do broker, além do buffer do dispositivo.

Se a própria conexão falhar, a causa está em outro lugar; veja PubSubClient rc=-2 e outros códigos de estado. Para o passo a passo completo da biblioteca, leia nosso tutorial do PubSubClient. Se você precisa de uma biblioteca que também receba mensagens em stream, o guia do ArduinoMqttClient mostra uma abordagem alternativa, e o tutorial IoT com ESP32 reúne essas técnicas em um projeto completo.

Limites de Mensagem do Broker

O buffer do dispositivo é só metade da história. Todo broker também limita o tamanho das mensagens, e mensagens acima do limite são rejeitadas ou causam desconexão:

Ambiente Tamanho máximo de mensagem
Broker público do ConnectMQ (broker.connectmq.com) 64 KB
ConnectMQ Serverless (Free) 256 KB
ConnectMQ Serverless (Paid) 1 MB

Em dispositivos embarcados, ficar na casa de poucos kilobytes é uma boa prática de qualquer forma. Mensagens menores significam menos RAM, registros TLS mais rápidos e menos retransmissões em Wi-Fi fraco.

Envie Payloads Maiores com o ConnectMQ

Se a sua telemetria ficou grande demais para o servidor MQTT público, ou se já é hora de levar dados reais para produção, um broker MQTT gerenciado oferece credenciais privadas, TLS e limites de mensagem maiores. O ConnectMQ é um broker MQTT na nuvem com MQTT 3.1.1 e 5.0, um cliente MQTT no navegador e um explorador de topics ao vivo, onde você pode inspecionar exatamente o que seus dispositivos enviam. Crie um broker Serverless gratuito em app.connectmq.com, sem precisar de cartão de crédito.

Perguntas frequentes

Qual é o tamanho padrão do buffer do PubSubClient?

256 bytes, definidos como MQTT_MAX_PACKET_SIZE. Esse valor cobre o pacote inteiro, incluindo o topic e os cabeçalhos, então o payload utilizável é menor.

Devo editar MQTT_MAX_PACKET_SIZE ou usar setBufferSize()?

Use setBufferSize() no PubSubClient 2.8 ou posterior. Ele sobrevive às atualizações da biblioteca e permite verificar se a alocação deu certo.

Por que publish() retorna false com uma mensagem grande?

O topic mais o payload mais o cabeçalho ultrapassam o buffer. Aumente o buffer ou use beginPublish() para enviar o payload em stream.

O PubSubClient consegue receber mensagens maiores que o seu buffer?

Não. Mensagens recebidas grandes demais são descartadas antes de o seu callback rodar. Aumente o buffer ou mantenha as mensagens recebidas menores.

Artigos relacionados

Exemplo de MQTT com ESP-IDF: Monte um Cliente esp-mqtt do Zero

Monte um projeto ESP-IDF a partir de uma pasta vazia com o componente esp-mqtt: configuração do Wi-Fi em modo station, o handler de eventos MQTT, publicação e inscrição, propriedades do MQTT 5 e TLS com o bundle de certificados.

8 min de leitura

Usando os Recursos do MQTT 5 do esp-mqtt no ESP32 com ESP-IDF

Um projeto ESP-IDF prático que ativa o MQTT 5.0 no esp-mqtt e usa user properties, expiração de mensagens, topic aliases, expiração de sessão e response topics com um broker MQTT 5 real.

8 min de leitura

Todos os artigos →

Seu broker MQTT privado está a poucos cliques de distância

Crie uma conta, receba as credenciais do seu broker e conecte seu primeiro dispositivo — sem servidores para instalar, atualizar ou escalar.