wylandplex/zappctl
GitHub: wylandplex/zappctl
SuuntoPlus 手表应用的 Linux 开发客户端,通过 Bluetooth LE 实现应用的构建、安装和管理,无需数据线或厂商桌面软件。
Stars: 0 | Forks: 0
# zappctl
**一款用于 SuuntoPlus 手表应用的 Linux 开发客户端 —— 通过 Bluetooth LE 进行构建、安装、列出和拉取,无需数据线,也无需厂商的桌面软件。**
Suunto 官方的 SuuntoPlus 工具链通过一个打包的二进制文件从 VS Code 刷入应用
(`SDSApplicationServer`),该文件**仅限 Windows 和 macOS**。在 Linux 上,你可以*构建*
`.fea` 应用,但无法*安装*它。`zappctl` 弥补了这一空白:它直接与手表原生的
Bluetooth “whiteboard” 协议进行通信,因此“编辑 → 构建 → 安装 → 验证”的闭环可以完全
在 Linux 上运行。
```
$ ./bledeploy.sh my-app
[bledeploy] app=my-app appID=myapp01 variant=q
[bledeploy] building…
Build successful
[bledeploy] built myapp01-q.fea (53200 bytes)
[bledeploy] deploying over Bluetooth…
[scan] found via service UUID: Suunto Vertical 2 ABCD @ 7F:2C:19:A4:0B:63 (RSSI -54)
[connect] connected, MTU=127 (BLE chunks 124B)
[plan] 53,200 bytes -> 266 frames (139 init + 1 name + 118 stream + 8 finish)
[send] 260/266 crc=ok
[done] 266 frames sent, install CONFIRMED (status 200). App 'myapp01' is on the watch.
```
## 状态
功能正常,且每天用于真实的应用开发 —— 但它是一款逆向工程工具,测试覆盖面较窄,并非正式商业产品。
| 功能 | 状态 |
|---|---|
| 安装 `.fea` 应用 | ✅ 正常工作,通过状态码确认安装 |
| 列出目录 | ✅ 正常工作 |
| 下载文件(包括固件 syslog) | ✅ 正常工作 |
| 删除应用 | ❌ 未实现 —— 需在手表上移除 |
| 锻炼记录、设置、路线、固件更新 | ❌ 超出范围(属于主机端,不走此通道) |
**测试环境:** Suunto Vertical 2 (NG3) 和 Suunto 9 Peak Pro (NG1),运行在 Fedora 和 BlueZ 5.87 环境下。
其他 SuuntoPlus 手表(Race、Race S、Race 2、Ocean、Vertical)使用相同的协议系列,且
极大概率可以正常工作,但尚未经过测试 —— 欢迎反馈。请参阅 [docs/VARIANTS.md](docs/VARIANTS.md)
以确定要构建的显示变体。
## 依赖要求
- **装有 BlueZ 的 Linux**(在 5.87 版本上开发)以及可正常工作的 Bluetooth LE 适配器
- **Python 3.11+**(需要 `tomllib`)和 [`bleak`](https://github.com/hbldh/bleak)
- **仅 `bledeploy.sh` 需要:** Node.js 和 Suunto 的 SuuntoPlus Editor 扩展,它提供了
`build-app.js` 编译器。如果你已有构建好的 `.fea`,则不需要此项。
## 安装
```
git clone https://github.com/wylandplex/zappctl
cd zappctl
python3 -m venv .venv && ./.venv/bin/pip install -r requirements.txt
```
可选择创建 `watches.toml`(参见 `watches.example.toml`)—— 只有在同时
有多块兼容手表在通信范围内时才有用。**单块手表无需任何配置。**
## 首次运行
**1. 对手表进行一次配对。** BlueZ 需要与其配对一次;之后配对记录会一直保留。
```
./pair-watch.sh scan # find the watch and its address
./pair-watch.sh init # open a pairing session
./pair-watch.sh go # start pairing — a 6-digit code pops up on the watch
./pair-watch.sh 123456 # feed the code back
./pair-watch.sh done # ALWAYS finish with this, or the adapter scans forever
```
没有需要寻找的“配对界面”:只需打开手表的 **Connectivity(连接)** 菜单并保持
屏幕常亮。完整的操作步骤和失败模式详见 [docs/PAIRING.md](docs/PAIRING.md)。
**2. 释放 Bluetooth 连接。** 手表同一时间只接受**一个**中心设备。在整个
操作过程中关闭手机的 Bluetooth(或强制停止 Suunto app)—— 否则
这里的每项操作都会连接失败。
**3. 在进行任何写入操作前先验证通道。**
```
./.venv/bin/python watchfs.py list b:/zapp
```
出现已安装应用列表意味着协议、配对和连接均已正常。
**4. 安装应用。**
```
./bledeploy.sh path/to/my-app # build + install
./.venv/bin/python deploy.py my-app-q.fea # install a prebuilt .fea
```
## 命令
```
# 一步完成 build + install(需要 vendor build-app.js)
./bledeploy.sh [--variant q] [--appid ] [--watch ] [--dry-run]
# 安装预编译的 .fea
python deploy.py [--watch ] [--dry-run]
# filesystem
python watchfs.py list b:/zapp # installed apps
python watchfs.py list b:syslogs # firmware logs
python watchfs.py pull b:syslogs/systemevent_1297017.log --out fresh.log
# diagnostics(安全的 read-only)
python probe.py --selftest-only # offline codec check, no watch needed
python probe.py --no-write # GATT/handshake sanity check
python probe_paths.py # which path spellings this watch accepts
python probe_dump.py # dump every reply frame
python probe_ngfs.py # read the device-assigned filesystem handle
# protocol RE(请先阅读 docs/SAFETY.md — 部分 resources 会使 watch 重启)
python wbexplore.py register "/Device/SystemEvent"
python wbinvoke.py --selftest
```
每个工具都采用相同的手表选择 flag:`--watch `(来自 `watches.toml`)、
`--needle `、`--mac `。这些参数都是可选的。
## 验证安装是否真正生效
**安装提交时返回 200 状态码并不能证明应用已在手表上。** 这让我们吃过
大亏:路径拼写错误会导致上传被丢弃在手表忽略的位置,而安装
步骤依然会返回 200。务必通过以下方式进行确认:
```
./.venv/bin/python watchfs.py list b:/zapp # look for .fea
```
## 工作原理
手表暴露了一个 GATT 服务,承载着带有帧结构的请求/响应协议(Suunto 的
“whiteboard”)。`zappctl` 会建立会话,将 `.fea` 流式传输到
手表文件系统上的 `/zapp/.zip`,然后通过 `/Plugin/Install` 提交。
有两个细节曾耗费了我们数天的调试时间,在你扩展此功能之前值得了解:
- **安装引用由设备分配。** 注册 `/Plugin/Install` 会返回一个 handle,
后续的帧必须寻址到该 handle。重放录制的值只能在其被录制的
那块手表上生效,在其他任何地方都会返回 400 报错。
- **驱动器前缀因代际而异。** NG3 需要 `b:/zapp`;NG1 需要 `/zapp`,并且对于
另一种写法,它会返回格式正确但*内容为空*的 `{}`。
完整的帧语法、能力映射以及开放的 RE 目标详见:[docs/PROTOCOL.md](docs/PROTOCOL.md)。
## 故障排除
连接问题几乎总是由以下四种情况之一引起:手机仍占用连接、
打开的桌面 Bluetooth 面板正在扫描、手表已休眠,
或者是代码在寻找一个地址而不是服务 UUID。错误码特征及修复方案详见:[docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md)。
## 致谢
该协议源自 *~eppuh* 开发的 **[SyncFix](https://git.sr.ht/~eppuh/SyncFix)**,基于
0BSD 许可发布。该项目完成了艰苦的逆向工程工作;`zappctl` 将其 Android
实现移植到了 Python,并扩展了文件系统访问、多代际支持、状态
解码以及设备分配的 handle 解析功能。我们在 [`vendor/SyncFix`](vendor/)
中保留了一份带有来源说明的固定副本 —— 表示感谢。
## 许可证
[0BSD](LICENSE),与上游协议工作保持一致:可用于任何目的,无需署名。
Suunto、SuuntoPlus 和 Amer Sports 是其各自所有者的商标,此处使用仅用于
说明本工具的兼容对象。
标签:MITM代理, SOC Prime, 云资产清单, 可穿戴设备, 开发工具, 物联网开发, 移动端开发, 蓝牙低功耗, 逆向工具, 逆向工程