🌡️ Temperatura online: ESP32 + DHT22 + Firebase + Dashboard Web

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.

1. Visió general del sistema

El sistema complet fa això:

2. Configuració de Firebase

1. Ves a console.firebase.google.com
2. Crea un projecte nou (pots desactivar Google Analytics per simplificar).
3. A l’apartat Build → Realtime Database, crea una base de dades.
4. Tria mode TEST per començar (després ja ho podràs restringir).
5. A Project settings → General → SDK setup and configuration (Web) copia:

Guarda aquests valors, els necessitaràs tant per al codi de l’ESP32 com per al dashboard web.

3. Estructura de dades a Firebase

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
      └── ...
    

4. Codi ESP32 + DHT22 + Firebase

Aquest codi s’executa a l’ESP32. Fa:

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).

5. Dashboard web: HTML + CSS + JS

Ara crearem un únic fitxer HTML que pots pujar a GitHub Pages o obrir localment. Aquest fitxer:

5.1. HTML complet del dashboard

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>
    

6. Resum final del projecte

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.