Files
continuum-schemas/schemas/continuum/v1/edge/printer_telemetry.schema.json
T
octoturgeandClaude Opus 5 9b8f0c2308 Carry camera stream information on the edge protocol
Two optional fields so a user can be pointed at a printer's camera.

`hello.stream_base_url` is where the gateway serves restreams - the base of
the go2rtc instance it already supervises. Reported by the gateway rather
than configured in the cloud, because only the gateway knows the address it
is actually reachable at.

That address is a LAN one, and the schema says so where someone will read it
rather than leaving it to be discovered in a support ticket. A browser on the
same network as the gateway can play the stream; one outside it cannot, and
the relay that would fix it does not exist. For a print farm whose operator
is in the workshop this is the normal case and it works; for anyone else it
is a limit, and anything surfacing the field has to cope with it being
unreachable.

`printer_telemetry.camera_stream` is the stream's name, present only when
there is one. Absent is the common answer today and the reason is worth
recording: a Bambu P1/A1 publishes no RTSP URL at all - a real P1P's ipcam
block carries `ipcam_dev` and `ipcam_record` and nothing to connect to -
while an H2C reports `rtsps://<ip>:322/streaming/live/1`. So RTSP restreaming
covers the X1/H2 class and nothing else; the rest need Bambu's proprietary
port-6000 JPEG stream, which is a client no gateway has yet.

Both optional and additive, so existing gateways are unaffected.

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

119 lines
7.7 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_telemetry.schema.json",
"title": "PrinterTelemetry",
"description": "A flattened snapshot of one printer, in the terms an operator console displays. Deliberately not any vendor's native status shape: it has to describe a Bambu, a Prusa and a Klipper printer alike, and Bambu's wire format in particular has traps that should not reach the rest of the system (fan speeds are 0-15 gears rather than a percent, temperatures arrive as either integers or floats, and protocol v2 reuses one sequence_id indefinitely). Every field but printer_id and name is optional, because a printer that is unreachable or has only just connected genuinely has nothing to report, and 'no data yet' is not the same as zero.",
"type": "object",
"properties": {
"printer_id": {
"type": "string",
"description": "The gateway's own id for this printer, from its printers.toml - NOT the vendor serial number and NOT the backend's printers.id UUID. The backend resolves it to a UUID via printers.gateway_printer_id, scoped to the farm the gateway authenticated as."
},
"name": {
"type": "string"
},
"state": {
"type": [
"string",
"null"
],
"description": "The printer's own state string, passed through rather than narrowed to an enum here, so an unrecognised state reaches the backend as itself instead of being dropped at the edge. Bambu emits IDLE, RUNNING, PAUSE, FINISH and FAILED. Readers must accept an explicit null here as equivalent to the key being absent: a gateway omits what it has no value for, but a serde-derived Rust producer emits null unless every field is annotated, and rejecting a whole batch over that is not worth it."
},
"percent": {
"type": [
"integer",
"null"
],
"minimum": 0,
"maximum": 100,
"description": "Readers must accept an explicit null here as equivalent to the key being absent: a gateway omits what it has no value for, but a serde-derived Rust producer emits null unless every field is annotated, and rejecting a whole batch over that is not worth it."
},
"stage": {
"type": "integer",
"description": "The printer's current stage, when it is doing something other than laying down plastic: bed levelling, nozzle cleaning, extrusion calibration and so on. Bambu's `stg_cur`, passed through as the printer's own number rather than decoded, because the code-to-name table is not something we have. 0 means printing.\n\nWithout this, progress is ambiguous in a way that reads as a fault. Confirmed on a P1P (2026-10-11): `percent` climbed to 14% while `layer` stayed 0 for several minutes, because the printer was working through its pre-print stages - it reported `stg_cur` 54, then 14, then 13, then 0, and only then began layer 1. A console showing 14% at layer 0 with no stage looks stuck."
},
"layer": {
"type": [
"integer",
"null"
],
"minimum": 0,
"description": "Readers must accept an explicit null here as equivalent to the key being absent: a gateway omits what it has no value for, but a serde-derived Rust producer emits null unless every field is annotated, and rejecting a whole batch over that is not worth it."
},
"total_layers": {
"type": [
"integer",
"null"
],
"minimum": 0,
"description": "Readers must accept an explicit null here as equivalent to the key being absent: a gateway omits what it has no value for, but a serde-derived Rust producer emits null unless every field is annotated, and rejecting a whole batch over that is not worth it."
},
"remaining_minutes": {
"type": [
"integer",
"null"
],
"minimum": 0,
"description": "Readers must accept an explicit null here as equivalent to the key being absent: a gateway omits what it has no value for, but a serde-derived Rust producer emits null unless every field is annotated, and rejecting a whole batch over that is not worth it."
},
"job_name": {
"type": [
"string",
"null"
],
"description": "Normalised at the edge: a Bambu reports an empty string rather than omitting the field when idle, and the gateway turns that into an absent value so consumers need not know it. Readers must accept an explicit null here as equivalent to the key being absent: a gateway omits what it has no value for, but a serde-derived Rust producer emits null unless every field is annotated, and rejecting a whole batch over that is not worth it."
},
"nozzle_temp": {
"type": [
"number",
"null"
],
"description": "Readers must accept an explicit null here as equivalent to the key being absent: a gateway omits what it has no value for, but a serde-derived Rust producer emits null unless every field is annotated, and rejecting a whole batch over that is not worth it."
},
"nozzle_target": {
"type": [
"number",
"null"
],
"description": "Readers must accept an explicit null here as equivalent to the key being absent: a gateway omits what it has no value for, but a serde-derived Rust producer emits null unless every field is annotated, and rejecting a whole batch over that is not worth it."
},
"bed_temp": {
"type": [
"number",
"null"
],
"description": "Readers must accept an explicit null here as equivalent to the key being absent: a gateway omits what it has no value for, but a serde-derived Rust producer emits null unless every field is annotated, and rejecting a whole batch over that is not worth it."
},
"bed_target": {
"type": [
"number",
"null"
],
"description": "Readers must accept an explicit null here as equivalent to the key being absent: a gateway omits what it has no value for, but a serde-derived Rust producer emits null unless every field is annotated, and rejecting a whole batch over that is not worth it."
},
"print_error": {
"type": [
"integer",
"null"
],
"description": "Vendor error code. Non-zero means the printer is reporting a fault. Readers must accept an explicit null here as equivalent to the key being absent: a gateway omits what it has no value for, but a serde-derived Rust producer emits null unless every field is annotated, and rejecting a whole batch over that is not worth it."
},
"active_alerts": {
"type": [
"integer",
"null"
],
"minimum": 0,
"description": "How many Health-Management-System entries the printer currently has active. A count only for now - decoding the {attr, code} pairs into messages needs Bambu's HMS tables, which Continuum does not have. Readers must accept an explicit null here as equivalent to the key being absent: a gateway omits what it has no value for, but a serde-derived Rust producer emits null unless every field is annotated, and rejecting a whole batch over that is not worth it."
},
"camera_stream": {
"type": "string",
"description": "The stream name to ask the gateway's stream_base_url for, when this printer has a camera the gateway could restream. Absent means no stream: either the printer reports no camera source, or it is a model whose camera is not reachable this way. That second case is the common one today - a Bambu P1/A1 does not publish an RTSP URL at all (confirmed: a P1P's ipcam block has ipcam_dev and ipcam_record and no rtsp_url), and only the X1/H2 class does. Those need Bambu's proprietary port-6000 JPEG stream instead, which is a separate client this gateway does not have yet."
}
},
"required": [
"printer_id",
"name"
]
}