简体中文 | 繁體中文 | English |
XiaoZhi Android Agent:ESP32-S3 零 PC 控制安卓的完整工程复盘

XiaoZhi Android Agent:ESP32-S3 零 PC 控制安卓的完整工程复盘

typecho
2026-11-12 / 0 评论 / 1 阅读 / 正在检测是否收录... ===> PDD优惠福利券,千万好物,不要错过 <===

给 ESP32-S3 接上 WiFi、同一局域网下开启安卓平板「无线调试」,不用电脑、不用装伴侣 APP,只靠语音就能让小智帮你打开 WPS、写作文、保存、关机 —— 这听起来像科幻,但我们在 10 轮实测迭代后把它跑通了。

背景与架构选型:为什么是 ESP32-S3 做 ADB Host

市面上绝大多数 ESP32 语音助手走的是「云端 MQTT/WebSocket 控制智能家居」的老路:开关灯、查天气、放音乐。要控制安卓设备,常见方案要么需 PC 常驻跑 ADB 桥接,要么手机端装无障碍服务/Root 伴侣 APP,要么上云端大模型 Function Calling 再回调设备 —— 都不够「边缘侧、离线可控、零依赖」。

ESP32-S3 通过 WiFi 连接同一局域网下的安卓设备,用 mDNS 发现无线调试端口,建立 ADB TCP 连接。配合小智已有的 MCP 协议栈,我们在固件层注册了 10 个标准工具:connect/launch/look/tap/swipe/key/type/batch/status/done,覆盖安卓设备的完整控制链路 —— 启动应用、查看屏幕树、点击坐标、滑动、按键、文本输入、批量原子步骤、状态查询、任务完成标记。零 PC、零伴侣 APP、零 Root,联网即用。

核心难点一:中文输入死结 —— ADBKeyboard 广播直输

Android 13+ 的 adb shell input text 直输中文会抛 NPE:KeyCharacterMap 里根本没有 CJK 字符映射。剪贴板 Binder 编组方案我们试过,跨进程序列化失败放弃。最终落地的是开源虚拟键盘 ADBKeyboard:

  • ime enable com.android.adbkeyboard/.AdbIME 启用
  • ime set com.android.adbkeyboard/.AdbIME 切换
  • am broadcast -a ADB_INPUT_TEXT --es msg '<文本>' 广播直输 Unicode
  • 约 500 ms 延迟等待 IME 绑定生效
  • 用完 ime set 切回原输入法

PC 端 adb 实测全链路通,固件侧封装成 type 步骤供 batch 调用,中文作文一句话进、逐字出,零乱码。

核心难点二:长任务多会话自动续接 —— 心跳看门狗 + 自愈机制

官方服务器有个黑盒约束:60-70 秒无语音输入强制收尾会话(发 goodbye),且工具调用不刷新该计时器。MCP 工具超时虽已从 10 秒提至 300 秒(issue #1102),但真瓶颈是静默超时。一篇 WPS 作文少说要 3-5 分钟,必须多会话接力。

设备侧三板斧:

  1. TLS 心跳看门狗 + UDP keepalive:双通道保活,对抗 adbd 空闲断连
  2. Goodbye 自愈判据:收到 goodbye 时,若距上次工具调用 ≤90 秒且非自然收尾,自动重开会话并发 7 字 listen detect 文本「继续刚才的任务」
  3. Detect 文本长度受限:实测 16 字被拒、7 字通过,续接细节全靠工具描述引导 LLM「先读 recent 再看屏幕接着写」

实测 8 次自愈全链路通,批预算从 22 秒提至 35 秒(留 25 秒余量给 TTS 轮次),自愈去重(10 秒内重复 goodbye 只续一次)避免服务器重发消耗预算。

核心难点三:陈旧上下文污染 LLM —— 10min TTL + 分隔标记隔离

续接后的新会话里,LLM 不调工具转而去搜服务端长期记忆 —— 旧作文《我的梦想》在记忆里,当前任务《我的武侠梦》不在。test8 实锤:LLM 宣布「已写 3 篇作文」并陷入 4 轮「抱歉我记错了」道歉循环,~50 秒零工具调用。

借鉴业界观察掩码/滑动窗口策略(SWE-agent、JetBrains 2025 验证),我们在受限设备上实现最小版:

  • 写端打标记:launch 成功写入 ==== 新任务开始====
  • 读端截取:status recent 仅返回最后一个标记之后的文本
  • TTL 兜底:10 分钟无写入自动重置缓冲,清理陈旧会话残留

test10 验证:续接读 recent 只看到当前作文,彻底消除「已写 N 篇」幻觉。

核心难点四:自然收尾误判为中断 —— 用户语音时间戳 45s 窗口

用户说「拜拜/休息」后服务器发 goodbye,若此时触发自愈就是「假续接」。关键洞察:服务器仅对静默 60-70 秒的会话强制收尾;用户在场时 goodbye 必伴随近期语音。

在 MAIN_EVENT_SEND_AUDIO 发包处记 last_user_voice_ms_,goodbye 自愈前守卫:用户 45 秒内说过话 = 自然收尾,跳过续接。test9 验证:564s 用户发话 → 568s goodbye → 零自愈;test5 真中断场景用户 80 秒无语音 → 正常续接。done 工具描述扩大到「任意手机任务完成后调用」,兜底 LLM 忘调 done 的情况。

10 轮迭代测试演进:从报错到闭环的完整复盘

轮次暴露问题最小补丁关键指标
test3detect 文本过长被拒(ERROR: Detect is only for wake words)缩为 7 字「继续刚才的任务」续接不再报错
test4无 recent 缓冲,续接看不到旧文本NVS 缓冲 + RecordTypedText可读回已写内容
test5重复道歉循环 + 假续接done 工具 + 语音时间戳守卫收尾不再假续接
test7batch 预算小、自愈重发消耗、recent 重复读空转35s 预算 + 10s 去重 + 5s 读缓存单会话动作数翻倍
test8recent 污染「已写 3 篇」launch 写分隔标记污染减少但未根治
test9自然收尾守卫生效验证45s 语音窗口守卫拜拜后零自愈
test10最终验收读端截取最后一个标记后文本全指标达标

最终验收指标对比:test8 vs test10

指标test8(污染版)test10(最终版)
自愈次数8 次(含假续接)2 次(仅真中断)
done 调用0 次3 次
ERROR有零
recent 污染「已写 3 篇」+ 道歉循环仅当前作文,零幻觉
假续接每次拜拜都续零
会话切换次数~6 次~3 次

工程经验总结

  1. 黑盒服务器约束下,设备侧只能做「最小必要」的补偿:心跳保活、自愈续接、上下文隔离、自然收尾检测 —— 每个机制解决一个确定性痛点,无过度设计
  2. 观察掩码/滑动窗口在受限设备上的落地:写端打标记、读端截取、TTL 兜底,三行代码解决跨会话上下文污染,避免了重写 NVS schema 的工程量
  3. 「用户在场」比「任务完成」更可靠的自然收尾信号:语音时间戳 45 秒窗口零误报,配合 done 兜底,双重保险
  4. ADBKeyboard 广播是 Android 13+ 中文输入的唯一可行解:绕过 KeyCharacterMap,无需 Root、无需无障碍、无需剪贴板,PC 端验证即可上车
  5. 10 轮最小补丁迭代胜过大重构:每轮暴露一个问题、打一个补丁、跑一遍验收,形成可复现的工程演进链路

完整代码已开源:github.com/cljproton/xiaozhi-esp32-android-agent,基于 ESP-IDF release/v6.1,支持 ESP32-S3 系列板卡,烧录即用。

0

评论

博主关闭了当前页面的评论