OpenStrap/research
GitHub: OpenStrap/research
WHOOP 4.0 手环的 BLE 协议逆向工程文档与单文件 Python 参考客户端,让订阅过期后的手环重新可用。
Stars: 1 | Forks: 2
# OpenStrap 研究
这里记录了协议的详细信息,你也可以在这里通过终端,直接通过蓝牙与 WHOOP 4.0 手环进行通信。你可以扫描设备、建立连接、导出历史记录、实时流式传输心率和运动数据、解码每一条记录;甚至无需插入手环,直接回放你之前保存的抓包数据。只需一个 Python 文件就能完成所有这些操作,外加一份详细映射了每个字节的文档。
如果说 OpenStrap 的其他部分是正式的产品,那么这里就是实验笔记。如今在生产环境中运行的解码器,最初都是在这里研究出来的。
## 诚实的初衷
WHOOP 手环是一款真正出色的硬件。当订阅服务停止时,手环本身并没有损坏,它只是“变暗”了,因为唯一能够读取它的 App 停止了与它的通信。于是,一个完好的传感器就这样变成了抽屉里的镇纸。这个项目的存在,就是为了让那个手环能够继续发挥作用。
它能替代 WHOOP 吗?老实说,不能。可能永远也无法替代。他们拥有多年的研究成果和一整家公司来支撑那些分数计算;而我只有逆向工程得出的协议和教科书上的公式。这个项目能给你的,是让那些原本会变成电子垃圾的硬件重获新生,让你能够随意处理从自己手环获取的原始数据。如果你正在为 WHOOP 付费并且对其感到满意,请继续付费,这个项目无意取代它。这个项目是专门为那些订阅服务已经到期的手环准备的。
## 开始前的郑重警告
**一旦你开始在手环上使用此项目,请停止同时打开官方 WHOOP App。**
如果手环重新连接到 WHOOP,它可能会拉取固件更新,而这极有可能导致本项目所依赖的事件和记录发生偏移,或者无法像现在这样正常工作。目前所有的测试都是在 WHOOP 4.0 当前的固件版本上进行的。我无法保证它能在未经测试的更新中存活下来。请做出选择。
此外:**这里的所有内容都只在 WHOOP 4.0 上测试过,没有测试过其他设备。** 如果你使用的是 3.0 或 5.0 版本,某些功能可能会生效,但有些肯定行不通,我很乐意听取你的反馈。
## 事件解析是一项集体合作
关于事件代码和经验性字段,情况是这样的:它们中的很多都是基于经验的推测。我观察手环的运作,观察某个字节的变化,然后记录下我认为它代表的含义。比如佩戴、摘下、充电、双击,这些我非常有把握,因为我可以随时触发它们并看着它们响应。但其余的大量内容,都是通过模式匹配和满怀希望的分析得出的。
没有任何单个人能 100% 确认这些东西。这需要一群人,使用不同的手环、有着不同的使用习惯,各自独立地注意到同一个现象,推测才能最终成为事实。因此,如果你运行了这些工具并发现了不一致的地方,或者破解了我弄错的事件,请大声说出来。只要有足够多的人在足够长的时间里不断探索,我真心认为我们最终能够满怀信心地锁定每一个事件。我们目前还没达到那个水平。我们要么共同努力实现这一目标,要么永远也无法达成。
## 命令行
```
python3 research_playground.py [--address ... --duration ... --quiet]
```
无需安装任何东西,也不需要手环:
| 命令 | 作用 |
|---------|--------------|
| `selftest` | 自检帧封装层:包括 CRC、帧往返、ACK 字节以及重组器。请首先运行此命令。 |
| `decode HEX` | 解码你粘贴进来的单个帧,输出 JSON。 |
| `replay PATH` | 在没有硬件的情况下,重新解码已保存的抓包数据或 hex dump。 |
| `catalog` | 打印出它知晓的所有命令、事件和记录类型,并标记出具有危险性的操作。 |
如果已安装 `bleak` 且附近有手环:
使用 `scan` 查找设备,使用 `info` 读取其身份和电量信息,使用 `monitor` 查看实时事件,使用 `sync` 正确地导出历史 flash 数据,使用 `live` 流式传输 HR 和运动数据(仅在你确定需要时才添加 `--force-optical`)。此外还有 `battery`、`clock` / `set-clock`、`rename`、`haptic`、`alarm`,以及当光学 LED 卡住时可以使用的 `off` / `reboot`。
## 手环的实际通信方式
完整的映射表位于 `PROTOCOL.md` 中。简短版本如下:
每条消息都被封装在一个帧中:一个 `0xAA` 起始字节,一个两字节的长度,一个仅覆盖这些长度字节的 CRC-8,填充至四字节倍数的实际 payload,以及覆盖该填充 payload 的 CRC-32。有一个值得注意的陷阱:重组器是根据声明的长度进行识别的,而不是根据 `0xAA` 字节。传感器 payload 中充满了 `0xAA` 字节,而 Bluetooth 的分包恰好可能落在这些字节上,因此任何“根据 `0xAA` 重新同步”的操作都会破坏你的记录。必须基于长度,没有其他选择。
帧内部的结构为 `[packet type][sequence][opcode or event id][body]`。你会看到的 packet 类型有:`0x23` 代表你发送的 command,`0x24` 代表回复,`0x28` 和 `0x2B` 代表实时数据,`0x2F` 代表同步期间的历史记录,`0x30` 代表事件,`0x31` 代表同步标记。
同步握手是最繁琐的部分。你发送一个包含五个 packet 的开场白,以“发送你的历史记录”结束,然后手环就会向你发送海量的记录和标记。每当它遇到 `HistoryEnd` 标记时,它会在 `inner[13:21]` 处包含一个 8 字节的 token,你必须将该 token 原样回传。哪怕出现一点偏差,手环都会开心地永远重复发送同一批数据,这就是“土拨鼠之日”bug。正确回传后,游标才会前进。当它发送 `HistoryComplete` 时,同步就会停止,而且在此过程中它永远不会擦除自己的 flash,因此你可以随心所欲地对同一个手环进行无数次导出。
`research_playground.py` 中的代码是按照文档的顺序排列的:首先是帧封装(`build_frame`、`parse_frame`、`FrameReassembler`),然后是 command(`build_command`、`build_batch_ack`),接着是解码器(`parse_r24` 及其相关函数),最后是将这一切串联起来的异步状态机 `WhoopClient`。
## 我的把握与推测
**有把握(已在真实手环上验证):** Bluetooth 服务及其特征值、帧封装、packet 类型、位于字节 `[17]` 的心率(与实时流相比只有一两次心跳的误差)、整个同步握手及那个 8 字节的 token、明显的事件(佩戴、充电、双击、绑定)、基础 command 以及实时 HR/PPG/IMU 流。
**经验性推断(已提取特征但未经证实):** header 之后的大部分 1 Hz 记录。位于 `[36:48]` 的加速度计(静止时约为 1g)、`[70]` 附近的温度通道(佩戴时会升高)、`[72]` 的 SpO₂(静止时在 90 多左右)、位于 `[88]` 的静息 HR。这些数据随着我能验证的因素而发生正确的趋势变化,这令人鼓舞,但是手环是将它们作为原始数据传送到云端的,因此我没有可供核对的真实基准。
**完全未知:** 剩余的未知字节、任何类似于 HRV 或心跳间期的内容(我还没有在手环暴露的数据中找到它),以及 WHOOP 自身的恢复率、劳损和睡眠评分,这些都是在他们的云端计算并永远不会通过链路下发的。
## 不要让你的手环变砖
手环具备擦除 flash、重启设备或卡死光学 LED 的 command。客户端从不发送擦除 command,默认情况下将光学功能保持为由手腕触发,将重启视为一项有意为之的手动恢复步骤,并将具有危险性的 opcode 置于保护措施之后,以防止意外触发。如果 LED 确实卡住了,依次使用 `off` 和 `reboot` 通常就能解决。`PROTOCOL.md` 列出了这些危险操作。
## 使用说明
仅供个人使用您自己的手环,且非商业用途。这不是医疗设备,不进行任何诊断,且不提供任何担保。采用 MIT 许可证。
标签:Python, 云资产清单, 可穿戴设备, 无后门, 物联网, 生物传感器数据, 蓝牙低功耗 (BLE), 逆向工具, 逆向工程