Battery-operated ESP32 node that samples temperature, pressure and humidity (BME280), buffers readings across deep-sleep cycles in RTC RAM, and uploads them in batches over Wi-Fi as JSON. Written from scratch — no external libraries: the BME280 driver is implemented directly from the Bosch datasheet, the ring buffer and uplink are plain C/C++ on top of the ESP32 Arduino core.
┌───────────── wake (RTC timer, every 5 min) ─────────────┐
│ │
deep sleep ──► read BME280 ──► push to RTC-RAM ring buffer ──► batch full?
~10 µA (forced mode, (CRC-guarded, survives │no │yes
~10 ms) sleep & brownout) ▼ ▼
deep sleep Wi-Fi on,
POST JSON,
Wi-Fi off
| Decision | Reason |
|---|---|
| Forced-mode BME280, 1× oversampling | Sensor sleeps at ~0.1 µA between wakes; never free-runs. One conversion costs ~10 ms. |
| Batch uploads (12 samples ≈ 1/hour) | Radio dominates the energy budget: ~100 mA while Wi-Fi is up vs. ~40 mA sampling. Batching cuts average current by roughly 10×. |
| Ring buffer in RTC slow memory | Survives deep sleep without flash wear. CRC-16 over the payload detects brownout corruption — on mismatch the node resets the buffer instead of uploading garbage. |
| Overwrite-oldest on overflow | If the server is down for hours, the node keeps acquiring and retains the freshest 16 samples instead of stalling. |
| Exponential upload backoff (1→2→4 batches, capped) | A dead endpoint must not cost a 15 s Wi-Fi timeout every hour. Backoff caps the damage while the ring buffer preserves data. |
| No external libraries | Every register access is reviewable against the datasheet. Nothing hides in a vendor HAL. |
Assumptions: bare ESP32 module (no dev-board USB bridge/LEDs), low-quiescent LDO (~5 µA), 5-minute sampling, hourly upload.
| State | Current | Duration | Charge per hour |
|---|---|---|---|
| Deep sleep | ~15 µA | ~3595 s | ~15 µAh |
| Sample wake (12×) | ~45 mA | ~0.5 s each | ~75 µAh |
| Upload wake (1×) | ~120 mA avg | ~5 s | ~167 µAh |
| Total | ~257 µAh/h ≈ 0.26 mA avg |
A 2500 mAh 18650 cell ≈ 13 months on paper. Real numbers depend on your board: a typical dev board idles at 10–20 mA because of the USB bridge and power LED — for real battery life use a bare module or cut those loads, and measure with a meter (e.g. a µCurrent or a Nordic PPK2) rather than trusting this table.
pip install platformio
pio run # compile for ESP32
pio test -e native # run unit tests on your PC, no hardware needed
pio run -t upload && pio device monitor # flash + watch logsEdit src/config.h first: Wi-Fi credentials, endpoint, and pins. Don't
commit real credentials.
| BME280 | ESP32 |
|---|---|
| VIN | 3V3 |
| GND | GND |
| SDA | GPIO 21 |
| SCL | GPIO 22 |
{
"device": "node-01",
"boot": 42,
"samples": [
{"t": 12300, "temp_c": 25.31, "press_pa": 101325, "rh": 46.333}
]
}t is seconds since first boot (monotonic across sleeps). Any HTTP endpoint
that accepts a JSON POST works as a collector.
The ring buffer is pure C and unit-tested on the host (pio test -e native):
CRC corruption detection, order preservation, overflow behavior, cold-boot
recovery. CI builds the firmware and runs the tests on every push.
MIT