ApneaWatch — overnight apnea monitoring.
Consumer pulse oximeters show a number and store nothing. Clinical gear is expensive and gives caregivers no live view and no usable record. ApneaWatch turns an off-the-shelf oximeter into an overnight monitor: it streams blood-oxygen over Bluetooth, watches through the night, and turns it into clinical-style analytics a doctor can actually read.
The problem
Picture someone who needs their blood-oxygen watched overnight — for suspected sleep apnea or another condition that causes oxygen to dip during sleep — and a caregiver who wants to keep an eye on it from another room and bring a clear record to the next appointment.
The off-the-shelf options fail this person at both ends. A consumer fingertip oximeter shows a live number and remembers nothing — no history, no trend, no export. Clinical equipment is expensive, episodic, and gives a caregiver no live view and no record they can take home. A full sleep study is the gold standard for diagnosis, but it isn't something you run at home every night. There was a gap between "a number on a screen" and "something a doctor can act on." ApneaWatch fills it.
Note: ApneaWatch uses consumer-grade sensors and is not an FDA-cleared medical device; it's a logging and visualization tool to inform a conversation with a physician. All screens and data shown here are synthetic samples — no real person's data is displayed.
Who it's for
The caregiver is the primary operator: they pick a profile, pair the device, start a session, and can watch live from another room. The person being monitored just wears the oximeter overnight — the app applies age-appropriate thresholds (it supports pediatric as well as adult ranges). The clinician is the audience for the output: a clear overnight summary they can read in seconds.
Product & journey
One night, end to end
- Pair — Bluetooth pairing on mobile, or the browser's Web Bluetooth picker on the web build; two oximeter families are supported, and can even run in parallel.
- Monitor — a live session opens a WebSocket and streams oxygen saturation, heart rate, perfusion index, and the pulse waveform, with a live dashboard a caregiver can watch from another room.
- Persist — every session is stored: high-frequency raw samples plus one-second aggregates, so nothing is lost across an eight-hour night.
- Review — a session detail view with timeline charts and a clinical-style summary; a home dashboard rolls multiple nights into trends.
- Share — a one-tap text summary (ODI, time below 90%, lowest and average SpO₂, events) copied to the clipboard for a doctor's visit.
Architecture
The hard part is the front of the pipe: decoding a proprietary Bluetooth stream reliably for hours. The Flutter client decodes and aggregates the BLE feed, streams it over a WebSocket to a Kotlin/Ktor API, which persists raw and one-second data to PostgreSQL and computes the overnight analytics. The same Flutter codebase ships to Android and the web.
Craft & challenges
- Reverse-engineered two BLE protocols — one device speaks a Nordic UART service with ~36 Hz waveform frames and 13-byte vitals packets; the other needs a custom CRC-8 (extracted from a decompiled vendor library) and a handshake to unlock a 50 Hz pulse waveform. Get the polynomial wrong and the device silently ignores you.
- Resilient decode & reconnect — a rolling frame parser that survives split and concatenated Bluetooth notifications, plus per-device reconnect loops so an eight-hour overnight session survives Wi-Fi and BLE flaps.
- A data-integrity fix that mattered — switching aggregation to fixed wall-clock one-second buckets recovered roughly a quarter of samples that arrival-timed bucketing had been dropping.
- Sleep-oximetry analytics — ODI (3% desaturation index), time below 90%, hypoxic burden, nadir and mean SpO₂, with age-adjusted pediatric vs adult thresholds drawn from the clinical literature.
Honest scope: the doctor-visit export is a structured text summary copied to the clipboard — not a generated PDF. It's framed as "discuss with your physician," never as diagnosis.
Selected screens
All screens below use synthetic sample data.
(synthetic data — capture pending)
(synthetic data — capture pending)
(synthetic data — capture pending)