Hear cheap Bluetooth thermometers; push the readings to a place you own.

| Status | Live — flash from the browser, no IDE needed |
| Hears | Govee, Xiaomi, Inkbird, Ruuvi, SwitchBot + ~120 device types (Theengs Decoder) |
| Chips | ESP32 · S3 · C3 · C6 |
| Delivers | Webhook (HTTP POST) · MQTT — oat-ods/0.3 |
| License | GPL-family (via the Theengs Decoder dependency — see below) |
The first entry in the OAT Sketch Library, and the template the rest follow.
A self-configuring ESP32 node that listens for BLE sensor broadcasts (Govee,
Xiaomi, Inkbird, Ruuvi, SwitchBot and ~120 other devices via Theengs Decoder),
harvests every measurand each device emits — temperature, humidity, soil
moisture, conductivity, CO₂, PM2.5, illuminance, pressure, and the rest — folds
the chatty readings into steady values, and pushes them to an endpoint the grower
owns — by webhook or MQTT. What each key means is defined once in the shared
measurand dictionary (oat-measurands.yaml → oat_measurands.h).
A grower flashes it from the browser at
openagriculturetechnology.com/build/sketches/ble-sensor-listener/
(ESP Web Tools / Web Serial), then configures it on the device's own captive
portal. No IDE, no command line, no code editing.
| File | What it is |
|---|---|
oat_ble_listener.ino |
The firmware source (Arduino / ESP32). |
platformio.ini |
Multi-chip build matrix (ESP32, S3, C3, C6). Pinned libs. |
merge_bin.py |
Post-build hook → one merged factory image per chip at out/firmware-<mcu>.bin, flashable at offset 0. |
make_manifest.py |
Assembles manifest.json from whatever bins exist (data-driven). |
build.sh |
Builds every chip, copies bins + manifest into the site assets. |
PlatformIO core only — no Arduino IDE:
pipx install platformio # or: pip install --user platformio
./build.sh
build.sh produces one merged, flash-at-offset-0 image per chip in out/, plus
the manifest.json the OAT site's flash-from-browser page serves.
First-build knob: if pio run can't resolve the platform, bump the
pioarduino release pin in platformio.ini (it tracks Arduino-ESP32 3.x, which
NimBLE 2.x and the native-USB chips need). That pin is the one thing to verify.
Built for every WiFi+BLE ESP32; the site's buy-list recommends the native-USB ones (no driver — the #1 newbie wall):
| Chip | USB | In the build | Recommended to buy |
|---|---|---|---|
| ESP32 (classic) | external bridge (CP2102/CH340) | yes | no (driver friction) |
| ESP32-S3 | native | yes | yes |
| ESP32-C3 | native | yes | yes (cheapest) |
| ESP32-C6 | native | yes | yes (WiFi 6) |
| ESP32-S2 / H2 / P4 | — | no | — (no BLE / no WiFi / no radio) |
One ESP Web Tools button serves them all: it reads the chip and auto-picks the
matching build. Adding a chip = add an env in platformio.ini; make_manifest.py
picks it up.
oat-ods/0.3A signed JSON batch: a shared source (assignable gateway id, tier, fw) plus a
messages array of per-measurement records. Each record carries its logical stream
(the sensor), the canonical measurement + SenML unit, the folded value, an agg
block (window, samples, method), and swappable source provenance (physical MAC,
brand, model, battery, rssi). One record per measurand — a flower-care sensor emits
temperature + soil_moisture + soil_conductivity + illuminance + battery, not just temp.
Units are canonical on the wire (°C, %RH, ppm, lx …); the endpoint derives dewpoint
and VPD from temperature + humidity. Big fan-ins are chunked into bounded,
independently-signed POSTs (chip-aware) so the body + JSON DOM + TLS block always fit
in heap. MQTT sends one small retained message per measurement. A decoder key the
dictionary hasn't mapped is forwarded raw (no unit) and listed on /status so it can
be promoted. The schema id travels in the packet so the endpoint can version it.
ARDUINOJSON_USE_LONG_LONG=1). https://decoder.theengs.io/use/include.htmlsetInsecure() for newbie-friendly first contact; pin a cert for
production.TheengsDecoder is GPL-family, so a distributed binary inherits that obligation — which is exactly why this source ships openly. Unlike its Apache-2.0 siblings in this library, treat this sketch as GPL-governed when you distribute builds of it. Keep it open; it's the right posture for OAT anyway.
Docs: CC BY 4.0 · An OAT sketch — an OpenCDC initiative.