← Matt Giallourakis

A home sleep-study app for a Bluetooth wearable

A Flutter → React Native rewrite that needed a C library inside Expo, a reverse-engineered BLE protocol, and a clinical data format


Built at Haneke Design for a sleep-health client, as a rewrite of their existing app. Three of the hard parts had nothing to do with UI, and each was new to me when I started.

The problem

A home sleep study: a patient pairs a Bluetooth wearable, wears it overnight — for several nights — and the app has to capture the physiological signals, package them in the format sleep clinicians' tools actually read, and submit the study. The client's existing app was Flutter and needed to become React Native; the wearable's BLE protocol had no usable documentation; and the clinical file format is written by a C library that has no JavaScript equivalent. I rewrote the app in React Native / Expo in about seven weeks and carried it through user-acceptance testing and post-UAT fixes.

A C library inside Expo, via Nitro Modules

The study output is EDF+ — the European Data Format, the standard for polysomnography — and the reference implementation is EDFlib, in C. Rather than port it, I compiled it into the app: a local Nitro Modules package wraps EDFlib as a hybrid object with generated Swift-C++ and JNI bridges, so JavaScript gets a typed EDF+ writer — open a file, declare each signal's label, sample rate, and calibrated physical/digital range, stream samples, close — backed by the real library on both platforms. I proved the approach in a standalone example first — Nitro Math, my own code, public on GitHub — then dropped it in.

Reverse-engineering the BLE protocol

With no spec to work from, the device's protocol had to be recovered at the byte level against the live hardware: which command opcodes it answers (real-time data, device info, set-parameter), how packets are framed, and the checksum it expects — a CRC-8 with its lookup table reconstructed from captured traffic. I built a separate testbench app as the harness for that work, so protocol experiments never touched the patient-facing app, and the resulting device integration is a clean connected-device abstraction the rest of the app talks to.

Acquisition that survives the operating system

Overnight recording is the part mobile platforms fight hardest. The app keeps a multi-night session alive with background-fetch tasks and, on Android, a foreground service, persists its state so an interruption doesn't lose a night, and gives the patient troubleshooting and per-night results flows so a bad night is visible rather than silent.

Learning the data

Writing valid study files meant learning the domain, not just the format: what each channel represents, what sample rates and calibrated ranges are physiologically sensible, and what a clinician's software will reject. It's the part of the project I'd least have predicted enjoying, and it's what made the C-library and protocol work matter — the output had to be something a sleep lab could actually read.