我用 ESP32 做了一个 Codex 小助手,叫“小团”。它能同步 Codex 的用量、提醒我查看新回复或回答问题,还接入了飞书提醒,以及久坐、喝水提醒。

屏幕上的小团是一个头顶嫩芽的奶白团子。Codex 额度充足时,它精神很好;剩余额度变少,它也会慢慢垂下眼皮、趴下来休息。需要关注的事情,则通过屏幕提示、短音或固定中文语音提醒。

这个项目把几类原本分散在电脑里的状态,放到了一个可以看见、可以按键回应的小设备上。下面记录它目前的硬件、数据来源和提醒逻辑。

一块 ESP32-S3 和一台 Mac

硬件用的是正点原子 ESP32-S3 开发板,复用现有的 1.3 英寸 ST7789 SPI 屏幕、四个按键,以及 ES8388 音频部分和喇叭。屏幕按 240 × 240 像素绘制,设备通过 USB 供电,也可以接普通充电器。开发板的资料和例程可以在正点原子的官方仓库查看。

Mac 负责取得 Codex、飞书和电脑使用状态,整理成小规模的状态与事件,再通过 BLE 蓝牙直连发给设备。ESP32 负责画面、声音、按键,以及提醒的确认和去重。蓝牙模式不要求两端接入同一个 Wi-Fi;项目另外保留 USB 串口模式,供调试和恢复时使用。

小团的实际数据链路:Mac 通过官方接口读取 Codex 配额、适配本地聊天记录、读取飞书菜单栏 @ 计数和电脑活动状态,经认证的 BLE 连接发送状态与事件;ESP32-S3 负责屏幕、声音和按键。另有 USB 串口调试与恢复模式。

依据当前代码自绘的数据链路图。USB 是单独选择的调试、恢复模式。

这也决定了它的运行条件:喂食、陪玩、睡觉等桌宠互动可以离线运行;Codex 和飞书同步则需要 Mac 保持唤醒、后台服务运行,并与设备连接。

把 Codex 剩余额度变成精神状态

数字来自账号配额

Mac 端通过本机已登录的 Codex CLI,调用 account/rateLimits/read。这个接口提供的是账号配额窗口,其中包括已用比例、窗口长度和重置时间,字段含义见 Codex App Server 官方文档。

程序优先读取 Codex 对应的配额桶,把已用比例换算成剩余比例。如果有多个有效窗口,主页选取剩余比例最小的那个,并标出窗口。这里显示的是账号共享的套餐额度;当前聊天用了多少 token,属于另一种统计口径。

额度采集默认每 5 分钟一次,设备心跳约每 15 秒一次。心跳用来说明连接仍然有效,并不意味着每 15 秒重新取得了一份额度。登录凭据由电脑上的 Codex 管理,不发给板子;查询额度本身也不需要调用模型推理接口。

表情分档 数字保留真实值

小团的待机姿态分成五档:从举手、亮眼,到半垂眼、打哈欠,最后软趴下来。下方仍显示实际剩余整数百分比和能量条,按 KEY2 可以查看窗口详情及重置时间。

小团的五档 Codex 能量姿态预览,从 100% 时举手,到 80%、60%、40%,再到 20% 时趴下休息。

由项目实际固件绘图代码生成的界面预览,非实机照片。100%、80%、60%、40%、20% 是示例输入,不是实时账号额度;图中文字标记的是各档示意。

表情只表示大致档位。例如剩余 27% 时使用最低档的休息姿态,数字仍显示 27%,不会为了配合表情写成 20%。额度降至 10% 或以下时会短鸣一次,恢复到 20% 以上后才重新允许下一次低额度提醒,避免低位反复响。

另一个处理是让过期数据失效。超过 45 秒没有心跳、额度快照超过 10 分钟,或者旧窗口已经到了重置时刻,小团会回到中性待机,显示 WAIT MAC 或 --。暂时没有数据,就保留未知状态。

任务提醒分成需要回答和有新回复

任务提醒走的是另一条链路。Mac 端增量读取本地 Codex 主聊天索引和会话记录,约每 2 秒检查一次,识别两类主要情况:

  • Codex 发起需要用户填写的提问,显示 NEED YOUR ANSWER。
  • 一轮回复结束,显示 NEW REPLY,提示回到对应聊天查看。

普通进度消息和内部子代理的更新不会全部触发提醒。这里的“有新回复”只表示本轮回复已经结束,实际工作是否成功、还有哪些问题,需要回到 Codex 查看内容。关于执行结果为何还需要验证,之前的从 Prompt 到 Skill 再到 Loop也讨论过这个问题。

这部分依赖本地记录格式的适配,Codex 升级后可能需要跟着调整。它与读取额度使用的官方接口,有不同的维护边界。

设备上的 KEY2 用来确认当前一批提醒。主机和板子保存事件序号及已确认进度,确认旧批次时不会顺便清掉后来才到的新事件。启动时也先建立基线,避免把已有历史记录当作刚发生的消息全部播一遍。

发给设备的是提醒类别、数量和用于区分聊天的简短标识,聊天正文留在电脑上。小屏幕负责告诉我何时值得回去看,详细内容仍在原来的工作环境里。

飞书提醒读取菜单栏的 @ 计数

飞书这部分采用了很具体的本机方案:通过 macOS 辅助功能接口,读取飞书顶部菜单栏中的 @ 计数,约每 2 秒检查一次。计数增加时,主机生成一个提醒事件,设备播放固定语音:“飞书有新的艾特消息啦”。

它使用的是桌面客户端已经呈现的计数,不需要建立飞书应用机器人,也没有通过开放平台订阅聊天消息。采集范围限定在菜单栏计数,聊天正文不会传到小团。

第一次启动先记住当前计数,只有后续增加才产生事件;计数不变不会反复提醒。飞书退出、辅助功能读取失败或电脑长时间休眠后,会重新建立基线。提醒还带有过期时间,避免连接恢复后补播很久以前的提示。

这种接法也继承了菜单栏计数的局限:它可能包含 @所有人,无法说明谁发了消息;如果读掉旧消息和收到新 @ 恰好让总数不变,也可能漏掉。遇到 99+ 等不能可靠解析的格式时,程序停止把它当作精确计数。辅助功能权限和飞书客户端界面发生变化,也需要重新检查适配。

当前已完成标明为合成事件的语音与去重测试;真实新增 @ 的持续自动触发,仍待后续使用观察。

久坐和喝水提醒按电脑使用时间计时

日常提醒默认在北京时间工作日 09:00–18:00 生效:累计活动使用电脑 60 分钟后提醒起身活动,90 分钟后提醒喝水。这两个间隔可以调整,属于使用设置。

“久坐”在这里具体实现为电脑使用计时。它没有用摄像头判断坐姿,也没有检测实际饮水量。按 KEY2 表示已经看过提醒,不等于设备确认我完成了活动或喝水。

计时没有简单地按墙上时钟不断累加。电脑空闲达到 5 分钟后,活动计时重置,喝水计时暂停;休眠、用户切换或离开工作时段,不会把这段时间补成一串到期提醒。相应地,长时间阅读、看视频也可能被系统判断为空闲,这个判断并不完全等同于人是否坐在电脑前。

Mac 菜单栏里的“小团”提供暂停一小时、恢复和稍后十分钟等控制。检测到音频输入正在使用时,程序也会暂停日常提醒,适合覆盖部分通话场景;它只能感知音频输入状态,无法判断所有会议。

在设备端,Codex 提示优先于健康提醒显示,KEY2 也先确认 Codex 提示。小团睡觉时暂缓健康播报。飞书采用独立语音提醒路径;静音时该事件仍会被记录为已处理,恢复声音后不会补播旧消息。

小屏幕之外还有按键和声音

四个按键承担了日常交互,短按和长按分别处理:

按键短按长按约一秒
KEY0喂食动画和短音—
KEY1陪玩并播放中文问候切换三档音量
KEY2确认当前 Codex 或健康提示,否则看额度详情切换声音与静音
KEY3睡觉或唤醒—

中文问候、飞书和健康提醒采用预先生成的 16 kHz 单声道 PCM 片段,打包进 Flash,通过 ES8388 和喇叭播放。常用提醒在设备上即可出声,无需每次联网合成语音;目前支持的是这些固定片段。

设备还利用 OV2640 摄像头的局部画面变化,让眼睛朝运动区域看过去;麦克风检测拍手一类的突发声音,触发惊讶和唤醒。它们是本地的运动与声音检测,背景移动、敲击等也可能触发。播放声音及余音期间会暂停拍手判定,避免自己的喇叭把自己叫醒。

Mac 与设备的 BLE 连接在发送业务数据前,会用新随机数和共享密钥做 HMAC 身份校验。这个机制用于确认连接双方持有配对密钥,不能据此把蓝牙传输描述成已经加密。密钥留在本机私有配置和设备存储中。

小团把额度、消息和日常节奏放在同一块屏幕上,但每类提醒保留了自己的含义:额度是持续状态,新回复是待查看的事件,活动和喝水则是可以暂停的个人计时。接下来需要继续观察的,是飞书真实新 @ 的触发,以及日常工作中长时间计时、休眠和重连后的表现。