Inspiration
Ocean buoys that track water temperature and waves cost thousands of dollars each, and most of them need a satellite or cell connection to send data back. We wanted to see how far we could get with $10 ESP32s and hobby sensors. We also wanted to know if a group of cheap buoys could share one link to shore instead of each one needing its own.
What it does
Each Raylay buoy is a small manta ray shaped hull with an ESP32, a waterproof water temperature probe, an air temperature and pressure sensor, a motion sensor and GPS. It takes a reading every 30 seconds (every 2 seconds in demo mode) and saves it to flash.
The buoys talk to each other over ESP-NOW, and every buoy keeps a copy of every other buoy's readings. So the base station, an ESP32 plugged into a laptop, doesn't have to reach every buoy. If it can hear one of them, it gets the whole fleet's data, including buoys that are out of range or dead. Once the laptop saves a reading, that confirmation spreads back through the mesh and the buoys delete their copies to free up space.
On the laptop there's a web console:
- A live map of the fleet you can color by water temperature, waves, pressure and more
- History charts for each buoy
- Alerts for rough water, tilting, a buoy going quiet, or the water temperature changing too fast
- Buoys close to the base stream their motion sensor at 50 Hz, and the console shows them as a 3D model rocking in real time
There's also a voice assistant, and you can pick Grok or Gemini Live. Ask it "is anything wrong?" and it checks the alerts and recent data, opens the chart for that buoy and draws a red box around when it happened. It can also pull up buoys and change the map, and it hangs up on its own when you say you're done.
To show it scales, we can add 500 simulated buoys off the Georgia coast and pan over to them.
How we built it
- Firmware: C++ with PlatformIO. Each reading is a 42 byte record, so 5 fit in one radio packet. Every few seconds each buoy broadcasts a list of what it has, and neighbors that are missing records get sent them.
- Motion: the motion sensor buffers samples at 50 Hz, so we don't lose any while the main loop is busy writing to flash. Each record stores wave energy, wave peak and average tilt for that period.
- Base station: an ESP32 that bridges the mesh to USB serial. A Python script saves everything to SQLite and sends the confirmations back.
- Orientation: the laptop turns the raw accelerometer and gyro data into the 3D orientation.
- Web app: the backend is FastAPI, and the frontend is React with MapLibre for the map and three.js for the 3D model. The voice agent runs in the browser. The server only hands out short-lived tokens, so our API keys never leave the laptop.
- Mesh simulator: it runs on a single ESP32, puts virtual buoys on a fake, unreliable radio and prints PASS or FAIL. It caught most of our sync bugs before the hardware was even wired up.
Challenges we ran into
- Clone sensor: our "MPU6050" turned out to be an MPU6500 clone. The Adafruit library wouldn't talk to it, so we wrote our own driver.
- Mirrored model: once the sensor worked, the 3D model was flipped left/right and up/down, because the chip is mounted on its edge inside the hull. We had to work out the axis rotation for how it's mounted.
- Spinning model: the 3D model slowly spun in circles. If the buoy is moving when it boots, it skips gyro calibration and ends up with about 2 degrees per second of error. Now the laptop recalculates that error whenever the buoy sits still, which cut the drift from 1.6 to about 0.1 degrees per second.
- Syncing over a bad radio: ESP-NOW broadcasts aren't acknowledged, so we hit packets dropped silently, gaps after a buoy got wiped, and neighbors holding different chunks of data. We added resend requests, a wait before giving up on missing records, and a checksum on the serial link after we caught corrupted lines at 921600 baud.
- One buoy kept dropping out: our real-sensor buoy went quiet while the bare test board was fine. The data showed it kept recording the whole time and sent the backlog later, so the problem was the radio link, not the code. Where the antenna sits in the hull, and the power supply, matter a lot.
- Missed alert: we held the water probe in our hand, the temperature rose 10 °C in two minutes, and the voice agent said everything looked normal. Our alert only compared two readings in a row, so we added one for changes over a few minutes.
Accomplishments that we're proud of
- Data hops from buoy to buoy to reach the base. Our simulator passes with a chain of 4 buoys and a lossy radio.
- Battery monitoring and solar charging
- A live 3D model of the buoy in the browser, driven by real sensor data.
- A voice agent that actually shows you the problem on the chart instead of just reading out numbers.
- Everything runs locally on one laptop. The only cloud part is the voice model.
What we learned
- Cheap sensors can surprise you: clones, gyros that start off calibrated wrong, chips that report a different ID. Check what you actually have before trusting a library.
- On real hardware the radio is the hard part. Where the antenna sits can matter more than any code change.
- Saving all the raw data from day one paid off. We fixed the gyro drift by replaying recorded motion data instead of guessing.
What's next for Raylay
- A relay-only version for sensorless extender nodes
- LoRa radios for more range between buoys
- A compass, so the heading doesn't drift
- Putting a few in real water: a lake first, then the coast
Built With
- c++
- css3
- html5
- i2c
- mesh
- networking
- python
- typescript
- uart
- websockets
Log in or sign up for Devpost to join the conversation.