Selected work
Projects
A run of things I've built for the fun of it — small services wired together with Kafka, a bit of hardware and radio, and a homelab to run it all on. The throughline: turning noisy real-world signals into something useful, with no cloud bill. Two of them are live and public right now; the rest are below.
Currently building
World of Buoys
Live NOAA telemetry × KafkaNOAA publishes readings from roughly 1,300 ocean buoys, and almost none of it is pleasant to look at. World of Buoys pulls the whole network every thirty minutes and turns it into a dark telemetry console — a clustered live map, a fleet operations view, a nearby-buoy finder, and a research tab that runs correlation and lead/lag analysis across the fleet. Kafka-native end to end, self-hosted on the homelab, and the largest thing I've built for myself.
Scrapes NDBC's realtime2 feed and station table on a 30-minute cycle — ~1,300 stations, fetched 16 at a time — and publishes one message per station per stream to Kafka.
Consumes the observation and station topics and upserts into CouchDB, so a replayed topic converges on the same state instead of duplicating it.
A FastAPI service over CouchDB: per-station detail and time series, nearest-buoy lookup by station or coordinates, and fleet stats broken out by owner, type, and coverage.
Where it gets interesting — network and regional trend bands, a pairwise correlation matrix across any set of buoys, teleconnections that rank the whole fleet against a reference station, upstream leaders with a lead/lag forecast, and live anomaly ranking.
A React/Vite SPA with Leaflet marker clustering behind nginx. Every buoy has its own shareable link, and readings convert from SI to imperial at the edge.
Four Docker services on a Proxmox LXC, published through a Cloudflare Tunnel. It brings its own Kafka and CouchDB connections rather than standing up its own — it plugs into the homelab that was already there.
The original attempt — the pipelines that first proved NOAA's buoy data was worth pulling, and the starting point for everything above.
Sassy Routes
Live Routing & road scoringA router that optimizes for fun instead of speed. Give it two points and it scores the roads between them on how good they are to drive, then hands you the fun-versus-time tradeoff and a Google Maps deep link for the winner. Built for the way I actually drive the Miata, which is to say: the long way. The hard part isn't the map — it's defining "fun" as a number and then getting a routing engine to respect it.
Scores every OSM way in the corridor on curvature and elevation — 13,000+ candidate roads between Grand Rapids and Holland alone. Currently being reworked to score the best fixed-length window within a road rather than averaging the whole thing, so a great five-mile road can outrank a 150-meter fragment.
How much extra time a good road is worth. Expressed as minutes-per-kilometer plus an absolute floor, because a multiplier is the wrong shape — 3× allows forty extra minutes on an errand and three extra hours on a road trip, which is backwards from what a driver wants.
Threading a specific road into a trip. Pinning its midpoint made the router solve two independent legs and drive up and back down the same road; pinning both endpoints in trip order fixed it, and made the sought road cheaper to include than the doubled-back version.
A tunable per-kilometer cost charged against the detour budget for driving the same road twice. A penalty rather than a ban, since a genuinely spectacular dead end earns the double-back.
Also built
Earlier projects, still running. Most of these are where the Kafka-and-sensors habit came from.
The Chair™
Load cells × KafkaCuriosity about load cells turned into a fully event-driven sensor platform. A load cell under my living-room chair streams weight data into Kafka, where a pipeline of small services turns raw readings into meaningful sit events — driving everything from automated lighting to a live analytics dashboard. Built to scale: more sensors to come.
A Raspberry Pi, HX711 amplifier, and load cell read weight off the chair and publish to a raw-sit topic.
Joins a sit-down and a sit-up reading into a single Sit event — average weight, start/end timestamps, and duration.
Reacts to sit events: turns on the light behind the chair when you sit, and dims it when you get up.
Derives the live status of each device from the raw-sit stream — the source of truth for "in use / free".
Aggregates every sit and serves it over REST, plus WebSockets that push live device status to subscribers.
A leaner rewrite of the stats API in Go — same data, less overhead.
A Vue.js UI over the API: filter by weight, time, and duration; click a device for live status and details.
Whisper Bot
ML pipelines on KafkaMy friends share an anonymous meme chat with a constant firehose of messages — a perfect playground for Kafka and machine learning. Whisper Bot consumes the chat, trains a model on it, and once a day posts a (gloriously absurd) message of its own. The results are silly; the pipeline behind them is the fun part — now evolving toward LLAMA 2 with embeddings.
Uses the Telegram bot API to ingest every message from the anonymous group into a raw-message topic.
Cleans and normalizes the raw messages, then emits a tidy, training-ready set to its own topic.
Trains a TensorFlow model on the cleaned data, exports it to a compact TFLite file, and publishes it to a model topic.
Loads the latest model and generates text from a seed and word count — hot-swapping new models as they arrive.
Schedules one whisper to be posted to the chat each day, always using the freshest model.
Embeds each message and generates with a LLAMA 2 model. Next up: embedding the links and images people share for richer context.
Scores the sentiment of messages — an experiment in feeding the room's mood back into future models.
Farm Sensor Network
LoRa IoTA long-range, solar-powered sensor network for monitoring conditions out in the field, where Wi-Fi doesn't reach. Battery-friendly ESP32 nodes speak raw point-to-point LoRa — deliberately skipping LoRaWAN and Meshtastic overhead — to a Raspberry Pi hub that feeds a Go API and a live dashboard. Designed for nodes that run for years on a small panel, and the dashboard can even flash new firmware to a board straight from the browser.
ESP32 (Heltec V3) firmware sending a compact binary payload over SX1262 LoRa, with solar and deep-sleep power budgeting — and nRF52 boards under evaluation for even lower sleep current.
A Go daemon on a Raspberry Pi that receives LoRa packets off the radio and relays them upstream.
Go services handling auth, farms, hubs, and nodes, with secrets managed in HashiCorp Vault.
A React/TypeScript dashboard with live sensor charts — and in-browser ESP32 flashing for new nodes.
Personal Health Pipeline
Self-hosted data engineeringA self-hosted pipeline that turns phone and wearable sensor data into a personal health dashboard — end to end, with no cloud dependency. The same Kafka instincts from my day job, pointed at my own data.
Home Assistant exposes phone and wearable sensors and streams every reading into the pipeline.
A single Kafka topic carries it all, with consumer-side filtering that keeps the producers dumb and simple.
CouchDB persists each reading under an idempotent {entity_id}:{last_updated} document id, so replays never double-count.
A mobile-first React + Vite + Recharts frontend for vitals, activity, and sleep trends — built to extend to new data sources.
More builds
Smaller experiments — a place to try an idea end to end.
A citizen-science dashboard exploring whether geomagnetic activity affects sleep — NOAA space-weather data against crowdsourced sleep reports. Built, run for a while, and retired once it had answered the question for me.
Detects passing cars by sound alone — covering the gap where cameras fail in the dark. A Raspberry Pi with a Google Coral Edge TPU runs YAMNet classification and publishes events to the same Kafka topic as the camera pipeline.
A remote-controlled RC car that live-streams its video feed and takes driving input over Redis.
Automates a Blichmann grain mill so it stops itself after each fill — a small homebrewing quality-of-life win.
Reusable database sinks I drop into side projects to land streaming data without reinventing the wheel.
A guided tool that turns a fuzzy sense of career or life direction into concrete next actions, then tracks follow-through. Self-contained React, AI-facilitated, with local persistence.
Homelab & self-hosting
Most of these projects don't run on a cloud bill — they run at home. The homelab is where I deploy, break, and rebuild things on real infrastructure.
A Proxmox cluster that self-hosts most of the projects on this page — LXC and Docker behind Cloudflare Tunnels, no cloud dependency. My always-on playground for shipping side projects to real URLs.
A self-hosted LLM server running entirely on home hardware — Ollama with AMD ROCm GPU acceleration — so experiments never depend on a third-party API.
A privacy-first, self-hosted password manager (Vaultwarden) running in a lightweight Alpine LXC.
A Meshtastic LoRa mesh (Heltec V4, LongFast preset) tuned for internet-free, multi-hop communication — range work through antenna placement and hop-limit configuration.