xiaodu bridge 折腾记:把米家和其他 HA 设备接进小度

最近在整理家里的设备,米家有一部分,Zigbee有一部分,空调、窗帘、传感器又散在不同集成里。Home Assistant这边倒是能统一看,自动化也好写,但小度这边一直不够听话:有些设备发现不了,有些能力不全,状态还经常对不上。

后来我把 HA 当中间层来用,做了一个自定义集成 xiaodu bridge(domain xiaodu_bridge)。简单说,它把 HA 里的设备映射成 DuerOS 认识的设备,再交给小度发现和控制。这个集成早先叫 ha-xiaodu,后来改名并换了仓库,下面统一用新名字。链路大概是:

米家 / Zigbee / 蓝牙 / Matter / 其他 HA 集成
        ↓
Home Assistant 实体和房间
        ↓
xiaodu bridge
        ↓
小度云端
        ↓
小度音箱 / 小度 App

xiaodu bridge 的仓库在这里:https://github.com/Jackroyal/xiaodu-bridge,已经收录进 HACS 默认商店。

我建议不要一上来就改小度配置,先把 HA 里的设备搞稳,再装桥接层,最后解决小度云端访问 HA 的问题。公网入口这篇用 DDNS 举例;内网穿透后面有空再补。

第一步:先把设备接进 HA

这一步比后面所有配置都重要。xiaodu bridge 暴露的是 HA 里已有的实体。如果 HA 里点不开、状态不动、实体名乱七八糟,小度那边只会更乱。

接米家

我这里用 HA 的 Xiaomi Home 集成接米家。流程不复杂:

  1. 在 HACS 里安装 Xiaomi Home。
  2. 打开 设置 -> 设备与服务 -> 添加集成。
  3. 搜索 Xiaomi Home,登录米家账号。
  4. 选择家庭,等设备同步进来。
  5. 先在 HA 页面里手动开几次灯、空调、插座,确认状态能变。

接完别急着下一步,先在 HA 设备页里看一遍。Xiaomi Home 可以直接把米家里的设备名称和房间同步过来,所以大部分设备不用在 HA 里重新起名。要确认的主要是两件事:

1. 名字改成适合说话的。
   比如:客厅吸顶灯、卧室空调、书房插座。

2. 房间分配。
   客厅就是客厅,卧室就是卧室。
   后面开小度房间同步会省很多事。

如果发现某个设备没带上米家名称,或者变成了 switch.switch_1 这种不好念的名字,再回米家 App 里改名,或者直接在 HA 里调整。这种名字直接丢给小度,语音体验会很差。

其他设备怎么接

Zigbee2MQTT、ZHA、Matter、品牌集成都行,关键是它们最后要变成 HA 能控制的标准实体。

常见对应关系大概这样:

light          -> 开关、亮度、色温、颜色
switch         -> 开关
climate        -> 空调开关、温度、模式、风速
cover          -> 窗帘打开、关闭、位置
fan            -> 风扇开关、风速、模式
sensor         -> 温度、湿度
vacuum         -> 扫地机启动、暂停、回充
media_player   -> 播放、暂停、音量

有些设备没有现成的小度能力,比如某个自定义场景或私有协议设备。能包装成 switch 的,就先在 HA 里做成一个开关实体,再让小度控制这个开关。不能包装的,就别硬塞进去。

安装 xiaodu bridge

装法有两种。用 HACS 方便一点,手动复制更适合调试。

HACS 安装

xiaodu bridge 已经进了 HACS 默认商店,不用再加自定义仓库(本文按 v0.9.17 写):

  1. HACS → Integrations,搜索 xiaodu bridge,下载。
  2. 重启 HA。

手动安装

先把仓库拉下来,然后把 custom_components/xiaodu_bridge 整个目录复制到 HA 的 custom_components 下面:

git clone https://github.com/Jackroyal/xiaodu-bridge.git
cp -R xiaodu-bridge/custom_components/xiaodu_bridge /config/custom_components/

用 HAOS 的 Terminal and SSH 时,也可以直接从电脑传:

scp -r custom_components/xiaodu_bridge root@192.168.1.10:/config/custom_components/

这里面的 192.168.1.10 是我举的例子,换成你自己的 HAOS 内网地址。

复制完检查一下路径:

正确:/config/custom_components/xiaodu_bridge/manifest.json
错误:/config/custom_components/xiaodu_bridge/xiaodu_bridge/manifest.json

多套一层是老问题。确认没套错后重启:

ha core restart

小度为什么必须能访问 HA

正式接入时,DuerOS 云端会回调 xiaodu bridge 提供的几个 HTTPS 接口:

/api/xiaodu/oauth/authorize
/api/xiaodu/oauth/token
/api/xiaodu/service

所以纯内网地址不能完成正式接入。小度云端不是运行在你家局域网里的,它必须能从外面访问到你 HA 里的这些路径。

我的结构是这样:

小度云端
  -> 公网域名
  -> 路由器端口转发
  -> HAOS 反向代理
  -> HA Core 8123
  -> xiaodu bridge

用 DDNS 暴露公网入口

我这次走的是「有公网 IPv4 + DDNS」的路线。它不是唯一方案,只是对我来说最直接。没有公网 IP 的话可以看内网穿透,这部分这篇先不写。

1. 确认真的有公网 IPv4

去路由器看 WAN IP,再用手机流量查一下出口 IP。两边一致才算比较靠谱。

如果路由器 WAN IP 是 100.x.x.x、10.x.x.x、172.16.x.x 或者 192.168.x.x,基本就是运营商侧又套了一层,不是能直接入站的公网 IP。这种就先别折腾端口转发了。

2. 配域名和 DDNS

准备一个自己的域名,比如 ha.example.com。DNS 里加一条 A 记录:

ha.example.com.    A    你的公网IP

公网 IP 会变的话,就配 DDNS。路由器自带 DDNS、NAS 上的脚本、Cloudflare API 更新都可以,关键是 IP 变了以后域名要跟着变。

测试阶段建议 DNS 用“仅 DNS”,别急着套 CDN,不然证书和回源问题会混在一起,排错会麻烦很多。

3. 端口转发

国内家宽对 80 和 443 的入站管控挺常见,有的地区直接拦,有的时好时坏。所以我没把公网入口放在 443,用的是 8443 这种非标端口:

外部协议:TCP
外部端口:8443
内部目标:HAOS 主机 IP,例如 192.168.1.10
内部端口:443

外面的 8443 是给公网访问用的;里面的 443 还是给 HAOS 反向代理监听。你的反向代理如果也能改成其他监听端口,也可以自己调整,但公网那一段尽量别用 443。

另外,不要把 HA 的 8123 直接暴露出去。公网入口统一走 HTTPS,后面由反向代理转到 HA Core 的 8123。

4. 证书和反向代理

HAOS 上可以用 Let’s Encrypt 和 NGINX SSL proxy。我用 DNS 校验,这样不依赖 80 端口。这两个 add-on 的证书目录和文件名默认就是配套的:Let’s Encrypt 签出来的证书放到 /ssl,NGINX SSL proxy 默认也从 /ssl 读同名文件。所以装完不需要手动挪证书,也不用改证书路径。

Cloudflare DNS 的例子大概是这样:

challenge: dns
dns:
  provider: dns-cloudflare
  cloudflare_api_token: <你的Cloudflare API Token>
domains:
  - ha.example.com
email: <你的邮箱>

NGINX SSL proxy 只需要配域名和其他选项:

domain: ha.example.com
hsts: max-age=31536000; includeSubDomains
client_max_body_size_megabytes: 1
cloudflare: false

为了少暴露东西,我建议反向代理只放行小度需要的路径,其他公网请求直接 404。至少要放行:

/api/xiaodu
/api/xiaodu/oauth/authorize
/api/xiaodu/oauth/token
/api/xiaodu/service

如果 HA 里还有别的 OAuth 回调路径,再按需放行,不要为了省事把整个 HA 管理页面开放到公网。

然后到 HA 的 设置 -> 系统 -> 网络 里设置外部地址:

https://ha.example.com:8443

如果外网请求返回 400,HA 日志里又出现 forwarded 或 trusted proxy 相关内容,多半是反向代理网段没进 trusted_proxies。把 NGINX 所在网段加进去,并开启 use_x_forwarded_for。

在小度后台建技能

登录小度开发者平台,创建一个智能家居技能。这里会用到几样东西:

botId                 小度后台基础信息里的技能 ID
回调地址               小度平台生成的回调地址
Client_Id             自己定义
ClientSecret          自己定义
授权地址               HA 里的授权地址
Token地址              HA 里的 token 地址
WebService            HA 里的小度服务地址

假设公网地址是 https://ha.example.com:8443,那三个 HA 地址就是:

https://ha.example.com:8443/api/xiaodu/oauth/authorize
https://ha.example.com:8443/api/xiaodu/oauth/token
https://ha.example.com:8443/api/xiaodu/service

Client_Id 和 ClientSecret 不是百度后台分配的,是自己定义,然后把两边配一致。Secret 别太短,别用 123456 这种。

还有一个容易搞反的地方:回调地址 不是你的 HA 地址,而是小度平台生成出来的地址。它要填到 HA 集成里。

在 HA 里配置 xiaodu bridge

重启 HA 后,打开:

设置 -> 设备与服务 -> 添加集成 -> xiaodu bridge

按下面这些填:

界面字段填什么示例
Client_Id自定义客户端 IDdueros_xxx
ClientSecret自定义随机密钥<长随机字符串>
小度技能ID(botId,控制台基础信息页可见)小度后台技能 ID12345678
回调地址(小度平台生成,不可修改)小度平台生成的地址https://xiaodu.baidu.com/...
公网访问地址(HTTPS)HA 公网基础地址https://ha.example.com:8443

保存后集成会把授权地址、Token地址、WebService 显示出来。再对照小度后台检查一遍,尤其是端口:

https://ha.example.com:8443/...

用 8443 后,所有相关地址都要带 :8443。不能有些带端口,有些不带。

选择要暴露给小度的设备

添加成功后,打开集成条目 →「配置」,会进入 小度中枢 页。这里我建议别一次全选。

注意:从 v0.9.15 起,设备暴露改成按需选择。全新安装、或者从没保存过设备选项的实例,默认不会向小度发送任何设备,必须在这里选好并保存。

先拿两三台设备练手,比如客厅灯、卧室空调、一个插座。流程是:

  1. 点「添加 / 移除设备」。
  2. 选择要暴露的 HA 设备,顺便决定要不要「同步 HA 房间到小度」。
  3. 点进某个设备,先选「设备能力」勾选能力;需要时再进「高级配置」限制能力、隐藏实体、绑定语义角色或改小度侧的展示名。
  4. 保存后让小度重新发现。
  5. 用小度后台的模拟测试试一遍。

能力大概可以这样理解:

灯:开关、亮度、色温、颜色
插座:开关
空调:开关、目标温度、模式、风速
窗帘:打开、关闭、位置
传感器:温度、湿度
扫地机:启动、暂停、回充

开关能力始终启用,不能取消。温湿度这种只读能力按需勾。你不需要把 HA 里的每个实体都丢给小度,暴露得越精准,后面误触发越少。

测试

我一般按这个顺序来:

  1. 小度开发者后台触发设备发现。
  2. 看设备有没有出现在小度侧。
  3. 查询一个温湿度传感器。
  4. 打开一个灯或插座。
  5. 关掉它。
  6. 再用音箱和 App 试试真实语音。

HAOS 上可以看日志:

ha core logs -f | grep "Xiaodu access"

成功的发现请求会看到类似这样的内容:

kind=dueros
namespace=DuerOS.ConnectedHome.Discovery
name=DiscoverAppliancesRequest
result=DiscoverAppliancesResponse
status=200

如果这里连请求都没有,就别急着改 HA 配置,先查域名、端口转发、证书和反向代理。

语音可以试这些:

小度小度,打开客厅灯。
小度小度,把卧室空调调到 26 度。
小度小度,关闭书房插座。
小度小度,客厅温度是多少?

我踩到的坑

集成搜索不到

先看目录有没有多套一层:

错误:/config/custom_components/xiaodu_bridge/xiaodu_bridge
正确:/config/custom_components/xiaodu_bridge

再看 manifest.json 是否存在,然后重启 HA。

HA 里正常,小度发现不到

按顺序查:

1. 小度后台 WebService 地址对不对。
2. 地址里的端口有没有少。
3. 公网能不能访问 HA。
4. 反向代理有没有放行 /api/xiaodu。
5. HA 集成里有没有选中设备。
6. HA 日志里有没有 Xiaodu access。

别只盯音箱。正式链路里,小度云端访问不到 HA,音箱喊破天也没用。

米家设备状态不准

这时候问题多半不在 xiaodu bridge。先回 Xiaomi Home 或米家集成里看:HA 页面能不能控制,状态会不会变,实体有没有被禁用。HA 侧不稳定,桥接层写得再好也没办法。

授权页打不开

多半是公网入口没通。可以从外面测:

curl -I https://ha.example.com:8443/api/xiaodu/service

能拿到 HTTP 状态码,说明入口至少通了。TLS 都失败的话,先解决证书、DNS 和端口转发。

控制失败但发现成功

这类多半是能力没勾全,或者设备类型映射得不对。比如空调要有温度、模式、风速这些能力;窗帘要有打开、关闭能力。回集成选项里改完,再让小度重新发现。

安全上要注意

公网入口开了以后,就别图省事了:

1. 只转发需要的非标端口,比如 8443。
2. 不把 HA 管理页面对公网开放。
3. ClientSecret 用长随机串。
4. Cloudflare Token 只给必要权限。
5. Token、私钥、真实 IP 别提交到 Git。
6. 定期更新 HA、Xiaomi Home、xiaodu bridge 和加载项。

最后

这套东西的核心不是“米家转小度”,而是让 HA 当家里的统一中间层。米家、Zigbee、Matter、其他品牌集成都先进 HA,xiaodu bridge 再把这些实体交给小度。

设备命名和房间分好,后面语音体验会顺很多。先暴露少量设备测试,稳定后再慢慢加。

这篇先记录到这,内网穿透那部分等后面补。