Skip to content

J1S print upload pipeline

How a GCode file gets from the slicer to the J1S printer through the bridge — and why every stage of the pipeline streams rather than buffering.

Sequence

sequenceDiagram
    autonumber
    participant S as Slicer / Mainsail
    participant B as Bridge (HTTP)
    participant FS as Pi filesystem
    participant GP as GCode post-processor
    participant SACP as SACP layer
    participant J as J1S printer

    S->>B: POST /server/files/upload<br/>multipart, large body
    B->>B: MultipartReader (no in-memory copy)
    B->>FS: stream "file" part to gcodes/foo.gcode<br/>(io.Copy)
    B-->>S: 201 Created

    opt "Upload and Print"
        B->>GP: ProcessFile(src=gcodes/foo.gcode,<br/>dst=gcodes/foo.gcode.tmp, model=J1S)
        GP->>FS: pass 1: scan for metadata + slicer thumbnail
        GP->>FS: pass 2: write V1/V0 header + transformed body
        GP-->>B: total processed line count

        B->>SACP: StartUpload(filename, srcPath=foo.gcode.tmp, timeout)
        SACP->>FS: stream io.Copy → md5.New() to compute hash
        loop chunk request loop
            SACP->>FS: ReadAt(offset, 60 KiB)
            SACP->>J: SACP chunk packet
        end
        SACP-->>B: upload complete (md5 verified by printer)
        B->>FS: remove foo.gcode.tmp
        B-->>S: print started
    end

Memory profile

Stage Pre-v1.6.0 v1.6.0+
Multipart parse full body in RAM (ParseMultipartForm) streamed (MultipartReader)
File save full body in RAM io.Copy chunked
GCode post-process full body in RAM as []byte bufio.Scanner line-by-line, no full buffer
SACP upload prep full processed body in RAM md5.New() + ReadAt from disk
SACP chunking full body in RAM single 60 KiB chunk buffer
Peak RAM (250 MB tile print) ~1.0–1.5 GB (OOM-kill on 921 MB Pi 3) a few MB, regardless of file size

API contracts that changed

The streaming refactor changed several internal Go APIs. If you have custom code that calls into bridge packages:

Old New
gcode.Process(data []byte, model string) []byte gcode.ProcessFile(srcPath, dstPath, model string) (uint32, error)
sacp.StartUpload(conn, filename, gcode []byte, timeout) sacp.StartUpload(conn, filename, srcPath, timeout)
printer.Client.Upload(filename, data []byte) printer.Client.Upload(filename, srcPath string)
printer.Client.UploadFile (removed — no callers)
— files.Manager.SaveFromReader(root, filename, src io.Reader)
— files.Manager.FilePath(root, filename) string
— gcode.CountProcessedLines(srcPath, model) (uint32, error)

If the bridge restarts mid-print, it queries the J1S over SACP for the current print's filename. The bridge then locates that file in gcodes/ (recursively, by basename, picking the most-recently-modified match) and runs gcode.CountProcessedLines to recompute the total line count without re-processing the file. Progress recovery also fires while paused — a restart during a pause was the diagnostic that surfaced the original bug.

See v1.5.1 (now v1.6.0) notes for the bug history.