【免费下载链接】EchoMuse
Alexa replacement and controller for Echo Dot 2nd Generation device.
EchoMuse 是一个把二手Echo Dot 2 代改造成 Home Assistant 本地语音助手的开源项目,没有 Alexa、没有亚马逊云,你的话术全程留在局域网内。那么,躺在你书架上的 Dot 和运行在电脑里的控制器,到底是怎么"说话"的?答案藏在三条 WebSocket 通道和一套Capability(能力)协商协议里——本文带你用大白话看懂 EchoMuse 的设备与控制器通信机制。
一、先认识"对话双方":设备端与控制器端
EchoMuse 由两个独立部署的组件组成(见 CLAUDE.md):
| 角色 | 位置 | 职责 |
|---|---|---|
| 设备端(device/) | 运行在被改造的 Echo Dot 上 | 采集麦克风、播放声音、驱动 LED 灯环与按键、本地唤醒词打分 |
| 控制器端(controller/) | 运行在你的服务器 / Home Assistant 插件中 | 管理设备、唤醒词检测、与 Assist 语音管线对接、提供 Web 仪表盘 |
控制器并不主动"找上门"。设备启动后通过mDNS 广播(服务名_emcontroller._tcp.local)发现控制器,然后由设备主动拨出三条 WebSocket 连接。这三条通道各司其职,互不越权。
二、三条 WebSocket 通道:各走各的"车道"
完整契约见 docs/device-controller-interface.md。三个"平面"(plane)的职责如下:
| 通道路径 | 载荷格式 | 方向 | 用途 |
|---|---|---|---|
/control | JSON 文本 | 双向 | 注册握手、LED 控制、麦克风开停、按键事件、配置下发、WiFi 管理 |
/data | 二进制 | 双向 | 麦克风 PCM 上行;扬声器与音乐 PCM 下行 |
/shell/{device_id} | 原始二进制 | 按需拨出 | Root shell 代理,仅在控制器下发shell_open指令后才打开 |
打个比方:/control是指挥调度频道(说人话的 JSON),/data是音频专用管道(跑 PCM 裸数据,追求低延迟),/shell则是维修通道(平时锁着门,管理员要修东西才开门)。
三个细节值得注意:
- 心跳保活:控制器每 20 秒发送一次 WebSocket ping,若 30 秒内没有 pong 应答就断开重连。
- 按需才开:
/shell平面在控制器明确要求前不会建立,避免不必要的攻击面。 - 双协议并存:三条通道都有明文
ws://和加密wss://(TLS)两种形态,配合后文的令牌认证。
/data通道上的二进制帧,首字节就是帧类型号。有意思的是帧号按方向命名空间隔离——0x04在两个方向上含义完全不同,这是刻意设计(源码注释见 device/internal/client/data.go):
| 方向 | 帧号 | 含义 |
|---|---|---|
| 控制器 → 设备 | 0x02/0x03 | 语音 PCM 数据块 / 语音流结束 |
| 控制器 → 设备 | 0x04/0x05 | 音乐 PCM 数据块 / 音乐流结束(仅当设备声明了audio_mix) |
| 设备 → 控制器 | 0x01 | 麦克风 PCM 块(16 kHz 单声道) |
| 设备 → 控制器 | 0x07 | 私有监听会话音频(仅当控制器声明了listen_session) |
三、注册握手:设备如何"自报家门"
/control通道一建立,设备立刻发送一条register消息——这是整场对话的开场白(构造逻辑见 device/internal/client/control.go)。简化后的长这样:
{ "type": "register", "device_id": "<稳定设备标识>", "version": "v2.18.0", "capabilities": ["mic", "speaker", "leds", "oww_trigger", "..."], "board": "biscuit", "base_os": "emos", "pairing": true }其中capabilities数组就是协商的核心信号:设备逐条列出自己实现了哪些功能。固件里这个列表由capabilities()函数统一产出(device/internal/client/control.go),代表性能力包括:
| Capability | 含义(通俗版) |
|---|---|
mic/speaker | 能上行麦克风流 / 能播放扬声器音频 |
led_anim | 能按一份动画规格在本地渲染 LED 灯效,而非逐帧接收 |
oww_shadow | 能在本地给唤醒词打分并只报告 |
oww_trigger | 能基于本地唤醒检测采取行动(与上一项刻意分开,下文细说) |
audio_mix | 能把音乐放在独立音频平面、混在语音下方"压低播放"而非暂停 |
oww_local_only | 能私有监听:唤醒词未命中前,一个字节都不上传 |
ambient_light | 仅当环境光传感器真实可读时才声明(条件能力) |
pairing | 主人长按按键 5 秒时,能主动发起配对请求 |
控制器收到注册后,回一条ack表示"登记通过"。注意ack里还带着一个features数组——那是控制器自己的能力清单,协商是双向的,这一点后面单独讲。
四、为什么用 Capability 协商,而不是比版本号?
这是 EchoMuse 兼容性设计的灵魂。设备固件、控制器、emOS 三方独立发版,现实中必然出现"新固件配老控制器"或反向的组合。如果靠比对版本号决定功能开关,控制器就得内置整个发版历史,而且一遇到开发者构建版就失灵。
于是项目立下两条铁律(均由 controller/tests/test_capabilities.py 用测试钉死):
- 按能力协商,不按版本协商。控制器读取
Device.capabilities,逐项功能做开关。仪表盘上,设备不具备的能力对应的控件会显示为禁用并说明原因,绝不会变成一个"点了没反应的哑按钮"。 - 退化到旧行为,绝不退化成错误答案。双方都忽略未知的 JSON 字段和未知消息类型;新字段缺失时存NULL 而不是 0——一台"测不了"的设备,绝不能被显示成"测出来是零"。
两个最能说明"为什么要拆细"的例子:
oww_shadow与oww_trigger是两条能力,不是同一条。早期固件只发布了打分上报(shadow),没有"据此起动会话"的代码。若控制器把"会打分"误当成"会触发",就会放弃自己的唤醒检测、坐等一个永远不会来的触发——表现为一台"打分满分却永远不应答"的神秘故障。ambient_light是条件能力。只有硬件上真有可读的传感器才上报;一个永远出不了数据的传感器实体,比没有实体更糟。
而控制器端如何消费这些字符串?每个能力对应Device类上的一个属性门(property gate),例如led_anim_capable、audio_mix_capable(controller/em_controller.py)。项目还有一条防呆机制:控制器检查的每个字符串,必须都是设备能发出来的,反之亦然——任何一边的拼写错误都会让功能静默失效,测试会直接抓出双向错拼。
五、双向协商:ack里的features与配置下发
协商不止设备"报"、控制器"听"。ack消息携带的features(当前包括ble_adverts_data、listen_session、output_chain、ble_connect)是控制器的能力清单,读法和设备清单完全一样:缺什么,就是控制器做不了什么。
为什么必须双向?一个真实教训:BLE 广播数据从/control平面迁移到了/data平面的0x06帧后,老控制器看不懂新帧会静默丢弃,两端都没有报错——所有广播就这样无声消失了。凡是"迁移"消息,就必须靠双方各自声明能力来对齐;新增消息则可以不管。
协商之后就是日常运维:配置下发(config push)。控制器在连接建立时和每次配置变更时推送config消息,字段采用 camelCase,遵循部分更新语义——非零/非 nil 字段生效,零/nil 字段忽略,所以false和"未设置"靠指针类型区分。所有改动即时生效,无需重启设备。可下发的键涵盖 VAD 阈值、唤醒词模型、AEC/AGC、EQ 均衡、LED 场景、限幅器、barge-in(打断)参数等几十个(完整清单见 docs/device-controller-interface.md 的 "Config push" 一节)。
六、安全层:TLS 与X-EM-Token令牌
三条通道在安全上有统一规则(详见 docs/device-controller-interface.md 的 "Link auth & TLS"):
- 每次拨号都从设备凭证文件重新读取,并携带
X-EM-Token请求头——推送新凭证后,下一次重连自动生效,无需重启。 - 持有 CA 证书的设备只走
wss://(TLS),服务器身份固定为 DNS 名echomuse-controller,永不使用 IP,控制器换地址也不受影响。 - 判定逻辑在
em_linkauth.decide(controller/em_linkauth.py):令牌错误一律拒绝;已存令牌的凭证却未出示,只在"首次出示前"的窗口内放行(凭证推送本身就走这条通道),之后拒绝。 /data和/shell只能来自该设备当前/control连接的同一地址与同一协议,等于把三条通道绑成一个整体。
七、核心要点回顾
| 设计点 | 一句话总结 |
|---|---|
| 三条通道 | JSON 指挥(/control)、二进制音频(/data)、按需 shell(/shell),各走各的车道 |
| 能力协商 | 设备用capabilities数组自报功能,控制器按字符串逐项开关,绝不比版本号 |
| 双向对齐 | ack.features是控制器的能力清单,消息"迁移"必须双方都声明才能切换 |
| 优雅退化 | 未知字段忽略、缺失存 NULL、缺能力的控件禁用并说明原因 |
| 安全兜底 | TLS +X-EM-Token全覆盖,凭证重连即生效,/data//shell绑定控制面地址 |
| 测试钉契约 | controller/tests/test_capabilities.py 双向校验能力字符串,杜绝静默错拼 |
想继续深挖?推荐按这条路线阅读:协议全文 docs/device-controller-interface.md → 监听与隐私模型 docs/listening.md → 语音全链路 docs/voice-pipeline.md → 仪表盘前端 controller/static/dashboard.jsx 与 Web API controller/em_api.py。理解了这个"设备 ↔ 控制器"接口契约,你就掌握了 EchoMuse 扩展到 Echo Show 8 等新硬件的全部钥匙。
【免费下载链接】EchoMuse
Alexa replacement and controller for Echo Dot 2nd Generation device.
相关推荐
Universe 内部通信协议全解析:VNC 像素通道与 Rewarder WebSocket 控制通道
Universe 内部通信协议全解析:VNC 像素通道与 Rewarder WebSocket 控制通道 Universe 是一套用于在大量游戏、网站等真实应用
人工智能强化学习AI AgentWebSocket子协议终极指南:如何实现自定义协议处理和协商机制
WebSocket子协议终极指南:如何实现自定义协议处理和协商机制 WebSocket技术作为实时通信的基石,为Web应用提供了全双工通信能力。而子协议(Sub
后端通信OpenClaw Gateway 架构深度解析:WebSocket 控制平面、握手协议与设备配对
OpenClaw Gateway 架构深度解析:WebSocket 控制平面、握手协议与设备配对 本文基于仓库文档 architecture.md https:
人工智能大模型LLM 网关AI Agent即时通讯
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考