Configuring Persistent Zigbee Device Name

Since update to Synology 7.2 my Conbee II Zigbee stick started acting weird. It started disconnecting and reconnecting randomly, every few weeks, seemingly without an reason and without any interaction from my side. This has a dire consequence: its device name changes from /dev/ttyACM0 to /dev/ttyACM1. I don’t know any way to force it to change the id to 0 without restarting, and restarting Synology server is long and painful.

Linux has a method of using persistent names for serial devices in /dev/serial/by-id. Rules for that are managed by udev and are stored in /usr/lib/udev/rules.d. A little digging showed that my Synology didn’t have rules for serial devices at all, so I had to add the following /usr/lib/udev/rules.d/60-serial.rules file:

ACTION=="remove", GOTO="serial_end"
SUBSYSTEM!="tty", GOTO="serial_end"

SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id", IMPORT{builtin}="hwdb --subsystem=usb"
SUBSYSTEMS=="pci", ENV{ID_BUS}=="", ENV{ID_BUS}="pci", \
  ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{device}", \
  IMPORT{builtin}="hwdb --subsystem=pci"

# /dev/serial/by-path/, /dev/serial/by-id/ for USB devices
KERNEL!="ttyUSB[0-9]*|ttyACM[0-9]*", GOTO="serial_end"

SUBSYSTEMS=="usb-serial", ENV{.ID_PORT}="$attr{port_number}"

IMPORT{builtin}="path_id"
ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="", SYMLINK+="serial/by-path/$env{ID_PATH}"
ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="?*", SYMLINK+="serial/by-path/$env{ID_PATH}-port$env{.ID_PORT}"

ENV{ID_BUS}=="", GOTO="serial_end"
ENV{ID_SERIAL}=="", GOTO="serial_end"
ENV{ID_USB_INTERFACE_NUM}=="", GOTO="serial_end"
ENV{.ID_PORT}=="", SYMLINK+="serial/by-id/$env{ID_BUS}-$env{ID_SERIAL}-if$env{ID_USB_INTERFACE_NUM}"
ENV{.ID_PORT}=="?*", SYMLINK+="serial/by-id/$env{ID_BUS}-$env{ID_SERIAL}-if$env{ID_USB_INTERFACE_NUM}-port$env{.ID_PORT}"

LABEL="serial_end"

Once in place, we can reload the rules with the following commands:

# udevadm control --reload-rules
# udevadm trigger

After the second command, my Conbee II stick has been automatically added under /dev/serial/by-id/.

Next step is to reconfigure Home Assistant to recognize the new path. I’m deploying Home Assistant with docker-comopse, so I simply had to change the appropriate part of docker-compose.yml.

services:
  homeassistant:
    devices:
      - "/dev/serial/by-id/<stick-id>:/dev/serial/by-id/<stick-id>"

Format is “onhost:incontainer”, but for simplicity I preserved the original path here. Even though on-host files in /dev/serial/by-id are symbolic links to /dev/ttyUSBX, Docker correctly resolves them inside container and adds them as “standalone” files/devices. We can confirm with:

# docker-compose exec homeassistant ls -hal /dev/serial/by-id

Next we need to restart Home Assistant’s container in a way which recognizes the change in configuration:

# docker-compose stop homeassistant
# docker-compose up --force-recreate -d homeassistant

When Home Assistants boots, we must reconfigure Zigbee Coordinator in ZHA by clicking Settings → Devices and Services → Zigbee Home Automation. There should be an entry for our Coordinator. We can reconfigure it by clicking “Configure” button next to it and “Migrate Radio” on the next page. ZHA will ask us few questions. Basically we need to choose a correct radio from the list and tell ZHA to merely migrate the radio, keeping the network’s configuration intact.

After that there is one last step, and I think it might be necessary because of bug in ZHA or Home Assistant Core. All automations for Zigbee devices which are triggered by device-id (not by entity) say that they work, they even show an indication of being triggered, but they don’t change anything in the real world. So for example my Zigbee button which turns on air conditioning had triggered the automation, but automation didn’t trigger the actual air conditioner.

The “fix” is restart of container. Another fix that I’ve found is rewriting conditions for misbehaving automations, but it takes considerably more thought and time.

Note added on 2024-01-04: This fix won’t survive Synology (DSM) update, so it should be reapplied after update. To be on the safe side, I’m going to check existence of udev rule file after each boot and copy it from a persistent storage in case it doesn’t exist.

References