panghy/cm2-probe
GitHub: panghy/cm2-probe
通过厂商 HID 通道对 Work Louder Creator Micro 2 键盘进行只读探测与灯效控制的非官方 Rust CLI 工具。
Stars: 0 | Forks: 0
# cm2-probe
一个非官方的、只读安全的 Rust CLI,用于通过其厂商 HID 通道探测和控制 [Work Louder](https://worklouder.cc/) 的 **Creator Micro 2**(VID `0x303A`,PID `0x8298`)——这是一个位于 HID report ID `6` 上的 64 字节 JSON-RPC 传输通道。它可以枚举设备、查询固件/状态、读取(绝不写入)设备上的文件系统、流式传输输入事件和固件日志,以及通过 USB 和 Bluetooth 驱动 RGB 灯效。
这里记录的所有信息都是通过对厂商通道的净室观察(基于同系列的 [Codex Micro 协议文档](https://github.com/boopdotpng/work-louder-oai/blob/main/docs/PROTOCOL.md))发现的,并且已在固件 **v0.6.0** 和 Work Louder Input 应用 **0.18.0-rc.8**(截至 2026-08-01)上进行了实时验证。
## 快速开始
```
cargo build
cargo run -- enumerate # find the device
cargo run -- version # query firmware version
cargo run -- status # battery, charging, profile, layer
cargo run -- led zones --backlight ff0000 --underglow 0000ff
```
macOS 注意事项:
- 厂商 HID page(usage page `0xFF00`,usage `0x0001`)**无需特殊权限** —— 所有 RPC 和灯光命令都可以直接运行。
- 使用 `listen` 捕获启动键盘报告(report ID `1`)可能需要授予终端 **输入监控** 权限。
- hidapi 构建时使用了 `macos-shared-device` 特性;如果不以共享(非独占)方式打开,macOS 会拒绝该设备并报错 `0xE00002E2`。
支持 **USB 和 Bluetooth**:厂商通道在两种传输方式上均有暴露(USB 上有 6 个 HID usage 对,BT 上有 4 个,两种方式均存在厂商对),具有相似的延迟,并且在 BT 上能正确进行多分片重组。
## 命令
| 命令 | 描述 |
| --- | --- |
| enumerate [--all] | 列出 HID 设备(默认仅列出 VID 为 0x303A 的设备;--all 列出所有设备)。显示每个设备的 usage 对和传输总线(bus=)。 |
| version | 通过 sys.version 获取固件版本。 |
| status | 通过 device.status 获取电池、充电、配置文件和层级。 |
| fs-list | 通过 fs.list 列出设备文件系统上的文件。 |
| fs-read [NAME] | 通过 fs.read 读取文件(默认:keymap.json)。只读。 |
| listen [--raw] [--duration ] | 流式传输固件日志和已解码的输入通知。--raw 以十六进制转储每个报告;--duration 限制捕获时长(默认:直到按下 Ctrl-C)。 |
| led zones --backlight --underglow | 通过 lights.preview 为背光 + 侧灯区域上色。 |
| led set | 通过 v.oai.thstatus 根据 ID 为单个 LED 上色。 |
| led multi = [...] | 在单次 v.oai.thstatus 调用中为多个 LED 上色,例如 led multi 0=ff0000 1=00ff00。 |
| led sweep [--delay-ms ] | 用不同的颜色遍历 ID 为 0..=16 的 LED,以展示哪些是可寻址的(默认延迟:500 毫秒)。完成后会恢复彩虹预览。 |
| led rgbcfg --keys --ambient | 通过 v.oai.rgbcfg 为分组的按键 + 环境区域上色。 |
`zones`、`set`、`multi` 和 `rgbcfg` 共享的标志有:`--effect `(默认 `solid`)和 `--brightness <0.0-1.0>`(默认 `0.5`);`zones` 和 `rgbcfg` 还支持 `--speed <0.0-1.0>`(默认 `0.5`)。颜色接受 `#ff0000` 或 `ff0000` 格式。
示例:
```
cargo run -- fs-read keymap.json
cargo run -- listen --duration 10
cargo run -- led multi 0=ff0000 1=ff6600 2=ffcc00 3=ccff00 4=66ff00 5=00ff00
cargo run -- led rgbcfg --keys ff0000 --ambient 00ffff --effect breath
```
## 协议
通过观察厂商通道逆向工程得出;以下所有内容均已在 Creator Micro 2 上进行了实时验证。
### 数据帧
- HID report ID `6` 是厂商通道。每个数据包大小为 64 字节:`[reportId=6][channel][len][payload ≤ 61 bytes]`。
- 通道 `1` 传输固件日志文本;通道 `2` 传输 JSON-RPC。大于单个数据包的消息会被分片并必须进行重组(分片的请求和响应都能正常工作,例如包含 17 个条目的 `v.oai.thstatus` 调用,或 1898 字节的 `fs.read` 结果)。
### JSON-RPC
请求格式类似于 `{"method":"sys.version","params":null,"id":1}`;响应格式类似于 `{"id":1,"result":{...}}`。
已知的 Host→Device 方法:
| 方法 | 参数 | 备注 |
| --- | --- | --- |
| sys.version | null | 固件版本。 |
| device.status | null | 电池、充电、配置文件、层级索引。 |
| fs.list | null | 列出设备文件(例如 keymap.json,以及自固件 v0.6.0 起的 smart_actions.json)。 |
| fs.read | {"file": ""} | 返回 {"data": ""}。只读。 |
| lights.preview | backlight/underglow 区域对象(effect, brightness, speed, magic, color) | 普通层级上的区域灯光。返回 result: null 作为确认。 |
| v.oai.thstatus | 包含 {id, c: <24-bit RGB>, b: <0-1>, e: , s: } 的数组 | 单个 LED 的状态灯光(Codex 层级)。返回 {ok:1} 作为确认。 |
| v.oai.rgbcfg | keys/ambient 区域对象 (e, b, s, m, c) | Codex 层级上的分组区域灯光。返回 {ok:1} 作为确认。 |
Device→Host 在通道 2 上的流量:
- `kb.radial` 通知 —— 径向模式下的摇杆流(参见[输入捕获](#input-capture))。
- `v.oai.hid` 通知 —— Codex 层级上的厂商按键/编码器事件:`{"m":"v.oai.hid","p":{"k":"AG00","act":1}}`。
- `host.focused_app` **请求** —— 设备会定期向 Host 轮询当前聚焦的应用程序(AppSense);它会发送带有自身 `id` 的 JSON-RPC 请求。
### 厂商按键事件(Codex 层级)
Codex 层级将控件映射到厂商 keycode,从而发出 `v.oai.hid` 通知,而不是标准的 HID 报告:
- `KV_OAI_AG00`–`KV_OAI_AG05` —— 6 个代理键(上两排)。
- `KV_OAI_ACT06`–`KV_OAI_ACT12` —— 7 个动作键(下两排)。
- `KV_OAI_ENC_CC` / `KV_OAI_ENC_CW` / `KV_OAI_ENC_CLK` —— 编码器逆时针 / 顺时针 / 点击。
`act` 值:`1` = 按下,`0` = 释放,`2` = 旋转(编码器)。以上均已在 CM2 上验证:`ENC_CC`/`ENC_CW` 发出 `act=2`,每个棘齿段触发一个事件;`ENC_CLK` 像按键一样发出 `act=1`/`0` 的按下/释放动作。
## 灯光模型
渲染哪种灯光表面取决于**当前激活层级的类型**:
| 当前层级 | 渲染 | 不渲染 |
| --- | --- | --- |
| 普通 (KC_*) 层级 | lights.preview(背光 + 侧灯区域) | v.oai.thstatus 帧(会返回确认,但不可见) |
| Codex 层级 | v.oai.thstatus(单个 LED,代理键)+ v.oai.rgbcfg(keys = 按键背光,ambient = 侧灯/边框) | lights.preview(返回确认 result: null,但不进行渲染) |
通过 `v.oai.thstatus` 控制单个 LED:
- 只有 ID **0..=5 会映射到物理 LED** —— 即六个代理键:id 0 = 左上角 (AG00),id 1 = 右上角 (AG01),ids 2..=5 = 第二排从左到右 (AG02..AG05)。
- ID **6..=16 会被确认(**`{ok:1}`**)但未映射** —— 一个仅包含 ID 6..16 且全亮度的帧不会点亮任何灯光(没有动作键、指示灯或侧灯)。与 Codex Micro 的表面相同。
- 其语义是**全帧重绘**:每次调用都会替换整个状态显示;从数组中省略的 ID 不会被保留。若要为单个 LED 设置动画,请在 Host 端维持完整帧并整体重新发送(包含多个条目的帧完全可以适应;分片请求可正常工作)。
单个 LED 控制的需求链:
1. 固件 **≥ v0.6.0**(在 v0.4.0 上,`v.oai.thstatus` 会对每个 ID 返回 `404 Method not found`)。
2. keymap 上需配置 **Codex 层级**(要求 Input 应用版本 ≥ 0.18-rc;该层级模板会将按键映射到 `KV_OAI_*` keycode)。
3. 该 Codex 层级必须在设备上被**激活**。
所有灯光效果都是**易失性的** —— 不会向 flash 写入任何内容,拔出/重新插入设备会恢复配置的行为。
## 输入捕获
`listen` 所能看到的内容取决于当前激活的层级:
- **普通层级**:按键发出标准的启动键盘报告(report ID `0x01`),编码器发出消费者控制报告(report ID `0x02`,例如音量加/减)—— 没有厂商通知。在 macOS 上读取这些内容可能需要“输入监控”权限。
- **摇杆(径向模式,任意层级)**:发生偏转时,会以约 40 Hz 的频率流式传输 `kb.radial` 厂商通知:`{"m":"kb.radial","p":{"a":,"d":,"s":,...}}`。释放动作由 `d: 0, s: -1` 信号指示。`s` 索引的是该层级的径向菜单扇区;选择某个扇区会将该扇区的 keycode 作为普通的 HID 报告发出。
- **摇杆(VENDOR 模式,Codex 层级)**:发生偏转时,会以约 45 Hz 的频率流式传输包含原始极坐标的厂商通知:`{"a": , "d": }`。与径向模式不同,它**没有扇区字段** —— 固件不会计算扇区,由 Host 自行进行扇区数学计算。释放时,会以一个最终的中心事件(`a: 0.0, d: 0.0`)结束流。
- **Codex 层级**:按键和编码器在通道 2 上发出 `v.oai.hid` 厂商事件(`KV_OAI_*`,带有按下/释放/旋转的 `act` 代码),而不是标准的 HID 报告。
## 安全性
本工具在关键操作上严格保持只读:
- **文件系统**:仅实现了 `fs.list` 和 `fs.read`。`fs.write`、`fs.delete`、`sys.bootloader` 和其他持久化/破坏性的方法**未公开暴露**,也从未被调用。
- **灯光**:所有灯光方法(`lights.preview`、`v.oai.thstatus`、`v.oai.rgbcfg`)都是易失性的 —— 它们永远不会触及 flash,重启或拔插设备即会重置。
- 本工具所做的任何操作都不会导致设备变砖或进行持久化修改。
## 免责声明
这是一个非官方的社区项目,与 Work Louder 或 OpenAI 无关,也未获得其认可。协议细节来自对设备厂商 HID 通道的净室观察。行为已在截至 2026-08-01 的固件 v0.6.0 和 Input 0.18.0-rc.8 上进行了验证;未来的固件可能会对此进行更改。
## 许可证
Apache-2.0 —— 见 [LICENSE](LICENSE)。
标签:HID协议, Rust, 可视化界面, 外设管理, 嵌入式系统, 硬件控制, 网络流量审计, 通知系统