上一篇把房間那盞自發電開關的燈接進了 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 開關接起來,整條路算是走完了,只是房間兩個字不能省。

參考連結