esp-mqtt é o cliente MQTT que vem com o framework ESP-IDF da Espressif. Ele roda na própria task do FreeRTOS, reconecta automaticamente, suporta MQTT 3.1.1 e MQTT 5 e fala TCP, TLS, WebSocket e WebSocket seguro com uma única URI. Você o configura com uma struct esp_mqtt_client_config_t, registra um handler de eventos e chama esp_mqtt_client_start().
Este tutorial monta passo a passo uma aplicação completa para ESP32: primeiro TCP puro, depois eventos, publicação, assinaturas, um Last Will e, por fim, TLS na porta 8883. Em cada etapa você verifica o resultado pelo navegador, para saber que o problema está no dispositivo, e não no seu teste, quando algo quebrar.
Preparando o projeto
Você precisa do ESP-IDF 5.x instalado (idf.py --version deve funcionar no terminal). Parta do helper de Wi-Fi usado pelos exemplos oficiais para que o tutorial foque em MQTT:
idf.py create-project mqtt_demo
cd mqtt_demo
idf.py set-target esp32Torne o componente de Wi-Fi de exemplo visível no CMakeLists.txt da raiz (antes da linha include(...project.cmake)) e declare as dependências do main. O componente mqtt faz parte do ESP-IDF, então #include "mqtt_client.h" funciona sem downloads extras nas versões 5.x.
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)idf_component_register(SRCS "main.c"
INCLUDE_DIRS "."
PRIV_REQUIRES mqtt esp_wifi esp_netif nvs_flash protocol_examples_common)Depois rode idf.py menuconfig, abra Example Connection Configuration e informe o SSID e a senha do seu Wi-Fi.
A configuração do cliente
A configuração mínima é uma URI do broker. O esquema escolhe o transporte:
| URI | Transporte | Porta |
|---|---|---|
mqtt://broker.connectmq.com:1883 | TCP | 1883 |
mqtts://broker.connectmq.com:8883 | TLS | 8883 |
ws://broker.connectmq.com:8083/mqtt | WebSocket | 8083 |
wss://broker.connectmq.com:8084/mqtt | WebSocket seguro | 8084 |
Uma configuração realista também define client ID, credenciais, keep alive e um Last Will:
#include "esp_log.h"
#include "mqtt_client.h"
static const char *TAG = "mqtt_demo";
static esp_mqtt_client_handle_t client;
#define BASE_TOPIC "testmqtt/espidf/dev01"
static esp_mqtt_client_handle_t mqtt_create(void)
{
const esp_mqtt_client_config_t cfg = {
.broker.address.uri = "mqtt://broker.connectmq.com:1883",
.credentials.client_id = "espidf-dev01",
.credentials.username = "username",
.credentials.authentication.password = "password",
.session.keepalive = 30,
.session.last_will = {
.topic = BASE_TOPIC "/status",
.msg = "offline",
.qos = 1,
.retain = 1,
},
.network.reconnect_timeout_ms = 5000,
};
return esp_mqtt_client_init(&cfg);
}Todo cliente precisa de um client ID único: dois dispositivos com o mesmo ID derrubam um ao outro do broker em um loop infinito. Em produção, derive-o do endereço MAC, ou gere um com o gerador de client ID durante os testes. Mantenha as credenciais fora do controle de versão movendo-as para opções do Kconfig.projbuild (CONFIG_MQTT_USER, CONFIG_MQTT_PASS) e definindo-as no menuconfig ou no sdkconfig.defaults.
Tratando os eventos
O esp-mqtt informa tudo pelo event loop do ESP. Um único handler recebe todos os eventos; event_data aponta para um esp_mqtt_event_t:
static void mqtt_event_handler(void *args, esp_event_base_t base,
int32_t event_id, void *event_data)
{
esp_mqtt_event_handle_t event = event_data;
switch ((esp_mqtt_event_id_t)event_id) {
case MQTT_EVENT_CONNECTED:
ESP_LOGI(TAG, "connected");
esp_mqtt_client_publish(client, BASE_TOPIC "/status", "online", 0, 1, 1);
esp_mqtt_client_subscribe(client, BASE_TOPIC "/cmd/#", 1);
break;
case MQTT_EVENT_DISCONNECTED:
ESP_LOGW(TAG, "disconnected, will retry");
break;
case MQTT_EVENT_SUBSCRIBED:
ESP_LOGI(TAG, "subscribed, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_PUBLISHED:
ESP_LOGI(TAG, "broker acked msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_DATA:
// topic e data NÃO terminam em nulo
ESP_LOGI(TAG, "%.*s = %.*s", event->topic_len, event->topic,
event->data_len, event->data);
break;
case MQTT_EVENT_ERROR:
if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
ESP_LOGE(TAG, "transport error: esp-tls 0x%x, tls stack 0x%x, errno %d",
event->error_handle->esp_tls_last_esp_err,
event->error_handle->esp_tls_stack_err,
event->error_handle->esp_transport_sock_errno);
} else if (event->error_handle->error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED) {
ESP_LOGE(TAG, "refused, CONNACK code %d",
event->error_handle->connect_return_code);
}
break;
default:
break;
}
}Três detalhes pegam quase todo mundo:
- Assine em
MQTT_EVENT_CONNECTED, não depois destart(). O cliente pode reconectar muitas vezes; assinar a cada conexão restaura suas assinaturas mesmo quando o broker não manteve a sessão. topicedatanão são strings C. Use sempre os campos_len. Mensagens grandes chegam em vários eventosMQTT_EVENT_DATA;event->current_data_offseteevent->total_data_lenpermitem remontá-las, ou aumente o buffer com.buffer.size.- O handler roda na task MQTT. Não bloqueie dentro dele. Repasse trabalho demorado para outra task por meio de uma fila do FreeRTOS.
Inicie tudo a partir de app_main:
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "protocol_examples_common.h"
void app_main(void)
{
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(example_connect()); // bloqueia até o Wi-Fi ter IP
client = mqtt_create();
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
esp_mqtt_client_start(client);
}Grave o firmware e acompanhe o log com idf.py flash monitor. Você deve ver connected seguido de subscribed.
Publicando telemetria
esp_mqtt_client_publish(client, topic, data, len, qos, retain) retorna um ID de mensagem (0 com QoS 0, -1 em caso de erro). Passe len = 0 para a biblioteca chamar strlen() em um payload de texto. Com QoS 1 e 2 a chamada escreve a partir da task que a invocou e pode esperar a rede; esp_mqtt_client_enqueue() recebe os mesmos argumentos mais um flag store e deixa o envio para a task MQTT:
static void telemetry_task(void *arg)
{
char payload[64];
for (int i = 0;; i++) {
int len = snprintf(payload, sizeof payload,
"{\"seq\":%d,\"heap\":%lu}", i,
(unsigned long)esp_get_free_heap_size());
int id = esp_mqtt_client_enqueue(client, BASE_TOPIC "/telemetry",
payload, len, 1, 0, true);
if (id < 0) ESP_LOGW(TAG, "outbox full or not connected");
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
// em app_main, depois de esp_mqtt_client_start():
// xTaskCreate(telemetry_task, "telemetry", 3072, NULL, 5, NULL);Mensagens enfileiradas enquanto o cliente está offline ficam no outbox (limitadas por CONFIG_MQTT_OUTBOX_EXPIRED_TIMEOUT_MS e pelo heap disponível) e são enviadas após a reconexão. Escolha o QoS com cuidado: veja níveis de QoS no MQTT para entender o custo de cada nível.
Verificando pelo navegador
Abra o cliente MQTT online, conecte em broker.connectmq.com (WebSocket 8084, path /mqtt) e assine testmqtt/espidf/dev01/#. Você deve ver:
testmqtt/espidf/dev01/status=online, marcado como retained.- Uma mensagem de telemetria a cada cinco segundos.
Agora publique reboot em testmqtt/espidf/dev01/cmd/system pelo navegador: o log do ESP32 imprime o topic e o payload. Por fim, puxe o cabo de alimentação. Depois de cerca de 1,5 × keep alive (45 segundos aqui) o broker publica o Last Will e o status muda para offline. Esse único teste prova que conexão, publish, subscribe e will funcionam. Como funciona o Last Will explica os tempos.
Acompanhe seu ESP32 no cliente online
Adicionando TLS na porta 8883
Mudar para TLS exige duas alterações: uma URI mqtts:// e uma forma de verificar o certificado do servidor. O mais simples é o ESP x509 certificate bundle, que contém as CAs raiz públicas mais comuns e vem ativado por padrão (CONFIG_MBEDTLS_CERTIFICATE_BUNDLE):
#include "esp_crt_bundle.h"
const esp_mqtt_client_config_t cfg = {
.broker.address.uri = "mqtts://broker.connectmq.com:8883",
.broker.verification.crt_bundle_attach = esp_crt_bundle_attach,
.credentials.client_id = "espidf-dev01",
.credentials.username = "username",
.credentials.authentication.password = "password",
};Se o seu broker usa uma CA privada ou você quer fixar uma raiz específica, embuta o arquivo PEM:
idf_component_register(SRCS "main.c" INCLUDE_DIRS "."
PRIV_REQUIRES mqtt esp_wifi esp_netif nvs_flash protocol_examples_common
EMBED_TXTFILES "ca.pem")extern const uint8_t ca_pem_start[] asm("_binary_ca_pem_start");
const esp_mqtt_client_config_t cfg = {
.broker.address.uri = "mqtts://broker.connectmq.com:8883",
.broker.verification.certificate = (const char *)ca_pem_start,
};O TLS precisa do horário correto para checar a validade do certificado e de cerca de 30–40 KB de heap extra durante o handshake; em um ESP32 ocupado, é o primeiro lugar para olhar quando as conexões falham. Nosso guia de TLS no ESP32 cobre certificados de cliente (.credentials.authentication.certificate e .key) para TLS mútuo.
Erros comuns do esp-mqtt
| Linha de log | Causa | Correção |
|---|---|---|
esp-tls: couldn't get hostname for :broker.connectmq.com: | Falha de DNS, geralmente Wi-Fi ainda não conectado | Inicie o MQTT só depois de ter IP |
mbedtls_ssl_handshake returned -0x2700 | Falha na verificação do certificado | Use o bundle ou a CA correta; confira o horário do sistema |
mbedtls_ssl_setup returned -0x7F00 | Falta de memória para o TLS | Libere heap, reduza outros buffers |
Error transport connect | Porta bloqueada ou par esquema/porta errado | Confira mqtts:// com 8883 e mqtt:// com 1883 |
MQTT_ERROR_TYPE_CONNECTION_REFUSED, código 5 | Não autorizado | Verifique usuário, senha e ACLs |
Os códigos numéricos de CONNACK e os reason codes do MQTT 5 estão no buscador de reason codes. Para dispositivos que conectam e caem a cada poucos minutos, leia desconexões de Wi-Fi e MQTT no ESP32.
Ativando o MQTT 5
Ative CONFIG_MQTT_PROTOCOL_5 no menuconfig (Component config → ESP-MQTT Configurations) e defina .session.protocol_ver = MQTT_PROTOCOL_V_5. As chamadas básicas de publish e subscribe continuam iguais; extras do MQTT 5 como user properties, message expiry e response topics são definidos com funções como esp_mqtt5_client_set_publish_property() antes do publish. Conexões recusadas passam a informar reason codes precisos em vez dos seis códigos de retorno do MQTT 3.1.1, o que facilita muito depurar a autenticação.
O esp-mqtt entrega um cliente pronto para produção com pouquíssimo código: uma struct de configuração, um handler de eventos e o transporte escolhido pela URI. Faça funcionar em TCP puro, comprove publish, subscribe e will pelo cliente online, depois mude para mqtts:// e mantenha os mesmos testes como verificação de regressão antes de cada release de firmware.
Perguntas frequentes
Preciso de uma biblioteca extra para MQTT no ESP-IDF?
Na maioria dos projetos, não. O cliente esp-mqtt vem com o ESP-IDF (header mqtt_client.h). Algumas versões mais novas do ESP-IDF o distribuem pelo component registry como espressif/mqtt, então confira a documentação da sua versão se o header não for encontrado.
Por que esp_mqtt_client_publish bloqueia?
Com QoS 1 e 2 ele escreve no socket a partir da task que o chamou e pode ficar esperando a rede. Use esp_mqtt_client_enqueue para colocar a mensagem no outbox e deixar a task MQTT enviá-la, o que é mais seguro em código com requisitos de tempo.
O esp-mqtt suporta MQTT 5?
Sim. Ative o MQTT 5 no menuconfig (Component config → ESP-MQTT Configurations) e defina session.protocol_ver como MQTT_PROTOCOL_V_5. Assim você tem reason codes, user properties e os demais extras do MQTT 5 por meio de APIs dedicadas.

