En aquesta pàgina tens el projecte explicat pas a pas: ESP32 + DHT22 + Firebase Realtime Database + Dashboard web amb gràfiques, humitat, alertes i estat del dispositiu.
El sistema complet fa això:
status i lastSeen per saber si està online.Guarda aquests valors, els necessitaràs tant per al codi de l’ESP32 com per al dashboard web.
El nostre objectiu és tenir una estructura clara com aquesta:
sensor
├── status: "online"
├── lastSeen: 1700000000
└── history
├── 1700000000
│ ├── temp: 23.4
│ └── hum: 45.2
├── 1700000060
│ ├── temp: 23.6
│ └── hum: 46.0
└── ...
sensor/status: indica si l’ESP32 està "online" (actualitzat periòdicament).sensor/lastSeen: últim timestamp (en segons) en què l’ESP32 ha actualitzat l’estat.sensor/history/<timestamp>/temp: temperatura en °C.sensor/history/<timestamp>/hum: humitat relativa en %.Aquest codi s’executa a l’ESP32. Fa:
API_KEY i DATABASE_URL.sensor/history/<timestamp>.sensor/status i sensor/lastSeen.Substitueix els textos en majúscules (EL_TEUS_SSID, LA_TEVA_CONTRASENYA, etc.) pels teus valors reals.
#include <WiFi.h>
#include <DHT.h>
#include <Firebase_ESP_Client.h>
#include "time.h"
// ---------- CONFIGURACIÓ WIFI ----------
#define WIFI_SSID "EL_TEUS_SSID"
#define WIFI_PASSWORD "LA_TEVA_CONTRASENYA"
// ---------- CONFIGURACIÓ FIREBASE ----------
#define API_KEY "LA_TEVA_API_KEY"
#define DATABASE_URL "https://EL_TEU_PROJECTE.firebaseio.com/"
// ---------- CONFIGURACIÓ DHT ----------
#define DHTPIN 4 // GPIO on tens el DHT22
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
// Objectes Firebase
FirebaseData fbdo;
FirebaseAuth auth;
FirebaseConfig config;
// Per controlar cada quant actualitzem l'estat
unsigned long lastStatusUpdate = 0;
// --------------------
// Connectar al WiFi
// --------------------
void connectWiFi() {
Serial.print("Connectant a WiFi");
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(300);
Serial.print(".");
}
Serial.println();
Serial.print("Connectat! IP: ");
Serial.println(WiFi.localIP());
}
// --------------------
// Configurar hora NTP
// --------------------
void setupTime() {
configTime(0, 0, "pool.ntp.org");
Serial.println("Sincronitzant hora NTP...");
struct tm timeinfo;
while (!getLocalTime(&timeinfo)) {
Serial.println("Encara sense hora, esperant...");
delay(500);
}
Serial.println("Hora NTP configurada correctament.");
}
// --------------------
// SETUP
// --------------------
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println();
Serial.println("Iniciant ESP32 + DHT22 + Firebase...");
connectWiFi();
// Configuració bàsica de Firebase
config.api_key = API_KEY;
config.database_url = DATABASE_URL;
// Inicialitzar Firebase
Firebase.begin(&config, &auth);
Firebase.reconnectWiFi(true);
// Configurar hora
setupTime();
// Inicialitzar DHT
dht.begin();
Serial.println("DHT inicialitzat.");
}
// --------------------
// LOOP
// --------------------
void loop() {
// 1. Llegir temperatura i humitat
float temp = dht.readTemperature(); // °C
float hum = dht.readHumidity(); // %
if (!isnan(temp) && !isnan(hum)) {
time_t now = time(nullptr); // timestamp en segons
// Path base per a aquest timestamp
String basePath = "/sensor/history/" + String(now);
// Guardar temperatura
Firebase.RTDB.setFloat(&fbdo, (basePath + "/temp").c_str(), temp);
// Guardar humitat
Firebase.RTDB.setFloat(&fbdo, (basePath + "/hum").c_str(), hum);
Serial.printf("Enviat a %s: %.1f°C, %.1f%%\n", basePath.c_str(), temp, hum);
} else {
Serial.println("Error llegint del DHT22 (NaN).");
}
// 2. Actualitzar estat cada 30 segons
if (millis() - lastStatusUpdate > 30000) {
time_t now = time(nullptr);
Firebase.RTDB.setString(&fbdo, "/sensor/status", "online");
Firebase.RTDB.setInt(&fbdo, "/sensor/lastSeen", now);
lastStatusUpdate = millis();
Serial.println("Estat actualitzat: online");
}
// 3. Esperar 60 segons abans de la següent lectura
delay(60000);
}
Pots ajustar el temps de delay(60000) si vols lectures més freqüents (p. ex. cada 10s).
Ara crearem un únic fitxer HTML que pots pujar a GitHub Pages o obrir localment. Aquest fitxer:
Aquest és el codi complet del dashboard. Substitueix API_KEY i
https://PROJECT.firebaseio.com pels valors del teu projecte.
<meta charset="UTF-8">
<title>Dashboard ESP32 + DHT22</title>
<!-- Chart.js per a la gràfica -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<!-- Firebase SDK (App + Database) -->
<script src="https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/10.7.1/firebase-database.js"></script>
<style>
body {
font-family: Arial, sans-serif;
max-width: 900px;
margin: auto;
background: #020617;
color: #e5e7eb;
padding: 20px;
}
.card {
background: #020617;
border: 1px solid #1e293b;
padding: 16px;
margin: 12px 0;
border-radius: 10px;
}
h1, h2 {
color: #38bdf8;
}
button {
padding: 8px 12px;
border-radius: 8px;
border: none;
cursor: pointer;
margin: 4px;
background: #38bdf8;
color: #020617;
font-weight: bold;
}
#status {
font-size: 1.1rem;
margin-top: 8px;
}
#status.online { color: #22c55e; }
#status.offline { color: #ef4444; }
#alertTemp { color: #f87171; font-weight: bold; }
#alertHum { color: #fbbf24; font-weight: bold; }
</style>
</head>
<body>
<h1>🌡️ Dashboard ESP32 + DHT22</h1>
<div class="card">
<h2>Estat del dispositiu</h2>
<div id="status">Carregant...</div>
<div id="lastSeen"></div>
</div>
<div class="card">
<h2>Lectures actuals</h2>
<h3>Temperatura: <span id="temp">--</span></h3>
<h3>Humitat: <span id="hum">--</span></h3>
<div id="alertTemp"></div>
<div id="alertHum"></div>
</div>
<div class="card">
<h2>Historial</h2>
<button onclick="setRange('1h')">1 hora</button>
<button onclick="setRange('24h')">24 hores</button>
<button onclick="setRange('all')">Tot</button>
<canvas id="chart"></canvas>
</div>
<script>
// 1. Configuració Firebase
const firebaseConfig = {
apiKey: "API_KEY",
databaseURL: "https://PROJECT.firebaseio.com"
};
firebase.initializeApp(firebaseConfig);
const db = firebase.database();
// 2. Variables globals
let dataGlobal = {};
let range = "all";
// 3. Configuració de la gràfica
const ctx = document.getElementById("chart").getContext("2d");
const chart = new Chart(ctx, {
type: "line",
data: {
labels: [],
datasets: [
{ label: "Temperatura (°C)", data: [], borderColor: "#38bdf8", tension: 0.2 },
{ label: "Humitat (%)", data: [], borderColor: "#fbbf24", tension: 0.2 }
]
}
});
// 4. Filtrar dades per rang
function filterData(data) {
const now = Date.now() / 1000;
return Object.entries(data).filter(([t]) => {
t = Number(t);
if (range === "1h") return now - t < 3600;
if (range === "24h") return now - t < 86400;
return true;
});
}
// 5. Actualitzar gràfica i textos
function update() {
const filtered = filterData(dataGlobal);
const labels = [];
const temps = [];
const hums = [];
filtered.forEach(([t, obj]) => {
labels.push(new Date(t * 1000).toLocaleTimeString());
temps.push(obj.temp);
hums.push(obj.hum);
});
chart.data.labels = labels;
chart.data.datasets[0].data = temps;
chart.data.datasets[1].data = hums;
chart.update();
if (temps.length) {
const lastT = temps.at(-1);
const lastH = hums.at(-1);
document.getElementById("temp").innerText = lastT.toFixed(1) + " °C";
document.getElementById("hum").innerText = lastH.toFixed(1) + " %";
document.getElementById("alertTemp").innerText =
lastT > 30 ? "⚠️ Temperatura massa alta!" : "";
document.getElementById("alertHum").innerText =
lastH > 70 ? "⚠️ Humitat massa alta!" : "";
}
}
// 6. Canviar rang
function setRange(r) {
range = r;
update();
}
window.setRange = setRange;
// 7. Estat del dispositiu
db.ref("sensor/status").on("value", snap => {
const status = snap.val();
const el = document.getElementById("status");
if (status === "online") {
el.innerText = "🟢 Online";
el.className = "online";
} else {
el.innerText = "🔴 Offline";
el.className = "offline";
}
});
db.ref("sensor/lastSeen").on("value", snap => {
const ts = snap.val();
if (!ts) return;
document.getElementById("lastSeen").innerText =
"Últim cop vist: " + new Date(ts * 1000).toLocaleString();
});
// 8. Lectura en temps real de l'historial
db.ref("sensor/history").on("value", snap => {
dataGlobal = snap.val() || {};
update();
});
</script>
</body>
</html>
sensor/history/<timestamp> a Firebase.sensor/status i sensor/lastSeen per indicar que és viu.A partir d’aquí pots afegir: configuració de llindars des de la web, notificacions (Telegram, email), o fins i tot múltiples sensors/ESP32 al mateix dashboard.