# Bondrucker-Bruecke Kleiner Dienst für den Raspberry Pi, an dem der Kassendrucker per USB hängt. ## Warum es ihn gibt Der Bondrucker ist ein USB-Gerät (`/dev/usb/lp0`) und damit nur für den Rechner sichtbar, an dem er steckt. Der Timer-Server läuft woanders. Diese Brücke nimmt fertige ESC/POS-Daten per HTTP entgegen und schiebt sie in das Gerät — mehr nicht. Der Bon selbst wird in der Timer-App gebaut (`backend/receipt.js`), damit Layout-Änderungen nicht auf dem Pi angefasst werden müssen. Nur Python-Standardbibliothek, kein pip, kein CUPS, kein node. ## Installation — der übliche Weg **Aus der Oberfläche:** ⚙️ Einstellungen → **🔧 Drucker einrichten**. Dort IP, Benutzer und Passwort des Pi eintragen und auf **🚀 Jetzt einrichten** klicken. Die App macht dann per SSH genau das, was unten von Hand steht: Dateien übertragen, `install.sh` mit einem frisch erzeugten Token starten, aufräumen und nachsehen, ob der Dienst antwortet. Anschließend übernimmt sie den Drucker selbst — es ist **nichts** mehr in der `.env` einzutragen. Das Protokoll zeigt Schritt für Schritt, was passiert ist. Der Pi braucht dafür nur: SSH, Python 3, systemd, einen Benutzer mit sudo — und den Drucker am USB-Anschluss. Ein frisch aufgesetztes Raspberry Pi OS erfüllt das ab Werk. Die Zugangsdaten werden einmal benutzt und danach verworfen. Gespeichert wird allein das erzeugte Drucker-Token. ## Installation von Hand Falls die App den Pi nicht erreicht (anderes Netz, kein sudo per Passwort): ```bash # Dateien auf den Pi kopieren scp print-server.py bondrucker.service install.sh README.md pi@:/tmp/bondrucker/ # Dort einrichten ssh pi@ cd /tmp/bondrucker && sudo bash install.sh ``` `install.sh` legt an: | Was | Wo | |---|---| | Dienstbenutzer `bondrucker` (Gruppe `lp`) | — | | Programm | `/opt/bondrucker/print-server.py` | | Token | `/etc/bondrucker/env` (`0640`, root:bondrucker) | | udev-Regel | `/etc/udev/rules.d/99-bondrucker.rules` | | Dienst | `bondrucker.service`, aktiviert und gestartet | Am Ende gibt das Skript den Wert für das Token aus. `install.sh` ist wiederholbar: ein erneuter Aufruf überschreibt Programm und Token und **startet den Dienst neu**. Ohne den Neustart liefe er mit dem alten Token weiter und würde jeden Druckauftrag mit HTTP 403 ablehnen. ## Timer-Server konfigurieren Nach der Einrichtung aus der Oberfläche: nichts zu tun. Nach der Einrichtung von Hand: in ⚙️ Einstellungen unter „Drucker einrichten" lassen sich Adresse, Port und Token auch direkt eintragen. Der Druckerzugang liegt in der Datenbank, nicht in der `.env` — ein Neustart des Dienstes ist für einen Druckerwechsel nicht nötig. Die Werte `PRINTER_HOST`, `PRINTER_PORT`, `PRINTER_TOKEN` und `PRINTER_WIDTH` in der `.env` sind nur die **Startwerte beim allerersten Start**. Sind sie leer, ist die Druckfunktion aus und die App verhält sich wie vorher — ohne Bon-Knöpfe, bis ein Drucker eingerichtet wird. ## Prüfen ```bash # Zustand des Druckers curl -s http://:9100/status # Aus der App heraus: Einstellungen -> "Probedruck" ``` Antwortet `/status` mit `"ok": true`, ist das Gerät da und beschreibbar. ## Schnittstelle | Aufruf | Wirkung | |---|---| | `GET /status` | JSON mit Gerät, Beschreibbarkeit, letztem Fehler. HTTP 503, wenn kein Drucker da ist | | `POST /print` | Rumpf = rohe ESC/POS-Bytes, Kopfzeile `X-Print-Token`. Antwortet `{"ok":true,"bytes":N}` | `/status` verlangt **kein** Token — der Zustand des Druckers ist nicht geheim und soll für Health-Checks abfragbar bleiben. Wird trotzdem eines mitgeschickt, meldet die Antwort in `tokenAccepted`, ob es passt. Die App nutzt das nach der Einrichtung als Gegenprobe: erreichbar allein genügt nicht, das Token muss auch angenommen werden. Fehler kommen als HTTP 4xx/5xx mit `{"error": "..."}` zurück — die Timer-App zeigt diesen Text unverändert an. „Papier leer" und „Deckel offen" landen als `OSError` beim Schreiben und damit als HTTP 502 in der Anzeige. ## Betriebshinweise * **Feste IP vergeben.** Der Pi hängt per WLAN und DHCP im Netz. Wechselt die Adresse, druckt die App nicht mehr, bis `PRINTER_HOST` angepasst ist. Eine DHCP-Reservierung im Router ist der einfachste Weg. * **USB-Kabel neu gesteckt?** Aus `lp0` kann `lp1` werden. Der Dienst sucht deshalb selbst nach `/dev/usb/lp*` — es ist nichts zu tun. * **Ein Auftrag nach dem anderen.** Der Dienst serialisiert Druckaufträge, zwei gleichzeitige Bons können sich nicht ineinanderschieben. * **Netzgrenze.** Port 9100 sollte nur aus dem eigenen Netz erreichbar sein. Das Token schützt gegen versehentliche, nicht gegen entschlossene Zugriffe. ## Fehlersuche ```bash systemctl status bondrucker journalctl -u bondrucker -f # jeder Auftrag wird protokolliert # Am Pi direkt drucken, ohne App und ohne Dienst: printf 'Test\n\n\n\n\x1dVB\x00' | sudo tee /dev/usb/lp0 > /dev/null ``` | Symptom | Ursache | |---|---| | `/status` sagt `deviceExists: false` | Drucker aus oder USB-Kabel ab | | `/status` sagt `writable: false` | udev-Regel nicht gegriffen — `sudo udevadm trigger`, Dienst neu starten | | HTTP 403 | `PRINTER_TOKEN` in der `.env` weicht von `/etc/bondrucker/env` ab | | App meldet „nimmt keine Verbindung an" | Dienst läuft nicht, oder falsche IP/Port | | Einrichtung bricht bei „Dienst eingerichtet" ab | sudo-Passwort falsch, oder der Benutzer darf kein sudo | | Einrichtung meldet „lehnt das neue Token ab" | auf dem Pi läuft noch eine Brücke vor 1.1 — Einrichtung wiederholen | | Umlaute als Kringel | Drucker beherrscht Codepage 16 (WPC1252) nicht — siehe `backend/escpos.js` |