Da Proof of Concept a Produzione: snapmaker_moonraker raggiunge la v1.1.0

# Da Proof of Concept a Produzione: snapmaker_moonraker raggiunge la v1.1.0

A febbraio abbiamo pubblicato [Collegare la Snapmaker J1S all’ecosistema Moonraker](/bridging-the-snapmaker-j1s-to-the-moonraker-ecosystem/), descrivendo come abbiamo costruito un bridge protocollare open-source che fa comportare la Snapmaker J1S come una stampante Klipper. Al momento della pubblicazione, il bridge poteva inviare file da PrusaSlicer, monitorare le temperature, seguire le stampe e trasmettere il flusso webcam — ma mancavano ancora dei pezzi. L’integrazione con Spoolman era nella roadmap. Il controllo dual-extruder era incompleto. Le funzionalità IDEX della J1S — modalità Copy e Mirror — non erano state toccate.

Quattro settimane e quaranta commit dopo, ogni punto di quella roadmap è completato, e il bridge ha acquisito funzionalità che non avevamo originariamente previsto. Questo post racconta il percorso dalla v0.0.7 alla v1.1.0: cosa è cambiato, cosa abbiamo imparato e a che punto si trova il progetto oggi.

### Il problema del token che non era un problema

Il post originale elencava “refresh e persistenza del token” come una sfida futura. Le stampanti Snapmaker autenticano le connessioni HTTP API con un token che deve essere confermato toccando il touchscreen, e quel token viene perso al riavvio. Ci aspettavamo di dover implementare un meccanismo di refresh del token.

Si è scoperto che non ne abbiamo bisogno. Il protocollo SACP della J1S sulla porta 8888 — l’unico canale di comunicazione utilizzato dal bridge — non richiede alcuna autenticazione tramite token. Il token era rilevante solo per la HTTP API sulla porta 8080, che abbiamo confermato essere chiusa sulla J1S. Poiché il bridge instrada tutto attraverso SACP, il problema della conferma sul touchscreen semplicemente non si pone. Il campo token esiste ancora nel file di configurazione, ma è vestigiale — un ricordo del percorso HTTP API che non abbiamo mai intrapreso.

Questo è un buon esempio di come i vincoli possano semplificare un design. Essere costretti alla comunicazione esclusivamente via SACP inizialmente sembrava una limitazione, ma ha eliminato un’intera classe di problemi di autenticazione e gestione delle sessioni.

### Insegnare al bridge a parlare GCode: il post-processore

Il nuovo sottosistema più significativo è il post-processore GCode, introdotto nella v0.0.8. Il problema che risolve è sottile ma critico: il touchscreen HMI della Snapmaker J1S legge i metadati da un blocco header personalizzato in cima al file GCode. Senza questo header, il touchscreen non mostra alcuna informazione sulla stampa — nessun tempo stimato, nessun tipo di materiale, nessuna anteprima miniatura. Luban genera questi header automaticamente; PrusaSlicer no.

Il post-processore opera come una pipeline a due passate durante ogni upload di file.

**La passata 1** analizza l’intero file per estrarre i metadati: temperature per estrusore dai comandi M104/M109, temperatura del piatto da M140/M190, bounding box dai movimenti G0/G1, utilizzo di filamento per tool in millimetri (tracciando l’estrusione assoluta e relativa separatamente per ogni tool), altezza dello strato, tempo di stampa stimato e tipo di filamento dai commenti dello slicer. Rileva anche i comandi M605 per la selezione della modalità IDEX — approfondiremo più avanti.

**La passata 2** trasforma il GCode riga per riga. Qui avvengono tre trasformazioni:

**Rimappatura dei numeri tool.** La configurazione dual-extruder di PrusaSlicer assegna i tool come T2 e T3 (uno per stampante fisica nel suo modello interno). La J1S si aspetta T0 e T1. Il post-processore rimappa tutti i numeri tool modulo 2 — T2 diventa T0, T3 diventa T1 — nei comandi di cambio tool, nei comandi di temperatura (M104/M109) e nei comandi ventola (M106/M107).

**Spegnimento dell’ugello inutilizzato.** Quando avviene un cambio tool e il post-processore sa dall’analisi della prima passata che il tool precedente non verrà più utilizzato nel file, inietta `M104 S0 Tx` per spegnere quel riscaldatore. Su una stampante dual-extruder dove molti lavori usano un solo ugello per parte della stampa, questo evita che un hotend inattivo resti inutilmente in temperatura — risparmiando energia e riducendo il rischio di heat creep.

**Generazione dell’header Snapmaker V1.** Il post-processore costruisce un header di metadati di 25 righe nel formato Snapmaker V1, contenente il nome della stampante, il tempo stimato, il conteggio totale delle righe, i campi per estrusore (diametro ugello, materiale, temperatura, impostazioni di retrazione), la temperatura del piatto, il bounding box dell’area di lavoro e — aspetto cruciale — la modalità IDEX degli estrusori.

L’header include anche una **miniatura**. Il post-processore estrae i blocchi miniatura di PrusaSlicer e OrcaSlicer (dati PNG codificati in base64 incorporati nei commenti GCode) e li converte nel formato data URI atteso dal touchscreen della J1S. Questo significa che i file slicizzati caricati da Mainsail mostrano anteprime corrette sullo schermo della stampante — un piccolo dettaglio che fa una vera differenza quando si gestiscono più stampe in coda.

Il post-processore è idempotente: se rileva un marcatore `;Header Start` già presente, restituisce il file invariato.

### Controllo nativo della stampa: eliminare lo strato intermedio

Il bridge originale controllava le stampe inviando comandi GCode (M24 per riprendere, M25 per mettere in pausa) attraverso il canale di esecuzione GCode di SACP. Funzionava, ma era un percorso indiretto — chiedevamo al firmware di interpretare comandi testuali quando potevamo parlargli nel suo protocollo binario nativo.

Nella v0.1.0, il controllo della stampa è stato riscritto per utilizzare comandi SACP nativi: `0xAC/0x04` per annullare, `0xAC/0x05` per mettere in pausa, `0xAC/0x06` per riprendere. Il bridge ha anche acquisito una sequenza di avvio stampa corretta tramite `sacp.StartScreenPrint()`, che avvia la stampa attraverso l’MCU del touchscreen — assicurando che l’HMI resti sincronizzato con lo stato effettivo della stampa.

Un miglioramento correlato nella v0.0.8 è stato il **pattern di upload con doppia disconnessione**. Durante il caricamento di un file, il bridge ha scoperto che il touchscreen della J1S necessita di tempo per indicizzare il nuovo file prima di poterlo stampare. La soluzione è una sequenza specifica: caricare il file, disconnettersi da SACP, attendere tre secondi affinché l’HMI finalizzi il suo indice dei file, riconnettersi e poi inviare il comando di stampa. Senza questa pausa, il touchscreen non riconosce il file e la stampa non parte. Questo è uno di quei comportamenti che nessuna documentazione descrive — sono serviti ripetuti test sulla stampante reale per identificarlo e risolverlo.

La sequenza di upload ha anche acquisito la funzionalità di **avvio automatico**: quando un file viene caricato da Mainsail con l’opzione “avvia stampa”, il bridge gestisce automaticamente l’intero ciclo upload → disconnessione → riconnessione → avvio.

### Controllo dual extruder: due ugelli, due ventole, due bobine

Il bridge originale poteva leggere le temperature da entrambi gli estrusori ma non poteva controllarli indipendentemente. La v0.2.0 ha introdotto il controllo completo della temperatura dual-extruder, intercettando i comandi M104/M109 (estrusore) e M140/M190 (piatto) dalla console di Mainsail e instradandoli attraverso i comandi SACP `SetToolTemperature` (CommandSet `0x10`, CommandID `0x02`) e `SetBedTemperature` (`0x14/0x02`). Il comando Klipper `ACTIVATE_EXTRUDER` permette di selezionare quale estrusore Mainsail considera “attivo”.

La v1.0.0 ha esteso questo al **controllo delle ventole**. La J1S ha ventole di raffreddamento pezzo indipendenti per ogni estrusore, e il bridge ora le espone come oggetti ventola separati in Moonraker — `extruder_partfan` e `extruder1_partfan`. I comandi M106/M107 vengono instradati alla ventola fisica corretta in base al parametro P (con la stessa rimappatura modulo 2 dei numeri tool), e il comando Klipper `SET_FAN_SPEED` funziona per il controllo diretto delle ventole dall’interfaccia Mainsail.

### Spoolman: da una singola bobina al tracciamento per estrusore

Il post originale indicava l’integrazione con Spoolman come il principale traguardo rimanente. La v0.0.7 ha fornito l’implementazione iniziale: sfogliare e selezionare le bobine dal pannello Spoolman di Mainsail, riportare il consumo di filamento al server Spoolman durante le stampe e verificare lo stato della connessione Spoolman.

Ma la prima implementazione tracciava solo una singola bobina — adeguata per lavori a singolo estrusore, sbagliata per una stampante dual-extruder. La v1.0.0 ha ricostruito il tracciamento su base per-estrusore.

Le modifiche hanno toccato ogni livello:

**L’archiviazione nel database** è passata da una singola chiave `spoolman.spool_id` a `spoolman.spool_id.0` e `spoolman.spool_id.1`, con migrazione automatica della vecchia chiave al tool 0 al primo avvio.

**L’API** ha acquisito un parametro `tool` sia sugli endpoint HTTP (`GET/POST /server/spoolman/spool_id`) sia sui gestori WebSocket RPC, in linea con l’API Moonraker estesa che il pannello Spoolman di Mainsail utilizza per configurazioni multi-estrusore.

**La contabilità del filamento** nel post-processore GCode è stata corretta per tracciare l’estrusione per tool in modo indipendente. L’accumulatore singolo `lastAbsE` è stato suddiviso in `lastAbsE[2]`, uno per estrusore. Senza questa correzione, un lavoro dual-extruder avrebbe riportato tutto il consumo di filamento al tool attivo per ultimo.

**Il reporting dell’utilizzo** durante le stampe calcola i delta per tool dal numero di riga GCode (mappato sugli array cumulativi di filamento per tool estratti durante il post-processing) e invia richieste PUT indipendenti al server Spoolman per la bobina di ogni tool. I report vengono inviati solo quando il delta supera 0,1mm, evitando rumore da movimenti di spostamento e retrazioni.

Un ultimo tassello è arrivato dopo la v1.0.0: il bridge ora espone una `tool_spool_map` nella risposta dell’API Spoolman, che è ciò che il pannello Spoolman multi-estrusore di Mainsail legge per visualizzare le assegnazioni delle bobine per tool. Senza questo campo, Mainsail ricade nella modalità a singola bobina indipendentemente da quante bobine siano configurate nel backend.

Il risultato: Mainsail mostra la bobina corretta assegnata a ciascun estrusore, il consumo di filamento è tracciato indipendentemente e il server Spoolman dispone di dati di consumo accurati per ogni bobina — la stessa configurazione che utilizziamo sulle nostre macchine Klipper a singolo estrusore, ma estesa per la doppia estrusione.

### Modalità IDEX Copy e Mirror

La J1S è una stampante IDEX (Independent Dual Extrusion), il che significa che le sue due testine di stampa possono muoversi indipendentemente. Oltre alla stampa standard dual-materiale, IDEX abilita due modalità di produttività: **Copy** (entrambe le testine stampano lo stesso pezzo simultaneamente, raddoppiando la produttività) e **Mirror** (entrambe le testine stampano copie speculari, utile per pezzi simmetrici). Far funzionare queste modalità attraverso il bridge ha richiesto il coordinamento di tre meccanismi separati.

**Rilevamento GCode.** PrusaSlicer segnala la modalità IDEX tramite `M605 S2` (Copy/Duplication) o `M605 S3` (Mirror) nel GCode iniziale. La prima passata del post-processore rileva questi comandi e registra la modalità.

**Header V1.** La modalità rilevata viene scritta nell’header Snapmaker V1 come `;Extruder Mode:Duplication` o `;Extruder Mode:Mirror`. Questa è stata la scoperta fondamentale — l’HMI della J1S legge questo campo dell’header per configurare la modalità di stampa prima di eseguire il GCode. Senza l’header corretto, la stampante ignora completamente M605 e stampa nella modalità predefinita.

**Comando SACP SetPrintMode.** Come misura aggiuntiva di affidabilità, il bridge invia il comando SACP `0xAC/0x0A` con il byte di modalità appropriato (`0x02` per Duplication, `0x03` per Mirror) dopo l’upload e prima di avviare la stampa. Questo assicura che il firmware sia nella modalità corretta anche in caso di edge case nel parsing dell’header da parte dell’HMI.

La v1.1.0 include anche **dodici profili stampante per PrusaSlicer** che coprono tutte e tre le modalità (Default, Copy, Mirror) con otto preset di qualità ciascuno (da 0,08mm ultra-fine a 0,28mm bozza). I profili Copy e Mirror utilizzano dimensioni di metà piatto (150×200mm) per tenere conto dell’area di stampa specchiata/duplicata e includono i comandi M605 corretti nel loro GCode iniziale. Questi profili permettono a un utente di passare dall’installazione del bridge alla stampa in modalità IDEX Copy senza dover configurare manualmente nulla in PrusaSlicer.

### Rafforzamento della sicurezza

La v0.2.1 è stata una release dedicata alla sicurezza. Il bridge è un servizio di rete che accetta upload di file ed esegue comandi su una stampante — una superficie d’attacco che merita attenzione accurata.

**Prevenzione del path traversal.** Tutti gli endpoint per operazioni sui file (upload, download, eliminazione, spostamento, copia, creazione directory) ora verificano che i percorsi risolti restino all’interno della directory di archiviazione GCode. I tentativi di uscirne tramite sequenze `../` vengono respinti prima di qualsiasi operazione sul filesystem.

**Prevenzione dell’injection di namespace.** Il parametro namespace dell’API database viene validato contro un pattern di allowlist rigoroso, impedendo l’iniezione di chiavi arbitrarie nell’archiviazione persistente.

**Sanitizzazione degli header.** Gli header di risposta HTTP costruiti a partire da valori forniti dall’utente (come i nomi file in Content-Disposition) vengono sanitizzati per prevenire attacchi di header injection.

**Limiti di dimensione dei messaggi WebSocket.** Le connessioni WebSocket impongono una dimensione massima dei messaggi per prevenire l’esaurimento della memoria da payload sovradimensionati.

**Validazione dei comandi systemctl.** L’API di gestione dei servizi (usata da Mainsail per riavviare/fermare i servizi) valida i nomi dei servizi contro una allowlist rigorosa prima di passarli a systemctl.

Nessuna di queste misure è stata una risposta a vulnerabilità scoperte — si è trattato di rafforzamento proattivo basato sulla revisione della superficie d’attacco. Per un servizio in esecuzione su un Raspberry Pi in una rete locale, il rischio è basso, ma il costo di farlo bene è altrettanto basso.

### Storico delle temperature e il ring buffer

Uno dei miglioramenti più discreti nella v0.2.0 è stata l’aggiunta di uno **storico delle temperature** — un ring buffer da 1200 letture per sensore che alimenta il grafico delle temperature di Mainsail. Il bridge originale riportava solo le temperature correnti; il grafico di Mainsail mostrava un singolo punto che saltava ad ogni aggiornamento. Con il ring buffer, il grafico visualizza uno storico scorrevole fluido, corrispondente al comportamento che gli utenti si aspettano da una vera istanza Moonraker.

### Resilienza della connessione SACP

Il post originale notava che la connessione SACP “occasionalmente va in timeout, anche se la riconnessione automatica recupera rapidamente.” La v0.1.1 ha affrontato il problema con un meccanismo di retry adeguato: quando il bridge perde la connessione SACP, tenta di riconnettersi fino a cinque volte con un ritardo di due secondi tra i tentativi. Il ritardo è importante perché il firmware della J1S a volte necessita di tempo per ripulire una connessione interrotta prima di accettarne una nuova — riconnettersi troppo rapidamente risulta in una connessione rifiutata.

Il bridge ha anche acquisito nella v0.2.0 **comandi di disconnessione/connessione** accessibili dal pannello di gestione servizi di Mainsail, permettendo la riconnessione manuale senza riavviare l’intero processo del bridge.

### Build cross-platform

A partire dalla v0.1.1, la pipeline CI produce binari per tre piattaforme: Linux x86_64, Windows x86_64 e macOS ARM64 (Apple Silicon). Il caso d’uso principale rimane l’immagine per scheda SD del Raspberry Pi, ma i binari cross-platform permettono di eseguire il bridge su una macchina desktop per sviluppo o test — o su qualsiasi server Linux nella rete, non solo su un RPi.

### Dove siamo ora

Il post originale si concludeva con un elenco di lavori futuri. Ecco lo stato di ciascun punto:

| Punto della roadmap originale | Stato |
|—|—|
| Tracciamento filamento Spoolman | Fatto (v0.0.7), esteso a per-estrusore (v1.0.0) |
| Progresso 0% per stampe avviate dal touchscreen | Risolto — il controllo stampa nativo SACP significa che le stampe vengono sempre avviate attraverso il bridge |
| Affidabilità connessione SACP | Fatto (v0.1.1) — 5 tentativi di retry con ritardo configurabile |
| Refresh/persistenza del token | Non necessario — SACP non richiede autenticazione |

E le funzionalità che non erano nella roadmap originale ma sono arrivate comunque:

– Post-processore GCode con header Snapmaker V1, rimappatura tool, spegnimento ugelli e miniature HMI
– Supporto modalità IDEX Copy e Mirror con profili PrusaSlicer
– Controllo completo di temperatura e ventole dual-extruder
– Rafforzamento della sicurezza su tutti gli endpoint esposti in rete
– Binari cross-platform
– Editor del file di configurazione Mainsail e grafici dello storico temperature

Il numero di versione racconta la storia. A febbraio, la v0.0.6 era un proof of concept funzionale — poteva eseguire il flusso di lavoro principale (slicing → upload → stampa → monitoraggio) ma con spigoli vivi e pezzi mancanti. Oggi, la v1.1.0 è uno strumento di produzione che gestisce ogni modalità di stampa supportata dalla J1S, traccia il filamento per estrusore, fornisce adeguati confini di sicurezza e viene distribuita con profili slicer pronti all’uso.

La Snapmaker J1S è ora un membro a tutti gli effetti della nostra farm di stampa. Esegue lo slicing dalla stessa installazione di PrusaSlicer, carica sulla stessa interfaccia Mainsail, traccia il filamento nella stessa istanza Spoolman e monitora le stampe con lo stesso server Obico di ogni altra macchina in produzione. Il bridge protocollare nato per evitare l’uso di Luban è diventato un livello completo di integrazione della flotta.

### Cosa resta da fare

Il progetto non è finito — il software non lo è mai — ma i punti rimanenti sono raffinamenti piuttosto che funzionalità mancanti:

– **Z baby-stepping** (M290) è accettato ma non ancora implementato via SACP. Si tratta di una funzionalità di comodità per regolazioni del primo strato in tempo reale.
– **Supporto stampanti più ampio.** Il bridge è costruito per la J1S, ma il protocollo SACP è condiviso su tutta la gamma Snapmaker. Il post-processore GCode genera già header V0 per i modelli A150/A250/A350/Artisan. Estendere il supporto completo ad altre stampanti Snapmaker è architetturalmente fattibile, anche se ogni modello avrà le proprie particolarità protocollari da scoprire.
– **Test da parte della community.** Il progetto è stato sviluppato e testato su una singola J1S. Più utenti significano più edge case, versioni firmware e configurazioni di rete — il tipo di validazione reale che nessuna quantità di test in solitaria può sostituire.

### Open Source e dichiarazione sull’uso dell’IA

snapmaker_moonraker resta disponibile su [GitHub](https://github.com/goeland86/snapmaker_moonraker) sotto MIT License. Si basa sul lavoro sul protocollo SACP di sm2uploader di macdylan e snapmaker-sm2uploader di kanocz.

Questo progetto continua a essere sviluppato con l’assistenza di Claude (Anthropic). Ogni commit nel repository è co-authored, riflettendo un flusso di lavoro di collaborazione uomo-IA che si è dimostrato efficace per questo tipo di programmazione di sistemi a livello protocollare — in particolare per il lavoro tedioso ma critico di parsing di protocolli binari, dove un collaboratore IA che non perde il filo dei byte offset e dell’endianness nel corso di una sessione di più ore è genuinamente utile.

Segui
Newest Art:
SHOPPING BAG 0
RECENTLY VIEWED 0