Files
continuum-schemas/schemas/continuum/v1/edge/printer_command.schema.json
T
octoturgeandClaude Opus 5 9b57c22fff Add start_print to the printer command set
Held back until there was a job path to drive it; the queueing work needs it
now. Three things are deliberate about its shape.

It carries a presigned `url`, not the file's bytes. A sliced project runs to
tens of megabytes and this is a JSON control channel, and a URL also keeps
object-store credentials out of every gateway on every farm. The schema says
to presign it at *dispatch* time rather than at job creation, because a queued
job may wait days and a URL minted when it was queued would be long expired.

It carries no filament or AMS field. The gateway reads the mapping out of the
archive's own `Metadata/slice_info.config`, because the archive is the only
thing that knows what each plate was sliced for. Passing one in would
reintroduce the guess that made a real P1P heat to 250C and pause itself at
1%.

And its description is blunt about what accepting it means, which is: very
little. A full end-to-end run on 2026-10-15 was accepted, uploaded
byte-perfect, reached RUNNING, and then the printer paused itself on a
physical broken-filament fault. Job state has to be driven by the telemetry
that follows, never by this reply - so the schema says so where a caller will
read it, rather than leaving it to be rediscovered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-15 22:43:03 +00:00

73 lines
4.8 KiB
JSON

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/continuum/v1/edge/printer_command.schema.json",
"title": "PrinterCommand",
"description": "The closed set of things the cloud may ask a printer to do. Deliberately small. It now includes start_print, which was held back until there was a job path to drive it; the thing to understand about that variant is that its acceptance says almost nothing - see its own description.",
"oneOf": [
{
"type": "object",
"title": "StartPrint",
"description": "Print a job's file. The gateway downloads it from `url`, transfers it to the printer, and starts it. Three things about this variant that a caller must not get wrong. (1) Its result means the gateway accepted the job, not that anything is printing: a real end-to-end run on a P1P (2026-10-15) was accepted, uploaded byte-perfect, reached RUNNING, and then the printer paused itself on a physical filament fault. Job state has to be driven by the telemetry that follows, never by this reply. (2) There is no filament or AMS field on purpose. The gateway reads the mapping out of the project archive's own Metadata/slice_info.config, because the archive is the only thing that knows what each plate was sliced for - guessing it instead is what made that same printer heat to 250C and pause itself at 1%. (3) `url` is short-lived and must be presigned at dispatch time, not at job creation: a queued job may wait days, and a URL minted when it was queued would be expired by the time anything sent it.",
"properties": {
"action": { "const": "start_print" },
"job_id": {
"type": "string",
"format": "uuid",
"description": "The cloud's print_jobs.id. The gateway does not need it to do the work - it is here so that a gateway's logs can be lined up against a job without reading its own telemetry back out of the cloud."
},
"file_name": {
"type": "string",
"minLength": 1,
"maxLength": 500,
"description": "What to call the file on the printer's own storage. A `.3mf` is treated as a sliced project (filament mapping derived from inside it); anything else is treated as bare gcode, which carries no mapping and prints with whatever its own tool changes select."
},
"url": {
"type": "string",
"format": "uri",
"description": "A presigned, expiring GET for the job file. Deliberately a URL rather than the bytes: the file is tens of megabytes, this is a JSON control channel, and handing the gateway a URL keeps object-store credentials out of every gateway on every farm. Expect it to be valid for minutes, and note that an upload to a printer is slow - 0.2 MiB/s measured over FTPS to a P1P - so the download must finish before the transfer starts, not stream through it."
}
},
"required": ["action", "job_id", "file_name", "url"]
},
{
"type": "object",
"title": "SetFanSpeed",
"properties": {
"action": { "const": "set_fan_speed" },
"percent": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"description": "A percentage on this wire regardless of what the vendor wants. Each gateway converts: for Bambu that means an M106 PWM value over MQTT, which the printer then reports back as one of 16 fan gears - so a value set here does not read back identically."
}
},
"required": ["action", "percent"]
},
{
"type": "object",
"title": "ControlPrint",
"description": "Pause, resume or stop the print the printer is currently running. Confirmed live on a P1P (2026-10-11): all three are accepted and a stop during PREPARE/RUNNING moved the printer to FAILED, which is how Bambu reports an operator-cancelled print. Note what the printer's acceptance does not mean - all three answered success while it was IDLE with nothing to pause, so a caller that needs to know the state actually changed must read telemetry back rather than trust the command result.",
"properties": {
"action": { "const": "control_print" },
"control": {
"enum": ["pause", "resume", "stop"],
"description": "Stop is a cancel, not a pause: it ends the print and it cannot be undone. A UI should treat it accordingly."
}
},
"required": ["action", "control"]
},
{
"type": "object",
"title": "SendGcodeLine",
"properties": {
"action": { "const": "send_gcode_line" },
"line": {
"type": "string",
"description": "A single raw gcode line. By far the most powerful variant, and the one to be most careful about ever exposing to a UI."
}
},
"required": ["action", "line"]
}
]
}