Build · Sketch · source · v1.0.0

Analog & 4–20 mA Reader.

What it does
Reads analog / 4–20 mA sensors through a 16-bit ADC and pushes each as oat-ods
Interface
I²C · ADS1115 16-bit ADC
License
Open: copy it, change it, own it

A world of good sensors speak only analog: a voltage, or a 4–20 mA current loop. This node reads them cleanly through an ADS1115 16-bit converter, turns each into a real measurement with a two-number calibration, and pushes it as oat-ods to a place you own.

What it does.

Reads analog sensors through an ADS1115 (a clean 16-bit I²C ADC), applies a two-number linear calibration, and emits each as oat-ods. The bridge for the huge install base of analog and current-loop sensors that have no digital bus.

That’s the Collect layer for one more kind of sensor: it reads the instrument, shapes each reading into the one oat-ods message every Open Agriculture Technology device speaks, and pushes it to a place you own. The value is what you do with the reading later; this just gets it flowing.

What it reads.

Each reading goes out as its own oat-ods message, filed under the place you name, so swapping the hardware later never breaks the record:

What you need.

Wiring. The ADS1115 sits on I²C (SDA / SCL, 3.3 V). Wire each sensor output to a channel input (A0–A3). For a 4–20 mA sensor, put a precise sense resistor across the input so the current becomes a voltage the ADC can read (150 Ω gives 0.6–3.0 V, inside range).

The channel map.

One line per ADS1115 channel on the setup page:

<ch> <stream_id> <measure>:<unit>:<m>:<b>
0 canopy-par par:umol/m2/s:800:0
1 tank       level:%:41.667:-25

value = m × volts + b. Two points (volts, value) define the line, so any linear sensor fits, including 4–20 mA turned into a voltage by a sense resistor.

What it sends.

It pushes to wherever you point it (Webhook (HTTP POST) or MQTT) as oat-ods/0.3. One measurement per message; here’s a reading from this node:

{
  "schema": "oat-ods/0.3",
  "observed_at": "2026-06-25T14:30:00Z",
  "stream": { "id": "canopy-par", "name": "…", "location": "…" },
  "measurement": "par",
  "value": 642,
  "unit": "umol/m2/s",
  "source": { "tier": "oat-analog-reader", "gateway_id": "…", "physical_id": "ads1115:0" }
}

That’s the same envelope the whole library speaks. Point it at your webhook, an Home Assistant broker, or Open Agriculture Technology; the receiver can’t tell which node produced it. The developer reference has the full spec.

Get it & build it.

The project downloads whole: the sketch plus the shared oat_ods library it needs, so it builds as-is:

Full PlatformIO project (.zip) Just the sketch (.ino)

Two ways to put it on a board, both free:

  1. PlatformIO (builds all four ESP32 chips): unzip, then cd oat-analog-reader && pio run -t upload. The bundled lib/oat_ods/ resolves automatically.
  2. Arduino IDE: open the .ino, install the libraries below from the Library Manager, pick your ESP32 board, and upload.

Compile-verified on our side (it builds clean with the pinned toolchain), but not yet run on the bench with real sensors, so it ships as source rather than a browser image. If you run it, tell the collective how it went; that’s what turns it into a flash-from-browser sketch.

Set it up.

Setup happens on the device’s own page: nothing to install, works with no internet:

  1. Power the node from any USB charger or your computer.
  2. Join its Wi-Fi network OAT-Analog-XXXX, and the setup page opens (or visit http://192.168.4.1).
  3. Sign in, set your Wi-Fi, where the readings go, and the sensor details above, then save.
  4. Point delivery at the test endpoint and watch your first reading arrive, checked against the standard.

Settings persist across reboots; a fresh flash wipes them, on purpose, so a re-used board never carries the last owner’s Wi-Fi or endpoint.

Notes.

  • A 16-bit ADC and a two-point calibration get you accuracy a raw ESP32 analog pin can’t. The live-volts table on the setup page shows the reading so you can derive m and b.
  • Each channel becomes one oat-ods message, with physical_id recording ads1115:<ch>.

For makers & trainers.

Frequently asked questions.

How do I read a 4–20 mA sensor with an ESP32?

Put a precise sense resistor (commonly 150 ohm) across the current loop so the 4–20 mA becomes roughly 0.6–3.0 V, then read that voltage with a 16-bit ADS1115 ADC over I2C. A linear calibration (value = m × volts + b) maps the voltage back to the sensor’s engineering units.

Can an ESP32 read 0–5 V analog sensors accurately?

The ESP32’s built-in ADC is noisy and tops out around 3.3 V. An external ADS1115 gives clean 16-bit readings, and for 0–5 V you scale into range with a resistor divider or use amplified 0–2.5 V outputs. A two-point calibration then converts volts to the measurement.