IoT ๐Ÿ“– 18 min read
๐Ÿ“… Published: ๐Ÿ”„ Updated:

Build a Cloud-Connected Weather Station

How accurate is a $12 sensor compared to your local weather service? I tested mine against Weather Underground for 3 months. Temperature was within 1.2ยฐF. Humidity was off by 15%. So โ€” it depends on what you're measuring. This tutorial covers wiring a BME280 to an ESP32, pushing data to a cloud dashboard, and keeping the whole thing alive outdoors. Total cost is about $11 in parts and an afternoon of fumbling with breadboard wires.

๐Ÿ› ๏ธ Before You Start

๐Ÿ’ป
Hardware ESP32/ESP8266 dev board, sensors as specified, USB cable
๐Ÿ“ฆ
Software Arduino IDE 2.x or PlatformIO, USB drivers
โฑ๏ธ
Estimated Time 45-90 minutes

Accuracy numbers below are from my unit. Yours will vary.

๐ŸŒง๏ธ The humidity problem:

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:

Wiring Diagram
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

Terminal: Package installation
Terminal: Package installation

Arduino IDE

  1. Install Arduino IDE from arduino.cc
  2. Add ESP32 board support:
    • File โ†’ Preferences
    • Additional Boards Manager URLs: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
  3. Tools โ†’ Board โ†’ Boards Manager โ†’ Search "ESP32" โ†’ Install
  4. 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:

Arduino
#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)

  1. Create free account at thingsboard.cloud
  2. Add new device โ†’ Name it "Weather Station"
  3. Copy the Access Token
Arduino (Full Code)
#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:

Arduino
#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:

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

Arduino
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:

Arduino
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