I wanted to know the temperature in my garden without walking outside. This is, on the face of it, a solved problem. You can buy a weather station for under twenty quid. But I had an ESP32, a BME280 sensor, a long bank holiday weekend, and the kind of stubbornness that turns a twenty-quid purchase into a three-day project. Here's where it got to, including the parts that still don't quite work.
The easy version
The BME280 is a lovely little sensor. Temperature, humidity, and pressure over I2C, three wires plus power, and a well-supported Arduino library. Getting a first reading took about ten minutes of wiring and twenty lines of code.
#include <Wire.h>
#include <Adafruit_BME280.h>
Adafruit_BME280 bme;
void setup() {
Serial.begin(115200);
bme.begin(0x76);
}
void loop() {
Serial.printf("%.1f C %.0f%% %.0f hPa\n",
bme.readTemperature(),
bme.readHumidity(),
bme.readPressure() / 100.0);
delay(60000);
}
That worked first time, which should have been a warning. Anything that works first time on an embedded project is hiding a problem for later, and this one was hiding two.
Problem one: the ESP32 heats its own sensor
The first readings indoors looked plausible. Then I sat the thing on the windowsill next to a proper thermometer and it read consistently two to three degrees high. The cause is embarrassingly obvious in hindsight: the ESP32 is a little furnace. With WiFi up and the chip awake it draws enough current to warm the board noticeably, and the BME280 was mounted right next to it, dutifully measuring the temperature of the microcontroller rather than the air.
The fix is partly physical and partly behavioural. Physically, you move the sensor away from the chip, on flying leads, a few centimetres of distance, so it isn't sitting in the warm air rising off the board. Behaviourally, you stop keeping the ESP32 awake. Which leads neatly to the second problem.
Problem two: it needed to sleep
I wanted this thing to run on a battery eventually, and an ESP32 with WiFi up draws something like 80–120mA continuously. That flattens any reasonable battery in a day or two, and as established, it also cooks the sensor. The answer is deep sleep. The ESP32 wakes, connects to WiFi, takes a reading, pushes it somewhere, and goes back to sleep for a few minutes, drawing microamps in between.
#define SLEEP_SECONDS 300
void deepSleep() {
esp_sleep_enable_timer_wakeup(SLEEP_SECONDS * 1000000ULL);
esp_deep_sleep_start();
}
Deep sleep solves the self-heating beautifully, because the chip is cold for the 295 seconds out of every 300 that it's asleep. By the time it wakes and reads, the board has had five minutes to cool to ambient. The reading is taken in the first second after waking, before the chip has time to warm anything up.
The catch is that deep sleep isn't really sleep, it's closer to a reboot. The chip restarts from the top of setup() every cycle, so there's no persistent state unless you stash it in RTC memory or send it off-device each time. Reconnecting WiFi from cold takes a couple of seconds and a chunk of the energy budget, which on battery matters more than the reading itself.
Where it pushes the data
Each wake cycle it posts a reading over HTTP to a little endpoint I run, which drops it into a database and onto a Grafana dashboard. No fancy protocol. The ESP32 has just enough time awake to do a single HTTP POST before sleeping, and a single POST is all this needs.
http.begin("http://homelab.local:8080/reading");
http.addHeader("Content-Type", "application/json");
http.POST(payload);
The "mostly" in the title
So why "mostly works"? A few reasons, all of them honest.
The temperature is now good, within half a degree of my reference thermometer, which is fine for "is it cold in the garden". Humidity is plausible but I've nothing to calibrate it against, so I trust it loosely. Pressure is the most reliable of the three, because absolute pressure is hard to get wrong and the trend line tracks the actual weather nicely.
The bits that don't work are around the edges. Occasionally the WiFi connect times out on wake and that cycle is simply lost, a gap in the graph. I could retry, but retries cost battery, and a missing reading every so often is no great loss for garden weather. The enclosure is also not properly weatherproofed yet, so it's living on the windowsill rather than actually outside, which rather undermines the entire premise. A vented box that keeps rain off the electronics whilst letting air at the sensor is its own little project, and a harder one than the electronics turned out to be.
Still. It wakes every five minutes, reads cold, posts the numbers, and goes back to sleep, and the dashboard fills up with a temperature I can read from the sofa. That's most of what I wanted. The last ten percent, the weatherproofing and the dropped readings, is the ten percent that always takes as long as the first ninety, and I'm content to leave it on the windowsill being mostly right for now.