GROUU · Nodes · WellBouy
Deployed · live dashboard

WellBouy — well-water quality node

An open reference design for monitoring a water source over LoRaWAN — temperature, dissolved solids, turbidity and pH. All four sensors are calibrated and validated, the node deep-sleeps and streams to a live dashboard, and the dedicated board is fabricated. This page documents how it works and how to reproduce it from the published source.

XIAO ESP32-S3 Wio-SX1262 EU868 · OTAA CayenneLPP PlatformIO
Level of development

What's published so far

Firmware and the data pipeline are complete and running; the dedicated board is fabricated. Enclosure, full BOM and field deployment are the next deliverables.

Firmware Staged PlatformIO envs — LoRaWAN, all four sensors, deep sleep, RTC session resume and downlink commands, validated and integrated. ✓ Complete
Data pipeline TTN → Node-RED → InfluxDB → Grafana, plus a public dashboard — all live on the edge server. ✓ Live
Schematic (atopile) XIAO + radio, on-board ADS1115, switched 5 V rail, dividers, reverse-polarity + load-switch protection, Grove expansion. ✓ Done
PCB layout Round board with M4 mounts, laid out in KiCad and sent to fabrication; bring-up firmware validates it. ✓ Fabricated
Enclosure (CAD) Sealed housing with sensor glands and antenna pass-through. ○ Planned
Documentation Bring-up plan, board-design notes and this page published; grows with each stage. ✓ Ongoing
How it works

Sense, encode, transmit, sleep

Analog probes are conditioned by dedicated modules and scaled to ≤3.3 V into a shared ADS1115 16-bit I²C ADC; temperature is read digitally over OneWire. Readings are packed as CayenneLPP and sent as a LoRaWAN uplink, after which the node deep-sleeps between cycles and resumes its session on wake.

Node
XIAO ESP32-S3 + Wio-SX1262
ADS1115 front-end · deep sleep
Transport
Gateway → TTN
EU868 · OTAA join · v3
Backend
MQTT → storage → dashboards
Mosquitto Node-RED InfluxDB Grafana

The backend is the shared GROUU Stack — any TTN-connected application can consume the same uplinks.

Hardware · sensor stack

Four probes, one shared front-end

All modules are off-the-shelf, so the design is reproducible without custom silicon. Every driver is written and calibrated.

01 · Temperature✓ calibrated
DS18B20 probe

Waterproof digital probe on OneWire, 4.7 kΩ pull-up, 3.3 V. Clean °C to serial and uplink.

02 · TDS✓ calibrated
Seeed Grove TDS

Analog Grove TDS on the switched 5 V rail, output into the ADS (A0). DFRobot Gravity two-point k-value algorithm, temp-compensated ppm.

03 · Turbidity✓ calibrated
DFRobot SEN0189

Optical head, cabled; a 10k / 6.8k divider brings the 5 V output into ADC range (A1) → NTU, anchored to clear water.

04 · pH✓ calibrated
Phidgets 1130 + BNC probe

GigaΩ analog conditioner (pH 7 ≈ 2.5 V) through a 10k / 18k divider into the ADS (A2); two-point calibration at pH 4 & 7.

Shared ADC ADS1115 16-bit I²C, addr 0x48, at 3.3 V — analog sensors on A0–A2, DS18B20 stays digital.
🔌📷 Photo / diagram: the node's power rails & 5 V load switch (breadboard or board close-up)
Hardware · power

Two rails, battery-powered

3.3 V

Always-on — MCU, radio, ADS1115 and the DS18B20.

5 V

Switched — analog conditioners powered ~1 s per cycle through a high-side load switch (GPIO1 / D0).

Bat

1S LiPo (4500 mAh), mains-rechargeable — a fixed well site needs no panel. The board still exposes battery + solar inputs, so the same design runs off-grid elsewhere.

Firmware & parts

Encode four readings, uplink, sleep

The whole node is in one PlatformIO project — staged bring-up sketches plus the integrated production firmware. Below is the heart of it, and the full parts list with links.

src/main.cpp — CayenneLPP encode + uplink (TDS & turbidity ride the Luminosity type, 0–65535, because Analog Input overflows above 327.67)

CayenneLPP lpp(51);
lpp.reset();
lpp.addTemperature(1, isnan(r.temperatureC) ? 0.0f : r.temperatureC);
lpp.addAnalogInput(2, r.ph);
lpp.addLuminosity (3, (uint16_t)(r.turbidityNtu + 0.5f));   // turbidity
lpp.addLuminosity (4, (uint16_t)(r.tdsPpm + 0.5f));         // TDS
if (bootCount == 1 || (bootCount % 24) == 0)
  lpp.addGPS(5, FIXED_LATITUDE, FIXED_LONGITUDE, FIXED_ALTITUDE);

int16_t state = node.sendReceive(lpp.getBuffer(), lpp.getSize(),
                                 LPP_FPORT, dnData, &dnLen, /*confirmed=*/false);
PartRoleSource
Seeed XIAO ESP32-S3 + Wio-SX1262MCU + LoRa radio (SX1262 via RadioLib)Seeed ↗
ADS1115Shared 16-bit I²C ADC @ 0x48 — analog sensors on A0–A2
DS18B20 (waterproof)Water temperature — 1-Wire (GPIO4) + 4.7k pull-upAdafruit ↗in stock
Seeed Grove TDSDissolved solids (A0) — DFRobot Gravity algorithm, temp-compensatedSeeed ↗in stock
DFRobot SEN0189Turbidity (A1) via 10k/6.8k divider, 5 VDFRobot ↗in stock
Phidgets ASP200 pH probepH 0–14, gel-filled BNC probePhidgets ↗in stock
Phidgets 1130 pH/ORP adapterBNC → analog (A2) via 10k/18k divider, 5 VPhidgets ↗in stock
Grove Shield for XIAOSensor carrier (in lieu of a custom PCB)Seeed ↗to order
LiPo 1S 4500 mAhBattery — mains-rechargeable, no solar (B1)Mauser ↗to order
Dividers 10k/6.8k · 10k/18kLevel-shift turbidity & pH into ADC range
M12 glands · BNC bulkhead · O-ringsHull pass-throughs & sealto order
pH buffers 4 / 7 · TDS KCl 84 / 1413 µS/cmTwo-point calibration standardsto order
How to reproduce

Prove the radio, then each sensor

The firmware is organised so anyone can rebuild it incrementally — flash one stage, confirm it on serial and TTN, move on. Register your own device on TTN (EU868, LoRaWAN 1.0.x, OTAA), set the payload formatter to CayenneLPP, and keep your keys in an untracked secrets.h.

Stage 0
TTN setup
Gateway, application + device, keys, CayenneLPP formatter
✓ documented
Stage 1
Firmware bring-up
LoRaWAN ✓ · temp ✓ · TDS ✓ · turbidity ✓ · pH ✓ · integrated
✓ complete
Stage 2
Dedicated board (atopile → KiCad)
Schematic + layout done, board fabricated; re-run bring-up on the board
✓ fabricated
Stage 3
Enclosure
Sensor glands, antenna, sealing; print & fit check
○ planned
Stage 4
Field assembly & soak test
End-to-end on TTN, tune interval, verify power budget
○ planned