Ficha 02 · MQTT + dashboards + sleep
Exercício 1 · MQTT broker público
ESP32 conecta a broker.hivemq.com:1883, publica string "olá" no topic aulify/teste/<random> a cada 3 segundos. Verifica recepção com cliente externo (HiveMQ websocket client em browser).
Resposta:
Library: "PubSubClient" by Nick O'Leary.
#include <WiFi.h>
#include <PubSubClient.h>
const char* SSID = "...";
const char* PASS = "...";
const char* MQTT_HOST = "broker.hivemq.com";
const int MQTT_PORT = 1883;
WiFiClient esp;
PubSubClient mqtt(esp);
String topic;
void reconnect() {
while (!mqtt.connected()) {
String id = "aulify-" + String(random(0xffff), HEX);
if (mqtt.connect(id.c_str())) {
Serial.println("MQTT conectado");
} else {
delay(2000);
}
}
}
void setup() {
Serial.begin(115200);
randomSeed(analogRead(34));
topic = "aulify/teste/" + String(random(1000));
Serial.print("Topic: ");
Serial.println(topic);
WiFi.begin(SSID, PASS);
while (WiFi.status() != WL_CONNECTED) delay(500);
mqtt.setServer(MQTT_HOST, MQTT_PORT);
}
void loop() {
if (!mqtt.connected()) reconnect();
mqtt.loop();
static unsigned long t = 0;
if (millis() - t > 3000) {
t = millis();
mqtt.publish(topic.c_str(), "olá");
Serial.println("publicado");
}
}
Verificar: 1. Vai a hivemq.com/demos/websocket-client/. 2. Connect (host: broker.hivemq.com, port: 8000). 3. Subscribe ao topic que viste no Serial Monitor. 4. Mensagens "olá" aparecem.
Exercício 2 · MQTT subscribe + LED
ESP32 subscreve a aulify/led/cmd. Quando recebe payload "on", liga LED interno; "off" apaga. Testa publicando do website HiveMQ.
Resposta:
#include <WiFi.h>
#include <PubSubClient.h>
const char* SSID = "...";
const char* PASS = "...";
WiFiClient esp;
PubSubClient mqtt(esp);
void callback(char* topic, byte* payload, unsigned int len) {
String msg;
for (unsigned int i = 0; i < len; i++) msg += (char)payload[i];
Serial.print("Recebido em ");
Serial.print(topic);
Serial.print(": ");
Serial.println(msg);
if (String(topic) == "aulify/led/cmd") {
if (msg == "on") digitalWrite(LED_BUILTIN, HIGH);
else if (msg == "off") digitalWrite(LED_BUILTIN, LOW);
}
}
void reconnect() {
while (!mqtt.connected()) {
if (mqtt.connect("aulify-led-client")) {
mqtt.subscribe("aulify/led/cmd");
Serial.println("Subscrito.");
} else delay(2000);
}
}
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
WiFi.begin(SSID, PASS);
while (WiFi.status() != WL_CONNECTED) delay(500);
mqtt.setServer("broker.hivemq.com", 1883);
mqtt.setCallback(callback);
}
void loop() {
if (!mqtt.connected()) reconnect();
mqtt.loop();
}
Testar: HiveMQ websocket client → publish topic aulify/led/cmd payload on/off. LED responde.
Aviso: broker público é visível a todos. Para produção usa broker autenticado.
Exercício 3 · Adafruit IO setup
Cria conta gratuita em io.adafruit.com. Cria 2 feeds: temperatura e humidade. Configura ESP32 para publicar leituras DHT22 (real ou simuladas) a cada 30s.
Resposta:
- io.adafruit.com → sign up.
- Feeds → "+" → "temperatura"; depois "humidade".
- My Key → copia username + Active Key.
Library: "Adafruit MQTT Library".
#include <WiFi.h>
#include <Adafruit_MQTT.h>
#include <Adafruit_MQTT_Client.h>
#include <DHT.h>
#define DHT_PIN 4
DHT dht(DHT_PIN, DHT22);
const char* SSID = "...";
const char* PASS = "...";
const char* AIO_USER = "teu-username";
const char* AIO_KEY = "aio_xxxx";
WiFiClient client;
Adafruit_MQTT_Client mqtt(&client, "io.adafruit.com", 1883, AIO_USER, AIO_KEY);
Adafruit_MQTT_Publish tempFeed(&mqtt, "teu-username/feeds/temperatura");
Adafruit_MQTT_Publish humidFeed(&mqtt, "teu-username/feeds/humidade");
void connect() {
while (mqtt.connect() != 0) {
Serial.println("MQTT falhou, retry...");
mqtt.disconnect();
delay(3000);
}
Serial.println("MQTT OK");
}
void setup() {
Serial.begin(115200);
dht.begin();
WiFi.begin(SSID, PASS);
while (WiFi.status() != WL_CONNECTED) delay(500);
}
void loop() {
if (!mqtt.connected()) connect();
float t = dht.readTemperature();
float h = dht.readHumidity();
if (!isnan(t)) {
tempFeed.publish(t);
Serial.print("T="); Serial.println(t);
}
if (!isnan(h)) {
humidFeed.publish(h);
Serial.print("H="); Serial.println(h);
}
delay(30000);
}
Em io.adafruit.com → Feeds → vês os gráficos auto-actualizar.
Exercício 4 · Adafruit IO dashboard
Cria dashboard no Adafruit IO com: - Line chart de temperatura (últimas 24h). - Gauge de humidade actual. - Toggle button (feed novo "led") que controla LED do ESP32.
Resposta:
Adafruit IO: 1. Dashboards → New Dashboard → "Aulify IoT". 2. + (add block): - Line chart → feed "temperatura". - Gauge → feed "humidade" → min 0, max 100. - Toggle → criar feed novo "led" → ON/OFF.
ESP32 (continuação do exercício 3, adicionar):
Adafruit_MQTT_Subscribe ledFeed(&mqtt, "teu-username/feeds/led");
void connect() {
// ... connect ...
mqtt.subscribe(&ledFeed);
}
void loop() {
// Publish (como antes)
// ...
// Receive
Adafruit_MQTT_Subscribe *sub;
while ((sub = mqtt.readSubscription(100))) {
if (sub == &ledFeed) {
char* val = (char*)ledFeed.lastread;
Serial.print("LED: "); Serial.println(val);
if (strcmp(val, "ON") == 0) {
digitalWrite(LED_BUILTIN, HIGH);
} else {
digitalWrite(LED_BUILTIN, LOW);
}
}
}
}
Toggle no dashboard → LED no ESP32 reage. Sem precisar de IP/firewall — tudo via cloud MQTT.
Exercício 5 · Deep sleep
Modifica o sketch de ThingSpeak/Adafruit para entrar em deep sleep durante 5 minutos depois de cada envio. Mede consumo com multímetro (se possível) — devia descer drasticamente.
Resposta:
#include <WiFi.h>
#include <HTTPClient.h>
#include <DHT.h>
#include "esp_sleep.h"
#define DHT_PIN 4
#define SLEEP_MIN 5
DHT dht(DHT_PIN, DHT22);
const char* SSID = "...";
const char* PASS = "...";
const char* TS_KEY = "...";
RTC_DATA_ATTR int bootCount = 0; // sobrevive deep sleep
void enviar() {
HTTPClient http;
float t = dht.readTemperature();
float h = dht.readHumidity();
if (isnan(t) || isnan(h)) {
Serial.println("DHT erro");
return;
}
String url = "http://api.thingspeak.com/update?api_key=";
url += TS_KEY;
url += "&field1=" + String(t, 2);
url += "&field2=" + String(h, 2);
http.begin(url);
int code = http.GET();
http.end();
Serial.printf("Boot #%d: t=%.1f h=%.1f → %d\n", bootCount, t, h, code);
}
void setup() {
Serial.begin(115200);
++bootCount;
Serial.printf("Boot %d\n", bootCount);
dht.begin();
delay(2000); // DHT22 estabilizar
WiFi.begin(SSID, PASS);
int tries = 0;
while (WiFi.status() != WL_CONNECTED && tries < 20) {
delay(500);
tries++;
}
if (WiFi.status() == WL_CONNECTED) {
enviar();
}
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
Serial.printf("Dormir %d min...\n", SLEEP_MIN);
esp_sleep_enable_timer_wakeup(SLEEP_MIN * 60 * 1000000ULL);
esp_deep_sleep_start();
}
void loop() {} // nunca chega
Bateria 2500mAh: - Sem sleep: ~15 horas. - Com deep sleep 5min: ~3 meses.
Exercício 6 · Home Assistant (opcional avançado)
Se tens Raspberry Pi ou Docker, instala Home Assistant. Adiciona ESP32 como sensor MQTT. Cria automation: "se temperatura > 28°C, envia notificação".
Resposta:
HA instalação rápida (Docker):
docker run -d --name homeassistant --restart unless-stopped \
-v ~/ha-config:/config \
--network=host \
ghcr.io/home-assistant/home-assistant:stable
Abrir http://localhost:8123 → onboarding.
Mosquitto broker (Add-on Store → Mosquitto broker → install + start).
ESP32: configurar para publicar em home/sala/temp, home/sala/humid.
HA config (/config/configuration.yaml):
mqtt:
sensor:
- name: "Temperatura Sala"
state_topic: "home/sala/temp"
unit_of_measurement: "°C"
device_class: "temperature"
- name: "Humidade Sala"
state_topic: "home/sala/humid"
unit_of_measurement: "%"
device_class: "humidity"
Reinicia HA. Aparece em Dashboard como sensor.
Automation (Settings → Automations → Create):
- Trigger: Numeric state → entity sensor.temperatura_sala → above 28.
- Action: Call service → notify.mobile_app_meu_phone → message "Está calor: {{ states.sensor.temperatura_sala.state }}°C".
Resultado: temperatura acima de 28 → notificação no telemóvel.
Exercício 7 · Segurança — MQTT com auth
Migra de broker público para um broker autenticado (cria conta em HiveMQ Cloud free, ou usa Adafruit IO). Atualiza credenciais no código. Confirma que sem credenciais não conecta.
Resposta:
HiveMQ Cloud Free: 1. console.hivemq.cloud → sign up. 2. Create cluster (free). 3. Access Management → adicionar utilizador. 4. Anotar broker URL (xxx.s1.eu.hivemq.cloud), porta 8883 (TLS).
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
const char* BROKER = "xxxxx.s1.eu.hivemq.cloud";
const int PORT = 8883; // TLS
const char* MQTT_USER = "aulify";
const char* MQTT_PASS = "minha-pass-forte";
WiFiClientSecure secureClient;
PubSubClient mqtt(secureClient);
void setup() {
Serial.begin(115200);
WiFi.begin("ssid", "pass");
while (WiFi.status() != WL_CONNECTED) delay(500);
// Skip cert validation (rápido para dev — em prod usar cert verdadeiro)
secureClient.setInsecure();
mqtt.setServer(BROKER, PORT);
}
void reconnect() {
while (!mqtt.connected()) {
if (mqtt.connect("aulify-secure", MQTT_USER, MQTT_PASS)) {
Serial.println("Connected TLS");
} else {
Serial.print("Erro: ");
Serial.println(mqtt.state());
delay(3000);
}
}
}
void loop() {
if (!mqtt.connected()) reconnect();
mqtt.loop();
static unsigned long t = 0;
if (millis() - t > 5000) {
t = millis();
mqtt.publish("aulify/teste", "secure-message");
}
}
Sem MQTT_USER/PASS, broker rejeita. Encriptação TLS impede sniffing.
Para produção: usar setCACert(rootCA) com cert real (do site da CA).
Exercício 8 · Mini-dashboard custom
Constrói dashboard próprio HTML+JS que se conecta ao broker MQTT (via WebSocket) e mostra leituras em tempo real, sem usar ThingSpeak/Adafruit.
Resposta:
ESP32 continua a publicar em aulify/sala/temp e aulify/sala/humid.
dashboard.html:
<!DOCTYPE html>
<html lang="pt">
<head>
<meta charset="utf-8">
<title>Dashboard Aulify</title>
<style>
body { font-family: sans-serif; background: #111; color: #eee;
text-align: center; padding: 50px; }
h1 { font-size: 5em; margin: 10px; color: #0070f3; }
p { font-size: 2em; color: #888; }
.ok { color: #0f0; }
.err { color: #f00; }
</style>
</head>
<body>
<h1 id="temp">--</h1>
<p>Temperatura</p>
<h1 id="humid">--</h1>
<p>Humidade</p>
<p id="status" class="err">Disconnected</p>
<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
<script>
// HiveMQ usa porta 8884 para WebSockets TLS
const client = mqtt.connect("wss://broker.hivemq.com:8884/mqtt");
const tempEl = document.getElementById("temp");
const humidEl = document.getElementById("humid");
const statusEl = document.getElementById("status");
client.on("connect", () => {
statusEl.textContent = "Connected";
statusEl.className = "ok";
client.subscribe("aulify/sala/temp");
client.subscribe("aulify/sala/humid");
});
client.on("message", (topic, payload) => {
const v = parseFloat(payload.toString());
if (topic.endsWith("/temp")) {
tempEl.textContent = v.toFixed(1) + "°C";
} else if (topic.endsWith("/humid")) {
humidEl.textContent = v.toFixed(0) + "%";
}
});
client.on("error", err => {
statusEl.textContent = "Erro: " + err.message;
statusEl.className = "err";
});
</script>
</body>
</html>
Abre dashboard.html no browser (file:// ou serve com python3 -m http.server).
Vai mostrar leituras em tempo real, sem cloud platform — só broker MQTT + browser.
Para produção: usar broker próprio + auth + TLS, deploy do HTML em Vercel/Netlify.