Ramblings of an aging IT geek
← Ramblings of an aging IT geek
hardware

an esp32 weather station, and the lies sensors tell

Building an ESP32 weather station that reports temperature, humidity and pressure to the homelab, and the long tail of small problems that "mostly works" hides.

A breadboard with sensors and jumper wires

I wanted to know what the temperature actually was outside the back door, not what an app told me it was for a town six miles away. That's the entire justification for this project, and I think it's a perfectly good one. The result is an ESP32 sat in a vented box on the north wall, reading temperature, humidity and pressure every minute and posting them to the homelab. It mostly works. The "mostly" is where all the interesting parts live.

the easy 80 percent

The build is almost embarrassingly simple now. An ESP32 dev board, a BME280 for temperature, humidity and pressure on the I2C bus, and a few jumper wires. The BME280 is a lovely little part: one sensor, three readings, talks I2C, costs almost nothing. Wire up power, ground, SDA and SCL, and you're most of the way there.

The firmware is equally undramatic. Connect to wifi, read the sensor, push the values out, sleep, repeat. I post readings over MQTT to the broker that already runs in the house, because everything else already speaks MQTT and there was no reason to invent a new protocol for the weather.

#include <Adafruit_BME280.h>
Adafruit_BME280 bme;

void setup() {
  Serial.begin(115200);
  if (!bme.begin(0x76)) {
    Serial.println("BME280 not found, check wiring");
    while (1) delay(10);
  }
}

void loop() {
  float t = bme.readTemperature();
  float h = bme.readHumidity();
  float p = bme.readPressure() / 100.0F;  // hPa
  publish(t, h, p);
  delay(60000);
}

Flash that, watch the serial monitor, see three plausible numbers, feel briefly like a genius. The graph in Grafana lit up within the hour and showed a believable temperature curve. Project done, surely.

A close-up of the sensor board and wiring

the long, irritating 20 percent

It was not done. Here is the list of small lies the thing told me, in roughly the order I caught them.

The temperature was wrong, and consistently so. The BME280 reads a degree or two high because it's sitting next to an ESP32 that runs warm, and heat from the board leaks straight into the sensor. The honest fixes are physical: move the sensor away from the board on a short cable, or put the ESP32 into deep sleep between readings so it isn't gently cooking its own thermometer. I went with deep sleep, which had the happy side effect of the next problem becoming relevant.

Powering it was the real project. Running an ESP32 flat out on wifi is fine on a USB cable and hopeless on a battery. Awake and transmitting it draws a noticeable current; in deep sleep it draws almost nothing. So the firmware grew a proper cycle: wake, connect, read, publish, sleep for a minute. The catch is that wifi association is the expensive part, and doing it every single wake undoes some of the saving, so there's a genuine tradeoff between how fresh you want the data and how long the battery lasts. I landed on a minute because I value the graph more than I value never changing the battery.

Wifi drops, and the naive code just stops. The first version assumed the network was always there. It is not. The router reboots, the signal dips, the access point has a moment. A weather station that silently dies the first time wifi hiccups is not a weather station, it's an ornament. So now it retries with a backoff, and crucially it gives up after a while and goes back to sleep rather than sitting there awake and draining, hammering a network that isn't answering.

int tries = 0;
WiFi.begin(ssid, pass);
while (WiFi.status() != WL_CONNECTED && tries < 20) {
  delay(500);
  tries++;
}
if (WiFi.status() != WL_CONNECTED) {
  esp_deep_sleep_start();  // give up, try again next cycle
}

Humidity near 100% is a lie too, but an honest one. On a wet October morning the sensor cheerfully reported 99% and then hovered there. That's not a fault, that's the sensor saturating, and there isn't much to be done about it beyond knowing it happens and not trusting the top of the range as gospel. The pressure reading, for what it's worth, has been rock solid throughout and is quietly the most useful number of the three for guessing what the next few hours hold.

was it worth it

The graph has been running for a fortnight and I check it more than I'd like to admit. There is a particular satisfaction in glancing at a dashboard and knowing the number came from a box I built, on my wall, reading my air, rather than from someone's API.

"Mostly works" is, I've decided, the correct and honest description of every hardware project of this size. The sensor is cheap and the code is short, and those two facts hide a tail of small physical truths: heat leaks, batteries drain, networks drop, sensors saturate. None of them are hard once you know them. All of them are invisible until the thing is running in the real world and lying to you with total confidence. That tail is the actual project. The soldering was the warm-up.