小米音箱接入任意 LLM(open-xiaoai-bridge 刷机方案)
PVE
小爱音箱(Xiaomi 智能音箱 Pro / OH2P)刷补丁固件后,通过 open-xiaoai-bridge 把语音链路完全接管到自托管 LLM(DeepSeek / Hermes / OpenClaw),唤醒词、ASR、LLM 全部不经过小米云端。
背景:MiGPT(云端 MIoT 接口,免刷机)项目已停维护,open-xiaoai 系是活跃继任方案。
这条语音链路是 Hermes 之外的一条独立入口 —— 它挂掉时 Hermes 仍能正常工作,反之亦然。这正是 homelab 的 AI 独立性所要求的:关键路径不要寄生在单一 AI 服务上。
架构
[OH2P 音箱: Rust Client] ←WebSocket:4399→ [Bridge 容器 @ Docker 主机] → Hermes Agent API Server(本机,带联网/工具)
唤醒: 本地 KWS(自定义词) ASR: sense_voice INT8 离线 TTS: 小爱原生(tts_speaker: xiaoai)
- 硬性门槛:仅支持两款机型——小爱音箱 Pro(LX06,旧款需拆机)和 Xiaomi 智能音箱 Pro(OH2P,新款 Type-C 免拆)
- 服务端 open-xiaoai-bridge(coderzc)活跃维护,默认 openai 段
session_header: X-Hermes-Session-Key开箱直指 Hermes Agent API Server - HTTP API :9092 可远程播放(/api/play/text)
- 当前部署(2026-08):后端已切 Hermes Agent API Server (
http://<bridge_host>:8642/v1,modelhermes-agent)——音箱"你好小明"获得联网搜索/工具/记忆能力(DeepSeek API 本身无联网搜索,网页端才有) - 双通道共存 :说"小爱同学 xxx"走小米云端(原功能不变),说"你好小明 xxx"走本地链路,互不干扰
自制补丁固件(固件版本 > 官方支持时)
官方 release 只到 OH2P v1.58.6 / LX06 v1.94.13,版本更高必须自制。构建脚本自动拉设备当前 romVersion 的 OTA,天然版本匹配。
git clone https://github.com/idootop/open-xiaoai.git
cd packages/client-patch
# .env:
# MI_USER=小米数字ID # ⚠️ 必须数字 ID,邮箱/手机号报 NumberFormatException
# MI_PASS=密码 # 密码登录必触发异地验证码 → 改用 MI_TOKEN
# MI_TOKEN=V1:xxx # passToken:Chrome 登录 account.xiaomi.com → F12 → Cookies
# MI_DID=音箱DID # 米家 App 设备信息页可查
# SSH_PASSWORD=自定义
docker run --rm --platform linux/amd64 --env-file .env \
-v $(pwd)/assets:/app/assets -v $(pwd)/patches:/app/patches \
idootop/open-xiaoai:latest
# 产物: assets/mico_all_*_<版本>/root-patched.squashfs(刷这个)+ root.squashfs(原版备用)
刷机(Windows)
必须 cmd / PowerShell / Git Bash, 不能用 WSL (interop 干扰 uboot 通信时序,bulkcmd 报 strncmp != 0)。
cd /d Amlogic_Flash_Tool_v6.0.0\bin
update.exe identify :: 拔插电源后立即执行,直到显示版本号
update.exe bulkcmd "setenv bootdelay 15"
update.exe bulkcmd "setenv boot_part boot0"
update.exe bulkcmd "saveenv"
update.exe partition system0 root_patched.squashfs
- 双系统保障:
boot_part boot1切回原厂,刷机失败可回滚 - 刷完 SSH:
ssh -o HostKeyAlgorithms=+ssh-rsa root@<音箱IP>(密码 open-xiaoai) - 恢复出厂设置不改变固件版本,米家 App 设备信息页是准的
Client 安装(音箱 root shell)
mkdir /data/open-xiaoai
echo 'ws://<bridge主机IP>:4399' > /data/open-xiaoai/server.txt # 服务端 IP,不是音箱 IP
curl -sSfL https://gitee.com/idootop/artifacts/releases/download/open-xiaoai-client/init.sh | sh
curl -L -o /data/init.sh https://gitee.com/idootop/artifacts/releases/download/open-xiaoai-client/boot.sh
reboot # 开机自启
服务端部署
mkdir -p /root/docker/open-xiaoai-bridge/models
# 模型: release vad-kws-asr-models(~470MB);zip 解压后是嵌套 models/models/,须移出来删 __MACOSX
curl -O https://raw.githubusercontent.com/coderzc/open-xiaoai-bridge/main/config.py
curl -O https://raw.githubusercontent.com/coderzc/open-xiaoai-bridge/main/docker-compose.yml
# compose 里 OPENCLAW_ENABLE=1 → 换 OPENAI_ENABLE=1
docker compose up -d
接入 Hermes Agent API Server(联网方案,2026-08 实战)
- Hermes gateway 启用 api_server 平台:环境变量
API_SERVER_ENABLED=1+API_SERVER_KEY=<key>( 无 key 拒绝启动 )+API_SERVER_HOST=0.0.0.0(跨主机访问),写入 ~.hermes.env 后重启 gateway - ⚠️ gateway 进程内不能自杀:
systemctl restart hermes-gateway会被 Hermes 拦截,需外部 shell 执行,或写脚本 +systemd-run --on-active=3s <script>绕过 - 端点:
POST /v1/chat/completions(OpenAI 兼容,默认端口 8642),认证Authorization: Bearer $API_SERVER_KEY - bridge config.py openai 段:
base_url: http://<PVE host IP>:8642/v1、api_key: <API_SERVER_KEY>、model: hermes-agent - 效果:音箱问话走 Hermes agent(联网搜索/工具/记忆);max_tokens 512 也够用(实测联网正常);agent 响应慢属正常(工具循环)
排障(实战踩坑)
- 唤醒词路由 :默认 before_wakeup 把"小智"→ xiaozhi 后端(禁用)、"小爪"→ qwenpaw(禁用),只有"小黑"→ openai。唤醒"你好小智"后无响应就是这个。改 config.py 对应
return "openai"。 - VAD 切碎 :min_silence_duration 500ms 太敏感,一句话切成多段分别请求。改 1000ms。
- <2 字空识别发空请求 :ASR 把残响识别成"。"。容器内 external_conversation.py
if not text:改为if not text or len(text.strip()) < 2:,docker cp 出来改后挂载覆盖。 - 断连后服务假死 :音箱连接断开(
Connection reset by peer+ TTS 错误)后 OpenAI 模块状态污染,后续请求 4-6 秒即报"抱歉,我没有收到回复",docker compose restart即愈。建议挂 watchdog(ping 音箱在线 + 日志近 3 分钟有 No response → 自动 restart)。 - INFO 日志看不到 TTS 播报 :Speak 事件是 DEBUG 级,日志停在"我说:xxx"不代表没播,判断成功与否直接听音箱。
- 容器内调试用
*app*.venv/bin/python(默认 python3 缺 aiohttp),backend 类名是 OpenAIManager。 - 两个超时别搞混 :连续对话静默退出 =
wakeup.timeout(默认 20 秒,改 120 即 2 分钟不说话才"再见");等 LLM 回复 = openai 段response_timeout(120→300 给 agent 工具循环留时间)。 - xiaomusic 共存 :音箱双通道下 xiaomusic 可能"自动播放"——列表续播(
continue_play=True)是设计;搜歌请求看来源 IP(iWebPlayer 网页 vs 音箱语音劫持get_ask_by_mina/enable_pull_ask)。 - bridge 僵尸态(2026-08 实测,watchdog 盲区) :容器活着、4399/9092 LISTEN、TCP 握手正常,但 bridge 只回 TCP 层 ACK、无任何应用层响应(无
101 Switching Protocols、无已连接日志),事件循环不再处理新连接。判定法:bridge 日志从最后一次活动后完全空白(无断开/无重连/无 No response——原 watchdog 找 No response 所以失灵)+ 音箱侧tcpdump -i wlan0 -n 'tcp port 4399'抓到握手后无数据流。修法:docker compose restart。音箱侧 client 进程活着但 0 packets = 同款僵尸:先kill <pid>+sh /data/init.sh >/dev/null 2>&1 &重启 client,无效则僵尸在 bridge 侧。 - 唤醒词选词(sherpa-onnx KWS 实测) :只有"你好小X"四字模式识别稳定("你好小智"4 次全中);"小智小智"(重复音节、卷舌音"智"音素边界被吞)和"小智同学"都匹配不上,keywords_score 加到 4.0 也救不回来;且"小智同学"会误触发原生"小爱同学"抢答(发音太像,日志见
[XiaoAI] 触发唤醒: 小爱同学+returned: None)。换词要同步改 config.py 三处:keywords 列表、before_wakeup 路由判断(if "小明" in text)、提示语/退出语文案。 - 距离 1 米唤醒失效 = 增益问题 :50cm 能唤醒、1m 不行,但"小爱同学"1m 正常——原生唤醒是硬件 DSP+波束成形,本地 KWS 靠 client 裸音频流。修法:config.py
audio_input.gain1.0→4.0(上限 8.0,core/xiaoai.py处理,作用于 VAD/KWS/ASR 全链路)。放音乐时本地唤醒失败同理(mic 采到外放+人声,信噪比差),排查先暂停音乐。 - "音乐自动播放"溯源链 :说"播放音乐"→ Hermes agent 工具调用已执行(curl xiaomusic 播歌)→ 但 LLM 回复超时(response_timeout 120 太短)→ 用户听到"抱歉,我没有收到回复"以为 AI 没理 → 歌单已起来 +
continue_play=True续播 → 音乐盖住后续唤醒测试,形成"AI 听不见"假象。停播:POST :8090/cmd必须带did(否则 422)。 - docker compose restart 不重载环境变量 :改 compose 的 LOGLEVEL 后必须
up -d重建才生效;改挂载的 config.py 则 restart 即可。
音箱 root shell 内部探索(2026-08)
刷机后音箱本身就是个可 SSH 的 Linux 小盒子(ssh -o HostKeyAlgorithms=+ssh-rsa root@<音箱IP>),实际翻了一遍:
系统底子
- OpenWrt/LEDE 系:PID 1 是 procd ,SSH 是 dropbear ,Amlogic 4 核 aarch64
- 内存仅 ~241MB(可用 ~150MB),跑不了重东西
- 根分区
/只读且 30.6M 已满;/data(ubi)可写 125.8M,只用了 ~3M
跑着的服务与端口
| 端口 | 进程 | 说明 |
|---|---|---|
| —— | —— | —— |
| 22 | dropbear | SSH(刷机加的) |
| 53 | dnsmasq | 局域网 DNS/DHCP,改配置会影响全家 |
| 8899 | mpas | 小米 MiPlay 多音箱同步协议,对外监听 |
| 54322/54323 | miio_client | 小米 IoT 协议,仅本地回环 |
| 8888 | nano_httpd | mico 内置微 HTTP,仅 127.0.0.1 |
- mpas(:8899) :
/etc/init.d/miplay启动的 MiPlay ServerApp,负责多音箱组队/全屋播放(strings 里全是MultiGroupDeviceInfo、SpeakerSeek、MediaInfo、P2PStatus、handleCmd_SyncState)。协议是私有二进制帧(握手即回$(\0j...+ 13 位毫秒时间戳),不是 HTTP。只有组第二台音箱才有用,单音箱场景是空闲端口。 - nano_httpd(:8888) :小米 mico 框架自带微 HTTP server,只支持 GET,外部不可达,实测各路径无响应——内部调试残件,无实际 handler。
- adbd 是假的 :
/etc/init.d/adbd内容只有setusbconfig boot(USB 配置),不是 ADB daemon,无 adb 端口——ADB over WiFi 不存在。
配置都在哪
| 路径 | 内容 |
|---|---|
| —— | —— |
/data/etc/device.info | SN、MAC、miio_did、miio_key(小米 IoT 设备凭证) |
/data/etc/nightmode.cfg | 夜间模式时段(默认 21:50–06:50),可降音量/灯光 |
/data/etc/sound.cfg | 音效模式:0 强劲低音 / 1 均衡全频 / 2 清晰纯净(注释提示用 micocfg_* 工具读写) |
/data/wifi/wpa.conf | WiFi 密码明文 |
/data/TOKEN | 小米账号 token |
/data/ai-crontab/crontab.dat | 小米 AI 定时任务(二进制) |
结论:shell 真正有价值的就三类
- 配置读写:sound.cfg(音效)、nightmode.cfg(夜间时段)
- 网络排障:tcpdump 抓包(
-i wlan0)、nc 测端口 - client 生命周期:重启
/data/init.sh、改/data/open-xiaoai/server.txt换服务端
别碰:根分区只读区(改系统文件 = 变砖路径)、device.info / TOKEN(小米云端绑定凭证)。busybox 缺 nohup/setsid/sshpass,timeout 语法是 timeout -t <秒>。