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, 可视化界面, 外设管理, 嵌入式系统, 硬件控制, 网络流量审计, 通知系统