Este tutorial de ESP32 MQTT leva você de um sketch Arduino vazio a uma placa que publica telemetria em JSON, recebe comandos e liga e desliga o LED embutido. Primeiro você vai se conectar à área pública gratuita do broker.connectmq.com, o servidor MQTT público da ConnectMQ, e depois migrar o mesmo sketch para TLS na porta 8883, com credenciais do portal do ConnectMQ. Cada etapa é verificada com mosquitto_sub e mosquitto_pub a partir do seu computador.
O Que Você Vai Construir
O dispositivo final faz três coisas:
- Conecta ao Wi-Fi e a um broker MQTT, com reconexão automática.
- Publica uma mensagem JSON como
{"temperature":23.41,"uptime":125}a cada cinco segundos. - Assina um topic de comandos e liga ou desliga o LED embutido ao receber
ON,OFFouTOGGLE.
A estrutura de topics mantém tudo sob um único prefixo:
| Topic | Direção | Payload de exemplo |
|---|---|---|
connectmq/demo/<deviceId>/telemetry |
O ESP32 publica | {"temperature":23.41,"uptime":125} |
connectmq/demo/<deviceId>/led/set |
O ESP32 assina | ON, OFF, TOGGLE |
connectmq/demo/<deviceId>/led/state |
O ESP32 publica (retido) | ON ou OFF |
Se termos MQTT como topic, QoS ou retained message são novidade para você, leia primeiro O Que É um Broker MQTT?.
Pré-requisitos
- Uma placa de desenvolvimento ESP32 (qualquer ESP32 DevKit serve; a maioria tem um LED azul no GPIO 2).
- Arduino IDE 2.x com o pacote de placas esp32 instalado pelo Boards Manager.
- A biblioteca PubSubClient, de Nick O'Leary, instalada pelo Library Manager.
- Uma rede Wi-Fi de 2,4 GHz (o ESP32 não suporta 5 GHz).
- Opcional: os clientes de linha de comando do Mosquitto (
mosquitto_sub,mosquitto_pub) no seu computador, para testes. - Opcional: um sensor DHT22. O sketch simula a temperatura por padrão, então nenhum hardware extra é necessário.
Endpoints do Broker para ESP32 MQTT
O broker.connectmq.com expõe quatro listeners. O ESP32 usa os dois primeiros; as portas WebSocket são para navegadores.
| Protocolo | Porta | Uso neste tutorial |
|---|---|---|
| MQTT (TCP) | 1883 | Parte 1, área pública anônima |
| MQTT sobre TLS | 8883 | Parte 2, credenciais do portal |
| WebSocket | 8083 | Não usado (clientes de navegador) |
| WebSocket seguro | 8084 | Não usado (clientes de navegador) |
A área pública não exige conta: conecte sem usuário nem senha e com um client ID único e não vazio. Lembre-se de que tudo o que é publicado ali pode ser lido por qualquer pessoa, então envie apenas dados de demonstração, nunca dados de produção. A área pública permite 64 KB por mensagem, 10 mensagens por segundo e 10 assinaturas por cliente, e até 8 níveis de topic, o que é mais do que suficiente para este projeto. Veja o guia do servidor MQTT público (gratuito) para mais detalhes.
Parte 1: ESP32 MQTT sobre TCP Simples (Porta 1883)
Gere um client ID único a partir do endereço MAC
Client IDs precisam ser únicos: se dois clientes se conectam com o mesmo ID, o broker desconecta o mais antigo. Todo ESP32 tem um endereço MAC gravado de fábrica, o que o torna uma fonte perfeita para um ID estável e único. O sketch monta algo como esp32-a1b2c3d4e5f6 e o reutiliza no prefixo dos topics.
O sketch completo
Crie um novo sketch, cole o código abaixo e defina o SSID e a senha do seu Wi-Fi.
#include <WiFi.h>
#include <PubSubClient.h>
// ---- Wi-Fi ----
const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
// ---- MQTT ----
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const int LED_PIN = 2; // LED embutido na maioria dos ESP32 DevKits
const unsigned long PUBLISH_INTERVAL_MS = 5000;
WiFiClient netClient;
PubSubClient mqtt(netClient);
String deviceId;
String topicTelemetry, topicLedSet, topicLedState;
unsigned long lastPublish = 0;
unsigned long lastReconnectAttempt = 0;
bool ledOn = false;
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.print("\nWi-Fi connected, IP: ");
Serial.println(WiFi.localIP());
}
String makeDeviceId() {
uint8_t mac[6];
WiFi.macAddress(mac);
char buf[20];
snprintf(buf, sizeof(buf), "esp32-%02x%02x%02x%02x%02x%02x",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return String(buf);
}
void publishLedState() {
mqtt.publish(topicLedState.c_str(), ledOn ? "ON" : "OFF", true); // retido
}
void onMessage(char* topic, byte* payload, unsigned int length) {
String msg;
for (unsigned int i = 0; i < length; i++) msg += (char)payload[i];
msg.trim();
Serial.printf("Message on %s: %s\n", topic, msg.c_str());
if (String(topic) != topicLedSet) return;
if (msg.equalsIgnoreCase("ON")) ledOn = true;
else if (msg.equalsIgnoreCase("OFF")) ledOn = false;
else if (msg.equalsIgnoreCase("TOGGLE")) ledOn = !ledOn;
else return; // ignora qualquer outra coisa
digitalWrite(LED_PIN, ledOn ? HIGH : LOW);
publishLedState();
}
bool connectMqtt() {
Serial.printf("Connecting to MQTT as %s ... ", deviceId.c_str());
if (mqtt.connect(deviceId.c_str())) { // sem usuário/senha: área pública
Serial.println("connected");
mqtt.subscribe(topicLedSet.c_str(), 1);
publishLedState();
return true;
}
Serial.printf("failed, state=%d\n", mqtt.state());
return false;
}
float readTemperature() {
// Sensor simulado: 22-25 °C com um pouco de ruído
return 23.5 + 1.5 * sin(millis() / 60000.0) + random(-20, 20) / 100.0;
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
connectWiFi();
deviceId = makeDeviceId();
String base = "connectmq/demo/" + deviceId;
topicTelemetry = base + "/telemetry";
topicLedSet = base + "/led/set";
topicLedState = base + "/led/state";
Serial.println("Topic prefix: " + base);
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setCallback(onMessage);
mqtt.setBufferSize(512); // o padrão é 256 bytes, veja abaixo
mqtt.setKeepAlive(30);
}
void loop() {
if (WiFi.status() != WL_CONNECTED) {
connectWiFi();
}
if (!mqtt.connected()) {
unsigned long now = millis();
if (now - lastReconnectAttempt > 5000) { // nova tentativa não bloqueante a cada 5 s
lastReconnectAttempt = now;
if (connectMqtt()) lastReconnectAttempt = 0;
}
return;
}
mqtt.loop(); // processa mensagens recebidas e o keepalive
if (millis() - lastPublish >= PUBLISH_INTERVAL_MS) {
lastPublish = millis();
char json[96];
snprintf(json, sizeof(json), "{\"temperature\":%.2f,\"uptime\":%lu}",
readTemperature(), millis() / 1000);
mqtt.publish(topicTelemetry.c_str(), json);
Serial.printf("Published %s\n", json);
}
}
Selecione a placa e a porta, faça o upload e abra o Serial Monitor em 115200 baud. Você deve ver o endereço IP, o prefixo dos topics e uma linha Published a cada cinco segundos. Copie o device ID do prefixo; você vai precisar dele no próximo passo.
Como funciona a lógica de reconexão
O sketch nunca fica bloqueado para sempre esperando o broker. Se a conexão cair, o loop() tenta de novo a cada cinco segundos enquanto o resto do programa continua rodando. Dois detalhes importam:
mqtt.loop()precisa rodar com frequência. Ele lê os pacotes recebidos e envia os pings de keepalive; se você chamardelay(10000)no loop, o broker vai considerar o cliente morto.- Depois de reconectar, o sketch assina os topics de novo. Com a clean session padrão, as assinaturas não sobrevivem a uma desconexão, então reassinar dentro de
connectMqtt()é essencial.
mqtt.state() retorna um código numérico em caso de falha: -2 geralmente indica um problema de rede (host errado, sem internet), enquanto valores positivos vêm do CONNACK do broker, por exemplo 2 para identificador rejeitado.
Usando um sensor DHT22 de verdade
Para usar dados reais, instale a "DHT sensor library" da Adafruit, ligue o pino de dados do sensor ao GPIO 4 e substitua readTemperature():
#include <DHT.h>
DHT dht(4, DHT22); // chame dht.begin() no setup()
float readTemperature() {
float t = dht.readTemperature();
return isnan(t) ? -127.0 : t; // envia um valor sentinela em vez de NaN
}
Verifique com mosquitto_sub e mosquitto_pub
Abra um terminal e assine tudo sob o prefixo do seu dispositivo. Troque esp32-a1b2c3d4e5f6 pelo ID exibido no Serial Monitor.
mosquitto_sub -h broker.connectmq.com -p 1883 -i laptop-watch-81f3 \
-t "connectmq/demo/esp32-a1b2c3d4e5f6/#" -v
A saída fica assim:
connectmq/demo/esp32-a1b2c3d4e5f6/led/state OFF
connectmq/demo/esp32-a1b2c3d4e5f6/telemetry {"temperature":23.41,"uptime":125}
connectmq/demo/esp32-a1b2c3d4e5f6/telemetry {"temperature":23.47,"uptime":130}
Agora mude o LED a partir de um segundo terminal:
mosquitto_pub -h broker.connectmq.com -p 1883 -i laptop-cmd-81f3 \
-t "connectmq/demo/esp32-a1b2c3d4e5f6/led/set" -q 1 -m "TOGGLE"
O LED muda e o subscriber imprime o novo led/state retido. Como o estado é retido, qualquer dashboard que assine depois recebe imediatamente o valor atual. A assinatura usa QoS 1 para que o comando seja confirmado pelo broker; Níveis de QoS MQTT Explicados aborda os prós e contras.
Você também pode acompanhar o dispositivo entrando e saindo: assine $SYS/broker/connection/esp32-a1b2c3d4e5f6/state e você verá 1 quando ele se conectar e 0 quando se desconectar.
O Tamanho do Buffer do PubSubClient
O PubSubClient aloca um único buffer para pacotes recebidos e enviados, e por padrão ele tem apenas 256 bytes. Esse buffer precisa comportar o pacote MQTT inteiro: cabeçalho fixo, nome do topic e payload. Um topic longo mais um documento JSON de tamanho moderado ultrapassam esse limite rapidamente e, quando isso acontece, publish() retorna false silenciosamente e mensagens recebidas que não cabem são descartadas.
Chame setBufferSize() antes de conectar e verifique o valor de retorno de publish():
| Tamanho do payload (aprox.) | setBufferSize() sugerido |
|---|---|
| Até ~150 bytes | 256 (padrão) |
| Até ~400 bytes | 512 |
| Até ~1.800 bytes | 2048 |
| Documentos maiores | Divida a mensagem ou envie só os campos alterados |
Cada byte extra sai da RAM do ESP32, então dimensione o buffer para as suas mensagens reais, e não para o máximo do broker.
Parte 2: ESP32 MQTT Seguro com TLS na Porta 8883
A área pública é perfeita para aprender, mas dispositivos de produção devem usar TLS e credenciais próprias. Entre em https://app.connectmq.com, crie um broker e copie o usuário, a senha e o client ID gerados. Clientes autenticados caem no seu próprio tenant isolado, e não na área pública.
Em conexões autenticadas, os client IDs ficam restritos ao ID emitido e às suas variantes com _sufixo. Dê a cada dispositivo seu próprio sufixo, por exemplo YOUR_CLIENT_ID_esp32a, e outro ao seu notebook.
Mudanças no sketch
Só mudam o cliente de rede, a porta, as credenciais e os topics. Baixe o certificado ISRG Root X1 (isrgrootx1.pem) da Let's Encrypt; a cadeia do certificado do broker chega até ele. Cole o conteúdo no sketch:
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <time.h>
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 8883;
const char* MQTT_USER = "YOUR_USERNAME";
const char* MQTT_PASS = "YOUR_PASSWORD";
const char* MQTT_CLIENT_ID = "YOUR_CLIENT_ID_esp32a";
// ISRG Root X1 (cole o PEM completo de isrgrootx1.pem)
static const char ROOT_CA[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
...paste the certificate body here...
-----END CERTIFICATE-----
)EOF";
WiFiClientSecure netClient;
PubSubClient mqtt(netClient);
void syncClock() {
configTime(0, 0, "pool.ntp.org", "time.nist.gov");
time_t now = time(nullptr);
while (now < 1700000000) { delay(250); now = time(nullptr); }
}
// no setup(), depois de connectWiFi():
// syncClock();
// netClient.setCACert(ROOT_CA);
// mqtt.setServer(MQTT_HOST, MQTT_PORT);
// base dos topics: "plant1/" + String(MQTT_CLIENT_ID) em vez de "connectmq/demo/..."
bool connectMqtt() {
if (mqtt.connect(MQTT_CLIENT_ID, MQTT_USER, MQTT_PASS)) {
mqtt.subscribe(topicLedSet.c_str(), 1);
publishLedState();
return true;
}
return false;
}
Algumas observações:
setCACert()faz o ESP32 verificar se está realmente falando com obroker.connectmq.com. Sincronizar o relógio via NTP antes permite que a pilha TLS confira as datas de validade do certificado.setInsecure()pula a verificação por completo. É prático para um teste de cinco minutos, mas remove a proteção contra ataques man-in-the-middle, então nunca o coloque em produção.- O TLS usa bem mais RAM e o primeiro handshake leva um instante. Isso é normal no ESP32.
- Mantenha as credenciais fora do seu repositório Git, por exemplo em um
secrets.hseparado listado no.gitignore. O checklist de segurança MQTT aborda o armazenamento de segredos nos dispositivos e outras medidas de proteção.
Verifique a conexão segura
Assine com suas próprias credenciais e um sufixo diferente. No Linux, você pode usar o repositório de CAs do sistema com --capath /etc/ssl/certs; em qualquer sistema, pode passar o certificado raiz baixado:
mosquitto_sub -h broker.connectmq.com -p 8883 --cafile isrgrootx1.pem \
-u YOUR_USERNAME -P YOUR_PASSWORD -i YOUR_CLIENT_ID_laptop \
-t "plant1/#" -v
Envie um comando da mesma forma:
mosquitto_pub -h broker.connectmq.com -p 8883 --cafile isrgrootx1.pem \
-u YOUR_USERNAME -P YOUR_PASSWORD -i YOUR_CLIENT_ID_cli \
-t "plant1/YOUR_CLIENT_ID_esp32a/led/set" -q 1 -m "ON"
Você também pode abrir o cliente MQTT no navegador ou o explorador de topics ao vivo no portal do ConnectMQ para acompanhar a telemetria sem instalar nada.
Solução de Problemas
| Sintoma | Causa provável | Solução |
|---|---|---|
state=-2 em loop |
Sem internet, host errado ou porta bloqueada | Verifique o Wi-Fi, teste a porta 1883, confira o firewall |
state=2 |
Client ID rejeitado | Use um ID único e não vazio; com credenciais, use o ID emitido ou uma variante _suffix |
state=4 ou 5 |
Credenciais inválidas | Copie de novo o usuário e a senha do portal |
| As publicações param sem erro aparente | Mensagem maior que o buffer | Aumente setBufferSize(), verifique o retorno de publish() |
| Desconexões constantes | mqtt.loop() não é chamado com frequência, ou outro cliente usa o mesmo ID |
Remova delay() longos, dê a cada cliente seu próprio ID |
| TLS falha, TCP simples funciona | CA errada, relógio não ajustado | Use o ISRG Root X1, sincronize a hora via NTP antes de conectar |
Comece com o ConnectMQ
Agora você tem um ESP32 que envia telemetria e recebe comandos via MQTT, primeiro na área pública e depois via TLS com credenciais próprias. Quando estiver pronto para ir além dos dados de demonstração, crie um broker MQTT gerenciado no plano Serverless gratuito em https://app.connectmq.com — sem cartão de crédito — e use o dashboard, a visão de dispositivos e os alertas por limite do portal para ficar de olho na sua frota.
Perguntas frequentes
Qual biblioteca MQTT devo usar no ESP32 com a Arduino IDE?
O PubSubClient é a escolha mais usada: é pequeno, estável e funciona tanto com WiFiClient quanto com WiFiClientSecure. Ele suporta MQTT 3.1.1 com publicação em QoS 0 e assinaturas em QoS 0 ou 1, o que cobre a maioria dos projetos com sensores.
Por que a publicação do meu ESP32 falha sem nenhum erro?
Na maioria das vezes, o pacote é maior que o buffer padrão de 256 bytes do PubSubClient. Chame mqtt.setBufferSize(512) ou mais antes de conectar e verifique o booleano retornado por publish().
Preciso de uma conta para testar ESP32 MQTT com o broker.connectmq.com?
Não. Conecte na porta 1883 ou 8883 sem usuário e com um client ID único para usar a área pública. Para dados reais, crie uma conta gratuita e use as credenciais geradas.
Posso usar setInsecure() com WiFiClientSecure?
Só para testes rápidos. Ele criptografa o tráfego, mas pula a verificação do servidor, então um atacante poderia se passar pelo broker. Use setCACert() com o ISRG Root X1 em tudo o que for implantado.
Como dou a várias placas ESP32 seus próprios client IDs em um mesmo broker?
Acrescente um sufixo ao client ID emitido no portal, como YOUR_CLIENT_ID_esp32a e YOUR_CLIENT_ID_esp32b. O ConnectMQ aceita o ID emitido e suas variantes com _suffix em conexões autenticadas.



