-
Notifications
You must be signed in to change notification settings - Fork 649
How NOOP Works
NOOP is a fully offline companion app for WHOOP straps (4.0 and 5.0/MG). It pairs directly with your strap over Bluetooth, decodes biometric data on your device, stores everything locally in SQLite, and computes recovery, strain, HRV, and sleep metrics without ever touching the internet. This page explains the big picture and how your data flows.
Not affiliated with WHOOP. NOOP is an independent, interoperability project built on community reverse-engineering. It is not a medical device — all metrics are approximate. Use only with a device you own. See Disclaimer for the full legal notice.
NOOP is built around a simple flow: biometric data flows in from the strap (or from an import file), gets decoded into typed records, lands durably in a local SQLite database, is read back through a thin repository layer, is transformed into daily metrics by pure analytics functions, and finally renders in the UI. Nothing ever leaves your machine.
┌──────────────────────────────────────────────┐
WHOOP strap │ NOOP (entirely on your device) │
(4.0 or 5.0) │ │
│ BLE decode → SQLite → Analytics → UI │
┌──────────────┐ │ │
│ Bluetooth LE │───────┼──▶ Decode frame bytes │
│ (your data) │ BLE │ ├─ Extract heart rate, HRV │
└──────────────┘ │ ├─ Extract sleep, SpO₂, temp, respiration │
│ └─ Extract battery & device events │
│ │ │
│ ▼ │
──────────────────────┼─────► SQLite database │
CSV export │ (all data stored locally) │
or Apple Health │ │ │
│ ▼ │
│ Pure analytics math (on-device) │
│ ├─ Sleep staging from biometrics │
│ ├─ Recovery scoring vs your baseline │
│ ├─ Strain calculation from HR │
│ └─ HRV, RHR, zone analysis │
│ │ │
│ ▼ │
│ SwiftUI screens (macOS) │
│ Jetpack Compose (Android) │
│ │
└──────────────────────────────────────────────┘
All the work happens in five platform-agnostic Swift packages:
| Package | What it does |
|---|---|
| WhoopProtocol | Decodes BLE frames from the strap into typed fields. Handles CRC checksums, reassembles fragmented packets, and understands both WHOOP 4.0 and 5.0 protocols. |
| WhoopStore | Manages the SQLite database. Handles schema migrations, stores decoded streams (heart rate, RRV, SpO₂, temperature, etc.), and caches computed metrics. |
| StrandAnalytics | Pure functions for physiology math. Computes HRV (Task Force 1996), recovery scores, strain (Karvonen %HRR + TRIMP), sleep staging, and correlation analysis. |
| StrandImport | Imports your existing history. Parses WHOOP CSV exports and Apple Health exports so you can bring years of data into NOOP at once. |
| StrandDesign | The visual design system — colors, typography, charts (sparklines, heatmaps, hypnograms), and SwiftUI components. |
The macOS app target (Strand/) is the reference implementation. Android and iOS use the same five packages, so all core logic is truly cross-platform.
NOOP has two BLE routes, optimized for different purposes:
Every ~1 second, your strap broadcasts real-time heart rate and R-R intervals to NOOP. This arrives on both:
- The standard BLE Heart Rate Measurement characteristic (
0x2A37) — works even without bonding, carries a wall-clock timestamp. - The custom REALTIME_DATA frames — proprietary format, flows only after bonding, used mainly for UI responsiveness and events (double-tap, wrist on/off).
The live path is the most responsive but the least complete — the strap's live stream carries sparse samples. NOOP records standard HR/RR continuously and independently of everything else.
Why it matters: you see live HR ~1 Hz on the Live screen, haptic-coaching is real-time, and automations (like locking your Mac when you remove the strap) are instant.
Your strap stores ~14 days of rich biometric history: heart rate, R-R intervals, SpO₂, skin temperature, respiration, and accelerometer data. NOOP offloads this history the same way the official app does:
- On connect: the first sync happens automatically after ~1.5 seconds.
- Every 15 minutes (by default) while connected and bonded.
- On demand if you double-tap the strap.
The offload is a state machine:
send SEND_HISTORICAL_DATA
│
▼
HISTORY_START (open the chunk)
│
├─ HISTORICAL_DATA (type-47 record) … (repeat ~50 records per chunk)
│
├─ HISTORY_END (close the chunk and give trim cursor)
│ ├─ decode the chunk
│ ├─ write decoded data to database ← DURABLE
│ ├─ send ack with the trim cursor
│ └─ strap now forgets that chunk (only after ack)
│
└─ HISTORY_COMPLETE (offload finished)
Why it's robust: data is written to your database before the strap is told to forget it. If the connection drops mid-offload, the next session resumes exactly where it left off — no data is ever lost, no re-syncing needed.
Why historical is primary: it carries real unix timestamps and rich biometric fields. The live stream is mostly HR/RR. Recovery, strain, and sleep scoring are computed from the historical data.
Everything lives in a single SQLite file at:
macOS: ~/Library/Application Support/OpenWhoop/whoop.sqlite
The schema has four groups:
- One row per strap you've bonded (
devicetable).
-
hrSample— heart rate (BPM) at each timestamp. -
rrInterval— R-R intervals (beat-to-beat milliseconds) for HRV analysis. -
spo2Sample,skinTempSample,respSample,gravitySample— SpO₂, temperature, respiration, accelerometer from type-47 offload. -
event— strap events (battery level, wrist on/off, double-tap, device alarms). -
battery— battery state of charge and voltage.
All are durable — they are never deleted or pruned.
-
dailyMetric— one row per calendar day: recovery score, strain, sleep minutes (deep/REM/light), resting HR, average HRV, SpO₂%, temperature deviation, respiration rate, workout count. -
sleepSession— one row per sleep session: start/end times, efficiency, resting HR, HRV, and a hypnogram (the sleep-stage diagram). -
workout— detected exercise sessions with duration, energy, average/max HR, strain, and distance. -
journal— user-answered daily prompts (mood, stress, etc.). -
appleDaily— Apple Health daily aggregates (steps, calories, weight, VO₂max, etc.) if you imported them. -
metricSeries— a generic long-format table for any scalar metric, so you can query and compare metrics uniformly.
-
cursors— watermarks for resumable offload (so the next connect knows where the last one left off). -
rawBatch— optionally, the original BLE frame bytes in compressed form (only if you enable the research toggle). Pruned every 24 hours, but decoded streams are never touched.
- Scan for the strap's custom Bluetooth service.
- Connect and discover characteristics.
- NOOP sends a benign
GET_BATTERY_LEVELcommand over BLE with confirmation requested. - The strap's confirmation creates a bond (one-time pairing).
- After bonding, NOOP sends a handshake:
HELLO,SET_CLOCK,GET_CLOCK, then begins the first offload.
The 5.0 strap uses the same bonding concept but a different transport ("puffin" protocol). The pairing process is the same, but:
- The strap uses CRC16-Modbus instead of CRC8 for header integrity.
- There's a static
CLIENT_HELLOframe the app sends. - Packet types are slightly different.
NOOP's protocol layer handles both transparently — the rest of the code doesn't care which generation you're using.
Note: The strap holds a single encrypted bond at a time. If it's bonded to the official WHOOP app on your phone, NOOP can't get the full bond (you'll get "Encryption is insufficient" errors). To pair NOOP, close the official app, put the strap in pairing mode, and try again. See Strap Support and Pairing for detailed steps.
All metric math is pure functions — they never touch the database. Given a day's raw streams and your personal profile, they compute recovery, strain, sleep stages, and more.
Analyzes cardiorespiratory features (HRV, RR stability) and motion (gravity vector) to detect:
- In-bed vs awake (binary).
- Deep, REM, light, and awake stages (if you're wearing the strap at night).
Produces sleep efficiency, resting HR, average HRV, and a hypnogram showing when you transitioned between stages.
A composite metric based on:
- Nightly HRV vs your personal baseline (if HRV dropped, recovery is lower).
- Resting heart rate trend (if RHR crept up, recovery is lower).
- Sleep quality (a proxy from efficiency, deep/REM ratio, and disturbances).
After a few nights of data, NOOP learns your baseline and the score becomes more personalized.
Integrates the day's heart rate into a cardiovascular load score:
- Based on Karvonen %HRR (percentage of max HR you spend training).
- Weighted by Edwards TRIMP (the longer at elevated HR, the higher the strain).
- Capped at 21 to match WHOOP's scale.
Sedentary days score near 0; hard training days score higher.
- HRV (RMSSD) — computed from R-R intervals using Task Force 1996 standard, filtered for ectopic beats.
- Resting HR — the lowest HR during sleep.
- VO₂ max (if imported from Apple Health).
- Heart-rate zones — custom (by age/max HR) or from your profile.
- Correlations — the app can correlate any two metrics (e.g., "is my recovery lower on high-stress days?").
Already have WHOOP data or Apple Health exports? NOOP can import them once, and they're merged into the same database as your live strap data.
If you've downloaded your data from the official app as a .zip containing:
physiological_cycles.csvsleeps.csvworkouts.csvjournal_entries.csv
NOOP parses each file and loads it into the database. The same import handles WHOOP 4, 5, and MG exports.
From the Health app on your phone, export your data as export.xml (can be >1 GB). NOOP streams the parsing (so it doesn't blow up memory) and imports:
- Daily summaries: steps, active energy, basal energy, heart rate, VO₂max, weight.
- Workouts with duration and energy.
- Sleep records.
Both import paths converge on the same WhoopStore database, so your imported history lights up instantly on all screens — no waiting for live sync.
NOOP's core logic lives in five Swift packages that declare both iOS 16+ and macOS 13+, with all UI-framework code behind #if canImport(UIKit) / #if canImport(AppKit) guards.
| Platform | Status | Details |
|---|---|---|
| macOS | ✅ Full | Reference implementation. SwiftUI, all features. |
| Android | ✅ Full | Native Kotlin port. Jetpack Compose, all features. Grab the APK from Releases. |
| iOS | 🧪 Experimental | Community port in PR #42. Builds from source only (no App Store — Apple requires a real developer identity, incompatible with this project's anonymity). |
All three platforms:
- Pair over BLE with the same protocol logic.
- Decode frames with the same
WhoopProtocolpackage. - Store data in the same SQLite schema via
WhoopStore. - Compute metrics with the same
StrandAnalyticsfunctions.
The only difference is the UI layer.
Beyond reading metrics, NOOP can automate things on your device — all running locally:
- HR-zone haptic feedback: buzz when you enter/leave a heart-rate zone during exercise.
- HRV breathing guide (Breathe mode): pace your breathing with haptic cues and show live HR + HRV as you relax.
- Double-tap actions: lock your Mac, dismiss notifications, mark a moment, or run a Shortcut by name.
- Wrist wear/remove: lock when you take off the strap; run a Shortcut when you put it back on.
- Smart alarm: schedule a firmware alarm that fires even if the Mac is asleep.
- Stress nudges: the strap buzzes if autonomic load is creeping up.
- HIIT timer: the strap buzzes every transition (work → rest, countdown ticks, finish), no screen needed.
All of these are optional, default-off, and compute entirely on-device using your live biometric stream.
NOOP uses a careful concurrency model to avoid data corruption:
-
WhoopStoreis anactor— all database writes run serially on its own executor (off the main thread), so GRDB's blocking calls don't freeze the UI. - BLE, UI state, and orchestration run on the main thread — CoreBluetooth callbacks arrive there anyway.
-
The historical offload drains frames one-by-one — so
HISTORY_START→ records →HISTORY_ENDcan never be reordered mid-chunk, and trim cursors are always safe.
SQLite settings:
- WAL (write-ahead logging) — two simultaneous readers (UI + collector) never deadlock.
- 5-second busy timeout — if write contention happens, readers wait rather than fail immediately.
- 16 MB page cache — faster multi-thousand-row batch writes during offload and import.
Result: smooth UI, durable data, no crashes from concurrent access.
NOOP is resumable by design:
- Live path: live HR/RR notifications stop, but re-connect simply resumes.
-
Historical offload: the
strap_trimcursor is durably stored. The next offload picks up where the last left off — no data is re-synced or lost. - Import: runs to completion once; re-import is safe because the database dedupes by natural key.
| Feature | Latency | Notes |
|---|---|---|
| Live HR/RR | ~1 sec | real-time BLE notifications |
| Double-tap response | ~1 sec | instant |
| Recovery / strain / sleep | ~1–2 min after offload | computed from type-47 history |
| Import full year | ~1–2 min | streamed, doesn't block UI |
| Metric correlations | ~few sec | computed on-demand from database |
The app is responsive; heavy operations (big imports, correlation analysis) run off the main thread.
Every number NOOP computes comes from documented, published methods:
- HRV: Task Force 1996 standard (RMSSD, SDNN).
- Recovery: published HRV/RHR baselines + logistic composite (cited in-code).
- Strain: Karvonen %HRR (age-based max HR) + Edwards TRIMP.
- Sleep staging: cardiorespiratory + motion heuristics (approximate, not validated).
Crucially:
- NOOP is not a medical device — all metrics are approximations for personal insight, not diagnosis.
-
The source code is open — inspect the
StrandAnalyticspackage to see exactly how every number is calculated. - No proprietary black box — you're not trusting a neural network trained on unknown data; you're trusting documented math you can verify.
- Strap Support and Pairing — detailed steps for pairing 4.0 and 5.0.
- Protocol — the full BLE reverse-engineering reference (frame format, commands, offload state machine).
- Privacy and Security — data privacy, optional AI Coach, and security considerations.
- Features — full list of screens and automations.
- Contributing — how to help with reverse-engineering, Android porting, or other work.
NOOP is an independent, unofficial, non-commercial interoperability project — not affiliated with, endorsed by, or sponsored by WHOOP, Inc. "WHOOP" is a trademark of WHOOP, Inc., used nominatively. Works only with a device you own; not a medical device; every metric is an approximation, not medical advice. · Privacy and Security · Donations · Releases
Get started
Tutorials
- Tracking a Workout
- Recovery, Strain & Readiness
- Automations
- Breathe & Intervals
- Importing History
- AI Coach
- Widget & Notifications
- Reading Your Sleep
- Explore & Compare
Reference
Project