ESP32 e Arduino

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.

Placa ESP32 enviando pacotes MQTT 5 com user properties e cabeçalhos de expiração, ilustrando esp-mqtt mqtt 5 no ESP-IDF
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

O suporte esp-mqtt MQTT 5 no ESP-IDF permite que um ESP32 anexe metadados às mensagens, defina tempos de expiração, encurte topics com aliases e monte fluxos de request/response sem inventar convenções próprias. Neste tutorial, você vai ativar o MQTT 5.0 em um projeto ESP-IDF, conectar ao broker.connectmq.com e usar cada recurso em código C funcional que pode gravar hoje mesmo.

Por que MQTT 5 em um microcontrolador

O MQTT 3.1.1 é perfeitamente utilizável em um ESP32, e a maioria das bibliotecas Arduino para por aí. O MQTT 5.0 adiciona recursos que resolvem problemas reais de dispositivos:

Recurso Problema que resolve no dispositivo
User properties Enviar versão de firmware, unidades ou versão de schema sem inchar o payload JSON
Message expiry interval Impedir que comandos ou leituras desatualizados sejam entregues horas depois
Topic alias Substituir uma string de topic longa por um número de 2 bytes em publicações repetidas
Session expiry interval Manter assinaturas durante quedas curtas de Wi-Fi sem mantê-las para sempre
Response topic e correlation data Request/response padronizado, para que um backend chame um dispositivo e associe a resposta
Reason codes Saber exatamente por que um connect, publish ou subscribe foi rejeitado

O esp-mqtt, cliente MQTT incluído no ESP-IDF, implementa tudo isso por trás de uma opção de compilação. Você também precisa de um broker que fale MQTT 5.0. O ConnectMQ suporta 3.1.1 e 5.0 em todos os listeners, então o mesmo endpoint serve para firmware antigo e novo. Para o contexto do protocolo, recursos do MQTT 5 explicados cobre cada propriedade em profundidade.

Pré-requisitos

  • ESP-IDF 5.x instalado, com idf.py funcionando no terminal.
  • Qualquer placa da família ESP32 (ESP32, S3, C3, C6).
  • Clientes Mosquitto 2.x no seu computador; o mosquitto_sub consegue imprimir propriedades MQTT 5.
  • Familiaridade com um projeto MQTT básico em ESP-IDF. Se ainda não montou um, comece pelo exemplo MQTT com ESP-IDF e depois volte.

Configuração do projeto e ativação do MQTT 5.0

Crie uma pasta de projeto com esta estrutura:

mqtt5_connectmq/
  CMakeLists.txt
  sdkconfig.defaults
  main/
    CMakeLists.txt
    main.c

O CMakeLists.txt de nível superior, que inclui o auxiliar de Wi-Fi usado pelos exemplos oficiais:

cmake_minimum_required(VERSION 3.16)
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(mqtt5_connectmq)

main/CMakeLists.txt:

idf_component_register(SRCS "main.c"
                       INCLUDE_DIRS "."
                       PRIV_REQUIRES mqtt nvs_flash esp_netif esp_event protocol_examples_common)

O sdkconfig.defaults ativa os caminhos de código MQTT 5 no esp-mqtt. Sem essa opção, as funções esp_mqtt5_* não são compiladas:

CONFIG_MQTT_PROTOCOL_5=y

Depois, defina suas credenciais de Wi-Fi com idf.py menuconfig em Example Connection Configuration. Se a sua versão do ESP-IDF não traz mais o componente mqtt na árvore, adicione-o com idf.py add-dependency espressif/mqtt.

O código de exemplo esp-mqtt MQTT 5

Substitua a parte aleatória de PREFIX e o client ID pelos seus próprios valores.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "esp_log.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "nvs_flash.h"
#include "esp_random.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "protocol_examples_common.h"
#include "mqtt_client.h"

#define PREFIX     "connectmq/demo/idf5m7"
#define CLIENT_ID  "idf5-demo-m7q2"

static const char *TAG = "mqtt5";
static esp_mqtt_client_handle_t client;

/* Propriedades enviadas uma vez no CONNECT */
static esp_mqtt5_connection_property_config_t connect_property = {
    .session_expiry_interval = 600,      /* mantém a sessão 10 min após desconectar */
    .maximum_packet_size = 4096,
    .receive_maximum = 16,
    .topic_alias_maximum = 4,            /* aliases que aceitamos do broker */
    .request_resp_info = true,
    .request_problem_info = true,
    .will_delay_interval = 15,           /* espera 15 s antes de publicar o will */
    .payload_format_indicator = true,
    .message_expiry_interval = 3600,
    .content_type = "text/plain",
};

static esp_mqtt5_user_property_item_t connect_user_props[] = {
    {"model", "esp32-devkit"},
    {"fw", "1.4.2"},
};

static esp_mqtt5_user_property_item_t telemetry_user_props[] = {
    {"schema", "telemetry-v2"},
    {"unit", "celsius"},
};

static void print_user_properties(mqtt5_user_property_handle_t props)
{
    if (!props) return;
    uint8_t count = esp_mqtt5_client_get_user_property_count(props);
    if (!count) return;
    esp_mqtt5_user_property_item_t *items = malloc(count * sizeof(*items));
    if (items && esp_mqtt5_client_get_user_property(props, items, &count) == ESP_OK) {
        for (int i = 0; i < count; i++) {
            ESP_LOGI(TAG, "  user property %s = %s", items[i].key, items[i].value);
            free((char *)items[i].key);
            free((char *)items[i].value);
        }
    }
    free(items);
}

static void publish_telemetry(float temperature, int seq)
{
    char payload[96];
    int len = snprintf(payload, sizeof(payload),
                       "{\"seq\":%d,\"temperature\":%.2f}", seq, temperature);

    esp_mqtt5_publish_property_config_t pub_prop = {
        .payload_format_indicator = 1,    /* payload UTF-8 */
        .message_expiry_interval = 60,    /* inútil após um minuto */
        .topic_alias = 1,                 /* associa este topic ao alias 1 */
        .content_type = "application/json",
    };
    esp_mqtt5_client_set_user_property(&pub_prop.user_property, telemetry_user_props,
        sizeof(telemetry_user_props) / sizeof(telemetry_user_props[0]));
    esp_mqtt5_client_set_publish_property(client, &pub_prop);

    int msg_id = esp_mqtt_client_publish(client, PREFIX "/telemetry", payload, len, 1, 0);
    ESP_LOGI(TAG, "published seq %d, msg_id %d", seq, msg_id);

    esp_mqtt5_client_delete_user_property(pub_prop.user_property);
}

static void reply_to_request(esp_mqtt_event_handle_t event)
{
    char response_topic[128];
    int n = event->property->response_topic_len;
    if (n <= 0 || n >= (int)sizeof(response_topic)) return;
    memcpy(response_topic, event->property->response_topic, n);
    response_topic[n] = '\0';

    esp_mqtt5_publish_property_config_t pub_prop = {
        .correlation_data = event->property->correlation_data,
        .correlation_data_len = event->property->correlation_data_len,
        .message_expiry_interval = 30,
    };
    esp_mqtt5_client_set_publish_property(client, &pub_prop);

    char body[96];
    int len = snprintf(body, sizeof(body), "{\"ok\":true,\"freeHeap\":%lu}",
                       (unsigned long)esp_get_free_heap_size());
    esp_mqtt_client_publish(client, response_topic, body, len, 1, 0);
    ESP_LOGI(TAG, "replied on %s", response_topic);
}

static void mqtt5_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, session present: %d", event->session_present);
        esp_mqtt_client_publish(client, PREFIX "/status", "online", 0, 1, 1);

        esp_mqtt5_subscribe_property_config_t sub_prop = {
            .subscribe_id = 7,
            .no_local_flag = true,        /* não ecoa nossas próprias publicações */
        };
        esp_mqtt5_client_set_subscribe_property(client, &sub_prop);
        esp_mqtt_client_subscribe(client, PREFIX "/request", 1);
        break;
    }
    case MQTT_EVENT_DATA:
        ESP_LOGI(TAG, "data on %.*s: %.*s", event->topic_len, event->topic,
                 event->data_len, event->data);
        print_user_properties(event->property->user_property);
        if (event->property->response_topic_len > 0) {
            reply_to_request(event);
        }
        break;
    case MQTT_EVENT_DISCONNECTED:
        ESP_LOGW(TAG, "disconnected");
        print_user_properties(event->property->user_property);
        break;
    case MQTT_EVENT_ERROR:
        if (event->error_handle->error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED) {
            ESP_LOGE(TAG, "connection refused, reason code 0x%02x",
                     event->error_handle->connect_return_code);
        }
        break;
    default:
        break;
    }
}

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());

    esp_mqtt_client_config_t cfg = {
        .broker.address.uri = "mqtt://broker.connectmq.com:1883",
        .session.protocol_ver = MQTT_PROTOCOL_V_5,
        .credentials.client_id = CLIENT_ID,
        .session.last_will = {
            .topic = PREFIX "/status",
            .msg = "offline",
            .msg_len = 7,
            .qos = 1,
            .retain = 1,
        },
    };

    client = esp_mqtt_client_init(&cfg);

    esp_mqtt5_client_set_user_property(&connect_property.user_property, connect_user_props,
        sizeof(connect_user_props) / sizeof(connect_user_props[0]));
    esp_mqtt5_client_set_connect_property(client, &connect_property);
    esp_mqtt5_client_delete_user_property(connect_property.user_property);
    connect_property.user_property = NULL;

    esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt5_event_handler, NULL);
    esp_mqtt_client_start(client);

    for (int seq = 0;; seq++) {
        vTaskDelay(pdMS_TO_TICKS(5000));
        float temperature = 20.0f + (esp_random() % 500) / 100.0f;
        publish_telemetry(temperature, seq);
    }
}

Compile, grave e monitore:

idf.py set-target esp32
idf.py build flash monitor

Vendo as propriedades chegarem

O Mosquitto 2.x imprime propriedades MQTT 5 quando você assina com a versão de protocolo 5 e saída detalhada:

mosquitto_sub -h broker.connectmq.com -p 1883 -V mqttv5 -i viewer-idf5m7 \
  -t "connectmq/demo/idf5m7/#" -F "%t %P %p"

O token de formato %P imprime as user properties, então cada linha de telemetria mostra schema:telemetry-v2 unit:celsius ao lado do JSON. Agora teste o request/response. Em um terminal, escute a resposta e, em outro, envie uma requisição com response topic e correlation data:

mosquitto_sub -h broker.connectmq.com -p 1883 -V mqttv5 -i reply-idf5m7 \
  -t "connectmq/demo/idf5m7/reply/laptop" -v

mosquitto_pub -h broker.connectmq.com -p 1883 -V mqttv5 -i req-idf5m7 \
  -t "connectmq/demo/idf5m7/request" -m '{"cmd":"health"}' \
  -D publish response-topic "connectmq/demo/idf5m7/reply/laptop" \
  -D publish correlation-data "req-001"

O ESP32 registra a requisição, copia a correlation data e publica a resposta no topic escolhido por quem fez a requisição. É o mesmo padrão que um serviço de backend usa para chamar milhares de dispositivos, como os inversores de uma usina solar, sem fixar topics de resposta no código. O servidor MQTT público (gratuito) é compartilhado e pode ser lido por todos, então use-o apenas para testes e protótipos, nunca para dados de produção; a página dele explica seus limites, como a expiração de sessão limitada a 30 minutos.

Como cada recurso se comporta

User properties

User properties são pares de strings chave/valor no cabeçalho do pacote. São ideais para metadados que os consumidores podem filtrar sem interpretar o payload. Mantenha-as curtas: elas são enviadas com cada mensagem e contam no tamanho do pacote.

Message expiry interval

Uma mensagem de telemetria com expiração de 60 segundos que fica na fila para um assinante offline é descartada pelo broker quando passa de um minuto. Quando ela é entregue, o tempo de vida restante é repassado. Use expirações curtas para comandos como "abrir válvula", que nunca devem ser executados com atraso.

Topic alias

O cliente envia o topic uma vez com o alias 1 e depois pode se referir a ele pelo número. O esp-mqtt mantém sua própria tabela de aliases para isso. O CONNACK do broker informa quantos aliases ele aceita; nunca ultrapasse esse número. A economia faz mais diferença em topics longos no estilo ISA-95 sobre links lentos ou tarifados.

Expiração de sessão e atraso do will

Com session_expiry_interval = 600, o broker mantém suas assinaturas e mensagens QoS 1 em fila por dez minutos após uma desconexão, e o session_present no evento CONNECTED informa se isso aconteceu. O atraso de will de 15 segundos significa que uma oscilação rápida do Wi-Fi seguida de reconexão não faz um status offline piscar em todos os dashboards. O guia de Last Will MQTT no ESP32 compara isso com o comportamento do MQTT 3.1.1.

Response topic e correlation data

Quem faz a requisição decide para onde vai a resposta e anexa um valor de correlação opaco, e o dispositivo simplesmente devolve os dois. Isso mantém o firmware genérico: ele nunca precisa saber qual serviço ou usuário perguntou. Dê a cada solicitante seu próprio topic de resposta e mantenha a correlation data curta, como um contador ou um UUID, porque o dispositivo a copia na resposta.

Reason codes

O MQTT 5 substitui o punhado de códigos de retorno do 3.1.1 por uma lista detalhada que também cobre publish, subscribe e disconnect. Registre-os em log. Um dispositivo que informa "0x87 not authorized" ou "0x97 quota exceeded" nos logs economiza horas em comparação com um loop de reconexão silencioso, e permite distinguir erros de configuração de problemas de rede.

Checklist de solução de problemas

  1. Erros de link nas funções esp_mqtt5_*. CONFIG_MQTT_PROTOCOL_5 não está definido. Apague o sdkconfig, mantenha o sdkconfig.defaults e recompile.
  2. Conexão recusada com reason code 0x85. Identificador de cliente inválido. Use um ID não vazio e, em um broker de produção, use exatamente o client ID emitido com suas credenciais.
  3. Reason code 0x97 após muitas publicações. Cota excedida. Diminua o ritmo: o servidor MQTT público permite 10 mensagens por segundo por cliente.
  4. Erros de topic alias e desconexões. Você ultrapassou o Topic Alias Maximum do broker. Use menos aliases ou defina topic_alias como 0.
  5. Lixo ao imprimir topics. event->topic e event->data não terminam em nulo; sempre imprima com %.*s e os campos de tamanho.

Migrando para TLS com suas próprias credenciais

Para produção, mude a URI para mqtts://broker.connectmq.com:8883, adicione .broker.verification.crt_bundle_attach = esp_crt_bundle_attach (inclua esp_crt_bundle.h) e defina .credentials.username = "YOUR_USERNAME", .credentials.authentication.password = "YOUR_PASSWORD" e .credentials.client_id = "YOUR_CLIENT_ID". O bundle de certificados já contém a raiz do Let's Encrypt, então nenhum arquivo PEM é necessário. A lista completa de opções está na documentação MQTT do ESP-IDF.

Rode dispositivos MQTT 5 no ConnectMQ

As propriedades só ajudam se o broker as respeitar de ponta a ponta. O ConnectMQ é um broker MQTT gerenciado com expiração de sessão, expiração de mensagens, topic aliases, response topics e reason codes claros do MQTT 5.0, além de assinaturas compartilhadas no plano Serverless pago para escalar consumidores de backend. Crie um broker Serverless gratuito em app.connectmq.com, sem cartão de crédito, e aponte este projeto para ele. Para mais receitas de dispositivos, o tutorial de IoT com ESP32 reúne a série completa.

Perguntas frequentes

O esp-mqtt suporta MQTT 5 em todos os chips ESP32?

Sim. O suporte a MQTT 5 faz parte do componente esp-mqtt, não do chip, então funciona no ESP32, S2, S3, C3, C6 e outros alvos suportados pela sua versão do ESP-IDF, desde que CONFIG_MQTT_PROTOCOL_5 esteja ativado.

Posso misturar clientes MQTT 5 e MQTT 3.1.1 no mesmo broker?

Sim. Um broker que suporta as duas versões entrega mensagens entre elas. Assinantes MQTT 3.1.1 simplesmente não veem as propriedades do MQTT 5.

Preciso definir propriedades antes de cada publicação?

As propriedades de publicação se aplicam à próxima chamada de publish, então defina-as sempre que quiser usá-las. As propriedades de conexão são definidas uma única vez antes de iniciar o cliente.

Existe biblioteca Arduino com suporte a MQTT 5?

A maioria das bibliotecas MQTT populares para Arduino implementa apenas MQTT 3.1.1. Se você precisa de MQTT 5 em um ESP32, o ESP-IDF com esp-mqtt é a opção mais completa, e você também pode usá-lo em projetos Arduino como componente.

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

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.