A personal, offline WHOOP companion app for macOS, iOS and the Apple Watch. It pairs directly with a WHOOP strap over Bluetooth, stores everything on-device in SQLite, and computes recovery, strain, HRV, and sleep locally — no cloud, no account, nothing leaves the device.
Not affiliated with WHOOP, Inc. "WHOOP" is used only to identify the hardware this interoperates with. Not a medical device — every metric is an approximation, not clinically validated.
You bought the strap. The biometric stream it produces is yours.
- Own your data. NOOP reads heart rate, R-R intervals, SpO₂, skin temperature, respiration, accelerometer/gravity, battery, and event data straight off the strap over Bluetooth and writes it to a local SQLite database.
- Account-free and local. NOOP never logs into a WHOOP account and never hits a WHOOP server.
- Bring your history. Import a WHOOP CSV export and/or an Apple Health
export.xmlonce, and it's permanently on the machine. - Transparent math. Recovery, strain, HRV, and sleep are recomputed on-device from documented, citable methods (Task Force 1996 HRV, Karvonen %HRR, Edwards/Banister TRIMP, Tanaka HRmax).
Everything lives behind a single sidebar (Strand/App/RootView.swift). Each row below is a real
screen in Strand/Screens/.
| Screen | What it does |
|---|---|
| Today | Home dashboard: recovery ring, a "today's synthesis" insight, stat tiles with 14-day sparklines, live strap battery % and HR trend, recent workouts, data-sources footer. |
| Readiness | An on-device "should you push today?" read synthesizing HRV vs baseline, resting-HR drift, sleeping respiratory-rate drift, training-load balance, and training monotony into one headline. |
| Live | Real-time view of the connected strap — heart rate and frame stream as they arrive (~1 Hz). |
| Breathe | HRV haptic breathing biofeedback: the strap paces your breath with buzzes and shows live HR + rolling RMSSD. Presets: Relax 4-6, Coherence 5.5, Box 4-4. |
| Intervals | Silent haptic HIIT timer — the strap buzzes every transition. |
| Explore | Interrogate any single metric over time. |
| Compare | Plot two metrics together / against each other over a shared timeline. |
| Insights | Behavioral and correlational insights derived from your own series, including Activity Cost. |
| Sleep | Sleep sessions with a hypnogram, stage breakdown, efficiency, resting HR, and HRV. |
| Trends | Long-range trends and a shareable one-page PDF report, rendered entirely on-device. |
| Workouts | Detected and manual exercise sessions with strain and heart-rate detail. |
| Health | Biometric overview (HR, HRV, SpO₂, skin temperature, respiratory rate). |
| Stress | Day-level stress / autonomic load visualization. |
| Mind | A daily mood check-in correlated against recovery, sleep and HRV. Non-clinical. |
| Apple Health | Browse and reconcile data imported from an Apple Health export. |
| Data Sources | One-tap import of a WHOOP CSV export, an Apple Health export, a workout file (GPX/TCX/FIT), or a nutrition CSV. |
| Notifications | Configure local notifications and thresholds. |
| Automations | Turn the strap's physical inputs and live biometrics into Mac actions (see below). |
| Coach | An optional AI Coach, off until a key is added (Anthropic, OpenAI, or any OpenAI-compatible endpoint including a local model). Sends only a short text summary, never raw streams. |
| Settings | Profile, preferences, step calibration, unit choices, an opt-in Experimental section. |
| Support | Contact + attribution to the reverse-engineering work this is built on. |
There is also a menu-bar extra (Strand/MenuBar/MenuBarContent.swift) with a glanceable live HR
readout, and a first-run onboarding wizard.
Strand/Screens/AutomationsView.swift + Strand/System/MacActions.swift:
- Double-tap → Mac action. Double-tap the strap to lock the Mac, buzz back to confirm, mark a moment, do nothing, or run any macOS Shortcut by name.
- Wear & presence. Lock the Mac (or run a Shortcut) the moment the strap leaves the wrist; run a Shortcut when it goes back on.
- Haptic coaching. HR-zone coaching and an experimental resting-stress nudge.
- Inactivity reminder. An optional gentle wrist buzz after sitting still too long — off by default.
- Smart alarm. Arms the strap's own firmware alarm to buzz at a set wake time.
| Platform | Status |
|---|---|
| macOS | Full app (Strand/, SwiftUI, macOS 13+). Pairs over BLE, offloads the strap's history, and scores recovery/strain/sleep on-device. |
| iOS | Build-from-source in Xcode (StrandiOS/), no App Store. Shares the cross-platform Swift packages, so scoring matches macOS. |
| watchOS | Companion app (NOOPWatch/, NOOPWatchComplications/) via HealthKit. |
| Strap | Status |
|---|---|
| WHOOP 4.0 | The tested, supported path. Live HR, recovery, strain, sleep, history offload — the full experience. |
| WHOOP 5.0 / MG | Live heart rate works. Deeper metrics (recovery, strain, sleep) are still being mapped; there's an opt-in Settings → Experimental toggle. |
A WHOOP strap holds an encrypted Bluetooth bond with only one device at a time, normally the official WHOOP app. You can't just scan for it — if the strap is still bonded elsewhere, pairing is refused ("Encryption is insufficient" / "bond refused" in the strap log). Live heart rate is the exception — it rides the standard Bluetooth heart-rate profile, so it streams without a bond, but the deeper features need one.
To pair:
- Close the official WHOOP app on the phone (or turn that phone's Bluetooth off).
- Put the strap in pairing mode — on a 5.0/MG, tap the band repeatedly until the LEDs flash blue.
- In NOOP: Live → choose "WHOOP 5.0 / MG" → Scan & Connect. Success looks like "CLIENT_HELLO acked — link established" in the strap log.
Only one device at a time. Don't leave the strap connected to a phone and a Mac at once — HR streams on all of them, but only one holds the real encrypted bond. If HR works but buzz, alarm, double-tap and history don't, that's the tell.
- Live heart rate shows the moment the strap connects.
- Strain and sleep appear after wearing it and syncing — the strap's last ~14 days offload automatically.
- Recovery needs a few nights to learn a personal baseline, then sharpens each night.
- In a hurry? Import a WHOOP export in Data Sources and history fills in about a minute.
The repository is split into platform-pure Swift packages plus the macOS and iOS app targets. All
packages declare both .iOS(.v16) and .macOS(.v13); framework-specific UI is guarded with #if canImport(UIKit) / #if canImport(AppKit).
Strand/ macOS SwiftUI app
StrandiOS/ iOS app (shares most of Strand/)
NOOPWatch/ watchOS companion app
Packages/
WhoopProtocol/ BLE frame parsing, CRC, command/event/packet decode
WhoopStore/ GRDB/SQLite persistence (migrations, streams, caches)
StrandAnalytics/ HRV / recovery / strain / sleep / correlation math
StrandImport/ WHOOP CSV + Apple Health importers
StrandDesign/ SwiftUI design system (palette, components, charts)
Tools/Backfill/ CLI tool for backfilling decoded data
Platform-pure (no CoreBluetooth import) so it runs in tests and CLI tools unchanged. It implements the on-wire frame format for both strap generations:
public enum DeviceFamily: String, Sendable, CaseIterable {
case whoop4 // CRC8 (poly 0x07) header check; service 61080001-…
case whoop5 // CRC16-Modbus header check, "puffin" packet types; service fd4b0001-…
}Decoding is schema-driven (Resources/whoop_protocol.json) and includes CRC8, CRC16-Modbus, and
zlib CRC-32 implementations, frame framing, value interpretation, and historical-stream
reassembly. The app layer (Strand/BLE/, Strand/Collect/) wraps these UUID strings in CBUUID
and handles bonding, offload, and live notifications.
Everything is stored on-device in SQLite via GRDB.swift.
The schema is a versioned migrator (Database.swift).
Pure, database-free analyzers, each grounded in published methods:
| File | Computes |
|---|---|
HRVAnalyzer.swift |
RMSSD + SDNN from R-R intervals (Task Force 1996), with range + Malik ectopic filtering. |
RecoveryScorer.swift |
A 0–100 recovery score: HRV-dominant z-score + logistic composite vs personal baselines. |
StrainScorer.swift |
A 0–21 logarithmic strain scale from %HRR (Karvonen) and Edwards/Banister TRIMP. |
SleepStager.swift |
Sleep/wake detection + approximate 4-class staging from cardiorespiratory + gravity features. |
CorrelationEngine.swift |
Pearson r, OLS regression, day-aligned and lagged correlations between two series. |
- WHOOP CSV export (
WhoopExportImporter.swift): tolerant parser forphysiological_cycles.csv,sleeps.csv,workouts.csv,journal_entries.csv, from a folder or.zip. - Apple Health export (
AppleHealthImporter.swift): a streaming SAX parser forexport.xml(which can exceed 1 GB). - Nutrition CSV — a tolerant importer for daily-nutrition exports from Cronometer and MacroFactor.
Palette, typography, motion, and reusable components/charts — no external UI dependencies.
Requirements: macOS 13+, Xcode 15+ (Swift 5.9), and a Mac with Bluetooth. To pair live, a WHOOP strap; to just explore, import a CSV / Apple Health export instead.
The Xcode project is generated from project.yml with
XcodeGen.
# 1. Clone
git clone <this-repo-url> NOOP
cd NOOP
# 2. (Re)generate the Xcode project from project.yml
brew install xcodegen # if you don't have it
xcodegen generate
# 3. Open and run
open Strand.xcodeproj
# Select the "Strand" scheme → Run (⌘R). The built app is named NOOP.Notes:
- Bundle id
com.noopapp.noop, product name NOOP, sandboxed with the Bluetooth and user-selected-files entitlements. - Swift Package Manager resolves the only third-party dependencies automatically: GRDB.swift (SQLite) and ZIPFoundation (export unzip).
- Run the tests from Xcode (the
StrandTeststarget + each package's test target), or per-package withswift testinsidePackages/<Name>/.
To build and run the iPhone app instead, see docs/BUILD.md for the NOOPiOS
scheme (needs a real device — BLE doesn't work in the simulator).
WHOOP strap ──BLE──▶ Strand/BLE + Strand/Collect ──▶ WhoopProtocol (decode)
│
WHOOP CSV ─┐ ▼
Apple Health ├─▶ StrandImport (parse) ──────────▶ WhoopStore (local SQLite)
Nutrition CSV┘ │
▼
StrandAnalytics (recovery/strain/
HRV/sleep, on-device)
│
▼
Strand (SwiftUI) + StrandDesign
Every arrow stays on the device.
Offline by design. No server, no telemetry, no account. Strap data, imports, and computed metrics live in a local SQLite database and never leave the device.
Built on community interoperability and protocol-documentation work. With thanks:
johnmiddleton12/my-whoop— the WHOOP 4.0 BLE protocol; theWhoopProtocolandWhoopStorepackages and the collection logic are adapted from this work.b-nnett/goose— the WHOOP 5.0 / MG BLE protocol documentation.groue/GRDB.swift— SQLite persistence.weichsel/ZIPFoundation— export unzipping.
Contains no WHOOP proprietary code, firmware, logos, or assets, and performs no DRM circumvention.
Full detail in ATTRIBUTION.md.
Independent, unofficial, non-commercial interoperability project. Not affiliated with, endorsed by, or connected to WHOOP, Inc. "WHOOP" is used nominatively, only to identify the hardware this interoperates with.
Not a medical device. Heart rate, HRV, recovery, strain, sleep stages, SpO₂, respiratory rate, and skin temperature are approximations computed from published methods — not clinically validated and not medical advice.
Provided as-is, with no warranty, for personal use. Use it at your own risk.
Source-available under the PolyForm Noncommercial License 1.0.0. The license covers
NOOP's own original code and docs. Protocol facts (frame layouts, command numbers, byte offsets)
are uncopyrightable; bundled dependencies keep their own licenses (GRDB.swift and ZIPFoundation
are MIT — see NOTICE).
docs/ARCHITECTURE.md— module map.docs/BUILD.md— build & run instructions (macOS + iOS).docs/PROTOCOL.md— the WHOOP BLE protocol.docs/ANALYTICS.md— the scoring math.docs/PRIVACY_SECURITY.md— privacy & security model.ATTRIBUTION.md— full credits and licensing notes.project.yml— XcodeGen project definition (source ofStrand.xcodeproj).