Um cliente MQTT assíncrono no ESP32 permite que o seu firmware continue lendo sensores, controlando displays e servindo páginas web enquanto o tráfego MQTT é tratado em segundo plano. Neste guia, você vai montar um sketch completo com AsyncMqttClient, com callbacks de eventos, reconexão automática e publicação com QoS 1 em um broker real. Também vai conhecer as armadilhas da biblioteca para decidir quando ela é a escolha certa.
Por Que Usar um Cliente MQTT Assíncrono
Bibliotecas bloqueantes como o PubSubClient funcionam bem, mas dependem de você chamar loop() com frequência, e connect() pode congelar o sketch até o timeout do socket quando a rede está ruim. Em um dispositivo que também aciona o motor de uma cortina de aviário em uma fazenda, atualiza uma fita de LED ou hospeda uma página de configuração, essas pausas ficam visíveis.
O AsyncMqttClient, de Marvin Roger, adota outra abordagem. Ele roda sobre a biblioteca AsyncTCP, que executa a rede em sua própria tarefa do FreeRTOS. O seu código registra callbacks para eventos (conectado, desconectado, mensagem recebida, publicação confirmada) e a biblioteca os chama quando algo acontece. Não há loop() para chamar.
| Aspecto | PubSubClient | AsyncMqttClient |
|---|---|---|
| Modelo de programação | Polling, chamar loop() |
Callbacks de eventos |
Comportamento de connect() |
Bloqueia até o CONNACK ou o timeout | Retorna imediatamente |
| QoS de publicação | 0 | 0, 1 e 2 |
| QoS de inscrição | 0 e 1 | 0, 1 e 2 |
| Payloads grandes recebidos | Precisam caber no buffer | Entregues em partes |
| TLS no ESP32 | Sim, via WiFiClientSecure |
Inviável com o AsyncTCP padrão |
| Dependências | Nenhuma | AsyncTCP |
A última linha importa. O suporte a TLS do AsyncMqttClient depende de uma compilação do AsyncTCP com SSL, que não está disponível na biblioteca AsyncTCP padrão do ESP32. Na prática, esta biblioteca é melhor para MQTT puro na porta 1883 dentro de uma rede confiável, ou para prototipagem. Se você precisa de TLS com uma API orientada a eventos, a biblioteca espMqttClient, mantida ativamente, oferece um estilo de callbacks muito parecido com suporte a TLS no ESP32. Tratamos dessas escolhas no final.
Instale as Bibliotecas
- Instale o pacote de placas "esp32 by Espressif Systems" na Arduino IDE.
- Instale o AsyncTCP para ESP32. Existem vários forks mantidos no Gerenciador de Bibliotecas; escolha um compatível com a versão do seu core ESP32.
- Instale o AsyncMqttClient. Se ele não aparecer no Gerenciador de Bibliotecas, baixe o ZIP da release no repositório do projeto e use Sketch → Include Library → Add .ZIP Library.
- Com PlatformIO, adicione os dois a
lib_depsnoplatformio.ini.
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
lib_deps =
marvinroger/AsyncMqttClient
esp32async/AsyncTCP
O Sketch Completo do Cliente MQTT Assíncrono no ESP32
Este exemplo se conecta ao broker.connectmq.com, o servidor MQTT público (gratuito) do ConnectMQ para testes e protótipos, na porta 1883. Ele reconecta o Wi-Fi e o MQTT com timers do FreeRTOS, publica telemetria com QoS 1 a cada dez segundos e alterna o LED da placa quando chega um comando.
#include <WiFi.h>
#include <AsyncMqttClient.h>
extern "C" {
#include "freertos/FreeRTOS.h"
#include "freertos/timers.h"
}
#define WIFI_SSID "YOUR_WIFI"
#define WIFI_PASSWORD "YOUR_WIFI_PASSWORD"
#define MQTT_HOST "broker.connectmq.com"
#define MQTT_PORT 1883
#define LED_PIN 2
static const char* DEVICE_ID = "async-4b81d2"; // torne-o único
String topicTelemetry = String("connectmq/demo/") + DEVICE_ID + "/telemetry";
String topicCommand = String("connectmq/demo/") + DEVICE_ID + "/led/set";
String topicStatus = String("connectmq/demo/") + DEVICE_ID + "/status";
AsyncMqttClient mqttClient;
TimerHandle_t mqttReconnectTimer;
TimerHandle_t wifiReconnectTimer;
unsigned long lastPublish = 0;
void connectToWifi() {
Serial.println("Connecting to Wi-Fi...");
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
}
void connectToMqtt() {
Serial.println("Connecting to MQTT...");
mqttClient.connect();
}
void onWifiEvent(WiFiEvent_t event) {
switch (event) {
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
Serial.print("Wi-Fi connected, IP ");
Serial.println(WiFi.localIP());
connectToMqtt();
break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
Serial.println("Wi-Fi lost");
xTimerStop(mqttReconnectTimer, 0); // sem MQTT enquanto o Wi-Fi estiver fora
xTimerStart(wifiReconnectTimer, 0);
break;
default:
break;
}
}
void onMqttConnect(bool sessionPresent) {
Serial.printf("MQTT connected, session present: %d\n", sessionPresent);
mqttClient.subscribe(topicCommand.c_str(), 1);
mqttClient.publish(topicStatus.c_str(), 1, true, "online");
}
void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) {
Serial.printf("MQTT disconnected, reason %d\n", static_cast<int>(reason));
if (WiFi.isConnected()) {
xTimerStart(mqttReconnectTimer, 0);
}
}
void onMqttSubscribe(uint16_t packetId, uint8_t qos) {
Serial.printf("Subscribe ack, packetId %u, granted QoS %u\n", packetId, qos);
}
void onMqttPublish(uint16_t packetId) {
Serial.printf("Publish ack (PUBACK), packetId %u\n", packetId);
}
void onMqttMessage(char* topic, char* payload,
AsyncMqttClientMessageProperties properties,
size_t len, size_t index, size_t total) {
// o payload não termina em nulo e pode ser apenas uma parte de uma mensagem maior
if (index != 0 || len != total) {
Serial.println("Fragmented message ignored in this demo");
return;
}
String msg;
msg.reserve(len);
for (size_t i = 0; i < len; i++) msg += payload[i];
Serial.printf("Message on %s (QoS %u): %s\n", topic, properties.qos, msg.c_str());
if (msg == "ON") digitalWrite(LED_PIN, HIGH);
if (msg == "OFF") digitalWrite(LED_PIN, LOW);
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
mqttReconnectTimer = xTimerCreate("mqttTimer", pdMS_TO_TICKS(2000), pdFALSE,
nullptr, reinterpret_cast<TimerCallbackFunction_t>(connectToMqtt));
wifiReconnectTimer = xTimerCreate("wifiTimer", pdMS_TO_TICKS(2000), pdFALSE,
nullptr, reinterpret_cast<TimerCallbackFunction_t>(connectToWifi));
WiFi.onEvent(onWifiEvent);
mqttClient.onConnect(onMqttConnect);
mqttClient.onDisconnect(onMqttDisconnect);
mqttClient.onSubscribe(onMqttSubscribe);
mqttClient.onPublish(onMqttPublish);
mqttClient.onMessage(onMqttMessage);
mqttClient.setServer(MQTT_HOST, MQTT_PORT);
mqttClient.setClientId(DEVICE_ID); // o ponteiro precisa continuar válido
mqttClient.setKeepAlive(30);
mqttClient.setWill(topicStatus.c_str(), 1, true, "offline");
connectToWifi();
}
void loop() {
// O código da sua aplicação roda livremente aqui; o MQTT é tratado em segundo plano.
if (mqttClient.connected() && millis() - lastPublish > 10000) {
lastPublish = millis();
char payload[96];
snprintf(payload, sizeof(payload), "{\"uptime\":%lu,\"rssi\":%d,\"heap\":%u}",
millis() / 1000, WiFi.RSSI(), (unsigned)ESP.getFreeHeap());
uint16_t id = mqttClient.publish(topicTelemetry.c_str(), 1, false, payload);
Serial.printf("Queued publish, packetId %u\n", id);
}
}
Acompanhe o tráfego e envie comandos a partir do seu computador:
mosquitto_sub -h broker.connectmq.com -p 1883 -i watch-4b81d2 -t "connectmq/demo/async-4b81d2/#" -v -q 1
mosquitto_pub -h broker.connectmq.com -p 1883 -i cmd-4b81d2 -t "connectmq/demo/async-4b81d2/led/set" -m ON -q 1
Como Funciona o Fluxo de Eventos
Entender a ordem dos eventos evita a maioria dos bugs:
connectToWifi()inicia a conexão Wi-Fi e retorna na hora.- Quando a estação obtém um endereço IP,
onWifiEventchamaconnectToMqtt(). mqttClient.connect()retorna imediatamente. Depois, a tarefa do AsyncTCP recebe o CONNACK e a biblioteca chamaonMqttConnect.- Dentro de
onMqttConnect, você faz as inscrições e publica o status "online". Este é o lugar certo, porque as inscrições em uma clean session precisam ser renovadas a cada reconexão. - Se o broker ou a rede derrubar você,
onMqttDisconnectagenda um timer de disparo único, que chamaconnectToMqtt()dois segundos depois.
publish() retorna um packet ID para mensagens QoS 1 e 2, 1 para uma mensagem QoS 0 colocada na fila e 0 quando a mensagem não pôde ser enviada. O onMqttPublish(packetId) correspondente é disparado quando o broker confirma a entrega, o que permite acompanhar se mensagens importantes realmente chegaram.
Armadilhas que Aparecem em Produção
Os Callbacks Rodam em Outra Tarefa
Seus callbacks são executados na tarefa do AsyncTCP, e não em loop(). Não chame delay() nem faça I/O lento ali; isso bloqueia todo o tráfego de rede e pode disparar o watchdog de tarefas. Copie os dados para uma variável ou fila e trate-os em loop(). Se loop() e um callback compartilham uma variável, proteja-a, por exemplo com uma fila do FreeRTOS ou com uma flag volatile nos casos simples.
Tempo de Vida das Strings
setClientId(), setCredentials(), setServer() com nome de host e setWill() armazenam ponteiros, não cópias. Passar o c_str() de uma String temporária faz com que lixo seja enviado na reconexão. Use constantes globais ou objetos String globais, como no exemplo.
Payloads Fragmentados
Mensagens grandes chegam em várias chamadas de onMessage, com index crescente e o mesmo total. Se você espera payloads de mais de algumas centenas de bytes, acumule as partes em um buffer de tamanho total e processe quando index + len == total.
Sem Reconexão Embutida
A biblioteca não se reconecta sozinha. O padrão com timers mostrado acima é a abordagem usual. Sempre pare o timer do MQTT quando o Wi-Fi cair; caso contrário, você vai disparar tentativas de conexão que não têm como dar certo.
Sessões Persistentes para Comandos Perdidos
Por padrão, o cliente se conecta com clean session, então os comandos enviados enquanto o dispositivo estava offline se perdem. Chame mqttClient.setCleanSession(false) antes de conectar e inscreva-se com QoS 1: o broker então mantém a sessão e enfileira as mensagens QoS 1 para o dispositivo até que ele volte. A sessão não dura para sempre; os brokers a expiram depois de um tempo (no servidor MQTT público, as sessões expiram em no máximo 30 minutos). Verifique sessionPresent em onMqttConnect para saber se o broker lembrou de você e refaça as inscrições quando não lembrou.
Os Limites do Broker Continuam Valendo
Assíncrono não significa ilimitado. O servidor MQTT público permite 10 mensagens por segundo por cliente, 10 inscrições por cliente e 64 KB por mensagem. Um loop apertado chamando publish() vai atingir esses limites rapidamente.
Escolhendo Entre Clientes Assíncronos e Bloqueantes
Use este checklist para decidir:
- Precisa de TLS na porta 8883? Escolha o PubSubClient com
WiFiClientSecureou o cliente seguro do espMqttClient. O AsyncMqttClient padrão não é uma boa opção. - Precisa publicar com QoS 1 ou 2 a partir do dispositivo? AsyncMqttClient ou espMqttClient.
- Vai rodar um servidor web (ESPAsyncWebServer) no mesmo dispositivo? O AsyncMqttClient compartilha muito bem a pilha AsyncTCP.
- Também vai usar o ESP8266? O AsyncMqttClient funciona nele com o ESPAsyncTCP, embora a memória seja mais apertada. Veja o tutorial de MQTT com ESP8266.
- Quer o código mais simples para um projeto de iniciante? O tutorial do PubSubClient é um começo mais tranquilo.
Se a sua preocupação é garantia de entrega, leia níveis de QoS do MQTT explicados antes de trocar de biblioteca só por causa do QoS 2; muitas cargas de trabalho de sensores ficam melhor com QoS 0 ou 1. O tutorial IoT com ESP32, mais amplo, mostra como o MQTT se encaixa em um projeto completo de sensores.
Configuração de Produção com o ConnectMQ
Para implantações fora do seu laboratório, use no dispositivo um broker MQTT na nuvem dedicado, em vez do servidor MQTT público. No ConnectMQ, você cria um broker no portal e recebe um usuário, uma senha e um client ID. Com o AsyncMqttClient, você os define assim, lembrando que os client IDs ficam vinculados às credenciais emitidas:
mqttClient.setServer("broker.connectmq.com", 1883);
mqttClient.setClientId("YOUR_CLIENT_ID");
mqttClient.setCredentials("YOUR_USERNAME", "YOUR_PASSWORD");
Como as credenciais na porta 1883 trafegam sem criptografia, prefira uma biblioteca com suporte a TLS para dispositivos fora de uma rede confiável.
Comece a Usar o ConnectMQ
O ConnectMQ é um broker MQTT gerenciado com MQTT 3.1.1 e 5.0, TLS, WebSockets, um explorador de topics ao vivo e alertas por limite no portal, para você acompanhar as publicações do seu ESP32 em tempo real. Crie um broker Serverless gratuito em app.connectmq.com, sem cartão de crédito, e conecte seu primeiro dispositivo assíncrono em minutos.
Perguntas frequentes
O AsyncMqttClient funciona com TLS no ESP32?
Não com a biblioteca AsyncTCP padrão, que não tem compilação com SSL para o ESP32. Use o PubSubClient com WiFiClientSecure ou uma biblioteca assíncrona mantida, como o espMqttClient, quando precisar da porta 8883.
Por que preciso de timers do FreeRTOS para reconectar?
O AsyncMqttClient não tem reconexão automática. Timers de disparo único permitem tentar de novo após um pequeno intervalo sem bloquear loop() nem a tarefa de rede.
Posso chamar delay() dentro de onMqttMessage?
Não. Os callbacks rodam na tarefa do AsyncTCP, e bloquear ali trava todo o tráfego de rede. Guarde a mensagem e processe-a em loop().
O AsyncMqttClient ainda é mantido?
A biblioteca original não recebe desenvolvimento ativo há algum tempo, embora continue muito usada. Verifique a situação do repositório antes de iniciar um produto novo de longa duração e considere o espMqttClient como alternativa mantida.



