Build recipe · Edge Collective

Mesh Water Level

A solar-powered ultrasonic sensor that measures the distance down to a water surface, radios it over a MeshCore LoRa link to a WiFi gateway, and logs it to Bayou. This page lists every part, where to buy it, and how it's wired.

Sensor MaxBotix MB7383 Sender Rook v4 (nRF52840 + SX1262) Gateway Heltec WiFi LoRa 32 V3 / V4 Radio 910.525 MHz · SF7 · BW 62.5 kHz Default interval 300 s
Overview

How it fits together

Two devices, one link each. The sender sits in a weatherproof box over the water, reads the ultrasonic sensor on a timer, and transmits a short LoRa message with the distance and its battery voltage. The receiver sits indoors within WiFi range (a LoRa link can span a few hundred metres to a few kilometres depending on terrain and antennas), acknowledges each reading, and posts it to a Bayou feed as distance_meters and battery_volts.

Sender (Rook) ultrasonic + LoRa LoRa Receiver (Heltec V3/V4) LoRa + WiFi WiFi Bayou data store Sender (Rook) ultrasonic + LoRa LoRa Receiver (Heltec V3/V4) LoRa + WiFi WiFi Bayou data store

Illustration courtesy Jacob Lloyd

Illustration: a solar panel and a grey junction box mounted on a wooden board at the water's edge; the ultrasonic sensor points down out of the bottom of the box.

Illustration courtesy Jacob Lloyd

The sender in the field: a small 5 V USB solar panel feeds the Rook's USB port, which charges the lithium battery inside the box. The sensor threads through the bottom of the box and points straight down.
Bill of materials

Parts & where to get them

Prices are approximate (USD, 2026) and shown only for budgeting. Where a part is generic, the spec that matters is listed instead of a single product, so you can buy from whichever supplier ships to you.

Pick the right radio band. This firmware runs at 910.525 MHz (the US 902–928 MHz band). Order the 915 MHz / “high band” (863–928 MHz) version of the Heltec and of any antennas. A 433 or 470 MHz board will not work.

Sensor node (sender)

1 per monitoring site
PartWhat it doesWhere to get itQtyApprox.
MaxBotix MB7383HRXL-MaxSonar-WRLST, IP67, TTL serial Ultrasonic rangefinder. Reports distance in millimetres, 0.5–10 m, about 6 readings per second. maxbotix.comAlso sold by RobotShop and others 1$110
Rook v4nRF52840 pro-micro + Wio-SX1262 LoRa + OLED The sender's microcontroller and radio. Reads the sensor over UART, shows status on its screen, transmits over MeshCore. Assembled, bare PCB, or build it yourself — see Getting a Rook 1see below
LoRa antenna, 915 MHze.g. Nearson S463AH-915: 2 dBi whip, RP-SMA male Connects to the Wio-SX1262 module. Never power the radio without an antenna attached. The Wio's socket is a tiny U.FL (IPEX), so an RP-SMA antenna also needs a U.FL to RP-SMA female bulkhead pigtail, which doubles as a clean pass-through in the enclosure wall. DigiKey — Nearson S463AH-915Pigtail: search “U.FL to RP-SMA female bulkhead” 1 + pigtail$10–20
Li-ion / LiPo battery3.7 V single cell, 3000–10000 mAh Runs the node overnight and through cloudy days. Must have a protection circuit and a JST-PH 2.0 mm 2-pin plug. Check the plug's polarity before connecting: vendors don't agree on which side is positive (see wiring). Adafruit, SparkFun, or any hobby battery supplier. Search “3.7V LiPo JST-PH 10000mAh” 1$10–30
5 V USB solar panel~5–6 W, USB-A output, outdoor-rated Charges the battery through the Rook's USB-C port. Any panel with a regulated 5 V USB output will do. Generic. Search “5V 6W USB solar panel waterproof” 1$15–30
USB-A to USB-C cableoutdoor length as needed Runs from the panel into the box, through a cable gland. Generic 1$5
Hook-up wire, 3 conductors22–24 AWG, or shielded 3-core cable Sensor to Rook: signal, power, ground. Keep it short if you can. For longer runs, use shielded cable. Generic, or MaxBotix's shielded cable (sold under “accessories” on the MB7383 page) ~30 cm$2
Weatherproof junction boxIP65+, roughly 100 × 100 × 70 mm Holds the Rook and battery. Needs room for the sensor's thread through the bottom and a gland for the solar cable. Hardware or electrical supplier 1$10–20
¾″ fitting + cable gland¾″ PVC/conduit locknut or coupling; PG7 or M12 gland The MB7383's housing threads into standard ¾″ electrical PVC fittings. The gland seals the USB cable entry. Hardware store 1 each$5
Mounting board / bracket Holds the panel and box so the sensor points straight down, clear of the dock or bank. Local lumber, or a steel angle bracket 1—

Gateway (receiver)

1 per site, or shared by nearby sensors
PartWhat it doesWhere to get itQtyApprox.
Heltec WiFi LoRa 32 V3 or V4ESP32-S3 + SX1262, 863–928 MHz Receives the LoRa messages, acknowledges them, and posts each reading to Bayou over WiFi. Its screen shows WiFi and Bayou status. Either version works: the V4 has more transmit power, the V3 is cheaper and widely stocked. heltec.org — V3
heltec.org — V4Also Rokland, Amazon, and other resellers
1$20–30
LoRa antenna Usually included with the Heltec. A better antenna placed high, by a window, increases range. The same Nearson whip works here with a U.FL to RP-SMA pigtail. Included, or Nearson S463AH-915 1incl.
USB-C power supply + cable Powers the gateway continuously. Any phone charger works. Generic 1$10
Case (optional) Heltec sells a housing kit for the V4. 3D-printed cases exist for both boards. heltec.org — expansion kit 1$5–15

Online services

ServiceWhat it doesLink
Bayouopen-source data store Stores the readings and serves them as charts, JSON and CSV. Create a feed and give its keys to the gateway. See Data in Bayou. gitlab.com/p-v-o-s/agroeco/bayou
Setup & flashing page Flashes both devices from Chrome or Edge, and gives you a serial console for pairing and WiFi setup. edgecollective.io/micro-config
Firmware sourceexample v3-ultrasonic MeshCore-based firmware for both the sender and the receiver. github.com/edgecollective/MeshCore-simple-sensor
The sender board

Getting a Rook

The Rook is an open-hardware carrier board designed by Edge Collective / PVOS. It pairs an nRF52840 “pro-micro” module with a Seeed Wio-SX1262 LoRa radio, a small OLED screen, a user button, a power switch, and a JST battery connector. There are three ways to get one.

Some soldering

Bare PCB + parts kit

Buy the bare PCB and source the modules yourself. All the modules are through-hole or socketed:

Full DIY

From the design files

KiCad schematic, PCB layout and Gerbers are open source. Order boards from any PCB fab and build from the schematic.

github.com/edgecollective/rook (v4 hardware lives in hardware/v0.4)

0.96 inch blue SSD1306 OLED module with four header pins labelled VCC, GND, SCL, SDA along the top edge

Check the OLED's pin order before soldering. The Rook's OLED header is wired VCC · GND · SCL · SDA, left to right. The labels printed above the four pins on your display must read in that same order, as they do on this module. Many look-alike OLEDs put GND first, and plugging one of those in reverses the power and can destroy the display.

Pro-micro clones vary in sleep current and in their charge circuit. A nice!nano v2 is the known-good reference. Test a clone before you rely on it in the field.
Sender

Wiring the sensor node

Only three wires go between the sensor and the Rook. The MB7383 runs freely, sending a frame like R0742 (742 mm) about six times a second at 9600 baud. The Rook listens on its RX1 pin and never talks back. Power comes in through the pro-micro's USB-C port from the solar panel. The battery plugs into the Rook's JST socket.

MaxBotix MB7383 7-pin pads on the back of the sensor 1 temp sensorNC 2 pulse widthNC 3 analogNC 4 RX / startNC 5 serial out 6 V+ 7 GND Rook v4 nRF52840 pro-micro + Wio-SX1262 + OLED RX1 P0.08 · D0 · Serial1 RX · pin 2 3V3 3.3 V out · pin 21 GND any GND · pins 3, 4, 23, 26 USB-C JST-PH Solar panel 5 V USB output Li-ion battery 3.7 V · 3000–10000 mAh Leave pins 1–4 unconnected. Pin 4 floating = continuous ranging mode.
Serial data (sensor → Rook) Positive supply Ground
Schematic view, not to scale. “Pin N” on the Rook side is the pin number on the pro-micro footprint in the Rook schematic. On the board itself, use the silkscreen labels RX1, 3V3 and GND.
MB7383 pinSignalRook connectionNotes
5TTL serial outRX1 (P0.08)9600 baud, 8N1, frames Rdddd\r in mm
6V+3V3Sensor accepts 2.7–5.5 V and draws about 3 mA
7GNDGNDAny ground pad
4Ranging start—Leave floating so the sensor ranges continuously
1, 2, 3Temp / PWM / analog—Not used
Power the sensor from 3V3, not BAT or USB. The MB7383's serial output swings all the way to its supply voltage. Powered from 3.3 V, it's safe for the nRF52840's input pin. Powered from the battery (up to 4.2 V) or USB (5 V), it can exceed the chip's 3.6 V input limit. If you want the extra range margin of a 5 V supply, add a resistor divider on the data line.
Check the battery's JST polarity before you plug it in. JST-PH plugs are not standardised: some battery vendors wire red (+) to the opposite pin from what the Rook expects. Before connecting, compare the plug with the + and − markings beside the Rook's battery socket, or confirm with a multimeter which pin is positive. If it's reversed, swap the two wires in the plug by gently lifting each plastic latch and pulling the crimped pins out. Never just force it in: a reversed battery can destroy the pro-micro's charger instantly.

About charging: the solar panel's 5 V goes into the pro-micro's USB-C port, and the pro-micro's built-in charger tops up the battery. That charger is small (about 100 mA on a nice!nano), so a bigger panel won't charge any faster. A larger battery gives you more cloudy days in reserve, not faster charging. A 10000 mAh cell is a sensible choice for winter or for shaded sites.

Installation

Mounting over the water

The sensor measures the distance down to the water, so level = (height of sensor above your datum) − distance_meters. Record the mount height once at install time. Mount the box level, so the sensor points straight down over open water.

piling / post solar panel Rook + battery MB7383, ¾″ thread highest expected water ≥ 0.5 m sensor reads nothing closer than 50 cm ≥ 1 m clear of pilings, ropes, hulls
Cross-section, not to scale. The sensor reports the strongest echo, so anything nearer than the water inside its beam (a piling, a rope, a boat) will be measured instead.
  1. Drill the bottom of the box for the sensor. Thread the MB7383 through a ¾″ PVC fitting or locknut so its face points down, and seal the joint.
  2. Fit a cable gland in the side or bottom of the box for the solar panel's USB cable. Keep all openings facing down so rain can't run in.
  3. Mount the box so the sensor face is at least 0.5 m above the highest water you expect. The MB7383 reads anything closer as 50 cm. Its maximum range is 10 m.
  4. Face the panel toward the equator (south in the northern hemisphere), tilted up and unshaded for as much of the day as you can manage.
  5. Test in air first: aim the sensor at the floor from a known height and confirm the OLED reads it to within about 1 cm.
Receiver

The gateway

The Heltec needs no wiring: attach its LoRa antenna, plug it into USB power, and place it indoors where it gets WiFi, as close to the sensor as you can, ideally high up by a window facing the sensor. Once configured, it reconnects to WiFi by itself after a power cut.

USB-C charger 5 V wall power Heltec V3 / V4 LoRa receive → WiFi post 915 MHz antenna WiFi Router Bayou Readings arrive by LoRa from the Rook; each is acknowledged, then posted.
Software

Firmware & setup

Everything is done from one web page in Chrome or Edge on a desktop computer: edgecollective.io/micro-config. It walks through these steps in detail.

  1. Flash the gateway. Plug in the Heltec and flash the v3-ultrasonic receiver firmware straight from the browser.
  2. Flash the Rook (skip if it came pre-flashed). Double-tap reset, and a NICENANO drive appears. Drag the .uf2 file onto it.
  3. Pair them. With both powered up side by side, cycle each OLED to its Advert page with short presses, then long-press to broadcast. Each device adds the other as a contact.
  4. Point the sender at the gateway. In the Rook's serial console, run:
    list
    target Receiver
  5. Put the gateway on WiFi from its serial console:
    set wifi_ssid your-network
    set wifi_password your-password
    reboot
  6. Point it at your own Bayou feed. Create a feed at bayou.pvos.org (see Data in Bayou), then enter its two keys:
    set bayou_public_key YOUR_PUBLIC_KEY
    set bayou_private_key YOUR_PRIVATE_KEY
  7. Optional: change how often readings are sent with set interval 300 (seconds, minimum 10), and give the node a name with set name Dock-North.

To check it's working, run send on the Rook to force a reading now, and status on the gateway to see the WiFi and Bayou status and the last reading received.

Data

Data in Bayou

Bayou is a small open-source data server (Node/Express + PostgreSQL) from PVOS. It keeps sensor readings in feeds and serves them back as charts, JSON and CSV. The public instance runs at bayou.pvos.org, or you can run your own from the GitLab repo. The repo's docs/API.md is the full API reference.

There are no accounts. Each feed has two 12-character keys:

  • Public key: appears in URLs. Anyone who has it can read the feed and see its charts.
  • Private key: sent in the body of each post. Anyone who has it can write to the feed, and can also clear it. Keep it private.

To create a feed, open bayou.pvos.org and click Create new feed. The page that follows shows both keys, the feed's URL and a QR code. Save them: that page is the only place the private key is shown. You don't need to set up fields: every feed accepts the full set listed below.

Use your own feed. The gateway firmware ships with a shared demo feed's keys built in, so it posts somewhere as soon as it's on WiFi. Because those keys are published with the firmware, anyone can write to or clear that feed. Set your own keys before you rely on the data.

From sensor to database

Each reading changes shape twice on its way into Bayou:

1 · Rook → Heltec A LoRa direct message: one line of text, end-to-end encrypted by MeshCore.
[SENSOR] node_id=1 dist=1.234m
 batt=3.87V fwd_hops=0 fwd_path=none
2 · Heltec → Bayou An HTTPS POST of JSON to https://bayou.pvos.org/data/<public_key>
{
  "private_key": "YOUR_PRIVATE_KEY",
  "node_id": 1,
  "distance_meters": 1.234,
  "battery_volts": 3.870,
  "aux_1": 0,
  "aux_2": 0,
  "log": "path=none route=DIRECT",
  "source": "Sensor"
}
3 · Stored in Bayou One row per reading. The server adds the id and timestamp and leaves unsent fields empty (null).
{
  "id": 2490734,
  "timestamp": "2026-07-20T09:04:31.832Z",
  "parameters": {
    "distance_meters": 1.234,
    "battery_volts": 3.87,
    "node_id": 1,
    "aux_1": 0, "aux_2": 0,
    "log": "path=none route=DIRECT",
    "temperature_c": null,
    "…": "every other field, null"
  }
}
Example values. The Heltec acknowledges the reading over LoRa first, then posts it. If the post fails, for example because WiFi is down, the Heltec retries every 15 seconds. It only holds the most recent reading, so a newer one replaces an unsent older one: readings aren't queued up while offline.

What the gateway sends

FieldMeaningWhere it comes from
private_keyProves the post is allowed to write to this feedGateway setting bayou_private_key
node_idWhich sensor sent the reading (1–65535). Lets several sensors share one feed, each drawn as its own lineSender setting set node_id (default 1)
distance_metersDistance from the sensor face down to the water, in metres (mm resolution)MB7383 reading
battery_voltsSender's battery voltage. Watch it for charging problemsRook battery divider
aux_1Mesh hops the message took, as measured on arrivalGateway
aux_2Hops the sender believed its route had (255 = unknown)Sender
logRoute details as key=value text: the repeater path, and DIRECT or FLOOD routingGateway
sourceThe sender's name. Not stored: Bayou ignores field names it doesn't recogniseSender setting set name

Posting to Bayou yourself

POST/data/<public_key>/ accepts JSON (Content-Type: application/json) or an ordinary form post. It must include private_key and can include any of the fields below. Unknown fields are silently dropped, and omitted fields are stored as empty. The server records the time a reading arrives. You can't send your own timestamp, so old data can't be backfilled. A successful post returns 200 Measurement recorded. A wrong key returns 400 Private key mismatch.

GroupAccepted fields
Distancedistance_meters, distance_meters_1, distance_meters_2, distance_meters_3
Environmenttemperature_c, humidity_rh, pressure_mbar, co2_ppm, light_lux
Extra temperaturestemperature_c_1, temperature_c_2, temperature_c_3
Powerbattery_volts, voltage_1, voltage_2, voltage_3
GPSgps_lat, gps_lon, gps_alt
General purposeaux_1, aux_2, aux_3
Mesh / radionode_id, rssi, next_hop, next_rssi
Free textlog

You can test a new feed from a laptop before any hardware is involved:

curl -X POST https://bayou.pvos.org/data/YOUR_PUBLIC_KEY/ \
  -H 'Content-Type: application/json' \
  -d '{"private_key":"YOUR_PRIVATE_KEY","node_id":1,"distance_meters":1.72,"battery_volts":3.9}'

Reading data out

Every read address needs only the public key, so you can share them freely. The JSON addresses allow cross-origin requests (CORS), so any web page can fetch a feed directly. Replace <key> with the feed's public key and <node> with a sender's node_id.

AddressReturns
/data/<key>/?plot_param=distance_metersThe feed's web page: a live chart (one line per node, refreshed every 5 s), a node table, links and a QR code. plot_param chooses the field to chart and defaults to CO₂, so add it for water level.
/data/<key>/json/The newest readings as JSON, oldest first. Add ?limit=N for the latest N (default 2000).
/data/<key>/json/<node>Same, for one sensor only.
/data/<key>/latest/Only the single newest reading, as JSON. Useful for dashboards and “is it still alive?” checks.
/data/<key>/latest/<node>The newest reading from one sensor.
/data/<key>/latest/csv/The newest reading as a one-row CSV file.
/data/<key>/csv/The feed's entire history as a CSV download, all columns.
/data/<key>/csv/<node>Entire history for one sensor, as CSV.
/stations/A phone-friendly dashboard of several feeds: a map, each station's chart, and printable QR codes. Configured in the page and shareable as a link.

Every JSON response has the same shape: {"feed_pubkey", "feed_name", "data": [ {"id", "timestamp", "parameters": {…}} ]}. Each reading's parameters contains all the accepted fields, with null for any that weren't sent. Timestamps are UTC. Bayou has no filtering by date range: fetch the latest N with limit, or download the full CSV.