Partilhar: WhatsApp
aulify
UC UC00643 · T. Desenv. Software, T. Sist. Comp. Redes

Ficha 02 · MQTT + dashboards + sleep

Cloud IoT, baixo consumo, segurança
Versão · Aluno
Tempo · 45 minutos
Aluno(a)
Turma
Data

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:
  1. io.adafruit.com → sign up.
  2. Feeds → "+" → "temperatura"; depois "humidade".
  3. 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.