Four wires and an accurate air sensor, reporting to a place you own.
| Status | Live — flash from the browser, no IDE needed |
| Interface | I²C · SHT-30 (0x44 / 0x45 — one or two sensors per board) |
| Chips | ESP32 (classic; bench-verified) |
| Delivers | Webhook (HTTP POST) · MQTT — oat-ods/0.3 |
| License | Apache-2.0 |
A self-configuring ESP32 node that reads an SHT-30 air temperature + humidity sensor over I²C and pushes the readings as oat-ods to an endpoint the grower owns, by webhook or MQTT.
This is the first wired-sensor sketch built on the reference node's full core.
Everything except the sensor read is lifted from oat-ble-listener v1.4.8: config
+ NVS, the shared field registry (one applyField/readField behind both the web
form and the serial Console), WiFi/AP/captive portal with connect-then-confirm,
mDNS, the push engine (https-preferred with signed-http fallback, HMAC signing,
oat-ods batch, monotonic seq), the 60 s health heartbeat, and the two-way USB
Console. Set up one OAT node and you have set up all of them.
A grower flashes it from the browser at
openagriculturetechnology.com/build/sketches/sht30-node/
(ESP Web Tools / Web Serial), then configures it on the device's own page.
No IDE, no command line, no code editing.
| SHT-30 | ESP32 |
|---|---|
| VIN / VCC | 3V3 (not 5 V unless the breakout regulates) |
| GND | GND |
| SDA | GPIO 21 |
| SCL | GPIO 22 |
| ADDR | open = 0x44 · tied to 3V3 = 0x45 (a second sensor) |
Why 21 and 22. GPIO 6–11 are the SPI flash the firmware runs from. GPIO
0/2/12/15 are strapping pins, and an I²C bus idles high through its pull-ups,
which is exactly the pull that flips one into the wrong boot mode. GPIO 1/3 are
UART0, the USB console. GPIO 34–39 are input-only with no pull-ups, so they can
never carry a bidirectional bus. GPIO 16/17 are free on a plain devkit but are the
PSRAM lines on WROVER modules and are already spent by the other sketches in this
library (SDI-12 data, Modbus RX/TX, HX711). That leaves 21/22, which are also
the ESP32 core's default Wire pins and what the two other I²C sketches here
(Analog Reader, CWSI Node) default to. One wiring habit for the whole library.
Both pins are configurable on the setup page for a board that differs.
Pull-ups. I²C needs a resistor pulling each line to 3.3 V. Nearly every SHT-30 breakout has them (typically 10 k); a bare chip does not — add 4.7 k–10 k.
| measurement | unit | fold over the push window |
|---|---|---|
temperature |
Cel |
mean |
humidity |
%RH |
mean |
Dewpoint and VPD are not computed here. The node reports what it observed and the endpoint derives the rest, the same rule every OAT node follows.
source.physical_id carries the chip's own serial number (sht30:<serial>), read
at boot — swappable provenance for "which gadget is filling this slot." A clone
breakout that doesn't implement the serial command falls back to sht30:0x44.
status.0x44 and
0x45 each get their own stream id. Inside and outside from one board. The node
finds whichever are present at boot, and picks up one wired later.agg.window_s is the window actually covered, not the configured interval.
Sampling continues while the network is down, so the first push after an outage
can carry hours of readings, and it says so.| File | What it is |
|---|---|
oat_sht30_node.ino |
The firmware source (Arduino / ESP32). |
platformio.ini |
Build config. One env: classic ESP32. Pinned libs. |
merge_bin.py |
Post-build hook → one merged factory image at out/firmware-esp32.bin, flashable at offset 0. |
make_manifest.py |
Assembles manifest.json + manifest-esp32.json from whatever bins exist. |
build.sh |
Builds, then copies bins + manifests + source downloads into the site assets. |
docker-build.sh |
The same compile with no host toolchain (Docker + PlatformIO). |
pipx install platformio # or: pip install --user platformio
./build.sh
No host toolchain?
./docker-build.sh # compile in Docker
SKIP_BUILD=1 ./build.sh # then the asset steps
build.sh produces the merged, flash-at-offset-0 image in out/, plus the
manifest and download bundle the OAT site's flash-from-browser page serves.
Every OAT sketch is split by sensor, never by chip, so adding the S3 / C3 / C6
later is an env in platformio.ini plus a default pin pair, not a second sketch.
The classic ESP32 is what shipped first here because it is the board this was cut
for and bench-run on. The pin guidance above is classic-ESP32 specific: the other
chips number their pins differently and each needs its own verified default pair
before it earns a button on the site.
oat-ble-listener/ — the reference node this core comes from.../lib/oat_ods/ — the shared oat-ods encoder + measurand vocabulary.../lib/oat_sign/ — the shared HMAC push signer.Code: Apache-2.0 · Docs: CC BY 4.0 · An OAT sketch — an OpenCDC initiative.