Featured image of post 让 Codex 用灯光叫我回来:CodexLightHub 的 ROG 与 Yeelight 实机记录

让 Codex 用灯光叫我回来:CodexLightHub 的 ROG 与 Yeelight 实机记录

把 Codex 的工作、询问和完成状态接到 ROG 键盘与 Yeelight 挂灯:从 Aura REST 走到 ASUS HID,再用扫码登录打通本地 MIoT,并修正橙色误报与完成后的灯效恢复。

Codex 在后台跑任务时,我不想一直盯着窗口。更需要提醒我的时刻,是它已经停下来,等我回答一个问题。既然桌上已经有 RGB 键盘和挂灯,能不能让这些灯直接表示任务状态?

这个想法来自 Codex Micro 的状态指示灯。它用颜色告诉我 Codex 正在工作、等待输入还是已经完成,我也想让整张桌面有同样直观的反馈。最后做出来的 CodexLightHub,是一个运行在 Windows 上的 Python 小程序。它接收 Codex 的生命周期事件,再把状态发给 ROG 笔记本灯光和 Yeelight 挂灯的后方氛围灯。

这篇记录整理到 1.0.6。ASUS 和 Yeelight 两条链路已经接通,EZ80 仍停在只读探测阶段。本文中的灯光照片均来自实际运行效果,连接图只用于解释程序结构。

先确定灯光应该告诉我什么

最早的设想很直接:工作时蓝色,需要我时橙色,完成时绿色。真正用起来以后,才发现颜色背后的判断比变色本身更难。

例如,Codex 发起权限检查时,可能由自动审核处理,根本没有弹窗让我操作。如果每次都变橙色,我就会不断被叫回来,最后开始忽略它。

1.0.6 把规则收紧到了下面这样:

状态当前行为
工作中满亮蓝色;ROG 使用硬件呼吸形成动态起伏,Yeelight 保持静态蓝色
真实询问request_user_input 调用前变成满亮橙色,回答后恢复蓝色
自动审核或后台权限检查保持蓝色,不触发橙色提醒
本轮完成满亮绿色保持 180 秒,再恢复日常彩色灯效
收到中断事件满亮红色短暂提示,默认保持 4 秒
空闲ASUS 恢复 G-Helper 保存的灯效;Yeelight 按配置恢复彩虹
蓝色:Codex 正在工作
蓝色:Codex 正在工作
橙色:等待我回答问题
橙色:等待我回答问题
绿色:这一轮已经完成
绿色:这一轮已经完成
彩色:恢复平时的桌面灯效
彩色:恢复平时的桌面灯效

这里的“完成”指当前一轮结束,不代表整个项目已经做完。红色也不能覆盖所有错误:当前安装器没有注册通用错误事件,Hub 只是保留了 Interrupt 的处理入口。

还有一个限制要单独说清楚。项目默认启用了 plan_stop_is_waiting,当收到带有 permission_mode=plan 的 Stop 时,会保守地显示等待状态。它是为部分计划确认界面补的启发式判断,不能证明屏幕上一定有弹窗。普通询问使用的是实际工具事件,这两条路径不能混为一谈。

一条很短的事件链

程序分成三层:Hook 接事件,Hub 判断状态,Adapter 控灯。

CodexLightHub 的事件、状态机和双设备输出示意

Codex lifecycle hooks
        │
codex-light-hook.cmd → hook.py
        │  UDP 127.0.0.1:17871
        ▼
Hub:按会话保存状态、去重、决定当前灯色
        ├── ASUS HID → ROG 键盘与机身灯光
        └── 本地 MIoT → Yeelight 后方氛围灯

hook.py 只挑出事件名、会话、轮次、工具名等少量字段,发送到本机回环地址后退出。提示词、工具参数、工具输出、回复正文和工作目录都不在转发白名单里。灯光程序也不输出允许或拒绝权限的决定。

硬件写入在 Hub 的工作线程里执行。挂灯连接慢了,或某个设备不可用,不应该让 Codex 的 Hook 一直等着。

Codex Desktop、ROG 键盘和 Yeelight 挂灯联动的桌面实拍

同一时间开多个任务时,Hub 按下面的顺序选择灯色:

waiting > running > interrupted > completed > idle

因此,一个任务完成不会用绿色盖住另一个任务的橙色询问;只要还有任务在运行,整体也会优先显示蓝色。这意味着绿色的 3 分钟计时到了,不一定马上看到彩虹,还要看其他会话是否仍然活跃。

ROG:从没有设备的 REST 接口,走到 ASUS HID

这次使用的笔记本是 ROG Strix G16 G615LR,灯光由 G-Helper 0.279.0 管理。

最初的 Aura REST 探测没有枚举到设备,返回值是 5。我们随后试过 Windows Dynamic Lighting,又遇到了 PyWinRT 枚举接口的参数问题。但确认平时由 G-Helper 控灯后,主线就转向了它使用的 ASUS HID 通道。

G-Helper 的公开源码提供了实际设备控制实现。项目据此整理协议,再结合本机 G-Helper 日志确认目标设备,而不是只看到 ASUS 的厂商编号就发送数据。

本机核对到的参数是:

VID: 0B05
PID: 19B6
Aura collection: col04
Report length: 64 bytes

桥接层会同时检查 PID 白名单、配置中的允许列表、日志里的完整 HID 路径、报告长度和 Aura Probe 结果。检查不通过时,拒绝写入。

灯效写入沿用了三段报告的顺序:

5D-B3-00-{mode}-{R}-{G}-{B}-{speed}-...
5D-B5-00-00-00
5D-B4

上面是协议结构说明,不是可以直接复制发送的完整数据包。它也不代表所有 ASUS 设备都适用。当前配置默认只允许这次验证过的 19B6,换型号后应重新探测。

GHelperAuraBridge.py 提供 blue、waiting、complete、error 和 restore 命令。ASUS 的运行、等待和错误效果使用硬件呼吸模式,不需要 Python 持续计算每一帧亮度。四种任务状态都按 100% 亮度发送。

恢复灯效时,程序读取 G-Helper 保存的 aura_mode、aura_color 和 aura_speed,再发送对应模式。它不会修改 G-Helper 的配置。这恢复的是保存的日常设置,并不是对接管前任意瞬时灯光状态的完整快照。

Yeelight:55443 不通,仍然可以走本地 MIoT

挂灯是 Yeelight 显示器挂灯 2 Pro,设备型号为 yeelink.light.lamp42。这里控制的是背后的氛围灯。

最容易绕进去的地方,是把“Yeelight 设备”与“开放传统 Yeelight LAN Control”画上等号。本机测试中,传统局域网控制端口 55443 没有开放。这只能说明那条路径没有跑通,不能据此判定设备没有其他本地接口。

另一个障碍是登录。早期 python-miio/micloud 的密码登录在这次环境中失败,后面改用了 mihome-ctl 的米家扫码登录,取得设备的本地 IP 和 Token。这个工具用于完成设备信息获取;日常灯色切换由 CodexLightHub 的本地 MIoT 后端处理。

所以最终链路是:扫码取得凭据,然后通过本地 MIoT 控灯。它不需要每变一次颜色就重新扫码,也没有把米家账号密码写进 Hub。

在这盏灯上,氛围灯属性已经按下面的映射完成实机验证:

属性SIID / PIID
开关3 / 1
亮度3 / 2
RGB 颜色3 / 3
模式3 / 4

颜色按 RGB 转成十进制值:

color = (r << 16) | (g << 8) | b

正常变色时,后端依次写入静态模式、颜色、亮度和开关。恢复日常灯效时则把模式放到最后写,因为写颜色可能让设备回到静态模式。此次验证中,最后写入 mode=1 可以恢复彩虹。

这里还有一个设备差异:ASUS 的蓝色运行和橙色等待灯效使用硬件呼吸,当前 Yeelight 后端则显示对应颜色的满亮静态灯效。两者表示同一个状态,动画并不完全一致。

Codex 完成后,Yeelight 挂灯显示绿色状态

Token 是设备控制密钥,只放在本地配置。文章没有附设备地址、Token、登录二维码或账号信息;如果分享工程,应使用 config.example.json,不能打包自己的运行配置。

真正费时间的是状态细节

Hook 显示完成,不等于灯光脚本执行了

Windows 包装命令曾是一个排查点。项目最后用 .cmd 包装器承接 Python 调用,避免在 Hook 字符串里反复嵌套引号。

安装器会备份并合并已有的 hooks.json,保留不属于本项目的处理器。最终版本还移除了本项目之前注册的 PermissionRequest 处理器,让自动审核不再触发橙色。

这套安装方式是在本机路径和 Codex 版本下跑通的。包装命令依赖路径格式,迁移到包含空格的用户目录时仍应检查,不能把一次成功当成所有 Windows 环境的保证。

“已完成”和“我已经看过”不是一回事

开发中一度参考 Codex Micro 的“完成且未读”思路,让绿色一直保持到下一次交互。用了一下,我又把需求改成了“过几分钟切回彩色”。

最后的配置是:

{
  "completed_hold_seconds": 180,
  "sticky_completed": false
}

新提示提交后立即回到蓝色,不必等计时结束。Hub 虽然保留了 MarkRead 和 Read 入口,但当前接入没有收到“用户已阅读回复”的事件,不能根据绿色是否消失判断我有没有看过结果。

橙色必须值得我回来

最后一次调整是把手工变橙色测试改成真正的询问弹窗测试。

先手工写入橙色,只能证明设备能接收颜色。真正调用 request_user_input、出现问题、回答后恢复蓝色,才能检查从 Codex 到设备的整条链路。开发记录里最终确认了“弹窗且橙色”,随后完成 1.0.6。

Hub 还处理了一个并行工具细节:已经在等我回答时,另一个工具先结束,不应把等待状态提前冲掉。只有匹配的询问结束,才应该回到运行状态。

目前完成到哪里

截至这次记录,已经完成 ASUS HID 写入、Yeelight 本地 MIoT 写入、双设备接入同一状态机、真实询问提醒,以及完成 3 分钟后的彩色恢复。最终开发记录报告 11 项自动测试通过,代码中的测试覆盖了状态变化、HID 构造与保护条件、MIoT 属性写入等部分。自动测试与实机变色是两类证据,不能互相替代。

EZ80 暂时只有只读 HID 探测器,没有写入灯光协议。当前项目也没有实现逐键显示多个任务、按某个键跳回会话,或者识别所有客户端弹窗。这些早期讨论过的想法,没有被写成已经完成的功能。

工程仍是围绕这台 G615LR 和这盏 lamp42 做的个人工具。设备断开、网络超时、G-Helper 与其他灯光控制器竞争,以及不同 Codex 版本的事件差异,都是后续使用时需要观察的地方。

对我来说,最有用的结果是:起伏的蓝色让我知道它还在工作,橙色提示有问题要回答,绿色短暂保留完成状态,然后桌面回到原来的彩色。这个小工具的价值,取决于这些提醒能不能对应真实情况。

评论

聊聊你的想法,使用 GitHub 账号参与讨论。

在 GitHub 查看

滚动到此处时加载评论。