Estação Meteorológica IoT
ESP32 + DHT22 + Banco de Dados + Pilhas
🎯 O que vamos fazer?
Neste projeto incrível, você vai criar uma Estação Meteorológica Profissional! Não usaremos cabo USB para energia; o ESP32 rodará com 4 pilhas AA. Para economizar bateria, ele usará a técnica de Deep Sleep (Hibernação) - acordando a cada X minutos para ler o sensor e enviar para um Banco de Dados MySQL via PHP.
Bateria & Deep Sleep
Alimentação via 4 pilhas AA. O chip dorme para durar meses.
Banco de Dados MySQL
Os dados não se perdem! Ficam salvos no servidor para sempre.
Dashboard Histórico
Gráficos dinâmicos com histórico diário, semanal e mensal.
🛠️ Material Necessário
-
⚡
1x Placa ESP32 O cérebro do projeto
-
🌡️
1x Sensor DHT22 Branco, com 4 pernas ou módulo com 3
-
🔋
1x Suporte + 4 Pilhas AA Fornecerá ~6V (ou 4.8V se recarregáveis)
-
🔌
Breadboard e Jumpers Para fazer as conexões
-
🔘
1x Botão Push-button (Opcional) Para "Acordar" o ESP32 manualmente (Wake Up Externo)
🔌 Esquema de Montagem
⚠️ ATENÇÃO COM A ENERGIA!
- O fio Vermelho do Suporte de Pilhas DEVE ser ligado no pino VIN (ou 5V) do ESP32, e não no 3.3V!
- O pino VIN possui um regulador interno que baixa a tensão das pilhas para os 3.3V seguros para o chip.
- Ligar 6V direto no pino 3.3V queimará seu ESP32.
| Componente | Pino no Componente | Conexão no ESP32 | Cor Sugerida/Observação |
|---|---|---|---|
| Suporte 4 Pilhas | Cabo Positivo (+) | VIN ou 5V | Vermelho |
| Cabo Negativo (-) | GND | Preto | |
| Sensor DHT22 | Pino 1 (VCC) ou + | 3.3V | Vermelho (O DHT22 trabalha em 3.3V) |
| Pino 2 (DATA) ou OUT | GPIO 4 | Verde/Azul | |
| Pino 4 (GND) ou - | GND | Preto | |
| Botão (Wake Up) | Terminal 1 | 3.3V (HIGH) | Laranja/Vermelho |
| Terminal 2 | GPIO 33 | Ligamos GPIO no botão em modo Pull-Down interno |
💻 Códigos do Projeto
Criar Tabela no MySQL
Rode este código no seu phpMyAdmin
Este SQL cria a estrutura onde os dados climáticos viverão. Ele precisa
rodar no banco de dados configurado no api/config.php.
-- Criar a tabela para armazenar os dados de clima
CREATE TABLE IF NOT EXISTS estacao_meteorologica (
id INT AUTO_INCREMENT PRIMARY KEY,
ambiente VARCHAR(50) NOT NULL,
temperatura FLOAT NOT NULL,
umidade FLOAT NOT NULL,
bateria FLOAT DEFAULT NULL,
data_registro TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
API PHP (api/clima.php)
Recebe os POSTs do ESP e fornece GETs ao Gráfico
Crie este arquivo api/clima.php. Ele faz a ponte mágica entre
o hardware (ESP32) e o software Front-end.
<?php
require_once 'config.php';
setCorsHeaders();
$method = $_SERVER['REQUEST_METHOD'];
try {
$pdo = getConnection();
// GET: Enviar os dados ao Gráfico do Dashboard
if ($method === 'GET') {
$filtro = $_GET['filtro'] ?? 'hoje';
$sql = "SELECT * FROM estacao_meteorologica WHERE 1=1";
switch ($filtro) {
case 'hoje': $sql .= " AND DATE(data_registro) = CURDATE()"; break;
case 'semana': $sql .= " AND data_registro >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)"; break;
case 'mes': $sql .= " AND data_registro >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)"; break;
case 'ano': $sql .= " AND data_registro >= DATE_SUB(CURDATE(), INTERVAL 365 DAY)"; break;
}
$sql .= " ORDER BY data_registro ASC";
$stmt = $pdo->prepare($sql);
$stmt->execute();
jsonResponse([
'success' => true,
'dados' => $stmt->fetchAll(PDO::FETCH_ASSOC)
]);
}
// POST: Receber os dados do ESP32 via Wi-FI
else if ($method === 'POST') {
$data = getPostData();
$ambiente = sanitize($data['ambiente'] ?? 'N/A');
$temp = filter_var($data['temperatura'], FILTER_VALIDATE_FLOAT);
$umid = filter_var($data['umidade'], FILTER_VALIDATE_FLOAT);
if ($temp === false || $umid === false) {
jsonResponse(['success' => false, 'error' => 'Temp/Umidade invalidos'], 400);
}
$sql = "INSERT INTO estacao_meteorologica (ambiente, temperatura, umidade) VALUES (?, ?, ?)";
$pdo->prepare($sql)->execute([$ambiente, $temp, $umid]);
jsonResponse([
'success' => true,
'message' => 'Salvo!'
], 201);
}
} catch (PDOException $e) {
jsonResponse(['success' => false, 'error' => 'Erro de DB'], 500);
}
?>
Arduino: ESP32 com Deep Sleep
Embarque na placa após configurar o IP da URL.
Este é o cérebro físico! Ele dorme quase o tempo todo consumindo o mínimo absoluto de bateria. Acorda a cada 30 minutos, lê as métricas, atira na API e pisca o LED de sono dinovo!
⚠️ MUDE O IP: Não se esqueça de alterar
http://192.168.../api/clima.php para o IP da sua máquina local no XAMPP ou seu
domínio.
#include <WiFi.h>
#include <HTTPClient.h>
#include <DHT.h>
// ⚠️ CONFIGURE SUA REDE AQUI
const char* ssid = "SUA_REDE_WIFI";
const char* password = "SUA_SENHA_WIFI";
const char* serverUrl = "http://192.168.0.XYZ/iot/api/clima.php";
const String NOME_AMBIENTE = "Sala de Estar";
#define DHTPIN 4
#define DHTTYPE DHT22
#define BOTAO_WAKEUP 33
#define TEMPO_DORMINDO 30 * 60 * 1000000ULL // 30 Minutos em Microsegundos
DHT dht(DHTPIN, DHTTYPE);
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("--- ESP32 ACORDOU! ---");
dht.begin();
delay(2000); // DHT precisa estabilizar
float umidade = dht.readHumidity();
float temperatura = dht.readTemperature();
if (isnan(umidade) || isnan(temperatura)) {
Serial.println("Falha Sensor!");
voltarADormir();
return;
}
WiFi.begin(ssid, password);
int t = 0;
while (WiFi.status() != WL_CONNECTED && t < 20) {
delay(500);
t++;
}
if (WiFi.status() == WL_CONNECTED) {
enviarDados(temperatura, umidade);
}
voltarADormir();
}
void loop() {
// O chip dorme no SETUP. O LOOP NUNCA RODA no Deep Sleep
}
void enviarDados(float temp, float umid) {
HTTPClient http;
http.begin(serverUrl);
http.addHeader("Content-Type", "application/json");
String payload = "{\"ambiente\":\"" + NOME_AMBIENTE + "\",\"temperatura\":" + String(temp) + ",\"umidade\":" + String(umid) + "}";
http.POST(payload);
http.end();
}
void voltarADormir() {
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
esp_sleep_enable_timer_wakeup(TEMPO_DORMINDO);
esp_sleep_enable_ext0_wakeup((gpio_num_t)BOTAO_WAKEUP, 1);
Serial.println("ZzZzZzZ");
Serial.flush();
esp_deep_sleep_start();
}
Front-end: Dashboard Interativo
Biblioteca Chart.js e Requisição Fetch
Este JavaScript deve ser adicionado no final da sua página HTML (antes de
fechar o </body>). É ele que busca os dados da API e "desenha" o gráfico
bonito na tela!
// 1. Variável global e Estado
let myChart = null;
let ambienteAtual = 'Todos';
// 2. Modificado: Busca dados do ambiente selecionado também!
async function carregarDados(filtro) {
try {
const url = `api/clima.php?filtro=${filtro}&ambiente=${encodeURIComponent(ambienteAtual)}`;
const resposta = await fetch(url);
const json = await resposta.json();
if (json.success && json.dados.length > 0) {
atualizarGraficoDashboard(json.dados);
} else {
console.warn("Sem dados...");
}
} catch (erro) { console.error(erro); }
}
// 3. Recebe o JSON do banco e prepara os arrays
function atualizarGraficoDashboard(dados_bd) {
const labels = dados_bd.map(r => new Date(r.data_registro).toLocaleString('pt-BR'));
const temps = dados_bd.map(r => parseFloat(r.temperatura));
const umids = dados_bd.map(r => parseFloat(r.umidade));
renderizarChart(labels, temps, umids);
}
// 4. Desenha o Gráfico Linha Vermelha / Azul usando Chart.js
function renderizarChart(labels, temps, umids) {
const ctx = document.getElementById('climaChart').getContext('2d');
// Se já existe um gráfico, destrói e recria (para animação funcionar)
if (myChart) myChart.destroy();
myChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: 'Temperatura (°C)',
data: temps,
borderColor: '#f59e0b',
borderWidth: 3,
tension: 0.4
},
{
label: 'Umidade (%)',
data: umids,
borderColor: '#3b82f6',
borderWidth: 2,
borderDash: [5, 5],
tension: 0.4
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: { mode: 'index', intersect: false }
}
});
}
// Carrega automático ao abrir a página!
window.addEventListener('load', () => carregarDados('hoje'));
📊 Dashboard da Estação Metereológica
🎮 Desafio Final
Em Breve: Quiz da Atividade 11