# cc2-spoolman-bridge Bridge zwischen der Canvas des **Elegoo Centauri Carbon 2** (Community-Firmware, CC2 Control) und **Spoolman**, mit Weboberfläche und **NFC-Tag-Generator**. - Liegt eine Spule mit eigenem Tag in der Canvas, erkennt die Bridge die Spoolman-Spule am Tag, legt sie in CC2 Control an (bzw. findet sie wieder), weist sie dem Slot zu und setzt in Spoolman den Ort („CC2 Slot 1–4“, beim Entnehmen „Lager“). - Den von CC2 Control gemessenen Verbrauch bucht sie nach jedem Druck auf genau diese Spoolman-Spule. - Der Tag-Generator gibt für jede Spoolman-Spule die vollständige Befehlskette zum Beschreiben eines leeren NTAG213 aus. ## Was die Bridge darf und was nicht | System | Zugriff | |---|---| | Drucker | **nur lesen**: `cat /opt/usr/canvas_record_info.json` per SSH. Kein MQTT, keine Druckerbefehle, keine Registrierung als MQTT-Client. | | CC2 Control | Spulenverwaltung: `GET /api/spools`, `POST /api/spools/save`, `/assign`, `/enable` (nur nach Bestätigung) | | Spoolman | `GET /spool`, `PATCH /spool/{id}` (`location`), `PUT /spool/{id}/use` | **Dry-Run ist Standard.** Solange er aktiv ist, werden alle Schreibaktionen nur als „würde …“ protokolliert. Ausschalten unter *Einstellungen* (mit Rückfrage). ## Einrichtung ### 1. SSH-Schlüssel auf dem Drucker einschränken Eigenen Schlüssel erzeugen (ohne Passphrase): ```bash ssh-keygen -t ed25519 -N "" -C cc2-spoolman-bridge -f cc2_bridge_key ``` Auf dem Drucker in `/root/.ssh/authorized_keys` (bzw. dem Pfad, den dein SSH-Server nutzt) eintragen. Jede Ausführung liefert dann nur noch den Canvas-Datensatz, egal welcher Befehl gesendet wird, und nur von `10.10.6.10`: ``` command="cat /opt/usr/canvas_record_info.json",from="10.10.6.10",no-pty,no-port-forwarding,no-agent-forwarding,no-X11-forwarding ssh-ed25519 AAAA… cc2-spoolman-bridge ``` Die Oberfläche zeigt diese Zeile mit dem passenden öffentlichen Schlüssel nach dem Hochladen an. Container auf `10.10.6.10` verbinden sich über Docker-NAT mit der Host-IP, `from="10.10.6.10"` passt also. ### 2. Container starten (Docker-Host 10.10.6.10) ```bash mkdir -p /opt/cc2-spoolman-bridge && cd /opt/cc2-spoolman-bridge # docker-compose.yml und .env.example aus dem Repo hierher kopieren cp .env.example .env mkdir -p data && sudo chown 1000:1000 data # privates Image: einmalig mit einem Gitea-Token (package: Lesen) anmelden docker login git.menzel.center -u menzeljonas docker compose pull && docker compose up -d ``` Oberfläche: `http://10.10.6.10:5066` ### 3. In der Oberfläche 1. *Einstellungen* prüfen (Drucker-IP, CC2-Control- und Spoolman-URL, ID-Offset 10000). 2. Privaten Schlüssel einfügen oder hochladen und speichern. Er liegt dann mit Rechten 600 in `/data/ssh/id_key`; die Oberfläche zeigt nur Fingerprint und öffentlichen Schlüssel. 3. **Verbindung testen**: zeigt die 4 Slots und den Host-Key-Fingerprint des Druckers. Fingerprint vergleichen und bestätigen. Danach wird er bei jeder Verbindung geprüft; ein geänderter Host-Key wird abgelehnt. 4. *Status* im Dry-Run beobachten. Wenn die „würde …“-Einträge stimmen, Dry-Run ausschalten. ### Umgebungsvariablen Siehe [`.env.example`](.env.example). `PRINTER_HOST`, `SSH_PORT`, `SSH_USER`, `CC2_URL`, `SPOOLMAN_URL`, `ID_OFFSET`, `POLL_INTERVAL` und `DRY_RUN` sind nur Startwerte, bis die Einstellungen einmal in der Oberfläche gespeichert wurden (`/data/settings.json`). `BRIDGE_USER` und `BRIDGE_PASSWORD` schalten Basic-Auth ein (`/healthz` bleibt offen). `PORT` (Standard 5066), `LOG_LEVEL`. ## Funktionsweise **Tag erkennen.** Die Bridge liest alle ~5 s über eine stehende SSH-Verbindung den Canvas-Datensatz. `channels0`–`channels3` sind Slot 1–4. Ist `weight` ≥ `ID_OFFSET`, ist `weight − ID_OFFSET` die Spoolman-Spulen-ID; kleinere Werte sind echte Gewichte von Original- oder Fremdtags und werden ignoriert. Neu ist ein Tag, wenn sich die ID im Slot gegenüber dem letzten Datensatz ändert. **Zuordnung.** Die Bridge speichert Slot ↔ Spule selbst (`/data/state.json`). Fällt `weight` nach einer Bearbeitung am Display auf 1000 zurück, bleibt die Zuordnung. Aufgehoben wird sie nur, wenn - der Slot länger als 20 s leer ist (Tray-Status 0), - CC2 Control die Spule ins Lager legt (Slot ohne Spule und ohne offene Rückfrage), - im Slot ein Tag mit einer anderen ID gescannt wird oder - die Spule in einem anderen Slot gescannt wird (Umzug). Weist du eine gespiegelte Spule in CC2 Control von Hand einem belegten Slot zu, übernimmt die Bridge das. **CC2 Control.** Gespiegelte Spulen erkennt die Bridge an `note` = `spoolman:`. Fehlt die Spule, legt sie sie mit `name` = Label (`lot_nr`, sonst Hersteller + ID dreistellig), `brand`, `material`, `color`, `diameter`, `density`, `net`, `remaining`, `tare` und `price` aus Spoolman an und weist sie dem Slot zu. Damit ist auch die Rückfrage beantwortet. Passt das Spoolman-Material nicht zum Tag (CC2-Regel: gleich Typ/Name oder Produktlinie wie `PLA+` zu `PLA`), nimmt sie den Untertyp vom Tag, damit CC2 Control nicht erneut fragt. **Verbrauch.** CC2 Control zieht den gemessenen Verbrauch vom `remaining` der zugewiesenen Spule ab. (`used` ist in CC2 Control der Zeitstempel der letzten Nutzung, keine Menge.) Die Bridge bucht für jede gespiegelte Spule die Abnahme von `remaining` seit der letzten Buchung per `PUT /spool/{id}/use` nach Spoolman. Das geschieht erst, wenn kein Druck läuft (`job.active`) und `remaining` 60 s unverändert ist. Der gebuchte Stand wird persistent gespeichert. Vor jeder Buchung schreibt die Bridge die Absicht in den Zustand. Bricht eine Buchung ab, prüft sie nach dem Neustart an `used_weight` in Spoolman, ob sie schon angekommen ist, statt doppelt zu buchen. Steigt `remaining` (z. B. durch Nachwiegen in CC2 Control), wird nichts gebucht, nur protokolliert, und der neue Stand gilt als Ausgangspunkt. **Spulenverwaltung aus.** Ist sie in CC2 Control ausgeschaltet, zählt CC2 Control keinen Verbrauch. Die Oberfläche zeigt das deutlich an und bietet einen Button zum Einschalten (mit Rückfrage, im Dry-Run nur protokolliert). ## Tag-Format (Canvas, NTAG213/215/216) NFC Tools → *Other* → *Advanced NFC commands*, Befehle `A2:PP:XXXXXXXX` (PP = Seite hex), kommagetrennt. Seiten 0–3 werden nie geschrieben. **Nie „Lock tag“ oder „Set password“ verwenden.** | Seite | Inhalt | |---|---| | 04–09 | NDEF-URL (`https://www.elegoo.com`; der Drucker ignoriert sie) | | 10 | `36` + Herstellercode `EEEEEE` | | 11 | `EE` + Filament-Code (2 Byte) + `00` | | 12 | Material (4 Byte) | | 13 | Untertyp (2 Byte) + `0000`; danach wählt der Elegoo Slicer das Profil | | 14 | Farbe RRGGBB + Finish-Byte (`FF`) | | 15 | Düse min / max (je 2 Byte, °C) | | 16 | `00000000` | | 17 | Durchmesser in 1/100 mm + **Gewicht = Spulen-ID + 10000** | | 18 | `0036C800` | Die Farbe wird auf die nächste Farbe der Display-Palette vorgeschlagen (Abstand in CIELAB; gesättigtes Blau wird auf `2850DF` abgebildet). Material und Untertyp werden aus dem Spoolman-Material vorgeschlagen; alles ist änderbar. Optional kann statt der Elegoo-URL ein Link auf die Spule in Spoolman auf den Tag (noch nicht am Drucker getestet). ## Entwicklung ```bash docker build --target test . # Tests (pytest) im Container docker build -t cc2-spoolman-bridge:dev . # Image python tools/mock_env.py --host-header mock:8081 # Mock für Drucker (SSH :2222), CC2 Control, Spoolman ``` Die Tests decken ab: Tag-Kodierung gegen die verifizierte Vorlage, ID-Dekodierung, Palette und Materialvorschlag, Zuordnung bei verschwindender ID, Verbrauchs-Delta und Idempotenz (auch nach Abbruch), Dry-Run, Request-Format für CC2 Control (Host-Header, kein Origin, `X-CC2-Request`, `key=value`-Body, `enable` als nacktes `on`/`off`) und SSH mit Host-Key-Prüfung gegen einen lokalen SSH-Server. CI (Gitea Actions, `.gitea/workflows/ci.yml`): pytest → Build `linux/amd64` → auf `main` Push nach `git.menzel.center/menzeljonas/cc2-spoolman-bridge` mit den Tags `latest` und `sha-`. Für den Push braucht das Repo ggf. ein Actions-Secret `REGISTRY_TOKEN` (Token mit *package*: Lesen und Schreiben). ## Quellen und Lizenzen - Material- und Untertyp-Codes (`app/materials.py`, Lizenztext in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)) aus [cjtallman/canvas-rfid-tool](https://github.com/cjtallman/canvas-rfid-tool) (`index.html`), **MIT License**, Copyright (c) cjtallman. Dort wiederum nach [DnG-Crafts/ELG-RFID](https://github.com/DnG-Crafts/ELG-RFID) und [Savion/elegoo-rfid-editor](https://github.com/Savion/elegoo-rfid-editor). - Spool-API und HTTP-Guard von CC2 Control: [Neme77/centauri-carbon-2-community](https://github.com/Neme77/centauri-carbon-2-community) (`cc2-control/docs/API.md`, Abschnitt „Spool tracking“, und `src/spools.h`, Branch `develop`).