# 小米音箱接入任意 LLM（open-xiaoai-bridge 刷机方案）

小爱音箱（Xiaomi 智能音箱 Pro / OH2P）刷补丁固件后，通过 open-xiaoai-bridge 把语音链路完全接管到自托管 LLM（DeepSeek / Hermes / OpenClaw），唤醒词、ASR、LLM 全部不经过小米云端。

背景：MiGPT（云端 MIoT 接口，免刷机）项目已停维护，open-xiaoai 系是活跃继任方案。

这条语音链路是 Hermes 之外的一条独立入口 —— 它挂掉时 Hermes 仍能正常工作，反之亦然。这正是 [[homelab-ai-independent|homelab 的 AI 独立性]]所要求的：关键路径不要寄生在单一 AI 服务上。

## 架构

```text
[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`，model `hermes-agent`）——音箱"你好小明"获得联网搜索/工具/记忆能力（DeepSeek API 本身无联网搜索，网页端才有）
- **双通道共存** ：说"小爱同学 xxx"走小米云端（原功能不变），说"你好小明 xxx"走本地链路，互不干扰

## 自制补丁固件（固件版本 > 官方支持时）

官方 release 只到 OH2P v1.58.6 / LX06 v1.94.13，版本更高必须自制。构建脚本自动拉设备当前 romVersion 的 OTA，天然版本匹配。

```bash
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`）。

```batch
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）

```bash
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    # 开机自启
```

## 服务端部署

```bash
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=&lt;key&gt;`（ **无 key 拒绝启动** ）+ `API_SERVER_HOST=0.0.0.0`（跨主机访问），写入 ~*.hermes*.env 后重启 gateway
- ⚠️ gateway 进程内不能自杀：`systemctl restart hermes-gateway` 会被 Hermes 拦截，需外部 shell 执行，或写脚本 + `systemd-run --on-active=3s &lt;script&gt;` 绕过
- 端点：`POST /v1/chat/completions`（OpenAI 兼容，默认端口 8642），认证 `Authorization: Bearer $API_SERVER_KEY`
- bridge config.py openai 段：`base_url: http://&lt;PVE host IP&gt;:8642/v1`、`api_key: <API_SERVER_KEY>`、`model: hermes-agent`
- 效果：音箱问话走 Hermes agent（联网搜索/工具/记忆）；max_tokens 512 也够用（实测联网正常）；agent 响应慢属正常（工具循环）

## 排障（实战踩坑）

1. **唤醒词路由** ：默认 before_wakeup 把"小智"→ xiaozhi 后端（禁用）、"小爪"→ qwenpaw（禁用），只有"小黑"→ openai。唤醒"你好小智"后无响应就是这个。改 config.py 对应 `return "openai"`。
2. **VAD 切碎** ：min_silence_duration 500ms 太敏感，一句话切成多段分别请求。改 1000ms。
3. **<2 字空识别发空请求** ：ASR 把残响识别成"。"。容器内 external_conversation.py `if not text:` 改为 `if not text or len(text.strip()) < 2:`，docker cp 出来改后挂载覆盖。
4. **断连后服务假死** ：音箱连接断开（`Connection reset by peer` + TTS 错误）后 OpenAI 模块状态污染，后续请求 4-6 秒即报"抱歉，我没有收到回复"，`docker compose restart` 即愈。建议挂 watchdog（ping 音箱在线 + 日志近 3 分钟有 No response → 自动 restart）。
5. **INFO 日志看不到 TTS 播报** ：Speak 事件是 DEBUG 级，日志停在"我说：xxx"不代表没播，判断成功与否直接听音箱。
6. 容器内调试用 `*app*.venv/bin/python`（默认 python3 缺 aiohttp），backend 类名是 OpenAIManager。
7. **两个超时别搞混** ：连续对话静默退出 = `wakeup.timeout`（默认 20 秒，改 120 即 2 分钟不说话才"再见"）；等 LLM 回复 = openai 段 `response_timeout`（120→300 给 agent 工具循环留时间）。
8. **xiaomusic 共存** ：音箱双通道下 xiaomusic 可能"自动播放"——列表续播（`continue_play=True`）是设计；搜歌请求看来源 IP（iWebPlayer 网页 vs 音箱语音劫持 `get_ask_by_mina`/`enable_pull_ask`）。
9. **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 &lt;pid&gt;` + `sh /data/init.sh >/dev/null 2>&1 &` 重启 client，无效则僵尸在 bridge 侧。
10. **唤醒词选词（sherpa-onnx KWS 实测）** ：只有"你好小X"四字模式识别稳定（"你好小智"4 次全中）；"小智小智"（重复音节、卷舌音"智"音素边界被吞）和"小智同学"都匹配不上，keywords_score 加到 4.0 也救不回来；且"小智同学"会误触发原生"小爱同学"抢答（发音太像，日志见 `[XiaoAI] 触发唤醒: 小爱同学` + `returned: None`）。换词要同步改 config.py 三处：keywords 列表、before_wakeup 路由判断（`if "小明" in text`）、提示语/退出语文案。
11. **距离 1 米唤醒失效 = 增益问题** ：50cm 能唤醒、1m 不行，但"小爱同学"1m 正常——原生唤醒是硬件 DSP+波束成形，本地 KWS 靠 client 裸音频流。修法：config.py `audio_input.gain` 1.0→**4.0**（上限 8.0，`core/xiaoai.py` 处理，作用于 VAD/KWS/ASR 全链路）。放音乐时本地唤醒失败同理（mic 采到外放+人声，信噪比差），排查先暂停音乐。
12. **"音乐自动播放"溯源链** ：说"播放音乐"→ Hermes agent 工具调用**已执行**（curl xiaomusic 播歌）→ 但 LLM 回复超时（response_timeout 120 太短）→ 用户听到"抱歉，我没有收到回复"以为 AI 没理 → 歌单已起来 + `continue_play=True` 续播 → 音乐盖住后续唤醒测试，形成"AI 听不见"假象。停播：`POST :8090/cmd` 必须带 `did`（否则 422）。
13. **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 真正有价值的就三类

1. 配置读写：sound.cfg（音效）、nightmode.cfg（夜间时段）
2. 网络排障：tcpdump 抓包（`-i wlan0`）、nc 测端口
3. client 生命周期：重启 `/data/init.sh`、改 `/data/open-xiaoai/server.txt` 换服务端

别碰：根分区只读区（改系统文件 = 变砖路径）、device.info / TOKEN（小米云端绑定凭证）。busybox 缺 nohup/setsid/sshpass，`timeout` 语法是 `timeout -t <秒>`。

## 参考

- [coderzc/open-xiaoai-bridge](https://github.com/coderzc/open-xiaoai-bridge)
- [idootop/open-xiaoai（刷机教程/Client）](https://github.com/idootop/open-xiaoai)
