← All projects
Sensors Beginner

Mini Weather Station: DHT22 Temperature & Humidity in the Browser

A DHT22 sensor plus ESP32 gives you a live page that refreshes every 5 seconds without reloading — and a JSON API for other devices.

Difficulty
Beginner
Time
1–2 h
Category
Sensors

The idea

Every few seconds the ESP32 polls a DHT22 sensor and exposes two endpoints:

  • / — a clean dashboard page with big numbers;
  • /data — JSON like {"t":22.4,"h":48.0} that Home Assistant, Node-RED or any script can consume.

The page pulls new data with fetch(), so you never need to reload it.

Parts

  • ESP32 DevKit
  • DHT22 (AM2302) sensor — more accurate than DHT11 (±0.5 °C, 0–100 % RH)
  • 10 kΩ resistor (not needed if your DHT22 is on a 3-pin breakout board)
  • Breadboard, jumper wires

Wiring

DHT22 ESP32
VCC (1) 3V3
DATA (2) GPIO 4 + 10 kΩ pull-up to 3V3
NC (3) —
GND (4) GND

Libraries

Install from the Library Manager:

  • DHT sensor library (Adafruit)
  • Adafruit Unified Sensor (pulled in as a dependency)

Code

#include <WiFi.h>
#include <WebServer.h>
#include <DHT.h>

#define DHT_PIN  4
#define DHT_TYPE DHT22

const char* WIFI_SSID = "YOUR_SSID";
const char* WIFI_PASS = "YOUR_PASSWORD";

DHT dht(DHT_PIN, DHT_TYPE);
WebServer server(80);

float temperature = NAN;
float humidity = NAN;
unsigned long lastRead = 0;

const char PAGE[] = R"rawliteral(
<!doctype html><html><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>ESP32 Weather</title>
<style>
  body{font-family:system-ui;background:#0f1720;color:#e8eef3;display:grid;place-items:center;min-height:100vh;margin:0}
  .box{display:flex;gap:48px;text-align:center}
  .v{font-size:3.5rem;font-weight:700}.l{opacity:.7}
</style></head><body>
<div class="box">
  <div><div class="v" id="t">--</div><div class="l">°C</div></div>
  <div><div class="v" id="h">--</div><div class="l">% RH</div></div>
</div>
<script>
async function update() {
  const d = await (await fetch('/data')).json();
  document.getElementById('t').textContent = d.t === null ? '--' : d.t.toFixed(1);
  document.getElementById('h').textContent = d.h === null ? '--' : d.h.toFixed(0);
}
update();
setInterval(update, 5000);
</script></body></html>
)rawliteral";

String jsonNumber(float v) {
  return isnan(v) ? String("null") : String(v, 1);
}

void readSensor() {
  float t = dht.readTemperature();
  float h = dht.readHumidity();
  if (!isnan(t) && !isnan(h)) {
    temperature = t;
    humidity = h;
  } else {
    Serial.println("DHT read failed");
  }
}

void setup() {
  Serial.begin(115200);
  dht.begin();

  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) delay(500);
  Serial.println(WiFi.localIP());

  server.on("/", [] { server.send(200, "text/html", PAGE); });
  server.on("/data", [] {
    String json = "{\"t\":" + jsonNumber(temperature) + ",\"h\":" + jsonNumber(humidity) + "}";
    server.send(200, "application/json", json);
  });
  server.begin();
}

void loop() {
  server.handleClient();
  // The DHT22 must not be polled more often than every 2 seconds
  if (millis() - lastRead > 2500) {
    lastRead = millis();
    readSensor();
  }
}

Gotchas

  • Don't block loop(): sensor reads run on a non-blocking millis() timer, so the server always answers instantly.
  • Seeing DHT read failed? Check the 10 kΩ pull-up and that the sensor is powered from 3.3 V.
  • Keep the sensor away from the board: the ESP32 warms up by 3–5 °C and skews readings.

Where to go next

  • Swap the DHT22 for a BME280 (I²C) to get barometric pressure too.
  • Keep history in a ring buffer and plot it on the page (Chart.js).
  • Add the OLED display from the "NTP Clock" project so the station works without a phone.