A solar buoy that sits on the water, measures a few stats, uses cellular internet to send them to a website you host.
Generated photoreal of the 24″ PVC-square hull (Pelican 1120, solar on the lid). Not a field photo. Parts count: labeled isometric and exploded SVG.
Open-source hardware kit. Order the parts, build the hull, flash the board, point it at your ingest URL. This repository is the kit. We host no servers, no database, and no live fleet.
GitHub Pages is documentation plus a dashboard of fixture JSON labeled simulated. Live numbers come from docker compose up on a machine you control.
Turn the site on once (the Actions token cannot do this for you):
- Repo Settings → Pages.
- Either Deploy from a branch →
gh-pages// (root), or GitHub Actions. - CI already pushes
site/distto thegh-pagesbranch on everymainpush. The Actions Pages deploy is best-effort if that source is not enabled yet.
Every 15 minutes the board wakes, samples, writes CSV on the microSD card, HTTPS POSTs, and sleeps:
device_id, token, ts, lat, lon, temp_c, ph, spcond_ms_cm, sal_psu, do_mgl, do_pct, depth_m, batt_v, rssi, fw
Temperature, pH, conductivity → practical salinity, dissolved oxygen (mg/L and %), depth, plus GPS, battery, and time. No turbidity — the cheap analog probe is not waterproof and is not NTU.
A YSI EXO2 lists around $7,300–$8,450 before sensors (Fondriest). A NexSens-class buoy is another few thousand. This kit’s BOM sums to about $866 of street prices — check the linked pages; they move.
This hardware has not been in a lake. Specs are copied from manufacturer wikis. See limits.
LilyGO T-A7670G R2 with GPS — ESP32, LTE Cat-1, L76K GNSS, microSD, 18650 sled, 5–6 V solar jack. Florida preserves have no Wi-Fi. Do not buy the Without-GPS ASIN B0CLLNK4MX.
Default assembly is Gravity cables on a screw-terminal block in an IP67 box. No custom PCB, no soldering required for the default path.
cd firmware
make test # UNESCO / Benson–Krause checks
pio run -e lilygo-t-a7670g # compile for the buoyHTTPS uses TinyGSM-fork (lewisxhe) (-DTINY_GSM_MODEM_A7670 -DTINY_GSM_FORK_LIBRARY). ESP32 ADC is 12-bit millivolts, not Uno 1024*5000. Pin map: hardware/pinmap.md.
SD card config.json (from firmware/data/config.example.json):
{
"ingest_url": "https://YOUR-HOST/api/v1/ingest",
"device_id": "buoy-01",
"token": "change-me",
"apn": "iot.1nce.net",
"sample_period_s": 900,
"stay_awake": false
}Serial waits 12 s after boot for cal (or set stay_awake). Dissolved oxygen is a two-point in NVS (doair / dozero); uncalibrated POSTs send do_mgl: null.
cd selfhost
cp .env.example .env
docker compose up --build
python3 ../scripts/simulate-buoy.pyOpen http://127.0.0.1:8080. The LTE radio cannot see localhost — put the same stack on a VPS (or a tunnel) before the bucket test.
Numbered high-school steps: buy → host → flash → config → calibrate → wire → hull → bucket → water.
| Tree | License |
|---|---|
| Firmware and site | MIT |
| Hardware | CERN-OHL-S-2.0 |
| Docs and diagrams | CC BY 4.0 |

