🔑 Key Insights
✦ AI·GENThe author wanted to control the lights in Home Assistant by voice and chose a route that avoids the cloud: using home-assistant-matter-hub to wrap HA entities into a Matter bridge, with the Google TV Streamer 4K in the living room acting as the hub. During pairing, Google Home showed "Couldn't find device" twice, for completely different reasons. The first time, the bridge used the Matter test vendor ID 0xFFF1, which requires first creating an integration with a matching VID/PID in the Google Home Developer Console. The second time, the server's ufw only had IPv4 LAN rules; ICMPv6 is allowed by default so ping6 worked, but the actual IPv6 traffic to UDP 5540 was dropped, and since the post-pairing connections use link-local addresses, fe80::/10 also has to be allowed. The post includes how to confirm each stage and the commands to fix it, and records how things work in practice after pairing: the lights need to be changed to the Light type and assigned a room, and voice commands must name the room; the air conditioner's setpoint deadband settings crashed the entire bridge, which was resolved by switching to the fork maintained by RiDDiX; and the air conditioner only accepts absolute temperatures.
In the previous post I connected my room's self-powered kinetic switch light to Home Assistant. Once I could control it from my phone, the next thing I wanted was to turn the light on and off by voice while lying in bed.
The approach is to use home-assistant-matter-hub to wrap HA entities into a Matter bridge, with the Google TV in the living room acting as the hub. The whole path stays on the local network and never goes through the cloud. The setup itself isn't hard. What took time was pairing: Google Home kept showing "Couldn't find device", and in the end it turned out to be two problems stacked on top of each other. One was Google's restriction on in-development devices, and the other was my own server's firewall.
Below, in order: why I chose Matter, how matter-hub is configured, which stages pairing goes through, and how I confirmed and fixed each stage where I got stuck. At the end I note a few issues I ran into actually using voice control after pairing.
Why Matter#
There are roughly three ways to connect HA to Google Home:
| Nabu Casa Cloud | Manual google_assistant integration | Matter bridge | |
|---|---|---|---|
| Cost | Monthly subscription | Free | Free |
| Control path | Phone → Google cloud → Nabu Casa → HA | Phone → Google cloud → your internet-facing HA | Direct on the LAN |
| Needs public exposure | No | Yes, /api/google_assistant must be reachable by Google | No |
| Works without internet | No | No | Yes |
| Prerequisite | Paid plan | Google Cloud project, OAuth, service account | A Matter hub at home |
A Matter bridge needs a Matter hub. I initially assumed the SwitchBot Hub 3 I already had would work, but after reading the official docs I learned it's only a bridge and still needs a separate home hub from a third-party platform:
So for a while I was going to take the second route. Then I remembered that the Google TV Streamer 4K in the living room supports acting as a Matter hub, so I switched back to the third option: no cloud, no publicly exposed endpoints, and no need to worry about my CrowdSec accidentally blocking Google's servers.
Setting up matter-hub#
The original project (t0bst4r) stopped being maintained and was archived in January 2026, with v3.0.4 as the final release. It's now carried on by RiDDiX's fork. I started with the original project and later switched to the fork because of a crash bug (more on that later). For a new setup, I'd recommend going straight to the fork's image.
services:
matter-hub:
image: ghcr.io/riddix/home-assistant-matter-hub:latest
container_name: matter-hub
restart: unless-stopped
network_mode: host # Required: Matter relies on mDNS and IPv6 for discovery
environment:
- HAMH_HOME_ASSISTANT_URL=http://<your-HA-address>:8123/
- HAMH_HOME_ASSISTANT_ACCESS_TOKEN=${HA_TOKEN}
- HAMH_HTTP_PORT=8482 # Admin web UI
- HAMH_MDNS_NETWORK_INTERFACE=enp6s0 # On a host with multiple NICs, pin the actual LAN interface
- TZ=Asia/Taipei
volumes:
- ./data:/dataA few configuration details:
network_mode: hostcan't be left out. Matter discovery uses mDNS (UDP 5353) and IPv6, which bridge mode doesn't receive.- It uses ports 5353 (mDNS), 5540/5541 (Matter), and 8482 (admin UI); the docs say to open them.
- The token is an HA long-lived access token: avatar in the bottom-left → Security → "Long-lived access tokens" at the very bottom. I keep it in
.envwith permissions 600. - Besides the LAN, my host has WireGuard, Incus subnets, the NanoKVM's USB network adapter, and a pile of docker bridges. Without pinning the interface, the log kept printing
wg0: send Unknown system error -126. SettingHAMH_MDNS_NETWORK_INTERFACEto the LAN NIC made it go away. This only affects the log and has nothing to do with the pairing problems later on.
Once it's running, create a bridge in the admin UI at :8482. I only wanted to include two lights and planned to filter by entity_id, but that option isn't in the dropdown:
Using pattern with the full entity ID gives an exact match. Be careful with the other two: choosing switch under domain exports every switch, and choosing esphome under platform also brings along diagnostic sensors like WiFi signal and IP.
After saving, it generates a pairing code and a QR code. In the Google Home app the path is + → Set up device → Already have a device? → Matter device.
The stages of pairing#
First, let me break the pairing flow down, so it's easier to tell later which stage is failing:
The annoying part is that when the second or third stage fails, Google Home shows the exact same screen:
And in both cases the matter-hub log is empty, so there's no way to tell whether the phone found it. I got stuck at both stages.
Stage 1: mDNS discovery#
My first suspicion was network segmentation: the phone and the Google TV are both on 5 GHz, while the server is wired. But during pairing, what needs to work is the connection between the phone and the server. The Google TV only takes over as the hub after pairing is done, so which band the TV is on doesn't affect pairing.
The most direct way to confirm whether the phone actually found the bridge is to listen to mDNS on the LAN. I wrote a small program on the server that joins the multicast group and listens on 5353. The result:
phone query _matterc._udp ×30
server responses ×30
connections to matter-hub 0The phone queried 30 times and the server answered 30 times, but the phone never connected once. So stage 1 passes, and the problem is further along.
TIP
If you have avahi installed, avahi-browse -rt _matterc._udp also shows what the bridge is advertising, including the TXT field used in the next section.
Stage 2: Google's VID/PID check#
Breaking down the server's response, the TXT record contains VP=65521+32768, which in hexadecimal is Vendor ID 0xFFF1 and Product ID 0x8000. 0xFFF1 is a vendor ID that Matter reserves for testing, and projects like matter-hub that aren't CSA-certified all use it.
According to Google's developer docs, for Google Home to pair an in-development Matter device, you first have to create a Matter integration in the Google Home Developer Console with a VID and PID that match the device. For testing, you can pick one of the alliance-assigned VIDs from 0xFFF1 to 0xFFF4:
matter-hub advertises 0xFFF1 / 0x8000, and my account had no integrations at all.
NOTE
The matter.js docs say Google shows an "allow pairing of uncertified device" confirmation dialog when it encounters an uncertified device, but that's the old behavior. What I actually got was "Couldn't find device" directly, with no dialog at all.
Steps to register:
Go to the Google Home Developer Console, sign in with the same account as the Google Home app, and create a project.
Click Add Matter integration. The first time, an intro page appears; click Next: Develop, then Next: Setup.
Pick any Product name, and choose Outlet (On-Off Plug-in Unit) as the Device type.
For Vendor ID choose the Test VID 0xFFF1, and for Product ID enter 0x8000. They have to match the values the bridge advertises.
After saving, I went back to the app and scanned again. The screen changed to "Device detected nearby", so this stage was cleared:
Stage 3: the IPv6 connection#
After tapping Continue, it was "Couldn't find device" again:
The matter-hub log was empty this time too. I ruled out one direction first: since the phone kept repeating its queries, I suspected the wireless AP was dropping multicast from the wired side. So I wrote a small program to unicast the server's response directly to the phone. I sent it 122 times and still got no connection. So the phone was definitely receiving the records.
What remained unverified was the connection itself. The advertisement only contains IPv6 addresses (the Matter spec requires IPv6), and the phone has to connect to UDP 5540 at that address. I had earlier tested those addresses with ping6 from the NanoKVM on the same subnet, and they all responded, so I hadn't looked further in this direction.
Later I switched to testing TCP. Using HA's 8123 as the target, on the same machine and the same address, I tried ping, IPv4, and IPv6:
ping -6 -c 3 <server-IPv6-address>
curl -4 -m 5 -s -o /dev/null -w '%{http_code}\n' 'http://<server-IPv4-address>:8123/'
curl -6 -m 5 -s -o /dev/null -w '%{http_code}\n' 'http://[<server-IPv6-address>]:8123/'Results:
ping6 → OK (0.6ms)
IPv4 8123 → 200
IPv6 8123 → Connection timed outIPv6 ping worked, but TCP timed out. A timeout is different from connection refused: refused means the other side rejected the connection, while a timeout means the packets were dropped with no response at all, which usually points to a firewall.
The cause: ufw's LAN rules were IPv4-only#
This server uses , which blocks all incoming connections by default. A week earlier, when I couldn't reach the HA web UI from my desktop, I had added rules like this:
sudo ufw allow from 192.168.50.0/24 to any port 8123 proto tcp comment 'HA UI from LAN'The source is an IPv4 subnet, so ufw only creates an IPv4 rule. sudo ufw status verbose makes it obvious (excerpt):
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)Every Anywhere rule without a specified source has a matching (v6) entry, while none of the rules restricted to a LAN source do. In other words, this machine opened the needed ports to the LAN over IPv4, but over IPv6 it didn't open a single port to the LAN.
ping6 worked because ufw's default rules allow ICMPv6 (IPv6 neighbor discovery needs it), and mDNS is also allowed by a default rule. So discovery and ping both looked normal, and only the TCP and UDP that actually establish connections were blocked. Google Home displays this case as "Couldn't find device" too.
The fix#
I added two rules so that IPv6 traffic from the LAN can come in the same way IPv4 does:
sudo ufw allow from fc00::/7 comment 'LAN IPv6 (ULA)'
sudo ufw allow from fe80::/10 comment 'LAN IPv6 (link-local)'fc00::/7 is the private range, and fe80::/10 is . Neither can be routed in from the internet. After adding them, connecting to 8123 over IPv6 went from timeout to 200, and pairing succeeded on the next try.
fe80::/10 can't be left out. In the log after pairing succeeded, the two connections that the phone and the Google TV established with matter-hub both came from link-local addresses starting with fe80::. If you only open ULA, it still won't connect after pairing.
WARNING
These two lines let every IPv6 device on the LAN reach every port on this machine, which is broader than my port-by-port IPv4 rules. My LAN only has my own devices, so I set it up this way. If your LAN has devices you don't trust, you can open only Matter's 5540/5541 to these two ranges, but fe80::/10 must be included.
After pairing#
Once pairing worked, I added a few more devices: a floor lamp, the doorbell floodlight, the air conditioner, the dehumidifier, the air purifier, and the power for the cat tower, for a total of 8. I didn't include HA scripts, automations, or input_booleans, since they show up in Google Home as a bunch of outlets. I also left out the security camera switches to avoid accidental voice triggers.
Then I ran into three issues.
Voice commands have to name the room#
The first time I said "turn off the lights" to Google, it turned off the floor lamp and the doorbell floodlight, and left my room's lights alone:
The two lights in my room are switch entities in HA, so matter-hub exports them as outlets (OnOffPlugInUnit), and Google's "turn on the lights" and "turn off the lights" only act on devices whose type is light. In the Google Home app, long-press the device → Settings → change the device type to "Light", then assign each device to a room.
After that change, a different issue came up: now that they're all lights, just saying "turn on the lights" makes Google turn on the floor lamp and the light in my mom's room as well. So in practice I always say "Hey Google, turn on my room light". Only when I name the room does it touch just that one light.
The air conditioner crashed the whole bridge#
After adding the air conditioner, the whole bridge went down and kept restarting, taking the other devices offline with it:
climate_leng_qi.thermostat
minHeatSetpointLimit (1600) must be ≤ minCoolSetpointLimit (1600) − minSetpointDeadBand (200)
Failed to update bridge due to error: [constraint]The Matter thermostat spec requires the minimum heat setpoint to be at least 2°C below the minimum cool setpoint, and my air conditioner has both minimums at 16°C. The configuration had already been written to data/, so it hit this on every startup and the admin UI was unreachable. The only option was to stop the container, use a one-off container to edit the bridge config file and remove the air conditioner, and then start it again.
The original project is archived and won't be fixed. RiDDiX's fork changed how the thermostat setpoint deadband is handled in August (#435), and someone later reported the same situation I hit (#454 "Thermostat with equal min_temp for heat/cool crashes entire bridge process"). Migrating only requires switching the image to the fork's. data/ doesn't need to change, the fabric and node IDs stay the same, and Google Home doesn't need to be re-paired. After switching, the air conditioner was recognized as a RoomAirConditioner and could be added normally.
The air conditioner only accepts absolute temperatures#
"Raise the AC by one degree" did nothing. The log showed:
Invoke « thermostat.setpointRaiseLower
Validation-Error 0x80: Missing mandatory field mode in field modePer the spec, this command needs a mode and an amount, and it looks like the packet Google sends is missing the mode field. "Set the AC to 26 degrees" writes the target temperature directly and works fine, and turning the AC on and off is also fine, so that's how I'm using it for now.
Troubleshooting order#
Here it is as a table. If you get "Couldn't find device", you can check these in order:
| Check | How to confirm | My result |
|---|---|---|
| Does the phone receive mDNS responses | Listen on 5353, or avahi-browse -rt _matterc._udp | Yes, 30 queries, 30 answers |
| Which VID the bridge uses | The VP field in TXT; 65521 is 0xFFF1 | Test VID |
| Is it registered in the Developer Console | Same Google account, matching VID/PID | No; after registering, "Device detected nearby" appeared |
| Does IPv6 TCP/UDP get through | Connect to a TCP service with curl -6; ping alone isn't enough | ping OK, TCP timeout |
| Does the firewall have IPv6 LAN rules | Look for (v6) in sudo ufw status verbose | No |
| Is link-local allowed | fe80::/10 | Pairing succeeded after adding it |
Now I can lie in bed and say "Hey Google, turn off my room light" to turn the light off. Combined with the RF switch from the previous post, the whole chain is complete, as long as I remember to name the room.
- home-assistant-matter-hub: the actively maintained fork by RiDDiXGitHub
- Google Home Developers: Create a Matter integrationdevelopers.home.google.com
- Google Home Developer Consoleconsole.home.google.com
No comments yet
✨ Be the first to comment