把 Agent 装进桌面宠物:Orange Pi + ESP32 实验
用 Orange Pi 运行 Agent 与语音服务,让 ESP32 专注屏幕、麦克风、扬声器和动作控制。
最近我在做一个桌面智能宠物。
它有一块圆形屏幕,可以显示表情、字幕、时间和服务状态;它可以听见我说话、用语音回复,也可以在未来通过旋转、点头和摇头表达动作。
但我真正想做的,并不是一个缩小版的智能音箱,而是 Agent 在现实世界中的一个身体。
从聊天窗口走到桌面上
现在的大多数 Agent 都生活在聊天窗口里:输入一段文字,等待搜索、分析或执行,再看结果。这种方式适合复杂内容,却不适合所有场景。
例如:
- 一个长时间运行的任务刚刚完成
- 博客服务器出现异常
- GitHub 构建失败
- 收到一封需要尽快处理的邮件
- 下一场会议即将开始
- Codex 或其他 Agent 正在执行某个步骤
- 家里的设备状态发生变化
这些信息不值得专门打开电脑,却适合通过桌面上的常驻设备轻量呈现。
于是,这个桌面宠物逐渐从「会说话的硬件」变成了一个现实世界中的 Agent 终端。
为什么使用两块计算设备
核心问题是:所有事情都交给谁做?
如果全部交给 ESP32,它既要处理屏幕、麦克风和扬声器,又要连接云端 ASR、TTS 和大模型,还要维护复杂会话状态,固件会迅速变得难以维护。
如果全部交给 Orange Pi,屏幕刷新、实时音频、触摸和电机控制都要直接依赖 Linux,设备结构会更复杂。
最终采用两层架构:
云端 ASR / TTS / 模型 / 外部服务
│
▼
Orange Pi Zero 3W
Agent、会话、插件、设备服务
│
Wi-Fi / WebSocket
│
▼
ESP32-S3
屏幕、触摸、麦克风、扬声器、电机
边界很明确:Orange Pi 负责理解、决策和连接;ESP32 负责感知、表达和执行。
Orange Pi:Agent 的运行时
Orange Pi Zero 3W 运行 Linux,承担相对复杂的部分:
- 连接网络
- 运行 Hermes 或其他 Agent Runtime
- 对接豆包 Seed-ASR 2.0 / Seed-TTS 2.0
- 保存会话上下文
- 执行工具和插件
- 管理 ESP32 设备连接
- 提供 Web 管理后台
- 管理长期记忆、定时任务和安全策略
这里有一个重要判断:
Agent Runtime 不等于完整服务端。
Hermes 或 Pi SDK 可以负责 Agent Loop、工具决策和上下文,但设备管理、WebSocket、ASR/TTS、用户系统、插件管理和安全,仍然应由单独的服务端模块承担。
因此 Orange Pi 既是「大脑所在的设备」,也是本地服务节点,内部仍需清晰的模块边界。
ESP32:保持简单的受控终端
ESP32-S3 不负责理解用户意图,主要完成:
- I2S 麦克风采集与扬声器播放
- 圆形屏幕刷新、字幕与表情
- 按键和触摸事件
- 电量、温度等状态采集
- 电机和灯光控制
- 本地唤醒词
- 断网时的基础状态展示
服务端下发语义化指令,例如:
{
"type": "display.expression",
"payload": {
"name": "thinking",
"loop": true
}
}
或:
{
"type": "motion.perform",
"payload": {
"name": "nod",
"intensity": 0.6
}
}
ESP32 再把语义动作转换为帧动画、电机角度或音频操作。固件不必知道背后的模型来自哪里,也不必关心 Agent 用了什么工具。
一次完整的语音对话
当前确定的语音链路:
- 用户点击宠物,或说出唤醒词
- ESP32 开启 I2S 麦克风
- 每 100ms 将 PCM 发给 Orange Pi
- Orange Pi 转发至豆包 Seed-ASR 2.0
- ASR 持续返回增量识别结果
- Orange Pi 将 partial 字幕推给 ESP32
- 检测到约 800ms 静音
- 等待最终识别文本,交给 Hermes
- Hermes 返回
silent、execute或reply - 若是
reply,文本流式发送给 Seed-TTS 2.0 - Orange Pi 将回复字幕和音频同步推给 ESP32
- 播放完成后恢复待机
最重要的不是「回答一句话」,而是让用户始终知道设备处于什么阶段:
idle → listening → recognizing → thinking
→ executing → speaking → idle
说话时显示实时字幕;调用工具时显示「正在检查服务器」;等待 TTS 时显示思考动画;开始播放再切到说话状态。语音交互就不该是一个不可见的黑盒。
为什么优先使用 Wi-Fi 直连
我希望 Orange Pi 一边连外部 Wi-Fi,一边为 ESP32 提供独立热点:
互联网
│
家庭或办公室 Wi-Fi
│
Orange Pi(STA + AP)
│
ESP32
ESP32 不必直接暴露在家庭网络中,所有通信都经过 Orange Pi。蓝牙可用于首次配网或备用连接,但不适合作为持续传输 PCM、字幕和状态的主链路。
桌面宠物不应该一直说话
语音助手里容易被忽略的问题是:什么时候不回复?
如果随口一句话也每次正式回答,设备很快会变得吵闹。因此 Hermes 最终需要三类决策:
{ "action": "silent" }
{
"action": "execute",
"tool": "device.motion",
"arguments": { "name": "turn_right" }
}
{
"action": "reply",
"text": "服务器运行正常,当前温度是 52 摄氏度。"
}
silent 不是失败,而是一种产品能力。有陪伴感的设备,不只学会回答,也要学会安静地待在旁边。
未来的插件化方向
管理后台应以插件方式接入外部服务。插件不直接操作屏幕,而是上报结构化状态:
{
"source": "github",
"level": "warning",
"title": "部署失败",
"message": "blog-main 构建未通过"
}
设备策略再决定是否立刻展示、是否播放提示音、用哪种表情、是否需要确认、是否允许语音追问。
未来可接入:GitHub Actions、博客服务器、Codex 任务、邮件与日历、天气、家庭设备、Agent 长任务进度。
到这一步,桌面宠物就不再只是语音设备,而是可观察、可控制的 Agent 现实终端。
最难的部分其实不是模型
真正困难的并不是调用 ASR、TTS 或大模型,而是大量细节:
- 网络抖动时音频不能断
- 实时字幕不能频繁跳动
- 设备重连后状态需要恢复
- 指令必须支持 ACK 和超时
- 电机动作不能与语音播放冲突
- Agent 执行工具时必须有明确反馈
- 屏幕空间有限,不能堆太多文字
- 不同硬件版本需要能力发现
- API 密钥不能下发到 ESP32
- 断网时也应保留基本交互
模型只是能力来源,系统设计才决定它是否真的可用。
写在最后
我想做的,并不是一个「可以接入大模型」的硬件外壳。
我更关心:当 Agent 从聊天窗口走到桌面之后,它应该如何被看见、被理解和被信任——什么时候听见了我,什么时候在思考,什么时候正在执行,又什么时候选择不打扰我。
屏幕、语音、动作和状态,最终都在回答同一个问题:
一个真正存在于现实环境中的 Agent,应该是什么样子?