Wie erstellt man einen Symlink für ttyUSBx?
Das USB-Geräte-Chaos nach dem Reboot – und die udev-Regel, die es ein für alle Mal löst.
Das Problem: Nach dem Reboot ist alles durcheinander
Jeder, der einen Raspberry Pi oder Homelab-Server mit mehreren USB-Geräten betreibt, kennt das: Ein Zigbee-Coordinator-Stick steckt an /dev/ttyUSB0, ein Z-Wave-Stick an /dev/ttyUSB1. Alles läuft – bis zum nächsten Reboot. Plötzlich sind die Nummern vertauscht, Zigbee2MQTT greift auf den Z-Wave-Stick zu, und das Smarthome steht Kopf.
Die Ursache: Linux vergibt die ttyUSB-Nummern in der Reihenfolge, in der die USB-Geräte beim Booten erkannt werden. Die ist nicht deterministisch. Die Lösung ist eine udev-Regel, die jedem Gerät einen festen Symlink-Namen gibt – einmal einrichten, nie wieder dran denken.
Mit udevadm info die Vendor- und Product-ID des USB-Geräts auslesen, eine Regel in /etc/udev/rules.d/ anlegen, udevadm trigger – fertig. Das Gerät ist ab sofort unter einem fixen Namen wie /dev/ttyZCOORD erreichbar.
USB-Gerät identifizieren
Zuerst brauchen wir die eindeutigen IDs des USB-Geräts. Steck den Stick ein und lies die Attribute aus:
udevadm info --name=/dev/ttyUSB0 --attribute-walk
Die Ausgabe ist lang – wir suchen nach idVendor und idProduct. Diese Kombination identifiziert den Gerätetyp eindeutig:
looking at parent device '/devices/platform/.../usb1/1-1/1-1.2':
KERNELS=="1-1.2"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{bDeviceClass}=="00"
ATTRS{bMaxPower}=="100mA"
ATTRS{idProduct}=="ea60"
ATTRS{idVendor}=="10c4"
ATTRS{manufacturer}=="Silicon Labs"
ATTRS{product}=="slae.sh cc2652rb stick - slaesh's iot stuff"
In diesem Fall: idVendor="10c4" und idProduct="ea60" – ein Silicon Labs CP2102-basierter Zigbee-Stick. Die Werte notieren, die brauchen wir gleich.
udev-Regel anlegen
Jetzt erstellen wir eine Regel-Datei, die beim Einstecken oder Booten automatisch einen festen Symlink anlegt:
sudo nano /etc/udev/rules.d/98-usb-device.rules
Inhalt der Datei – idVendor und idProduct mit deinen eigenen Werten ersetzen, SYMLINK ist der Name, unter dem das Gerät künftig erreichbar sein soll:
KERNEL=="ttyUSB?", SUBSYSTEMS=="usb", ATTRS{idProduct}=="ea60", ATTRS{idVendor}=="10c4", SYMLINK+="ttyZCOORD"
Hast du zwei USB-Geräte mit identischer Vendor/Product-ID (z.B. zwei Silicon-Labs-Sticks), reicht diese Methode allein nicht. Du musst dann zusätzlich nach ATTRS{serial} oder dem physischen USB-Port (KERNELS) filtern. Sonst landen beide auf dem gleichen Symlink.
Regel anwenden und testen
Die neuen Regeln laden – ein voller Reboot ist nicht nötig:
sudo udevadm trigger
Prüfen, ob der Symlink da ist:
ls -l /dev/ttyZ*
lrwxrwxrwx 1 root root 7 28. Jun 00:17 /dev/ttyZCOORD -> ttyUSB0
Ab jetzt zeigt /dev/ttyZCOORD immer auf den richtigen Stick – egal welche ttyUSB-Nummer Linux ihm beim Booten gibt. In Zigbee2MQTT, Z-Wave JS oder ESPHome einfach den Symlink als Device-Pfad eintragen.
Wo das im Homelab relevant wird
Dieses Pattern kommt bei mir ständig vor:
- Zigbee-Coordinator (CC2652, SkyConnect) →
/dev/ttyZCOORD - Z-Wave-Stick (Aeotec Z-Stick) →
/dev/ttyZWAVE - ESP32-Flasher via UART →
/dev/ttyESPFLASH
Gerade wenn mehrere Sticks gleichzeitig am selben Host hängen, ist das die einzig vernünftige Lösung. Ohne udev-Regeln ist jeder Reboot ein Glücksspiel.
Was ich weggelassen habe
Ein paar Dinge, die hier bewusst nicht behandelt werden:
- USB-Geräte ohne ttyUSB-Interface (z.B. reine HID-Devices) – da greift ein anderer udev-Mechanismus.
- systemd-Dependencies. Wenn ein Service beim Booten den Symlink erwartet, bevor udev die Regel ausführt, braucht man eine
After=-Abhängigkeit. Das ist ein eigenes Thema. - Docker-Passthrough. Wenn der Stick in einem Docker-Container genutzt wird, muss zusätzlich
--device /dev/ttyZCOORDin den Container gemappt werden.
Fazit
Eine udev-Regel ist fünf Minuten Arbeit und spart einem jahrelang Debugging nach jedem Reboot. Für jedes USB-Gerät, das du dauerhaft betreibst – Zigbee, Z-Wave, GPS, Seriell-Adapter – gehört ein fester Symlink angelegt. Das ist einer dieser "warum habe ich das nicht sofort gemacht"-Momente.