Accuracy numbers below are from my unit. Yours will vary.
Both the DHT22 and BME280 drift on humidity readings when mounted in outdoor enclosures. Condensation forms on the sensor element, and once that happens your humidity readings are garbage until it dries out. I've replaced the sensor twice. Conformal coating helps a little. Proper ventilation helps more. But outdoor humidity from a $4 sensor is always going to be approximate โ don't build anything critical around it.
The basic idea: an ESP32 reads temperature, humidity, and barometric pressure every 5 minutes, then sends the data to a cloud dashboard where you can view history, set alerts, and see trends. You can also hook it into Home Assistant to trigger fans or dehumidifiers automatically.
Hardware Shopping List
- ESP32 Dev Board: ~$5 (I use the ESP32-WROOM-32)
- BME280 Sensor: ~$4 (temperature, humidity, pressure in one chip)
- Jumper Wires: ~$2
- Micro USB Cable: You probably have one
- Optional: 3D printed enclosure, battery + solar panel for outdoor use
Total: About $12. Order from AliExpress for cheapest, or Amazon for faster delivery.
The DHT22 sensor is popular but honestly not great for precision (BME280 reads temperature ยฑ1ยฐC, humidity ยฑ3% in ideal conditions โ real-world is worse). I switched after my DHT22 readings drifted 3ยฐF from a calibrated thermometer within a month. The BME280 also gives you pressure in the same package. Worth the extra $2.
Wiring
BME280 uses I2C, so only 4 wires:
BME280 ESP32
โโโโโโโ โโโโโ
VIN โโโโโโโโโโ 3.3V
GND โโโโโโโโโโ GND
SCL โโโโโโโโโโ GPIO 22
SDA โโโโโโโโโโ GPIO 21
That's it. No resistors, no level shifters. The ESP32's default I2C pins have internal pull-ups so it just works.
Software Setup
Arduino IDE
- Install Arduino IDE from arduino.cc
- Add ESP32 board support:
- File โ Preferences
- Additional Boards Manager URLs:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
- Tools โ Board โ Boards Manager โ Search "ESP32" โ Install
- Select your board: ESP32 Dev Module
Libraries
Sketch โ Include Library โ Manage Libraries:
- Adafruit BME280 Library
- Adafruit Unified Sensor
Basic Sensor Code
Let's start by just reading the sensor:
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
Adafruit_BME280 bme;
void setup() {
Serial.begin(115200);
// Wait for sensor to initialize
if (!bme.begin(0x76)) { // Try 0x77 if 0x76 doesn't work
Serial.println("Couldn't find BME280 sensor!");
while (1);
}
Serial.println("BME280 found!");
}
void loop() {
float temperature = bme.readTemperature();
float humidity = bme.readHumidity();
float pressure = bme.readPressure() / 100.0F; // Convert to hPa
Serial.printf("Temp: %.1fยฐC | Humidity: %.1f%% | Pressure: %.1f hPa\n",
temperature, humidity, pressure);
delay(5000);
}
Upload this. Open Serial Monitor (115200 baud). You should see readings updating every 5 seconds.
Troubleshooting: If "Couldn't find BME280 sensor!" appears, try address 0x77 instead of 0x76. Different manufacturers use different addresses. Also check your wiring.
Adding WiFi and Cloud
Now we need to get this data off the serial monitor and somewhere useful. Two options here: ThingsBoard (free cloud tier, easiest) or MQTT to Home Assistant (if you already run one).
Option 1: ThingsBoard Cloud (Easiest)
- Create free account at thingsboard.cloud
- Add new device โ Name it "Weather Station"
- Copy the Access Token
#include <WiFi.h>
#include <HTTPClient.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
// WiFi credentials
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
// ThingsBoard
const char* thingsboardServer = "thingsboard.cloud";
const char* accessToken = "YOUR_DEVICE_ACCESS_TOKEN";
Adafruit_BME280 bme;
void setup() {
Serial.begin(115200);
// Initialize sensor
if (!bme.begin(0x76)) {
Serial.println("BME280 not found!");
while (1);
}
// Connect to WiFi
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(" Connected!");
Serial.println(WiFi.localIP());
}
void sendData(float temp, float humidity, float pressure) {
HTTPClient http;
String url = "http://" + String(thingsboardServer) +
"/api/v1/" + String(accessToken) + "/telemetry";
http.begin(url);
http.addHeader("Content-Type", "application/json");
// Create JSON payload
String payload = "{\"temperature\":" + String(temp) +
",\"humidity\":" + String(humidity) +
",\"pressure\":" + String(pressure) + "}";
int httpCode = http.POST(payload);
if (httpCode == 200) {
Serial.println("Data sent successfully");
} else {
Serial.printf("Error sending data: %d\n", httpCode);
}
http.end();
}
void loop() {
float temperature = bme.readTemperature();
float humidity = bme.readHumidity();
float pressure = bme.readPressure() / 100.0F;
Serial.printf("Temp: %.1fยฐC | Humidity: %.1f%% | Pressure: %.1f hPa\n",
temperature, humidity, pressure);
// Send to cloud
if (WiFi.status() == WL_CONNECTED) {
sendData(temperature, humidity, pressure);
} else {
Serial.println("WiFi disconnected, reconnecting...");
WiFi.reconnect();
}
// Deep sleep for 5 minutes to save power (optional)
// esp_sleep_enable_timer_wakeup(5 * 60 * 1000000);
// esp_deep_sleep_start();
delay(300000); // 5 minutes
}
โ ๏ธ If this fails: Check that you've selected the right board and port in the Arduino IDE. Wrong board selection is the #1 issue I see.
Flash this code. In ThingsBoard, go to your device โ Latest telemetry. You should see data appearing.
Create a dashboard: Dashboards โ + โ Add widget โ Charts โ Time series. Select your device and the "temperature" key. Boom โ live graphs.
Option 2: Home Assistant (Local)
If you run Home Assistant, you can send data via MQTT:
#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
const char* ssid = "YOUR_WIFI";
const char* password = "YOUR_PASSWORD";
const char* mqtt_server = "192.168.1.100"; // Home Assistant IP
WiFiClient espClient;
PubSubClient client(espClient);
Adafruit_BME280 bme;
void reconnect() {
while (!client.connected()) {
if (client.connect("ESP32Weather")) {
Serial.println("MQTT connected");
} else {
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
bme.begin(0x76);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) delay(500);
client.setServer(mqtt_server, 1883);
}
void loop() {
if (!client.connected()) reconnect();
client.loop();
float temp = bme.readTemperature();
float humidity = bme.readHumidity();
float pressure = bme.readPressure() / 100.0F;
// Publish to MQTT topics
client.publish("home/weather/temperature", String(temp).c_str());
client.publish("home/weather/humidity", String(humidity).c_str());
client.publish("home/weather/pressure", String(pressure).c_str());
delay(60000); // 1 minute
}
In Home Assistant configuration.YAML:
mqtt:
sensor:
- name: "Office Temperature"
state_topic: "home/weather/temperature"
unit_of_measurement: "ยฐC"
device_class: temperature
- name: "Office Humidity"
state_topic: "home/weather/humidity"
unit_of_measurement: "%"
device_class: humidity
Making It Reliable
My first version crashed after 3 days. The WiFi dropped and the ESP32 just sat there doing nothing until I power cycled it. Here's what fixed that:
WiFi Watchdog
unsigned long lastSuccessfulSend = 0;
void loop() {
// If no successful send in 10 minutes, restart
if (millis() - lastSuccessfulSend > 600000) {
Serial.println("Watchdog triggered, restarting...");
ESP.restart();
}
// ... rest of loop
if (sendData(temp, humidity, pressure)) {
lastSuccessfulSend = millis();
}
}
Deep Sleep for Battery Power
If running on battery, use deep sleep:
void loop() {
// Read and send data...
// Sleep for 5 minutes
esp_sleep_enable_timer_wakeup(5 * 60 * 1000000); // microseconds
Serial.println("Going to sleep...");
esp_deep_sleep_start();
// Code after this never runs โ ESP32 restarts from setup()
}
Deep sleep draws about 10ยตA. With a 2000mAh battery, that's months of runtime (my ESP32 gets about 3 weeks on a single 18650 cell with 5-minute intervals โ your mileage depends on how long WiFi association takes). One thing that tripped me up: deep sleep on the ESP32 is finicky about which GPIO pins you can use for wakeup. Only RTC-capable GPIOs work โ that's 0, 2, 4, 12-15, 25-27, 32-39. I wasted an evening trying to get GPIO 16 to work as an external wakeup source before reading the datasheet properly.
Outdoor Deployment
For outdoor use, you need:
- Waterproof enclosure: IP65 rated junction box or 3D printed case (seal it properly or humidity will kill the sensor within months โ ask me how I know)
- Ventilation: BME280 needs airflow for accurate readings, but no rain ingress. Stevenson screen design works well.
- Power: USB cable through wall, or solar panel (1W is enough)
One mistake I made: putting the sensor in direct sunlight. It read 15ยฐC higher than actual air temperature (direct sun on a black enclosure is basically a greenhouse). Always mount in shade or use a radiation shield โ even a white plastic cup with holes cut in it is better than nothing.
Expanding the Station
Once the basics are working, you can pile on more sensors:
- BH1750: Light intensity (lux)
- Rain gauge: Tipping bucket attached to GPIO interrupt
- Anemometer: Wind speed via pulse counting
- UV sensor: ML8511 or VEML6075
- Air quality: MQ-135 or Sensirion SCD30
Same wiring approach โ I2C bus for the digital ones, analog pins for the rest. The ESP32 has plenty of both.
Data Visualization
After a few months of data you start noticing patterns. Pressure drops pretty reliably about 12 hours before storms roll in. My office humidity craters below 30% every winter. Temperature varies 8ยฐC between the ground floor and upstairs. None of this is groundbreaking but you wouldn't notice it without a graph in front of you.
Practical outcomes: I bought a humidifier for the office, moved my server to the cooler basement, and I sometimes glance at the pressure chart before deciding whether to bring an umbrella. ThingsBoard's free tier handles all of this fine โ you can set up alerts too, though I never bothered.
Total Cost Breakdown
| Component | Cost |
|---|---|
| ESP32 Dev Board | $5 |
| BME280 Sensor | $4 |
| Jumper Wires | $2 |
| USB Cable | $0 (already had) |
| Total | ~$11 |
A Davis Vantage Vue runs $300+ and needs a subscription for cloud access. This does most of the same job for the price of lunch.
Current Status
The station has been running for about 11 months now. Temperature readings are solid โ still within a degree or two of Weather Underground. Humidity is a guess. Pressure tracks well enough that I can see storms coming about 12 hours early, which is genuinely useful.
I check Weather Underground more than my own dashboard, which says something. The data is interesting in aggregate โ I learned my office drops below 30% humidity every winter, and that there's an 8ยฐC difference between floors in my house โ but for a quick "do I need a jacket" check, I still reach for my phone. Maybe I'll build a better dashboard eventually. For now the station just quietly logs data and occasionally reminds me to replace the humidity sensor.
๐ฌ Comments