Thanks for looking. This is a small project and PRs genuinely get read.
This is the one thing worth getting right before you start, because it decides where the code lives. OpenStrap is three packages and the split is strict:
| Your change | Repo |
|---|---|
| A new record type, opcode, event, or anything about the bytes on the wire | protocol |
| A new metric, or a change to how an existing number is computed | analytics |
| Bluetooth reliability, storage, background sync, UI, anything app-shaped | edge (here) |
If you're not sure, open an issue first and ask — it's cheaper than moving code between repos afterwards.
Never fabricate a number. If an input isn't there, the metric returns
null, not a plausible-looking guess. Every metric carries a confidence and a
tier (AUTH / HIGH / ESTIMATE / RELATIVE), and those need to stay honest.
A metric that quietly invents a value when the data is missing is worse than no
metric.
Cite the method. Anything in analytics implements a published, peer-reviewed
algorithm, and the citation goes in a comment next to the code. If nothing in
the literature fits what you want to do, that's fine — mark it ESTIMATE, give
it low confidence, and say so. Don't invent constants and present them as
science.
Some ceilings are real, not bugs. HRV here is PRV derived from 1 Hz beat timing. Deep sleep is a low-confidence HR-flatness overlay. SpO2 and skin temperature are relative ADC values, never absolute. These are properties of what the band actually hands over. Please don't "fix" them by making the output look more confident than the input justifies.
Bump kAlgoVersion (in lib/compute/derivation_engine.dart) whenever a
change alters any analytics output. Stored day results are versioned and
immutable; without a bump, devices keep serving stale values. If your bump's
changelog entry cites an analytics change, check that the pinned analytics SHA
in pubspec.yaml actually contains it — that mismatch has shipped a bug before.
Put logic in the pure policy classes. Bluetooth decisions belong in
lib/ble/ble_state.dart or lib/sync/sync_policy.dart as small, testable
classes; the engine wires them together and the policies decide. That's what
makes any of this testable without a band on your wrist.
git clone https://github.com/OpenStrap/edge.git
cd edge
cp .env.example .env
flutter pub get
flutter run --dart-define-from-file=.envQuit the official WHOOP app before pairing — Bluetooth only lets one app own the band at a time.
For local work across all three packages at once, create a pubspec_overrides.yaml
(it's gitignored) pointing at your sibling checkouts:
dependency_overrides:
openstrap_protocol:
path: ../protocol
openstrap_analytics:
path: ../analyticsflutter analyze
flutter test --concurrency=1--concurrency=1 matters: the suite runs against real database files and
parallel workers race on them.
A few tests replay whoop_hist.jsonl, a real band capture kept beside the
repo rather than committed to it. If you don't have it those tests skip
automatically — that's expected, and CI runs the same way. Everything else runs
everywhere.
CI runs flutter analyze and the full suite on every PR. Please make sure both
are green locally first.
- Branch off
main. One logical change per PR. - Explain why, not just what. If it fixes an issue, link it.
- If it changes anything a user sees, say what it looked like before and after.
- Protocol changes: say how you verified it. "Decoded N real records off my own band and the values were plausible" is a perfectly good answer, and honestly more useful than a unit test alone.
- No
Co-Authored-Bytrailers.
The app's strings live in lib/l10n/app_en.arb (the source of truth — only a
handful of strings are wired up so far, more get pulled in over time). To add
a language: copy app_en.arb to app_<code>.arb (e.g. app_es.arb) in the
same folder, translate the values, and keep every key identical to the
English file. Run flutter gen-l10n to regenerate, then add your language's
display name to _kLanguageNames in lib/ui2/profile/profile.dart so it
shows up in the picker. Open a PR.
If you've worked out a field, an opcode, or an event we don't decode yet, that's one of the most valuable things you can contribute. Open an issue in protocol with the raw bytes, what you think the field is, and how you convinced yourself. A lot of the current event table is empirical guesswork by one person — more eyes genuinely helps.
This project talks to hardware people already own, using their own data, on their own device. Please keep contributions within that: no scraping WHOOP's services, no redistributing their code, firmware, or assets, and no vendored material from other reverse-engineering projects whose licences don't permit it. Facts about a protocol are fine. Someone else's source code is not.