[TUTO] USB déporté par IP avec usbip et systemd

Pourquoi ce tuto

La question a déjà été abordée à plusieurs reprises, par exemple ici ou 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 :

  1. exécuter un « service » d’export du device USB sur le SBC sur lequel est connecté le dongle USB à présenter à Jeedom,
  2. 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 bus 8-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:

  1. énumère les ports attaché,
  2. vérifie si notre port exporté sur le serveur est bien attaché localement,
  3. si ce n’est pas le cas, le port exporté sur le serveur est attaché localement.
  4. 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.

17 « J'aime »

:+1: trés technique mais faut ce qu’il faut
description et explications super
je bookmark et ferait un test dés que possible
Un grand Merci

Intéressant, j’imagine que cela fonctionne pour n’importe quel périphérique USB.
N’ayant pas toutes les compétentes, je serai surement passé par Jeelink sur OrangePI

1 « J'aime »

usbip exporte tout type de device USB sur IP. Il se « contente » d’encapsuler le traffic USB et de le faire transiter via une socket TCP/IP.

Donc oui, cela devrait fonctionner avec tout type de périphérique USB, que ce soit une clé de stockage, un clavier/souris, ou une webcam :wink:

Installé aujourd’hui en remplacement de ma Box Atlas.
Jeedom en VM sur Proxmox et la clé Popp sur un PI3 avec usbip entre les 2.
Bon, faut jouer un peu avec systemd pour pouvoir lancer les divers trucs sur le « serveur » et le « client ».
Après, un peu de Linux de temps en temps, ça dérouille :grin:

Merci à toi pour ce tuto qui est me permet de tester Jeedom sous Bullseye avec des controlleurs USB dans une VM Hyper-V sous Windows Server 2022 et que j’espère mettre en production un jour.
Pour ce faire, j’utilise la version Windows du projet usbipd https://github.com/dorssel/usbipd-win.
Je ne suis pas expert Linux et j’ai eu un peu de mal à franchir toutes les étapes (bug sur plugins testés) ainsi qu’un pb avec les chemins dans ton script sh.
Pour info dans mon installation, grep n’est pas dans /usr/bin mais dans /bin, du coup, ça ne se passait pas bien en automatisant l’attachement au port usb.
Voici le script adapté à ma config :


ENDLESS=true
LANG=C
SERVER='192.168.0.200'
BUSID='3-11'

trap_exit() {
    ENDLESS=false
    for PORT in $(/usr/sbin/usbip port | grep ^Port | cut -d':' -f1 | 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 | grep "usbip:.*${SERVER}.*${BUSID}$" &> /dev/null; then
        /usr/sbin/usbip attach -r ${SERVER} -b ${BUSID}
    fi
    sleep 30
done

trap_exit

NB : la version server Windows de usbip tourne aussi avec WSL2 pour ceux qui voudraient tester sans monter un Hyper-V.

Bonsoir @ricobelo, je déterre un peu ce tuto,

Tout d’abord, merci pour ce tuto, extrêment bien décrit…

Petit question : comment fais-tu pour monter deux clés usb (zigbee, zwave) automatiquement au niveau de usbip.service ?

Merci.

Intéressant et merci…
Je garde ca dans un coins ca vas surrement me servir :wink:

Sinon usb redirector marche très bien aussi il donne le lien du tuto dans le premier post.

2 « J'aime »

Juste pour vous dire que j’ai utilisé ce tutoriel avec mon dongle enocean et le plugin enocean sur un jeedom atlas avec un raspberry pi 0 w et cela marche trés bien.

Bonjour,

Merci pour ce magnifique tuto !
Avec un système récent c’est encore plus simple !

Pour ma part j’ai installé le serveur USBIP sur un raspberry 3 avec un Raspberry Pi OS Lite (64 bit) fraichement téléchargé. J’ai donc un Debian GNU/Linux 12 (bookworm) :slight_smile:

Comment en suis-je arrivé a utilisé USBIP ? J’ai récemment migré mon jeedom de raspberry pi 3 vers une VM Synology (DS220+ avec 18 Go de RAM) et j’ai eu le problème de compatibilité de mes vieilles clés USB2 (Z-wave et Edisio) avec Synology (elles se détachent de la VM…), pas de problème en revanche avec RFXCOM. Dans un 1er temps j’ai utilisé jeelink en solution d’urgence, mais je suis en train de migrer sur USBIP. Voici mon retour sur l’installation du serveur :

L’install : (client et serveur)
Quelques petits changement du fait de l’OS plus récent :

sudo apt install -y usbip

… et c’est tout : pas besoin de « linux-tools-generic » qui n’existe pas de toutes façons sur cette distro.

Les modules (sur serveur uniquement) :

  • usbip_core
  • usbip_host

J’ai mis leur chargement dans : /etc/modules-load.d/modules.conf

Pour les charger manuellement sans rebouter :

sudo modprobe usbip_core usbip_host

Le service (sur serveur uniquement) :

J’ai préféré appelé mon service usbipd, donc le fichier est :

/etc/systemd/system/usbipd.service

Le fichier de description du service est quasi identique :

[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/sbin/usbipd
ExecStartPost=/usr/sbin/usbip bind --busid=1-1.2
ExecStartPost=/usr/sbin/usbip bind --busid=1-1.5
ExecStop=/usr/sbin/usbip unbind --busid=1-1.2
ExecStop=/usr/sbin/usbip unbind --busid=1-1.5

[Install]
WantedBy=multi-user.target

Vous notez quelques changement :

  • J’exporte 2 clés USBs dont on trouve les busid comme expliqué dans le tuto initial
  • J’ai simplifié le path de « usbip » qui utilise la version courante installée (et plus une version spécifique).
  • J’ai ajouté le « busid » à la commande « usbip unbind » car il semble maintenant obligatoire.

Et enfin il ne faut pas oublier d’activer le service pour qu’il soit lancé au reboot :

sudo systemctl enable usbipd

Voilà, si ça peut aider…

P.S. : J’ai essayé d’utiliser l’option -D de usbipd qui lance un démon (fork un process) en supprimant « Type=simple », mais sans succes… Si qqu’un à une ID ?

1 « J'aime »

Coté client maintenant, cad Jeedom :

Clé USB Edisio : OK
Clé USB Z-Wave : NOK (Aeotec Z-Stick Gen5, une antiquité).

La clé Z-wave Aeotec disparait du client et du serveur quelques secondes après « usbip attach ».
Le device « /dev/serial/by-id/xxx » utilisé par jeedom disparait… :frowning:
La clé n’apparait plus dans « usbip port » sur le client, et non plus dans « usbip list -r 999.999.999.999 », et « usbip bind --busid=1-1.5 » n’indique pas « is already bound to usbip-host » comme il devrait mais « complete » comme si la clé n’avait jamais été bindé.

Ça semble bien compromis avec la clé AEOTECK…

Si tu utilises la nouvelle stack zwavejs en lieu et place du déprécié openzwave, alors c’est très probablement ni la faute de usbip, ni celle de ta clé z-wave, mais plutôt de l’option soft-reset, cochée à « on » par défaut, dans ta configuration zwavejs : cette option peut forcer un « soft-reset » de la clé USB sur certaines opérations.

Comme la clé reboote, usbpip la détache, puisqu’elle n’est plus accessible !

Deux options : soit écrire un bout de code qui monitore la « disparition » du device pour le remonter dès qu’il réapparaît, soit plus simplement (et tout aussi efficace), décocher l’option fatale dans zwavejs :grin:

2 « J'aime »

Salut
Peut-être réglé pour debian 12 mais debian en 64 bit est réputé instable avec les pi <4.

Antoine

1 « J'aime »

Pour déporter le protocole zwave de Jeedom, tu as aussi cette solution.

1 « J'aime »

Ricobelo un grand merci.

En effet, grâce à ton astuce (décocher le « soft reset » du plugin zwavejs), j’ai pu installé ma clé Z-wave (Aeotec Z-Stick Gen5, une antiquité) directement sur Synolog : C’est beaucoup plus simple ! :slight_smile:

Du coup, n’ayant plus besoin d’USBIP, je n’ai pas continué les investigations, mais le problème était très probablement le même. Je conserve donc USBIP dans un coin de mémoire comme une excellente solution en cas de besoin.

Merci à tous.

Merci Tonio.

Je me suis très peu servi de l’OS 64 bit. j’installerais un OS 32 bits la prochaine fois.

Merci Jeandhom,

Très bonne suggestion.

J’avais 3 machines : un raspbx (portier vidéo et téléphonie), un jeedom sur raspberry et un Synology DS215J (Vidéo surveillance, DHCP et NAS, Mosquitos…).

Une seule machine avec des VMs (et un réseau IP dédié à la domotique et à la téléphonie sans wifi) est plus fiable et plus facile a maintenir (la magie des VMs). J’ai donc acheté un SYNOLOGY DS220+ (d’occase) et installé une VM freepbx16 et une VM Jeedom. YES !

Et grâce au tips de Ricobelo (décocher le « soft reset » du plugin zwavejs) j’ai pu installer la clé USB Z-wave directement sur mon Synology DS220+ a côté de la rfxcom. Il me reste un problème a régler avec la clé EDISIO.

Mes cles zigbee sont déportées sur plusieurs zigbee2tasmota (idem zigbee2mqtt mais sur ESP32). Ceci permet :

  1. de virer les gateway Xiaomi (1ere version),
  2. de fragmenter le réseau zigbee pour qu’il reste rapide,
  3. de faire des scripts directement sur le microcontrôleur (Berry scripting). Ce qui donne de la robustesse en évitant de tout centraliser dans les scenarios jeedom.
1 « J'aime »