Vom Proof of Concept zur Produktion: snapmaker_moonraker erreicht v1.1.0

# Vom Proof of Concept zur Produktion: snapmaker_moonraker erreicht v1.1.0

Im Februar haben wir [Die Brücke zwischen Snapmaker J1S und dem Moonraker-Ökosystem](/bridging-the-snapmaker-j1s-to-the-moonraker-ecosystem/) veröffentlicht und beschrieben, wie wir eine Open-Source-Protokollbrücke gebaut haben, die den Snapmaker J1S wie einen Klipper-Drucker erscheinen lässt. Zum Zeitpunkt der Veröffentlichung konnte die Brücke Dateien aus PrusaSlicer senden, Temperaturen überwachen, Drucke verfolgen und eine Webcam streamen — aber es fehlten noch einige Teile. Spoolman-Integration stand auf der Roadmap. Die Dual-Extruder-Steuerung war unvollständig. Die IDEX-Fähigkeiten des J1S — Copy- und Mirror-Modus — waren noch unangetastet.

Vier Wochen und vierzig Commits später ist jeder Punkt dieser Roadmap abgearbeitet, und die Brücke hat Fähigkeiten hinzugewonnen, die wir ursprünglich nicht geplant hatten. Dieser Beitrag behandelt den Weg von v0.0.7 zu v1.1.0: was sich geändert hat, was wir gelernt haben und wo das Projekt heute steht.

### Das Token-Problem, das keines war

Der ursprüngliche Beitrag listete „Token-Aktualisierung und -Persistenz» als zukünftige Herausforderung. Snapmaker-Drucker authentifizieren HTTP-API-Verbindungen mit einem Token, der durch Antippen des Touchscreens bestätigt werden muss und bei einem Neustart verloren geht. Wir hatten erwartet, einen Token-Aktualisierungsmechanismus implementieren zu müssen.

Es stellte sich heraus, dass das nicht nötig ist. Das SACP-Protokoll des J1S auf Port 8888 — der einzige Kommunikationskanal, den die Brücke nutzt — erfordert keinerlei Token-Authentifizierung. Der Token war nur für die HTTP-API auf Port 8080 relevant, die, wie wir früh bestätigen konnten, auf dem J1S geschlossen ist. Da die Brücke alles über SACP leitet, trifft das Problem der Touchscreen-Bestätigung schlicht nicht zu. Das Token-Feld existiert noch in der Konfigurationsdatei, ist aber ein Überbleibsel — eine Erinnerung an den HTTP-API-Pfad, den wir nie eingeschlagen haben.

Dies ist ein gutes Beispiel dafür, wie Einschränkungen ein Design vereinfachen können. Auf reine SACP-Kommunikation beschränkt zu sein, fühlte sich anfangs wie eine Limitierung an, eliminierte aber eine ganze Klasse von Authentifizierungs- und Sitzungsverwaltungsproblemen.

### Der Brücke GCode beibringen: Der Post-Processor

Das bedeutendste neue Subsystem ist der GCode-Post-Processor, eingeführt in v0.0.8. Das Problem, das er löst, ist subtil, aber entscheidend: Der Touchscreen-HMI des Snapmaker J1S liest Metadaten aus einem benutzerdefinierten Header-Block am Anfang der GCode-Datei. Ohne diesen Header zeigt der Touchscreen keine Druckinformationen an — keine geschätzte Zeit, keinen Materialtyp, keine Thumbnail-Vorschau. Luban generiert diese Header automatisch; PrusaSlicer nicht.

Der Post-Processor läuft als zweistufige Pipeline bei jedem Datei-Upload.

**Pass 1** durchsucht die gesamte Datei, um Metadaten zu extrahieren: Temperaturen pro Extruder aus M104/M109-Befehlen, Betttemperatur aus M140/M190, Bounding Box aus G0/G1-Bewegungen, Filamentverbrauch pro Tool in Millimetern (wobei absolute und relative Extrusion für jedes Tool separat verfolgt werden), Schichthöhe, geschätzte Druckzeit und Filamenttyp aus Slicer-Kommentaren. Ausserdem erkennt er M605-Befehle zur IDEX-Modusauswahl — dazu später mehr.

**Pass 2** transformiert den GCode Zeile für Zeile. Drei Transformationen finden hier statt:

**Tool-Nummern-Remapping.** Die Dual-Extruder-Konfiguration von PrusaSlicer weist Tools als T2 und T3 zu (eines pro physischem Drucker in seinem internen Modell). Der J1S erwartet T0 und T1. Der Post-Processor mappt alle Tool-Nummern modulo 2 um — T2 wird zu T0, T3 wird zu T1 — über Toolwechsel-Befehle, Temperaturbefehle (M104/M109) und Lüfterbefehle (M106/M107) hinweg.

**Abschaltung ungenutzter Düsen.** Wenn ein Toolwechsel stattfindet und der Post-Processor aus seiner ersten Analyse weiss, dass das vorherige Tool in der Datei nicht mehr verwendet wird, injiziert er `M104 S0 Tx`, um den betreffenden Heizer abzuschalten. Bei einem Dual-Extruder-Drucker, bei dem viele Aufträge nur eine Düse für einen Teil des Drucks verwenden, verhindert dies, dass ein ungenutztes Hotend unnötig auf Temperatur gehalten wird — das spart Energie und reduziert das Risiko von Heat Creep.

**Snapmaker-V1-Header-Generierung.** Der Post-Processor erstellt einen 25-zeiligen Metadaten-Header im Snapmaker-V1-Format, der den Druckernamen, die geschätzte Zeit, die Gesamtzeilenzahl, Felder pro Extruder (Düsendurchmesser, Material, Temperatur, Retraction-Einstellungen), Betttemperatur, Arbeitsbereich-Bounding-Box und — entscheidend — den IDEX-Extruder-Modus enthält.

Der Header enthält auch ein **Thumbnail**. Der Post-Processor extrahiert PrusaSlicer- und OrcaSlicer-Thumbnail-Blöcke (base64-kodierte PNG-Daten, die in GCode-Kommentaren eingebettet sind) und konvertiert sie in das Data-URI-Format, das der J1S-Touchscreen erwartet. Das bedeutet, dass aus Mainsail hochgeladene, geslicte Dateien korrekte Thumbnail-Vorschauen auf dem Bildschirm des Druckers anzeigen — ein kleines Detail, das einen echten Unterschied macht, wenn man mehrere Druckaufträge in der Warteschlange verwaltet.

Der Post-Processor ist idempotent: Wenn er einen bereits vorhandenen `;Header Start`-Marker erkennt, gibt er die Datei unverändert zurück.

### Native Drucksteuerung: Die Zwischenschicht entfernen

Die ursprüngliche Brücke steuerte Drucke durch das Senden von GCode-Befehlen (M24 zum Fortsetzen, M25 zum Pausieren) über den GCode-Ausführungskanal von SACP. Das funktionierte, war aber ein indirekter Weg — wir liessen die Firmware Textbefehle interpretieren, obwohl wir in ihrem nativen Binärprotokoll mit ihr sprechen konnten.

In v0.1.0 wurde die Drucksteuerung auf native SACP-Befehle umgeschrieben: `0xAC/0x04` für Abbruch, `0xAC/0x05` für Pause, `0xAC/0x06` für Fortsetzen. Die Brücke erhielt ausserdem eine ordentliche Druckstartsequenz über `sacp.StartScreenPrint()`, die den Druck über den Touchscreen-MCU initiiert — und so sicherstellt, dass das HMI mit dem tatsächlichen Druckzustand synchron bleibt.

Eine verwandte Verbesserung in v0.0.8 war das **Double-Disconnect-Upload-Muster**. Beim Hochladen einer Datei stellte sich heraus, dass der J1S-Touchscreen Zeit braucht, um die neue Datei zu indexieren, bevor sie gedruckt werden kann. Die Lösung ist eine bestimmte Abfolge: Datei hochladen, SACP-Verbindung trennen, drei Sekunden warten, bis das HMI seinen Dateiindex finalisiert hat, neu verbinden und dann den Druckbefehl senden. Ohne diese Pause erkennt der Touchscreen die Datei nicht und der Druck startet nicht. Dies war eines jener Verhaltensweisen, die keine Dokumentation beschreibt — es brauchte wiederholte Tests am echten Drucker, um das Problem zu identifizieren und zu lösen.

Die Upload-Sequenz erhielt auch eine **Auto-Start**-Funktion: Wenn eine Datei aus Mainsail mit der Option „Druck starten» hochgeladen wird, übernimmt die Brücke den gesamten Zyklus aus Upload → Trennen → Neuverbinden → Starten automatisch.

### Dual-Extruder-Steuerung: Zwei Düsen, zwei Lüfter, zwei Spulen

Die ursprüngliche Brücke konnte Temperaturen von beiden Extrudern auslesen, aber sie nicht unabhängig steuern. v0.2.0 führte die vollständige Dual-Extruder-Temperatursteuerung ein, indem M104/M109 (Extruder) und M140/M190 (Bett) Befehle von der Mainsail-Konsole abgefangen und über die SACP-Befehle `SetToolTemperature` (CommandSet `0x10`, CommandID `0x02`) und `SetBedTemperature` (`0x14/0x02`) geroutet werden. Der Klipper-Befehl `ACTIVATE_EXTRUDER` wechselt den Extruder, den Mainsail als „aktiv» betrachtet.

v1.0.0 erweiterte dies um die **Lüftersteuerung**. Der J1S hat unabhängige Bauteilkühler pro Extruder, und die Brücke stellt sie nun als separate Moonraker-Lüfterobjekte bereit — `extruder_partfan` und `extruder1_partfan`. M106/M107-Befehle werden basierend auf dem P-Parameter an den richtigen physischen Lüfter geleitet (mit dem gleichen Mod-2-Remapping wie bei den Tool-Nummern), und Klippers `SET_FAN_SPEED`-Befehl funktioniert für die direkte Lüftersteuerung über die Mainsail-Oberfläche.

### Spoolman: Von einer einzelnen Spule zur Verfolgung pro Extruder

Der ursprüngliche Beitrag listete die Spoolman-Integration als wichtigsten verbleibenden Meilenstein. v0.0.7 lieferte die erste Implementierung: Spulen im Spoolman-Panel von Mainsail durchsuchen und auswählen, Filamentverbrauch während des Drucks an den Spoolman-Server melden und die Spoolman-Verbindung per Health-Check prüfen.

Doch die erste Implementierung verfolgte nur eine einzelne Spule — ausreichend für Einzelextruder-Aufträge, falsch für einen Dual-Extruder-Drucker. v1.0.0 baute die Verfolgung auf eine Pro-Extruder-Basis um.

Die Änderungen betrafen jede Schicht:

**Datenbankspeicherung** wechselte von einem einzelnen `spoolman.spool_id`-Schlüssel zu `spoolman.spool_id.0` und `spoolman.spool_id.1`, mit automatischer Migration des alten Schlüssels auf Tool 0 beim ersten Start.

**Die API** erhielt einen `tool`-Parameter sowohl auf den HTTP-Endpunkten (`GET/POST /server/spoolman/spool_id`) als auch in den WebSocket-RPC-Handlern, passend zur erweiterten Moonraker-API, die Mainsails Spoolman-Panel für Multi-Extruder-Setups verwendet.

**Die Filament-Buchhaltung** im GCode-Post-Processor wurde korrigiert, um die Extrusion pro Tool unabhängig zu verfolgen. Der ursprüngliche einzelne `lastAbsE`-Akkumulator wurde in `lastAbsE[2]` aufgeteilt, einen pro Extruder. Ohne diese Korrektur hätte ein Dual-Extruder-Auftrag den gesamten Filamentverbrauch dem zuletzt aktiven Tool zugeordnet.

**Die Verbrauchsmeldung** während des Drucks berechnet Deltas pro Tool anhand der GCode-Zeilennummer (abgeglichen mit den kumulativen Filament-Arrays pro Tool, die während der Nachbearbeitung extrahiert wurden) und sendet unabhängige PUT-Requests an den Spoolman-Server für die Spule jedes Tools. Meldungen werden nur ausgelöst, wenn das Delta 0,1 mm überschreitet, um Rauschen durch Fahrbewegungen und Retractions zu vermeiden.

Ein letztes Puzzlestück kam nach v1.0.0 hinzu: Die Brücke stellt nun eine `tool_spool_map` in der Spoolman-API-Antwort bereit, die Mainsails Multi-Extruder-Spoolman-Panel ausliest, um Spulenzuweisungen pro Tool anzuzeigen. Ohne dieses Feld fällt Mainsail auf den Einzelspulenmodus zurück, unabhängig davon, wie viele Spulen im Backend konfiguriert sind.

Das Ergebnis: Mainsail zeigt die korrekte Spule pro Extruder an, der Filamentverbrauch wird unabhängig verfolgt, und der Spoolman-Server hat genaue Verbrauchsdaten pro Spule — das gleiche Setup, das wir auf unseren Einzelextruder-Klipper-Maschinen betreiben, aber erweitert für Dual-Extrusion.

### IDEX Copy- und Mirror-Modus

Der J1S ist ein IDEX-Drucker (Independent Dual Extrusion), was bedeutet, dass seine beiden Druckköpfe unabhängig voneinander bewegt werden können. Über den standardmässigen Dual-Material-Druck hinaus ermöglicht IDEX zwei Produktivitätsmodi: **Copy** (beide Köpfe drucken gleichzeitig dasselbe Teil, was den Durchsatz verdoppelt) und **Mirror** (beide Köpfe drucken gespiegelte Kopien, nützlich für symmetrische Teile). Diese Modi über die Brücke zum Laufen zu bringen, erforderte die Koordination von drei separaten Mechanismen.

**GCode-Erkennung.** PrusaSlicer signalisiert den IDEX-Modus über `M605 S2` (Copy/Duplication) oder `M605 S3` (Mirror) im Start-GCode. Der erste Pass des Post-Processors erkennt diese Befehle und zeichnet den Modus auf.

**V1-Header.** Der erkannte Modus wird in den Snapmaker-V1-Header als `;Extruder Mode:Duplication` oder `;Extruder Mode:Mirror` geschrieben. Dies war die entscheidende Erkenntnis — das J1S-HMI liest dieses Header-Feld, um den Druckmodus vor der GCode-Ausführung zu konfigurieren. Ohne den korrekten Header ignoriert der Drucker M605 vollständig und druckt im Standardmodus.

**SACP-SetPrintMode-Befehl.** Als zusätzliche Absicherung sendet die Brücke den SACP-Befehl `0xAC/0x0A` mit dem entsprechenden Modus-Byte (`0x02` für Duplication, `0x03` für Mirror) nach dem Upload und vor dem Druckstart. Dies stellt sicher, dass sich die Firmware im korrekten Modus befindet, selbst wenn das Parsen des HMI-Headers Randfälle aufweist.

v1.1.0 wird ausserdem mit **zwölf PrusaSlicer-Druckerprofilen** ausgeliefert, die alle drei Modi (Default, Copy, Mirror) mit jeweils acht Qualitätsvoreinstellungen abdecken (von 0,08 mm ultrafein bis 0,28 mm Entwurf). Die Copy- und Mirror-Profile verwenden halbe Bettabmessungen (150×200 mm), um den gespiegelten/duplizierten Druckbereich zu berücksichtigen, und enthalten die korrekten M605-Befehle in ihrem Start-GCode. Diese Profile ermöglichen es, von der Installation der Brücke bis zum Drucken im IDEX-Copy-Modus zu gelangen, ohne in PrusaSlicer manuell etwas konfigurieren zu müssen.

### Sicherheitshärtung

v0.2.1 war ein dediziertes Sicherheitsrelease. Die Brücke ist ein Netzwerkdienst, der Datei-Uploads akzeptiert und Befehle auf einem Drucker ausführt — eine Angriffsfläche, die sorgfältige Aufmerksamkeit verdient.

**Schutz vor Path Traversal.** Alle Dateioperations-Endpunkte (Upload, Download, Löschen, Verschieben, Kopieren, Verzeichniserstellung) validieren nun, dass aufgelöste Pfade innerhalb des GCode-Speicherverzeichnisses bleiben. Versuche, über `../`-Sequenzen auszubrechen, werden abgewiesen, bevor eine Dateisystemoperation stattfindet.

**Schutz vor Namespace-Injection.** Der Namespace-Parameter der Datenbank-API wird gegen ein striktes Allowlist-Muster validiert, um das Einschleusen beliebiger Schlüssel in den persistenten Speicher zu verhindern.

**Header-Bereinigung.** HTTP-Antwort-Header, die aus benutzerdefinierten Werten konstruiert werden (wie Dateinamen in Content-Disposition), werden bereinigt, um Header-Injection-Angriffe zu verhindern.

**WebSocket-Nachrichtengrössenbegrenzung.** WebSocket-Verbindungen erzwingen eine maximale Nachrichtengrösse, um Speichererschöpfung durch überdimensionierte Payloads zu verhindern.

**Systemctl-Befehlsvalidierung.** Die Service-Management-API (von Mainsail zum Neustarten/Stoppen von Diensten verwendet) validiert Dienstnamen gegen eine strikte Allowlist, bevor sie an systemctl übergeben werden.

Keine dieser Massnahmen war eine Reaktion auf entdeckte Schwachstellen — es handelte sich um proaktive Härtung basierend auf einer Überprüfung der Angriffsfläche. Für einen Dienst, der auf einem Raspberry Pi in einem lokalen Netzwerk läuft, ist das Risiko gering, aber die Kosten, es richtig zu machen, sind es ebenfalls.

### Temperaturverlauf und der Ring Buffer

Eine der unauffälligeren Verbesserungen in v0.2.0 war die Einführung eines **Temperaturverlaufsspeichers** — ein Ring Buffer mit 1200 Messwerten pro Sensor, der den Temperaturgraphen von Mainsail speist. Die ursprüngliche Brücke meldete nur aktuelle Temperaturen; Mainsails Graph zeigte einen einzelnen Punkt, der bei jeder Aktualisierung sprang. Mit dem Ring Buffer zeigt der Graph einen gleichmässigen, rollierenden Verlauf an, der dem Verhalten entspricht, das Benutzer von einer echten Moonraker-Instanz erwarten.

### SACP-Verbindungsresilienz

Der ursprüngliche Beitrag merkte an, dass die SACP-Verbindung „gelegentlich abbricht, wobei die automatische Wiederverbindung sich schnell erholt». v0.1.1 adressierte dies mit einem ordentlichen Wiederholungsmechanismus: Wenn die Brücke ihre SACP-Verbindung verliert, versucht sie bis zu fünf Mal eine Neuverbindung mit einer Verzögerung von zwei Sekunden zwischen den Versuchen. Die Verzögerung ist wichtig, weil die J1S-Firmware manchmal Zeit braucht, um eine abgebrochene Verbindung aufzuräumen, bevor sie eine neue akzeptiert — zu schnelles Neuverbinden führt zu einer abgelehnten Verbindung.

Die Brücke erhielt in v0.2.0 ausserdem **Disconnect/Connect-Befehle**, die über Mainsails Service-Management-Panel zugänglich sind und eine manuelle Neuverbindung ermöglichen, ohne den gesamten Brückenprozess neu starten zu müssen.

### Plattformübergreifende Builds

Ab v0.1.1 erzeugt die CI-Pipeline Binärdateien für drei Plattformen: Linux x86_64, Windows x86_64 und macOS ARM64 (Apple Silicon). Der primäre Anwendungsfall bleibt das Raspberry-Pi-SD-Karten-Image, aber plattformübergreifende Binärdateien bedeuten, dass die Brücke auf einem Desktop-Rechner zur Entwicklung oder zum Testen laufen kann — oder auf jedem Linux-Server im Netzwerk, nicht nur auf einem RPi.

### Wo wir heute stehen

Der ursprüngliche Beitrag endete mit einer Liste zukünftiger Arbeiten. Hier ist der aktuelle Stand jedes Punktes:

| Ursprünglicher Roadmap-Punkt | Status |
|—|—|
| Spoolman-Filamentverfolgung | Erledigt (v0.0.7), erweitert auf Pro-Extruder-Verfolgung (v1.0.0) |
| 0% Fortschritt bei über Touchscreen gestarteten Drucken | Gelöst — native SACP-Drucksteuerung bedeutet, dass Drucke immer über die Brücke gestartet werden |
| SACP-Verbindungszuverlässigkeit | Erledigt (v0.1.1) — 5 Wiederholungsversuche mit konfigurierbarer Verzögerung |
| Token-Aktualisierung/-Persistenz | Nicht erforderlich — SACP erfordert keine Authentifizierung |

Und die Fähigkeiten, die nicht auf der ursprünglichen Roadmap standen, aber trotzdem hinzukamen:

– GCode-Post-Processor mit Snapmaker-V1-Headern, Tool-Remapping, Düsenabschaltung und HMI-Thumbnails
– IDEX Copy- und Mirror-Modus-Unterstützung mit PrusaSlicer-Profilen
– Vollständige Dual-Extruder-Temperatur- und Lüftersteuerung
– Sicherheitshärtung über alle netzwerkexponierten Endpunkte
– Plattformübergreifende Binärdateien
– Mainsail-Konfigurationsdatei-Editor und Temperaturverlaufsgraphen

Die Versionsnummer erzählt die Geschichte. Im Februar war v0.0.6 ein funktionaler Proof of Concept — er konnte den Kern-Workflow (Slicen → Hochladen → Drucken → Überwachen) bewältigen, aber mit rauen Kanten und fehlenden Teilen. Heute ist v1.1.0 ein Produktionswerkzeug, das jeden Druckmodus des J1S unterstützt, Filament pro Extruder verfolgt, ordentliche Sicherheitsgrenzen bietet und mit gebrauchsfertigen Slicer-Profilen ausgeliefert wird.

Der Snapmaker J1S ist jetzt ein vollwertiges Mitglied unserer Druckfarm. Er slict aus derselben PrusaSlicer-Installation, lädt in dieselbe Mainsail-Oberfläche hoch, verfolgt Filament in derselben Spoolman-Instanz und überwacht Drucke mit demselben Obico-Server wie jede andere Maschine in der Werkstatt. Die Protokollbrücke, die als Möglichkeit begann, Luban zu umgehen, ist zu einer vollständigen Flottenintegrationsschicht geworden.

### Was noch bleibt

Das Projekt ist nicht abgeschlossen — Software ist es nie — aber die verbleibenden Punkte sind Verfeinerungen und keine fehlenden Kernfunktionen:

– **Z-Baby-Stepping** (M290) wird akzeptiert, aber noch nicht über SACP implementiert. Dies ist eine Komfortfunktion für Live-Anpassungen der ersten Schicht.
– **Breitere Druckerunterstützung.** Die Brücke ist für den J1S gebaut, aber das SACP-Protokoll wird über die gesamte Snapmaker-Produktpalette geteilt. Der GCode-Post-Processor generiert bereits V0-Header für die A150/A250/A350/Artisan-Modelle. Die Erweiterung der vollständigen Unterstützung auf andere Snapmaker-Drucker ist architektonisch machbar, wobei jedes Modell seine eigenen Protokoll-Eigenheiten haben wird, die es zu entdecken gilt.
– **Community-Tests.** Das Projekt wurde gegen einen einzelnen J1S entwickelt und getestet. Mehr Benutzer bedeuten mehr Randfälle, Firmware-Versionen und Netzwerkkonfigurationen — die Art von Praxisvalidierung, die kein Umfang an Einzeltests ersetzen kann.

### Open Source & KI-Offenlegung

snapmaker_moonraker ist weiterhin auf [GitHub](https://github.com/goeland86/snapmaker_moonraker) unter der MIT License verfügbar. Es baut auf der SACP-Protokollarbeit von sm2uploader von macdylan und snapmaker-sm2uploader von kanocz auf.

Dieses Projekt wird weiterhin mit Unterstützung von Claude (Anthropic) entwickelt. Jeder Commit im Repository ist co-authored, was einen Mensch-KI-Kollaborations-Workflow widerspiegelt, der sich für diese Art von protokollnaher Systemprogrammierung als effektiv erwiesen hat — insbesondere für die mühsame, aber kritische Arbeit des Parsens binärer Protokolle, bei der ein KI-Mitarbeiter, der über eine mehrstündige Sitzung hinweg nicht den Überblick über Byte-Offsets und Endianness verliert, tatsächlich nützlich ist.

Folgen
Newest Art:
SHOPPING BAG 0
RECENTLY VIEWED 0