Pular para o conteúdo principal

Laboratório 05 (sem-dht22)

Checklist — DHT22, temporizador e MQTT com Node-RED​

  • platformio.ini com DHT, Unified Sensor e PubSubClient; diagram.json com DHT22 no GPIO15 (T1)
  • Leituras periódicas via Ticker + flag volatile, NaN descartado (T2)
  • Conexão Wi-Fi com reconexão no loop() (T3)
  • JSON publicado em elt85b/lab05/grupo-X/dht a cada 5 s, client ID único (T4)
  • Node-RED: mqtt in (JSON) → debug, fluxo exportado (T5)
  • LWT offline + online retidos em .../status, visíveis no Node-RED (T6)
  • (ESP32 físico sem DHT22) USAR_DHT22 0, sensor virtual e toque no GPIO4 funcionando
  • Previsões P1–P4 respondidas no README.md
  • Nenhum push --force; cada tarefa num commit Tn:
Pontuação

T1…T6 = 100 pts (esta aula não tem desafios): T1 10 · T2 20 · T3 15 · T4 20 · T5 15 · T6 20. O autograder normaliza para 0–10. Vale o commit Tn: mais recente de cada tarefa.

Antes de começar

Confira o ambiente (faça a configuração uma vez por máquina; a verificação, a cada aula):

  1. Instalar as ferramentas — git, GitHub CLI e VS Code
  2. Configurar o git — nome, e-mail e editor padrão (uma vez)
  3. Verificar git, gh e VS Code — deve mostrar as versões e Logged in to github.com

Verificação rápida de versão instalada:

git -v && gh --version && code -v && pio --version && wokwi-cli -V && wsl --version && docker --version

Verificação rápida de autenticação:

git config --get-regexp ^user\. && gh auth status && code --list-extensions --profile "ESP32IO"

Configuração do ambiente de desenvolvimento​

O Git é a ferramenta de gerenciamento de código-fonte mais utilizada por desenvolvedores profissionais.

  • O Git é um sistema de controle de versão distribuído gratuito e de código aberto, projetado para lidar com tudo, desde projetos pequenos até muito grandes, com rapidez e eficiência.
winget install --id Git.Git -e --source winget

Configurações do git:

git config --list --show-origin

Nesta aula o ESP32 vira um nó de telemetria: um temporizador dispara leituras periódicas do DHT22, o firmware publica cada leitura em JSON via MQTT, e o Node-RED assina o tópico e mostra as mensagens no painel debug. No projeto integrador o nó passa a anunciar se está online ou offline usando LWT.

Continua no Lab 06

No Lab 06 o mesmo firmware é reaproveitado: o Node-RED passa a gravar as leituras em SQLite e a mudar o intervalo do ESP32 remotamente.

Objetivos​

  • Ler temperatura e umidade do DHT22 e tratar leituras inválidas (NaN).
  • Usar um temporizador (Ticker) para disparar ações periódicas sem delay() e sem bloquear o loop().
  • Conectar o ESP32 ao Wi-Fi e a um broker MQTT, publicando leituras em JSON.
  • No Node-RED, assinar o tópico, receber o JSON já convertido em objeto e inspecionar os campos no debug.
  • (Integrador) Publicar o status do nó com LWT (Last Will and Testament).

Material​

  • VS Code + PlatformIO + extensão Wokwi (ESP32 simulado — sem placa física).
  • Alternativa: ESP32 físico sem DHT22 — veja ESP32 físico sem DHT22.
  • Stack Docker do curso (Mosquitto + Node-RED + MQTT Explorer) — nesta aula usamos o Node-RED e o MQTT Explorer.
  • Bibliotecas (baixadas pelo PlatformIO): adafruit/DHT sensor library, adafruit/Adafruit Unified Sensor, knolleary/PubSubClient. O Ticker já vem com o core Arduino do ESP32.
Por que Wokwi (e não placa física)?

Conferindo a matriz de simulação: GPIO (o DHT22 usa um único pino digital) e Wi-Fi são ✔️ no ESP32 clássico — então a aula roda inteira no Wokwi. O mesmo firmware funciona numa placa física trocando só SSID/senha e o endereço do broker (veja a aba "Placa física" na T3). Tem o ESP32 mas não tem o DHT22? Veja a alternativa com sensor virtual.

Clonar o repositório do grupo​

1. Crie e entre na pasta-mãe da disciplina:

mkdir "%USERPROFILE%\ELT85BN21" & cd /d "%USERPROFILE%\ELT85BN21"

2. Clone e entre no repositório do laboratório:

git clone https://github.com/ELT85B-N21-2026-2/lab05-grupo-a.git && cd lab05-grupo-a

3. Verifique status e abra o conteúdo do repositório no perfil ESP32IO do VS Code :

git status && code . --profile "ESP32IO"

Repositório do Grupo A para Laboratório 05:

  1. Copie o link do repositório acima.
  2. Abra a página de envio da atividade no Moodle (botão abaixo).
  3. Clique em Adicionar envio.
  4. Cole o link no campo Texto online.
  5. Clique em Salvar mudanças.
  6. Confira se o status mudou para Enviado para avaliação.
Abrir envio no Moodle
Avaliação por commits

Cada cartão Ponto de commit é uma tarefa: faça-a, copie o comando do cartão e rode. A mensagem começa com o código (T1:, T2:…) — é como a correção identifica sua entrega. Um commit por tarefa; pode refazer (vale o mais recente); e não esqueça o git push. Detalhes em Como funciona a avaliação.

Mapa de tarefas — 6 tarefas · 100 pts

O repositório do grupo nasce do lab05-template:

📂 lab05-grupo-X/
  • ├── 🔧platformio.ini👈 Edite Aqui
  • ├── 📂src
    • └── 📄main.cpp👈 Edite Aqui
  • ├── 🔧diagram.json👈 Edite Aqui
  • ├── 🔧wokwi.toml👈 Verificar
  • ├── 📄.gitignore👈 Verificar
  • ├── 📂.vscode
    • └── 🔧extensions.json👈 Verificar
  • ├── 📂nodered
    • └── 🔧flows-lab05.json👈 Criar Aqui
  • ├── 📂evidencias
    • ├── 📄t2-serial.txt👈 Criar Aqui
    • ├── 📄t3-wifi.txt👈 Criar Aqui
    • ├── 📄t4-mqtt.txt👈 Criar Aqui
    • ├── 📄t5-debug.txt👈 Criar Aqui
    • └── 📄t6-status.txt👈 Criar Aqui
  • └── 📝README.md👈 Edite Aqui
setup() × loop() nesta aula
  • setup() — o que acontece uma vez: iniciar Serial e DHT, conectar Wi-Fi e MQTT, armar o temporizador.
  • loop() — o comportamento contínuo: manter Wi-Fi/MQTT vivos (mqtt.loop()), e consumir a flag do temporizador (ler + publicar).
  • callback do temporizador — não é nem um nem outro: roda em outro contexto (a tarefa do esp_timer). Por isso ele só faz leituraPendente = true e mais nada.

Parte 1 — Circuito e dependências​

1.1 Circuito no Wokwi​

O DHT22 do Wokwi tem 4 pinos: VCC, SDA (dados), NC e GND. Ligue VCC → 3V3, GND → GND e SDA → GPIO15. O template já traz o ESP32; acrescente o sensor no diagram.json:

diagram.json
{
"version": 1,
"author": "ELT85B - Lab 05",
"editor": "wokwi",
"parts": [
{
"type": "board-esp32-devkit-c-v4",
"id": "esp",
"top": 0,
"left": 0,
"attrs": {}
},
{
"type": "wokwi-dht22",
"id": "dht1",
"top": -76.5,
"left": 157.8,
"attrs": { "temperature": "24.5", "humidity": "55" }
}
],
"connections": [
["esp:TX", "$serialMonitor:RX", "", []],
["esp:RX", "$serialMonitor:TX", "", []],
["dht1:VCC", "esp:3V3", "red", []],
["dht1:GND", "esp:GND.2", "black", []],
["dht1:SDA", "esp:15", "green", []]
],
"dependencies": {}
}
Mudando os valores durante a simulação

Com o simulador rodando, clique no DHT22: aparecem sliders de temperatura e umidade. É assim que você vai "esquentar" o sensor e ver os números mudarem no Node-RED.

1.2 Bibliotecas no platformio.ini​

platformio.ini
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
lib_deps =
adafruit/DHT sensor library@^1.4.6
adafruit/Adafruit Unified Sensor@^1.1.14
knolleary/PubSubClient@^2.8

O wokwi.toml do template já aponta para .pio/build/esp32dev/firmware.bin e .elf — por isso o nome do ambiente precisa continuar esp32dev.

Ponto de commit · T1 (10 pts) #

Dependencias e circuito do DHT22

git add platformio.ini diagram.json && git commit -m "T1: Dependencias e circuito do DHT22" && git push

Alternativa — ESP32 físico sem DHT22​

Para quem é esta seção

Para quem vai gravar o firmware num ESP32 de verdade mas não tem o DHT22. Se você só não tem o sensor, também pode simplesmente seguir a aula no Wokwi — lá o DHT22 é virtual.

A ideia é trocar o sensor por um sensor virtual dentro do próprio firmware. O resto da aula — temporizador, Wi-Fi, MQTT, JSON, Node-RED e LWT — fica idêntico, porque o resto do código não sabe de onde vêm t e h: ele só chama lerSensor(t, h).

Wokwi (DHT22)ESP32 físico sem DHT22
Origem dos dadosDHT22 no GPIO15sensor virtual no firmware
"Esquentar" o sensorslider do DHT22 no Wokwiencostar o dedo no GPIO4 (touch T0)
Leituras inválidasNaN do DHT22cerca de 3% de NaN sorteados
ExecutarWokwi: Start SimulatorPlatformIO: Upload + Serial Monitor
Evidênciascopiar do terminal do Wokwicopiar do Serial Monitor (115200)
Wi-FiWokwi-GUESTrede do laboratório (aba "Placa física" da T3)
Por que não o sensor de temperatura interno?

O ESP32 clássico tem temperatureRead(), mas ele usa um sensor não documentado que foi retirado das revisões mais novas do chip: na maioria das placas a leitura fica parada em 53,3 °C. Não serve para a aula.

Circuito​

Nenhum. Opcional: prenda um jumper no GPIO4 e use a ponta metálica como "botão de toque" maior. Não encoste no pino durante o boot — a referência do toque é medida no setup().

platformio.ini e diagram.json​

Faça a T1 igual aos demais grupos: o lib_deps do DHT continua (o código compila nas duas opções) e o diagram.json continua com o DHT22 — ele só é usado no Wokwi, mas faz parte da T1.

Código da T2​

src/main.cpp (T2, sem DHT22)
#include <Arduino.h>
#include <Ticker.h>
#include <DHT.h>

// 1 = DHT22 (Wokwi ou placa com sensor) | 0 = ESP32 fisico sem DHT22
#define USAR_DHT22 0

#define DHT_PIN 15
#define DHT_TIPO DHT22
#define TOQUE_PIN T0 // GPIO4: encoste o dedo para "esquentar" o sensor virtual

DHT dht(DHT_PIN, DHT_TIPO);
Ticker temporizador;

volatile bool leituraPendente = false; // setada pelo temporizador
const float INTERVALO_S = 5.0; // DHT22: minimo 2 s entre leituras
uint32_t seq = 0;

#if !USAR_DHT22
float tVirtual = 24.5, hVirtual = 55.0;
uint32_t toqueBase = 0;
#endif

// Roda no contexto do temporizador: NADA de Serial/DHT/MQTT aqui
void aoEstourarTemporizador() {
leituraPendente = true;
}

void iniciarSensor() {
#if USAR_DHT22
dht.begin();
#else
randomSeed(esp_random());
toqueBase = touchRead(TOQUE_PIN); // medido SEM o dedo no pino
Serial.printf("[SIM] sensor virtual, toque base = %lu\n", (unsigned long)toqueBase);
#endif
}

// Preenche t e h; devolve false se a leitura falhou (NaN)
bool lerSensor(float &t, float &h) {
#if USAR_DHT22
t = dht.readTemperature();
h = dht.readHumidity();
#else
bool tocando = touchRead(TOQUE_PIN) < toqueBase * 6 / 10;
float alvoT = tocando ? 34.0f : 24.5f; // dedo no pino = "aquece"
float alvoH = tocando ? 70.0f : 55.0f; // ... e "umedece"
tVirtual += 0.3f * (alvoT - tVirtual) + random(-20, 21) / 100.0f;
hVirtual += 0.3f * (alvoH - hVirtual) + random(-50, 51) / 100.0f;
hVirtual = constrain(hVirtual, 0.0f, 100.0f);
t = tVirtual;
h = hVirtual;
if (random(100) < 3) t = NAN; // ~3% de falhas, como um sensor real
#endif
return !(isnan(t) || isnan(h));
}

void setup() {
Serial.begin(115200);
delay(200);
Serial.println("\n=== Lab 05 - T2: DHT22 + temporizador ===");
iniciarSensor();
temporizador.attach(INTERVALO_S, aoEstourarTemporizador);
}

void loop() {
if (leituraPendente) {
leituraPendente = false;
float t, h;
if (!lerSensor(t, h)) {
Serial.println("[DHT] falha na leitura (NaN) - descartada");
return;
}
seq++;
Serial.printf("[DHT] seq=%lu t=%.1f C h=%.1f %% (%lu ms)\n",
(unsigned long)seq, t, h, (unsigned long)millis());
}
}
  • #if USAR_DHT22 — seleção em tempo de compilação: o pré-processador descarta o ramo não usado, e o mesmo arquivo serve para as duas opções. Com USAR_DHT22 1 o comportamento é idêntico ao da T2 principal.
  • lerSensor() isola a origem dos dados (uma "camada de hardware" mínima): trocar o sensor não mexe no temporizador, no Wi-Fi nem no MQTT.
  • O sensor virtual se aproxima de um alvo (24,5 °C sem toque, 34 °C com toque) 30% a cada leitura, mais um ruído pequeno — imita a inércia térmica de um sensor real.
  • No ESP32 clássico o touchRead() cai quando você encosta (ex.: ~70 → ~20). A referência é medida no setup(), e "tocando" é qualquer valor abaixo de 60% dela.
  • touchRead() é chamado no loop() (via lerSensor()), nunca no callback do Ticker.

Saída esperada no Serial Monitor (dedo no GPIO4 entre seq=6 e seq=10):

Serial Monitor
=== Lab 05 - T2: DHT22 + temporizador ===
[SIM] sensor virtual, toque base = 72
[DHT] seq=1 t=24.4 C h=55.2 % (5203 ms)
[DHT] seq=2 t=24.4 C h=55.2 % (10203 ms)
[DHT] seq=3 t=24.5 C h=54.8 % (15203 ms)
[DHT] seq=4 t=24.3 C h=54.6 % (20203 ms)
[DHT] seq=5 t=24.4 C h=54.4 % (25203 ms)
[DHT] seq=6 t=27.2 C h=59.2 % (30203 ms)
[DHT] seq=7 t=29.5 C h=62.7 % (35203 ms)
[DHT] seq=8 t=31.0 C h=64.9 % (40203 ms)
[DHT] falha na leitura (NaN) - descartada
[DHT] seq=9 t=32.3 C h=67.9 % (50203 ms)
[DHT] seq=10 t=29.9 C h=63.7 % (55203 ms)

Os números exatos variam (ruído e valor do toque dependem da placa e do dedo).

Preveja antes de rodar (P1 adaptada)

Segure o dedo no GPIO4 por três leituras e solte. A temperatura salta para 34 °C de uma vez, ou sobe aos poucos? Quantas leituras leva para voltar a ~24,5 °C? Relacione com a linha tVirtual += 0.3f * ....

Da T3 em diante​

A partir da T3, use o código principal da aula com quatro mudanças:

  1. Copie para o topo os #define USAR_DHT22 e TOQUE_PIN e as variáveis do bloco #if !USAR_DHT22 ... #endif.

  2. Copie as funções iniciarSensor() e lerSensor() para antes de lerEPublicar().

  3. Em setup(), troque dht.begin(); por iniciarSensor();.

  4. Em lerEPublicar(), troque as três linhas da leitura por:

    src/main.cpp (lerEPublicar)
    float t, h;
    if (!lerSensor(t, h)) {

E use o SSID/senha do laboratório (aba "Placa física" da T3).

src/main.cpp completo da T4 — ESP32 físico sem DHT22
src/main.cpp (T4, sem DHT22, placa fisica)
#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <Ticker.h>
#include <DHT.h>

#define GRUPO "a" // troque pela letra do seu grupo
// 1 = DHT22 (Wokwi ou placa com sensor) | 0 = ESP32 fisico sem DHT22
#define USAR_DHT22 0

#define DHT_PIN 15
#define DHT_TIPO DHT22
#define TOQUE_PIN T0 // GPIO4: encoste o dedo para "esquentar" o sensor virtual

const char* WIFI_SSID = "IoT-Broker";
const char* WIFI_PASS = "ESP32IoTLab";
// MQTT: broker publico; se a rede bloquear a 1883, use o IP do PC com o Mosquitto da stack
const char *MQTT_HOST = "broker.hivemq.com";
const uint16_t MQTT_PORTA = 1883;
const char *TOPICO_DADOS = "elt85b/lab05/grupo-" GRUPO "/dht";

DHT dht(DHT_PIN, DHT_TIPO);
WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);
Ticker temporizador;

volatile bool leituraPendente = false;
const float INTERVALO_S = 5.0;
uint32_t seq = 0;
char clientId[32];

#if !USAR_DHT22
float tVirtual = 24.5, hVirtual = 55.0;
uint32_t toqueBase = 0;
#endif

void aoEstourarTemporizador() {
leituraPendente = true;
}

void conectarWiFi() {
Serial.printf("[WiFi] conectando a %s", WIFI_SSID);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_SENHA); // rede real: sem fixar canal
while (WiFi.status() != WL_CONNECTED) {
delay(250);
Serial.print('.');
}
Serial.printf("\n[WiFi] conectado, IP %s\n", WiFi.localIP().toString().c_str());
}

void conectarMQTT() {
while (!mqtt.connected()) {
Serial.printf("[MQTT] conectando a %s como %s ... ", MQTT_HOST, clientId);
if (mqtt.connect(clientId)) {
Serial.println("ok");
} else {
Serial.printf("falhou (rc=%d), nova tentativa em 2 s\n", mqtt.state());
delay(2000);
}
}
}

void iniciarSensor() {
#if USAR_DHT22
dht.begin();
#else
randomSeed(esp_random());
toqueBase = touchRead(TOQUE_PIN); // medido SEM o dedo no pino
Serial.printf("[SIM] sensor virtual, toque base = %lu\n", (unsigned long)toqueBase);
#endif
}

// Preenche t e h; devolve false se a leitura falhou (NaN)
bool lerSensor(float &t, float &h) {
#if USAR_DHT22
t = dht.readTemperature();
h = dht.readHumidity();
#else
bool tocando = touchRead(TOQUE_PIN) < toqueBase * 6 / 10;
float alvoT = tocando ? 34.0f : 24.5f; // dedo no pino = "aquece"
float alvoH = tocando ? 70.0f : 55.0f; // ... e "umedece"
tVirtual += 0.3f * (alvoT - tVirtual) + random(-20, 21) / 100.0f;
hVirtual += 0.3f * (alvoH - hVirtual) + random(-50, 51) / 100.0f;
hVirtual = constrain(hVirtual, 0.0f, 100.0f);
t = tVirtual;
h = hVirtual;
if (random(100) < 3) t = NAN; // ~3% de falhas, como um sensor real
#endif
return !(isnan(t) || isnan(h));
}

void lerEPublicar() {
float t, h;
if (!lerSensor(t, h)) {
Serial.println("[DHT] falha na leitura (NaN) - descartada");
return;
}
seq++;
char payload[128];
snprintf(payload, sizeof(payload),
"{\"grupo\":\"%s\",\"seq\":%lu,\"t\":%.1f,\"h\":%.1f,\"uptime_ms\":%lu}",
GRUPO, (unsigned long)seq, t, h, (unsigned long)millis());
bool ok = mqtt.publish(TOPICO_DADOS, payload);
Serial.printf("[PUB] %s %s -> %s\n", ok ? "ok" : "ERRO", TOPICO_DADOS, payload);
}

void setup() {
Serial.begin(115200);
delay(200);
Serial.println("\n=== Lab 05 - T4: sensor + MQTT ===");
iniciarSensor();

uint64_t mac = ESP.getEfuseMac();
snprintf(clientId, sizeof(clientId), "lab05-%s-%04X", GRUPO, (uint16_t)(mac >> 32));

conectarWiFi();
mqtt.setServer(MQTT_HOST, MQTT_PORTA);
conectarMQTT();

temporizador.attach(INTERVALO_S, aoEstourarTemporizador);
}

void loop() {
if (WiFi.status() != WL_CONNECTED) conectarWiFi();
if (!mqtt.connected()) conectarMQTT();
mqtt.loop();

if (leituraPendente) {
leituraPendente = false;
lerEPublicar();
}
}
Broker na rede do laboratório

O ESP32 físico pode usar o mesmo broker.hivemq.com, desde que a rede libere a porta 1883 de saída. Se o log repetir [MQTT] ... falhou (rc=-2), a conexão não sai: use o Mosquitto da stack com MQTT_HOST = IP do computador na mesma rede (não localhost — para o ESP32, localhost é ele mesmo). Nesse caso aponte também o mqtt in do Node-RED e os comandos mosquitto_sub (-h IP-DO-PC) para esse broker.

LWT na placa física (T6)​

Onde a aula diz "pare a simulação", você tem duas formas de derrubar o nó:

  • Tirar o cabo USB — a placa some sem fechar o TCP; o broker só percebe pelo keepalive (1,5 × 15 s ≈ 22 s) e então publica offline.
  • Apertar EN/RST — a placa reinicia e reconecta em poucos segundos com o mesmo client ID; o broker derruba a sessão antiga, e o normal é ver offline seguido logo de online.

Experimente as duas e use a diferença para responder a P4.


Parte 2 — DHT22 com temporizador​

2.1 Por que um temporizador?​

O jeito ingênuo é ler(); delay(5000);. Funciona até você precisar fazer outra coisa no loop() — como manter a conexão MQTT viva, que exige chamar mqtt.loop() várias vezes por segundo. Com delay(5000) o cliente fica 5 s "surdo".

A solução é deixar um temporizador marcar o tempo e o loop() só reagir:

  • Ticker é o temporizador de software do core ESP32 (baseado no esp_timer, com resolução de microssegundos). attach(segundos, funcao) chama funcao periodicamente.
  • A flag é volatile bool porque é escrita num contexto (timer) e lida em outro (loop()): volatile impede o compilador de "cachear" o valor num registrador.
  • No ESP32, bool ocupa 1 byte e a escrita é atômica — não precisamos de mutex para uma flag.
DHT22 é lento

O DHT22 faz no máximo uma leitura a cada 2 s. A biblioteca da Adafruit respeita isso: se você pedir antes, ela devolve o último valor em cache.

2.2 Código​

src/main.cpp (T2)
#include <Arduino.h>
#include <Ticker.h>
#include <DHT.h>

#define DHT_PIN 15
#define DHT_TIPO DHT22

DHT dht(DHT_PIN, DHT_TIPO);
Ticker temporizador;

volatile bool leituraPendente = false; // setada pelo temporizador
const float INTERVALO_S = 5.0; // DHT22: minimo 2 s entre leituras
uint32_t seq = 0;

// Roda no contexto do temporizador: NADA de Serial/DHT/MQTT aqui
void aoEstourarTemporizador() {
leituraPendente = true;
}

void setup() {
Serial.begin(115200);
delay(200);
Serial.println("\n=== Lab 05 - T2: DHT22 + temporizador ===");
dht.begin();
temporizador.attach(INTERVALO_S, aoEstourarTemporizador);
}

void loop() {
if (leituraPendente) {
leituraPendente = false;
float t = dht.readTemperature();
float h = dht.readHumidity();
if (isnan(t) || isnan(h)) {
Serial.println("[DHT] falha na leitura (NaN) - descartada");
return;
}
seq++;
Serial.printf("[DHT] seq=%lu t=%.1f C h=%.1f %% (%lu ms)\n",
(unsigned long)seq, t, h, (unsigned long)millis());
}
}

Compile (PlatformIO: Build) e rode Wokwi: Start Simulator.

ESP32 físico sem DHT22?

Use o código da T2 com sensor virtual e grave na placa (PlatformIO: Upload).

Saída esperada (os ms variam um pouco):

Monitor serial
=== Lab 05 - T2: DHT22 + temporizador ===
[DHT] seq=1 t=24.5 C h=55.0 % (5204 ms)
[DHT] seq=2 t=24.5 C h=55.0 % (10204 ms)
[DHT] seq=3 t=24.5 C h=55.0 % (15204 ms)
Preveja antes de rodar
  • P1: troque INTERVALO_S para 1.0 e mude o slider do sensor entre duas leituras. Os valores acompanham o slider a cada linha? Por quê?
  • P2: e se você colocasse o Serial.printf dentro de aoEstourarTemporizador()? Pense em quem mais depende da tarefa do esp_timer e no tempo que um printf a 115200 baud leva.

Anote as previsões e o que observou no README.md. Depois volte o intervalo para 5 s.

Salve pelo menos 3 leituras do monitor serial (copie do terminal do Wokwi) em evidencias/t2-serial.txt, incluindo a linha === Lab 05.

Ponto de commit · T2 (20 pts) #

Leitura do DHT22 com temporizador

git add src/main.cpp evidencias/t2-serial.txt && git commit -m "T2: Leitura do DHT22 com temporizador" && git push

Parte 3 — Wi-Fi​

Acrescente o Wi-Fi ao programa da T2. No Wokwi a rede é Wokwi-GUEST, sem senha; passar o canal 6 no begin() pula a varredura e conecta mais rápido.

src/main.cpp (trechos novos da T3)
#include <WiFi.h>

const char *WIFI_SSID = "Wokwi-GUEST";
const char *WIFI_SENHA = "";

void conectarWiFi() {
Serial.printf("[WiFi] conectando a %s", WIFI_SSID);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_SENHA, 6); // canal 6 acelera no Wokwi
while (WiFi.status() != WL_CONNECTED) {
delay(250);
Serial.print('.');
}
Serial.printf("\n[WiFi] conectado, IP %s\n", WiFi.localIP().toString().c_str());
}

// em setup(), depois de dht.begin():
// conectarWiFi();
// no inicio de loop():
// if (WiFi.status() != WL_CONNECTED) conectarWiFi();

Saída esperada:

Monitor serial
=== Lab 05 ===
[WiFi] conectando a Wokwi-GUEST.....
[WiFi] conectado, IP 10.13.37.2
[DHT] seq=1 t=24.5 C h=55.0 % (6120 ms)
O while do Wi-Fi bloqueia — e tudo bem aqui

Enquanto não há Wi-Fi não há o que publicar, então bloquear na (re)conexão é aceitável nesta aula. Em produção você trocaria por uma máquina de estados não bloqueante.

Salve a saída (com a linha [WiFi] conectado) em evidencias/t3-wifi.txt.

Ponto de commit · T3 (15 pts) #

Conexao Wi-Fi

git add src/main.cpp evidencias/t3-wifi.txt && git commit -m "T3: Conexao Wi-Fi" && git push

Parte 4 — Publicação MQTT periódica​

4.1 Tópico e payload​

Cada grupo publica no seu tópico (troque a pela letra do grupo):

UsoTópicoExemplo de payload
Leituraselt85b/lab05/grupo-a/dht{"grupo":"a","seq":7,"t":24.5,"h":55.0,"uptime_ms":35210}
Status (T6)elt85b/lab05/grupo-a/statusonline / offline (retido)
  • seq é um contador: se chegarem seq 7, 8, 10, a mensagem 9 se perdeu.
  • uptime_ms (de millis()) mostra o instante da leitura do ponto de vista do ESP32.
Broker público

No Wokwi o ESP32 só alcança a internet, por isso usamos broker.hivemq.com:1883 — um broker público e sem senha: qualquer pessoa pode ler e publicar nesses tópicos. Não publique nada sensível. (Broker local no Wokwi exige o IoT Gateway do Wokwi Club.)

4.2 Código completo até aqui​

src/main.cpp (T4)
#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <Ticker.h>
#include <DHT.h>

#define GRUPO "a" // troque pela letra do seu grupo
#define DHT_PIN 15
#define DHT_TIPO DHT22

const char *WIFI_SSID = "Wokwi-GUEST";
const char *WIFI_SENHA = "";
const char *MQTT_HOST = "broker.hivemq.com";
const uint16_t MQTT_PORTA = 1883;
const char *TOPICO_DADOS = "elt85b/lab05/grupo-" GRUPO "/dht";

DHT dht(DHT_PIN, DHT_TIPO);
WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);
Ticker temporizador;

volatile bool leituraPendente = false;
const float INTERVALO_S = 5.0;
uint32_t seq = 0;
char clientId[32];

void aoEstourarTemporizador() {
leituraPendente = true;
}

void conectarWiFi() {
Serial.printf("[WiFi] conectando a %s", WIFI_SSID);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_SENHA, 6); // canal 6 acelera no Wokwi
while (WiFi.status() != WL_CONNECTED) {
delay(250);
Serial.print('.');
}
Serial.printf("\n[WiFi] conectado, IP %s\n", WiFi.localIP().toString().c_str());
}

void conectarMQTT() {
while (!mqtt.connected()) {
Serial.printf("[MQTT] conectando a %s como %s ... ", MQTT_HOST, clientId);
if (mqtt.connect(clientId)) {
Serial.println("ok");
} else {
Serial.printf("falhou (rc=%d), nova tentativa em 2 s\n", mqtt.state());
delay(2000);
}
}
}

void lerEPublicar() {
float t = dht.readTemperature();
float h = dht.readHumidity();
if (isnan(t) || isnan(h)) {
Serial.println("[DHT] falha na leitura (NaN) - descartada");
return;
}
seq++;
char payload[128];
snprintf(payload, sizeof(payload),
"{\"grupo\":\"%s\",\"seq\":%lu,\"t\":%.1f,\"h\":%.1f,\"uptime_ms\":%lu}",
GRUPO, (unsigned long)seq, t, h, (unsigned long)millis());
bool ok = mqtt.publish(TOPICO_DADOS, payload);
Serial.printf("[PUB] %s %s -> %s\n", ok ? "ok" : "ERRO", TOPICO_DADOS, payload);
}

void setup() {
Serial.begin(115200);
delay(200);
Serial.println("\n=== Lab 05 - T4: DHT22 + MQTT ===");
dht.begin();

uint64_t mac = ESP.getEfuseMac();
snprintf(clientId, sizeof(clientId), "lab05-%s-%04X", GRUPO, (uint16_t)(mac >> 32));

conectarWiFi();
mqtt.setServer(MQTT_HOST, MQTT_PORTA);
conectarMQTT();

temporizador.attach(INTERVALO_S, aoEstourarTemporizador);
}

void loop() {
if (WiFi.status() != WL_CONNECTED) conectarWiFi();
if (!mqtt.connected()) conectarMQTT();
mqtt.loop();

if (leituraPendente) {
leituraPendente = false;
lerEPublicar();
}
}

Pontos que valem a atenção:

  • "elt85b/lab05/grupo-" GRUPO "/dht" — literais de string adjacentes são concatenados pelo compilador; como GRUPO é um #define de string, o tópico é montado em tempo de compilação.
  • Client ID único: dois clientes com o mesmo ID no mesmo broker se derrubam mutuamente. Por isso o ID leva parte do MAC do chip.
  • %lu + cast para unsigned long: no ESP32 uint32_t e unsigned long têm ambos 4 bytes, mas são tipos diferentes para o printf — o cast deixa o formato correto e sem warnings.
  • O PubSubClient tem buffer padrão de 256 bytes (cabeçalho + tópico + payload). Nosso payload tem ~70 bytes; se um dia passar do limite, publish() devolve false (e o log mostra ERRO) — aí use mqtt.setBufferSize(...).
ESP32 físico sem DHT22?

Aplique as quatro mudanças da alternativa a este código, ou use o arquivo completo da T4 que está lá.

4.3 Verificando no broker​

Enquanto a simulação roda, assine o tópico. Use o MQTT Explorer da stack (conexão broker.hivemq.com, porta 1883) ou, pelo terminal, um mosquitto_sub descartável em Docker:

Terminal
docker run --rm eclipse-mosquitto:2 mosquitto_sub -h broker.hivemq.com \
-t 'elt85b/lab05/grupo-a/#' -v -C 5 | tee evidencias/t4-mqtt.txt

-C 5 encerra depois de 5 mensagens. Saída esperada:

evidencias/t4-mqtt.txt
elt85b/lab05/grupo-a/dht {"grupo":"a","seq":3,"t":24.5,"h":55.0,"uptime_ms":16288}
elt85b/lab05/grupo-a/dht {"grupo":"a","seq":4,"t":24.5,"h":55.0,"uptime_ms":21288}
elt85b/lab05/grupo-a/dht {"grupo":"a","seq":5,"t":31.2,"h":48.0,"uptime_ms":26288}
elt85b/lab05/grupo-a/dht {"grupo":"a","seq":6,"t":31.2,"h":48.0,"uptime_ms":31288}
elt85b/lab05/grupo-a/dht {"grupo":"a","seq":7,"t":31.2,"h":48.0,"uptime_ms":36288}
Preveja antes de rodar

P3: o que aparece se você assinar elt85b/lab05/+/dht (com + no lugar do grupo)? E o que aconteceria se dois grupos esquecessem de trocar o GRUPO e ficassem ambos com a?

Ponto de commit · T4 (20 pts) #

Publicacao MQTT periodica em JSON

git add src/main.cpp evidencias/t4-mqtt.txt && git commit -m "T4: Publicacao MQTT periodica em JSON" && git push

Parte 5 — Node-RED: receber e visualizar​

Suba a stack Docker do curso e abra o Node-RED em http://localhost:1880. Crie uma aba nova chamada Lab 05 e monte:

  1. mqtt in "leituras DHT22"
    • Server: novo broker broker.hivemq.com, porta 1883 (o mesmo em que o ESP32 publica — não o Mosquitto local, que o Wokwi não alcança).
    • Topic: elt85b/lab05/grupo-a/dht (seu grupo) · QoS 0.
    • Output: a parsed JSON object — o texto do payload vira um objeto JavaScript.
  2. debug "payload" → Output: msg.payload.
  3. debug "temperatura" → Output: msg.payload.t e marque node status (o valor aparece embaixo do nó, no próprio editor).
  4. Deploy. Com o Wokwi rodando, o nó mqtt in deve mostrar connected (verde) e o painel debug (ícone de inseto, à direita) recebe uma mensagem a cada 5 s.

Saída esperada no painel debug (uma mensagem, expandida):

Painel debug do Node-RED
29/09/2026, 18:40:12 node: payload
elt85b/lab05/grupo-a/dht : msg.payload : Object
▼ object
grupo: "a"
seq: 7
t: 24.5
h: 55
uptime_ms: 36288
JSON × texto

Troque temporariamente o Output do mqtt in para a String e faça Deploy: o debug passa a mostrar o payload entre aspas e o debug "temperatura" exibe undefined. Por quê? (Volte para a parsed JSON object antes de continuar.)

Evidências:

  1. No debug "payload", passe o mouse sobre 3 mensagens → ícone Copy value e cole cada uma numa linha de evidencias/t5-debug.txt:

    evidencias/t5-debug.txt
    {"grupo":"a","seq":7,"t":24.5,"h":55,"uptime_ms":36288}
    {"grupo":"a","seq":8,"t":24.5,"h":55,"uptime_ms":41288}
    {"grupo":"a","seq":9,"t":31.2,"h":48,"uptime_ms":46288}
  2. Selecione a aba Lab 05 → menu (☰) → Export → Current flow → JSON formatado → Download. Salve como nodered/flows-lab05.json.

Ponto de commit · T5 (15 pts) #

Node-RED recebendo as leituras no debug

git add nodered/flows-lab05.json evidencias/t5-debug.txt && git commit -m "T5: Node-RED recebendo as leituras no debug" && git push

Projeto integrador — status do nó com LWT​

Quem olha o Node-RED não tem como saber se o ESP32 caiu ou só está entre duas leituras. O MQTT resolve isso com o LWT (Last Will and Testament): ao conectar, o cliente deixa com o broker uma mensagem-testamento; se a conexão cair sem um DISCONNECT educado, o broker publica essa mensagem por ele.

Requisitos​

  1. Firmware
    • Novo tópico elt85b/lab05/grupo-X/status.
    • Conectar com LWT: mensagem offline, QoS 1, retida.
    • Logo após conectar, publicar online retido no mesmo tópico.
  2. Node-RED
    • Segundo mqtt in em .../status (Output: a String) → debug com node status ligado — o nó mostra online/offline no editor.
    • Exporte o fluxo de novo por cima de nodered/flows-lab05.json.
  3. Evidência — deixe um mosquitto_sub escutando o status, inicie a simulação, espere o online, pare a simulação e espere o offline; aí encerre com Ctrl+C. (Placa física: tire o cabo USB ou aperte EN — veja LWT na placa física.)
Terminal
docker run --rm eclipse-mosquitto:2 mosquitto_sub -h broker.hivemq.com \
-t 'elt85b/lab05/grupo-a/status' -v | tee evidencias/t6-status.txt

Dicas de API​

Trechos uteis (PubSubClient)
const char *TOPICO_STATUS = "elt85b/lab05/grupo-" GRUPO "/status";

// connect com LWT: (id, willTopic, willQos, willRetain, willMessage)
if (mqtt.connect(clientId, TOPICO_STATUS, 1, true, "offline")) {
mqtt.publish(TOPICO_STATUS, "online", true); // true = retida
}

Saída esperada:

evidencias/t6-status.txt
elt85b/lab05/grupo-a/status offline
elt85b/lab05/grupo-a/status online
elt85b/lab05/grupo-a/status offline

A primeira linha pode ser offline ou online (ou nem aparecer): é a mensagem retida de uma execução anterior, entregue assim que alguém assina o tópico.

Preveja antes de rodar

P4: depois de parar a simulação, quanto tempo leva para aparecer offline? Há dois caminhos: se a conexão TCP é fechada, o broker publica o testamento na hora; se ela só fica muda (ex.: cabo/Wi-Fi caiu), ele espera 1,5 × keepalive — e o keepalive padrão do PubSubClient é 15 s. Qual dos dois o Wokwi provocou? E o que muda se o firmware chamasse mqtt.disconnect() antes de parar?

Ponto de commit · T6 (20 pts) #

Status do no com LWT

git add src/main.cpp nodered/flows-lab05.json evidencias/t6-status.txt && git commit -m "T6: Status do no com LWT" && git push

Ao terminar — máquina de laboratório

Antes de sair, faça logout para não deixar sua conta do GitHub ativa na máquina (limpa a credencial salva do git e encerra a sessão do gh).