Der Gedanke, einen echten PC als zentralen Rechner mit vielen Octoprint-Instanzen zu nutzen, ist naheliegend und preiswert. Dabei will man jedoch nicht zwangsweise auf dem Host selbst installieren, sondern in eine virtuelle Maschine. Das hat zahlreiche Vorteile, wie z.B. Snapshots und vereinfachte Hostmigration, bringt aber auch Komplexität mit sich.
Die virtuelle Maschine kann die Drucker nun nicht mehr direkt sehen, weil /dev
auf dem Host nicht gleich /dev
in der virtuellen Maschine ist. Daher müssen die Drucker dynamisch und möglichst automatisch an die die virtuelle Maschine durch gereicht werden. Unter Linux kein Problem, dank udev.
Udev-Regel im VM Host erstellen
Die Regeln für udev sind leicht erstellt. Vorher braucht man aber den Identifikator des Gerätes. Ich nehme meist
watch -n .1 lsusb
und verbinde dann das Gerät. So sehe ich schnell, welches Gerät dazu kommt und welchen Identifikator es hat. Mein Drucker sieht dann so aus. Die ID
ist die entscheidende Information.
Bus 003 Device 007: ID 1a86:7523 QinHeng Electronics CH340 serial converter
Nun die Konfigurationsdatei in /etc/udev/rules.d/20_3dprinter.rules
erstellen. Meine sieht so aus.
ACTION=="add", SUBSYSTEMS=="usb", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", ENV{DEVTYPE}=="usb_device", RUN+="/opt/scripts/attach_wanhaoi3plus.sh"
Damit wird beim Anstecken des Gerätes ein Skript ausgeführt. Udev kann leider keine Argumente an die ausgeführten Skripte weitergeben. Daher braucht jeder Drucker ein eigenes Skript.
Dann das Skript in /opt/scripts/attach_wanhaoi3plus.sh
erstellen.
set -x
DEVICE="device octoprint --file /opt/scripts/wanhao_usb.xml --persistent --live"
virsh attach-"$DEVICE"
if [ $? != 0 ]; then
virsh detach-"$DEVICE"
virsh attach-"$DEVICE"
fi
sleep 2
Da udev
in der VM nicht wissen kann, ob das Gerät noch mit dem Host verbunden ist, muss dieser Fehlerfall behandelt werden. Wenn das Gerät noch an der virtuellen Maschine hängt, aber neu angesteckt wird und die udev
-Regel auslöst, ist es sowieso dysfunktional. Die virtuelle Maschine hat halt nicht mitbekommen, dass das Gerät nicht mehr da ist. Das kann durch kaputte Kabel etc. schon mal passieren.
Wie man im Skript bereits sieht, ist die Ressourcendefinition für KVM eine .xml
-Datei. Die ist dankenswerterweise sehr kurz. Es sollte auch auf Anhieb klar sein, dass hier die ID
von weiter oben erneut angepasst werden muss.
<hostdev mode='subsystem' type='usb' managed='yes'>
<source>
<vendor id='0x1a86'/>
<product id='0x7523'/>
</source>
</hostdev>
Anschließend mit
udevadm control --reload-rules
udev
neu laden, damit die neu erstellte Regel funktioniert. Wenn alles geklappt hat, ist das Gerät unter der exakt gleichen ID
in der virtuellen Maschine zu finden sein. Die Ausgabe von lsusb | grep '<ID>'
sollte nicht leer sein.
Drucker automatisch mit Octoprint verbinden
In der virtuellen Maschine übernimmt zuerst wieder udev
den entsprechenden Trigger. Die Datei /etc/udev/rules.d/20_3dprinter.rules
ist der oben genannten sehr ähnlich. Es wird aber zusätzlich ein statischer Alias namens wip
hinzugefügt, damit das Gerät im System immer einen eindeutigen Namen bekommt. Statt eines Skriptes wird auch ein Systemd-oneshot-Service gestartet.
ACTION=="add", SUBSYSTEMS=="usb", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", SYMLINK+="wip", TAG+="systemd", ENV{SYSTEMD_WANTS}="octoprint_connect@wip.service"
Das Editieren dieser Datei wieder mit udevadm control --reload-rules
abschließen.
Die Servicedefinition für den oneshot-Service liegt in /etc/systemd/system/octoprint_connect@.service
und hat folgenden Inhalt. Die Platzhalter sind der eigentliche Gimmick, denn so lassen sich für jeden definierten
Alias eine eigene Octoprint-Instanz ansprechen. Somit kann man verschiedene Drucker auf dem gleichen Host betreiben und braucht nicht für jeden Drucker ein eigenes OS.
[Unit]
Description=Connect printer to OctoPrint automatically
BindsTo=dev-%i.device
After=dev-%i.device
[Service]
Type=oneshot
User=moe
RemainAfterExit=yes
ExecStart=/home/octoprint/%I/connect_octoprint.py /dev/%I
Falls Octoprint noch kein Service ist, dann /etc/systemd/system/multi-user.target.wants/octoprint_wip.service
erstellen.
[Unit]
Description=The snappy web interface for your 3D printer
After=network-online.target
Wants=network-online.target
[Service]
Environment="LC_ALL=C.UTF-8"
Environment="LANG=C.UTF-8"
Type=exec
User=octoprint
ExecStart=/home/octoprint/wip/OctoPrint/bin/octoprint serve
[Install]
WantedBy=multi-user.target
Änderungen an Systemd mit systemctl daemon-reload
abschließen und dann die Services aktivieren.
systemctl enable octoprint_wip.service --now
systemctl enable octoprint_connect@wip.service
Für den letzten Schritt brauch man noch einen API-Key aus der Instanz. Den bekommt man unter Einstellungen->API
im Octoprint Webinterface. Dieser Key wird verwendet, um sich in Octoprint zu authentifizieren und den Drucker anzumelden. Das funktioniert über eine REST-API. Das Skript aus o.g. Unitfile liegt in /home/octoprint/wip/connect_octoprint.py
.
#!/home/octoprint/wip/OctoPrint/bin/python
OCTOPRINT_URL = 'http://localhost:5000/api/connection'
API_KEY = '<APIKEY>'
BAUDRATE = 115200
import requests
import sys
port = sys.argv[1]
headers = {'X-Api-Key': API_KEY}
json = {
"command": "connect",
"port": port,
"baudrate": BAUDRATE,
}
r = requests.post(
OCTOPRINT_URL,
json=json,
headers=headers
)
if (r.status_code == 204):
sys.exit(0)
else:
print(r)
sys.exit(1)
Das Ganze Konstrukt ist ziemlich Robust und hat bisher nach über einem Jahr Betrieb immer funktioniert.