Projeto 04 — Monitor de temperatura e umidade no celular

Robótica com C — Bloco 3: Projetos Práticos

O que vamos construir

Um monitor ambiental que lê temperatura e umidade do ar em tempo real e envia os dados para o celular via Bluetooth. O Arduino funciona de forma autônoma — envia leituras a cada 10 segundos automaticamente e também responde a pedidos sob demanda.

🌡️
Automático (10s)
Arduino envia:
TEMP:24.5 HUM:62
📱
Sob demanda
Celular envia "R"
Arduino responde com leitura imediata
⚠️
Alertas
Temperatura ou umidade fora dos limiares configurados

Componente novo: sensor DHT11 / DHT22. Este projeto apresenta o sensor de temperatura e umidade mais popular da plataforma Arduino. É o primeiro sensor deste curso que usa um protocolo de comunicação proprietário de 1 fio e requer uma biblioteca específica — um padrão que você vai encontrar em muitos outros sensores.

🌡️ Componente novo: sensor DHT11 e DHT22

O DHT11 e o DHT22 são sensores digitais de temperatura e umidade relativa. Apesar de terem aparência similar, diferem significativamente em precisão e faixa:

Especificação DHT11 DHT22 (AM2302)
Faixa de temperatura 0°C a 50°C -40°C a 80°C
Precisão de temperatura ±2°C ±0,5°C
Faixa de umidade 20% a 80% UR 0% a 100% UR
Precisão de umidade ±5% UR ±2% UR
Intervalo mínimo entre leituras 1 segundo 0,5 segundo
Protocolo 1-Wire proprietário 1-Wire proprietário
Resistor pull-up 10kΩ entre DATA e VCC 10kΩ entre DATA e VCC
Preço estimado (BR) R$ 8–15 R$ 20–35
Para este projeto ✅ Suficiente ✅ Melhor precisão

DHT11 ou DHT22? Para projetos de aprendizado e monitoramento residencial, o DHT11 é suficiente. Para projetos que exigem precisão (estufa, sala de servidor, laboratório), use o DHT22. O código é idêntico — só muda a constante DHT11 ou DHT22 na declaração do objeto.

Ligação física

O DHT11/22 tem 4 pinos, mas apenas 3 são usados. Sempre use um resistor pull-up de 10kΩ entre DATA e VCC:

Pino do DHT Conectar em Observação
VCC (pino 1) 5V DHT11 aceita 3,3V a 5V
DATA (pino 2) Pino 4 do Arduino + resistor 10kΩ → VCC Resistor pull-up obrigatório
NC (pino 3) Não conectar Not Connected — ignorar
GND (pino 4) GND —

Resistor pull-up não é opcional: o protocolo 1-Wire do DHT requer que a linha DATA fique em HIGH quando ninguém está transmitindo. Sem o pull-up de 10kΩ, a linha fica flutuante e as leituras retornam NaN ou -999 mesmo com o sensor funcionando. Muitos módulos DHT já vêm com o resistor soldado na placa — verifique antes de adicionar um segundo.

Instalando a biblioteca DHT

O sensor usa um protocolo proprietário temporalmente crítico — a biblioteca encapsula toda essa complexidade e expõe apenas duas funções simples:

  1. Na IDE: Sketch → Incluir Biblioteca → Gerenciar Bibliotecas
  2. Pesquise DHT sensor library
  3. Instale a versão de Adafruit
  4. Quando solicitado, instale também a dependência Adafruit Unified Sensor

No Wokwi: a biblioteca DHT da Adafruit já está disponível nativamente. Adicione o componente DHT22 ou DHT11 no diagrama, conecte ao pino desejado e use normalmente — sem instalar nada.

// Uso básico da biblioteca:
#include <DHT.h>

#define PINO_DHT  4
#define TIPO_DHT  DHT11   // ou DHT22

DHT dht(PINO_DHT, TIPO_DHT);   // cria o objeto sensor

void setup() {
    dht.begin();               // inicializa o sensor
}

void loop() {
    float temp = dht.readTemperature();   // graus Celsius
    float hum  = dht.readHumidity();      // porcentagem

    // Verifica leitura inválida (sensor desconectado ou erro)
    if (isnan(temp) || isnan(hum)) {
        Serial.println("ERR:leitura_falhou");
        return;
    }

    Serial.print("Temp: "); Serial.print(temp); Serial.println(" C");
    Serial.print("Hum:  "); Serial.print(hum);  Serial.println(" %");
}

isnan() — verificando leitura inválida: quando o sensor falha (desconectado, pull-up ausente, leitura fora do intervalo), readTemperature() retorna NaN (Not a Number). A função isnan() detecta esse caso — sem essa verificação, você exibiria "Temp: nan C" no celular e não saberia se é problema de hardware ou de código.

🖥️ Simulação no Wokwi

Boa notícia: diferente dos projetos com HC-05, o sensor DHT11/DHT22 é simulado nativamente no Wokwi. Você pode adicionar o componente, girar um slider virtual para mudar temperatura e umidade durante a simulação, e ver as leituras chegando no Monitor Serial em tempo real.

A única limitação continua sendo o HC-05 — use o Monitor Serial como substituto do celular para validar a lógica completa antes de passar para o hardware físico.

Roteiro de simulação no Wokwi:

  1. Adicione o componente DHT22 no diagrama e conecte ao pino 4
  2. Adicione um LED + resistor no pino 8 (LED de alerta)
  3. Use a versão Serial do código (Seção 7) — sem HC-05
  4. Clique em ▶ Play e abra o Monitor Serial
  5. Durante a simulação, clique no DHT22 para abrir o painel de controle e ajuste temperatura e umidade com os sliders
  6. Veja os alertas disparando quando os valores ultrapassam os limiares
  7. Envie "R" no Monitor Serial para forçar uma leitura imediata

Hardware completo

Componente Conexão Função
DHT11/22 DATA Pino 4 + 10kΩ → 5V Sensor de temperatura e umidade
HC-05 TX → pino 10 Direto Bluetooth — recebe comandos do celular
HC-05 RX ← pino 11 Via divisor R1=1kΩ, R2=2kΩ Bluetooth — envia dados ao celular
LED alerta + 220Ω Pino 8 → R → LED → GND Indica alerta de temperatura ou umidade

Código completo (versão com HC-05 físico)

/*
 * =====================================================
 * Projeto 04 — Monitor de temperatura e umidade
 * DHT11/DHT22 + Bluetooth HC-05
 * =====================================================
 * Envio automático a cada 10 segundos
 * Comandos: R=leitura imediata | I=intervalo
 *           A=alertas on/off   | ?=ajuda
 * Formato: TEMP:xx.x HUM:xx.x STATUS:ok/alerta
 * =====================================================
 */

#include <DHT.h>
#include <SoftwareSerial.h>

// ── Sensor ─────────────────────────────────────────
#define PINO_DHT  4
#define TIPO_DHT  DHT11
DHT dht(PINO_DHT, TIPO_DHT);

// ── Bluetooth ──────────────────────────────────────
SoftwareSerial bluetooth(10, 11);

// ── Pinos ──────────────────────────────────────────
const int PINO_ALERTA = 8;

// ── Configuração de alertas ────────────────────────
float limiarTempMax  = 30.0;   // °C — acima disso: alerta
float limiarHumMin   = 30.0;   // %  — abaixo disso: alerta
bool  alertasAtivos  = true;

// ── Temporização ───────────────────────────────────
unsigned long intervaloEnvio = 10000;  // 10s padrão
unsigned long ultimoEnvio    = 0;


// ════════════════════════════════════════════════════
// Lê o sensor e retorna true se leitura for válida
// ════════════════════════════════════════════════════
bool lerSensor(float* temp, float* hum) {
    *temp = dht.readTemperature();
    *hum  = dht.readHumidity();
    return (!isnan(*temp) && !isnan(*hum));
}

// ════════════════════════════════════════════════════
// Envia leitura formatada pelo Bluetooth
// ════════════════════════════════════════════════════
void enviarLeitura() {
    float temp, hum;

    if (!lerSensor(&temp, &hum)) {
        bluetooth.println("ERR:sensor");
        Serial.println("ERR:sensor");
        return;
    }

    // Verifica condições de alerta
    bool emAlerta = alertasAtivos &&
                    (temp > limiarTempMax || hum < limiarHumMin);

    digitalWrite(PINO_ALERTA, emAlerta ? HIGH : LOW);

    // Envia dados formatados
    bluetooth.print("TEMP:");  bluetooth.print(temp, 1);
    bluetooth.print(" HUM:");  bluetooth.print(hum,  1);
    bluetooth.print(" ST:");   bluetooth.println(emAlerta ? "ALERTA" : "OK");

    Serial.print("TEMP:");  Serial.print(temp, 1);
    Serial.print(" HUM:");  Serial.print(hum,  1);
    Serial.print(" ST:");   Serial.println(emAlerta ? "ALERTA" : "OK");
}

// ════════════════════════════════════════════════════
// Processa comandos do celular
// ════════════════════════════════════════════════════
void processarMensagem(String msg) {
    if (msg.length() == 0) return;
    char cmd = msg[0];

    switch (cmd) {
        case 'R': case 'r':
            // Leitura imediata sob demanda
            enviarLeitura();
            break;

        case 'I': case 'i':
            // "I30" → muda intervalo para 30 segundos
            if (msg.length() > 1) {
                int seg = constrain(msg.substring(1).toInt(), 5, 300);
                intervaloEnvio = (unsigned long)seg * 1000UL;
                bluetooth.print("INTERVALO:"); bluetooth.println(seg);
            }
            break;

        case 'A': case 'a':
            // Toggle de alertas
            alertasAtivos = !alertasAtivos;
            bluetooth.print("ALERTAS:");
            bluetooth.println(alertasAtivos ? "ON" : "OFF");
            if (!alertasAtivos) digitalWrite(PINO_ALERTA, LOW);
            break;

        case '?':
            bluetooth.println("Cmds: R=ler I5-300=intervalo A=alertas");
            break;

        case '\n': case '\r': return;

        default:
            bluetooth.print("ERR:"); bluetooth.println(cmd);
    }
}


// ════════════════════════════════════════════════════
// SETUP e LOOP
// ════════════════════════════════════════════════════

void setup() {
    pinMode(PINO_ALERTA, OUTPUT);
    dht.begin();

    Serial.begin(9600);
    bluetooth.begin(9600);

    Serial.println("=== Monitor Ambiental ===");
    delay(2000);   // DHT precisa de ~2s para estabilizar após ligar

    enviarLeitura();   // envia leitura inicial imediatamente
    ultimoEnvio = millis();
}

void loop() {
    // Envio periódico automático
    if (millis() - ultimoEnvio >= intervaloEnvio) {
        ultimoEnvio = millis();
        enviarLeitura();
    }

    // Processa comandos recebidos
    if (bluetooth.available() > 0) {
        String msg = bluetooth.readStringUntil('\n');
        msg.trim();
        processarMensagem(msg);
    }

    // Espelho para debug
    if (Serial.available() > 0) {
        bluetooth.write(Serial.read());
    }
}

=== Monitor Ambiental ===

TEMP:24.5 HUM:62.0 ST:OK

... 10s depois ...

TEMP:24.7 HUM:61.5 ST:OK

[celular envia "R"] TEMP:24.8 HUM:61.0 ST:OK

[celular envia "I30"] INTERVALO:30

... temperatura sobe para 31°C ...

TEMP:31.2 HUM:60.0 ST:ALERTA

[celular envia "A"] ALERTAS:OFF

Código adaptado para simulação no Wokwi

Substitua bluetooth por Serial. O sensor DHT funciona normalmente no Wokwi — ajuste temperatura e umidade com os sliders do componente durante a simulação:

// ── Versão WOKWI ───────────────────────────────────
// Remove SoftwareSerial — usa Serial direto
// DHT22 funciona normalmente no Wokwi
// Envie pelo Monitor Serial: R I30 A ?

#include <DHT.h>

#define PINO_DHT  4
#define TIPO_DHT  DHT22   // Wokwi usa DHT22 por padrão
DHT dht(PINO_DHT, TIPO_DHT);

const int PINO_ALERTA    = 8;
float     limiarTempMax  = 30.0;
float     limiarHumMin   = 30.0;
bool      alertasAtivos  = true;
unsigned long intervaloEnvio = 10000;
unsigned long ultimoEnvio    = 0;

bool lerSensor(float* temp, float* hum) {
    *temp = dht.readTemperature();
    *hum  = dht.readHumidity();
    return (!isnan(*temp) && !isnan(*hum));
}

void enviarLeitura() {
    float temp, hum;
    if (!lerSensor(&temp, &hum)) { Serial.println("ERR:sensor"); return; }
    bool emAlerta = alertasAtivos && (temp > limiarTempMax || hum < limiarHumMin);
    digitalWrite(PINO_ALERTA, emAlerta ? HIGH : LOW);
    Serial.print("TEMP:"); Serial.print(temp, 1);
    Serial.print(" HUM:"); Serial.print(hum, 1);
    Serial.print(" ST:");  Serial.println(emAlerta ? "ALERTA" : "OK");
}

void processarMensagem(String msg) {
    if (msg.length() == 0) return;
    char cmd = msg[0];
    switch (cmd) {
        case 'R': case 'r': enviarLeitura(); break;
        case 'I': case 'i':
            if (msg.length() > 1) {
                int seg = constrain(msg.substring(1).toInt(), 5, 300);
                intervaloEnvio = (unsigned long)seg * 1000UL;
                Serial.print("INTERVALO:"); Serial.println(seg);
            }
            break;
        case 'A': case 'a':
            alertasAtivos = !alertasAtivos;
            Serial.print("ALERTAS:"); Serial.println(alertasAtivos ? "ON" : "OFF");
            if (!alertasAtivos) digitalWrite(PINO_ALERTA, LOW);
            break;
        case '?': Serial.println("Cmds: R I5-300 A ?"); break;
        case '\n': case '\r': return;
        default: Serial.print("ERR:"); Serial.println(cmd);
    }
}

void setup() {
    pinMode(PINO_ALERTA, OUTPUT);
    dht.begin();
    Serial.begin(9600);
    Serial.println("=== Monitor Wokwi — R I30 A ?");
    delay(2000);
    enviarLeitura();
    ultimoEnvio = millis();
}

void loop() {
    if (millis() - ultimoEnvio >= intervaloEnvio) {
        ultimoEnvio = millis();
        enviarLeitura();
    }
    if (Serial.available() > 0) {
        String msg = Serial.readStringUntil('\n');
        msg.trim();
        processarMensagem(msg);
    }
}

Testando alertas no Wokwi: durante a simulação, clique no componente DHT22 e arraste o slider de temperatura para acima de 30°C. Você deve ver ST:ALERTA no Monitor Serial e o LED de alerta acender. Arraste de volta para menos de 30°C e confirme que o alerta desaparece. Isso valida toda a lógica de limiar antes de usar o hardware físico.

Decisões de projeto

Por que lerSensor(float*, float*) usa ponteiros?

A função precisa retornar dois valores (temperatura e umidade) e ainda indicar se a leitura foi válida — três informações em uma única chamada. A solução é passar os dois floats por ponteiro (saída) e usar o bool de retorno para indicar sucesso ou falha. É exatamente o padrão que você aprendeu na Aula 14 com minMax().

Por que o delay(2000) no setup()?

O DHT11/22 precisa de pelo menos 1–2 segundos para estabilizar após ser alimentado. Chamar readTemperature() antes disso retorna NaN. O delay() no setup() é aceitável aqui — acontece apenas uma vez e não bloqueia nenhuma tarefa crítica.

Por que enviar automaticamente em vez de só responder a pedidos?

Em um monitor ambiental, o usuário quer ser notificado de condições adversas — não ficar pedindo leituras manualmente. O envio periódico garante que o celular sempre tem dados atualizados e que alertas chegam mesmo quando o usuário não está interagindo com o app.

Padrão push vs pull: envio automático é o padrão "push" — o Arduino empurra dados pro celular. Resposta a comando é o padrão "pull" — o celular puxa os dados. Este projeto usa os dois simultaneamente, o que é o comportamento esperado de um sistema de monitoramento real.

Conceitos do curso aplicados neste projeto

Conceito Aula de origem Onde aparece neste projeto
Passagem por ponteiro — dois retornos Aula 14 lerSensor(float* temp, float* hum)
Verificação de NaN Conceito novo isnan(temp) — detecta leitura inválida do DHT
Envio periódico com millis() Aula 41b / 45 Leitura automática a cada intervaloEnvio ms
Intervalo configurável em runtime Projeto 01 Comando "I30" muda o intervalo sem reiniciar
Bluetooth HC-05 + protocolo Projetos 02 e 03 Canal de comunicação — substituível por Serial no Wokwi
Feedback estruturado chave:valor Aula 46 "TEMP:24.5 HUM:62.0 ST:OK"
Biblioteca externa com #include Aula 41a #include <DHT.h> — instala pelo Gerenciador

Quiz — Projeto 04

Teste sua compreensão do sensor DHT e do monitor ambiental:

1. Por que o resistor pull-up de 10kΩ entre DATA e VCC é obrigatório no DHT11/22?

2. O que isnan(temp) verifica?

3. Por que lerSensor() usa ponteiros (float*) em vez de retornar os valores diretamente?

4. Qual a principal diferença prática entre DHT11 e DHT22 para este projeto?

5. Por que há um delay(2000) no setup() antes da primeira leitura?

6. O projeto usa envio automático (push) E resposta a comandos (pull) simultaneamente. O que garante que os dois funcionem sem bloqueio mútuo?

7. Um aluno testou o projeto no Wokwi e quer acionar o alerta de temperatura. O que ele deve fazer?

8. O comando "I30" muda o intervalo de envio para 30 segundos sem reiniciar o Arduino. Qual padrão do curso isso demonstra?