Matter
The Matter page lets GEM act as both a Matter bridge (exposing GEM zones to Apple Home, Google Home, and other Matter ecosystems) and a Matter controller (commissioning and controlling external Matter devices from GEM).
Tabs
The page has two tabs: Bridge and Controller.
Bridge Tab
The Bridge tab configures GEM as a Matter bridge, making selected GEM zones available to Matter-compatible smart home platforms.
Bridge Status
A status grid at the top shows the current state of the bridge:
| Field | Description |
|---|---|
| Matter Bridge | Running (green), Enabled but not started (yellow), or Disabled (gray). |
| Paired | Whether the bridge is paired with a Matter controller/app: Yes (green) or No (gray). |
| Exposed Zones | The number of GEM zones currently exposed through the bridge. |
| Port | The network port the bridge is listening on. |
When the bridge is enabled but did not start, the reason is shown in a red panel beneath the status grid (for example, a port already in use or an mDNS startup failure). Fixing the cause and clicking Save & Apply restarts the bridge without restarting GEM.
Network Interface
By default the bridge advertises itself over mDNS on all interfaces. To pin it to one, set matter.interface in gem.json to the interface name (for example enp4s0). If the configured name does not exist on the host — a common result of moving a configuration between machines — GEM logs a warning listing the available interfaces and falls back to automatic detection rather than leaving the bridge offline.
Configuration
| Field | Description |
|---|---|
| Enable Matter Bridge | Toggle switch to enable or disable the bridge. When enabled, the bridge starts automatically with GEM. |
| Port | Network port for the Matter bridge. Default: 5540. |
| Bridge Name | The name that appears in Home apps when discovering the bridge (e.g., "GEM Bridge"). |
Click Save & Apply to save the configuration and restart the bridge.
Pairing
The pairing section changes based on the bridge state:
When the bridge is running but not yet paired:
- A QR code is displayed that you can scan with your Home app (Apple Home, Google Home, etc.).
- A manual pairing code is shown alongside the QR code for apps that support manual entry.
- Click Copy Code to copy the manual pairing code to your clipboard.
When the bridge is running and paired:
- A "Paired" badge is shown with a confirmation message.
- A Reset Bridge button is available. Resetting clears all pairing data and restarts the bridge. You will need to remove the old bridge from your Home app and re-pair.
After resetting the bridge, you must remove the old bridge entry from your Home app before re-pairing. The Home app may not automatically detect the change.
Zone Selection
The zone selection area lets you choose which GEM zones to expose through the Matter bridge. It has two panels:
Subsystem Sidebar (left):
- Lists all subsystems that have compatible zones, with the count of selected/total zones for each.
- Click a subsystem name to filter the zone list. Click "All Subsystems" to show all zones.
Zone Panel (right):
- Search field to filter zones by name.
- Select All and Deselect All buttons that apply to the currently filtered view.
- A checkbox list of zones. Each entry shows the zone label and its Matter device type (e.g., Light, Thermostat, Door Lock, Window Covering, Contact Sensor, Fan, Power Outlet).
| Button | Description |
|---|---|
| Save Zone Selection | Saves the selected zones without restarting the bridge. |
| Save & Apply | Saves the selected zones and restarts the bridge to apply changes. |
Compatible subsystems include: Lights, Climate, Doors/Locks, Shades, Gates/Garages, Fans, Power, and Security. Zones from other subsystems are not shown.
Controller Tab
The Controller tab lets GEM act as a Matter controller, commissioning external Matter devices and creating GEM zones for them automatically.
Controller Status
A status grid shows the current state:
| Field | Description |
|---|---|
| Controller | Running (green), Enabled but not started (yellow), or Disabled (gray). |
| Devices | The number of commissioned Matter devices. |
| Port | The network port the controller is using. |
Configuration
| Field | Description |
|---|---|
| Enable Controller | Toggle switch to enable or disable the controller. When enabled, it starts automatically with GEM. |
| Port | Local websocket port GEM uses to talk to chip-tool. Default: 9099. This is not the Matter protocol port — Matter devices themselves use 5540. |
Click Save & Apply to save the configuration and restart the controller.
Save & Apply merges the two fields above into the matter_controller block in gem.json. The settings described below that have no field on this page — storage_path, interface, icd_registration, poll_icd_nodes and the rest — are left exactly as you set them.
Commissioning Storage
The controller keeps its Matter fabric credentials in a chip-tool key-value store. Every
commissioned device is identified against that store, so if it is lost the devices remain listed
in GEM but no longer respond, and each has to be commissioned again.
chip-tool places this store in $TMPDIR (falling back to /tmp) unless told otherwise, and
neither location survives a reboot. GEM therefore always starts chip-tool with an explicit
storage directory, defaulting to matter_storage/chip-tool inside the GEM installation —
alongside matter_storage/gem-matter-bridge, where the bridge keeps its own state.
That location is deliberate: backups capture the GEM installation directory, so a store kept there is included in every backup and comes back automatically on restore. A store outside the installation is not backed up, and restoring onto replacement hardware would bring the bridge back intact while leaving every commissioned Matter device to be commissioned again by hand.
To override it, set matter_controller.storage_path in gem.json:
"matter_controller": {
"enabled": true,
"port": 9099,
"storage_path": "/opt/gem/matter_storage/chip-tool"
}
Keep any custom path inside the installation directory for the same reason, and avoid the
directories backups skip (backups, updates, node_modules, .git, .idea, dist, tmp,
.cache).
Changing storage_path points the controller at a different fabric. Devices commissioned under
the previous path will need to be commissioned again.
Battery sensors (intermittently connected devices)
Battery-powered Matter sensors are intermittently connected devices (ICDs). They are off the network almost all of the time, waking briefly on their own schedule or when a button is pressed. A Shelly H&T Gen3, for example, wakes every two hours.
This affects what you see when commissioning one. Pairing itself is quick and happens while the device is awake, but the step that follows — asking the device what clusters and endpoints it has — often reaches a device that has already gone back to sleep. When that happens the controller keeps the node and reports it as awaiting introspection rather than creating an empty device:
matter controller: node 1 commissioned but did not answer introspection (asleep?) - will retry until it wakes
It retries every five minutes and finishes the job the next time the sensor is awake, creating its zones then. Nothing is required of you; the zones appear on their own, typically within a few minutes of the device's next wake. Pressing the device's button wakes it immediately if you would rather not wait.
Optionally, the controller can register for check-ins, where the device announces itself when it wakes instead of waiting to be found:
"matter_controller": {
"enabled": true,
"port": 9099,
"icd_registration": true,
"icd_stay_active_ms": 30000
}
icd_registration is off by default. icd_stay_active_ms asks a long-idle device to stay awake
for that many milliseconds after commissioning, which gives introspection a chance to complete on
the first attempt; devices that do not support it ignore it. Both settings apply only to devices
commissioned after they are enabled — an already-commissioned sensor must be commissioned again to
register for check-ins.
These devices are not polled
The controller identifies an ICD by the ICD Management cluster on its root endpoint and records it against the device, then leaves it out of the polling cycle. This is not an optimisation — polling a sleeping device is actively harmful. Every read to an unreachable node holds the controller's single command channel for around a minute of retransmission, and enough of those in a row will delay the next command past its own timeout, which can make an unrelated operation report a failure that never happened. Subscriptions and check-ins deliver the readings instead.
To poll them anyway:
"matter_controller": {
"poll_icd_nodes": true
}
Devices commissioned before this behaviour existed are still polled, and are reclassified the next time they are commissioned or introspected.
How often a battery sensor reports
Reporting is driven by the device, not by GEM. When the controller subscribes it asks for a 120
second maximum interval, and the device is free to answer with its own — a Shelly H&T Gen3
negotiates 43200 seconds (12 hours). Between those, it reports when a reading moves past the
threshold configured on the device itself, not in GEM. On a Shelly that is report_thr_C for
temperature and report_thr for humidity, reachable through its own web interface or RPC API, and
the firmware enforces its own limits (temperature will not go below 0.5 °C). Lowering a threshold
means more frequent reports at the cost of battery life.
Advertising interface (hosts with VPN tunnels)
The controller advertises its own operational record over mDNS, and commissioned devices use that
record to find it again for subscription reports and check-ins. chip-tool advertises on every
interface it can see, which on a host with a WireGuard or NetBird tunnel includes an interface
where multicast cannot work. The send fails and the whole advertisement is reported as failed:
[DIS] Warning: Attempt to mDNS broadcast failed on wt0: OS Error 0x0200007E: Required key not available
[DIS] Failed to advertise records
Commissioning still works — device discovery uses a different path — but the controller's own record may not reach the LAN. Pin advertising to the interface that carries your Matter traffic:
"matter_controller": {
"enabled": true,
"port": 9099,
"interface": "eth0"
}
interface takes an interface name and is resolved to its index. Set interface_id instead to
supply the index directly. Both are optional; with neither set, chip-tool keeps its default
behaviour of advertising on every interface.
This applies to the controller only. The bridge has its own matter.interface setting.
chip-tool must be unconfined
The GEM installer installs chip-tool with snap install --devmode. The snap's default strict
confinement limits it to its own directory under ~/snap, which is outside the installation and
therefore outside backups. Installed strictly, chip-tool cannot write to the storage path above
and exits at startup with:
[CTL] Init Storage failure: src/controller/ExamplePersistentStorage.cpp:89: Error 0x000000AD
If you see that, reinstall with sudo snap install --devmode chip-tool. A chip-tool built from
the Matter SDK is also unconfined and works without the flag.
When chip-tool will not stay running
If chip-tool exits unexpectedly, GEM restarts it automatically, waiting longer between each
attempt — 5 seconds, then 10, 20, 40, 80, 160, and 300 seconds. A process that ran for more than
two minutes before dying is treated as a one-off, and the next restart starts back at 5 seconds.
After eight failed attempts in a row — roughly fifteen minutes — GEM stops trying, logs
matter controller: chip-tool failed to stay running after 8 attempts - giving up
and marks the controller device disconnected. This is deliberate: a chip-tool that cannot start
at all (missing binary, strict confinement, an unwritable storage path) will not fix itself, and
retrying forever would fill the log and spawn a process every few seconds indefinitely.
Fix the underlying cause, then reload the controller — Save & Apply on this page, or reloading the device from Devices — to start over.
When chip-tool stops responding
A different failure looks like nothing at all: chip-tool stays running and its connection to GEM
stays open, but it stops servicing that connection. Neither the process nor the socket goes away,
so commands simply sit unread until each one hits its own timeout.
GEM watches for this. When the connection has been silent for a minute it sends a harmless local
command as a liveness probe; three unanswered probes in a row — about three minutes of silence —
count as unresponsive. A chip-tool GEM started is then restarted (SIGTERM, escalating to
SIGKILL after five seconds) and picked up by the restart backoff described above:
matter controller: chip-tool is unresponsive - restarting it
A busy controller never pays for a probe — any reply or subscription report already proves it is alive — and no probe is sent while a command is in flight, so a long commissioning is never mistaken for silence.
If chip-tool is supervised outside GEM (a development setup), GEM reconnects instead and logs
what to do if that is not enough:
matter controller: chip-tool is unresponsive and runs externally - reconnecting.
if this repeats, restart it (systemctl restart chip-tool)
chip-tool serves one client at a time and does not reliably release the slot, so a wedged one
will refuse the replacement connection too — it has to be restarted.
Commission Device
This section appears only when the controller is running. It lets you add new Matter devices to GEM.
| Field | Description |
|---|---|
| Method | How to identify the device: Pairing Code (QR code string or 11-digit manual code) or Passcode + Discriminator (enter both values separately). |
| Pairing Code | The QR code string (e.g., MT:Y3.13OTB...) or 11-digit manual pairing code. Shown when Method is "Pairing Code." |
| Passcode | The device passcode (e.g., 20202021). Shown when Method is "Passcode + Discriminator." |
| Discriminator | The device discriminator value (e.g., 3840). Shown when Method is "Passcode + Discriminator." |
Click Commission Device to pair the device. The button changes to "Commissioning..." during the process.
After successful commissioning, a result panel shows:
- The assigned Node ID.
- The Device ID.
- A list of GEM zones that were automatically created for the device, with their labels and subsystems. Each zone label is a link — click it to open that zone for editing without leaving the Matter page.
When commissioning fails
A failed attempt has usually still changed something on the device: chip-tool commonly installs
GEM's credentials and only then stops answering, so the device counts GEM as a joined fabric even
though GEM reported a failure. Left alone that device cannot be commissioned again — every attempt
claims another of the handful of fabric slots a Matter device has, and after a few it refuses to
commission at all.
GEM therefore tells the device to forget it too, and hands the node ID back so a retry does not walk the numbering forward. Two cases end differently:
- The controller could not reach the device to unpair it. GEM logs
the device may still hold this fabric; factory reset it before retrying. Factory reset the device before trying again. - Zones were already created before the failure. The device is half-registered rather than unpaired, so GEM leaves it paired rather than stranding its zones. Remove it from Commissioned Devices below, then commission it again.
Zones created for a device
After pairing, GEM asks the device which endpoints it has and which clusters each one implements,
then creates GEM zones from the answer. Zones point at the controller device and are addressed
<node id>:<endpoint>. The clusters found decide the subsystem and the zone control, and drive
these zone attributes:
| Cluster | Subsystem | Zone attributes |
|---|---|---|
| On/Off | Power | state |
| Level Control | Lights | level, state |
| Color Control | Lights | level, state |
| Door Lock | Doors | state (locked/unlocked) |
| Thermostat | Climate | temperature, heat_setpoint, cool_setpoint, system_mode |
| Window Covering | Shades | level, state |
| Fan Control | Fans | level, state |
| Occupancy Sensing | Security | state (occupied/unoccupied) |
| Boolean State | Security | state (open/closed) |
| Temperature Measurement | Climate | temperature |
| Relative Humidity Measurement | Climate | humidity |
The cluster list is also stored on the zone as its matter_clusters attribute.
Sensors that report more than one measurement
A temperature-and-humidity sensor reports each reading on its own endpoint, but it is one physical
device and belongs in one zone. GEM merges endpoints that carry only read-only measurements
into a single climate zone — a Shelly H&T, for example, becomes one zone with both temperature
and humidity, named after the device rather than suffixed with an endpoint number.
Merging is deliberately narrow:
- Anything controllable stays on its own zone. Two switchable endpoints are two things to control.
- Two endpoints reporting the same measurement stay apart — two temperature probes are two readings, and merging them would have one overwrite the other.
- Endpoints in different subsystems are never merged.
Behind the scenes each endpoint keeps its own cluster list, so GEM only ever asks an endpoint for what it actually implements. Devices commissioned before this behaviour existed keep one zone per endpoint until they are commissioned again.
Re-commissioning a device
A device that is removed and commissioned again gets a new node ID, so its zones are addressed differently the second time. Removing a device disables its zones rather than deleting them, and a re-commission reclaims them: GEM matches the zone by name, moves it to the new address and re-enables it. The zone keeps its ID, its history, and every UI and macro that references it.
The previous node is dropped from the controller's device list at the same time, since the device forgot that fabric when its commissioning window was reopened.
Commissioned Devices
A table listing all Matter devices controlled by GEM:
| Column | Description |
|---|---|
| Device | The device label. |
| Node ID | The Matter node identifier (shown in monospace). |
| Status | Online (green) or Offline (gray). |
| Zones | The GEM zones associated with this device, each a link that opens that zone for editing without leaving the Matter page — the same links the commissioning result panel shows. Falls back to a count when the zones cannot be resolved. |
Each row has a Remove button to decommission the device (removes it from GEM's Matter fabric). The zones it created are disabled rather than deleted, so commissioning the device again reclaims them — see Re-commissioning a device.
If no devices are commissioned, a message directs you to use the commission form above.
Devices that stop answering
A device that has gone for good — hardware removed, or a sensor factory reset and re-paired under a
new node ID — stays in the controller's list, and every read aimed at it costs around 45 seconds of
failed discovery. That matters more than it sounds: the controller talks to chip-tool over a
single channel that serves one command at a time, so two or three dead devices can delay the next
command past its own timeout and make an unrelated operation report a failure that never happened.
After three polling rounds in which a device answers nothing, GEM pauses its polls and subscriptions for thirty minutes and logs:
matter controller: node 4 has not answered 3 times - pausing its polls and subscriptions for 30m.
if the device was re-paired it has a new node id now and this one can be removed.
When the pause expires one attempt is allowed through; if it also goes unanswered the device is
paused again. Any reply at all — from a poll or a subscription report, and even an unchanged reading
— clears the pause immediately and is logged as node 4 is answering again. A device GEM never
actually asked (no clusters recorded) is never counted against.
If the device is genuinely gone, Remove it from the table above.
Related Documentation
- Zones - Zone configuration
- Attributes - The zone attributes listed above