One wire, many probes, every reading landing somewhere you own.
| Status | Live — flash from the browser, no IDE needed |
| Interface | 1-Wire · DS18B20 (64-bit address per probe, up to ten on one bus) |
| Chips | ESP32 · S3 · C3 · C6 |
| Delivers | Webhook (HTTP POST) · MQTT — oat-ods/0.3 |
| License | Apache-2.0 |
A self-configuring ESP32 node that reads one or many DS18B20 temperature probes on a single 1-Wire data line and pushes the readings as oat-ods to an endpoint the grower owns, by webhook or MQTT.
Everything except the sensor read is lifted from the SHT-30 Node, which took it
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/ds18b20-node/
(ESP Web Tools / Web Serial), then configures it on the device's own page.
No IDE, no command line, no code editing.
| DS18B20 | ESP32 | wire colour on a sealed probe |
|---|---|---|
| VDD | 3V3 | red |
| GND | GND | black |
| DQ (data) | the data pin (default below) | yellow, sometimes white or blue |
Plus one 4.7 kΩ resistor between DQ and 3V3 — see below. Every probe on the run shares all three wires.
| Board | Default | Why not the same number everywhere |
|---|---|---|
| Classic ESP32 | GPIO 4 | free; not flash, not strapping, not input-only |
| ESP32-S3 | GPIO 4 | free |
| ESP32-C3 | GPIO 4 | free |
| ESP32-C6 | GPIO 6 | GPIO 4 is a strapping pin on the C6 |
A 1-Wire bus idles HIGH through its pull-up, which is exactly the nudge that flips
a strapping pin into the wrong boot mode — so the C6 gets a different default
rather than a shared one that would sometimes break a boot. pinOkForOneWire()
refuses flash, strapping, USB-JTAG, UART0 and input-only pins per chip, with the
reason, because a bad pin is persisted in NVS and re-applied at every boot: it is
not a mistake you get to notice and correct.
Pin lists are from Espressif's per-chip GPIO documentation, not folklore.
1-Wire is an open-drain bus: devices only ever pull the line DOWN, and a pull-up resistor is the only thing that pulls it back UP. With no resistor there is no bus at all, just a floating pin reading noise — which looks exactly like a broken sensor.
The protocol sets no limit. Every DS18B20 carries a unique 64-bit address
lasered in at the factory (family code 28h + 48-bit serial + CRC), and the node
enumerates whatever is present with the ROM SEARCH command (F0h, the algorithm
from Maxim application note 187). Addressing is not the constraint.
The electrical limit is real: every device and every metre of cable adds
capacitance, which rounds off the edges of a 60 µs bit until the bus stops being
readable. On a short run with the standard 4.7 kΩ pull-up, eight to ten probes is
comfortable. This firmware caps its list at MAX_DS (10) for that reason — an
honest bound of ours, not a protocol one, and it says so on the console when the
list fills.
| measurement | unit | fold over the push window |
|---|---|---|
temperature |
Cel |
mean |
One stream per probe. source.physical_id carries the probe's own 64-bit address
(ds18b20:28ff641f8c1a3b02), so a reading is traceable to a chip, not to a
position in a list: swap a failed probe and the history of that spot carries on
with the change of hardware visible in the record.
Probes are listed in address order, so the numbering on the setup page is stable across reboots even when one is added.
READ POWER SUPPLY (B4h) reports it and the status
page says "wire VDD to 3V3" rather than pretending the readings are fine.SKIP ROM + CONVERT T) and the loop collects the results once the window
has passed. A person pressing "read now" gets the blocking version, because a
person is waiting.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.9 to 12 bits, set on the setup page and written to every probe on the wire.
| bits | step | conversion |
|---|---|---|
| 9 | 0.5 °C | 93.75 ms |
| 10 | 0.25 °C | 187.5 ms |
| 11 | 0.125 °C | 375 ms |
| 12 | 0.0625 °C | 750 ms |
Default is 12. The unused low bits at lower resolutions are masked rather than reported as decimal places they do not carry.
Specs above are from the DS18B20 datasheet (Analog Devices / Maxim): ±0.5 °C from −10 °C to +85 °C, ±2 °C over the full −55 °C to +125 °C range.
| File | What it is |
|---|---|
oat_ds18b20_node.ino |
The firmware source (Arduino / ESP32). |
platformio.ini |
Build config. Four envs: classic 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 + manifest-<mcu>.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 all four chips in Docker
SKIP_BUILD=1 ./build.sh # then the asset steps
build.sh produces one merged, flash-at-offset-0 image per chip in out/, plus
the manifest and download bundle the OAT site's flash-from-browser page serves.
oat-sht30-node/ — the wired sibling: air temperature and humidity, on I²C.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.