Scanner-Server mit USB-Scanner über Netzwerk

Problem

Viele haben einen Scanner, der nur über USB direkt an einem PC betrieben werden kann. Wenn dieser Scanner aber z.B. ein spezieller Dokumentenscanner ist und/oder von mehreren Personen benutzt werden soll (z.B. ein „Abteilungsscanner“ auf dem Flur), dann ist das entweder unmöglich, oder ein extra „Scan-PC“ muss neben dem Scanner stehen, der auch immer laufen muss — oder für jeden Scan extra eingeschaltet werden muss. Diesen „Scan-PC“ kann man aber sehr kostengünstig und so geschickt aufsetzen, dass daraus eine wahrlich geniale Lösung wird:

Lösungskonzept

     

      • kleine Linux-Kiste (alter Raspberry Pi) neben dem Scanner, an der der Scanner direkt per USB hängt. Natürlich muss die Box im Netz hängen.

      • per udev-Regel wird das Einschalten des Scanners erkannt und ein Dienst gestartet.

           

            • dieser Dienst startet den USB-IP-Hostdienst und stellt den Scanner somit als USB-Gerät im Netz zur Verfügung

            • ausserdem logt sich der Dienst per SSH im Verarbeitungsserver ein und startet dort ebenfalls einen Dienst

        • Dieser Dienst hängt mit USB-IP den remote Scanner als lokales USB-Gerät

             

              • Auf diesem Verarbeitungsserver kann dann die kompliziertere Image-Verarbeitung (OCR, etc.) stattfinden.

        Somit steht der Scanner automatisch wenige Sekunden nach dem Einschalten auf der remote Box als „pseudo“ lokales Gerät zur Verfügung.

        Vorteile dieses Ansatzes

        Der PC, der die Scans verarbeitet muss durchaus einige Ressourcen in Bezug auf CPU und Memory haben, weil die graphische Aufbereitung der Scans (entrauschen), sowie OCR doch relativ Ressourcen-hungrig sind. Dazu kommt, dass im Fall eines Dokumentenscanners eigentlich alle Scan-Parameter vorher festgelegt werden können. Der Scanner sollte eigentlich auf Knopfdruck starten und die Scans idealerweise irgendwo im Netzwerk ablegen. Mit diesem Ansatz kann die eigentliche Verarbeitung des Scans irgendwo im Netz (z.B. auf einer ausreichend dimensionierten VM) stattfinden. Vor Ort muss der Linux-Rechner eigentlich nur das USB-Gerät über das Netz weiterreichen. Dafür reicht auch ein uralt-Raspi.

        Natürlich gibt es Dokumentenscanner, die das alles out-of-the-box können, aber die sind im Vergleich sehr teuer — sehr viel teurer, als ein einfacher, aber zuverlässiger Dokumentenscanner plus ein alter Raspberry Pi. Daher entstand die Idee.

        Raspi konfigurieren

           

            • usbip installieren + kernel-module ‚usbip-host‘ laden

            • ssh-connection zu Client-Computer vorbereiten (ssh-copy-id)

          Jetzt müssen mehrere Dienste konfiguriert werden. Zunächst die udev-Regel, die den USB-IP Dienst zur Weiterleitung des Geräts startet, sobald der Scanner „sichtbar“ wird. Das passiert z.B. beim Einschalten des Scanners (oder Aufwachen aus dem Standby).

          /etc/udev/rules.d/90-usbip.rules

          SUBSYSTEM=="usb", \
          ATTR{idVendor}=="04c5", \
          ATTR{idProduct}=="1626", \
          ENV{DEVTYPE}=="usb_device", \
          TAG+="systemd", \
          ENV{SYSTEMD_WANTS}+="usbip@%k.service"

          Die Vendor- und ProductID müssen natürlich angepasst werden. Einfach mit lsusb nachschauen, wenn der Scanner per USB verbunden ist.

          Jetzt kommt der Dienst, der von der udev-Regel gestartet werden soll. Der Trick hier ist, dass per SSH der nötige Dienst zum remote einbinden des USB-Geräts auf dem Verarbeitungsserver automatisch gestartet wird, sobald der Scanner eingeschaltet wird.

          WICHTIG: In der zweiten ExecStart-Anweisung und der ersten ExecStop-Anweisung muss natürlich „root@verarbeitungsserver“ und „usbip@raspi“ geändert werden auf die tatsächlichen ssh-Benutzer und Hostnamen.

          /lib/systemd/system/usbip@.service

          [Unit]
          Description=USB-IP Binding %i
          After=network-online.target usbipd.service
          Wants=network-online.target usbipd.service
          PartOf=usbipd.service
          StopWhenUnneeded=yes
          
          [Service]
          Type=simple
          RemainAfterExit=yes
          
          # binding a device itself
          ExecStart=/bin/sh -c "/usr/sbin/usbip bind -b %i"
          # and attching the device on remote computer
          ExecStartPost=ssh root@verarbeitungsserver "/bin/systemctl start usbip@raspi:%i"
          
          # expecting errors of unbinding actually "already disconnected device"
          ExecStop=ssh root@verarbeitungsserver "/bin/systemctl stop usbip@raspi:%i"
          ExecStop=-/bin/sh -c "/usr/sbin/usbip unbind -b %i"
          ExecStop=/bin/systemctl reset-failed
          
          [Install]
          WantedBy=multi-user.target

          Dieser Dienst stellt sicher, dass der USB-IP Service immer läuft:

          /lib/systemd/system/usbipd.service

          [Unit]
          Description=USB-IP Host Daemon
          After=network-online.target
          Wants=network-online.target
          
          [Service]
          Type=simple
          Restart=always
          
          ExecStart=/bin/sh -c "/usr/sbin/usbipd"
          
          [Install]
          WantedBy=multi-user.target

          Und aktivieren und starten:

          udevadm control --reload-rules
          systemctl daemon-reload
          systemctl restart systemd-udevd
          systemctl enable usbipd && sudo systemctl start usbipd

          Verarbeitungsserver konfigurieren

             

              • usbip installieren und Kernel-module laden

              • ssh-connection zu Server konfigurieren

            Als erstes brauchen wir das Gegenstück für den Dienst, der auf dem Raspi aktiviert wird, wenn der Scanner eingeschaltet wird. Der Raspi startet dann diesen Dienst auf dem Verarbeitungsserver remote per SSH. Er dient dazu, den im Netz angebotenen USB-IP-Scanner lokal wieder als USB-Gerät zur Verfügung zu stellen.

            /etc/systemd/system/usbip@.service

            [Unit]
            Description=USB-IP [At/De]taching %i
            After=network-online.target
            Wants=network-online.target
            
            [Service]
            Type=simple
            Restart=on-failure
            RestartSec=1
            RemainAfterExit=yes
            
            ExecStart=/bin/sh -c "/usr/sbin/usbip attach -r $(echo %i | sed 's/:/ -b /1')"
            
            #Expected error of unbinding actually "already disconnected device"
            ExecStop=-/bin/sh -c "/usr/sbin/usbip detach --port `/usr/sbin/usbip port | grep -B2 $(echo %i | awk -F':' '{print $2}') | grep 'Port' | cut -b 6-7`"
            
            [Install]
            WantedBy=multi-user.target

            Dazu muss natürlich der USB-IP Dienst immer laufen.

            /etc/systemd/system/usbipd.service

            [Unit]
            Description=USB-IP Client Daemon
            After=network-online.target
            Wants=network-online.target
            
            [Service]
            Type=simple
            Restart=always
            RemainAfterExit=yes
            
            # creating a tmp-file if any USB port is available
            ExecStart=/bin/sh -c "/usr/sbin/usbip list -r raspi | grep '/' > /tmp/usbip-exportable-list"
            
            # cleaning garbage after yourself
            ExecStop=-rm /tmp/usbip-exportable-list
            # sometime had happened too
            ExecStop=systemctl reset-failed
            
            [Install]
            WantedBy=multi-user.target

            Und noch ein kleiner Dienst für den Fehlerfall (siehe Kommentare im Code):

            /etc/systemd/system/usbipd-restart.service

            [Unit]
            Description=USB-IP Deamon Remote Restarting (runs by client)
            After=network-online.target usbipd.service
            Wants=network-online.target usbipd.service
            
            [Service]
            Type=simple
            
            EnvironmentFile=/etc/usbip.conf
            
            # Is any USB port already available? - This is the case if we bekome online after the server.
            # If so - restart usbipd.service remotely to be attached.
            # If not (file is empty) - nothing to do, USB will be attached
            # later on while being inserted into the host.
            ConditionFileNotEmpty=/tmp/usbip-exportable-list
            ExecStart=-ssh root@raspi "systemctl restart usbipd"
            
            # Clean up usbipd's garbage
            ExecStartPost=-rm /tmp/usbip-exportable-list
            ExecStartPost=systemctl reset-failed
            
            [Install]
            WantedBy=multi-user.target

            systemctl daemon-reload
            systemctl enable usbipd && sudo systemctl start usbipd

            Damit steht der Scanner dynamisch über ein Ethernet Netzwerk als USB-Gerät auf dem Verarbeitungsserver zur Verfügung. 

            Jetzt kann auf dem Verarbeitungsserver die Scannersoftware eingerichtet werden, so als ob der Scanner am lokalen USB-Port hängen würde, also z.B. ein Treiber/Software/Dienst, der den Scan-Button auslesen kann (falls es das für den eingesetzten Scanner gibt). Auch das Anstossen des eigentlichen Scans, sowie die weitere Verarbeitung müssen über eine zum Scanner passende Scansoftware konfiguriert werden. Tut man das auf einem Server (z.B. eine VM) im Netz und richtet den Prozess mitsamt fester Scanparameter so ein, dass die fertigen Dokumente auf ein Netzlaufwerk gestellt werden, hat man eine maximal flexible Lösung, wo der Scanner im Prinzip überall stehen kann, solange eine Netzwerkdose in der Nähe ist.

            Der Prozess für den Benutzer ist einfach: Der Raspi läuft bei mir immer (ich habe noch keine Lösung gefunden, um ihn durch das Einschalten eines angesteckten USB-Gerätes zu starten). D.h. Scanner einschalten, warten bis er „da ist“ (wenige Sekunden), in der Zwischenzeit die Dokumente einlegen und auf den Scan-Knopf drücken. Der dadurch angestoßene Prozess über die remote Services, die den eigentlichen Scan auslösen dauert bei mir unter 1 Sekunde. Fast augenblicklich fängt der Scanner an zu arbeiten.

            [print-me]

            Schreibe einen Kommentar