by

From eibd to knxd: the migration that wasn't

Eleven years after putting eibd on a Raspberry Pi, I replaced it. It took one apt install, no code changed, and no commit records it. Here is the working config, what each flag is for, and why a compatible protocol is both the best and the most quietly expensive thing that can happen to you.

In 2014 I wrote about putting eibd on a Raspberry Pi so that something other than a wall switch could talk to my KNX bus. It worked, and then it kept working, which is the problem with infrastructure: it removes every reminder that it is there.

That Pi is still on the network. I logged into it while writing this. The eibd binaries are dated March 2015, knxtool and groupswrite are still sitting beside them, and there is still an init script in /etc/init.d. There are no symlinks pointing at it and no systemd unit wrapping it, so nothing has started it in years. It was not decommissioned. It was just stopped being asked, and it has been sitting there ever since with the lights on.

The bus is now served by knxd on a proper server. This is what that change involved, which is a shorter article than I expected to write.

Why move at all

eibd stopped. Version 0.0.5 is the end of the line, upstream has not moved in over a decade, and it is not in any current distribution's repositories, which is why mine was built from source in the first place. knxd is the maintained fork of it, by Michael Bittner and Matthias Urlichs, and the practical difference is that you can type apt install knxd.

That is a smaller reason than it sounds and also the only one that matters. A daemon you build from source is a daemon you will be rebuilding at midnight in four years, on a distribution whose compiler has moved on, in order to fix something unrelated. Being packaged is a feature. It brings a systemd unit, socket activation, a dedicated user, and somebody else's responsibility for the next security update.

The whole change

One package install, in November 2025, and one config file. Here it is in full, which is the part I would have wanted to find when I was looking:

# /etc/knxd.conf
KNXD_OPTS="-e 0.0.1 -E 1.1.240:8 -u /tmp/eib -b ipt:<gateway>"

# previously -E had 0.0.2:8 which means tunneling clients got
# different sender addresses in a range.

Four flags. They are worth taking one at a time, because between them they are the entire configuration of the thing.

  • -b ipt:<gateway> is the backend, and the only line that says anything about the physical world. ipt: is KNX/IP tunnelling: a point-to-point connection to one gateway, rather than ip: routing, which is multicast and a different set of problems. Behind that address is a Siemens KNX/IP gateway and behind that is the twisted pair. If you are coming from a Pi with a TPUART hat, this is the flag that changes: the daemon no longer needs to be anywhere near the bus.
  • -e 0.0.1 is the daemon's own individual address on the bus. Every KNX device has one. knxd needs one too, because it is a device now.
  • -E 1.1.240:8 is a pool of eight further addresses that get handed out to tunnelling clients. This is the flag that catches people, and mine has the only hand-written comment in the file sitting above it.
  • -u /tmp/eib creates a Unix socket at that exact path. That path is not a knxd convention. It is where eibd used to put its socket, and it is the first hint of what this article is actually about.
What the config line actually builds
What the config line actually builds

The one thing I had to think about

When a program connects to knxd over tunnelling and sends a telegram, that telegram needs a sender address, and it cannot be knxd's own, because then two programs talking at once are both claiming to be the same device. So knxd hands each client one address out of a pool: 1.1.240 through 1.1.247 here, eight of them.

The pool should live in the same area and line as the rest of your installation, and it must not collide with any real device. 0.0.2 satisfies neither: it is a valid address, so nothing complains, but the telegrams then arrive from a part of the address space where nothing is supposed to exist. Everything continues to work, because group communication does not care who sent it. It only bites later, when you are looking at a bus monitor trying to work out which device did something, or when a coupler is filtering by line and quietly drops you.

Pick a range in your own line, leave headroom above your real devices, and give it more slots than you think you need. They are free.

Why no code changed

Here is the thing I keep coming back to. The software on top of this bus is a fair-sized system - about 69,000 lines of Python, twenty-nine processes, described at length elsewhere - and swapping the daemon underneath it required changing none of it. Not a line.

Two design decisions in knxd are responsible, and both are somebody choosing compatibility over cleanliness years ago:

  1. It kept eibd's client protocol, on eibd's port, 6720.
  2. -u lets you put the Unix socket back at the old path.

So the client library in my project - a vendored EIBConnection3.py whose header still reads EIBD client library, Copyright 2005-2010 Martin Koegler - opened a connection to a daemon released fifteen years after it was written and noticed nothing.

What changed, and what did not
What changed, and what did not

The connection string is still ip:<host> with no port, because 6720 is the default and always was. The setting in my config is still called EIBD_SOCKET_URL. Two functions do all the work and both have their original names:

  • EIBOpenVBusmonitorText() for listening. That opens a text bus monitor, which hands you every frame on the bus whether it is addressed to you or not, as a line of text that gets picked apart with a regular expression. It is not elegant and it has never once failed.
  • EIBOpen_GroupSocket(False) for sending. Exactly one process in the house opens this, and it is the only thing that can make anything physically move.

What the package gives you

The unit is socket-activated, which is the part worth knowing about because it changes where you look when something is wrong. knxd.socket owns the listening sockets and starts knxd.service on demand; the service is Type=notify, reads its arguments from /etc/knxd.conf through EnvironmentFile, and runs as its own unprivileged user.

Consequences, in the order they surprised me:

  • Editing /etc/knxd.conf needs a restart of the service. It is an environment file, not a config file the daemon watches.
  • If you change what it listens on, the socket unit needs restarting too, and restarting the service alone will look like it did nothing.
  • There is a /etc/default/knxd on the system that is inert under systemd. It exists for the old init script. Editing it produces no error and no effect, which is the worst combination available.
  • The daemon runs as knxd, so the socket at /tmp/eib is owned by that user and your client has to be able to read it.

Checking it actually works

The first thing I reached for was not there. The Ubuntu package does not ship knxtool, so groupswrite and friends are not available and the obvious smoke test is unavailable with them.

What I check instead:

systemctl status knxd knxd.socket

# the sockets it should have created
ls -l /tmp/eib /run/knx
ss -lntp | grep 6720

# and the real test: watch the bus and press a light switch
journalctl -u knxd -f

If the sockets exist and the port is listening, knxd is up. That does not mean it is talking to the gateway. The genuine test is to open the bus monitor, walk over to a wall, press a switch, and see the telegram arrive. Nothing short of that distinguishes a healthy daemon from one happily serving an empty bus.

Contact me

Questions, ideas, or spotted a bug? Send me a note.