Compare commits
4
Commits
6ad400d44c
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8462f25fc
|
||
|
|
2bc5b88dd7
|
||
|
|
1b1df9c438
|
||
|
|
d355955fb6
|
@@ -62,6 +62,11 @@ export const AppPrinterStateSchema = Type.Object({
|
||||
* calling; null means render no player at all.
|
||||
*/
|
||||
cameraStream: Type.Optional(Nullable(Type.String())),
|
||||
/**
|
||||
* True when the camera can only be read as refreshing JPEG frames (Bambu
|
||||
* P1/A1); `GET /printers/:id/stream` then answers `kind: "snapshot"`.
|
||||
*/
|
||||
cameraSnapshot: Type.Optional(Type.Boolean()),
|
||||
/**
|
||||
* When a gateway last reported this printer; null means never. This is how
|
||||
* a client tells "idle" from "nobody has heard from it in an hour", which
|
||||
|
||||
@@ -65,6 +65,11 @@ export const PrinterTelemetrySchema = Type.Object({
|
||||
* implements yet.
|
||||
*/
|
||||
camera_stream: Maybe(Type.String()),
|
||||
/**
|
||||
* The camera can only be read as single JPEG frames (Bambu P1/A1) and the
|
||||
* gateway answers `camera_snapshot_request` for it.
|
||||
*/
|
||||
camera_snapshot: Maybe(Type.Boolean()),
|
||||
layer: Maybe(Type.Integer({ minimum: 0 })),
|
||||
total_layers: Maybe(Type.Integer({ minimum: 0 })),
|
||||
remaining_minutes: Maybe(Type.Integer({ minimum: 0 })),
|
||||
@@ -126,6 +131,14 @@ export const PrinterCommandSchema = Type.Union([
|
||||
* may wait days and the URL would be long expired.
|
||||
*/
|
||||
url: Type.String({ format: "uri" }),
|
||||
/**
|
||||
* Use this AMS slot (0-based, across all units: AMS1 slot 4 is 3)
|
||||
* instead of the one the project's own filament colour would pick.
|
||||
* Only valid for a single-filament project, and the slot must hold the
|
||||
* same material type; the gateway refuses otherwise. Absent: map by the
|
||||
* project's filament.
|
||||
*/
|
||||
ams_slot: Type.Optional(Type.Integer({ minimum: 0, maximum: 15 })),
|
||||
}, { additionalProperties: false }),
|
||||
Type.Object({
|
||||
action: Type.Literal("set_fan_speed"),
|
||||
@@ -296,6 +309,29 @@ export const EdgeClientMessageSchema = Type.Union([
|
||||
session_id: Type.String(),
|
||||
message: Type.String(),
|
||||
}),
|
||||
/** One JPEG frame from a `camera_snapshot_request`, or why there is none. */
|
||||
Type.Object({
|
||||
type: Type.Literal("camera_snapshot"),
|
||||
request_id: Type.String(),
|
||||
jpeg_base64: Maybe(Type.String()),
|
||||
error: Maybe(Type.String()),
|
||||
}),
|
||||
/**
|
||||
* What happened to a job the gateway was handed. Kept in the gateway's own
|
||||
* database and re-sent on every connection until answered with a
|
||||
* `job_event_ack`; the cloud applies it only to a job still in flight and
|
||||
* applying it twice changes nothing.
|
||||
*/
|
||||
Type.Object({
|
||||
type: Type.Literal("job_event"),
|
||||
event_id: Type.Integer(),
|
||||
job_id: Type.String({ format: "uuid" }),
|
||||
event: Type.Union([Type.Literal("started"), Type.Literal("finished"), Type.Literal("failed")]),
|
||||
/** For `failed`: the reason, in words for an operator. */
|
||||
detail: Maybe(Type.String()),
|
||||
/** When the gateway saw it, ms since the Unix epoch. */
|
||||
at_ms: Type.Integer(),
|
||||
}),
|
||||
Type.Object({
|
||||
type: Type.Literal("frame_batch"),
|
||||
printer_id: Type.String(),
|
||||
@@ -367,6 +403,28 @@ export const EdgeServerMessageSchema = Type.Union([
|
||||
/** The browser's offer SDP, passed through verbatim. */
|
||||
sdp: Type.String(),
|
||||
}, { additionalProperties: false }),
|
||||
/**
|
||||
* Read one JPEG frame from a printer whose camera is not RTSP (Bambu P1/A1).
|
||||
* Unlike `webrtc_offer` the picture itself crosses the cloud: frames are
|
||||
* small and requested one at a time.
|
||||
*/
|
||||
Type.Object(
|
||||
{
|
||||
type: Type.Literal("camera_snapshot_request"),
|
||||
request_id: Type.String(),
|
||||
/** The gateway's own printer id. */
|
||||
printer_id: Type.String(),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
),
|
||||
/** The cloud has dealt with a `job_event`; the gateway can forget it. */
|
||||
Type.Object(
|
||||
{
|
||||
type: Type.Literal("job_event_ack"),
|
||||
event_id: Type.Integer(),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
),
|
||||
Type.Object({
|
||||
type: Type.Literal("command"),
|
||||
command_id: Type.String(),
|
||||
@@ -374,5 +432,45 @@ export const EdgeServerMessageSchema = Type.Union([
|
||||
printer_id: Type.String(),
|
||||
command: PrinterCommandSchema,
|
||||
}),
|
||||
/**
|
||||
* Add a printer to this gateway. Relayed by the cloud, which keeps nothing:
|
||||
* it carries the printer's LAN credential, and printer credentials never
|
||||
* rest in the cloud. The gateway verifies them by connecting, stores them
|
||||
* in its own database, and answers with a `command_result` whose
|
||||
* `command_id` is `request_id`. **Never log this message.**
|
||||
*
|
||||
* No `ca_cert_path` and no serial port on purpose: both name things on the
|
||||
* gateway's own disk and USB bus, which the cloud does not get to choose.
|
||||
*/
|
||||
Type.Object(
|
||||
{
|
||||
type: Type.Literal("provision_printer"),
|
||||
request_id: Type.String(),
|
||||
printer: Type.Object(
|
||||
{
|
||||
/** The gateway's own printer id, chosen by the cloud. */
|
||||
id: Type.String({ pattern: "^[A-Za-z0-9_-]{1,64}$" }),
|
||||
name: Type.String({ minLength: 1, maxLength: 200 }),
|
||||
vendor: Type.String(),
|
||||
host: Type.Optional(Type.String({ maxLength: 255 })),
|
||||
access_code: Type.Optional(Type.String({ maxLength: 128 })),
|
||||
sn: Type.Optional(Type.String({ maxLength: 64 })),
|
||||
api_key: Type.Optional(Type.String({ maxLength: 256 })),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
),
|
||||
/** Forget a printer: leave the running fleet and delete its stored credential. */
|
||||
Type.Object(
|
||||
{
|
||||
type: Type.Literal("remove_printer"),
|
||||
request_id: Type.String(),
|
||||
/** The gateway's own printer id. */
|
||||
printer_id: Type.String(),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
),
|
||||
]);
|
||||
export type EdgeServerMessage = Static<typeof EdgeServerMessageSchema>;
|
||||
|
||||
@@ -62,6 +62,10 @@
|
||||
"type": ["string", "null"],
|
||||
"description": "The go2rtc stream name, when this printer has a camera that can actually be restreamed. Null is the common case - only Bambu's X1/H2 class publishes an RTSP URL. Present means it is worth calling GET /printers/:id/stream; null means render no player."
|
||||
},
|
||||
"cameraSnapshot": {
|
||||
"type": "boolean",
|
||||
"description": "True when the camera can only be read as refreshing JPEG frames (Bambu P1/A1); GET /printers/:id/stream then answers kind 'snapshot'."
|
||||
},
|
||||
"lastSeenAt": {
|
||||
"type": ["string", "null"],
|
||||
"format": "date-time",
|
||||
|
||||
@@ -183,6 +183,32 @@
|
||||
"message"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"title": "CameraSnapshot",
|
||||
"description": "The answer to a CameraSnapshotRequest: one JPEG frame (base64), or the reason there is none.",
|
||||
"properties": {
|
||||
"type": { "const": "camera_snapshot" },
|
||||
"request_id": { "type": "string" },
|
||||
"jpeg_base64": { "type": "string" },
|
||||
"error": { "type": "string", "description": "Set instead of jpeg_base64 when no frame could be read." }
|
||||
},
|
||||
"required": ["type", "request_id"]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"title": "JobEvent",
|
||||
"description": "What happened to a job this gateway was handed, kept in the gateway's own database and re-sent on every connection until the cloud answers with a JobEventAck - so an outcome reached while the cloud was unreachable (or the gateway restarted) still arrives. The cloud applies it only to a job that is still in flight, so a job it has since cancelled or reassigned is left alone, and applying the same event twice changes nothing.",
|
||||
"properties": {
|
||||
"type": { "const": "job_event" },
|
||||
"event_id": { "type": "integer", "description": "The gateway's own sequence number for this event; echoed in the JobEventAck." },
|
||||
"job_id": { "type": "string", "format": "uuid" },
|
||||
"event": { "enum": ["started", "finished", "failed"] },
|
||||
"detail": { "type": ["string", "null"], "description": "For failed: the reason, in words for an operator." },
|
||||
"at_ms": { "type": "integer", "description": "When the gateway observed it, milliseconds since the Unix epoch." }
|
||||
},
|
||||
"required": ["type", "event_id", "job_id", "event", "at_ms"]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"title": "DiscoveredPrinters",
|
||||
|
||||
@@ -27,6 +27,12 @@
|
||||
"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."
|
||||
},
|
||||
"ams_slot": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 15,
|
||||
"description": "Use this AMS slot (0-based across all units: AMS1 slot 4 is 3) instead of the one the project's own filament colour would pick. Only valid for a single-filament project, and the slot must hold the same material type; the gateway refuses otherwise."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -108,7 +108,11 @@
|
||||
},
|
||||
"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."
|
||||
"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 are served by camera_snapshot instead."
|
||||
},
|
||||
"camera_snapshot": {
|
||||
"type": "boolean",
|
||||
"description": "True when this printer's camera can only be read as single JPEG frames (a Bambu P1/A1: proprietary TLS stream on port 6000, no RTSP) and the gateway will answer a CameraSnapshotRequest for it. Mutually exclusive with camera_stream in practice. Absent means false."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -109,6 +109,113 @@
|
||||
"sdp"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"title": "CameraSnapshotRequest",
|
||||
"description": "Read one JPEG frame from a printer's camera and send it back as a CameraSnapshot. For the printers whose camera is not RTSP (Bambu P1/A1, telemetry camera_snapshot true). Unlike WebrtcOffer this DOES carry the picture through the cloud, which is acceptable only because the frames are small, low-rate and requested one at a time.",
|
||||
"properties": {
|
||||
"type": { "const": "camera_snapshot_request" },
|
||||
"request_id": { "type": "string" },
|
||||
"printer_id": { "type": "string", "description": "The gateway's own printer id." }
|
||||
},
|
||||
"required": ["type", "request_id", "printer_id"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"title": "JobEventAck",
|
||||
"description": "The cloud has dealt with a JobEvent (applied it, or found it had nothing to change), so the gateway can forget it. Not sent when the cloud could not record it, so the gateway sends it again.",
|
||||
"properties": {
|
||||
"type": { "const": "job_event_ack" },
|
||||
"event_id": { "type": "integer" }
|
||||
},
|
||||
"required": ["type", "event_id"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"title": "ProvisionPrinter",
|
||||
"description": "Add a printer to this gateway. Sent by the console through the cloud, which relays it and keeps nothing: it carries the printer's LAN credential (access_code or api_key), and the platform's rule is that those never rest in the cloud. The gateway checks them by connecting to the printer, saves them to its own local store, and answers with a client command_result whose command_id equals request_id. NEVER LOG THIS MESSAGE. Deliberately has no ca_cert_path or serial port: those name things on the gateway's own disk and USB bus, which the cloud does not get to choose.",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "provision_printer"
|
||||
},
|
||||
"request_id": {
|
||||
"type": "string",
|
||||
"description": "Echoed as command_id in the command_result."
|
||||
},
|
||||
"printer": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^[A-Za-z0-9_-]{1,64}$",
|
||||
"description": "The gateway's own printer id, chosen by the cloud so that telemetry is reported under an id the cloud already knows."
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 200
|
||||
},
|
||||
"vendor": {
|
||||
"type": "string",
|
||||
"description": "A fleet vendor name: bambu, bambu_v1, bambu_v2, prusa_link, octoprint, klipper."
|
||||
},
|
||||
"host": {
|
||||
"type": "string",
|
||||
"maxLength": 255
|
||||
},
|
||||
"access_code": {
|
||||
"type": "string",
|
||||
"maxLength": 128
|
||||
},
|
||||
"sn": {
|
||||
"type": "string",
|
||||
"maxLength": 64
|
||||
},
|
||||
"api_key": {
|
||||
"type": "string",
|
||||
"maxLength": 256
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"vendor"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"request_id",
|
||||
"printer"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"title": "RemovePrinter",
|
||||
"description": "Forget a printer on this gateway: drop it from the running fleet and delete its stored credential. Answered with a command_result whose command_id equals request_id.",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "remove_printer"
|
||||
},
|
||||
"request_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"printer_id": {
|
||||
"type": "string",
|
||||
"description": "The gateway's own printer id."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"request_id",
|
||||
"printer_id"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user