Pourquoi ce tuto
La question a déjà été abordée à plusieurs reprises, par exemple ici ou là mais les réponses proposées sont soit basée sur du code tiers nécessitant la compilation de module kernel, soit incomplètes et/ou fragiles dans leur implémentation.
Je vous propose donc ma solution, basée sur le package usbip
et déjà packagé sous Debian et Ubuntu, et un set de units systemd pour gérer l’export et l’attachement d’un device USB par le réseau de façon automatique.
Le contexte
Mon instance Jeedom tourne dans une VM, sur un serveur, au fin fond de mon garage. Dans ces conditions il est illusoire d’espérer mailler correctement mon réseau Z-Wave depuis mon sous-sol… C’est pouquoi mon controlleur Z-Wave est déporté sur un SBC (un OrangePi dans mon cas, mais ce tuto est transposable quel que soit le SBC concerné, que ce soit RPi, ODroid, ou autre…)
pourquoi systemd
Initialement poussé par Red Hat via Fedora, systemd s’est imposé depuis presque 10 ans sur toutes les distributions majeures. Ce logiciel est chargé d’orchestrer le démarrage et l’arrêt des différents service du système.
Il est donc tout indiqué pour :
- exécuter un « service » d’export du device USB sur le SBC sur lequel est connecté le dongle USB à présenter à Jeedom,
- exécuter un « service » chargé de d’attacher automatiquement le device USB exporté sur le serveur exécutant Jeedom.
pourquoi usbip
Le kernel Linux inclue depuis plusieurs années un « module » (un pilote de périphérique) nommé « usbip ». Ce module permet au système de faire transiter le protocole de communication du bus USB via TCP/IP. Concrètement, cela permet précisément de connecter un périphérique USB connecté sur une machine distante, comme si le périphérique était connecté localement !
Ces modules seuls, déjà inclus sur votre système, ne sont pas suffisants pour fonctionner : il faut également les outils en mode utilisateur permettant de paramétrer usbip :
Sous Debian :
sudo apt install -y usbip
Les outils installés sont les suivants :
/usr/sbin/usbip
/usr/sbin/usbipd
Sous Ubuntu :
Pour des raisons historiques, Le paquet usbip
fourni par Ubuntu est cassé, les outils usbip
et usbipd
sont fournis par le paquet linux-tools-generic
sudo apt install -y linux-tools-generic
Sur ma distrib Arbian basée sur Ubuntu Xenial, les outils installés sont les suivants :
/usr/lib/linux-tools/4.4.0-203-generic/usbip
/usr/lib/linux-tools/4.4.0-203-generic/usbipd
Configuration du serveur
Attention: par serveur on entend l’hôte sur lequel est connecté le périphérique USB à exporter, qui va donc « servir » le périphérique USB. Dans mon cas, le SBC OrangePi !
installation de usbip
Mon SBC OrangePi est installé avec la Distribution Armbian, sur base Ubuntu Xenial. J’installe donc les paquets nécessaires :
sudo apt install -y linux-tools-generic
On configure ensuite le chargement des modules noyau nécessaires au démarrage ainsi qu’au fonctionnement de usbip en mode daemon :
sudo tee /etc/modules-load.d/usbip.conf <<EOF
usbip
usbip-core
usbip_common_mod
usbip-host
EOF
Les modules seront chargés dès le prochain redémarrage du serveur, mais pour l’heure, ils ne sont probablement pas encore chargés. Plutôt que de rebooter maintenant, on peut simplement charger les modules manuellement (après tout, on n’est pas sous Windows :D)
sudo modprobe usbip usbip-core usbip_common_mod usbip-host
Configuration systemd
On créé maintenant un unit systemd pour lancer usbip
au démarrage :
sudo tee /etc/systemd/system/usbip.service <<EOF
[Unit]
Description=Exports USB device over IP
Requires=network-online.target
After=network-online.target
[Service]
Type=simple
Restart=on-failure
User=root
Group=root
ExecStart=/usr/lib/linux-tools/4.4.0-203-generic/usbipd
ExecStartPost=/usr/lib/linux-tools/4.4.0-203-generic/usbip bind --busid=8-1
ExecStop=/usr/lib/linux-tools/4.4.0-203-generic/usbip unbind
[Install]
WantedBy=multi-user.target
EOF
- La ligne
ExecStart=
démarre le daemon usbipd proprement dit. - La ligne
ExecStartPost=
configure le périphérique à exporter (bind). Dans mon cas, ma clé Z-Wave est connectée sur le bus8-1
(cf. ci-après). Cette commande est exécutée après le démarrage de daemon usbipd. - La ligne
ExecStop=
libère le périphérique exporté proprement lors de l’arrêt du daemon.
trouver le bus-id de votre périphérique USB
la commande usbip
accepte le verbe list
afin d’énumérer les périphériques USB présents :
sudo /usr/lib/linux-tools/4.4.0-203-generic/usbip list -l
usbip: error: failed to open /usr/share/hwdata//usb.ids
- busid 7-1 (4d46:0002)
unknown vendor : unknown product (4d46:0002)
- busid 8-1 (0658:0200)
unknown vendor : unknown product (0658:0200)
Ma clé Z-Wave est identifié par son vendor Id et son product Id :
lsusb -d 0658:0200
Bus 008 Device 002: ID 0658:0200 Sigma Designs, Inc.
Ma clé est donc bien branchée sur le bus USB 8-1
démarrage su service
Une fois la configuration effectuée, il faut informer systemd d’un changement de configuration, puis activer et démarrer le service :
sudo systemctl daemon-reload
sudo systemctl enable usbip.service
sudo systemctl start usbip.service
Si tout se passe bien, le service doit être démarré :
systemctl status usbip.service
● usbip.service - Exports USB device over IP
Loaded: loaded (/etc/systemd/system/usbip.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2021-02-26 15:49:52 CET; 3h 41min ago
Main PID: 1805 (usbipd)
CGroup: /system.slice/usbip.service
└─1805 /usr/lib/linux-tools/4.4.0-203-generic/usbipd
Vous pouvez également consulter les journaux afin de voir ce qui se passe :
sudo journalctl -u usbip.service
-- Logs begin at Fri 2021-02-26 15:49:44 CET, end at Fri 2021-02-26 19:32:01 CET. --
Feb 26 15:49:51 orangepi systemd[1]: Starting Exports USB device over IP...
Feb 26 15:49:51 orangepi usbip[1806]: usbip: info: bind device on busid 8-1: complete
Feb 26 15:49:51 orangepi usbipd[1805]: usbipd: info: starting usbipd (usbip-utils 2.0)
Feb 26 15:49:51 orangepi usbipd[1805]: usbipd: info: listening on 0.0.0.0:3240
Feb 26 15:49:51 orangepi usbipd[1805]: usbipd: info: listening on :::3240
Feb 26 15:49:52 orangepi systemd[1]: Started Exports USB device over IP.
On voit ici que le daemon est bien en écoute sur le réseau, sur le port 3240, et que le périphérique USB connecté au bus 8-1 est bien exposé.
Configuration du client
installation de usbip
Mon Instance Jeedom tourne dans une machine virtuelle, sous Debian 10 (Buster). On installe donc les outils usbip
:
sudo apt install -y linux-tools-generic
On configure ensuite le chargement des modules noyau nécessaires au démarrage ainsi qu’au fonctionnement de usbip en mode client :
sudo tee /etc/modules-load.d/usbip.conf <<EOF
usbip-core
vhci-hcd
EOF
Les modules seront chargés dès le prochain redémarrage du serveur, mais pour l’heure, ils ne sont probablement pas encore chargés. Plutôt que de rebooter maintenant, on peut simplement charger les modules manuellement (après tout, on n’est pas sous Windows :D)
sudo modprobe usbip-core vhci-hcd
Avant d’aller plus avant, on peut déjà s’assurer que l’on accède bien aux périphériques partagés par notre SBC :
sudo usbip list -r 1.2.3.4
Exportable USB devices
======================
- 1.2.3.4
8-1: Sigma Designs, Inc. : Aeotec Z-Stick Gen5 (ZW090) - UZB (0658:0200)
: /sys/devices/platform/soc/1c1d400.usb/usb8/8-1
: Communications / unknown subclass / unknown protocol (02/00/00)
: 0 - Communications / Abstract (modem) / AT-commands (v.25ter) (02/02/01)
: 1 - CDC Data / Unused / unknown protocol (0a/00/00)
configuration du unit systemd
On créé maintenant un unit systemd pour lancer usbip
au démarrage :
sudo tee /etc/systemd/system/usbip.service <<EOF
[Unit]
Description=Connects remote USB device over IP
Requires=network-online.target
After=network-online.target
[Service]
Type=simple
Restart=on-failure
User=root
Group=root
ExecStart=/usr/local/sbin/usbip_zwave.sh
[Install]
WantedBy=multi-user.target
EOF
Ici, le unit systemd se contente d’exécuter un script shell bash /usr/local/sbin/usbip_zwave.sh
.
Ce script est le suivant :
sudo tee /usr/local/sbin/usbip_zwave.sh <<EOF
#!/bin/bash
ENDLESS=true
LANG=C
SERVER='1.2.3.4'
BUSID='8-1'
trap_exit() {
ENDLESS=false
for PORT in $(/usr/sbin/usbip port | /usr/bin/grep ^Port | /usr/bin/cut -d':' -f1 | /usr/bin/awk '{print$2}'); do
/usr/sbin/usbip detach -p $PORT
done
exit 0
}
trap trap_exit SIGINT
trap trap_exit SIGTERM
trap trap_exit SIGKILL
while $ENDLESS; do
if ! /usr/sbin/usbip port | /usr/bin/grep "usbip:.*${SERVER}.*${BUSID}$" &> /dev/null; then
/usr/sbin/usbip attach -r ${SERVER} -b ${BUSID}
fi
sleep 30
done
trap_exit
EOF
sudo chmod 0750 /usr/local/sbin/usbip_zwave.sh
Ce script exécute une boucle infinie, tant qu’il n’est pas interrompu par systemd, dans laquelle il:
- énumère les ports attaché,
- vérifie si notre port exporté sur le serveur est bien attaché localement,
- si ce n’est pas le cas, le port exporté sur le serveur est attaché localement.
- Enfin, si le script est interrompu, il prend soin de préalablement détacher tous les ports USB attachés localement avant de se terminer.
Attention: veillez à saisir les variables SERVER
et BUSID
avec les valeurs propres à votre environement.
démarrage du service
Une fois la configuration effectuée, il faut informer systemd d’un changement de configuration, puis activer et démarrer le service :
sudo systemctl daemon-reload
sudo systemctl enable usbip.service
sudo systemctl start usbip.service
Si tout se passe bien, le service doit être démarré :
sudo systemctl status usbip.service
● usbip.service - Connects remote USB device over IP
Loaded: loaded (/etc/systemd/system/usbip.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2021-02-26 15:54:28 CET; 3h 40min ago
Main PID: 10749 (usbip_zwave.sh)
Tasks: 2 (limit: 1146)
Memory: 1.2M
CGroup: /system.slice/usbip.service
├─10749 /bin/bash /usr/local/sbin/usbip_zwave.sh
└─23899 sleep 30
févr. 26 15:54:28 jeedom systemd[1]: Started Connects remote USB device over IP.
Vous pouvez également consulter les journaux afin de voir ce qui se passe :
sudo journalctl -u usbip.service
-- Logs begin at Fri 2021-02-26 12:25:01 CET, end at Fri 2021-02-26 19:35:03 CET. --
févr. 26 15:54:28 jeedom systemd[1]: Started Connects remote USB device over IP.
Conclusion
Avec ce setup, Jeedom devrait être suffisament résilient pour reconnecter automatiquement le périphérique USB, que ce soit dû au reboot de l’un ou l’autre des hôtes, ou à un disfonctionnement du réseau.