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.
150 lines
5.6 KiB
JSON
150 lines
5.6 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/client_message.schema.json",
|
|
"title": "EdgeClientMessage",
|
|
"description": "What a continuum-proxy gateway sends up to continuum-backend's /ws/edge/v1 route. Discriminated on 'type'. Field names are snake_case, matching every other schema in this repository - note that this differs from the camelCase the backend's HTTP routes use, because this wire format is shared with a Rust consumer whose serde structs derive these names directly.",
|
|
"oneOf": [
|
|
{
|
|
"type": "object",
|
|
"title": "Hello",
|
|
"description": "First message on every connection, before anything else is accepted. Carries the edge JWT minted by POST /auth/edge-token.",
|
|
"properties": {
|
|
"type": {
|
|
"const": "hello"
|
|
},
|
|
"gateway_id": {
|
|
"type": "string"
|
|
},
|
|
"farm_id": {
|
|
"type": "string"
|
|
},
|
|
"token": {
|
|
"type": "string"
|
|
},
|
|
"stream_base_url": {
|
|
"type": "string",
|
|
"format": "uri",
|
|
"description": "Where this gateway serves camera restreams, e.g. http://gateway.local:1984 - the base of a go2rtc instance the gateway supervises. Optional: a gateway with no cameras configured, or with go2rtc not installed, omits it. Reported by the gateway rather than configured in the cloud because only the gateway knows the address it is actually reachable at. NOTE the consequence, which is a real limit and not a bug: this is a LAN address. A browser on the same network as the gateway can play the stream directly; one outside it cannot, and making that work needs a relay that does not exist yet. Anything surfacing this to a user has to cope with it being unreachable."
|
|
}
|
|
},
|
|
"required": [
|
|
"type",
|
|
"gateway_id",
|
|
"farm_id",
|
|
"token"
|
|
]
|
|
},
|
|
{
|
|
"type": "object",
|
|
"title": "Heartbeat",
|
|
"properties": {
|
|
"type": {
|
|
"const": "heartbeat"
|
|
},
|
|
"seq": {
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"description": "Monotonic per connection, restarting at 1 on reconnect. Echoed back in heartbeat_ack so the gateway can tell a stalled connection from a slow one."
|
|
}
|
|
},
|
|
"required": [
|
|
"type",
|
|
"seq"
|
|
]
|
|
},
|
|
{
|
|
"type": "object",
|
|
"title": "Telemetry",
|
|
"description": "One snapshot per printer that has something to report, on a timer. Sent as a batch rather than a message per printer: a farm has one uplink and many printers, and the backend wants a coherent picture of the farm at an instant more than it wants the lowest possible latency per machine.",
|
|
"properties": {
|
|
"type": {
|
|
"const": "telemetry"
|
|
},
|
|
"printers": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "./printer_telemetry.schema.json"
|
|
}
|
|
}
|
|
},
|
|
"required": [
|
|
"type",
|
|
"printers"
|
|
]
|
|
},
|
|
{
|
|
"type": "object",
|
|
"title": "CommandResult",
|
|
"description": "The reply to a server 'command'. Correlated by command_id, which the backend generates - unlike Bambu's sequence_id, Continuum owns both ends of this protocol, so the side that needs the correlation gets to choose it.",
|
|
"properties": {
|
|
"type": {
|
|
"const": "command_result"
|
|
},
|
|
"command_id": {
|
|
"type": "string"
|
|
},
|
|
"ok": {
|
|
"type": "boolean"
|
|
},
|
|
"error": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"description": "Present only when ok is false. A human-readable message, not a code: its only destination is an operator looking at why a command did not take. 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."
|
|
}
|
|
},
|
|
"required": [
|
|
"type",
|
|
"command_id",
|
|
"ok"
|
|
]
|
|
},
|
|
{
|
|
"type": "object",
|
|
"title": "FrameBatch",
|
|
"description": "Camera stills already uploaded to object storage, handed over for the AI worker to score. Not yet sent by any gateway - continuum-proxy restreams cameras through go2rtc and has no frame-capture path yet - but the backend consumes it, so it is part of the contract.",
|
|
"properties": {
|
|
"type": {
|
|
"const": "frame_batch"
|
|
},
|
|
"printer_id": {
|
|
"type": "string"
|
|
},
|
|
"print_job_id": {
|
|
"type": [
|
|
"string",
|
|
"null"
|
|
],
|
|
"format": "uuid",
|
|
"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."
|
|
},
|
|
"frames": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object",
|
|
"properties": {
|
|
"object_key": {
|
|
"type": "string"
|
|
},
|
|
"captured_at": {
|
|
"type": "string",
|
|
"format": "date-time"
|
|
}
|
|
},
|
|
"required": [
|
|
"object_key",
|
|
"captured_at"
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"required": [
|
|
"type",
|
|
"printer_id",
|
|
"frames"
|
|
]
|
|
}
|
|
]
|
|
}
|