PubSubClient é a biblioteca MQTT mais usada em placas compatíveis com Arduino, e ela é pequena o suficiente para rodar em quase qualquer coisa que tenha uma pilha de rede. Neste tutorial você vai instalá-la, conectar um ESP32 a um broker real, publicar e assinar mensagens e conhecer as três configurações que causam a maioria dos problemas em campo: tamanho do buffer, keep alive e timeout do socket. Ao final, você terá um sketch resistente a reconexões que poderá reutilizar em todos os seus projetos.
O Que É a PubSubClient (e o Que Ela Não É)
A PubSubClient, escrita por Nick O'Leary, é um cliente MQTT 3.1.1 leve. Ela não é dona da conexão de rede. Em vez disso, ela encapsula qualquer objeto que implemente a interface Client do Arduino: WiFiClient no ESP32 e no ESP8266, EthernetClient em um shield cabeado ou WiFiClientSecure quando você quer TLS. É por causa desse design que a mesma biblioteca roda em tantas placas.
Ela também é minimalista de propósito. Conhecer os limites desde o início economiza horas de depuração:
| Recurso | Comportamento da PubSubClient |
|---|---|
| Versão do protocolo | MQTT 3.1.1 (MQTT 3.1 opcional em tempo de compilação), sem MQTT 5 |
| QoS de publicação | Apenas QoS 0 |
| QoS de assinatura | QoS 0 ou QoS 1 |
| Buffer de pacote padrão | 256 bytes (topic + payload + cabeçalho) |
| Keep alive padrão | 15 segundos |
| Timeout de socket padrão | 15 segundos |
| Last Will | Suportado em connect() |
| Publicação retida | Suportada |
| Modelo de threads | Single-thread, você precisa chamar loop() com frequência |
Se você precisa de propriedades do MQTT 5 ou de publicação com QoS 2 a partir do dispositivo, procure outro cliente. Para a grande maioria dos sensores e atuadores, telemetria com QoS 0 mais assinaturas de comandos com QoS 1 é exatamente o que você quer. Nosso guia sobre níveis de QoS do MQTT explica por que essa combinação funciona bem.
Instale a PubSubClient e Prepare a Placa
- Na Arduino IDE, abra Tools → Manage Libraries, procure por "PubSubClient" e instale a biblioteca de Nick O'Leary (versão 2.8 ou mais recente).
- Instale o pacote de placas de que você precisa: "esp32 by Espressif Systems" para placas ESP32 ou "esp8266 by ESP8266 Community" para placas no estilo NodeMCU.
- Selecione sua placa e a porta e, em seguida, envie uma vez o sketch Blink vazio para confirmar que o toolchain funciona.
- Escolha um ID de dispositivo exclusivo, por exemplo
pubsub-7f3a9c. Você vai usá-lo tanto como client ID MQTT quanto como prefixo de topic.
No primeiro teste, você vai usar a área pública de broker.connectmq.com, o servidor MQTT público gratuito operado pela ConnectMQ. Ele aceita conexões anônimas na porta 1883, desde que o client ID não esteja vazio. Tudo o que é publicado ali pode ser lido por qualquer pessoa, então nunca envie credenciais reais ou dados pessoais para ele.
O Exemplo Completo de PubSubClient
O sketch abaixo se conecta ao Wi-Fi, conecta-se ao broker com um Last Will, publica uma mensagem JSON a cada cinco segundos, assina um topic de comandos e se reconecta sem bloquear o restante do seu código.
#include <WiFi.h> // use <ESP8266WiFi.h> no ESP8266
#include <PubSubClient.h>
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* DEVICE_ID = "pubsub-7f3a9c"; // torne este valor exclusivo
char topicTelemetry[64];
char topicCommand[64];
char topicStatus[64];
WiFiClient netClient;
PubSubClient mqtt(netClient);
unsigned long lastPublish = 0;
unsigned long lastReconnectAttempt = 0;
void onMessage(char* topic, byte* payload, unsigned int length) {
// o payload NÃO termina em nulo: copie-o usando o length
char msg[64];
unsigned int n = length < sizeof(msg) - 1 ? length : sizeof(msg) - 1;
memcpy(msg, payload, n);
msg[n] = '\0';
Serial.printf("Message on %s: %s\n", topic, msg);
if (strcmp(msg, "ping") == 0) {
mqtt.publish(topicStatus, "pong");
}
}
void connectWifi() {
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.print("Connecting to Wi-Fi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.printf("\nIP address: %s\n", WiFi.localIP().toString().c_str());
}
bool connectMqtt() {
// connect(clientId, user, pass, willTopic, willQos, willRetain, willMessage)
bool ok = mqtt.connect(DEVICE_ID, nullptr, nullptr,
topicStatus, 1, true, "offline");
if (ok) {
mqtt.publish(topicStatus, "online", true);
mqtt.subscribe(topicCommand, 1);
Serial.println("MQTT connected");
} else {
Serial.printf("MQTT connect failed, rc=%d\n", mqtt.state());
}
return ok;
}
void setup() {
Serial.begin(115200);
snprintf(topicTelemetry, sizeof(topicTelemetry), "connectmq/demo/%s/telemetry", DEVICE_ID);
snprintf(topicCommand, sizeof(topicCommand), "connectmq/demo/%s/cmd", DEVICE_ID);
snprintf(topicStatus, sizeof(topicStatus), "connectmq/demo/%s/status", DEVICE_ID);
connectWifi();
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setCallback(onMessage);
mqtt.setBufferSize(512); // espaço para payloads JSON maiores
mqtt.setKeepAlive(30); // segundos
mqtt.setSocketTimeout(10); // segundos
}
void loop() {
if (WiFi.status() != WL_CONNECTED) {
connectWifi();
}
if (!mqtt.connected()) {
unsigned long now = millis();
if (now - lastReconnectAttempt > 5000) {
lastReconnectAttempt = now;
connectMqtt();
}
return;
}
mqtt.loop();
if (millis() - lastPublish > 5000) {
lastPublish = millis();
char payload[96];
snprintf(payload, sizeof(payload),
"{\"uptime\":%lu,\"rssi\":%d,\"heap\":%u}",
millis() / 1000, WiFi.RSSI(), ESP.getFreeHeap());
if (!mqtt.publish(topicTelemetry, payload)) {
Serial.println("Publish failed");
}
}
}
Acompanhe a saída a partir do seu computador com as ferramentas de linha de comando do Mosquitto:
mosquitto_sub -h broker.connectmq.com -p 1883 -i watcher-7f3a9c -t "connectmq/demo/pubsub-7f3a9c/#" -v
Depois, envie um comando para a placa:
mosquitto_pub -h broker.connectmq.com -p 1883 -i sender-7f3a9c -t "connectmq/demo/pubsub-7f3a9c/cmd" -m "ping"
Você deverá ver pong aparecer no topic de status em menos de um segundo.
Como Funcionam os Callbacks da PubSubClient
A assinatura do callback é fixa: void callback(char* topic, byte* payload, unsigned int length). Três detalhes importam.
Primeiro, o payload é um ponteiro para o buffer interno da biblioteca e não termina em nulo. Tratá-lo como uma string C com Serial.println((char*)payload) imprime lixo depois da mensagem real. Sempre copie usando length, como faz o exemplo.
Segundo, o callback roda dentro de mqtt.loop(). Se você publicar de dentro do callback, a biblioteca reutiliza o mesmo buffer, então os ponteiros originais topic e payload deixam de ser válidos depois disso. Copie tudo o que precisar antes de chamar publish().
Terceiro, mantenha os callbacks curtos. Um delay(2000) dentro do callback impede que loop() atenda a conexão e pode disparar um timeout de keep alive. Defina uma flag ou armazene o valor e aja sobre ele no seu loop principal.
Tamanho do Buffer: A Armadilha Mais Comum da PubSubClient
Desde a versão 2.8, você pode chamar setBufferSize() em tempo de execução, em vez de editar MQTT_MAX_PACKET_SIZE no cabeçalho da biblioteca. O buffer precisa conter o pacote MQTT inteiro: cabeçalho fixo, tamanho do topic, topic e payload. Com o padrão de 256 bytes, um documento JSON de 220 caracteres em um topic de 40 caracteres simplesmente falha, e publish() retorna false sem qualquer outra explicação. Mensagens recebidas grandes demais são descartadas silenciosamente antes mesmo de o seu callback ser chamado.
Regras práticas:
- Dimensione o buffer para o seu maior topic mais o payload, mais cerca de 10 bytes de cabeçalho.
- Chame
setBufferSize()antes deconnect()e verifique o valor de retorno: ele retornafalsese a alocação falhar. - No ESP8266, mantenha o buffer modesto, porque a RAM é compartilhada com o TLS e o Wi-Fi.
Entramos em detalhes, incluindo o envio de payloads grandes em streaming com beginPublish(), em tamanho do buffer da PubSubClient: corrigindo falhas com mensagens grandes.
Keep Alive e Timeout de Socket
O keep alive informa ao broker por quanto tempo o cliente pode ficar em silêncio. A PubSubClient envia um PINGREQ quando nada mais foi enviado durante o intervalo, mas apenas se loop() estiver sendo chamado. Se o seu código bloquear por mais de aproximadamente uma vez e meia o intervalo de keep alive, o broker derruba a sessão e o seu Last Will é disparado.
setKeepAlive(30)é um padrão sensato para dispositivos alimentados pela rede elétrica com um Wi-Fi razoável.- Use valores maiores (de 60 a 120 segundos) em links celulares, onde cada pacote custa energia ou dados.
setSocketTimeout(10)limita por quanto tempoconnect()e as leituras podem bloquear. Valores menores evitam que uma rede instável congele seu dispositivo por 15 segundos de cada vez.
Lendo o Estado da Conexão
Quando connect() falha, mqtt.state() diz o motivo. Valores negativos são problemas no nível da rede; valores positivos são recusas do broker.
state() |
Significado |
|---|---|
| -4 | Timeout de conexão, nenhum CONNACK a tempo |
| -3 | Conexão perdida |
| -2 | Falha na conexão de rede (DNS, porta, TLS, roteamento) |
| -1 | Desconectado de forma limpa |
| 0 | Conectado |
| 1 | Versão de protocolo inválida |
| 2 | Client ID rejeitado |
| 3 | Servidor indisponível |
| 4 | Usuário ou senha inválidos |
| 5 | Não autorizado |
De longe, o mais comum é o -2. Nosso guia dedicado sobre PubSubClient rc=-2 e outros códigos de estado percorre cada valor com as respectivas correções.
Migrando para TLS e Credenciais Reais
O servidor MQTT público é perfeito para aprender, mas dispositivos em produção precisam de criptografia e de credenciais próprias. Com um broker MQTT gerenciado da ConnectMQ, você cria um broker no portal e recebe um username, uma senha e um client ID gerados. A mudança no sketch é pequena:
#include <WiFiClientSecure.h>
WiFiClientSecure netClient; // substitui o WiFiClient
PubSubClient mqtt(netClient);
// ISRG Root X1, a raiz Let's Encrypt usada por broker.connectmq.com
const char* ROOT_CA = R"EOF(
-----BEGIN CERTIFICATE-----
...paste ISRG Root X1 PEM here...
-----END CERTIFICATE-----
)EOF";
void setup() {
// ... Wi-Fi como antes ...
netClient.setCACert(ROOT_CA);
mqtt.setServer("broker.connectmq.com", 8883);
}
bool connectMqtt() {
return mqtt.connect("YOUR_CLIENT_ID", "YOUR_USERNAME", "YOUR_PASSWORD",
topicStatus, 1, true, "offline");
}
A ConnectMQ vincula cada client ID às credenciais que emitiu, então use exatamente o client ID do portal. Em um broker privado, os prefixos de topic ficam a seu critério; o prefixo público connectmq/demo/... deixa de ser necessário. Para mais ideias de proteção, veja boas práticas de segurança MQTT.
Checklist de Solução de Problemas
- Conecta e depois cai a cada poucos segundos: dois dispositivos compartilham o mesmo client ID. O broker desconecta a sessão mais antiga sempre que o segundo se conecta.
publish()retorna false: o payload mais o topic excede o buffer, ou o cliente não está conectado.- O callback nunca é disparado: você esqueceu o
mqtt.loop(), a assinatura foi feita antes de a conexão existir ou a mensagem é maior que o buffer. - Desconexões aleatórias sob carga: código bloqueante longo dentro de
loop(); divida-o ou mova-o para um timer. - Funciona na 1883, falha na 8883: certificado raiz errado, ou o relógio não está ajustado em placas que verificam as datas de validade (comum no ESP8266).
Se você está escolhendo entre bibliotecas, o guia da biblioteca ArduinoMqttClient e o cliente MQTT assíncrono para ESP32 cobrem as principais alternativas, e o tutorial de IoT com ESP32 junta todas as peças em um projeto de sensor completo.
Conecte Seus Dispositivos PubSubClient à ConnectMQ
Depois que o seu sketch estiver rodando no servidor MQTT público, migrá-lo para um broker MQTT na nuvem privado é uma mudança de duas linhas. A ConnectMQ oferece MQTT via TCP, TLS na porta 8883 e WebSockets, um cliente MQTT no navegador para testes, um explorador de topics ao vivo e regras de alerta por limite. Crie um broker Serverless gratuito em app.connectmq.com, sem cartão de crédito, e você terá credenciais em poucos minutos.
Perguntas frequentes
A PubSubClient suporta MQTT 5?
Não. A PubSubClient fala MQTT 3.1.1, que todos os brokers mais usados, incluindo a ConnectMQ, ainda suportam junto com o MQTT 5. Se você precisa de propriedades do MQTT 5 no dispositivo, escolha outra biblioteca cliente.
A PubSubClient consegue publicar com QoS 1?
Não. publish() sempre envia com QoS 0. Você pode assinar com QoS 1, de modo que os comandos enviados ao dispositivo com QoS 1 sejam entregues de forma confiável pelo broker.
Por que o payload da PubSubClient imprime caracteres estranhos?
O buffer do payload não termina em nulo. Copie length bytes para o seu próprio buffer e adicione um \0 de terminação antes de tratá-lo como string.
Qual é o tamanho máximo de mensagem na PubSubClient?
O tamanho do buffer é um valor de 16 bits, então o teto prático fica um pouco abaixo de 64 KB, mas a RAM é o limite real. Mantenha as mensagens pequenas em placas com recursos limitados e lembre-se de que o plano do seu broker também tem um tamanho máximo de mensagem.



