Matter Bindings [script]

TL;DR: Here’s a script to establish bindings between two Matter devices in Home Assistant.

Background:

Matter supports binding two devices directly to each other. Home Assistant plans to add support for it, but as of this writing it’s not yet available. A few months ago I hacked together a way to bind a couple devices. It works. But is super clunky. I needed to bind a few more devices, so I leveraged @lboue 's notes on how to format websocket messages to get the job done and put it into a script.

Making a fresh thread to share it here for visibility. Hopefully this lowers the barrier to using bindings while we wait for a more polished option. Feel free to spin the code into something else (eg: wrap it up into something in HACS and save us the need to expose the WS port, etc).

HOW-TO

Without further ado, here’s a lightly edited copy of the README:

Expose the WebSocket port of the Matter Add-On

Navigate to Add-Ons → Matter Server → Config → Network

Enter 5580

Close it back up when you’re done. You don’t really want to leave it open on your network.

Run the binding script:

pip3 install websocket-client if you don’t have it already

Download this script: https://raw.githubusercontent.com/mactalla/misc-home-assistant/refs/heads/main/matter-binding.py

For each pair of devices to bind:

  • Find each Node ID from Settings → Devices → (device you want) → Device Info → Node ID
  • Run ./matter-binding.py --from (source node) --to (dest node)
    • For endpoints other than 1, specify it with :#. eg: Inovelli switches have the Dimmer Switch that we can bind on Endpoint 2 ./matter-binding.py --from 17:2 --to 21

Limitations

While this script Works For Me and does attempt to be smart about updates (no changes if none are needed, re-use a suitable entry in the ACL or binding tables). It still has notable limitations:

  • It has been tested only superficially.
  • It does not support un-binding.
  • It binds “everything” (ie: any matching command that the source can issue and the receiver will acknowledge will be bound).
    • There is no support for binding only a subset.
  • Use at your own risk, yadda yadda.

Future

Home Assistant has plans to add support for Binding in the future. Once that exists, this script should just be deleted and forgotten. It has no future :slight_smile:

6 Likes

Dear Mactalla,

first of all: great work! I am trying to set up a matter binding using your script. I have to say that I am pretty new to the smart world and had expected it to be more smart :wink:

My main goal ist to control things mostly from Apple Home, but very soon I figured out, that there are needs for 3rd party tools like HomeBridge (first days) or Home Assistant (using now).

I use some smart bulbs and would like to control them not only by scenes / Apps but also by physical switches. For that reason I bought some Matter Pushbutton Modules from Innovation Matters (Austria). These have 4 switch inputs (I have only need for one) and each input can recognise 3 different actions by default (single short press, double short press and long press).

Using Apple Home does not at all give me the options I need (toggle, dim).

So after looking around I recognised that many people are looking for this exact solution but it seems rather complicated to get this done even in Home Assistant. So instead of trying to set up a crazy automation, programming the stuff manually or using Node Red I would love to get Matter Bindings to work.

Looking for that left me with 3 options: Install OpenHAB, whose latest release seems to offer it; running the CLI-Tool (chip tool) or trying your script.

As I am not very experienced using command line tools, I am a bit lost doing so.

So any help would be highly appreciated.

My setup for this testing:

  1. Nanoleaf Essentials A19/A60
  • running latest Firmware
  • connected to Apple Home via Matter (Thread)
  • connected to Home Assistant
  • Node ID: 34
  1. Innovation Matters Pushbotton Module
  • connected to button switch
  • connected to Apple Home via Matter (WiFi)
  • connected to Home Assistant
  • Node ID: 35

Can you tell me, how (from what instance) to use your script and what parts I would have to edit?

As mentioned, these devices are setup for testing only, so messing up things does not matter as they can be reset at any time.

I am not a total noob, just very unexperienced with scripting.

If you do not have time for such chats, just let me know. That’s fine as well.

Regards,
Session

How do you find the endpoint numbers. For inovelli you mention dimmer is 2. I’m running docker matter-server so I don’t see the dashboard/webui and haven’t figured out how to access it in the docker container.

Happy to help where I can. I’ll start from scratch and you can fast forward the parts you already know.

A script such as this is for running on your computer that’s on the same network as your Home Assistant. So first of all, you need some kind of machine. Windows, macOS, Linux, Raspberry Pi, whatever.

Next you need to be able to “run” it. It’s a Python script. So you’ll need Python installed. If you’re on Linux, macOS, RaspberryPi, then you’re probably already set up. If you’re on Windows you may need to install it first. Or maybe Windows is coming with Python these days; I don’t know. If you need help here, then let me know what computer (operating system) you’re using.

Next you need a “terminal” or “command line” or “shell” or whatever you want to call it. On macOS it’s called “Terminal”. Again, for specifics let me know what machine you’re on.

Once you’ve found it, you should be able to type python3 --version and it’ll give you something that looks like a happy answer with a number. Otherwise it’ll give you something ugly like “command not found” or similar. If you get the ugly error, then you need to go install Python. Again, the answer is specific to the machine you’re on.

At this point you’re ready to run a Python script. So first go download the file form the OP and save it somewhere. It should definitely have a .py file extension. Sometimes Windows or even macOS may try to be “helpful” or “friendly” and hide that from you. Use cd /some/dir from the command line to move over to wherever you downloaded the file and use ls to see the filename. I’ll assume you saved it as matter-binding.py.

To run it use python3 matter-binding.py. It should complain that you didn’t give it a --to or --from. Great. Now you’ve run the script and everything’s happy.

In your case you’re wanting to bind FROM the button (node 35) TO your bulb (node 34).

So run this:
python3 matter-binding.py --from 35 --to 34

At this point it might look happy or it might complain. Two likely causes of complaint:

1: it can’t find the websocket module. Run this: pip3 install websocket-client and if that fails, then I’ll need to know more about the system you’re on.
or
2: it can’t connect to your Home Assistant server.
The script assumes homeassistant.local is the right URL for your HA instance. If yours is somewhere else, then change this line: misc-home-assistant/matter-binding.py at b7a45b9a13a2997cbf04bbe88ff4e12741f0fe19 · mactalla/misc-home-assistant · GitHub and punch in your hostname or IP address or whatever you use to reach your HA instance. Keep both the ws:// prefix and the :5580/ws suffix intact.
If that’s up-to-date, then double check you’ve followed the instructions to expose the Matter Add-On’s port at 5580.

From here it’s a game of finding the endpoint(s) for your button. You said your device has 4 switches on it. I would speculate that internally it’s got 4 endpoints – one for each switch.

So bind with this: python3 matter-binding.py --from 35:1 --to 34 (equivalent the earlier command without the :1). And that likely will bind ONE of the switches.
This command: python3 matter-binding.py --from 35:2 --to 34 I would expect will bind a different switch on that device. Without digging deeper, you can probably simply trial-and-error your way to find which endpoint maps to which switch and then pick your favourite.

HTH

2 Likes

Does that not show up under Settings → Add-Ons? Or give you a config option to “show in sidebar”? Honest question; I’ve got a Yellow and still coming to grips with the many moving parts of HA.

Well the WebUI of the Matter Server is my first answer :sweat_smile:

I knew of the Inovelli one because of the community forums for that product.

There’s always trial-and-error :joy: Many devices only have one endpoint (okay, 2, if you count endpoint 0 which is purely for management). So unlikely the endpoints reach a particularly high number. Brute force each one and see which gives you the desired result. :person_shrugging:

Hello Mactalla,

already big thanks for your tips. I just installed python on my Mac (MBPro, M1 Pro, macOS 15.2) via home-brew.
Hope, I’ve set the PATH variable correctly; I mostly followed this article, but I am struggling with some commands.
It looks like this now, when checking for python:
markus@MacBook-Pro-Markus PythonScript % python3 --version
Python 3.13.1
markus@MacBook-Pro-Markus PythonScript % which -a python3
/opt/homebrew/bin/python3
/usr/bin/python3

But I also get this:
markus@MacBook-Pro-Markus PythonScript % python --version
zsh: command not found: python

So maybe I have done something wrong.

Nevertheless I downloaded your script to /Users/markus/PythonScript and using ls command does show it with correct suffix:
markus@MacBook-Pro-Markus PythonScript % ls
matter-binding.py

Trying to run it gives me this:
markus@MacBook-Pro-Markus PythonScript % python3 matter-binding.py
File “/Users/markus/PythonScript/matter-binding.py”, line 104
misc-home-assistant/matter-binding.py at b7a45b9a13a2997cbf04bbe88ff4e12741f0fe19 · mactalla/misc-home-assistant · GitHub
^
SyntaxError: invalid character ‘·’ (U+00B7)

Same, if running with variables for bulb & switch
markus@MacBook-Pro-Markus PythonScript % python3 matter-binding.py --from 35 --to 34
File “/Users/markus/PythonScript/matter-binding.py”, line 104
misc-home-assistant/matter-binding.py at b7a45b9a13a2997cbf04bbe88ff4e12741f0fe19 · mactalla/misc-home-assistant · GitHub
^
SyntaxError: invalid character ‘·’ (U+00B7)

Port on Matter Server ist set:

Home Assistant is running in latest version (2025.1.2) on Pi5 as image (full) installation. Nothing else running on it.

I would love to get the bindings working. If I succeed with my test setup, I would like to go one step further and have one switch control 3 bulbs at the same time (the lamp consists of 3 nanoleaf Essential GU10 bulbs). In Home Assistant I have created a helper group for them to be able to control them with a switch. I’m afraid, that a switch cannot be bound to such a group but instead I would have to set up a more complex binding from 1 switch to 3 bulbs, right?
But one step after the other. Still having a test setup (1 switch, 1 bulb) for playing around.

Cheers

Update1: I made a binding between two Zemismart switchs and it’s working pretty good and super fast, without any automation to work as 3rd way switchs. Impressive!

matter-binding.py --from 147:4 --to 152:1

A problem I have: It seems to be unidirecional only. Node 147 can turn on and off node 153 normally, but node 152 can’t turn on/off node 147. It doesn’t even send its state to other switch, so using it as 3rd way isn’t perfect.

Hi, does this also apply to both matter over wifi & thread devices?
And does it still work if HA/matter server goes offline?

I tested both here.

  1. It continues working even with HA offline.
  2. I wasn’t able to bind my Thread relay to my Matter Wi-Fi switch. I tested to link my Zemismart Wi-Fi switch to my MS01 Thread relay, no success.

The connection isn’t perfect too. As I said, it isn’t bidirecional… I don’t know if it’s a limitation of protocol or script… I can’t bind more than two switch too.

1 Like

Thanks! So i guess this only binds to another matter device with specific protocol only (wifi > wifi & thread > thread)

Have you tried binding thread devices only?

1 Like

Unfortunately I don’t have another Thread device to test it :pensive:

At least in theory this should work. What Thread Border Router do you have? If OTBR, can you try with firewall disabled (in OTBR add-on settings).

Hi. I’m using HomePods and Apple TV as TBR.

A Thread device need to use SRP/mDNS to discover it’s binding peer. I did observe with Matter updates that Thread devices connected via Apple Thread Border routers were unable to find the OTA Provider (update file provider node in Matter), presumably because the Thread node to be updated was unable to resolve the OTA Provider node via SRP/mDNS. So this might be an Apple Thread Border Router issue, but this is a guess right now.

1 Like

Oh, I got it. What a pity.

Including, is this One-Way communication due to device, script or protocol problem?

That’s fine. A relic of The Great Migration. We continue to feel its aftershocks to this day. Don’t worry about it. Just stick to python3.

Can you open up the file and look at it? It’s just a text file. The very first line ought to be #!/usr/bin/env python3 and line 104 (that the error is complaining about) should be "attribute_path": "0/31/0",. If that’s not right, then you need to re-download the file.

The line that it’s showing makes it look like you downloaded the GitHub web page, rather than the file itself. Right click on Raw and choose Save Link As...

Binding is not syncing. It’s establishing a one-way communication.

On the source node we tell it “when things change – tell this other Node over here”. Normally when a node is provisioned it only reports things to the Hub who then coordinates everything. Now it’ll tell the Hub AND a friend.

Then on the destination node we tell it “when you are told stuff buy that Node over there, listen to what it says”. Normally a node will only respond to stuff from the Hub, so a rogue device can’t wreck havoc on your network. We’re giving it the A-OK to act on another source of instructions.

If you’re willing to factory reset in case of problems, you could try creating a second binding – one in reverse.

matter-binding.py --from 152:4 --to 147:1

For simple state changes, I imagine this should work. Node 147 turns on; it tells Node 152. Then if you turn off Node 152, it tells Node 147.

However I also see a potential glitch if there are frequent updates. Let’s say that for dimming instead of sending a “Start Level Up” it actually sends the levels “2…3…4…5”. Now with a 2-way binding, I could imagine the following:

Node 147 starts dimming up.
Now 2
Send ‘2’ to 152
Now 3

(over on 152)
Receives ‘2’
Send ‘2’ to 147

(back at 147)
Receives ‘2’
?? goes back down to 2 ??

“only one way to find out” Maybe it’ll work fine. Maybe you’ll get some jumpiness in your dimming. But simple On/Off I wouldn’t be surprised if it works well.

The devices I’m binding are all Thread.

2 Likes

Hello Mactalla,

you got me… :wink:
Indeed, I seem to have downloaded a webpage. Thought, the script itself was linked there so right clicked and downloaded.
Have now the *.py script laying in place.
Ran it and received this:

markus@MacBook-Pro-Markus PythonScript % python3 matter-binding.py --from 35 --to 34
Traceback (most recent call last):
File “/Users/markus/PythonScript/matter-binding.py”, line 5, in
import websocket
ModuleNotFoundError: No module named ‘websocket’
markus@MacBook-Pro-Markus PythonScript % pip3 install websocket-client
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try brew install
xyz, where xyz is the package you are trying to
install.

If you wish to install a Python library that isn't in Homebrew,
use a virtual environment:

python3 -m venv path/to/venv
source path/to/venv/bin/activate
python3 -m pip install xyz

If you wish to install a Python application that isn't in Homebrew,
it may be easiest to use 'pipx install xyz', which will manage a
virtual environment for you. You can install pipx with

brew install pipx

You may restore the old behavior of pip by passing
the '--break-system-packages' flag to pip, or by adding
'break-system-packages = true' to your pip.conf file. The latter
will permanently disable this error.

If you disable this error, we STRONGLY recommend that you additionally
pass the '--user' flag to pip, or set 'user = true' in your pip.conf
file. Failure to do this can result in a broken Homebrew installation.

Read more about this behavior here: <https://peps.python.org/pep-0668/>

note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.
markus@MacBook-Pro-Markus PythonScript % brew install pipx
==> Auto-updating Homebrew…
Adjust how often this is run with HOMEBREW_AUTO_UPDATE_SECS or disable with
HOMEBREW_NO_AUTO_UPDATE. Hide these hints with HOMEBREW_NO_ENV_HINTS (see man brew).
==> Homebrew collects anonymous analytics.
Read the analytics documentation (and how to opt-out) here:
Link to …docs.brew.sh/Analytics
No analytics have been recorded yet (nor will be during this brew run).

==> Homebrew is run entirely by unpaid volunteers. Please consider donating:
Link to …github.com/Homebrew/brew#donations

==> Auto-updated Homebrew!
Updated 2 taps (homebrew/core and homebrew/cask).
==> New Casks
mihomo-party

==> Downloading Link to …ghcr.io/v2/homebrew/core/pipx/manifests/1.7.1_1
##################################################################################################################### 100.0%
==> Fetching pipx
==> Downloading
Link to …ghcr.io/v2/homebrew/core/pipx/blobs/sha256:ca676ccaaf770e835c5a9ae2d3a648ef4539893c02aa8a70875bfd3e3
##################################################################################################################### 100.0%
==> Pouring pipx–1.7.1_1.arm64_sequoia.bottle.tar.gz
==> Caveats
zsh completions have been installed to:
/opt/homebrew/share/zsh/site-functions
==> Summary
:beer: /opt/homebrew/Cellar/pipx/1.7.1_1: 155 files, 1019.1KB
==> Running brew cleanup pipx
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see man brew).
markus@MacBook-Pro-Markus PythonScript % pip3 install websocket-client
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try brew install
xyz, where xyz is the package you are trying to
install.

If you wish to install a Python library that isn't in Homebrew,
use a virtual environment:

python3 -m venv path/to/venv
source path/to/venv/bin/activate
python3 -m pip install xyz

If you wish to install a Python application that isn't in Homebrew,
it may be easiest to use 'pipx install xyz', which will manage a
virtual environment for you. You can install pipx with

brew install pipx

You may restore the old behavior of pip by passing
the '--break-system-packages' flag to pip, or by adding
'break-system-packages = true' to your pip.conf file. The latter
will permanently disable this error.

If you disable this error, we STRONGLY recommend that you additionally
pass the '--user' flag to pip, or set 'user = true' in your pip.conf
file. Failure to do this can result in a broken Homebrew installation.

Read more about this behavior here: https://peps.python.org/pep-0668/

note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.

As you can see, I tried to install the web socket-client and as it looked to me that I needed pipe, I tried to install this via Homebrew as well, but no success.
I had to cut https:// in front of some of the links as I am restricted as new forums user.

Again, thanks for helping out!
Session

Thanks! I tried to, but I think the problem is on my Zemismart Swtiches.

I was able to bind on only one switch, from three swiches. Working switch is on v3.2, while other switches are on v3.1, according to uHome+.

Very interesting. Thanks a lot. It’d be excellent to smart lights connected to smart switches, but the leak of two-way communication wouldn’t broke the performance?
Including, just out of curiosity, how does this communication work? Is it through mDNS?

It is now. To save the next person the trouble you encountered.

This is saying ideally we can just run brew install websocket-client. But that doesn’t look like it exists. So try this instead:

python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install websocket-client

If you open a new shell, you’ll need to re-run source .venv/bin/activate before you can run the matter-binding.py script again. The other lines don’t need to be run again even if you reboot.

1 Like