Lab09 - MQTT
MQTT com ESP32
Visão geral da aula
Já sabemos fazer o ESP32 atuar como servidor e clienteweb, servindo páginas, expondo e requisitando dados por HTTP. Nesta aula, a proposta muda novamente de direção: em vez de trabalhar com requisições HTTP e rotas, o ESP32 passará a atuar com o protocolo MQTT, trocando mensagens com um broker por meio de tópicos.
O objetivo desta aula é apresentar o uso da biblioteca PubSubClient, mostrando como o ESP32 pode:
- conectar-se a uma rede Wi-Fi;
- conectar-se a um broker MQTT;
- publicar mensagens em um tópico;
- assinar tópicos para receber comandos;
- reagir a mensagens recebidas, controlando hardware.
Como o ESP32 conversa com outros sistemas usando MQTT.
O que é MQTT
MQTT é um protocolo de mensagens muito utilizado em IoT. Ao contrário do modelo HTTP, em que um cliente faz uma requisição para um servidor, no MQTT a comunicação acontece por meio de um broker e de tópicos.
A lógica básica é esta:
- um dispositivo pode publicar uma mensagem em um tópico;
- outro dispositivo pode assinar esse tópico;
- o broker recebe a mensagem e a entrega para quem estiver inscrito.
Em uma visão prática, pense assim:
- o broker é o intermediário;
- o tópico é o canal de comunicação;
- publicar é enviar uma mensagem;
- assinar é ficar ouvindo um canal.
MQTT x HTTP
É importante comparar com o que já vimos anteriormente.
No HTTP
- existe uma URL;
- um cliente faz uma requisição;
- um servidor responde;
- a comunicação normalmente acontece no formato requisição/resposta.
No MQTT
- existe um broker;
- os clientes se conectam ao broker;
- as mensagens circulam por tópicos;
- a comunicação é orientada a publicação e assinatura.
Na prática, isso faz do MQTT uma excelente escolha quando queremos:
- integrar vários dispositivos;
- desacoplar quem envia de quem recebe;
- trabalhar com telemetria;
- enviar comandos para dispositivos IoT.
Componentes importantes desta aula
1. Broker MQTT
O broker é o elemento central do sistema. É ele quem recebe as mensagens publicadas e distribui essas mensagens aos clientes inscritos nos tópicos correspondentes.
2. Cliente MQTT
O ESP32 será um cliente MQTT. Ele irá:
- conectar-se ao broker;
- publicar mensagens;
- assinar tópicos;
- receber comandos.
3. Tópicos
Os tópicos organizam a comunicação. Nesta aula, vamos trabalhar com os seguintes:
fiap/esp32/statusfiap/esp32/comandosfiap/esp32/telemetria
Esses nomes não são obrigatórios. São apenas uma convenção para manter a organização da aplicação.
Biblioteca utilizada
A biblioteca PubSubClient, é responsável por:
- conectar o ESP32 ao broker;
- publicar mensagens;
- assinar tópicos;
- registrar a função de callback que será chamada quando uma mensagem chegar.
Preparando o projeto
Clone o repositório do projeto para acessar o código-fonte do servidor web básico http-client:
git clone https://github.com/arnaldojr/esp32-wokwi
cd esp32-wokwi/mqtt-esp
code .
Agora, abra o arquivo mqtt-esp.ino.
Exemplo 1 — ESP32 como cliente MQTT
Neste primeiro exemplo, faremos com que o ESP32:
- conecte-se ao Wi-Fi;
- conecte-se ao broker MQTT;
- assine um tópico de comandos;
- publique uma mensagem inicial de status;
- ligue ou desligue LEDs de acordo com mensagens recebidas;
- publique periodicamente uma mensagem de telemetria simples.
Código-base
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
// =========================
// CONFIGURACAO DO WIFI
// =========================
#define WIFI_SSID "Wokwi-GUEST"
#define WIFI_PASSWORD ""
#define WIFI_CHANNEL 6
// =========================
// CONFIGURACAO DO BROKER MQTT
// =========================
const char* MQTT_BROKER = "broker.hivemq.com"; // usando o broker hivemq, que nao exige autenticacao
const int MQTT_PORT = 1883;
const char* MQTT_CLIENT_ID = "esp32-fiap-aula-mqtt";
const char* TOPIC_STATUS = "fiap/esp32/status";
const char* TOPIC_COMMANDS = "fiap/esp32/comandos";
const char* TOPIC_TELEMETRY = "fiap/esp32/telemetria";
// =========================
// HARDWARE
// =========================
const int LED1 = 26;
const int LED2 = 27;
bool led1State = false;
bool led2State = false;
// =========================
// CONTROLE DE TEMPO
// =========================
unsigned long lastPublishTime = 0;
const unsigned long PUBLISH_INTERVAL = 5000;
// =========================
// FUNCOES AUXILIARES
// =========================
String boolToText(bool value) {
return value ? "ON" : "OFF";
}
void updateLeds() {
digitalWrite(LED1, led1State);
digitalWrite(LED2, led2State);
}
void processCommand(const String& command) {
if (command == "led1_on") {
led1State = true;
} else if (command == "led1_off") {
led1State = false;
} else if (command == "led2_on") {
led2State = true;
} else if (command == "led2_off") {
led2State = false;
} else if (command == "all_on") {
led1State = true;
led2State = true;
} else if (command == "all_off") {
led1State = false;
led2State = false;
} else {
Serial.println("Comando desconhecido.");
return;
}
updateLeds();
}
// =========================
// FUNÇOES DE REDE
// =========================
void connectWiFi() {
WiFi.begin(WIFI_SSID, WIFI_PASSWORD, WIFI_CHANNEL);
Serial.print("Conectando ao WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(100);
Serial.print(".");
}
Serial.println();
Serial.println("WiFi conectado!");
Serial.print("IP: ");
Serial.println(WiFi.localIP());
}
void ensureWiFiConnected() {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi desconectado. Tentando reconectar...");
connectWiFi();
}
}
void connectMQTT() {
while (!mqttClient.connected()) {
Serial.print("Conectando ao broker MQTT...");
if (mqttClient.connect(MQTT_CLIENT_ID)) {
Serial.println(" conectado!");
mqttClient.subscribe(TOPIC_COMMANDS);
Serial.print("Inscrito no topico: ");
Serial.println(TOPIC_COMMANDS);
publishTelemetry();
} else {
Serial.print(" falhou. Codigo = ");
Serial.println(mqttClient.state());
Serial.println("Tentando novamente em 2 segundos...");
delay(2000); // por enquanto podemos usar esse delay
}
}
}
void ensureMQTTConnected() {
if (!mqttClient.connected()) {
connectMQTT();
}
}
// =========================
// MQTT - PUBLICA
// =========================
void publishTelemetry() {
StaticJsonDocument<128> doc;
doc["led1"] = led1State;
doc["led2"] = led2State;
char buffer[128];
serializeJson(doc, buffer);
mqttClient.publish(TOPIC_TELEMETRY, buffer);
Serial.print("Publicado em ");
Serial.print(TOPIC_TELEMETRY);
Serial.print(": ");
Serial.println(buffer);
}
// =========================
// MQTT - CALLBACK DE MENSAGENS RECEBIDAS
// =========================
void mqttCallback(char* topic, byte* payload, unsigned int length) {
Serial.print("Mensagem recebida no topico: ");
Serial.println(topic);
String message = "";
for (unsigned int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.print("Conteudo bruto da mensagem: ");
Serial.println(message);
if (String(topic) == TOPIC_COMMANDS) {
StaticJsonDocument<128> doc;
DeserializationError error = deserializeJson(doc, message);
if (error) {
Serial.print("Erro ao interpretar JSON: ");
Serial.println(error.c_str());
return;
}
if (!doc.containsKey("command")) {
Serial.println("Campo 'command' nao encontrado no JSON.");
return;
}
String command = doc["command"].as<String>();
Serial.print("Comando interpretado: ");
Serial.println(command);
processCommand(command);
}
}
// =========================
// FUNÇOES PRINCIPAIS
// =========================
void setup() {
Serial.begin(115200);
pinMode(LED1, OUTPUT);
pinMode(LED2, OUTPUT);
updateLeds();
connectWiFi();
mqttClient.setServer(MQTT_BROKER, MQTT_PORT);
mqttClient.setCallback(mqttCallback);
}
void loop() {
ensureWiFiConnected();
ensureMQTTConnected();
mqttClient.loop();
unsigned long now = millis();
if (now - lastPublishTime >= PUBLISH_INTERVAL) {
lastPublishTime = now;
publishTelemetry();
}
}
Desafio 1
Crie um novo tópico chamado:
fiap/esp32/heartbeat
Faça o ESP32 publicar periodicamente nesse tópico uma mensagem indicando que ele continua ativo.
Exemplo:
alive
Desafio 2
Modifique a aplicação para que o ESP32 publique também o tempo de execução do sistema em milissegundos.
Sugestão de tópico:
fiap/esp32/uptime
Desafio 3
Adicione um sensor de temperatura ao circuito e publique o valor em um tópico específico.
Sugestão de tópico:
fiap/esp32/temperatura
Desafio 4
Em vez de publicar apenas texto simples, publique a telemetria em formato JSON com mais informações.
Exemplo:
{
"led1": "ON",
"led2": "OFF",
"uptime": 12345
}
Desafio 5
Crie novos comandos MQTT para controlar os LEDs com mensagens mais estruturadas.
Por exemplo:
led1_toggleled2_togglestatus
Quando o comando status for recebido, o ESP32 deve publicar imediatamente uma mensagem no tópico fiap/esp32/status.