A Wi-Fi environmental sensor node built on a SparkFun Thing Plus ESP32-C6 and a BME280
breakout, reporting temperature, dew point, humidity, pressure, and battery state of charge to my
sensors ingest API. It reuses the batcave
station's old prefix and channel numbering, so its live data shows up at the same
Sensor Nodes
report that a much older weather board used to log to before going dark in 2016.
The interesting part of this project isn't really the sensor -- BME280 breakouts are a
commodity part -- it's the firmware: a small, reusable Arduino library
(github.com/larsi-org/sensor-node) that
handles Wi-Fi provisioning, reconnecting after a move, and reporting over HTTPS, so that wiring
up the next sensor node is mostly just picking a sensor and writing a
loop().
All firmware source is on GitHub: github.com/larsi-org/sensor-node,
MIT licensed. It's structured as a standard Arduino library (clone or symlink it into
~/Arduino/libraries/) with two example sketches -- a minimal placeholder-data
BasicNode, and the actual BME280Node this device runs.
Two boards, connected by a single Qwiic cable -- no soldering, no wiring diagram needed:
0x77) -- Bosch's
combined temperature/humidity/pressure sensor. Dew point is computed on the device from
temperature and humidity (the SparkFun BME280 library's own dewPointC()) rather than
server-side, so it logs as its own channel.Channel layout, matching the wire protocol's device/channel addressing (see sensor-node.php):
| Channel | Reading | Unit |
|---|---|---|
| 0 | Temperature | °C |
| 1 | Dew Point | °C |
| 2 | Relative Humidity | % |
| 3 | Pressure | hPa |
| 15 | Battery State of Charge | % |
Channel 15 -- the last of a device's 16 -- is reserved sitewide across every sensor-node device for battery state of charge, leaving 0-14 free for whatever the sketch actually measures.
The firmware is built around a small SensorNode Arduino library rather than a
single monolithic sketch, so the Wi-Fi/provisioning/logging plumbing is reusable for whatever
sensor gets wired up next. A sketch just calls node.begin() once in
setup() and node.log({...}) on whatever schedule it wants in
loop():
#include <SensorNode.h>
SensorNode node;
void setup() {
Serial.begin(115200);
node.begin(); // connects, or runs the setup portal if it can't
}
void loop() {
float temperatureC = readTemperature();
node.log({temperatureC});
delay(node.config().logIntervalMinutes * 60UL * 1000); // set via the portal
}
The device has no hardcoded Wi-Fi credentials. If it can't connect to any network it already
knows, it opens its own open access point (SensorNode-Setup-XXXX) with a captive
setup page: pick a network from a live scan, enter the password, and set a node name, an optional
location (e.g. "basement" -- lets two nodes at the same write key share a name but still report
distinct network hostnames), device ID, write key, and how often it should report (1–60
minutes). It remembers up to 3 networks, most-recently-added first -- so a node that moves
between a couple of locations (a workbench and its final install spot, say) reconnects
automatically on the way back instead of needing reprovisioning every time. When the portal does
come up because none of them are in range anymore, it's pre-filled with everything except the
network password, since the common case is just "this moved somewhere new," not "start over" --
leave the password blank to keep the one already saved for a known network.
Once connected, the device syncs its clock over NTP (needed so it can validate the server's TLS
certificate correctly) and posts each reading to log as
device|v0,v1,v2,v3, HTTPS only, with the connection verified against a pinned root CA
rather than trusting the system's default certificate store. A reading left blank is skipped
entirely rather than logged as zero, so a sensor that fails to read on a given cycle doesn't
pollute the data with a false reading. See sensor-node.php
for the full wire protocol if you want to log your own device to this same API.
A write key is a 16-character, per-station credential -- not a shared API key -- gating what prefix a device is allowed to log to. See sensor-node.php for how to request one and the exact key format if you're building your own client.