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