esp32 advanced 60 min

ESP32: build a weather station that logs to Wi-Fi

A complete weather station: BME280 over I2C, ESP32 web server, deep sleep between readings. Battery-powered, weatherproof case, web dashboard.

Code available for: ESP32 Arduino
Published Aug 25, 2026

This is the project tutorial that ties together everything from the foundations through the power cluster. A working weather station: ESP32 reads a BME280, serves the readings on a Wi-Fi web page, sleeps between readings to save power, and runs from a 18650 battery.

It is built from the parts in the tutorials in this book. If you have read the BME280, Wi-Fi, deep sleep, and 18650 tutorials, you have seen all the pieces. This is where they come together.

What you need

  • ESP32 dev board
  • BME280 breakout (the I2C variant)
  • 18650 + TP4056 + LDO regulator (from the 18650 tutorial)
  • 4 jumper wires
  • A USB cable for programming
  • A weatherproof enclosure (a clear plastic food container works for prototyping)

Wiring

ESP32 3.3V -- BME280 VCC
ESP32 GND  -- BME280 GND
ESP32 GPIO 21 -- BME280 SDA
ESP32 GPIO 22 -- BME280 SCL
ESP32 3.3V -- LDO OUT (from TP4056)
ESP32 GND  -- LDO GND (from TP4056)

The BME280 is on the default I2C pins. The LDO provides 3.3V from the 18650 battery. Add a 100uF capacitor across the LDO output for noise filtering.

The code

#include <WiFi.h>
#include <WebServer.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
#include <esp_sleep.h>

const char* ssid = "your-wifi";
const char* password = "your-password";

#define I2C_SDA 21
#define I2C_SCL 22

WebServer server(80);
Adafruit_BME280 bme;

float lastTemp = 0;
float lastHum = 0;
float lastPress = 0;
unsigned long lastRead = 0;

void setup() {
  Serial.begin(115200);
  delay(1000);

  Wire.begin(I2C_SDA, I2C_SCL);
  if (!bme.begin(0x76)) {
    Serial.println("Could not find BME280");
    ESP.restart();
  }

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.print("Connected. IP: ");
  Serial.println(WiFi.localIP());

  server.on("/", handleRoot);
  server.begin();

  // Take a reading now
  takeReading();

  // Sleep for 60 seconds
  esp_sleep_enable_timer_wakeup(60 * 1000000ULL);
  Serial.println("Going to sleep for 60 seconds");
  esp_deep_sleep_start();
}

void loop() {
  // Not reached; the ESP32 wakes from sleep and starts at setup()
  server.handleClient();
}

void takeReading() {
  lastTemp = bme.readTemperature();
  lastHum = bme.readHumidity();
  lastPress = bme.readPressure() / 100.0;
  lastRead = millis();
  Serial.printf("Temp: %.2f, Hum: %.2f, Press: %.2f\n",
                lastTemp, lastHum, lastPress);
}

void handleRoot() {
  String html = "<!DOCTYPE html><html><head>";
  html += "<meta charset='utf-8'>";
  html += "<meta http-equiv='refresh' content='5'>";
  html += "<title>Weather Station</title></head>";
  html += "<body style='font-family:sans-serif;max-width:480px;margin:2rem auto;'>";
  html += "<h1>Weather Station</h1>";
  html += "<p><strong>Temperature:</strong> " + String(lastTemp, 1) + " &deg;C</p>";
  html += "<p><strong>Humidity:</strong> " + String(lastHum, 1) + " %</p>";
  html += "<p><strong>Pressure:</strong> " + String(lastPress, 1) + " hPa</p>";
  html += "<p><small>Last update: " + String(lastRead / 1000) + " seconds after boot</small></p>";
  html += "</body></html>";
  server.send(200, "text/html", html);
}

Upload. Open Serial Monitor. After Wi-Fi connects, note the IP address. Open it in a browser. You should see the current temperature, humidity, and pressure.

The ESP32 then sleeps for 60 seconds. After 60 seconds, it wakes, takes a new reading, serves the web page again, and sleeps again. The web page refreshes every 5 seconds, but the readings only update every 60 seconds (when the ESP32 wakes).

The 60-second deep sleep

The 60-second sleep interval is a tradeoff:

  • Longer sleep: less Wi-Fi time, lower battery drain, less current data freshness.
  • Shorter sleep: more Wi-Fi time, higher battery drain, fresher data.

For a weather station, 60 seconds is the right pick. Atmospheric pressure changes on the order of minutes, not seconds.

For projects that need faster updates (e.g. a burglar alarm), shorten the sleep to 1-5 seconds and accept the battery drain.

The battery math

Average current draw:

  • Wake period (5 seconds): Wi-Fi active, ~100 mA. = 0.14 mAh per wake.
  • Sleep period (55 seconds): Wi-Fi off, ~10 mA. = 0.15 mAh per sleep.

Total: 0.29 mAh per minute = 17 mAh per hour = 410 mAh per day.

A 2500 mAh 18650 lasts about 6 days. For longer runtime, use a larger battery (e.g. 18650 + parallel) or a solar panel.

Adding a solar panel

For perpetual operation, wire a solar panel through the TP4056:

Solar panel -- TP4056 IN+ -- TP4056 OUT+ -- LDO -- ESP32

A 1W solar panel in 4 sun-hours per day provides about 200 mAh, which is half the daily consumption. Use a 2W panel for 100% replenishment.

The weatherproof enclosure

The ESP32 and battery need to be protected from rain. Options:

  • Clear plastic food container: cheap, easy to modify, works for prototyping. Drill holes for ventilation, seal with silicone.
  • Outdoor electrical junction box: the standard for permanent installations. Available at any hardware store.
  • 3D-printed enclosure: custom fit. Use ABS or PETG (PLA melts in summer sun).

The BME280 needs to be exposed to outside air but protected from direct rain. Mount it under a small overhang or with a Gore-Tex membrane over the sensor.

Logging to a database

For historical data, post to an MQTT broker or HTTP API. Combine with the MQTT tutorial:

#include <PubSubClient.h>

WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);

void setup() {
  // ... after Wi-Fi setup ...
  mqtt.setServer("192.168.1.50", 1883);
  mqtt.connect("esp32-weather");
}

void takeReading() {
  lastTemp = bme.readTemperature();
  lastHum = bme.readHumidity();
  lastPress = bme.readPressure() / 100.0;

  char payload[100];
  snprintf(payload, sizeof(payload),
    "{\"temp\":%.2f,\"hum\":%.2f,\"press\":%.2f}",
    lastTemp, lastHum, lastPress);
  mqtt.publish("weather/sensor", payload);
}

The Node-RED tutorial on the Pi shows how to consume these readings into InfluxDB or another time-series database.

What you learned

  • A working weather station can be built from the tutorials in this book.
  • The ESP32 wakes every 60 seconds, takes a reading, serves it on Wi-Fi, sleeps again.
  • Battery runtime is about 6 days with a single 18650.
  • Adding solar makes the project perpetual.

What to build next

  • The MQTT tutorial publishes these readings to a broker.
  • The Raspberry Pi Node-RED tutorial consumes the readings and graphs them.
  • The book IoT with ESP32 has more weather station patterns (wind speed, rain gauge, UV sensor).