Open-source BLE data reader for the Whoop 4.0 fitness band.
Connects to a Whoop 4.0 over Bluetooth Low Energy and reads real-time sensor data -- heart rate, R-R intervals, SpO2, skin temperature, and accelerometer readings. Built for researchers, tinkerers, and anyone who wants raw access to their own biometric data.
The Whoop 4.0 exposes a custom BLE GATT service (61080000-8d6d-82b8-614a-1c8cb0f8dcc6) with five characteristics for commands, responses, events, data streaming, and diagnostics. This tool sends command frames to the device and parses the 96-byte real-time data packets that come back.
Protocol knowledge comes from community reverse-engineering:
- jogolden/whoomp -- Python CLI + Web Bluetooth, discovered the GATT service/characteristic UUIDs
- bWanShiTong/reverse-engineering-whoop -- documented command/response codes
- jacc/whoop-re -- REST API reverse engineering
- Gadgetbridge methodology: HCI snoop log capture, Wireshark analysis, then implementation
| Field | Status | Bytes |
|---|---|---|
| Heart rate (BPM) | Decoded | 1-2 |
| R-R interval (ms) | Decoded | 3-4 |
| SpO2 (%) | Decoded | 5 |
| Skin temperature (C) | Decoded | 6 |
| Accelerometer X/Y/Z | Candidate (unconfirmed) | 7-12 |
| Motion intensity | Candidate (unconfirmed) | 13 |
| PPG amplitude | Candidate (unconfirmed) | 14-15 |
| Ambient light | Candidate (unconfirmed) | 16-17 |
| PPG signal quality | Candidate (unconfirmed) | 18-19 |
| Battery level | Decoded | (separate command) |
| Device info | Decoded | (separate command) |
Bytes 20-91 of the 96-byte real-time packet are not yet decoded. They likely contain additional sensor channels (gyroscope, respiration rate, PPG waveform samples). The codebase includes structured TODO comments at each byte position so contributors can fill them in.
Requires Python 3.10+ and a Bluetooth adapter that supports BLE.
# Clone the repo
git clone https://github.com/whoop-reader/whoop-reader.git
cd whoop-reader
# Install in development mode
pip install -e ".[dev]"- Linux: may require
bluezand running as root (or adding your user to thebluetoothgroup) - macOS: works out of the box with the built-in Bluetooth stack
- Windows: requires Windows 10+ with a compatible Bluetooth adapter
whoop-reader scan
whoop-reader scan --timeout 15whoop-reader battery --address AA:BB:CC:DD:EE:FFIf you omit --address, the tool scans and lets you pick from discovered devices.
whoop-reader info --address AA:BB:CC:DD:EE:FF# Stream to console
whoop-reader stream --address AA:BB:CC:DD:EE:FF
# Stream to CSV file
whoop-reader stream --address AA:BB:CC:DD:EE:FF --output session.csv
# Stream to JSON lines file
whoop-reader stream --address AA:BB:CC:DD:EE:FF --output session.jsonl --format jsonl
# Stop after 100 packets
whoop-reader stream --address AA:BB:CC:DD:EE:FF --count 100
# Verbose logging (shows raw BLE traffic)
whoop-reader -v stream --address AA:BB:CC:DD:EE:FFPress Ctrl+C to stop streaming. Data is flushed to disk after every packet, so nothing is lost if you interrupt.
import asyncio
from whoop_reader.ble import scan_for_whoops, WhoopConnection
async def main():
devices = await scan_for_whoops()
async with WhoopConnection(devices[0]) as whoop:
print(f"Battery: {await whoop.get_battery()}%")
async for packet in await whoop.stream_realtime():
print(f"HR: {packet.heart_rate_bpm} BPM")
if packet.heart_rate_bpm and packet.heart_rate_bpm > 180:
break
await whoop.stop_realtime()
asyncio.run(main())whoop-reader/
whoop_reader/
__init__.py -- Package metadata
protocol.py -- BLE UUIDs, command codes, CRC-32 implementation
parser.py -- 96-byte packet parser, response parsers
ble.py -- Connection management (scan, connect, notify, stream)
export.py -- CSV and JSON/JSONL export (batch and streaming)
cli.py -- Click CLI (scan, connect, info, battery, stream)
pyproject.toml -- Package configuration (hatchling)
LICENSE -- MIT
README.md
The biggest area for contribution is decoding the unknown bytes in the 96-byte real-time packet. Here is how to help:
- Capture an HCI snoop log while wearing the Whoop and performing known activities (resting, exercising, etc.)
- Open the log in Wireshark and filter for the data characteristic (
61080004-...) - Correlate byte values with the known activity (e.g., accelerometer values should change with movement)
- Open a PR with your findings, including the byte positions and your interpretation
The parser (whoop_reader/parser.py) contains TODO comments at each undecoded byte range to guide this work.
The Whoop 4.0 uses a CRC-32 variant with:
- Polynomial:
0x04C11DB7 - Initial value:
0xFFFFFFFF - Input/output reflection: yes
- Final XOR:
0xF43F44AC
[0xAA] [CMD] [LENGTH_LO] [LENGTH_HI] [PAYLOAD...] [CRC32_LE]
96 bytes, delivered as BLE notifications on 61080004-8d6d-82b8-614a-1c8cb0f8dcc6. The last 4 bytes are the CRC-32 of the preceding 92 bytes.
This project is an independent, community-driven interoperability effort conducted under the DMCA Section 1201(f) reverse engineering exemption for the purpose of achieving interoperability between the Whoop hardware and third-party software.
This project does not extract, distribute, or modify proprietary firmware, algorithms, or copyrighted content. It only observes and documents the BLE communication protocol.
This project is not affiliated with, endorsed by, or connected to Whoop, Inc. All trademarks are the property of their respective owners.
This software is provided as-is, without warranty. Use at your own risk. The authors are not responsible for any impact on your device's operation, warranty status, or Whoop subscription.
This tool only reads data from your own device. It does not circumvent any subscription paywall, modify firmware, or access Whoop's cloud services.
MIT -- see LICENSE.