SpinCycle
Live washer and dryer availability for campus laundry rooms, read from vibration sensors on the machines, with an email when your load is done.
- When
- Oct 2026 – present, in progress
- Built with
- ESP32-C6, MQTT (Mosquitto), Go, TimescaleDB, Redis, FastAPI, React, Prometheus
- Layers
Touches: Hardware, Firmware, Backend, Frontend
- Code
- GitHub
I own the MQTT broker, the Go ingest service, storage and the API. Twyla builds the hardware and firmware.
Sensor
Accelerometer on each machine, sampled at 100 Hz
Twyla
ESP32-C6
Vibration RMS every 5 s over MQTT/TLS
Twyla
Mosquitto
Per-device credentials; each device can only publish to its own topics
Aly
Ingest (Go)
Validate, dedupe, batch-write, and turn vibration into machine state
Aly
TimescaleDB + Redis
History in Postgres, live state in a cache that can be rebuilt
Aly
API + web
Live status, busy times, and finished-load emails
Aly
The problem
Walking down to the laundry room only to find every machine taken is a small, daily waste of time. SpinCycle puts a vibration sensor on each washer and dryer, works out whether it's free, running or finished, and shows it on a live page. Tap “Notify me” and you get an email when your load is done.
Underneath, it's a general IoT fleet platform: devices, device types and fleets are modeled generically, so a laundry room is the first fleet rather than the only one.
Decisions that matter
A machine never goes from offline straight to free. If a sensor drops off Wi-Fi mid-cycle, showing the machine as available would send someone to a running dryer, so the state engine forbids that transition outright. Every recorded wash cycle is replayed through the engine in tests.
Readings are acknowledged to the broker only after they're committed to the database, so a crash can't silently lose data. In the first crash test, ingest was killed with kill -9 partway through a 3,000-reading stream: after restart all 3,000 were stored exactly once, and the 10 redelivered duplicates were dropped.
Redis is a cache with persistence turned off, and every key is documented with how it rebuilds from Postgres, so losing it costs a few seconds rather than data.
Status
Targeting launch on November 8, 2026, with a live demo to follow.