1. Project Overview

In this tutorial, we'll build a real-time environmental monitor using the ESP32 microcontroller and DHT22 temperature/humidity sensor. The ESP32 will host its own web server accessible on your local WiFi network, displaying live sensor readings on a mobile-friendly dashboard.

2. Parts Required

ComponentQtyEst. PriceNotes
ESP32 Dev Board1~$5–8Any ESP32-WROOM-32 variant
DHT22 Sensor1~$3–5More accurate than DHT11
10kΊ Resistor1~$0.10Pull-up resistor for DHT22
Breadboard1~$2Half-size is sufficient
Jumper Wires~10~$1M-M wires
USB Cable (Micro/USB-C)1VariesFor programming ESP32
💡
Total project cost is under $20. All components are widely available on Tokopedia, Shopee, or AliExpress.

3. Wiring Diagram

Wire the DHT22 to your ESP32 as follows:

text
DHT22 Pin   →   ESP32 Pin
─────────────────────────
VCC (Pin 1) →   3.3V
Data (Pin 2)→   GPIO 4  (+ 10kΩ pull-up to 3.3V)
NC  (Pin 3) →   Not connected
GND (Pin 4) →   GND

Note: Place the 10kΊ resistor between Data and VCC pins.
⚠️
Use 3.3V for DHT22 with ESP32, NOT 5V. The ESP32 GPIO pins are NOT 5V tolerant. Using 5V may permanently damage your ESP32.

4. Arduino Code

First, install these libraries via Arduino IDE → Sketch → Include Library → Manage Libraries:

  • DHT sensor library by Adafruit
  • Adafruit Unified Sensor
  • WiFi (built-in for ESP32)
cpp (Arduino)
#include <WiFi.h>
#include <WebServer.h>
#include <DHT.h>
#include <ArduinoJson.h>

// ── Configuration ──
#define DHTPIN    4       // GPIO connected to DHT22 data pin
#define DHTTYPE   DHT22

const char* SSID = "Your_WiFi_SSID";
const char* PASSWORD = "Your_WiFi_Password";

// ── Globals ──
DHT dht(DHTPIN, DHTTYPE);
WebServer server(80);

// ── Sensor Data ──
float temperature = 0.0;
float humidity = 0.0;
unsigned long lastRead = 0;
const unsigned long READ_INTERVAL = 2000; // Read every 2 seconds

void readSensor() {
  if (millis() - lastRead < READ_INTERVAL) return;
  lastRead = millis();
  
  float t = dht.readTemperature();
  float h = dht.readHumidity();
  
  if (!isnan(t) && !isnan(h)) {
    temperature = t;
    humidity = h;
    Serial.printf("Temp: %.1f°C | Humidity: %.1f%%\n", t, h);
  } else {
    Serial.println("DHT22 read failed!");
  }
}

void handleRoot() {
  // Serve the web dashboard HTML (see next section)
  server.send(200, "text/html", getDashboardHTML());
}

void handleData() {
  // JSON API endpoint for live data
  StaticJsonDocument<128> doc;
  doc["temperature"] = temperature;
  doc["humidity"] = humidity;
  doc["heatIndex"] = dht.computeHeatIndex(temperature, humidity, false);
  doc["timestamp"] = millis();
  
  String output;
  serializeJson(doc, output);
  server.send(200, "application/json", output);
}

void setup() {
  Serial.begin(115200);
  dht.begin();
  
  // Connect to WiFi
  WiFi.begin(SSID, PASSWORD);
  Serial.print("Connecting to WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nConnected! IP: " + WiFi.localIP().toString());
  
  // Register routes
  server.on("/", handleRoot);
  server.on("/data", handleData);
  server.begin();
  Serial.println("HTTP server started");
}

void loop() {
  readSensor();
  server.handleClient();
}

5. ESP32 Web Server Dashboard

The ESP32 serves an HTML page that fetches live data from the /data endpoint every 2 seconds:

cpp - getDashboardHTML()
String getDashboardHTML() {
  return R"rawhtml(
<!DOCTYPE html>
<html>
<head>
  <meta charset='UTF-8'/>
  <meta name='viewport' content='width=device-width,initial-scale=1'/>
  <title>ESP32 Monitor</title>
  <style>
    body{font-family:sans-serif;background:#0D1526;color:#fff;margin:0;padding:20px;text-align:center}
    h1{font-size:1.5rem;color:#06B6D4;margin-bottom:30px}
    .card{background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.1);
          border-radius:16px;padding:30px;margin:15px auto;max-width:300px;display:inline-block}
    .val{font-size:3rem;font-weight:900}
    .unit{font-size:1.2rem;color:#94A3B8}
    .label{font-size:14px;color:#64748B;margin-top:8px}
    .temp{color:#EF4444}
    .hum{color:#3B82F6}
    #status{font-size:12px;color:#64748B;margin-top:20px}
  </style>
</head>
<body>
  <h1>⚡ ESP32 Environment Monitor</h1>
  <div class='card'>
    <div class='val temp'><span id='temp'>--</span><span class='unit'>°C</span></div>
    <div class='label'>🌡️ Temperature</div>
  </div>
  <div class='card'>
    <div class='val hum'><span id='hum'>--</span><span class='unit'>%</span></div>
    <div class='label'>💧 Humidity</div>
  </div>
  <div id='status'>Fetching data...</div>
  <script>
    async function update() {
      const r = await fetch('/data');
      const d = await r.json();
      document.getElementById('temp').textContent = d.temperature.toFixed(1);
      document.getElementById('hum').textContent = d.humidity.toFixed(1);
      document.getElementById('status').textContent = '🔄 Last updated: ' + new Date().toLocaleTimeString();
    }
    update();
    setInterval(update, 2000);
  </script>
</body>
</html>)rawhtml";
}

6. Optional: Send Data to Firebase

To log data to the cloud for historical viewing, add the Firebase ESP32 Client library:

cpp
#include <Firebase_ESP_Client.h>
#include <addons/TokenHelper.h>

#define FIREBASE_HOST "https://your-project-default-rtdb.firebaseio.com/"
#define FIREBASE_AUTH "YOUR_DATABASE_SECRET"

FirebaseData fbdo;
FirebaseAuth auth;
FirebaseConfig config;

void setupFirebase() {
  config.host = FIREBASE_HOST;
  config.signer.tokens.legacy_token = FIREBASE_AUTH;
  Firebase.begin(&config, &auth);
  Firebase.reconnectWiFi(true);
}

void sendToFirebase() {
  if (Firebase.RTDB.setFloat(&fbdo, "/sensor/temperature", temperature) &&
      Firebase.RTDB.setFloat(&fbdo, "/sensor/humidity", humidity)) {
    Serial.println("Firebase: Data sent!");
  } else {
    Serial.println("Firebase error: " + fbdo.errorReason());
  }
}
✅
With Firebase logging, you can visualize historical data trends on a web dashboard connected to the same Firebase project you learned to build in the React + Firebase tutorial!
🤖

Project Complete!

Explore more IoT and electronics projects: