Define the /ws/edge/v1 gateway uplink protocol
continuum-proxy and continuum-backend each had their own idea of what the edge WebSocket carried, and the two did not overlap anywhere: the backend expected an `auth` handshake the gateway never sends, telemetry shaped one printer per message where the gateway sends a batch, and UUID printer ids a gateway has no way to know. Neither end had a command path that matched the other. Nothing failed, because nothing was written down for both sides to mirror. This is that document. schemas/continuum/v1/edge/ is canonical: client_message, server_message, printer_telemetry and printer_command. packages/ts-types gets its first real TypeBox mirror - the placeholder there said to add one "once something actually consumes it", and continuum-backend's edge route now does. The Rust mirror stays in continuum-proxy/src/uplink/protocol.rs rather than packages/rust-types, since the gateway is its only Rust consumer. Two decisions worth recording: Field names are snake_case, unlike the backend's HTTP routes. The other end is serde-derived Rust, and snake_case is what those structs produce without a rename attribute on every field. It is also what the rest of this repository already uses. Optional fields accept an explicit null as well as being absent. A gateway omits what it has no value for, which is what these schemas describe - but serde emits null by default unless every field is annotated, and a single such field would otherwise fail validation and take a whole farm's telemetry batch down with it. Strict in what we send, liberal in what we accept; the contract tests on both sides pin down each half. Also bumps this package's @sinclair/typebox to ^0.33.12 to match what continuum-backend already pins. It was on ^0.32.0, which resolved to a separate 0.32.37 install - two TypeBox versions means two incompatible TSchema identities, and Elysia rejected every schema from here on those grounds alone.
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
{
|
||||
"$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."
|
||||
},
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"printer_id",
|
||||
"name"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user