上一篇把房间那盏自发电开关的灯接进了 Home Assistant,手机可以控制之后,接下来想做的就是躺在床上用语音开关灯。

做法是用 home-assistant-matter-hub 把 HA 的实体包成一个 Matter 桥接器,由客厅的 Google TV 当中枢,整条路都在区网里,不经过云端。架设本身不难,花时间的是配对:Google Home 一直显示「找不到装置」,查到最后发现是两个问题叠在一起,一个是 Google 对开发中装置的限制,另一个是我自己伺服器的防火墙。

下面照顺序整理:为什么选 Matter、matter-hub 的设定、配对要经过哪几关,以及每一关卡住时我怎么确认、怎么修。最后记录配对完之后,语音控制实际用起来的几个问题。

为什么选 Matter#

HA 接 Google Home 大概有三种做法:

Nabu Casa Cloud手动 google_assistant 整合Matter 桥接
费用月费订阅免费免费
控制路径手机 → Google 云 → Nabu Casa → HA手机 → Google 云 → 你家对外的 HA区网直连
要对外开放不用要把 /api/google_assistant 给 Google 打不用
断网能用不行不行可以
前提付费Google Cloud 专案、OAuth、服务帐号家里要有 Matter 中枢

Matter 桥接需要一台 Matter 中枢。我原本以为家里的 SwitchBot Hub 3 可以,查了官方说明才知道它只是桥接器,还需要另一台第三方平台的家庭闸道:

所以一度准备走第二条路。后来想到客厅的 Google TV Streamer 4K 本身就支援 Matter 中枢,就改回第三条:不用云、不用对外开任何端点,我的 CrowdSec 也不用担心误挡 Google 的伺服器。

架 matter-hub#

原本的专案(t0bst4r)在 2026 年 1 月停止维护并封存,最后一版是 v3.0.4,现在由 RiDDiX 的 fork 接手。我一开始用的是原专案,后来因为一个崩溃的 bug 换到 fork(后面会讲),新架的话建议直接用 fork 的 image。

yaml
services:
  matter-hub:
    image: ghcr.io/riddix/home-assistant-matter-hub:latest
    container_name: matter-hub
    restart: unless-stopped
    network_mode: host                     # 必要,Matter 靠 mDNS 和 IPv6 做探索
    environment:
      - HAMH_HOME_ASSISTANT_URL=http://<你的HA位址>:8123/
      - HAMH_HOME_ASSISTANT_ACCESS_TOKEN=${HA_TOKEN}
      - HAMH_HTTP_PORT=8482                # 管理网页
      - HAMH_MDNS_NETWORK_INTERFACE=enp6s0 # 主机有多张网卡时,锁定真正的区网介面
      - TZ=Asia/Taipei
    volumes:
      - ./data:/data

几个设定上的细节:

  • network_mode: host 不能省,Matter 的探索走 mDNS(UDP 5353)和 IPv6,bridge 模式收不到。
  • 会用到 5353(mDNS)、5540/5541(Matter)、8482(管理网页)这几个埠,文件有写要开。
  • 权杖用 HA 的长期存取权杖:左下角头像 → 安全性 → 最下面「长期存取权杖」。我放在 .env,权限 600。
  • 我的主机除了区网还有 WireGuard、Incus 的网段、NanoKVM 的 USB 网卡和一堆 docker bridge,不限定介面的话 log 会一直出现 wg0: send Unknown system error -126。HAMH_MDNS_NETWORK_INTERFACE 设成区网那张网卡之后就没了。这只影响 log,跟后面的配对问题无关。

起来之后到 :8482 的管理网页建立 bridge。我只想放两盏灯进去,原本想用 entity_id 筛选,但下拉选单里没有这个选项:

用 pattern 填完整的 entity ID 就是精确比对。domain 选 switch 会把所有开关都送出去,platform 选 esphome 会连 WiFi 讯号、IP 这些诊断感测器一起带过去,这两个要小心。

存档后会产生配对码和 QR code,Google Home App 的路径是 + → 设定装置 → 已有装置吗? → Matter 装置。

配对要过的几关#

先把配对的流程拆开,后面比较好对照是哪一关出问题:

手机用 mDNS找到桥接器 Google 检查VID / PID 手机用 IPv6连 UDP 5540 配对完成手机与中枢建立连线

麻烦的是,第二关和第三关失败时,Google Home 显示的都是同一个画面:

而且两种情况下 matter-hub 的 log 都是空的,看不出手机有没有找到它。我两关都卡了。

第一关:mDNS 探索#

我一开始怀疑的是网路分段:手机和 Google TV 都在 5G,server 是有线。不过配对阶段要通的是手机和 server 之间,Google TV 是配对完成后才接手当中枢的,所以电视在哪个频段不影响配对。

要确认手机到底有没有找到桥接器,最直接的方式是听区网上的 mDNS。我是在 server 上写了个小程式加入群播组、听 5353,结果是这样:

text
手机 查询 _matterc._udp   ×30
server 回应                ×30
matter-hub 收到的连线       0

手机查了 30 次,server 也回了 30 次,但手机一次都没有连过来。代表第一关是过的,问题出在后面。

TIP

有装 avahi 的话,用 avahi-browse -rt _matterc._udp 也能看到桥接器广播的内容,包括下一节会用到的 TXT 栏位。

第二关:Google 的 VID/PID 检查#

把 server 的回应拆开来看,TXT 纪录里有一个 VP=65521+32768,换成十六进位就是 Vendor ID 0xFFF1、Product ID 0x8000。0xFFF1 是 Matter 保留给测试用的厂商码,matter-hub 这种没有经过 CSA 认证的专案都用它。

照 Google 的开发者文件,要让 Google Home 配对开发中的 Matter 装置,得先在 Google Home Developer Console 建一个 Matter integration,VID 和 PID 要跟装置一致。测试用的 VID 可以选联盟配给的 0xFFF1 到 0xFFF4:

matter-hub 广播的是 0xFFF1 / 0x8000,而我的帐号底下根本没有任何 integration。

NOTE

matter.js 的文件写说 Google 遇到未认证装置会跳出「允许配对未认证装置」的确认框,但那是旧版的行为。我实际遇到的是直接显示「找不到装置」,没有任何确认框。

登记的步骤:

开专案

到 Google Home Developer Console,用跟 Google Home App 同一个帐号登入,建立一个专案。

新增 Matter integration

点 Add Matter integration,第一次会先出现说明页,按 Next: Develop、Next: Setup。

填产品资讯

Product name 自己取,Device type 选 Outlet(On-Off Plug-in Unit)。

填 VID 和 PID

Vendor ID 选 Test VID 的 0xFFF1,Product ID 填 0x8000,要跟桥接器广播的值一致。

存好之后回 App 再扫一次,画面变成了「侦测到这部装置在附近」,这关就过了:

第三关:IPv6 连线#

按下继续之后,又是「找不到装置」:

这次 matter-hub 的 log 一样是空的。我先排除了一个方向:手机一直重复查询,我怀疑无线 AP 把有线端的群播丢掉,所以写了个小程式把 server 的回应直接单播给手机,送了 122 次,还是没有连线。所以手机确实拿到了纪录。

剩下没验证的是连线本身。广播里只有 IPv6 位址(Matter 规范规定走 IPv6),手机要用这个位址连 UDP 5540。我之前从同网段的 NanoKVM 用 ping6 测过这几个位址,都有回应,就没再往这里查。

后来换成测 TCP。用 HA 的 8123 当目标,同一台机器、同一个位址,分别用 ping、IPv4 和 IPv6 连:

bash
ping -6 -c 3 <server的IPv6位址>
curl -4 -m 5 -s -o /dev/null -w '%{http_code}\n' 'http://<server的IPv4位址>:8123/'
curl -6 -m 5 -s -o /dev/null -w '%{http_code}\n' 'http://[<server的IPv6位址>]:8123/'

结果:

text
ping6        → 通(0.6ms)
IPv4 8123    → 200
IPv6 8123    → Connection timed out

IPv6 的 ping 会通,TCP 却 timeout。timeout 和 connection refused 不一样,refused 是对方回绝,timeout 是封包被丢掉没有任何回应,通常是防火墙。

原因:ufw 的区网规则只有 IPv4#

这台 server 用 ,预设挡掉所有进来的连线。一周前我在桌机连不到 HA 网页的时候,加过这种规则:

bash
sudo ufw allow from 192.168.50.0/24 to any port 8123 proto tcp comment 'HA UI from LAN'

来源指定的是 IPv4 网段,所以 ufw 只会建立 IPv4 的规则。用 sudo ufw status verbose 看就很清楚(节录):

text
22/tcp               ALLOW IN    Anywhere
8123/tcp             ALLOW IN    192.168.50.0/24    # HA UI from LAN
22/tcp (v6)          ALLOW IN    Anywhere (v6)

没指定来源的 Anywhere 规则都有对应的 (v6),而所有限定区网来源的规则都没有。换句话说,这台机器在 IPv4 对区网开了需要的埠,在 IPv6 对区网一个埠都没开。

ping6 会通,是因为 ufw 的预设规则放行 ICMPv6(IPv6 的邻居探索需要它),mDNS 也有预设规则放行。所以探索、ping 都正常,只有真正建立连线的 TCP 和 UDP 被挡掉,而 Google Home 把这种情况也显示成「找不到装置」。

修法#

我加了两条规则,让区网的 IPv6 跟 IPv4 一样可以进来:

bash
sudo ufw allow from fc00::/7  comment 'LAN IPv6 (ULA)'
sudo ufw allow from fe80::/10 comment 'LAN IPv6 (link-local)'

fc00::/7 是 私有位址,fe80::/10 是 ,两者都无法从网际网路路由进来。加完之后用 IPv6 连 8123 从 timeout 变成 200,再配对一次就成功了。

fe80::/10 不能省。配对成功后的 log 里,手机和 Google TV 跟 matter-hub 建立的两条连线,来源都是 fe80:: 开头的 link-local 位址,只开 ULA 的话配对完一样会连不上。

WARNING

这两行等于让区网内所有 IPv6 装置可以连这台机器的所有埠,比我 IPv4 逐埠开放的范围还宽。我家的区网都是自己的装置,所以这样设。如果你的区网有不信任的装置,可以只对这两个范围开放 Matter 用的 5540/5541,但 fe80::/10 要包含在内。

配对之后#

配对成功后我又加了几个装置:落地灯、门铃探照灯、冷气、除湿机、空气清净机、猫台座的电源,最后是 8 个。HA 的 script、automation、input_boolean 没有放进去,它们在 Google Home 会变成一堆插座。监视器的开关也没放,怕语音误触。

接着遇到三个问题。

语音一定要讲房间#

第一次对 Google 说「关灯」,它关掉的是落地灯和门铃探照灯,房间的灯没有动:

房间那两盏灯在 HA 里是 switch,matter-hub 会把它们当成插座(OnOffPlugInUnit)送出去,而 Google 的「开灯」「关灯」只作用在装置类型是灯的项目。在 Google Home App 长按装置 → 设定 → 装置类型改成「灯」,再帮每个装置指定房间。

改完之后换成另一个问题:现在它们都是灯了,只说「开灯」的话,Google 会把落地灯、妈妈房间的灯一起打开。所以实际上我每次都是说「Hey Google,开我房间的灯」,讲到房间才只会动到那一盏。

冷气让整个桥接器崩溃#

加入冷气之后,整个 bridge 挂掉并不断重启,其他装置也跟着离线:

text
climate_leng_qi.thermostat
minHeatSetpointLimit (1600) must be ≤ minCoolSetpointLimit (1600) − minSetpointDeadBand (200)
Failed to update bridge due to error: [constraint]

Matter 的恒温器规范要求制热下限比制冷下限低至少 2°C,我的冷气两个下限都是 16°C。设定已经写进 data/,每次启动都会撞到,管理网页也进不去,只能先停掉容器,用一次性的容器改 bridge 设定档,把冷气拿掉再启动。

原专案已经封存,不会再修。RiDDiX 的 fork 在 8 月改了恒温器温度死区的处理(#435),之后也有人回报跟我一样的状况(#454「Thermostat with equal min_temp for heat/cool crashes entire bridge process」)。迁移只要把 image 换成 fork 的,data/ 不用动,fabric 和 node 的 ID 都没变,Google Home 不需要重新配对。换完之后冷气被辨识成 RoomAirConditioner,可以正常加入。

冷气只能指定绝对温度#

「冷气调高一度」没有反应,log 是:

text
Invoke « thermostat.setpointRaiseLower
Validation-Error 0x80: Missing mandatory field mode in field mode

这个指令规格上要带模式和调整量,看起来 Google 送来的封包少了模式栏位。「冷气设成 26 度」走的是写入目标温度,可以正常运作,开关冷气也没问题,目前就先这样用。

排查顺序#

整理成一张表,遇到「找不到装置」可以照顺序检查:

检查项目怎么确认我的结果
手机有没有收到 mDNS 回应听 5353,或 avahi-browse -rt _matterc._udp有,30 问 30 答
桥接器用的 VIDTXT 的 VP 栏位,65521 就是 0xFFF1测试 VID
Developer Console 有没有登记同一个 Google 帐号,VID/PID 一致没有,登记后出现「侦测到附近」
IPv6 的 TCP/UDP 通不通用 curl -6 连一个 TCP 服务,不能只看 pingping 通、TCP timeout
防火墙有没有 IPv6 区网规则sudo ufw status verbose 看有没有 (v6)没有
link-local 有没有放行fe80::/10补上后配对成功

现在躺在床上说一句「Hey Google,关我房间的灯」就能关灯。跟上一篇的 RF 开关接起来,整条路算是走完了,只是房间两个字不能省。

參考連結