Início Etapa 2 Atividade 9
9
ETAPA 2 - MOBILE

Estação Meteorológica 🌡️📱

Monitore temperatura e umidade em tempo real no seu celular usando o DHT11!

🎯 Objetivo desta atividade

Criar um dashboard mobile que exibe temperatura e umidade em tempo real, com gráficos e indicadores visuais. O ESP32 lê o sensor DHT11 e envia os dados para a API PHP usando WiFiManager (configuração automática de WiFi)!

🏗️ Como funciona?

🌡️

DHT11

Sensor

📟

ESP32

WiFiManager

🖥️

API PHP

Servidor

📱

App

Dashboard

🌡️ 📟 🖥️ 📱

📚 Conceitos Novos

WiFiManager

Biblioteca que cria um portal de configuração WiFi. Não precisa mais alterar SSID/SENHA no código!

DHT11

Sensor digital que mede temperatura (0-50°C) e umidade (20-90%). Usa comunicação de 1 fio (one-wire).

Gauge (Indicador Circular)

Componente visual tipo "velocímetro" que mostra valores de forma intuitiva com cores e arcos.

Conforto Térmico

Combinação de temperatura e umidade que indica se o ambiente está confortável para humanos.

1

Backend - API PHP

Editar no VS Code

A API recebe os dados de temperatura e umidade do ESP32 via POST e retorna os dados para o app mobile via GET.

📁 Onde criar: Salve este arquivo em C:\xampp\htdocs\iot\api\dht.php

api/dht.php (VS Code)
<?php
/**
 * API para dados do sensor DHT11
 * Recebe temperatura e umidade do ESP32
 */

// CORS Headers
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
header("Content-Type: application/json");

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(200);
    exit();
}

$arquivo = __DIR__ . '/dht_dados.json';

// Inicializar arquivo
if (!file_exists($arquivo)) {
    file_put_contents($arquivo, json_encode([
        'temperatura' => 0,
        'umidade' => 0,
        'timestamp' => time()
    ]));
}

// GET: Retorna dados atuais
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
    $dados = json_decode(file_get_contents($arquivo), true);
    echo json_encode([
        'success' => true,
        'temperatura' => $dados['temperatura'] ?? 0,
        'umidade' => $dados['umidade'] ?? 0,
        'timestamp' => $dados['timestamp'] ?? time()
    ]);
    exit();
}

// POST: Recebe dados do ESP32
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $dados = json_decode(file_get_contents('php://input'), true);
    
    if (isset($dados['temperatura']) && isset($dados['umidade'])) {
        $novosDados = [
            'temperatura' => floatval($dados['temperatura']),
            'umidade' => floatval($dados['umidade']),
            'timestamp' => time()
        ];
        file_put_contents($arquivo, json_encode($novosDados));
        
        echo json_encode(['success' => true]);
    } else {
        http_response_code(400);
        echo json_encode(['error' => 'Campos obrigatórios']);
    }
    exit();
}
?>
2

ESP32 - Código com WiFiManager

Upload via Arduino IDE

📶 WiFiManager: Este código usa configuração automática de WiFi! Na primeira vez, conecte na rede "ESP32-Config" com senha "12345678" para configurar.

⚠️ Bibliotecas necessárias: Instale pelo Gerenciador de Bibliotecas:
WiFiManager (por tzapu)
DHT sensor library (por Adafruit)

dht_mobile.ino (Arduino IDE)
/************************************************
 * ESTAÇÃO METEOROLÓGICA - ESP32 + DHT11
 * 
 * Lê temperatura e umidade e envia para a API.
 * Usa WiFiManager para configuração automática!
 * 
 * Bibliotecas necessárias:
 * - WiFiManager (por tzapu)
 * - DHT sensor library (por Adafruit)
 ***********************************************/

#include <WiFiManager.h>   // Configuração automática de WiFi
#include <HTTPClient.h>    // Para requisições HTTP
#include "DHT.h"           // Biblioteca do sensor

// ========== CONFIGURAÇÕES ==========
// ALTERE PARA O IP DO SEU COMPUTADOR!
const char* URL_API = "http://192.168.1.100/iot/api/dht.php";

// Pinos
const int PINO_DHT = 12;    // Pino do sensor DHT11
const int PINO_BOTAO = 4;  // Botão para resetar WiFi

// Cria o objeto do sensor DHT
DHT dht(PINO_DHT, DHT11);

void setup() {
  Serial.begin(115200);
  pinMode(PINO_BOTAO, INPUT);
  
  // Inicializa o sensor DHT11
  dht.begin();
  
  Serial.println("Iniciando WiFiManager...");
  
  // ========== WiFiManager ==========
  // Cria o objeto WiFiManager
  WiFiManager wm;
  
  // Se o botão estiver pressionado no boot, reseta WiFi
  if (digitalRead(PINO_BOTAO) == LOW) {
    Serial.println("⚠️ Resetando configurações WiFi...");
    wm.resetSettings();
  }
  
  // Timeout de 3 minutos para configurar
  wm.setConfigPortalTimeout(180);
  
  // Tenta conectar. Se falhar, abre portal de configuração
  // Portal: rede "ESP32-Config" com senha "12345678"
  bool conectou = wm.autoConnect("ESP32-Config", "12345678");
  
  if (conectou) {
    Serial.println("✅ WiFi conectado!");
    Serial.print("IP: ");
    Serial.println(WiFi.localIP());
  } else {
    Serial.println("❌ Falha na conexão! Reiniciando...");
    ESP.restart();
  }
}

void loop() {
  // Verifica conexão WiFi
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("⚠️ WiFi desconectado! Reiniciando...");
    ESP.restart();
  }
  
  // Lê temperatura e umidade
  float temperatura = dht.readTemperature();
  float umidade = dht.readHumidity();
  
  // Verifica se a leitura foi válida
  if (isnan(temperatura) || isnan(umidade)) {
    Serial.println("❌ Erro ao ler o DHT11!");
    delay(2000);
    return;
  }
  
  Serial.print("🌡️ Temp: ");
  Serial.print(temperatura);
  Serial.print("°C | 💧 Umidade: ");
  Serial.print(umidade);
  Serial.println("%");
  
  // Envia para a API via POST
  HTTPClient http;
  http.begin(URL_API);
  http.addHeader("Content-Type", "application/json");
  
  // Monta o JSON
  String json = "{\"temperatura\":" + String(temperatura, 1) + 
                ",\"umidade\":" + String(umidade, 1) + "}";
  
  int httpCode = http.POST(json);
  
  if (httpCode == 200) {
    Serial.println("✅ Dados enviados!");
  } else {
    Serial.print("❌ Erro HTTP: ");
    Serial.println(httpCode);
  }
  
  http.end();
  
  // Aguarda 5 segundos
  delay(5000);
}

📶 Como funciona o WiFiManager?

  1. 1. Na primeira vez, o ESP32 cria a rede "ESP32-Config"
  2. 2. Conecte seu celular nessa rede (senha: 12345678)
  3. 3. Uma página abre automaticamente para escolher seu WiFi
  4. 4. O ESP32 salva e conecta automaticamente nas próximas vezes!
  5. 5. Para resetar: segure o botão (GPIO 4) ao ligar
3

App Mobile - Dashboard

Programar em snack.expo.dev

Este código cria um dashboard bonito com indicadores de temperatura e umidade, além de um indicador de conforto térmico!

⚠️ Importante: Altere a variável URL_API para o IP do seu computador!

App.js (Expo Snack)
/**
 * Estação Meteorológica - Dashboard Mobile
 * Tutorial do Prof. Reginaldo
 */

import React, { useState, useEffect } from 'react';
import { 
  View, 
  Text, 
  StyleSheet,
  TouchableOpacity,
  ActivityIndicator 
} from 'react-native';

// ⚠️ ALTERE PARA O IP DO SEU COMPUTADOR!
const URL_API = 'http://192.168.1.100/iot/api/dht.php';

export default function App() {
  // Estados do componente
  const [temperatura, setTemperatura] = useState(0);
  const [umidade, setUmidade] = useState(0);
  const [carregando, setCarregando] = useState(true);
  const [erro, setErro] = useState(null);
  const [ultimaAtualizacao, setUltimaAtualizacao] = useState('');

  // Função para buscar dados da API
  const buscarDados = async () => {
    try {
      const resposta = await fetch(URL_API);
      const dados = await resposta.json();
      
      if (dados.success) {
        setTemperatura(dados.temperatura);
        setUmidade(dados.umidade);
        setUltimaAtualizacao(new Date().toLocaleTimeString());
        setErro(null);
      }
    } catch (e) {
      setErro('Erro ao conectar');
    }
    setCarregando(false);
  };

  // Atualiza a cada 3 segundos
  useEffect(() => {
    buscarDados();
    const intervalo = setInterval(buscarDados, 3000);
    return () => clearInterval(intervalo);
  }, []);

  // Determina o conforto térmico
  const getConforto = () => {
    if (temperatura >= 20 && temperatura <= 26 && umidade >= 40 && umidade <= 60) {
      return { texto: 'Confortável 😊', cor: '#22c55e' };
    } else if (temperatura > 30 || umidade > 70) {
      return { texto: 'Desconfortável 🥵', cor: '#ef4444' };
    } else if (temperatura < 18) {
      return { texto: 'Frio 🥶', cor: '#3b82f6' };
    }
    return { texto: 'Aceitável 😐', cor: '#f59e0b' };
  };

  // Cor baseada na temperatura
  const getCorTemperatura = (temp) => {
    if (temp < 18) return '#3b82f6'; // Azul (frio)
    if (temp < 25) return '#22c55e'; // Verde (agradável)
    if (temp < 30) return '#f59e0b'; // Amarelo (quente)
    return '#ef4444'; // Vermelho (muito quente)
  };

  // Cor baseada na umidade
  const getCorUmidade = (umi) => {
    if (umi < 30) return '#f59e0b'; // Amarelo (seco)
    if (umi < 60) return '#22c55e'; // Verde (ideal)
    if (umi < 80) return '#3b82f6'; // Azul (úmido)
    return '#8b5cf6'; // Roxo (muito úmido)
  };

  const conforto = getConforto();

  if (carregando) {
    return (
      <View style={styles.container}>
        <ActivityIndicator size="large" color="#22d3ee" />
        <Text style={styles.carregandoTexto}>Carregando...</Text>
      </View>
    );
  }

  return (
    <View style={styles.container}>
      <Text style={styles.titulo}>🌡️ Estação Meteorológica</Text>
      <Text style={styles.subtitulo}>Monitoramento em tempo real</Text>
      
      // Cards de Temperatura e Umidade
      <View style={styles.cardsContainer}>
        // Card Temperatura
        <View style={[styles.card, { borderColor: getCorTemperatura(temperatura) }]}>
          <Text style={styles.cardEmoji}>🌡️</Text>
          <Text style={[styles.cardValor, { color: getCorTemperatura(temperatura) }]}>
            {temperatura.toFixed(1)}°C
          </Text>
          <Text style={styles.cardLabel}>Temperatura</Text>
        </View>
        
        // Card Umidade
        <View style={[styles.card, { borderColor: getCorUmidade(umidade) }]}>
          <Text style={styles.cardEmoji}>💧</Text>
          <Text style={[styles.cardValor, { color: getCorUmidade(umidade) }]}>
            {umidade.toFixed(1)}%
          </Text>
          <Text style={styles.cardLabel}>Umidade</Text>
        </View>
      </View>

      // Indicador de Conforto Térmico
      <View style={[styles.confortoContainer, { backgroundColor: conforto.cor + '20', borderColor: conforto.cor }]}>
        <Text style={[styles.confortoTexto, { color: conforto.cor }]}>
          {conforto.texto}
        </Text>
      </View>

      // Botão de Atualizar
      <TouchableOpacity style={styles.botaoAtualizar} onPress={buscarDados}>
        <Text style={styles.botaoTexto}>🔄 Atualizar</Text>
      </TouchableOpacity>

      {erro && (
        <Text style={styles.erro}>⚠️ {erro}</Text>
      )}

      <Text style={styles.ultimaAtualizacao}>
        Última atualização: {ultimaAtualizacao}
      </Text>
      
      <Text style={styles.rodape}>
        Tutorial do Prof. Reginaldo
      </Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#0f172a',
    alignItems: 'center',
    justifyContent: 'center',
    padding: 20,
  },
  titulo: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#22d3ee',
    marginBottom: 4,
  },
  subtitulo: {
    fontSize: 14,
    color: '#64748b',
    marginBottom: 30,
  },
  carregandoTexto: {
    color: '#64748b',
    marginTop: 10,
  },
  cardsContainer: {
    flexDirection: 'row',
    gap: 16,
    marginBottom: 24,
  },
  card: {
    backgroundColor: '#1e293b',
    borderRadius: 20,
    padding: 24,
    alignItems: 'center',
    borderWidth: 2,
    minWidth: 140,
  },
  cardEmoji: {
    fontSize: 40,
    marginBottom: 8,
  },
  cardValor: {
    fontSize: 32,
    fontWeight: 'bold',
    marginBottom: 4,
  },
  cardLabel: {
    fontSize: 14,
    color: '#64748b',
  },
  confortoContainer: {
    paddingHorizontal: 24,
    paddingVertical: 12,
    borderRadius: 20,
    borderWidth: 1,
    marginBottom: 24,
  },
  confortoTexto: {
    fontSize: 18,
    fontWeight: 'bold',
  },
  botaoAtualizar: {
    backgroundColor: '#334155',
    paddingHorizontal: 24,
    paddingVertical: 12,
    borderRadius: 12,
    marginBottom: 16,
  },
  botaoTexto: {
    color: '#fff',
    fontSize: 16,
  },
  erro: {
    color: '#ef4444',
    fontSize: 14,
    marginBottom: 10,
  },
  ultimaAtualizacao: {
    color: '#475569',
    fontSize: 12,
    marginBottom: 20,
  },
  rodape: {
    position: 'absolute',
    bottom: 30,
    color: '#475569',
    fontSize: 12,
  },
});

🧪 Testando o Sistema

  1. 1

    Inicie o XAMPP (Apache)

    Crie o arquivo api/dht.php

  2. 2

    Faça upload do código no ESP32

    Instale as bibliotecas WiFiManager e DHT

  3. 3

    Configure o WiFi

    Conecte na rede "ESP32-Config" e escolha seu WiFi

  4. 4

    Teste o app no Expo Snack

    Use o Expo Go para escanear o QR Code

🎉 Parabéns! Você criou uma estação meteorológica IoT com dashboard mobile e configuração automática de WiFi!

📝 Teste seus Conhecimentos

🎮

Quiz Gamificado

Responda às perguntas para ganhar XP e desbloquear a próxima atividade!

Carregando quiz...