☰
设备与控制器如何对话?EchoMuse 三条 WebSocket 通道与 Capability 协商协议揭秘
2026/10/15 15:25:13 网站建设 项目流程

【免费下载链接】EchoMuse

Alexa replacement and controller for Echo Dot 2nd Generation device.

项目地址:https://gitcode.com/gh_mirrors/ec/EchoMuse
点击查看免费下载

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)的职责如下:

通道路径载荷格式方向用途
/controlJSON 文本双向注册握手、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 用测试钉死):

  1. 按能力协商,不按版本协商。控制器读取Device.capabilities,逐项功能做开关。仪表盘上,设备不具备的能力对应的控件会显示为禁用并说明原因,绝不会变成一个"点了没反应的哑按钮"。
  2. 退化到旧行为,绝不退化成错误答案。双方都忽略未知的 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.

项目地址:https://gitcode.com/gh_mirrors/ec/EchoMuse
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询