graph TB
subgraph "Dispositivos de Campo"
S1[Sensor Umidade 1<br/>ESP32-C3]
S2[Sensor Umidade 2<br/>ESP32-C3]
S3[Sensor Temperatura<br/>ESP32-C3]
V1[Válvula Zona 1<br/>ESP32-C3]
V2[Válvula Zona 2<br/>ESP32-C3]
end
subgraph "Central"
C[Controlador Central<br/>ESP32-C6]
L[Lógica de Irrigação]
end
subgraph "Nuvem"
B[Broker MQTT<br/>AWS IoT / Mosquitto]
DB[(Banco de Dados<br/>TimeSeries)]
API[REST API]
end
subgraph "Interface"
APP[App Mobile]
WEB[Dashboard Web]
end
S1 -->|Zigbee| C
S2 -->|Zigbee| C
S3 -->|Zigbee| C
C -->|Zigbee| V1
C -->|Zigbee| V2
C <-->|WiFi/MQTT| B
B --> DB
B <--> API
API <--> APP
API <--> WEB
L -.->|Decisões| C
MQTT no ESP32: Conectando Dispositivos IoT ao Mundo Real 🌐
🚀
Bem-vindo ao universo da comunicação IoT! Hoje você vai descobrir como milhões de dispositivos ao redor do mundo conversam entre si usando MQTT, um protocolo elegante e eficiente que permite desde sensores minúsculos até sistemas industriais complexos trocarem informações de forma confiável e escalável.
Imagine por um momento que você está desenvolvendo um sistema de irrigação inteligente. Sensores espalhados pelo jardim precisam comunicar dados de umidade do solo para uma central que toma decisões sobre quando e quanto irrigar. A central precisa enviar comandos para válvulas solenoides que controlam o fluxo de água. E você, de qualquer lugar do mundo, quer monitorar e controlar todo o sistema pelo celular. Como fazer tudo isso funcionar de forma eficiente, confiável e escalável? A resposta está no MQTT, e você está prestes a dominar este protocolo fundamental para o mundo IoT.
A Essência do MQTT: Entendendo o Protocolo que Move a IoT 💡
MQTT significa Message Queuing Telemetry Transport, mas não deixe o nome formal intimidá-lo. No coração do MQTT está uma ideia simples e poderosa: ao invés de dispositivos se comunicarem diretamente uns com os outros (o que seria complexo e difícil de escalar), eles conversam através de um intermediário inteligente chamado broker. Pense no broker como um carteiro super eficiente que garante que cada mensagem chegue aos destinatários certos, mesmo que eles não estejam online no momento do envio.
🎯 Por Que MQTT é Perfeito para IoT
MQTT foi projetado especificamente para cenários onde recursos são limitados e a rede pode ser instável. Cada aspecto do protocolo reflete estas restrições práticas que você encontrará no mundo real dos sistemas embarcados.
Eficiência de Banda: MQTT usa cabeçalhos minúsculos - apenas 2 bytes no mínimo. Compare isso com HTTP que pode ter centenas de bytes de overhead. Quando você tem dezenas de sensores enviando dados a cada poucos segundos, essa diferença é dramática.
Baixo Consumo de Energia: O protocolo foi otimizado para minimizar o tempo que dispositivos precisam manter o rádio ligado. Isto é importante quando seus sensores funcionam com baterias e precisam durar meses ou anos.
Confiabilidade Configurável: MQTT oferece três níveis de garantia de entrega (QoS 0, 1 e 2), permitindo que vocês escolham o equilíbrio perfeito entre confiabilidade e overhead para cada tipo de mensagem.
Desacoplamento de Dispositivos: Publicadores não precisam saber quem são os assinantes, e vice-versa. Este desacoplamento torna o sistema muito mais flexível e fácil de expandir.
A arquitetura do MQTT é fundamentalmente diferente do modelo cliente-servidor tradicional que você talvez conheça do desenvolvimento web. No MQTT, temos três atores principais: o broker (servidor central que gerencia todas as mensagens), os publishers (dispositivos que publicam dados) e os subscribers (dispositivos que se inscrevem para receber dados). Um mesmo dispositivo pode ser tanto publisher quanto subscriber simultaneamente, criando sistemas de comunicação bidirecionais extremamente flexíveis.
A beleza desta arquitetura está na sua simplicidade conceitual combinada com poder computacional notável. Quando um sensor de umidade publica uma leitura no tópico jardim/sensor1/umidade, ele simplesmente envia a mensagem para o broker e esquece. O broker então entrega essa mensagem para todos os dispositivos que se inscreveram naquele tópico. Se ninguém está inscrito, a mensagem é descartada (a menos que vocês configurem retenção). Se dez dispositivos estão inscritos, todos recebem a mensagem. O publisher não precisa saber ou se preocupar com quem está ouvindo.
Anatomia do MQTT: Compreendendo Cada Componente 🔍
Para dominar MQTT, você precisa entender profundamente cada componente do sistema e como eles interagem. Vamos explorar cada peça deste quebra-cabeças elegante.
🏢 O broker: O Coração do Sistema MQTT
O broker é o componente central que gerencia toda a comunicação. Ele mantém conexões com todos os clientes, gerencia inscrições de tópicos, e roteia mensagens entre publishers e subscribers.
Funcionalmente, o broker desempenha múltiplos papéis críticos que garantem o funcionamento suave de todo o ecossistema MQTT. Ele aceita conexões de clientes, autentica dispositivos (se configurado), mantém sessões persistentes, gerencia assinaturas de tópicos, roteia mensagens baseado em padrões de tópicos, implementa níveis de QoS, armazena mensagens retidas, e gerencia mensagens last will para detectar desconexões inesperadas.
Brokers populares no ecossistema IoT incluem Eclipse Mosquitto (leve e open-source, perfeito para projetos educacionais e prototipagem), EMQX (altamente escalável, suporta milhões de conexões simultâneas, ideal para produção), HiveMQ (comercial mas com versão gratuita, excelente para ambientes empresariais), e serviços cloud como AWS IoT Core e Azure IoT Hub que incluem brokers MQTT gerenciados.
A escolha do broker depende dos requisitos do projeto. Para o laboratório e projetos educacionais, Mosquitto rodando localmente ou em um Raspberry Pi é perfeito. Para projetos que precisam ser acessíveis pela internet, serviços cloud oferecem infraestrutura robusta sem necessidade de gerenciamento de servidores.
📝 Tópicos: O Sistema de Endereçamento do MQTT
Tópicos são strings hierárquicas que funcionam como endereços para mensagens. Eles são fundamentais para o roteamento eficiente de mensagens no MQTT.
Tópicos seguem uma estrutura hierárquica usando barras (/) como separadores, similar a caminhos de arquivos em sistemas Unix. Esta hierarquia permite organização lógica e facilita filtragem de mensagens. Por exemplo: casa/sala/temperatura, jardim/sensor1/umidade, irrigacao/zona3/valvula/estado.
A convenção de nomenclatura de tópicos é essencial para manutenibilidade do sistema. Desenvolvam uma estrutura consistente desde o início do projeto. Uma convenção típica poderia ser: [localização]/[dispositivo]/[tipo_sensor]/[medida]. Por exemplo: jardim/esp32_01/dht22/temperatura ou estufa/controlador/bomba/comando.
Caracteres especiais em tópicos têm significados especiais e vocês devem compreendê-los profundamente:
O caractere + (mais) funciona como wildcard de nível único. Ele corresponde a qualquer valor em um único nível da hierarquia. Por exemplo, jardim/+/temperatura se inscreve em todos os sensores de temperatura no jardim, independentemente do dispositivo: jardim/sensor1/temperatura, jardim/sensor2/temperatura, etc.
O caractere # (hash) funciona como wildcard multinível e deve sempre ser o último caractere do tópico. Ele corresponde a qualquer número de níveis na hierarquia. Por exemplo, jardim/# se inscreve em absolutamente tudo relacionado ao jardim: jardim/sensor1/temperatura, jardim/sensor1/umidade, jardim/zona2/valvula/estado, etc.
O caractere $ (cifrão) no início de um tópico indica tópicos especiais do sistema que não devem ser usados por aplicações. Por exemplo, $SYS/broker/clients/connected pode fornecer informações sobre quantos clientes estão conectados ao broker.
Melhores práticas para design de tópicos incluem: usar apenas letras minúsculas para evitar confusão; evitar espaços (use underscore ou camelCase); manter hierarquia consistente em todo o sistema; não começar ou terminar com /; limitar profundidade a 4-5 níveis para manter legibilidade; incluir identificador único do dispositivo quando múltiplos dispositivos do mesmo tipo existirem.
🎚️ Níveis de QoS: Garantindo Confiabilidade
Quality of Service define quanto esforço o protocolo deve fazer para garantir que uma mensagem seja entregue. Cada nível tem trade-offs entre confiabilidade e overhead.
QoS 0 - At Most Once (No Máximo Uma Vez): Este é o nível mais simples e eficiente. A mensagem é enviada uma única vez sem confirmação. É como enviar uma carta sem recibo de entrega. O protocolo não garante que a mensagem chegará e não há retransmissão. Use QoS 0 quando: perda ocasional de dados é aceitável; dados são enviados com alta frequência (atualizações de temperatura a cada segundo); a latência mínima é crítica; recursos de rede/processamento são extremamente limitados.
QoS 1 - At Least Once (Pelo Menos Uma Vez): Neste nível, o protocolo garante que a mensagem será entregue pelo menos uma vez. O receiver deve enviar um PUBACK (acknowledgment) confirmando recebimento. Se o PUBACK não chegar em tempo, o sender retransmite a mensagem. Note que isto pode resultar em duplicatas! Use QoS 1 quando: cada mensagem é importante mas duplicatas podem ser tratadas; você pode implementar deduplicação na aplicação; precisa de confirmação de entrega mas não quer o overhead de QoS 2; a maioria dos casos de IoT se encaixa aqui.
QoS 2 - Exactly Once (Exatamente Uma Vez): O nível mais alto de garantia. O protocolo usa um handshake de quatro mensagens (PUBLISH, PUBREC, PUBREL, PUBCOMP) para garantir que a mensagem seja entregue exatamente uma vez, sem duplicatas. Este é o nível mais “caro” em termos de banda e processamento. Use QoS 2 quando: duplicatas são absolutamente inaceitáveis; comandos críticos (abrir/fechar válvulas de gás, ligar/desligar máquinas perigosas); transações financeiras ou dados críticos de segurança; você pode pagar o custo de overhead do handshake de quatro mensagens.
O nível de QoS é negociado entre publisher e broker, e entre broker e subscriber independentemente. O QoS final efetivo será o mínimo entre os dois. Se você publica com QoS 2 mas o subscriber se inscreveu com QoS 0, as mensagens serão entregues com QoS 0.
Trade-offs práticos de QoS que você deve considerar: QoS 0 consome ~2 bytes de overhead por mensagem; QoS 1 consome ~4 bytes e requer armazenamento temporário até receber ACK; QoS 2 consome ~10 bytes e requer múltiplos pacotes. Em um sistema com 100 sensores publicando a cada 10 segundos, a diferença entre QoS 0 e QoS 2 pode significar 10x mais tráfego de rede.
Configurando o Ambiente MQTT: Do broker ao ESP32 ⚙️
Antes de mergulharmos no código, você precisa de um ambiente funcional para desenvolver e testar. Vamos configurar tudo passo a passo, começando pelo broker e depois preparando o ESP32 para comunicação MQTT.
🖥️ Instalando e Configurando o Mosquitto broker
Mosquitto é perfeito para nosso laboratório: leve, confiável, e completamente open-source. Vamos instalá-lo e configurá-lo para aceitar conexões dos ESP32s.
Para instalar o Mosquitto no Linux (Ubuntu/Debian):
# Atualizar repositórios
sudo apt update
# Instalar Mosquitto *broker* e cliente
sudo apt install mosquitto mosquitto-clients
# O serviço inicia automaticamente
# Verificar status
sudo systemctl status mosquitto
# Se não estiver rodando, iniciar
sudo systemctl start mosquitto
# Habilitar para iniciar no boot
sudo systemctl enable mosquittoPara Windows, baixe o instalador de mosquitto.org/download e o execute. O Mosquitto será instalado como serviço do Windows.
Para macOS:
Configuração básica do Mosquitto é feita editando o arquivo /etc/mosquitto/mosquitto.conf (Linux) ou C:\Program Files\mosquitto\mosquitto.conf (Windows). Para nosso laboratório, uma configuração simples mas segura:
# Permitir conexões de qualquer IP na rede local
listener 1883 0.0.0.0
# Permitir conexões sem autenticação (apenas para desenvolvimento!)
allow_anonymous true
# Log detalhado para debugging
log_dest file /var/log/mosquitto/mosquitto.log
log_type all
# Persistência de dados
persistence true
persistence_location /var/lib/mosquitto/IMPORTANTE: Esta configuração permite conexões anônimas, o que é adequado para laboratório mas NUNCA para produção! Em projetos reais, sempre configure autenticação:
Para criar usuários com senha:
Testando o broker com ferramentas de linha de comando que vêm com o Mosquitto:
Terminal 1 (subscriber):
Terminal 2 (publisher):
Se a mensagem aparecer no Terminal 1, seu broker está funcionando perfeitamente!
📚 Bibliotecas MQTT para ESP32
O ESP-IDF oferece suporte robusto para MQTT através de componentes nativos bem mantidos e otimizados para o hardware Espressif.
O componente MQTT do ESP-IDF implementa completamente o protocolo MQTT 3.1.1 com suporte a TLS/SSL, autenticação, todos os níveis de QoS, e recursos avançados como last will testament e mensagens retidas. É altamente otimizado para o ESP32 e integrado ao sistema de gerenciamento de eventos do ESP-IDF.
Para usar MQTT no ESP-IDF, primeiro precisamos configurar as dependências no CMakeLists.txt do projeto:
A biblioteca abstrai toda a complexidade de gerenciar conexões TCP, handshakes MQTT, e retransmissões, permitindo que você foque na lógica da aplicação. Ela usa o sistema de eventos do ESP-IDF para notificar a aplicação sobre mudanças de estado e chegada de mensagens.
Implementando MQTT no ESP32: Da Teoria à Prática 💻
Agora vamos mergulhar no código! Você verá como todos os conceitos teóricos se materializam em implementações práticas e funcionais.
🔌 Estrutura Básica de um Cliente MQTT
Vamos construir uma implementação completa passo a passo, começando com a estrutura fundamental e adicionando complexidade gradualmente.
Primeiro, os includes necessários:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "mqtt_client.h"
/*
* Tag para logging - facilita filtrar mensagens deste módulo
* nos logs do sistema
*/
static const char *TAG = "MQTT_EXEMPLO";
/*
* Handle do cliente MQTT - este ponteiro será nossa referência
* para todas as operações MQTT após inicialização
*/
esp_mqtt_client_handle_t mqtt_client = NULL;Handler de eventos MQTT é onde toda a mágica acontece. Este callback é chamado pelo sistema sempre que algo relevante ocorre na conexão MQTT:
/*
* Esta função é o coração da sua aplicação MQTT.
* Ela será chamada automaticamente pelo ESP-IDF sempre que:
* - A conexão com o *broker* for estabelecida
* - A conexão for perdida
* - Uma mensagem chegar em um tópico assinado
* - Ocorrer um erro
* - E vários outros eventos
*
* O event_id indica qual tipo de evento ocorreu.
*/
static void mqtt_event_handler(void *handler_args,
esp_event_base_t base,
int32_t event_id,
void *event_data)
{
/*
* Cast do event_data para o tipo correto.
* Todos os eventos MQTT usam esta estrutura que contém
* informações específicas sobre o evento.
*/
esp_mqtt_event_handle_t event = (esp_mqtt_event_handle_t)event_data;
/*
* Extrair o handle do cliente para facilitar acesso
*/
esp_mqtt_client_handle_t client = event->client;
/*
* Switch baseado no tipo de evento.
* Cada case trata um evento específico do ciclo de vida MQTT.
*/
switch ((esp_mqtt_event_id_t)event_id) {
case MQTT_EVENT_CONNECTED:
/*
* Evento disparado quando conexão com *broker* é estabelecida.
* Este é o momento ideal para fazer subscrições em tópicos.
*/
ESP_LOGI(TAG, "Conectado ao *broker* MQTT!");
/*
* Subscrever em tópicos de interesse.
* Cada chamada retorna um msg_id que pode ser usado
* para rastrear o sucesso da subscrição.
*/
int msg_id;
// QoS 0 - mensagens de monitoramento não críticas
msg_id = esp_mqtt_client_subscribe(client,
"jardim/+/temperatura",
0);
ESP_LOGI(TAG, "Subscrito em jardim/+/temperatura, msg_id=%d",
msg_id);
// QoS 1 - comandos importantes que não podem ser perdidos
msg_id = esp_mqtt_client_subscribe(client,
"irrigacao/comandos/#",
1);
ESP_LOGI(TAG, "Subscrito em irrigacao/comandos/#, msg_id=%d",
msg_id);
break;
case MQTT_EVENT_DISCONNECTED:
/*
* Conexão perdida com o *broker*.
* O cliente MQTT automaticamente tentará reconectar,
* mas você pode querer implementar lógica adicional aqui.
*/
ESP_LOGW(TAG, "Desconectado do *broker* MQTT");
/*
* Aqui você poderia:
* - Armazenar dados localmente até reconectar
* - Ativar um LED indicando perda de conexão
* - Ajustar comportamento de sensores/atuadores
*/
break;
case MQTT_EVENT_SUBSCRIBED:
/*
* Confirmação de que subscrição foi bem sucedida.
* O msg_id corresponde ao retornado por subscribe().
*/
ESP_LOGI(TAG, "Subscrição confirmada, msg_id=%d",
event->msg_id);
break;
case MQTT_EVENT_UNSUBSCRIBED:
/*
* Confirmação de cancelamento de subscrição.
*/
ESP_LOGI(TAG, "Cancelamento de subscrição confirmado, msg_id=%d",
event->msg_id);
break;
case MQTT_EVENT_PUBLISHED:
/*
* Confirmação de que publicação foi bem sucedida.
* Só ocorre para QoS 1 e 2.
*/
ESP_LOGI(TAG, "Mensagem publicada com sucesso, msg_id=%d",
event->msg_id);
break;
case MQTT_EVENT_DATA:
/*
* Este é o evento mais importante: chegada de nova mensagem!
* Aqui você processa os dados recebidos.
*/
ESP_LOGI(TAG, "Mensagem recebida!");
/*
* Imprimir informações sobre o tópico.
* Note que usamos printf com especificador %.*s que permite
* imprimir string sem null-terminator usando comprimento.
*/
ESP_LOGI(TAG, "Tópico: %.*s",
event->topic_len,
event->topic);
/*
* Imprimir o payload (dados) da mensagem
*/
ESP_LOGI(TAG, "Dados: %.*s",
event->data_len,
event->data);
/*
* Aqui você implementaria a lógica de negócio:
* - Parse do payload (pode ser JSON, string, binário, etc)
* - Validação dos dados
* - Ações baseadas no conteúdo (controlar atuadores, etc)
*/
processar_mensagem_mqtt(event->topic,
event->topic_len,
event->data,
event->data_len);
break;
case MQTT_EVENT_ERROR:
/*
* Algo deu errado na comunicação MQTT.
* O campo error contém detalhes específicos.
*/
ESP_LOGE(TAG, "Erro MQTT!");
if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
ESP_LOGE(TAG, "Erro na camada de transporte");
ESP_LOGE(TAG, "Código de erro: 0x%x",
event->error_handle->esp_transport_sock_errno);
}
break;
default:
/*
* Outros eventos que podem ocorrer mas não precisamos
* tratar especificamente neste exemplo.
*/
ESP_LOGI(TAG, "Evento MQTT não tratado, id: %d", event_id);
break;
}
}Função de processamento de mensagens onde a lógica de negócio acontece:
/*
* Esta função é chamada sempre que uma mensagem chega.
* Aqui você implementa a lógica específica da sua aplicação.
*
* Parâmetros:
* topic: ponteiro para string do tópico (NÃO é null-terminated!)
* topic_len: comprimento da string do tópico
* data: ponteiro para os dados da mensagem (NÃO é null-terminated!)
* data_len: comprimento dos dados
*/
static void processar_mensagem_mqtt(const char *topic,
int topic_len,
const char *data,
int data_len)
{
/*
* Precisamos trabalhar com strings null-terminated.
* Vamos criar cópias locais com terminação nula.
*/
char topic_str[128];
char data_str[256];
/*
* Copiar topic e adicionar null terminator.
* Sempre verificar limites para evitar buffer overflow!
*/
int copy_len = topic_len < (sizeof(topic_str) - 1) ?
topic_len : (sizeof(topic_str) - 1);
memcpy(topic_str, topic, copy_len);
topic_str[copy_len] = '\0';
/*
* Mesmo processo para os dados
*/
copy_len = data_len < (sizeof(data_str) - 1) ?
data_len : (sizeof(data_str) - 1);
memcpy(data_str, data, copy_len);
data_str[copy_len] = '\0';
ESP_LOGI(TAG, "Processando: tópico='%s', dados='%s'",
topic_str, data_str);
/*
* Implementar lógica baseada no tópico.
* Aqui usamos comparação de strings para decidir a ação.
*/
if (strncmp(topic_str, "irrigacao/comandos/bomba", 24) == 0) {
/*
* Comando para controlar bomba de irrigação
*/
if (strcmp(data_str, "LIGAR") == 0) {
ESP_LOGI(TAG, "Acionando bomba de irrigação");
// Aqui você chamaria função para ligar GPIO da bomba
// gpio_set_level(GPIO_BOMBA, 1);
} else if (strcmp(data_str, "DESLIGAR") == 0) {
ESP_LOGI(TAG, "Desligando bomba de irrigação");
// gpio_set_level(GPIO_BOMBA, 0);
} else {
ESP_LOGW(TAG, "Comando desconhecido para bomba: %s", data_str);
}
}
else if (strncmp(topic_str, "irrigacao/comandos/valvula/", 27) == 0) {
/*
* Comando para válvula específica.
* Extrair número da válvula do tópico.
*/
int valvula_num = atoi(&topic_str[27]);
ESP_LOGI(TAG, "Comando para válvula %d: %s",
valvula_num, data_str);
/*
* Aqui você implementaria controle da válvula específica
*/
}
else if (strstr(topic_str, "temperatura") != NULL) {
/*
* Mensagem de sensor de temperatura.
* Converter string para float para processamento.
*/
float temperatura = atof(data_str);
ESP_LOGI(TAG, "Temperatura recebida: %.2f °C", temperatura);
/*
* Implementar lógica baseada na temperatura:
* - Acionar sistema de refrigeração
* - Ajustar intensidade de irrigação
* - Enviar alertas se temperatura crítica
*/
if (temperatura > 35.0) {
ESP_LOGW(TAG, "Temperatura alta! Aumentando irrigação");
// Implementar ação apropriada
}
}
else {
/*
* Tópico não reconhecido - apenas log para debug
*/
ESP_LOGD(TAG, "Tópico não tratado: %s", topic_str);
}
}Inicialização do cliente MQTT:
/*
* Esta função configura e inicializa o cliente MQTT.
* Deve ser chamada após WiFi estar conectado.
*
* Retorna: handle do cliente MQTT ou NULL em caso de erro
*/
esp_mqtt_client_handle_t mqtt_app_start(void)
{
/*
* Estrutura de configuração do cliente MQTT.
* Todos os parâmetros de conexão são definidos aqui.
*/
esp_mqtt_client_config_t mqtt_cfg = {
/*
* URI do *broker* no formato mqtt://host:porta
* Pode usar também mqtts:// para conexão criptografada
*/
.broker.address.uri = "mqtt://192.168.1.100:1883",
/*
* Credenciais de autenticação (se o *broker* requer)
* Comente estas linhas se *broker* permite acesso anônimo
*/
// .credentials.username = "esp32_irrigacao",
// .credentials.authentication.password = "senha_segura",
/*
* Client ID - identificador único deste cliente.
* O *broker* usa isto para gerenciar sessão persistente.
* Se dois clientes com mesmo ID conectarem, o primeiro
* será desconectado!
*/
.credentials.client_id = "esp32_jardim_central",
/*
* Last Will and Testament (LWT) - "testamento"
* Se conexão for perdida inesperadamente, o *broker*
* publicará automaticamente esta mensagem.
* Muito útil para detectar dispositivos offline!
*/
.session.last_will.topic = "irrigacao/status/central",
.session.last_will.msg = "OFFLINE",
.session.last_will.msg_len = 7,
.session.last_will.qos = 1,
.session.last_will.retain = true, // Manter mensagem para novos *subscriber*s
/*
* Keep alive - intervalo em segundos para enviar ping
* ao *broker* mantendo conexão ativa.
* Valores típicos: 30-120 segundos.
*/
.session.keepalive = 60,
/*
* Desabilitar auto-reconnect se quiser controlar
* manualmente tentativas de reconexão.
* Por padrão é habilitado, o que é geralmente desejado.
*/
.network.disable_auto_reconnect = false,
};
/*
* Criar instância do cliente com a configuração
*/
mqtt_client = esp_mqtt_client_init(&mqtt_cfg);
if (mqtt_client == NULL) {
ESP_LOGE(TAG, "Falha ao criar cliente MQTT!");
return NULL;
}
/*
* Registrar handler de eventos.
* Este callback será chamado para todos os eventos MQTT.
*/
esp_mqtt_client_register_event(mqtt_client,
ESP_EVENT_ANY_ID,
mqtt_event_handler,
NULL);
/*
* Iniciar o cliente.
* Isto dispara tentativa de conexão com o *broker*.
*/
esp_err_t err = esp_mqtt_client_start(mqtt_client);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Falha ao iniciar cliente MQTT: %s",
esp_err_to_name(err));
return NULL;
}
ESP_LOGI(TAG, "Cliente MQTT iniciado com sucesso");
/*
* Publicar mensagem inicial indicando que dispositivo está online.
* Note que fazemos isto aqui ao invés de no evento CONNECTED
* porque queremos enviar com retain=true.
*/
return mqtt_client;
}📤 Publicando Dados no MQTT
Publicar dados é onde seus sensores ganham vida, enviando informações para o mundo através do broker MQTT.
Função auxiliar para publicar com diferentes QoS:
/*
* Função wrapper para simplificar publicações MQTT.
* Encapsula lógica comum e tratamento de erros.
*
* Parâmetros:
* topic: tópico onde publicar
* data: dados a publicar (pode ser string, JSON, dados binários)
* len: comprimento dos dados (use 0 para calcular automaticamente se string)
* qos: nível de qualidade de serviço (0, 1, ou 2)
* retain: se true, *broker* manterá última mensagem para novos *subscriber*s
*
* Retorna: msg_id da publicação ou -1 em caso de erro
*/
int mqtt_publish_data(const char *topic,
const char *data,
int len,
int qos,
bool retain)
{
/*
* Verificar se cliente está inicializado
*/
if (mqtt_client == NULL) {
ESP_LOGE(TAG, "Cliente MQTT não inicializado!");
return -1;
}
/*
* Se len é 0, assumir que data é string e calcular comprimento
*/
if (len == 0) {
len = strlen(data);
}
/*
* Publicar mensagem
*/
int msg_id = esp_mqtt_client_publish(mqtt_client,
topic,
data,
len,
qos,
retain ? 1 : 0);
if (msg_id < 0) {
ESP_LOGE(TAG, "Falha ao publicar em %s", topic);
return -1;
}
ESP_LOGD(TAG, "Publicado em %s: %.*s (msg_id=%d, qos=%d)",
topic, len, data, msg_id, qos);
return msg_id;
}Exemplo de task que publica dados de sensores periodicamente:
/*
* Task que simula leitura de sensores e publica via MQTT.
* Em aplicação real, você leria sensores reais aqui.
*/
void mqtt_publisher_task(void *pvParameters)
{
/*
* Buffer para construir mensagens
*/
char msg_buffer[128];
/*
* Contadores para simular dados variáveis
*/
uint32_t contador = 0;
/*
* Aguardar conexão MQTT antes de começar publicações.
* Em implementação robusta, você usaria event group ou semáforo
* para sincronização precisa.
*/
ESP_LOGI(TAG, "Task de publicação aguardando conexão MQTT...");
vTaskDelay(pdMS_TO_TICKS(5000));
ESP_LOGI(TAG, "Iniciando publicações periódicas");
while (1) {
/*
* Publicar status online periodicamente.
* Isto ajuda sistema de monitoramento detectar dispositivo ativo.
*/
mqtt_publish_data("irrigacao/status/central",
"ONLINE",
0, // calcular comprimento automaticamente
1, // QoS 1 - importante garantir entrega
true); // retain - novos *subscriber*s saberão status
/*
* Simular leitura de temperatura
*/
float temperatura = 20.0 + (rand() % 100) / 10.0;
snprintf(msg_buffer, sizeof(msg_buffer), "%.2f", temperatura);
mqtt_publish_data("jardim/central/temperatura",
msg_buffer,
0,
0, // QoS 0 - dados frequentes, perda tolerável
false);
/*
* Simular leitura de umidade do solo
*/
float umidade = 30.0 + (rand() % 500) / 10.0;
snprintf(msg_buffer, sizeof(msg_buffer), "%.2f", umidade);
mqtt_publish_data("jardim/sensor1/umidade",
msg_buffer,
0,
0,
false);
/*
* Publicar dados em formato JSON estruturado.
* JSON facilita parsing no backend e permite
* enviar múltiplos valores em uma mensagem.
*/
snprintf(msg_buffer, sizeof(msg_buffer),
"{\"temp\":%.2f,\"umid\":%.2f,\"cnt\":%lu}",
temperatura, umidade, contador++);
mqtt_publish_data("jardim/central/telemetria",
msg_buffer,
0,
1, // QoS 1 - dados agregados são importantes
false);
/*
* Aguardar intervalo antes da próxima publicação.
* Ajuste baseado nos requisitos da aplicação:
* - Sensores de temperatura: 30-60 segundos
* - Sensores de movimento: imediato quando detecta
* - Dados de debug: 5-10 segundos
*/
vTaskDelay(pdMS_TO_TICKS(10000)); // 10 segundos
}
}Exemplo avançado com publicação baseada em eventos:
/*
* Sistema de publicação baseado em eventos.
* Ao invés de polling periódico, publica imediatamente
* quando algo interessante acontece.
*/
/* Event group para sinalizar eventos de sensores */
static EventGroupHandle_t sensor_events;
#define TEMP_MUDOU_BIT (1 << 0)
#define UMIDADE_MUDOU_BIT (1 << 1)
#define MOVIMENTO_BIT (1 << 2)
/*
* Esta task aguarda eventos e publica dados relevantes
*/
void mqtt_event_publisher_task(void *pvParameters)
{
char msg_buffer[128];
EventBits_t bits;
while (1) {
/*
* Bloquear aguardando qualquer evento de sensor.
* Timeout de 30 segundos para publicar heartbeat mesmo
* se nenhum evento ocorrer.
*/
bits = xEventGroupWaitBits(
sensor_events,
TEMP_MUDOU_BIT | UMIDADE_MUDOU_BIT | MOVIMENTO_BIT,
pdTRUE, // Limpar bits após retornar
pdFALSE, // Retornar se qualquer bit estiver setado
pdMS_TO_TICKS(30000) // Timeout de 30s
);
if (bits & TEMP_MUDOU_BIT) {
/*
* Temperatura mudou significativamente
*/
float nova_temp = ler_temperatura();
snprintf(msg_buffer, sizeof(msg_buffer), "%.2f", nova_temp);
mqtt_publish_data("jardim/central/temperatura",
msg_buffer, 0, 1, false);
ESP_LOGI(TAG, "Temperatura atualizada: %.2f °C", nova_temp);
}
if (bits & UMIDADE_MUDOU_BIT) {
/*
* Umidade mudou significativamente
*/
float nova_umidade = ler_umidade();
snprintf(msg_buffer, sizeof(msg_buffer), "%.2f", nova_umidade);
mqtt_publish_data("jardim/sensor1/umidade",
msg_buffer, 0, 1, false);
ESP_LOGI(TAG, "Umidade atualizada: %.2f %%", nova_umidade);
}
if (bits & MOVIMENTO_BIT) {
/*
* Movimento detectado - publicar imediatamente!
*/
mqtt_publish_data("jardim/seguranca/movimento",
"DETECTADO",
0,
1, // QoS 1 - não queremos perder alertas
false);
ESP_LOGW(TAG, "Movimento detectado no jardim!");
}
if (bits == 0) {
/*
* Timeout - nenhum evento ocorreu.
* Publicar heartbeat para indicar que dispositivo está vivo.
*/
mqtt_publish_data("irrigacao/status/heartbeat",
"OK",
0,
0,
false);
}
}
}
/*
* ISR ou callback de sensor que dispara evento.
* Exemplo de como ISR notificaria task MQTT.
*/
void IRAM_ATTR sensor_temperatura_isr(void *arg)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/*
* Setar bit indicando que temperatura mudou.
* Fazer isto de ISR é seguro com event groups.
*/
xEventGroupSetBitsFromISR(sensor_events,
TEMP_MUDOU_BIT,
&xHigherPriorityTaskWoken);
/*
* Se task de maior prioridade foi acordada, fazer context switch
*/
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}Padrões Avançados de MQTT para Sistemas IoT Robustos 🏗️
Agora que você domina o básico, vamos explorar padrões e técnicas avançadas que separam sistemas de hobby de sistemas prontos para produção.
🔒 Implementando Segurança com TLS/SSL
Em produção, NUNCA transmita dados sensíveis ou comandos críticos sem criptografia. MQTT sobre TLS (MQTTS) garante que dados não possam ser interceptados ou modificados.
Configuração de conexão segura:
/*
* Certificado raiz CA em formato PEM.
* Em produção, este deve ser embutido no firmware ou
* carregado de armazenamento seguro.
*/
extern const uint8_t mqtt_broker_ca_pem_start[] asm("_binary_ca_cert_pem_start");
extern const uint8_t mqtt_broker_ca_pem_end[] asm("_binary_ca_cert_pem_end");
esp_mqtt_client_handle_t mqtt_app_start_secure(void)
{
esp_mqtt_client_config_t mqtt_cfg = {
/*
* URI com mqtts:// para ativar TLS
* Porta padrão para MQTTS é 8883
*/
.broker.address.uri = "mqtts://mqtt.servidor-producao.com:8883",
/*
* Credenciais de autenticação
*/
.credentials.username = "esp32_dispositivo_001",
.credentials.authentication.password = "senha_forte_unica",
/*
* Configuração de certificados TLS.
* O *broker* deve apresentar certificado válido assinado por esta CA.
*/
.broker.verification.certificate = (const char *)mqtt_broker_ca_pem_start,
.broker.verification.certificate_len = mqtt_broker_ca_pem_end - mqtt_broker_ca_pem_start,
/*
* Opcionalmente, autenticação mútua (client certificate).
* Isto garante que apenas dispositivos autorizados podem conectar.
*/
// .credentials.authentication.certificate = client_cert_pem_start,
// .credentials.authentication.certificate_len = client_cert_pem_end - client_cert_pem_start,
// .credentials.authentication.key = client_key_pem_start,
// .credentials.authentication.key_len = client_key_pem_end - client_key_pem_start,
.credentials.client_id = "esp32_dispositivo_001",
.session.keepalive = 60,
};
mqtt_client = esp_mqtt_client_init(&mqtt_cfg);
esp_mqtt_client_register_event(mqtt_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
esp_mqtt_client_start(mqtt_client);
return mqtt_client;
}Para incluir certificados no firmware, adicione ao CMakeLists.txt:
💾 Gerenciamento de Sessão Persistente
Sessões persistentes permitem que o broker mantenha subscrições e mensagens QoS 1/2 mesmo quando dispositivo desconecta temporariamente.
esp_mqtt_client_config_t mqtt_cfg = {
.broker.address.uri = "mqtt://broker.local:1883",
/*
* Clean session = false significa que *broker* deve manter
* estado da sessão entre conexões.
*/
.session.protocol_ver = MQTT_PROTOCOL_V_3_1_1,
.session.disable_clean_session = false, // false = sessão persistente
/*
* Client ID deve ser único e consistente entre reinicializações
* para que *broker* associe sessões corretamente.
*/
.credentials.client_id = "esp32_sensor_12345",
.session.keepalive = 60,
};Benefícios de sessões persistentes:
- Mensagens QoS 1 e 2 publicadas enquanto dispositivo estava offline serão entregues quando reconectar
- Subscrições não precisam ser refeitas a cada conexão
- Economiza banda e reduz latência de reconexão
- Essencial para dispositivos que dormem periodicamente (deep sleep)
Custos de sessões persistentes:
- broker precisa alocar memória para manter estado de cada cliente
- Mensagens acumulam no broker se dispositivo ficar offline por muito tempo
- Pode causar rajada de mensagens antigas ao reconectar
📦 Buffering de Mensagens Durante Desconexões
Sistemas robustos não perdem dados quando conexão falha temporariamente. Implementar buffer local garante que dados críticos sejam eventualmente transmitidos.
/*
* Estrutura para armazenar mensagens pendentes
*/
typedef struct {
char topic[128];
char data[256];
int qos;
bool retain;
uint32_t timestamp;
} mqtt_pending_msg_t;
/*
* Fila para armazenar mensagens quando desconectado
*/
#define MAX_PENDING_MESSAGES 50
static QueueHandle_t pending_messages_queue;
static bool mqtt_connected = false;
/*
* Inicializar sistema de buffering
*/
void mqtt_buffer_init(void)
{
pending_messages_queue = xQueueCreate(MAX_PENDING_MESSAGES,
sizeof(mqtt_pending_msg_t));
if (pending_messages_queue == NULL) {
ESP_LOGE(TAG, "Falha ao criar fila de mensagens pendentes!");
}
}
/*
* Publicar mensagem com buffering automático.
* Se desconectado, mensagem vai para fila.
* Se conectado, envia imediatamente.
*/
int mqtt_publish_buffered(const char *topic,
const char *data,
int qos,
bool retain)
{
if (mqtt_connected && mqtt_client != NULL) {
/*
* Conectado - enviar imediatamente
*/
return mqtt_publish_data(topic, data, 0, qos, retain);
} else {
/*
* Desconectado - adicionar à fila
*/
mqtt_pending_msg_t msg;
strncpy(msg.topic, topic, sizeof(msg.topic) - 1);
msg.topic[sizeof(msg.topic) - 1] = '\0';
strncpy(msg.data, data, sizeof(msg.data) - 1);
msg.data[sizeof(msg.data) - 1] = '\0';
msg.qos = qos;
msg.retain = retain;
msg.timestamp = esp_timer_get_time() / 1000000; // segundos
if (xQueueSend(pending_messages_queue, &msg, 0) != pdTRUE) {
ESP_LOGW(TAG, "Fila de mensagens pendentes cheia! Descartando mensagem antiga.");
/*
* Fila cheia - remover mensagem mais antiga para abrir espaço
*/
mqtt_pending_msg_t old_msg;
xQueueReceive(pending_messages_queue, &old_msg, 0);
xQueueSend(pending_messages_queue, &msg, 0);
}
ESP_LOGI(TAG, "Mensagem armazenada para envio posterior: %s", topic);
return 0;
}
}
/*
* Task que envia mensagens pendentes quando reconecta
*/
void mqtt_flush_pending_task(void *pvParameters)
{
mqtt_pending_msg_t msg;
while (1) {
/*
* Aguardar até estar conectado
*/
while (!mqtt_connected) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
/*
* Tentar enviar todas as mensagens pendentes
*/
while (xQueueReceive(pending_messages_queue, &msg, 0) == pdTRUE) {
ESP_LOGI(TAG, "Enviando mensagem pendente: %s", msg.topic);
int result = mqtt_publish_data(msg.topic,
msg.data,
0,
msg.qos,
msg.retain);
if (result < 0) {
/*
* Falha ao enviar - devolver à fila para tentar depois
*/
ESP_LOGW(TAG, "Falha ao enviar mensagem pendente, tentando depois");
xQueueSendToFront(pending_messages_queue, &msg, 0);
break;
}
/*
* Pequena pausa entre mensagens para não sobrecarregar *broker*
*/
vTaskDelay(pdMS_TO_TICKS(100));
}
/*
* Verificar novamente daqui a pouco
*/
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
/*
* Atualizar handler de eventos para rastrear estado de conexão
*/
static void mqtt_event_handler_enhanced(void *handler_args,
esp_event_base_t base,
int32_t event_id,
void *event_data)
{
esp_mqtt_event_handle_t event = (esp_mqtt_event_handle_t)event_data;
switch ((esp_mqtt_event_id_t)event_id) {
case MQTT_EVENT_CONNECTED:
ESP_LOGI(TAG, "Conectado ao *broker* MQTT!");
mqtt_connected = true;
/*
* Fazer subscrições...
*/
break;
case MQTT_EVENT_DISCONNECTED:
ESP_LOGW(TAG, "Desconectado do *broker* MQTT");
mqtt_connected = false;
break;
/*
* Outros casos...
*/
}
}🔄 Descoberta Automática e Configuração Dinâmica
Sistemas escaláveis usam descoberta automática para adicionar novos dispositivos sem reconfiguração manual.
Padrão de descoberta baseado em tópicos:
/*
* Ao iniciar, dispositivo publica suas capacidades
*/
void mqtt_publish_device_capabilities(void)
{
/*
* Mensagem JSON descrevendo dispositivo e suas capacidades
*/
const char *capabilities = "{"
"\"device_id\":\"esp32_sensor_001\","
"\"type\":\"sensor_node\","
"\"capabilities\":[\"temperature\",\"humidity\",\"soil_moisture\"],"
"\"location\":\"jardim_zona_1\","
"\"version\":\"1.0.0\","
"\"sensors\":["
"{\"type\":\"DHT22\",\"pin\":4},"
"{\"type\":\"capacitive\",\"pin\":34}"
"]"
"}";
/*
* Publicar em tópico de descoberta com retain=true
* para que novos *subscriber*s descubram dispositivo
*/
mqtt_publish_data("iot/discovery/sensors",
capabilities,
0,
1,
true); // retain = true
}
/*
* Subscrever em tópico de configuração dinâmica
*/
void mqtt_subscribe_configuration(void)
{
/*
* Tópico específico para este dispositivo receber configurações
*/
esp_mqtt_client_subscribe(mqtt_client,
"iot/config/esp32_sensor_001",
1);
/*
* Tópico broadcast para todos os dispositivos
*/
esp_mqtt_client_subscribe(mqtt_client,
"iot/config/broadcast",
1);
}
/*
* Processar comandos de configuração
*/
void processar_configuracao_mqtt(const char *data, int data_len)
{
/*
* Em implementação real, você usaria biblioteca JSON
* como cJSON para parsing robusto
*/
ESP_LOGI(TAG, "Configuração recebida: %.*s", data_len, data);
/*
* Exemplo de configurações que poderiam ser ajustadas:
* - Intervalo de leitura de sensores
* - Limites de temperatura para alertas
* - Calibração de sensores
* - Modo de operação (normal, economia de energia, debug)
*/
/*
* Aplicar configurações e salvar em NVS para persistir
* após reinicialização
*/
}Integração com Projeto Integrador: Sistema de Irrigação Completo 🌱
Agora vamos ver como MQTT se integra perfeitamente ao projeto de irrigação inteligente que estou desenvolvendo.
🏗️ Arquitetura do Sistema com MQTT
O sistema de irrigação usa MQTT como espinha dorsal de comunicação entre todos os componentes.
Tópicos do sistema de irrigação:
/*
* Hierarquia de tópicos bem organizada
*/
// Status de dispositivos
#define TOPIC_STATUS_CENTRAL "irrigacao/central/status"
#define TOPIC_STATUS_SENSOR_FMT "irrigacao/sensor/%d/status"
#define TOPIC_STATUS_VALVULA_FMT "irrigacao/valvula/%d/status"
// Dados de sensores
#define TOPIC_UMIDADE_FMT "irrigacao/sensor/%d/umidade"
#define TOPIC_TEMPERATURA_FMT "irrigacao/sensor/%d/temperatura"
// Comandos para atuadores
#define TOPIC_VALVULA_CMD_FMT "irrigacao/valvula/%d/comando"
#define TOPIC_BOMBA_CMD "irrigacao/bomba/comando"
// Configuração
#define TOPIC_CONFIG_BROADCAST "irrigacao/config/broadcast"
#define TOPIC_CONFIG_CENTRAL "irrigacao/config/central"
#define TOPIC_CONFIG_SENSOR_FMT "irrigacao/config/sensor/%d"
// Alertas e eventos
#define TOPIC_ALERTA "irrigacao/alertas"
#define TOPIC_EVENTO_FMT "irrigacao/eventos/%s"
// Telemetria agregada
#define TOPIC_TELEMETRIA "irrigacao/telemetria"Implementação do controlador central:
/*
* Estrutura de dados para gerenciar estado do sistema
*/
typedef struct {
float umidade_solo[4]; // 4 sensores de umidade
float temperatura_ambiente;
bool valvulas_estado[4]; // Estado de 4 válvulas
bool bomba_ligada;
uint32_t ultimo_update[4]; // Timestamp última atualização de cada sensor
} sistema_irrigacao_t;
static sistema_irrigacao_t sistema_state = {0};
/*
* Lógica de decisão de irrigação
*/
void logica_irrigacao_task(void *pvParameters)
{
char topic[128];
char comando[32];
while (1) {
uint32_t agora = esp_timer_get_time() / 1000000;
/*
* Verificar cada zona de irrigação
*/
for (int zona = 0; zona < 4; zona++) {
/*
* Verificar se dados do sensor estão atualizados
* (recebidos nos últimos 5 minutos)
*/
if ((agora - sistema_state.ultimo_update[zona]) > 300) {
ESP_LOGW(TAG, "Sensor zona %d sem resposta há %lu segundos",
zona, agora - sistema_state.ultimo_update[zona]);
/*
* Publicar alerta
*/
snprintf(topic, sizeof(topic),
"irrigacao/alertas");
snprintf(comando, sizeof(comando),
"SENSOR_ZONA_%d_SEM_RESPOSTA", zona);
mqtt_publish_data(topic, comando, 0, 1, false);
continue; // Não tomar decisões sem dados válidos
}
/*
* Lógica de irrigação baseada em umidade
*/
float umidade = sistema_state.umidade_solo[zona];
if (umidade < 30.0 && !sistema_state.valvulas_estado[zona]) {
/*
* Umidade baixa - ligar irrigação
*/
ESP_LOGI(TAG, "Zona %d: umidade baixa (%.1f%%), ligando irrigação",
zona, umidade);
/*
* Ligar bomba se não estiver ligada
*/
if (!sistema_state.bomba_ligada) {
mqtt_publish_data("irrigacao/bomba/comando",
"LIGAR", 0, 1, false);
sistema_state.bomba_ligada = true;
vTaskDelay(pdMS_TO_TICKS(2000)); // Aguardar bomba ligar
}
/*
* Abrir válvula da zona
*/
snprintf(topic, sizeof(topic),
"irrigacao/valvula/%d/comando", zona);
mqtt_publish_data(topic, "ABRIR", 0, 1, false);
sistema_state.valvulas_estado[zona] = true;
}
else if (umidade > 70.0 && sistema_state.valvulas_estado[zona]) {
/*
* Umidade alta - desligar irrigação
*/
ESP_LOGI(TAG, "Zona %d: umidade adequada (%.1f%%), desligando irrigação",
zona, umidade);
snprintf(topic, sizeof(topic),
"irrigacao/valvula/%d/comando", zona);
mqtt_publish_data(topic, "FECHAR", 0, 1, false);
sistema_state.valvulas_estado[zona] = false;
/*
* Verificar se todas as válvulas estão fechadas
* para desligar bomba
*/
bool alguma_aberta = false;
for (int i = 0; i < 4; i++) {
if (sistema_state.valvulas_estado[i]) {
alguma_aberta = true;
break;
}
}
if (!alguma_aberta && sistema_state.bomba_ligada) {
mqtt_publish_data("irrigacao/bomba/comando",
"DESLIGAR", 0, 1, false);
sistema_state.bomba_ligada = false;
}
}
}
/*
* Publicar telemetria agregada periodicamente
*/
char telemetria[512];
snprintf(telemetria, sizeof(telemetria),
"{"
"\"timestamp\":%lu,"
"\"umidade\":[%.1f,%.1f,%.1f,%.1f],"
"\"temperatura\":%.1f,"
"\"valvulas\":[%d,%d,%d,%d],"
"\"bomba\":%d"
"}",
agora,
sistema_state.umidade_solo[0],
sistema_state.umidade_solo[1],
sistema_state.umidade_solo[2],
sistema_state.umidade_solo[3],
sistema_state.temperatura_ambiente,
sistema_state.valvulas_estado[0],
sistema_state.valvulas_estado[1],
sistema_state.valvulas_estado[2],
sistema_state.valvulas_estado[3],
sistema_state.bomba_ligada);
mqtt_publish_data("irrigacao/telemetria", telemetria, 0, 0, false);
/*
* Aguardar antes da próxima iteração
*/
vTaskDelay(pdMS_TO_TICKS(10000)); // Verificar a cada 10 segundos
}
}
/*
* Handler de mensagens MQTT do controlador central
*/
static void processar_mensagem_central(const char *topic,
int topic_len,
const char *data,
int data_len)
{
char topic_str[128];
char data_str[256];
/*
* Converter para strings null-terminated
*/
int copy_len = topic_len < (sizeof(topic_str) - 1) ?
topic_len : (sizeof(topic_str) - 1);
memcpy(topic_str, topic, copy_len);
topic_str[copy_len] = '\0';
copy_len = data_len < (sizeof(data_str) - 1) ?
data_len : (sizeof(data_str) - 1);
memcpy(data_str, data, copy_len);
data_str[copy_len] = '\0';
/*
* Processar dados de sensores
*/
if (strstr(topic_str, "/umidade") != NULL) {
/*
* Extrair número do sensor do tópico
*/
int sensor_num = -1;
sscanf(topic_str, "irrigacao/sensor/%d/umidade", &sensor_num);
if (sensor_num >= 0 && sensor_num < 4) {
sistema_state.umidade_solo[sensor_num] = atof(data_str);
sistema_state.ultimo_update[sensor_num] = esp_timer_get_time() / 1000000;
ESP_LOGI(TAG, "Sensor %d: umidade = %.1f%%",
sensor_num, sistema_state.umidade_solo[sensor_num]);
}
}
else if (strstr(topic_str, "/temperatura") != NULL) {
sistema_state.temperatura_ambiente = atof(data_str);
ESP_LOGI(TAG, "Temperatura ambiente: %.1f °C",
sistema_state.temperatura_ambiente);
}
/*
* Processar comandos manuais do app/web
*/
else if (strncmp(topic_str, "irrigacao/valvula/", 18) == 0 &&
strstr(topic_str, "/comando") != NULL) {
int valvula_num = -1;
sscanf(topic_str, "irrigacao/valvula/%d/comando", &valvula_num);
if (valvula_num >= 0 && valvula_num < 4) {
/*
* Repassar comando via Zigbee para dispositivo
* (implementação específica do Zigbee omitida)
*/
ESP_LOGI(TAG, "Comando manual para válvula %d: %s",
valvula_num, data_str);
// enviar_comando_zigbee(valvula_num, data_str);
/*
* Atualizar estado local
*/
if (strcmp(data_str, "ABRIR") == 0) {
sistema_state.valvulas_estado[valvula_num] = true;
} else if (strcmp(data_str, "FECHAR") == 0) {
sistema_state.valvulas_estado[valvula_num] = false;
}
}
}
}Implementação de nó sensor:
/*
* Código para ESP32-C3 que atua como sensor de umidade
*/
void sensor_node_task(void *pvParameters)
{
char topic[128];
char msg[64];
int sensor_id = 1; // ID único deste sensor
/*
* Configurar GPIO para ler sensor capacitivo de umidade
*/
adc_oneshot_unit_handle_t adc_handle;
// ... configuração ADC omitida para brevidade
while (1) {
/*
* Ler sensor de umidade do solo
*/
int adc_reading = 0;
adc_oneshot_read(adc_handle, ADC_CHANNEL_0, &adc_reading);
/*
* Converter leitura ADC para percentual de umidade
* (calibração específica do sensor)
*/
float umidade = (4095 - adc_reading) * 100.0 / 4095.0;
/*
* Publicar via MQTT
*/
snprintf(topic, sizeof(topic),
"irrigacao/sensor/%d/umidade", sensor_id);
snprintf(msg, sizeof(msg), "%.2f", umidade);
mqtt_publish_data(topic, msg, 0, 0, false);
ESP_LOGI(TAG, "Publicado: %s = %.2f%%", topic, umidade);
/*
* Aguardar intervalo antes da próxima leitura
* Em produção, poderia entrar em deep sleep aqui
* para economizar bateria
*/
vTaskDelay(pdMS_TO_TICKS(30000)); // 30 segundos
}
}Debugging e Monitoramento de Sistemas MQTT 🔧
Sistemas MQTT em produção requerem ferramentas robustas de debugging e monitoramento para diagnosticar problemas rapidamente.
🔍 Ferramentas Essenciais de Debug
Dominar estas ferramentas acelerará dramaticamente seu desenvolvimento e resolução de problemas.
MQTT Explorer - GUI para visualizar tópicos:
MQTT Explorer (mqtt-explorer.com) é uma ferramenta gráfica excepcional que permite:
- Visualizar hierarquia completa de tópicos
- Ver mensagens em tempo real
- Publicar mensagens manualmente
- Filtrar e buscar tópicos
- Visualizar histórico de mensagens
- Ver estatísticas de QoS
Mosquitto clients - ferramentas de linha de comando:
# Subscrever e ver todas as mensagens de todos os tópicos
mosquitto_sub -h *broker*.local -t '#' -v
# Subscrever com QoS específico
mosquitto_sub -h *broker*.local -t 'irrigacao/#' -q 1 -v
# Subscrever com credenciais
mosquitto_sub -h *broker*.local -t '#' -u usuario -P senha -v
# Publicar mensagem
mosquitto_pub -h *broker*.local -t 'teste/sensor1' -m '25.5'
# Publicar com retain
mosquitto_pub -h *broker*.local -t 'teste/status' -m 'ONLINE' -r
# Publicar com QoS
mosquitto_pub -h *broker*.local -t 'teste/comando' -m 'LIGAR' -q 1
# Publicar conteúdo de arquivo
mosquitto_pub -h *broker*.local -t 'teste/config' -f config.jsonLogging estruturado no ESP32:
/*
* Sistema de logging detalhado para debug de MQTT
*/
#define MQTT_DEBUG_LOG 1 // Ativar logs detalhados
#if MQTT_DEBUG_LOG
#define MQTT_LOG_VERBOSE(tag, format, ...) \
ESP_LOGI(tag, "[MQTT] " format, ##__VA_ARGS__)
#else
#define MQTT_LOG_VERBOSE(tag, format, ...) ((void)0)
#endif
/*
* Wrapper de publicação com logging detalhado
*/
int mqtt_publish_debug(const char *topic,
const char *data,
int len,
int qos,
bool retain)
{
MQTT_LOG_VERBOSE(TAG, "Publicando:");
MQTT_LOG_VERBOSE(TAG, " Tópico: %s", topic);
MQTT_LOG_VERBOSE(TAG, " Dados: %.*s", len, data);
MQTT_LOG_VERBOSE(TAG, " QoS: %d, Retain: %d", qos, retain);
int msg_id = esp_mqtt_client_publish(mqtt_client,
topic, data, len,
qos, retain ? 1 : 0);
if (msg_id < 0) {
ESP_LOGE(TAG, "ERRO: Falha ao publicar (código %d)", msg_id);
} else {
MQTT_LOG_VERBOSE(TAG, " msg_id: %d", msg_id);
}
return msg_id;
}
/*
* Estatísticas de mensagens
*/
typedef struct {
uint32_t total_publicadas;
uint32_t total_recebidas;
uint32_t falhas_publicacao;
uint32_t desconexoes;
uint32_t tempo_desconectado_ms;
uint32_t ultima_mensagem_ts;
} mqtt_stats_t;
static mqtt_stats_t stats = {0};
/*
* Task que imprime estatísticas periodicamente
*/
void mqtt_stats_task(void *pvParameters)
{
while (1) {
vTaskDelay(pdMS_TO_TICKS(60000)); // A cada minuto
ESP_LOGI(TAG, "=== Estatísticas MQTT ===");
ESP_LOGI(TAG, "Publicadas: %lu", stats.total_publicadas);
ESP_LOGI(TAG, "Recebidas: %lu", stats.total_recebidas);
ESP_LOGI(TAG, "Falhas: %lu", stats.falhas_publicacao);
ESP_LOGI(TAG, "Desconexões: %lu", stats.desconexoes);
ESP_LOGI(TAG, "Tempo offline: %lu ms", stats.tempo_desconectado_ms);
ESP_LOGI(TAG, "========================");
}
}📊 Monitoramento em Produção
Sistemas em produção precisam de telemetria contínua para detectar e resolver problemas proativamente.
Publicar métricas de sistema:
/*
* Publicar métricas de saúde do dispositivo
*/
void publish_device_health(void)
{
char telemetria[512];
/*
* Coletar informações do sistema
*/
uint32_t free_heap = esp_get_free_heap_size();
uint32_t min_free_heap = esp_get_minimum_free_heap_size();
int rssi = 0; // Força do sinal WiFi
esp_wifi_sta_get_rssi(&rssi);
/*
* Construir mensagem JSON com métricas
*/
snprintf(telemetria, sizeof(telemetria),
"{"
"\"device_id\":\"esp32_001\","
"\"uptime\":%lu,"
"\"free_heap\":%lu,"
"\"min_free_heap\":%lu,"
"\"wifi_rssi\":%d,"
"\"mqtt_connected\":%d,"
"\"msgs_sent\":%lu,"
"\"msgs_received\":%lu,"
"\"mqtt_failures\":%lu"
"}",
esp_timer_get_time() / 1000000,
free_heap,
min_free_heap,
rssi,
mqtt_connected ? 1 : 0,
stats.total_publicadas,
stats.total_recebidas,
stats.falhas_publicacao);
mqtt_publish_data("irrigacao/health/central",
telemetria, 0, 0, false);
}Melhores Práticas e Padrões de Projeto 🎯
Depois de toda esta jornada, vamos consolidar as melhores práticas que separam implementações profissionais de código de protótipo.
✅ Checklist de Qualidade para Produção
Use esta checklist para garantir que seu código MQTT está pronto para o mundo real.
Segurança:
- ✓ Usar MQTTS (TLS/SSL) para todos os dados sensíveis
- ✓ Implementar autenticação forte (usuário/senha ou certificados)
- ✓ Validar e sanitizar todos os dados recebidos
- ✓ Não incluir credenciais hardcoded no código
- ✓ Usar armazenamento seguro (NVS criptografado) para secrets
- ✓ Implementar rate limiting para prevenir abuse
- ✓ Usar tópicos específicos ao invés de permissões amplas
Confiabilidade:
- ✓ Implementar buffering para mensagens importantes durante desconexões
- ✓ Usar níveis de QoS apropriados para cada tipo de mensagem
- ✓ Implementar timeouts e retries para operações críticas
- ✓ Monitorar saúde da conexão e reconectar automaticamente
- ✓ Implementar last will testament para detectar falhas
- ✓ Validar dados de sensores antes de agir
- ✓ Ter fallback local se comunicação com cloud falhar
Performance:
- ✓ Minimizar tamanho de mensagens (usar JSON compacto, não formatted)
- ✓ Agrupar múltiplos valores em uma mensagem quando possível
- ✓ Usar QoS 0 para dados frequentes e não críticos
- ✓ Implementar throttling para evitar flooding do broker
- ✓ Configurar keep-alive apropriado (30-120 segundos típico)
- ✓ Desinscrever de tópicos não mais necessários
- ✓ Limpar sessões antigas quando apropriado
Manutenibilidade:
- ✓ Usar hierarquia de tópicos consistente e bem documentada
- ✓ Incluir versão do protocolo/formato em tópicos quando necessário
- ✓ Implementar logging estruturado e configurável
- ✓ Documentar formato esperado de cada tópico
- ✓ Usar constantes nomeadas para strings de tópicos
- ✓ Implementar comandos de debug (status, config, etc)
- ✓ Incluir timestamp em mensagens importantes
Reflexões Finais: MQTT no Ecossistema IoT 🌟
MQTT não é apenas mais um protocolo de comunicação - é o padrão de facto que permite a Internet das Coisas funcionar em escala global. A elegância do design publish-subscribe combinada com eficiência extrema tornam MQTT perfeito para o mundo dos sistemas embarcados onde recursos são limitados mas as expectativas são altas.
🎓 Lições Fundamentais
Dominar MQTT significa muito mais que conhecer uma API. Você internalizou um padrão arquitetural que influenciará como pensa sobre comunicação distribuída pelo resto de sua carreira.
Quando você domina MQTT, você domina os princípios fundamentais de sistemas distribuídos resilientes. O desacoplamento entre publishers e subscribers que MQTT proporciona é uma lição valiosa que se aplica muito além de IoT - arquiteturas de microserviços, sistemas de mensageria enterprise, e aplicações cloud-native, todos se beneficiam destes mesmos princípios.
A capacidade de escolher QoS apropriado desenvolve intuição sobre trade-offs fundamentais em engenharia: performance vs confiabilidade, overhead vs garantias, complexidade vs robustez. Esta habilidade de avaliar trade-offs informadamente é o que distingue engenheiros sênior de desenvolvedores júnior.
O entendimento de hierarquias de tópicos ensina organização e design de namespaces que você aplicará em APIs REST, estruturas de URLs, e organização de código. Pensamento hierárquico estruturado é uma habilidade cognitiva transferível para inúmeros domínios.
A conexão entre teoria e prática que você desenvolveu implementando cliente MQTT completo no ESP32 é imensurável. Você viu como abstrações elegantes (publish/subscribe) se materializam em código concreto com gerenciamento de memória, tratamento de erros, e otimização de recursos. Esta ponte entre conceitos e implementação é fundamental para ciência da computação aplicada.
A preparação para escalabilidade que vem de entender MQTT será importante quando seus projetos crescerem. Começar com dois dispositivos conversando no laboratório e evoluir para centenas ou milhares de dispositivos em campo requer os princípios arquiteturais sólidos que você aprendeu hoje.
A aplicação no Projeto Integrador dará vida a todo este conhecimento teórico. Quando você vir sensores no jardim reportando dados em tempo real, o controlador central tomando decisões inteligentes, e você controlando tudo do celular - tudo através de MQTT - você experimentará uma das satisfações mais profundas da engenharia: criar sistemas que funcionam elegantemente porque foram projetados sobre fundamentos sólidos.