<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>硬件实践 on heyaohua's Blog</title><link>https://blog.heyaohua.com/tags/%E7%A1%AC%E4%BB%B6%E5%AE%9E%E8%B7%B5/</link><description>Recent content in 硬件实践 on heyaohua's Blog</description><image><title>heyaohua's Blog</title><url>https://blog.heyaohua.com/og-image.png</url><link>https://blog.heyaohua.com/og-image.png</link></image><generator>Hugo</generator><language>zh-CN</language><lastBuildDate>Tue, 20 Oct 2026 10:00:00 +0800</lastBuildDate><atom:link href="https://blog.heyaohua.com/tags/%E7%A1%AC%E4%BB%B6%E5%AE%9E%E8%B7%B5/index.xml" rel="self" type="application/rss+xml"/><item><title>我用 ESP32 做了一个 Codex 小助手</title><link>https://blog.heyaohua.com/posts/2026/10/esp32-codex-personal-assistant/</link><pubDate>Tue, 20 Oct 2026 10:00:00 +0800</pubDate><guid>https://blog.heyaohua.com/posts/2026/10/esp32-codex-personal-assistant/</guid><description>我用 ESP32-S3 做了一个叫小团的桌面助手，通过 Mac 与蓝牙同步 Codex 剩余额度和聊天提醒，监听飞书菜单栏的 @ 计数，并按电脑使用时间提醒活动、喝水。记录它的屏幕交互、数据链路与当前边界。</description><content:encoded><![CDATA[<p>我用 ESP32 做了一个 Codex 小助手，叫“小团”。它能同步 Codex 的用量、提醒我查看新回复或回答问题，还接入了飞书提醒，以及久坐、喝水提醒。</p>
<p>屏幕上的小团是一个头顶嫩芽的奶白团子。Codex 额度充足时，它精神很好；剩余额度变少，它也会慢慢垂下眼皮、趴下来休息。需要关注的事情，则通过屏幕提示、短音或固定中文语音提醒。</p>
<p>这个项目把几类原本分散在电脑里的状态，放到了一个可以看见、可以按键回应的小设备上。下面记录它目前的硬件、数据来源和提醒逻辑。</p>
<h2 id="一块-esp32-s3-和一台-mac">一块 ESP32-S3 和一台 Mac</h2>
<p>硬件用的是正点原子 ESP32-S3 开发板，复用现有的 1.3 英寸 ST7789 SPI 屏幕、四个按键，以及 ES8388 音频部分和喇叭。屏幕按 240 × 240 像素绘制，设备通过 USB 供电，也可以接普通充电器。开发板的资料和例程可以在<a href="https://github.com/openedv/ATK-DNESP32S3-Board">正点原子的官方仓库</a>查看。</p>
<p>Mac 负责取得 Codex、飞书和电脑使用状态，整理成小规模的状态与事件，再通过 BLE 蓝牙直连发给设备。ESP32 负责画面、声音、按键，以及提醒的确认和去重。蓝牙模式不要求两端接入同一个 Wi-Fi；项目另外保留 USB 串口模式，供调试和恢复时使用。</p>
<p><img alt="小团的实际数据链路：Mac 通过官方接口读取 Codex 配额、适配本地聊天记录、读取飞书菜单栏 @ 计数和电脑活动状态，经认证的 BLE 连接发送状态与事件；ESP32-S3 负责屏幕、声音和按键。另有 USB 串口调试与恢复模式。" loading="lazy" src="/images/posts/esp32-codex-personal-assistant/function-map.svg"></p>
<p><em>依据当前代码自绘的数据链路图。USB 是单独选择的调试、恢复模式。</em></p>
<p>这也决定了它的运行条件：喂食、陪玩、睡觉等桌宠互动可以离线运行；Codex 和飞书同步则需要 Mac 保持唤醒、后台服务运行，并与设备连接。</p>
<h2 id="把-codex-剩余额度变成精神状态">把 Codex 剩余额度变成精神状态</h2>
<h3 id="数字来自账号配额">数字来自账号配额</h3>
<p>Mac 端通过本机已登录的 Codex CLI，调用 <code>account/rateLimits/read</code>。这个接口提供的是账号配额窗口，其中包括已用比例、窗口长度和重置时间，字段含义见 <a href="https://learn.chatgpt.com/docs/app-server">Codex App Server 官方文档</a>。</p>
<p>程序优先读取 Codex 对应的配额桶，把已用比例换算成剩余比例。如果有多个有效窗口，主页选取剩余比例最小的那个，并标出窗口。这里显示的是账号共享的套餐额度；当前聊天用了多少 token，属于另一种统计口径。</p>
<p>额度采集默认每 5 分钟一次，设备心跳约每 15 秒一次。心跳用来说明连接仍然有效，并不意味着每 15 秒重新取得了一份额度。登录凭据由电脑上的 Codex 管理，不发给板子；查询额度本身也不需要调用模型推理接口。</p>
<h3 id="表情分档-数字保留真实值">表情分档 数字保留真实值</h3>
<p>小团的待机姿态分成五档：从举手、亮眼，到半垂眼、打哈欠，最后软趴下来。下方仍显示实际剩余整数百分比和能量条，按 KEY2 可以查看窗口详情及重置时间。</p>
<p><img alt="小团的五档 Codex 能量姿态预览，从 100% 时举手，到 80%、60%、40%，再到 20% 时趴下休息。" loading="lazy" src="/images/posts/esp32-codex-personal-assistant/energy-preview.png"></p>
<p><em>由项目实际固件绘图代码生成的界面预览，非实机照片。100%、80%、60%、40%、20% 是示例输入，不是实时账号额度；图中文字标记的是各档示意。</em></p>
<p>表情只表示大致档位。例如剩余 27% 时使用最低档的休息姿态，数字仍显示 27%，不会为了配合表情写成 20%。额度降至 10% 或以下时会短鸣一次，恢复到 20% 以上后才重新允许下一次低额度提醒，避免低位反复响。</p>
<p>另一个处理是让过期数据失效。超过 45 秒没有心跳、额度快照超过 10 分钟，或者旧窗口已经到了重置时刻，小团会回到中性待机，显示 <code>WAIT MAC</code> 或 <code>--</code>。暂时没有数据，就保留未知状态。</p>
<h2 id="任务提醒分成需要回答和有新回复">任务提醒分成需要回答和有新回复</h2>
<p>任务提醒走的是另一条链路。Mac 端增量读取本地 Codex 主聊天索引和会话记录，约每 2 秒检查一次，识别两类主要情况：</p>
<ul>
<li>Codex 发起需要用户填写的提问，显示 <code>NEED YOUR ANSWER</code>。</li>
<li>一轮回复结束，显示 <code>NEW REPLY</code>，提示回到对应聊天查看。</li>
</ul>
<p>普通进度消息和内部子代理的更新不会全部触发提醒。这里的“有新回复”只表示本轮回复已经结束，实际工作是否成功、还有哪些问题，需要回到 Codex 查看内容。关于执行结果为何还需要验证，之前的<a href="/posts/2026/06/prompt-skill-loop-ai-debugging/">从 Prompt 到 Skill 再到 Loop</a>也讨论过这个问题。</p>
<p>这部分依赖本地记录格式的适配，Codex 升级后可能需要跟着调整。它与读取额度使用的官方接口，有不同的维护边界。</p>
<p>设备上的 KEY2 用来确认当前一批提醒。主机和板子保存事件序号及已确认进度，确认旧批次时不会顺便清掉后来才到的新事件。启动时也先建立基线，避免把已有历史记录当作刚发生的消息全部播一遍。</p>
<p>发给设备的是提醒类别、数量和用于区分聊天的简短标识，聊天正文留在电脑上。小屏幕负责告诉我何时值得回去看，详细内容仍在原来的工作环境里。</p>
<h2 id="飞书提醒读取菜单栏的--计数">飞书提醒读取菜单栏的 @ 计数</h2>
<p>飞书这部分采用了很具体的本机方案：通过 macOS 辅助功能接口，读取飞书顶部菜单栏中的 @ 计数，约每 2 秒检查一次。计数增加时，主机生成一个提醒事件，设备播放固定语音：“飞书有新的艾特消息啦”。</p>
<p>它使用的是桌面客户端已经呈现的计数，不需要建立飞书应用机器人，也没有通过开放平台订阅聊天消息。采集范围限定在菜单栏计数，聊天正文不会传到小团。</p>
<p>第一次启动先记住当前计数，只有后续增加才产生事件；计数不变不会反复提醒。飞书退出、辅助功能读取失败或电脑长时间休眠后，会重新建立基线。提醒还带有过期时间，避免连接恢复后补播很久以前的提示。</p>
<p>这种接法也继承了菜单栏计数的局限：它可能包含 @所有人，无法说明谁发了消息；如果读掉旧消息和收到新 @ 恰好让总数不变，也可能漏掉。遇到 <code>99+</code> 等不能可靠解析的格式时，程序停止把它当作精确计数。辅助功能权限和飞书客户端界面发生变化，也需要重新检查适配。</p>
<p>当前已完成标明为合成事件的语音与去重测试；真实新增 @ 的持续自动触发，仍待后续使用观察。</p>
<h2 id="久坐和喝水提醒按电脑使用时间计时">久坐和喝水提醒按电脑使用时间计时</h2>
<p>日常提醒默认在北京时间工作日 09:00–18:00 生效：累计活动使用电脑 60 分钟后提醒起身活动，90 分钟后提醒喝水。这两个间隔可以调整，属于使用设置。</p>
<p>“久坐”在这里具体实现为电脑使用计时。它没有用摄像头判断坐姿，也没有检测实际饮水量。按 KEY2 表示已经看过提醒，不等于设备确认我完成了活动或喝水。</p>
<p>计时没有简单地按墙上时钟不断累加。电脑空闲达到 5 分钟后，活动计时重置，喝水计时暂停；休眠、用户切换或离开工作时段，不会把这段时间补成一串到期提醒。相应地，长时间阅读、看视频也可能被系统判断为空闲，这个判断并不完全等同于人是否坐在电脑前。</p>
<p>Mac 菜单栏里的“小团”提供暂停一小时、恢复和稍后十分钟等控制。检测到音频输入正在使用时，程序也会暂停日常提醒，适合覆盖部分通话场景；它只能感知音频输入状态，无法判断所有会议。</p>
<p>在设备端，Codex 提示优先于健康提醒显示，KEY2 也先确认 Codex 提示。小团睡觉时暂缓健康播报。飞书采用独立语音提醒路径；静音时该事件仍会被记录为已处理，恢复声音后不会补播旧消息。</p>
<h2 id="小屏幕之外还有按键和声音">小屏幕之外还有按键和声音</h2>
<p>四个按键承担了日常交互，短按和长按分别处理：</p>
<table>
	<thead>
			<tr>
					<th>按键</th>
					<th>短按</th>
					<th>长按约一秒</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>KEY0</td>
					<td>喂食动画和短音</td>
					<td>—</td>
			</tr>
			<tr>
					<td>KEY1</td>
					<td>陪玩并播放中文问候</td>
					<td>切换三档音量</td>
			</tr>
			<tr>
					<td>KEY2</td>
					<td>确认当前 Codex 或健康提示，否则看额度详情</td>
					<td>切换声音与静音</td>
			</tr>
			<tr>
					<td>KEY3</td>
					<td>睡觉或唤醒</td>
					<td>—</td>
			</tr>
	</tbody>
</table>
<p>中文问候、飞书和健康提醒采用预先生成的 16 kHz 单声道 PCM 片段，打包进 Flash，通过 ES8388 和喇叭播放。常用提醒在设备上即可出声，无需每次联网合成语音；目前支持的是这些固定片段。</p>
<p>设备还利用 OV2640 摄像头的局部画面变化，让眼睛朝运动区域看过去；麦克风检测拍手一类的突发声音，触发惊讶和唤醒。它们是本地的运动与声音检测，背景移动、敲击等也可能触发。播放声音及余音期间会暂停拍手判定，避免自己的喇叭把自己叫醒。</p>
<p>Mac 与设备的 BLE 连接在发送业务数据前，会用新随机数和共享密钥做 HMAC 身份校验。这个机制用于确认连接双方持有配对密钥，不能据此把蓝牙传输描述成已经加密。密钥留在本机私有配置和设备存储中。</p>
<p>小团把额度、消息和日常节奏放在同一块屏幕上，但每类提醒保留了自己的含义：额度是持续状态，新回复是待查看的事件，活动和喝水则是可以暂停的个人计时。接下来需要继续观察的，是飞书真实新 @ 的触发，以及日常工作中长时间计时、休眠和重连后的表现。</p>
]]></content:encoded></item></channel></rss>