Case study
ThetaMask: one system, from device to cloud

ThetaMask is a light-based mask built for research studies. The first version, a sleep mask, was used in a study at the Donders Institute (Radboud University). v2 is a new device, developed for research with TNO.
I am one of three co-founders. I lead software and firmware. For v2 I designed and built the whole technical platform: the firmware on the mask, the phone app, the backend, a portal for researchers and the tool used to create light programs. This page is about how those parts fit together, and why they are built the way they are.
The system at a glance
Six running parts and one contract.
- Mask firmware. Nordic nRF52 series, Zephyr on nRF Connect SDK. Verifies light programs, runs them, and keeps running when the phone goes away.
- Phone app. React Native with Expo. Talks to the mask over Bluetooth Low Energy, delivers programs, watches sessions and collects study questionnaires.
- Backend. Supabase Postgres and edge functions. Delivers each participant's study configuration and receives pseudonymous study data.
- Researcher portal. Next.js. Enrolment, consent records and data export.
- Program creator. A browser tool for composing light programs, with a live preview that runs the firmware's own rendering code compiled to WebAssembly. What you see in the preview is what the mask will do, because it is the same code.
- Study tooling. Signs light programs and publishes study configurations to the backend.
- The contract. One versioned specification of the program format and the Bluetooth protocol. The app and the firmware both build against it, and both test suites read the same byte-for-byte test vectors.
Why one contract. The app and the firmware are written in different languages and run on different devices, so nothing forces them to agree. The contract does: with one specification and shared test vectors, a mismatch fails a test on one side before it ever reaches a device. The app can also run against a simulated mask, so app work never waits on hardware.
Flow 1: a program's life
A light program goes from the creator, through signing and publishing, to the backend, to the phone, to the mask. Session data comes back the other way, and only the portal reads it out.
- Authoring cannot sign. The creator holds no key and cannot produce a runnable program. It can be shared widely without widening who can put a program on a mask.
- Unsafe programs never get a signature. Signing runs the same validator the app uses, with no bypass. A program outside the safety limits is refused before it is ever signed.
- Published means frozen. A published study configuration can never change; a change is a new version. The researcher portal cannot edit them at all.
- Offline first. Once a participant's study configuration is on the phone, a session never waits on the network, and the app will not swap configurations while a session is running.
- The phone checks, the mask decides. The app verifies every program before sending it, but that is a sanity check against corruption. The real boundary is the mask: it checks integrity, signature and safety limits itself, and a program that fails never loads.
- Sessions survive the phone. A locked phone or a dropped Bluetooth link does not stop a session. The mask keeps running on its own, and the app reconnects and picks up watching.
- Uploads wait for consent. Study data stays in a queue on the phone and nothing leaves it until the participant's consent is on record.
Flow 2: how changes reach devices
- One codebase, four builds. Four build variants come from the same code, each fixed at build time. There is no runtime switch, and anything unrecognised falls back to the most locked-down build, never a more open one.
- App updates and study content travel separately. App code can update over the air during development, but over-the-air updates are off in participant builds and switched off before a study starts, so the app cannot change under running participants. Study content (schedules, questionnaires, programs) only ever changes through the signed study configuration.
- Firmware updates. Over Bluetooth with automatic rollback if a new image is not confirmed, plus a USB-C recovery route for a mask that has lost Bluetooth. The bootloader checks the image signature whichever route it came by, and the mask refuses to update mid-session.
- Designed for a sealed device. The mask is encased after its first flash, and the debug port goes with it. Release builds lock debug access, and acceptance checks on a real board prove both update routes work before the batch is sealed. Choices made at first flash last for the life of the unit, so they are checked while the door is still open.
Flow 3: the trust chain
- Two keys, two jobs. Light programs and firmware images are signed with separate keys. Private keys never enter the code repository, a single small piece of tooling is the only code that handles the program signing key, and the phone app contains no signing code at all.
- One public key, three places, one test. The app, the publishing tools and the firmware each carry the public key. A test fails if the three ever disagree.
- A test key that cannot leak into production. Bench work uses a separate test key. A release firmware build that would trust it, or accept unsigned programs, does not compile.
- Enrolment without stored secrets. A researcher enrols a participant with a one-time QR code. The server stores only a hash, so a copy of the database holds no usable credentials. A lost code is reissued with an audit record, and the old one stops working.
- Researchers sign in with two-factor authentication and must also be on an allowlist.
Flow 4: the privacy split
- Pseudonymous by construction. The data the phone uploads has no field for a name, email or phone number. Device identity is cut down to the model, and the backend rejects hardware serial numbers.
- Identifying data lives on a separate side. It sits apart from the research data, is not reachable through the public API, and revealing an identity in the portal writes an audit record before anything is shown.
- The export path cannot reach names. Exports run through a read-only access path that has no rights on the identifying side, and a provisioning check asserts that. Exports are designed to contain only pseudonymous data.
- Minimal data on the devices. The mask logs sessions and nothing about the person. Withdrawing consent on the phone clears the local study data and the upload queue.
- Erasure with accountability. Erasing a participant removes their research data and keeps only the audit trail needed for accountability.
- The known gap. Some questions allow a typed answer, and a participant could type something identifying there. The structure cannot prevent that; study procedure has to.
Beyond the software
ThetaMask has three co-founders: Hauk leads hardware and Erlend leads research and study design. Leading software and firmware means owning more than code.
- Production. I handled communication with our manufacturer through v1's production run.
- Funding. I wrote grant applications and pitched for funding.
- Certification. I am researching the certification path for v2.
- Planning. I plan the technical roadmap and timeline.
Status
v2 is a pre-beta research device. ThetaMask is not a medical device and makes no medical claims.