Jonas MenzelandClaude Opus 5.5 a9041eb538
CI / test (push) Successful in 1m6s
CI / image (push) Successful in 43s
Multiple printers, printer/slot in Spoolman, kept in sync
- One worker per printer (name, IP, CC2 Control URL, own pinned host key);
  a spool sits in at most one slot of one printer, moves between printers
- Spoolman: location "<printer> Slot N" plus extra fields "drucker"/"slot"
  (created by the bridge), cleared and "Lager" on removal
- Full comparison every minute: the printer wins over manual edits of loaded
  spools; stale printer locations go to storage; own locations untouched
- Automatic migration of v1 settings, state and host key
- Status hint when a tag is still in a tray but unmapped (e.g. after
  "Take out" in CC2 Control)
- SSH test: connection drops during exec give a clean error, not a 500
- Mock environment with several printers; tests for all of the above

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GcEk5tkfFSYD6NTF66NkC
2026-10-25 03:53:01 +01:00

cc2-spoolman-bridge

Bridge zwischen der Canvas eines oder mehrerer Elegoo Centauri Carbon 2 (Community-Firmware, CC2 Control) und Spoolman, mit Weboberfläche und NFC-Tag-Generator.

  • Liegt eine Spule mit eigenem Tag in einer Canvas, erkennt die Bridge die Spoolman-Spule am Tag, legt sie in der CC2 Control dieses Druckers an (bzw. findet sie wieder) und weist sie dem Slot zu.
  • In Spoolman steht dann, wo die Rolle liegt: Ort „ Slot N“ (z. B. „Centauri Carbon 2 Links Slot 3“) und die Zusatzfelder Drucker und Slot. Entnommene Rollen kommen ins „Lager“, die Felder werden geleert.
  • 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):

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)

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 → Drucker: Name, IP, SSH-Port/-Benutzer und CC2-Control-URL je Drucker; „Drucker hinzufügen“ für weitere. Speichern. Der Name erscheint so in Spoolman (Ort und Feld „Drucker“).
  2. Privaten Schlüssel einfügen oder hochladen und speichern. Er gilt für alle Drucker (dieselbe eingeschränkte authorized_keys-Zeile auf jedem Drucker) und liegt mit Rechten 600 in /data/ssh/id_key; die Oberfläche zeigt nur Fingerprint und öffentlichen Schlüssel.
  3. Je Drucker Verbindung testen: zeigt die 4 Slots und den Host-Key-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.

Ein Update von v1 übernimmt Einstellungen, Zuordnungen, Buchungsstände und den Host-Key automatisch; der bisherige Drucker heißt danach „CC2“ (Ort bleibt „CC2 Slot N“) und kann in den Einstellungen umbenannt werden – Ort und Felder in Spoolman ziehen dann mit.

Umgebungsvariablen

Siehe .env.example. PRINTER_HOST, SSH_PORT, SSH_USER, CC2_URL, SPOOLMAN_URL, ID_OFFSET, POLL_INTERVAL, DRY_RUN und PRINTER_NAME (erster Drucker) 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 Drucker/Slot ↔ Spule selbst (/data/state.json). Eine Spule liegt höchstens in einem Slot eines Druckers. 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 herausnimmt („Take out“) oder 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 oder im anderen Drucker gescannt wird (Umzug).

Weist du eine gespiegelte Spule in CC2 Control von Hand einem belegten Slot zu, übernimmt die Bridge das.

„Take out“ in CC2 Control zählt als Entnahme: Ort „Lager“, Felder leer. Bleibt die Rolle physisch im Tray, rechnet CC2 Control ihren Verbrauch keiner Spule mehr zu; der Status zeigt dann „Tag steckt noch, ist aber keinem Slot zugeordnet“. Zum erneuten Zuordnen die Rolle neu einlegen und den Scan bestätigen.

Spoolman synchron halten. Nach jeder Änderung schreibt die Bridge Ort und Zusatzfelder (die Felder legt sie beim ersten Mal selbst an). Zusätzlich vergleicht sie jede Minute alle Spoolman-Spulen: Geladene Rollen bekommen ihren Ort/ihre Felder zurück, falls sie dort von Hand geändert wurden (der Drucker gewinnt). Rollen mit einem Druckerort oder gesetztem Feld „Drucker“, die in keinem Slot liegen, kommen ins Lager. Eigene Orte wie „Regal 2“ bei nicht geladenen Rollen bleiben unangetastet. Wird ein Drucker umbenannt oder entfernt, ziehen Ort und Felder mit bzw. seine Rollen kommen ins Lager.

CC2 Control. Gespiegelte Spulen erkennt die Bridge an note = spoolman:<id>. 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

docker build --target test .                     # Tests (pytest) im Container
docker build -t cc2-spoolman-bridge:dev .        # Image
python tools/mock_env.py --host mock --printers 2  # Mock: 2 Drucker (SSH :2222/2223, CC2 :8081/8082), 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, mehrere Drucker (Umzug, Ausfall eines Druckers, Entfernen, Umbenennen), Spoolman-Ort und Zusatzfelder inkl. Korrektur von Hand- Änderungen, Migration von v1, 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-<commit>. Für den Push braucht das Repo ggf. ein Actions-Secret REGISTRY_TOKEN (Token mit package: Lesen und Schreiben).

Quellen und Lizenzen

S
Description
Elegoo CC2 Canvas ↔ Spoolman bridge mit NFC-Tag-Generator
Readme
561 KiB
0 Stars 1 Watchers 0 Forks
Languages
Python 70.7%
HTML 29%
Dockerfile 0.3%