gatttool is a command-line utility from the BlueZ Bluetooth stack for interacting with BLE devices. It is the most reliable method for sending commands to the Whoop 4.0 device.
Why gatttool?
While Python libraries like pygatt and bleak are useful for scanning and receiving notifications, they struggle with writing commands to Whoop 4.0. Commands sent via Python:- Sometimes throw exceptions
- Sometimes silently fail
- Work inconsistently
gatttool provides reliable command execution, though it also works “randomly” - it’s still more consistent than Python alternatives.
Even gatttool doesn’t achieve 100% reliability with Whoop 4.0, but it’s the best available option for writing characteristics.
Installation
On Debian/Ubuntu:Basic Command Structure
-i hci0- Bluetooth adapter to use (usuallyhci0)-t random- Address type (Whoop uses random BLE addresses)-b XX:XX:XX:XX:XX:XX- Device MAC address--char-write- Perform a characteristic write-a 0x0010- Characteristic handle (0x0010 = CMD_TO_STRAP)-n <hex_data>- Hex data to write (no spaces, no0xprefix)
sudo is required for gatttool to access Bluetooth hardware.Common Commands
Set Alarm
Set an alarm to ring in 10 seconds:aa10005723- Header70- Packet counter4201- Alarm command flags126d6566- Unix timestamp (little-endian)00000000- Padding3d59d8fd- CRC-32 checksum
Enable Heart Rate Broadcasting
Disable Heart Rate Broadcasting
Start Activity Recording
DATA_FROM_STRAP (handle 0x0018) every second with heart rate and sensor data.
Stop Activity Recording
Enable Health Monitor
Disable Health Monitor
Trigger Data Retrieval
DATA_FROM_STRAP.
Reboot Device
Erase Device
Characteristic Handles
Whoop 4.0 uses handle0x0010 for the writable CMD_TO_STRAP characteristic:
| Handle | UUID | Name | Access | Purpose |
|---|---|---|---|---|
| 0x0010 | 61080002-8d6d-82b8-614a-1c8cb0f8dcc6 | CMD_TO_STRAP | Write | Send commands |
| 0x0012 | 61080003-8d6d-82b8-614a-1c8cb0f8dcc6 | CMD_FROM_STRAP | Notify | Command responses |
| 0x0015 | 61080004-8d6d-82b8-614a-1c8cb0f8dcc6 | EVENTS_FROM_STRAP | Notify | Events |
| 0x0018 | 61080005-8d6d-82b8-614a-1c8cb0f8dcc6 | DATA_FROM_STRAP | Notify | Sensor data |
0x0010.
Workflow: gatttool + Python Notifications
For the best reverse engineering workflow:Observe data stream
The Python script will receive notifications every second with heart rate and sensor data:
Interactive Mode
gatttool also supports an interactive mode for exploring devices:For sending Whoop commands, use
char-write-cmd (write without response) as most Whoop commands don’t expect acknowledgment.Troubleshooting
Command appears to succeed but nothing happens
Command appears to succeed but nothing happens
This is common with Whoop 4.0. The device sometimes ignores commands. Try:
- Re-sending the command (gatttool is “randomly” reliable)
- Verify the checksum is correct
- Check packet counter byte (though testing shows it’s not validated)
- Ensure device is not already in the requested state
connect error: Connection refused (111)
connect error: Connection refused (111)
- Check the device is powered on and in range
- Verify the MAC address is correct
- Ensure
-t randomis specified (Whoop uses random addresses) - Try scanning first:
sudo hcitool lescan
Permission denied
Permission denied
gatttool requires root access:
Device not discoverable after commands
Device not discoverable after commands
If heart rate broadcasting was disabled, the device won’t be discoverable until you re-enable it:
Packet Counter Mystery
Commands include a packet counter byte, but testing shows:- Sending commands with the same counter value works
- Counter values don’t need to be sequential
- The device doesn’t validate or enforce counter behavior
See Also
- Packet Structure - Full breakdown of packet structure
- Python Libraries - For scanning and notifications
- Wireshark - For capturing commands from the official app