Compare commits
41
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ad400d44c
|
||
|
|
1386b1b4db
|
||
|
|
4d148ebaf7
|
||
|
|
9fd3a628f8
|
||
|
|
c667e16ded
|
||
|
|
99545703cb
|
||
|
|
b454e30c87
|
||
|
|
7a9b19f72a
|
||
|
|
83b2617000
|
||
|
|
5f46ea166b
|
||
|
|
14e8febfc9
|
||
|
|
40d3eab400
|
||
|
|
362c8222c1
|
||
|
|
88330ba9d3
|
||
|
|
bb5a2680af
|
||
|
|
ef6c422458
|
||
|
|
88919d500d
|
||
|
|
f94e59a981
|
||
|
|
b35fdf9e6a
|
||
|
|
952b5e1eca
|
||
|
|
8bdade66c2
|
||
|
|
0c7f6510ce
|
||
|
|
6381c27c48
|
||
|
|
0753703351
|
||
|
|
035e8ea236
|
||
|
|
a5f318bb0f
|
||
|
|
a6247b4b43
|
||
|
|
edb7d69d0e
|
||
|
|
0e774a4c34
|
||
|
|
d226758211
|
||
|
|
5c8d24a712
|
||
|
|
a558b9fc7d
|
||
|
|
26478e3655
|
||
|
|
fa30852bd5
|
||
|
|
ffcfe0d349
|
||
|
|
f2f7d07b77
|
||
|
|
9d0393f9ff
|
||
|
|
62f10f2d32
|
||
|
|
9b907cec48
|
||
|
|
d66f09e977
|
||
|
|
7c19245058
|
+1
-1
@@ -1,4 +1,4 @@
|
||||
# continuum-schemas has no runtime — it only emits generated types/structs.
|
||||
# continuum-schemas has no runtime - it only emits generated types/structs.
|
||||
# This file is kept for consistency with the other Continuum repos.
|
||||
|
||||
# Optional: private registry token, only needed if packages/* are ever
|
||||
|
||||
+4
-3
@@ -8,8 +8,9 @@ bun.lock
|
||||
Cargo.lock
|
||||
*.tsbuildinfo
|
||||
|
||||
packages/ts-types/src/generated/*
|
||||
!packages/ts-types/src/generated/.gitkeep
|
||||
|
||||
# Personal study files, not part of the real schema build.
|
||||
/examples/
|
||||
|
||||
# Emitted by `tsc -b` into each package's dist/ - never commit build output.
|
||||
*.js.map
|
||||
*.d.ts.map
|
||||
|
||||
@@ -1,35 +1,67 @@
|
||||
# continuum-schemas
|
||||
|
||||
Single source of truth for the Continuum data model. Protobuf definitions in
|
||||
`proto/` are compiled into two consumer packages:
|
||||
Single source of truth for the Continuum data model. Everything here is
|
||||
JSON Schema, compiled into two consumer packages:
|
||||
|
||||
- `packages/ts-types` — TypeScript types generated via `ts-proto`, plus
|
||||
hand-written [TypeBox](https://github.com/sinclairzx81/typebox) schemas
|
||||
under `src/schemas/` used for runtime validation in `continuum-backend`'s
|
||||
ElysiaJS routes.
|
||||
- `packages/rust-types` — a Rust crate (`continuum-rust-types`) that compiles
|
||||
the same `.proto` files with `prost-build` (via `build.rs`), producing
|
||||
`serde`-derived structs for `continuum-proxy` and `continuum-ai-worker`.
|
||||
- `packages/ts-types` - hand-written [TypeBox](https://github.com/sinclairzx81/typebox)
|
||||
schemas under `src/schemas/` used for runtime validation in
|
||||
`continuum-backend`'s ElysiaJS routes.
|
||||
- `packages/rust-types` - a Rust crate (`continuum-rust-types`) with
|
||||
hand-written, `serde`-derived structs for `continuum-proxy` and
|
||||
`continuum-ai-worker`.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
proto/continuum/v1/ canonical .proto message definitions
|
||||
packages/ts-types/ TypeBox schemas + generated TS proto types
|
||||
packages/rust-types/ prost-generated Rust structs (build.rs codegen)
|
||||
schemas/continuum/v1/ Continuum's own model
|
||||
ai/ continuum-backend <-> continuum-ai-worker: the
|
||||
stream:camera_frames work queue and the
|
||||
channel:print_alerts notification channel
|
||||
edge/ continuum-backend <-> continuum-proxy: the
|
||||
/ws/edge/v1 gateway uplink protocol
|
||||
material/ printer/ domain objects
|
||||
vendor.schema.json
|
||||
schemas/bambulab/ Bambu Lab MQTT wire schemas (v1/v2, report + request)
|
||||
packages/ts-types/ TypeBox schemas (hand-written, mirrors schemas/)
|
||||
packages/rust-types/ serde structs (hand-written, mirrors schemas/)
|
||||
```
|
||||
|
||||
## Build
|
||||
The two inter-service protocols - `edge/` and `ai/` - are here for a
|
||||
specific reason: each has two independent implementations, one per repo, and
|
||||
when there was nothing written down between them they drifted completely
|
||||
while every test passed. `/ws/edge/v1` had an `auth` handshake the gateway
|
||||
never sent; `stream:camera_frames` had a producer writing `printerId` and a
|
||||
consumer reading `printer_id`, so every frame was attributed to a printer
|
||||
called "unknown". Neither failure was visible from either side alone, which
|
||||
is what a canonical schema plus a contract test on each end is for.
|
||||
|
||||
Requires `protoc` on `PATH` and `npm install` run at the repo root first.
|
||||
`schemas/continuum/v1/*.schema.json` is the canonical shape for each
|
||||
domain object; `packages/ts-types/src/schemas/` and
|
||||
`packages/rust-types/src/lib.rs` both mirror it by hand. A mirror is added
|
||||
per schema only once something actually consumes it, so not every file here
|
||||
has one, and the two inter-service protocols keep their Rust mirror in the
|
||||
single repo that consumes it rather than in `packages/rust-types`:
|
||||
`edge/` in `continuum-proxy/src/uplink/protocol.rs`, `ai/` in
|
||||
`continuum-ai-worker/src/task.rs`. The set is small and changes slowly,
|
||||
which is why keeping hand-synced copies in sync is less overhead than a
|
||||
codegen pipeline (`protoc`/`ts-proto`/`prost-build`) nothing actually
|
||||
consumed - see git history for the
|
||||
protobuf version this replaced, and why it was dropped: every service in
|
||||
this system talks JSON (JSON Schema for the wire, TypeBox/serde for
|
||||
runtime validation), so protobuf's actual value - a single schema compiled
|
||||
into a compact **binary** wire format for both languages - never applied.
|
||||
|
||||
Whenever a domain schema changes, update all three by hand: the JSON
|
||||
Schema file, the TypeBox schema, and the Rust struct.
|
||||
|
||||
## Build / verify
|
||||
|
||||
Requires `npm install` at the repo root, and a Rust toolchain, first.
|
||||
|
||||
```
|
||||
npm install
|
||||
npm run build:ts # protoc + ts-proto -> packages/ts-types/src/generated
|
||||
npm run build:rust # cargo build, runs build.rs -> prost-generated structs
|
||||
npm run build # both
|
||||
npm run build:rust # cargo build, confirms the Rust structs compile
|
||||
npm run typecheck # tsc -b, confirms the TypeBox schemas compile
|
||||
```
|
||||
|
||||
Generated TS output under `packages/ts-types/src/generated` is build-derived
|
||||
and gitignored. The Rust crate's generated code lands in `OUT_DIR` per
|
||||
standard `prost-build` conventions and is never committed.
|
||||
There's no `build:ts` step - the TS side is hand-written, not generated.
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# Licence policy for a closed-source commercial product.
|
||||
# See docs/LICENSES.md in continuum-common for the rationale and the
|
||||
# non-cargo dependencies (external binaries, ML models) this file cannot see.
|
||||
#
|
||||
# Run: cargo deny check licenses
|
||||
|
||||
[licenses]
|
||||
# Permissive only. Anything copyleft reaching a shipped binary would make
|
||||
# the product undistributable without releasing our source.
|
||||
allow = [
|
||||
"MIT",
|
||||
"Apache-2.0",
|
||||
"Apache-2.0 WITH LLVM-exception",
|
||||
"BSD-2-Clause",
|
||||
"BSD-3-Clause",
|
||||
"ISC",
|
||||
"Zlib",
|
||||
"Unicode-3.0",
|
||||
"Unlicense",
|
||||
"MPL-2.0",
|
||||
"CC0-1.0",
|
||||
"BlueOak-1.0.0",
|
||||
]
|
||||
confidence-threshold = 0.9
|
||||
|
||||
# Our own crates declare `LicenseRef-Proprietary`, which is deliberately not
|
||||
# an SPDX identifier and must never appear in the allow list above - a
|
||||
# third-party crate claiming it would be a real finding. They are recognised
|
||||
# as first-party by `publish = false` in their manifests.
|
||||
private = { ignore = true }
|
||||
|
||||
[bans]
|
||||
deny = []
|
||||
|
||||
[advisories]
|
||||
yanked = "deny"
|
||||
+2
-3
@@ -1,18 +1,17 @@
|
||||
{
|
||||
"name": "continuum-schemas",
|
||||
"license": "UNLICENSED",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build:ts": "protoc --plugin=protoc-gen-ts_proto=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=packages/ts-types/src/generated --ts_proto_opt=esModuleInterop=true,outputServices=false,useOptionals=messages,outputIndex=true -I proto $(find proto -name '*.proto')",
|
||||
"build:rust": "cargo build -p continuum-rust-types --manifest-path packages/rust-types/Cargo.toml",
|
||||
"build": "npm run build:ts && npm run build:rust",
|
||||
"build": "npm run build:rust",
|
||||
"typecheck": "tsc -b"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ts-proto": "^1.181.0",
|
||||
"typescript": "^5.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
name = "continuum-rust-types"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
# Proprietary: deliberately not an SPDX id, so cargo-deny treats it as
|
||||
# first-party. See docs/LICENSES.md in continuum-common.
|
||||
license = "LicenseRef-Proprietary"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "continuum_types"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
prost = "0.13"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
||||
[build-dependencies]
|
||||
prost-build = "0.13"
|
||||
[dev-dependencies]
|
||||
serde_json = "1"
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let proto_root = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../proto")
|
||||
.canonicalize()
|
||||
.expect("proto root must exist");
|
||||
|
||||
let proto_files = [
|
||||
proto_root.join("continuum/v1/material.proto"),
|
||||
proto_root.join("continuum/v1/print_params.proto"),
|
||||
proto_root.join("continuum/v1/template.proto"),
|
||||
proto_root.join("continuum/v1/print_job.proto"),
|
||||
proto_root.join("bambulab/common.proto"),
|
||||
proto_root.join("bambulab/v1/request.proto"),
|
||||
proto_root.join("bambulab/v1/report.proto"),
|
||||
proto_root.join("bambulab/v2/request.proto"),
|
||||
proto_root.join("bambulab/v2/report.proto"),
|
||||
];
|
||||
|
||||
for file in &proto_files {
|
||||
println!("cargo:rerun-if-changed={}", file.display());
|
||||
}
|
||||
|
||||
let mut config = prost_build::Config::new();
|
||||
|
||||
// continuum.v1 — Continuum's own cross-service API. camelCase to match
|
||||
// the TS side's existing convention (this is a schema we designed
|
||||
// ourselves, so we get to pick the convention).
|
||||
config.type_attribute(".continuum", "#[derive(serde::Serialize, serde::Deserialize)]");
|
||||
config.type_attribute(".continuum", "#[serde(rename_all = \"camelCase\")]");
|
||||
|
||||
// bambulab.* — mirrors Bambu's actual MQTT wire JSON exactly
|
||||
// (snake_case), because this isn't a schema we get to design: it's
|
||||
// edge-only traffic (continuum-proxy only, never crosses into TS) that
|
||||
// has to match whatever the printer actually sends, byte for byte.
|
||||
//
|
||||
// IMPORTANT: these two type_attribute calls are scoped to ".bambulab"
|
||||
// specifically, not "." (global) — type_attribute calls are cumulative,
|
||||
// so a "." blanket rule here would stack a second, conflicting
|
||||
// #[serde(rename_all = ...)] onto every bambulab type alongside this
|
||||
// one and fail to compile. Verified: see commit history for this file.
|
||||
config.type_attribute(".bambulab", "#[derive(serde::Serialize, serde::Deserialize)]");
|
||||
config.type_attribute(".bambulab", "#[serde(rename_all = \"snake_case\")]");
|
||||
|
||||
// Some Bambu response fields aren't always present (e.g. `result`/
|
||||
// `reason` on some info responses) — serde requires every struct field
|
||||
// present in the JSON unless told otherwise. Add one line per such
|
||||
// field as you find them, e.g.:
|
||||
// config.field_attribute(".bambulab.v1.InfoResponse.result", "#[serde(default)]");
|
||||
//
|
||||
// Do NOT try to model the top-level {"info": {...}} / {"pushing": {...}}
|
||||
// envelope as a proto `oneof` here — prost-build's field/variant
|
||||
// attribute matching can't distinguish "the oneof field" from "the
|
||||
// variants inside it" (both match the same path prefix), so
|
||||
// `#[serde(flatten)]` lands in the wrong place and won't compile. Write
|
||||
// the envelope as a plain Rust enum instead, in continuum-proxy —
|
||||
// serde's default enum representation already produces exactly
|
||||
// {"info": {...}} with no configuration at all. See
|
||||
// continuum-proxy/src/printer/bambu_protocol.rs.
|
||||
|
||||
config
|
||||
.compile_protos(&proto_files, &[proto_root])
|
||||
.expect("failed to compile continuum .proto sources");
|
||||
}
|
||||
@@ -1,9 +1,5 @@
|
||||
pub mod continuum {
|
||||
pub mod v1 {
|
||||
include!(concat!(env!("OUT_DIR"), "/continuum.v1.rs"));
|
||||
}
|
||||
}
|
||||
|
||||
pub use continuum::v1::{
|
||||
MaterialProfile, PrintJob, PrintJobStatus, PrintParameters, Template,
|
||||
};
|
||||
//! The old MaterialProfile/PrintParameters/PrintJob/Template structs were
|
||||
//! removed (2026-09) when schemas/continuum/v1/ was restructured into
|
||||
//! material/printer/vendor - see that directory and its README. Add a
|
||||
//! hand-written serde struct here per file under schemas/continuum/v1/
|
||||
//! once something actually consumes it, same convention as before.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "@continuum/ts-types",
|
||||
"license": "UNLICENSED",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -9,8 +10,6 @@
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sinclair/typebox": "^0.32.0",
|
||||
"protobufjs": "^7.4.0",
|
||||
"long": "^5.2.3"
|
||||
"@sinclair/typebox": "0.34.52"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1 @@
|
||||
export * from "./schemas";
|
||||
|
||||
// `src/generated/**` is produced by `npm run build:ts` (protoc + ts-proto)
|
||||
// from the .proto sources in /proto and is not committed. Re-export it
|
||||
// here once generated so consumers get both the TypeBox runtime schemas
|
||||
// and the proto-derived TS types from a single package entry point.
|
||||
export * as proto from "./generated";
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import { Type, type Static } from "@sinclair/typebox";
|
||||
|
||||
/**
|
||||
* The `stream:camera_frames` work queue and the `channel:print_alerts`
|
||||
* notification channel, mirroring `schemas/continuum/v1/ai/*.schema.json`
|
||||
* by hand - the JSON Schema files are canonical, and the Rust mirror lives
|
||||
* in `continuum-ai-worker/src/task.rs`.
|
||||
*
|
||||
* These two are the only interface between `continuum-backend` and
|
||||
* `continuum-ai-worker`. They are written down here because the previous
|
||||
* arrangement - a producer and a consumer each naming their own fields,
|
||||
* with nothing in between - produced exactly the drift it sounds like: the
|
||||
* backend wrote `printerId` and the worker read `printer_id`, so every
|
||||
* frame the worker ever handled was attributed to a printer called
|
||||
* "unknown", and nothing failed.
|
||||
*/
|
||||
|
||||
/**
|
||||
* One entry on `stream:camera_frames`.
|
||||
*
|
||||
* ## Every value is a string
|
||||
*
|
||||
* A Valkey stream entry is a flat field/value map of strings. There is no
|
||||
* nesting and no numeric type, which is why `FrameTaskSchema` has no
|
||||
* numbers and no nested objects - a producer that needs one has to encode
|
||||
* it, and then both sides have to agree on the encoding, which is the thing
|
||||
* this file exists to avoid.
|
||||
*
|
||||
* ## An absent field is absent
|
||||
*
|
||||
* `print_job_id` is omitted from the XADD when the frame belongs to no
|
||||
* tracked job - not written as `""`. An empty string is a value: a consumer
|
||||
* that passes it along ends up looking up job id `""`, and one that writes
|
||||
* it to Postgres violates a uuid column. So the producer omits the field and
|
||||
* the consumer treats absent and empty alike.
|
||||
*/
|
||||
export const FrameTaskSchema = Type.Object({
|
||||
/** `farms.id`. */
|
||||
farm_id: Type.String({ format: "uuid" }),
|
||||
/**
|
||||
* `printers.id` - the backend's UUID, already resolved from the gateway's
|
||||
* own printer id, which means nothing outside its farm.
|
||||
*/
|
||||
printer_id: Type.String({ format: "uuid" }),
|
||||
/** `print_jobs.id`, or omitted. Never `""`. */
|
||||
print_job_id: Type.Optional(Type.String({ format: "uuid" })),
|
||||
/** Where the frame is in R2. The bytes are never in the stream. */
|
||||
r2_object_key: Type.String({ minLength: 1 }),
|
||||
/**
|
||||
* The gateway's clock, at capture - not enqueue time. A gateway that
|
||||
* buffered frames through an uplink outage produces tasks whose
|
||||
* `captured_at` is minutes old.
|
||||
*/
|
||||
captured_at: Type.String({ format: "date-time" }),
|
||||
});
|
||||
export type FrameTask = Static<typeof FrameTaskSchema>;
|
||||
|
||||
/**
|
||||
* One message on `channel:print_alerts`, as JSON.
|
||||
*
|
||||
* Unlike a stream entry this one is a document, because PUBLISH carries a
|
||||
* single opaque string. It is also fire-and-forget: an alert published with
|
||||
* no subscriber is gone, so this channel is a live-console feed and not the
|
||||
* record of a detection.
|
||||
*/
|
||||
export const PrintAlertSchema = Type.Object({
|
||||
printer_id: Type.String({ format: "uuid" }),
|
||||
print_job_id: Type.Optional(Type.String({ format: "uuid" })),
|
||||
/**
|
||||
* Free string, not an enum: the label set belongs to the model file and
|
||||
* changes without this schema changing.
|
||||
*/
|
||||
label: Type.String(),
|
||||
/**
|
||||
* The model's own score. Thresholding is the consumer's policy - an
|
||||
* operator may well want to see near-misses.
|
||||
*/
|
||||
confidence: Type.Number({ minimum: 0, maximum: 1 }),
|
||||
/** From the frame, so a console shows when the printer looked like this. */
|
||||
captured_at: Type.String({ format: "date-time" }),
|
||||
/** So a console can show the picture, not just the label. */
|
||||
r2_object_key: Type.String(),
|
||||
});
|
||||
export type PrintAlert = Static<typeof PrintAlertSchema>;
|
||||
|
||||
/** The stream the tasks go on, and the group the worker reads as. */
|
||||
export const CAMERA_FRAMES_STREAM = "stream:camera_frames";
|
||||
export const AI_WORKER_CONSUMER_GROUP = "ai-worker";
|
||||
/**
|
||||
* Where entries go that cannot be processed and must not be retried - a
|
||||
* malformed task, or one that has been redelivered too many times. Separate
|
||||
* from the main stream so a poison message cannot block the queue and
|
||||
* cannot be lost silently either.
|
||||
*/
|
||||
export const CAMERA_FRAMES_DEAD_LETTER_STREAM = "stream:camera_frames:dead";
|
||||
export const PRINT_ALERTS_CHANNEL = "channel:print_alerts";
|
||||
@@ -0,0 +1,98 @@
|
||||
import { Type, type Static, type TSchema } from "@sinclair/typebox";
|
||||
|
||||
/** Present but possibly null - a column that exists and has no value yet. */
|
||||
const Nullable = <T extends TSchema>(schema: T) => Type.Union([schema, Type.Null()]);
|
||||
|
||||
/**
|
||||
* What continuum-backend pushes down `/ws/telemetry` to an operator console,
|
||||
* mirroring `schemas/continuum/v1/app/telemetry_message.schema.json` by hand
|
||||
* - the JSON Schema is canonical, change it first.
|
||||
*
|
||||
* ## Why this is camelCase when everything else here is snake_case
|
||||
*
|
||||
* Because this one is not shared with the Rust gateway. `continuum/v1/edge/`
|
||||
* is snake_case so serde derives those names directly; this is the backend's
|
||||
* own HTTP vocabulary, and the browser already consumes camelCase from
|
||||
* `GET /printers`. Giving it a second model of a printer to reconcile would
|
||||
* be the cost of a consistency nobody benefits from.
|
||||
*
|
||||
* This file exists because a previous frontend invented a shape against
|
||||
* nothing - `printerId`, `nozzleTempC`, `progressPct`, `updatedAt` and a
|
||||
* closed `idle|printing|paused|error|offline` enum, inside an envelope no
|
||||
* backend ever sent. Both ends now mirror something.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A printer as the console displays it: the same shape `GET /printers/:id`
|
||||
* returns, so a client can apply a push to the row it already holds.
|
||||
*
|
||||
* Every telemetry field is nullable, and that is not laziness - a printer
|
||||
* that has never reported has no temperature, and "no data yet" is a
|
||||
* different thing from 0 degrees. A client that renders `?? 0` here will
|
||||
* show a cold printer as a real reading.
|
||||
*/
|
||||
export const AppPrinterStateSchema = Type.Object({
|
||||
id: Type.String({ format: "uuid" }),
|
||||
farmId: Type.String({ format: "uuid" }),
|
||||
name: Type.String(),
|
||||
vendor: Type.String(),
|
||||
status: Type.Optional(Nullable(Type.String())),
|
||||
/**
|
||||
* The printer's own word for what it is doing - a Bambu's `RUNNING`,
|
||||
* `PREPARE`, `FINISH`. Deliberately not an enum: an unrecognised state
|
||||
* must reach the screen as itself rather than being dropped on the way.
|
||||
*/
|
||||
vendorState: Type.Optional(Nullable(Type.String())),
|
||||
vendorErrorCode: Type.Optional(Nullable(Type.Integer())),
|
||||
nozzleTempC: Type.Optional(Nullable(Type.Number())),
|
||||
nozzleTargetTempC: Type.Optional(Nullable(Type.Number())),
|
||||
bedTempC: Type.Optional(Nullable(Type.Number())),
|
||||
bedTargetTempC: Type.Optional(Nullable(Type.Number())),
|
||||
progressPercent: Type.Optional(Nullable(Type.Integer())),
|
||||
currentLayer: Type.Optional(Nullable(Type.Integer())),
|
||||
totalLayers: Type.Optional(Nullable(Type.Integer())),
|
||||
remainingMinutes: Type.Optional(Nullable(Type.Integer())),
|
||||
currentJobName: Type.Optional(Nullable(Type.String())),
|
||||
/** A count of active HMS entries, not messages - we lack Bambu's tables. */
|
||||
activeAlerts: Type.Optional(Nullable(Type.Integer())),
|
||||
/**
|
||||
* 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 `GET /printers/:id/stream` is worth
|
||||
* calling; null means render no player at all.
|
||||
*/
|
||||
cameraStream: Type.Optional(Nullable(Type.String())),
|
||||
/**
|
||||
* 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
|
||||
* the status on its own cannot say.
|
||||
*/
|
||||
lastSeenAt: Type.Optional(Nullable(Type.String())),
|
||||
});
|
||||
export type AppPrinterState = Static<typeof AppPrinterStateSchema>;
|
||||
|
||||
export const AppTelemetryMessageSchema = Type.Union([
|
||||
Type.Object(
|
||||
{
|
||||
type: Type.Literal("printer.telemetry"),
|
||||
printer: AppPrinterStateSchema,
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
),
|
||||
/**
|
||||
* Sent once, right after the socket authenticates.
|
||||
*
|
||||
* Worth a message of its own rather than letting the client infer
|
||||
* readiness from silence: an operator with no printers claimed and an
|
||||
* operator whose gateway is offline both see nothing arrive, and those two
|
||||
* need different things said to them.
|
||||
*/
|
||||
Type.Object(
|
||||
{
|
||||
type: Type.Literal("ready"),
|
||||
visiblePrinters: Type.Integer({ minimum: 0 }),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
),
|
||||
]);
|
||||
export type AppTelemetryMessage = Static<typeof AppTelemetryMessageSchema>;
|
||||
@@ -0,0 +1,378 @@
|
||||
import { Type, type Static, type TSchema } from "@sinclair/typebox";
|
||||
|
||||
/**
|
||||
* Optional *and* nullable: the key may be missing, or present as `null`.
|
||||
*
|
||||
* Strict in what we send, liberal in what we accept. A gateway omits fields
|
||||
* it has no value for, which is what the JSON Schema describes - but a
|
||||
* serde-derived Rust struct emits `null` by default unless every field is
|
||||
* annotated, and one such field reaching here would otherwise fail
|
||||
* validation and make Elysia drop an entire telemetry batch over a printer
|
||||
* that merely had no job name. Not worth risking a farm's telemetry on.
|
||||
*/
|
||||
const Maybe = <T extends TSchema>(schema: T) => Type.Optional(Type.Union([schema, Type.Null()]));
|
||||
|
||||
/**
|
||||
* The /ws/edge/v1 uplink protocol, mirroring
|
||||
* `schemas/continuum/v1/edge/*.schema.json` by hand - the same convention
|
||||
* every other schema in this package follows. The JSON Schema files are
|
||||
* canonical; change them first, then this, then the Rust mirror (which for
|
||||
* this one protocol lives in `continuum-proxy/src/uplink/protocol.rs` rather
|
||||
* than in `packages/rust-types`, since the gateway is its only Rust
|
||||
* consumer).
|
||||
*
|
||||
* Field names are snake_case, unlike the backend's HTTP routes. That is not
|
||||
* an oversight: the other end of this wire is serde-derived Rust, and
|
||||
* snake_case is what those structs produce without per-field rename
|
||||
* attributes. It is also what the rest of this repository uses.
|
||||
*/
|
||||
|
||||
export const PrinterTelemetrySchema = Type.Object({
|
||||
/**
|
||||
* The *gateway's* own id from its printers.toml - not a vendor serial and
|
||||
* not `printers.id`. The backend resolves it through
|
||||
* `printers.gateway_printer_id`, scoped to the authenticated farm.
|
||||
*/
|
||||
printer_id: Type.String(),
|
||||
name: Type.String(),
|
||||
/**
|
||||
* The printer's own state string rather than an enum, so an unrecognised
|
||||
* state survives the trip instead of being dropped at the edge. Bambu
|
||||
* emits IDLE, RUNNING, PAUSE, FINISH, FAILED.
|
||||
*/
|
||||
state: Maybe(Type.String()),
|
||||
percent: Maybe(Type.Integer({ minimum: 0, maximum: 100 })),
|
||||
/**
|
||||
* What the printer is doing when it is not laying down plastic - bed
|
||||
* levelling, nozzle cleaning, extrusion calibration. Bambu's `stg_cur`,
|
||||
* passed through as its own number because we do not have the
|
||||
* code-to-name table. 0 means printing.
|
||||
*
|
||||
* Without it, progress is ambiguous in a way that reads as a fault: a P1P
|
||||
* was observed at 14% with `layer` still 0 for several minutes while it
|
||||
* worked through stages 54, 14 and 13. A console showing that with no
|
||||
* stage looks stuck.
|
||||
*/
|
||||
stage: Maybe(Type.Integer()),
|
||||
/**
|
||||
* The stream name to ask the gateway's `stream_base_url` for, when this
|
||||
* printer has a camera the gateway can restream. Absent means no stream.
|
||||
*
|
||||
* Absent is the common answer today: a Bambu P1/A1 publishes no RTSP URL
|
||||
* at all - a real P1P's ipcam block has `ipcam_dev` and `ipcam_record` and
|
||||
* nothing to connect to - so only the X1/H2 class works this way. The rest
|
||||
* need Bambu's proprietary port-6000 JPEG stream, which no gateway
|
||||
* implements yet.
|
||||
*/
|
||||
camera_stream: Maybe(Type.String()),
|
||||
layer: Maybe(Type.Integer({ minimum: 0 })),
|
||||
total_layers: Maybe(Type.Integer({ minimum: 0 })),
|
||||
remaining_minutes: Maybe(Type.Integer({ minimum: 0 })),
|
||||
job_name: Maybe(Type.String()),
|
||||
nozzle_temp: Maybe(Type.Number()),
|
||||
nozzle_target: Maybe(Type.Number()),
|
||||
bed_temp: Maybe(Type.Number()),
|
||||
bed_target: Maybe(Type.Number()),
|
||||
/** Vendor error code; non-zero means the printer is reporting a fault. */
|
||||
print_error: Maybe(Type.Integer()),
|
||||
/** Count of active Health-Management-System entries, not decoded messages. */
|
||||
active_alerts: Maybe(Type.Integer({ minimum: 0 })),
|
||||
});
|
||||
export type PrinterTelemetry = Static<typeof PrinterTelemetrySchema>;
|
||||
|
||||
/**
|
||||
* Deliberately small, and deliberately missing "start a print" - see the
|
||||
* JSON Schema for why that variant is absent until the gateway's
|
||||
* start-an-uploaded-file path has been run against real hardware by hand.
|
||||
*/
|
||||
/**
|
||||
* Every variant is closed (`additionalProperties: false`), unlike
|
||||
* `PrinterTelemetrySchema` which is deliberately open. The asymmetry is the
|
||||
* point, and it follows from who writes each message.
|
||||
*
|
||||
* Telemetry is written by gateways in the field, so it is read liberally: an
|
||||
* unknown field must not cost a whole farm's batch, and a gateway that is
|
||||
* newer than the backend should still be understood.
|
||||
*
|
||||
* Commands are written by this backend, are a handful of fields, and move
|
||||
* physical machines. Here an unrecognised field is a bug on the sending side,
|
||||
* and silently dropping it is the worst available outcome - a caller that
|
||||
* misspells a field would watch its command succeed and do something other
|
||||
* than what it asked for. `ams_mapping` is the concrete case: it is
|
||||
* deliberately not part of `start_print`, and a caller that passes one must
|
||||
* be told, not ignored.
|
||||
*/
|
||||
export const PrinterCommandSchema = Type.Union([
|
||||
Type.Object({
|
||||
action: Type.Literal("start_print"),
|
||||
/**
|
||||
* The cloud's `print_jobs.id`. The gateway does not need it to do the
|
||||
* work - it is here so a gateway's logs line up with a job.
|
||||
*/
|
||||
job_id: Type.String({ format: "uuid" }),
|
||||
/**
|
||||
* What to call the file on the printer's own storage. A `.3mf` is a
|
||||
* sliced project and its filament mapping is read out of the archive;
|
||||
* anything else is bare gcode and carries no mapping.
|
||||
*/
|
||||
file_name: Type.String({ minLength: 1, maxLength: 500 }),
|
||||
/**
|
||||
* A presigned, expiring GET for the job file - a URL rather than the
|
||||
* bytes, because the file is tens of megabytes and this is a JSON
|
||||
* control channel, and because it keeps object-store credentials out of
|
||||
* every gateway on every farm.
|
||||
*
|
||||
* Presign this at *dispatch* time, never at job creation: a queued job
|
||||
* may wait days and the URL would be long expired.
|
||||
*/
|
||||
url: Type.String({ format: "uri" }),
|
||||
}, { additionalProperties: false }),
|
||||
Type.Object({
|
||||
action: Type.Literal("set_fan_speed"),
|
||||
/**
|
||||
* A percentage on this wire whatever the vendor wants. A Bambu turns it
|
||||
* into an M106 PWM value and reports it back as one of 16 gears, so what
|
||||
* is set here does not read back identically.
|
||||
*/
|
||||
percent: Type.Integer({ minimum: 0, maximum: 100 }),
|
||||
}, { additionalProperties: false }),
|
||||
Type.Object({
|
||||
action: Type.Literal("control_print"),
|
||||
/**
|
||||
* Confirmed live on a P1P (2026-10-11). Beware what the printer's
|
||||
* acceptance means: all three answered `success` while it was IDLE with
|
||||
* nothing to pause, so confirmation that the state actually changed has
|
||||
* to come from telemetry, not from the command result.
|
||||
*
|
||||
* `stop` is a cancel and cannot be undone - a Bambu reports the
|
||||
* cancelled print as `FAILED`.
|
||||
*/
|
||||
control: Type.Union([
|
||||
Type.Literal("pause"),
|
||||
Type.Literal("resume"),
|
||||
Type.Literal("stop"),
|
||||
]),
|
||||
}, { additionalProperties: false }),
|
||||
Type.Object({
|
||||
action: Type.Literal("send_gcode_line"),
|
||||
/** The most powerful variant by far; be careful about exposing it to a UI. */
|
||||
line: Type.String(),
|
||||
}, { additionalProperties: false }),
|
||||
]);
|
||||
export type PrinterCommand = Static<typeof PrinterCommandSchema>;
|
||||
|
||||
const DiscoveredVendor = Type.Union([
|
||||
Type.Literal("bambu"),
|
||||
Type.Literal("klipper"),
|
||||
Type.Literal("prusa_link"),
|
||||
Type.Literal("octoprint"),
|
||||
Type.Literal("prusa_serial"),
|
||||
]);
|
||||
|
||||
/**
|
||||
* A printer a gateway found but cannot drive, because it is not in that
|
||||
* gateway's fleet file. Carries no credential and cannot: a Bambu access
|
||||
* code is on the printer's screen and a PrusaLink API key is in its web UI,
|
||||
* and no probe learns either. What it buys is that an operator adding a
|
||||
* printer in the web app does not have to go and read an IP address or a
|
||||
* serial number off a machine.
|
||||
*
|
||||
* Every string here was chosen by a device on the customer's network -
|
||||
* anything that answers an SSDP query or an HTTP probe names itself - so
|
||||
* these are untrusted display strings. Hence the length caps, which are the
|
||||
* reason this one object is closed while its sibling variants are open: an
|
||||
* unbounded `name` from the LAN should not reach a database column or a
|
||||
* React tree, and a gateway has no legitimate reason to send extra fields
|
||||
* here that this backend would have to guess the meaning of.
|
||||
*/
|
||||
export const DiscoveredPrinterSchema = Type.Object({
|
||||
/**
|
||||
* The `vendor` value this candidate would need in a fleet file. Note
|
||||
* `octoprint` where a probe saw only the OctoPrint API: PrusaLink serves
|
||||
* that same API, so claiming PrusaLink would assert more than was
|
||||
* observed - see `ambiguous_with`.
|
||||
*/
|
||||
vendor: DiscoveredVendor,
|
||||
/**
|
||||
* An IP or hostname for a network printer, or a device path such as
|
||||
* `/dev/ttyACM0` for one on a serial port. An address on the *gateway's*
|
||||
* network: not a URL, and not reachable from the cloud.
|
||||
*/
|
||||
address: Type.String({ minLength: 1, maxLength: 255 }),
|
||||
/** Only where a probe could learn it without a credential. */
|
||||
serial: Maybe(Type.String({ maxLength: 64 })),
|
||||
model: Maybe(Type.String({ maxLength: 128 })),
|
||||
/** Whatever the operator typed into their slicer. Display only. */
|
||||
name: Maybe(Type.String({ maxLength: 128 })),
|
||||
/** Human detail to recognise the machine by. Never parsed. */
|
||||
detail: Maybe(Type.String({ maxLength: 256 })),
|
||||
/**
|
||||
* A vendor this could equally be. Today only `prusa_link` versus
|
||||
* `octoprint`, which nothing on the network distinguishes. Present means
|
||||
* the operator must choose, and the web app should offer both rather than
|
||||
* quietly picking one.
|
||||
*/
|
||||
ambiguous_with: Maybe(DiscoveredVendor),
|
||||
/** Which probe found it, which bounds how much it can know. */
|
||||
source: Type.Union([
|
||||
Type.Literal("ssdp"),
|
||||
Type.Literal("certificate"),
|
||||
Type.Literal("http_api"),
|
||||
Type.Literal("usb_descriptor"),
|
||||
Type.Literal("serial_handshake"),
|
||||
]),
|
||||
/**
|
||||
* Whether this gateway build can actually drive this vendor. False means
|
||||
* the client is still a stub, so adding the printer would produce one that
|
||||
* connects and does nothing - worth saying in the UI rather than offering
|
||||
* it as ready.
|
||||
*/
|
||||
driver_ready: Type.Boolean(),
|
||||
}, { additionalProperties: false });
|
||||
export type DiscoveredPrinter = Static<typeof DiscoveredPrinterSchema>;
|
||||
|
||||
export const EdgeClientMessageSchema = Type.Union([
|
||||
Type.Object({
|
||||
type: Type.Literal("hello"),
|
||||
gateway_id: Type.String(),
|
||||
farm_id: Type.String(),
|
||||
/*
|
||||
* No credential here, deliberately. The edge JWT travels in an
|
||||
* `Authorization: Bearer` header on the upgrade request and is verified
|
||||
* before the socket exists. A `token` field used to live on this
|
||||
* message; it was removed, because a token in the first frame means a
|
||||
* live socket and a parsed message for a peer that has proved nothing.
|
||||
*
|
||||
* `farm_id` is redundant with the token's own claim, and is checked
|
||||
* against it rather than trusted.
|
||||
*/
|
||||
/**
|
||||
* Where this gateway serves camera restreams, e.g.
|
||||
* `http://gateway.local:1984`. Omitted by a gateway with no cameras or
|
||||
* no go2rtc.
|
||||
*
|
||||
* Reported rather than configured, because only the gateway knows the
|
||||
* address it is reachable at - and note what that address is: a LAN one.
|
||||
* A browser on the same network can play the stream; one outside it
|
||||
* cannot, and a relay for that does not exist yet.
|
||||
*/
|
||||
stream_base_url: Maybe(Type.String()),
|
||||
}),
|
||||
Type.Object({
|
||||
type: Type.Literal("heartbeat"),
|
||||
/** Monotonic per connection, restarting at 1 on reconnect. */
|
||||
seq: Type.Integer({ minimum: 0 }),
|
||||
}),
|
||||
Type.Object({
|
||||
type: Type.Literal("telemetry"),
|
||||
/**
|
||||
* A batch, not a message per printer: a farm has one uplink and many
|
||||
* printers, and a coherent picture of the farm at an instant is worth
|
||||
* more here than the lowest possible per-machine latency.
|
||||
*/
|
||||
printers: Type.Array(PrinterTelemetrySchema),
|
||||
}),
|
||||
Type.Object({
|
||||
type: Type.Literal("command_result"),
|
||||
command_id: Type.String(),
|
||||
ok: Type.Boolean(),
|
||||
/** Only when `ok` is false. A message for an operator, not a code. */
|
||||
error: Maybe(Type.String()),
|
||||
}),
|
||||
/** go2rtc's answer SDP for a `webrtc_offer`, passed through verbatim. */
|
||||
Type.Object({
|
||||
type: Type.Literal("webrtc_answer"),
|
||||
session_id: Type.String(),
|
||||
sdp: Type.String(),
|
||||
}),
|
||||
/**
|
||||
* No answer was obtainable: no such camera, go2rtc not running, or it
|
||||
* refused the offer. A variant of its own rather than an empty answer, so
|
||||
* a viewer is told something instead of watching a `<video>` that never
|
||||
* loads.
|
||||
*/
|
||||
Type.Object({
|
||||
type: Type.Literal("webrtc_error"),
|
||||
session_id: Type.String(),
|
||||
message: Type.String(),
|
||||
}),
|
||||
Type.Object({
|
||||
type: Type.Literal("frame_batch"),
|
||||
printer_id: Type.String(),
|
||||
print_job_id: Maybe(Type.String({ format: "uuid" })),
|
||||
frames: Type.Array(
|
||||
Type.Object({
|
||||
object_key: Type.String(),
|
||||
captured_at: Type.String({ format: "date-time" }),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
/**
|
||||
* Printers the gateway found on its own network or USB ports that are not
|
||||
* in its fleet file, so an operator can add one in the web app without
|
||||
* hunting for an IP address.
|
||||
*
|
||||
* A whole batch that replaces whatever this gateway reported before, not a
|
||||
* set of additions: a printer that has gone is reported by its absence,
|
||||
* and a gateway that sees nothing sends an empty list. Only sent at all
|
||||
* when discovery is enabled in the gateway's own config, which it is not
|
||||
* by default - scanning a customer's network is something they ask for.
|
||||
*/
|
||||
Type.Object({
|
||||
type: Type.Literal("discovered_printers"),
|
||||
printers: Type.Array(DiscoveredPrinterSchema, { maxItems: 256 }),
|
||||
/**
|
||||
* When the scan finished, so the cloud can show the age of the list
|
||||
* rather than implying it is live.
|
||||
*/
|
||||
scanned_at: Maybe(Type.String({ format: "date-time" })),
|
||||
}),
|
||||
]);
|
||||
export type EdgeClientMessage = Static<typeof EdgeClientMessageSchema>;
|
||||
|
||||
export const EdgeServerMessageSchema = Type.Union([
|
||||
Type.Object({ type: Type.Literal("hello_ack"), session_id: Type.String() }),
|
||||
Type.Object({ type: Type.Literal("hello_error"), message: Type.String() }),
|
||||
Type.Object({ type: Type.Literal("heartbeat_ack"), seq: Type.Integer({ minimum: 0 }) }),
|
||||
/**
|
||||
* Relay a browser's WebRTC offer to this gateway's go2rtc and send the
|
||||
* answer back. **Signalling only - no video crosses this socket.** The
|
||||
* gateway POSTs the offer to its own go2rtc over loopback, which go2rtc
|
||||
* exempts from its API password, so no credential is needed or sent;
|
||||
* browser and go2rtc then negotiate media directly over ICE.
|
||||
*
|
||||
* It exists because a browser cannot signal to a gateway itself. Measured
|
||||
* against go2rtc 1.9.14: it sends no `Access-Control-Allow-Origin` on
|
||||
* anything, and checks its password before CORS, so the unauthenticated
|
||||
* preflight a browser always sends gets a 401.
|
||||
*
|
||||
* Relaying only the signalling is the point. The media stays
|
||||
* peer-to-peer - on a workshop LAN it never leaves the building and costs
|
||||
* no cloud egress - and go2rtc's answer carries a server-reflexive
|
||||
* candidate as well as its host ones, so a remote viewer can often
|
||||
* hole-punch straight to the gateway. A TURN server in go2rtc's
|
||||
* `ice_servers` covers the NATs where that fails, and that is
|
||||
* configuration rather than a change to this protocol.
|
||||
*/
|
||||
Type.Object({
|
||||
type: Type.Literal("webrtc_offer"),
|
||||
/** Correlates the answer with this offer. One negotiation, not a subscription. */
|
||||
session_id: Type.String(),
|
||||
/**
|
||||
* The go2rtc stream name, which is what `PrinterTelemetry.camera_stream`
|
||||
* reported. Deliberately not called a printer id: the two are equal
|
||||
* today, but one is the gateway's own naming and the other is ours.
|
||||
*/
|
||||
camera_stream: Type.String(),
|
||||
/** The browser's offer SDP, passed through verbatim. */
|
||||
sdp: Type.String(),
|
||||
}, { additionalProperties: false }),
|
||||
Type.Object({
|
||||
type: Type.Literal("command"),
|
||||
command_id: Type.String(),
|
||||
/** The gateway's own printer id - the backend translates on the way out. */
|
||||
printer_id: Type.String(),
|
||||
command: PrinterCommandSchema,
|
||||
}),
|
||||
]);
|
||||
export type EdgeServerMessage = Static<typeof EdgeServerMessageSchema>;
|
||||
@@ -1,4 +1,21 @@
|
||||
export * from "./material";
|
||||
export * from "./print-params";
|
||||
export * from "./template";
|
||||
export * from "./print-job";
|
||||
// The old Material/PrintParameters/PrintJob/Template mirrors were removed
|
||||
// (2026-09) when schemas/continuum/v1/ was restructured into
|
||||
// material/printer/vendor - see that directory and its README. Add a
|
||||
// hand-written TypeBox schema here per file under schemas/continuum/v1/
|
||||
// once something actually consumes it (continuum-backend's validators,
|
||||
// most likely), same convention as before.
|
||||
|
||||
// The /ws/edge/v1 gateway uplink protocol - the first thing to actually need
|
||||
// a TypeBox mirror here, consumed by continuum-backend's edge WebSocket
|
||||
// route. Mirrors schemas/continuum/v1/edge/.
|
||||
export * from "./edge";
|
||||
|
||||
// The continuum-backend -> continuum-ai-worker interface: the
|
||||
// stream:camera_frames work queue and the channel:print_alerts notification
|
||||
// channel. Mirrors schemas/continuum/v1/ai/.
|
||||
export * from "./ai";
|
||||
|
||||
// The continuum-backend -> operator console push protocol: /ws/telemetry.
|
||||
// Mirrors schemas/continuum/v1/app/. camelCase, unlike the rest - see the
|
||||
// file for why.
|
||||
export * from "./app";
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Type, type Static } from "@sinclair/typebox";
|
||||
|
||||
export const MaterialProfileSchema = Type.Object({
|
||||
id: Type.String({ format: "uuid" }),
|
||||
name: Type.String({ minLength: 1 }),
|
||||
filamentType: Type.String({ minLength: 1 }),
|
||||
nozzleTempC: Type.Number(),
|
||||
bedTempC: Type.Number(),
|
||||
densityGCm3: Type.Number(),
|
||||
colorHex: Type.String({ pattern: "^#[0-9a-fA-F]{6}$" }),
|
||||
});
|
||||
|
||||
export type MaterialProfile = Static<typeof MaterialProfileSchema>;
|
||||
@@ -1,27 +0,0 @@
|
||||
import { Type, type Static } from "@sinclair/typebox";
|
||||
|
||||
export const PrintJobStatusSchema = Type.Union([
|
||||
Type.Literal("PRINT_JOB_STATUS_UNSPECIFIED"),
|
||||
Type.Literal("PRINT_JOB_STATUS_QUEUED"),
|
||||
Type.Literal("PRINT_JOB_STATUS_PRINTING"),
|
||||
Type.Literal("PRINT_JOB_STATUS_PAUSED"),
|
||||
Type.Literal("PRINT_JOB_STATUS_COMPLETED"),
|
||||
Type.Literal("PRINT_JOB_STATUS_FAILED"),
|
||||
Type.Literal("PRINT_JOB_STATUS_CANCELLED"),
|
||||
]);
|
||||
|
||||
export type PrintJobStatus = Static<typeof PrintJobStatusSchema>;
|
||||
|
||||
export const PrintJobSchema = Type.Object({
|
||||
id: Type.String({ format: "uuid" }),
|
||||
farmId: Type.String({ format: "uuid" }),
|
||||
printerId: Type.String({ format: "uuid" }),
|
||||
templateId: Type.String({ format: "uuid" }),
|
||||
status: PrintJobStatusSchema,
|
||||
progressPercent: Type.Number({ minimum: 0, maximum: 100 }),
|
||||
startedAtUnix: Type.Integer(),
|
||||
completedAtUnix: Type.Integer(),
|
||||
gcodeFileKey: Type.String({ minLength: 1 }),
|
||||
});
|
||||
|
||||
export type PrintJob = Static<typeof PrintJobSchema>;
|
||||
@@ -1,12 +0,0 @@
|
||||
import { Type, type Static } from "@sinclair/typebox";
|
||||
|
||||
export const PrintParametersSchema = Type.Object({
|
||||
layerHeightMm: Type.Number({ minimum: 0.04, maximum: 1.0 }),
|
||||
infillPercent: Type.Integer({ minimum: 0, maximum: 100 }),
|
||||
infillPattern: Type.String({ minLength: 1 }),
|
||||
printSpeedMmS: Type.Number({ minimum: 0 }),
|
||||
supportsEnabled: Type.Boolean(),
|
||||
wallCount: Type.Integer({ minimum: 0 }),
|
||||
});
|
||||
|
||||
export type PrintParameters = Static<typeof PrintParametersSchema>;
|
||||
@@ -1,18 +0,0 @@
|
||||
import { Type, type Static } from "@sinclair/typebox";
|
||||
import { MaterialProfileSchema } from "./material";
|
||||
import { PrintParametersSchema } from "./print-params";
|
||||
|
||||
export const TemplateSchema = Type.Object({
|
||||
id: Type.String({ format: "uuid" }),
|
||||
name: Type.String({ minLength: 1 }),
|
||||
description: Type.String(),
|
||||
ownerId: Type.String({ format: "uuid" }),
|
||||
modelFileKey: Type.String({ minLength: 1 }),
|
||||
material: MaterialProfileSchema,
|
||||
printParameters: PrintParametersSchema,
|
||||
createdAtUnix: Type.Integer(),
|
||||
updatedAtUnix: Type.Integer(),
|
||||
tags: Type.Array(Type.String()),
|
||||
});
|
||||
|
||||
export type Template = Static<typeof TemplateSchema>;
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package bambulab;
|
||||
|
||||
// Fields/messages genuinely shared between v1 and v2 (unversioned, since
|
||||
// they don't change across generations) go here — composition, embedded as
|
||||
// a field in v1/v2 messages via `import "bambulab/common.proto";`, not
|
||||
// inheritance. e.g. every Bambu command/response shares sequence_id +
|
||||
// command; that's a good candidate for a message here.
|
||||
@@ -1,15 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package bambulab.v1;
|
||||
|
||||
// import "bambulab/common.proto"; once common.proto has a shared message
|
||||
// worth embedding (composition, not inheritance — see that file).
|
||||
|
||||
// Your report/response messages go here, e.g.:
|
||||
//
|
||||
// message InfoResponse {
|
||||
// string sequence_id = 1;
|
||||
// string command = 2;
|
||||
// string result = 3; // not always present — see build.rs field_attribute
|
||||
// string reason = 4; // not always present — see build.rs field_attribute
|
||||
// }
|
||||
@@ -1,13 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package bambulab.v1;
|
||||
|
||||
// import "bambulab/common.proto"; once common.proto has a shared message
|
||||
// worth embedding (composition, not inheritance — see that file).
|
||||
|
||||
// Your command messages go here, e.g.:
|
||||
//
|
||||
// message InfoCommand {
|
||||
// string sequence_id = 1;
|
||||
// string command = 2;
|
||||
// }
|
||||
@@ -1,9 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package bambulab.v2;
|
||||
|
||||
// import "bambulab/common.proto"; once common.proto has a shared message
|
||||
// worth embedding (composition, not inheritance — see that file).
|
||||
|
||||
// V2-generation (X1/H2 series) report/response messages go here — e.g.
|
||||
// whatever AMS/report-schema fields V2 has that V1 doesn't.
|
||||
@@ -1,8 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package bambulab.v2;
|
||||
|
||||
// import "bambulab/common.proto"; once common.proto has a shared message
|
||||
// worth embedding (composition, not inheritance — see that file).
|
||||
|
||||
// V2-generation (X1/H2 series) command messages go here.
|
||||
@@ -1,13 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package continuum.v1;
|
||||
|
||||
message MaterialProfile {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
string filament_type = 3;
|
||||
double nozzle_temp_c = 4;
|
||||
double bed_temp_c = 5;
|
||||
double density_g_cm3 = 6;
|
||||
string color_hex = 7;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package continuum.v1;
|
||||
|
||||
enum PrintJobStatus {
|
||||
PRINT_JOB_STATUS_UNSPECIFIED = 0;
|
||||
PRINT_JOB_STATUS_QUEUED = 1;
|
||||
PRINT_JOB_STATUS_PRINTING = 2;
|
||||
PRINT_JOB_STATUS_PAUSED = 3;
|
||||
PRINT_JOB_STATUS_COMPLETED = 4;
|
||||
PRINT_JOB_STATUS_FAILED = 5;
|
||||
PRINT_JOB_STATUS_CANCELLED = 6;
|
||||
}
|
||||
|
||||
message PrintJob {
|
||||
string id = 1;
|
||||
string farm_id = 2;
|
||||
string printer_id = 3;
|
||||
string template_id = 4;
|
||||
PrintJobStatus status = 5;
|
||||
double progress_percent = 6;
|
||||
int64 started_at_unix = 7;
|
||||
int64 completed_at_unix = 8;
|
||||
string gcode_file_key = 9;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package continuum.v1;
|
||||
|
||||
message PrintParameters {
|
||||
double layer_height_mm = 1;
|
||||
int32 infill_percent = 2;
|
||||
string infill_pattern = 3;
|
||||
double print_speed_mm_s = 4;
|
||||
bool supports_enabled = 5;
|
||||
int32 wall_count = 6;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package continuum.v1;
|
||||
|
||||
import "continuum/v1/material.proto";
|
||||
import "continuum/v1/print_params.proto";
|
||||
|
||||
message Template {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
string description = 3;
|
||||
string owner_id = 4;
|
||||
string model_file_key = 5;
|
||||
MaterialProfile material = 6;
|
||||
PrintParameters print_parameters = 7;
|
||||
int64 created_at_unix = 8;
|
||||
int64 updated_at_unix = 9;
|
||||
repeated string tags = 10;
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
# bambulab/ JSON Schemas
|
||||
|
||||
Documentation/validation schemas for Bambu's raw MQTT wire format, mirroring
|
||||
continuum-proxy's `src/printer/bambu_commands/` - not used for codegen (the
|
||||
Rust structs there are hand-written; see that module's doc comment for why),
|
||||
but genuinely useful on their own: language-agnostic documentation of the
|
||||
wire format, and usable to validate a captured MQTT payload without writing
|
||||
any Rust to do it.
|
||||
|
||||
Sending a request with higher QoS (ie. QoS 1 - default is QoS 0) means higher priority for request.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
bambulab/
|
||||
common/
|
||||
envelope.schema.json the base - sequence_id + command, shared by
|
||||
every command in every generation, so it lives
|
||||
here unversioned, not duplicated under v1/v2
|
||||
v1/
|
||||
request.schema.json BambuRequest dispatcher - one key per category
|
||||
("info", "pushing", ...), each a oneOf (a
|
||||
category can have more than one request shape)
|
||||
report.schema.json BambuReport dispatcher, same idea
|
||||
common/ shapes shared across v1's request/ and report/
|
||||
ams/
|
||||
ams.schema.json
|
||||
ams_unit.schema.json
|
||||
ams_tray.schema.json
|
||||
vt_tray.schema.json shared base for AmsTray + the virtual/
|
||||
external spool slot, via allOf + $ref
|
||||
module_info.schema.json
|
||||
hms.schema.json, net.schema.json, online.schema.json,
|
||||
upload.schema.json, upgrade_state.schema.json, xcam.schema.json,
|
||||
ipcam.schema.json, lights_report.schema.json, nozzle_type.schema.json,
|
||||
nozzle_diameter.schema.json
|
||||
request/
|
||||
info/
|
||||
get_version.schema.json
|
||||
pushing/
|
||||
pushall.schema.json
|
||||
report/
|
||||
info/
|
||||
get_version.schema.json
|
||||
print/
|
||||
print.schema.json the ongoing print-state push
|
||||
v2/ same split as v1, once the wire format actually
|
||||
diverges per field - built out below as that's
|
||||
been confirmed, not copy-pasted speculatively
|
||||
request.schema.json dispatcher, same shape as v1's
|
||||
report.schema.json dispatcher, same shape as v1's
|
||||
common/ v2 grew a much larger shared shape set than v1
|
||||
(device/, job/, care, info, ...) because more
|
||||
of the wire format has been captured/confirmed
|
||||
ams/, device/, job/, and the same flat common/*.schema.json files
|
||||
as v1 (hms, net, online, upload, upgrade_state, xcam, ipcam,
|
||||
lights_report, nozzle_type, nozzle_diameter, vt_tray)
|
||||
request/
|
||||
info/
|
||||
get_version.schema.json
|
||||
report/
|
||||
info/
|
||||
get_version.schema.json
|
||||
_module_info.schema.json
|
||||
print/
|
||||
print.schema.json the ongoing print-state push (command
|
||||
"push_status")
|
||||
_2d_report.schema.json, _3d_report.schema.json,
|
||||
_nozzle_type.schema.json
|
||||
```
|
||||
|
||||
Add a category to a dispatcher (`v1|v2/request.schema.json` or
|
||||
`report.schema.json`) whenever a new `request/<category>/` or
|
||||
`report/<category>/` schema is added on disk - an unwired leaf schema is
|
||||
unreachable from validation even though it parses fine on its own.
|
||||
|
||||
**One real correction baked into this layout**: the report category for
|
||||
whatever a `pushall` request triggers is `report/print/`, not
|
||||
`report/pushing/`. Sending `pushall` (which *does* go to the `pushing` key
|
||||
on the request side) makes the printer start/refresh an ongoing state-push
|
||||
stream, and that stream arrives under the `print` key - a different root
|
||||
key from the request that triggered it. A category folder here means
|
||||
"what root key does this arrive under", not "what request caused it" - the
|
||||
first version of this schema got that wrong (see git history) before a
|
||||
closer read of the real capture caught it.
|
||||
|
||||
## The "inheritance" pattern
|
||||
|
||||
Every command/response schema *extends* `envelope.schema.json` with
|
||||
`allOf` + `$ref`:
|
||||
|
||||
```json
|
||||
{
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{ "type": "object", "properties": { "command": { "const": "get_version" } } }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
This is genuinely closer to real inheritance than anything else in this
|
||||
project's schema stack (proto, Rust): the instance must satisfy the base
|
||||
schema *and* the extension schema simultaneously, and - verified with
|
||||
negative test cases, not just asserted - `envelope.schema.json`'s
|
||||
`required: ["sequence_id", "command"]` is actually enforced on every
|
||||
schema that extends it, not just copy-pasted as documentation.
|
||||
|
||||
`v1/report/print/_ams_tray.schema.json` is the other JSON-Schema-specific
|
||||
tool worth knowing: `oneOf`, for "this is exactly one of several
|
||||
genuinely different shapes" (an AMS tray slot is either empty - just
|
||||
`id` - or loaded - the full field set - never something in between).
|
||||
`oneOf` requires exactly one branch to match; watch out for a base-style
|
||||
branch (like `EmptyTray`) accidentally also matching a more specific
|
||||
branch's instance if the specific branch doesn't require enough fields -
|
||||
verified and fixed once already here, see that file's description.
|
||||
|
||||
**One gotcha if you add a new base-style schema**: don't set
|
||||
`"additionalProperties": false` on something meant to be `allOf`-extended.
|
||||
`allOf` validates every sub-schema against the *whole* instance
|
||||
independently - it doesn't merge object schemas - so a closed base schema
|
||||
would reject every field the extending schema adds. Leave
|
||||
`additionalProperties` unset (defaults to allowed) on anything used as a
|
||||
base; enforce closedness only on the top-level dispatcher schemas
|
||||
(`v1/request.schema.json`/`v1/report.schema.json` do this correctly -
|
||||
check their `additionalProperties: false`).
|
||||
|
||||
## `$id` convention
|
||||
|
||||
Every schema's `$id` is its real Gitea raw-file URL:
|
||||
`https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/...`
|
||||
- a genuinely dereferenceable URI (verified: `raw/branch/main/<path>` is
|
||||
the correct Gitea pattern, not `raw/<path>` or `raw/<branch>/<path>`),
|
||||
rather than a made-up placeholder domain. `$ref`s between files stay
|
||||
relative paths; `$id` is what a validator uses as the base URI to resolve
|
||||
those relative refs, and it's also what lets a schema be fetched
|
||||
standalone by anything that wants to dereference it directly.
|
||||
|
||||
## Validating something
|
||||
|
||||
`$ref` resolution here relies on each schema's `$id` as the base URI for
|
||||
its own relative refs (standard JSON Schema behavior) - register schemas
|
||||
by `$id`, not by filename, or you'll hit collisions (`get_version.schema.json`
|
||||
exists under both `request/info/` and `report/info/`):
|
||||
|
||||
```python
|
||||
from referencing import Registry, Resource
|
||||
from jsonschema import Draft202012Validator
|
||||
import json, glob
|
||||
|
||||
resources = [
|
||||
(json.load(open(p))["$id"], Resource.from_contents(json.load(open(p))))
|
||||
for p in glob.glob("**/*.schema.json", recursive=True)
|
||||
]
|
||||
registry = Registry().with_resources(resources)
|
||||
|
||||
schema = json.load(open("v1/request.schema.json"))
|
||||
Draft202012Validator(schema, registry=registry).validate(
|
||||
{"info": {"sequence_id": "0", "command": "get_version"}}
|
||||
)
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/common/color_hex.schema.json",
|
||||
"title": "CommonColorHexSchema",
|
||||
"description": "Schema for hex color. Technically alpha (last two chars) should be always FF, but...",
|
||||
"type": "string",
|
||||
"pattern": "^[0-9a-fA-F]{8}$"
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/common/envelope.schema.json",
|
||||
"title": "CommandEnvelope",
|
||||
"description": "Fields every Bambu MQTT command/response shares, across both protocol generations - unversioned, at the bambulab/ root, not duplicated under v1/ or v2/. This is the 'base class' - other schemas extend it via allOf + $ref. JSON Schema doesn't have inheritance either, strictly speaking (no 'extends' keyword) - allOf is composition: it says the instance must satisfy THIS schema AND whatever else is listed alongside it. It's the closest thing to real inheritance ergonomics of anything used across this project's schema stack (proto, Rust), because $ref + allOf lets you name and reuse a shape without re-declaring its fields, which proto/Rust composition can't quite do (there you still write `common: PrinterReportCommon` by hand on every struct). Additionally, any field not defined by the schema will be returned by the printer int the original form - useful when making telemetrics.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sequence_id": {
|
||||
"type": "string",
|
||||
"description": "Sequence ID matching the original request"
|
||||
},
|
||||
"command": {
|
||||
"type": "string",
|
||||
"description": "Command being reported on"
|
||||
},
|
||||
"result": {
|
||||
"type": "string",
|
||||
"enum": ["success", "fail", "SUCCESS", "FAIL"],
|
||||
"description": "Result status (case-insensitive)"
|
||||
},
|
||||
"reason": {
|
||||
"type": "string",
|
||||
"description": "Optional reason or error message"
|
||||
},
|
||||
"is_from_mqtt": {
|
||||
"type": "boolean",
|
||||
"description": "Used by BambuMQTT v2"
|
||||
}
|
||||
},
|
||||
"required": ["sequence_id", "command"]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/common/file_url.schema.json",
|
||||
"title": "CommonFileUrlSchema",
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"pattern": "^(?:file|ftp):\/\/"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/common/http_url.schema.json",
|
||||
"title": "CommonHttpUrlSchema",
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"pattern": "^https?:\/\/"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/common/url.schema.json",
|
||||
"title": "CommonUrlSchema",
|
||||
"oneOf": [
|
||||
{ "$ref": "./http_url.schema.json" },
|
||||
{ "$ref": "./file_url.schema.json" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/common/version.schema.json",
|
||||
"title": "CommonVersionSchema",
|
||||
"type": "string",
|
||||
"pattern": "^(?:\\d{2}\\.){3}\\d{2}$"
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"name": "Bambu Lab MQTT Wire Schemas",
|
||||
"version": "1.0.0",
|
||||
"description": "JSON Schemas for Bambu Lab 3D Printers (X1/P1/A1 series)",
|
||||
"default_topics": [
|
||||
{
|
||||
"topic": "device/{{DEVICE_ID}}/report",
|
||||
"qos": 1,
|
||||
"color": "#22c55e",
|
||||
"description": "Printer real-time telemetry report stream"
|
||||
},
|
||||
{
|
||||
"topic": "device/{{DEVICE_ID}}/request",
|
||||
"qos": 1,
|
||||
"color": "#6366f1",
|
||||
"description": "Control requests sent to printer"
|
||||
}
|
||||
],
|
||||
"topic_groups": [
|
||||
{
|
||||
"name": "Printer Telemetry (v1)",
|
||||
"topic_pattern": "device/{{DEVICE_ID}}/report",
|
||||
"schema": "v1/report.schema.json",
|
||||
"description": "BambuReport dispatcher for push_status and print state pushes"
|
||||
},
|
||||
{
|
||||
"name": "Command Requests (v1)",
|
||||
"topic_pattern": "device/{{DEVICE_ID}}/request",
|
||||
"schema": "v1/request.schema.json",
|
||||
"description": "BambuRequest dispatcher for info, pushing, etc."
|
||||
},
|
||||
{
|
||||
"name": "Printer Telemetry (v2)",
|
||||
"topic_pattern": "device/{{DEVICE_ID}}/report",
|
||||
"schema": "v2/report.schema.json",
|
||||
"description": "BambuReport v2 dispatcher"
|
||||
},
|
||||
{
|
||||
"name": "Command Requests (v2)",
|
||||
"topic_pattern": "device/{{DEVICE_ID}}/request",
|
||||
"schema": "v2/request.schema.json",
|
||||
"description": "BambuRequest v2 dispatcher"
|
||||
}
|
||||
],
|
||||
"hidden_schemas": [
|
||||
"common/**",
|
||||
"v1/common/**",
|
||||
"v2/common/**",
|
||||
"**/_*.schema.json"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/common/ams/ams.schema.json",
|
||||
"title": "AmsState",
|
||||
"description": "Bambu's own naming has the outer container and the inner array both called `ams` - print.ams.ams, not a typo here, just what the printer actually sends.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ams": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "./ams_unit.schema.json" }
|
||||
},
|
||||
"ams_exists_bits": { "type": "string" },
|
||||
"tray_exist_bits": { "type": "string" },
|
||||
"tray_is_bbl_bits": { "type": "string" },
|
||||
"tray_tar": { "type": "string" },
|
||||
"tray_now": { "type": "string" },
|
||||
"tray_pre": { "type": "string" },
|
||||
"tray_read_done_bits": { "type": "string" },
|
||||
"tray_reading_bits": { "type": "string" },
|
||||
"version": { "type": "integer" },
|
||||
"insert_flag": { "type": "boolean" },
|
||||
"power_on_flag": { "type": "boolean" }
|
||||
},
|
||||
"required": ["ams"]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/common/ams/ams_mapping.schema.json",
|
||||
"title": "AmsMapping",
|
||||
"description": "AMS Mapping is a God forsaken hell. And it's CRUCIAL for multi-color. LOL. The ams_mapping array uses a reverse indexing system where:\n- Array positions represent color indices in your print file (starting from 0)\n- Array values represent AMS slot numbers (0-3 for typical 4-slot AMS)\n- -1 indicates unused color slots. ALWAYS - and I say ALWAYS - pad the array so it's length is equal to the sum of all AMS trays (slots) + virtual trays (external spools), ie. a P1P with one AMS would have `ams_mapping` array of length 5 - but H2C with 2x AMS 2 Pro and 1x AMS HT would have `ams_mapping` array of length 11 (printers with two nozzles in the toolhead have two external spools)",
|
||||
"type": "array",
|
||||
"items": { "type": "integer", "minimum": -1, "minItems": 1 }
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/common/ams/ams_tray.schema.json",
|
||||
"title": "AmsTray",
|
||||
"description": "A tray slot is either empty (just `id`) or loaded (the full field set) - two genuinely different shapes for the same slot, not one shape with optional fields. oneOf says the instance must match EXACTLY one of these. Note LoadedTray requires every field it lists, not just `id` - with only `id` required, a bare {\"id\":\"0\"} would validate against BOTH branches at once (it trivially satisfies Loaded's single requirement too) and oneOf would fail, since oneOf demands exactly one match. Verified this the hard way - see git history. The Rust equivalent is an untagged enum with #[serde(deny_unknown_fields)] on the Empty variant, for the same reason in reverse: without it, a loaded tray's extra fields wouldn't be rejected by Empty, and untagged enums try variants in order - see bambu_commands/print.rs. `drying_time` and `drying_temp` fields are only available for AMS 2 and AMS HT - AMS 1 doesn't have the drying option.",
|
||||
"oneOf": [
|
||||
{
|
||||
"title": "EmptyTray",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string" }
|
||||
},
|
||||
"required": ["id"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{ "$ref": "../vt_tray.schema.json" },
|
||||
{
|
||||
"title": "LoadedTray",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"state": { "type": "integer" },
|
||||
"total_len": { "type": "number", "minimum": 0 },
|
||||
"ctype": { "type": "integer" },
|
||||
"cols": { "type": "array", "items": { "type": "string" } },
|
||||
"drying_time": { "type": "string" },
|
||||
"drying_temp": { "type": "string" }
|
||||
},
|
||||
"required": [
|
||||
"state", "total_len", "ctype", "cols"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/common/ams/ams_unit.schema.json",
|
||||
"title": "AmsUnit",
|
||||
"description": "One physical AMS unit (Bambu's numbering starts at 0 via `id`).",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tray": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "./ams_tray.schema.json" }
|
||||
},
|
||||
"chip_id": { "type": "string" },
|
||||
"ams_id": { "type": "string" },
|
||||
"check": { "type": "integer" },
|
||||
"id": { "type": "string" },
|
||||
"humidity": { "type": "string" },
|
||||
"humidity_raw": { "type": "string" },
|
||||
"temp": { "type": "string" },
|
||||
"dry_time": { "type": "integer" },
|
||||
"info": { "type": "string" }
|
||||
},
|
||||
"required": ["humidity", "id", "temp", "tray"]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/common/bed_type.schema.json",
|
||||
"title": "BedType",
|
||||
"description": "Common schema for bed type. TODO: Add more bed types",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"auto"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/common/firmware_details.schema.json",
|
||||
"title": "FirmwareDetails",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": { "type": "string" },
|
||||
"force_update": { "type": "boolean" },
|
||||
"url": { "$ref": "../../common/url.schema.json" },
|
||||
"version": { "$ref": "../../common/version.schema.json" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/common/hms.schema.json",
|
||||
"title": "HMS",
|
||||
"description": "HMS Error Report",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"attr": { "type": "integer" },
|
||||
"code": { "type": "integer" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/common/ipcam.schema.json",
|
||||
"title": "Ipcam",
|
||||
"description": "Report about the in-built IP camera",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ipcam_dev": {
|
||||
"type": "string"
|
||||
},
|
||||
"ipcam_record": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enable",
|
||||
"disable",
|
||||
"ENABLE",
|
||||
"DISABLE"
|
||||
]
|
||||
},
|
||||
"timelapse": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enable",
|
||||
"disable",
|
||||
"ENABLE",
|
||||
"DISABLE"
|
||||
]
|
||||
},
|
||||
"resolution": {
|
||||
"type": "string"
|
||||
},
|
||||
"tutk_server": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enable",
|
||||
"disable",
|
||||
"ENABLE",
|
||||
"DISABLE"
|
||||
]
|
||||
},
|
||||
"mode_bits": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/common/led_node.schema.json",
|
||||
"title": "LedNode",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"chamber_light",
|
||||
"work_light",
|
||||
"chamber_light2"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/common/lights_report.schema.json",
|
||||
"title": "LightsReport",
|
||||
"description": "Report status of printer lights",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"on",
|
||||
"off",
|
||||
"ON",
|
||||
"OFF",
|
||||
"flashing",
|
||||
"FLASHING"
|
||||
]
|
||||
},
|
||||
"node": { "$ref": "./led_node.schema.json" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/common/module_info.schema.json",
|
||||
"title": "ModuleInfo",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"sw_ver": { "type": "string" },
|
||||
"hw_ver": { "type": "string" },
|
||||
"loader_ver": { "type": "string" },
|
||||
"product_name": { "type": "string" },
|
||||
"sn": { "type": "string" },
|
||||
"visible": { "type": "boolean" },
|
||||
"flag": { "type": "integer" }
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"sw_ver",
|
||||
"hw_ver",
|
||||
"loader_ver",
|
||||
"sn",
|
||||
"product_name",
|
||||
"visible",
|
||||
"flag"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/common/net.schema.json",
|
||||
"title": "Net",
|
||||
"description": "Network status",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"conf": {
|
||||
"type": "integer"
|
||||
},
|
||||
"info": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ip": {
|
||||
"type": "integer"
|
||||
},
|
||||
"mask": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"ip",
|
||||
"mask"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/common/nozzle_diameter.schema.json",
|
||||
"title": "NozzleDiameter",
|
||||
"description": "Common schema for nozzle diameter",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"0.2",
|
||||
"0.4",
|
||||
"0.6",
|
||||
"0.8"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/common/nozzle_type.schema.json",
|
||||
"title": "NozzleType",
|
||||
"description": "Common schema for nozzle type",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"stainless_steel",
|
||||
"hardened_steel"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/common/online.schema.json",
|
||||
"title": "Online",
|
||||
"description": "Printer online/offline status",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ahb": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"rfid": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"version": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/common/upgrade_state.schema.json",
|
||||
"title": "UpgradeState",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sequence": { "type": "string" },
|
||||
"progress": { "type": "string" },
|
||||
"status": { "type": "string", "enum": ["idle", "IDLE"]},
|
||||
"consistency_request": { "type": "boolean" },
|
||||
"dis_state": { "type": "integer" },
|
||||
"err_code": { "type": "integer" },
|
||||
"force_upgrade": { "type": "boolean" },
|
||||
"message": { "type": "string" },
|
||||
"module": { "type": "string" },
|
||||
"new_version_state": { "type": "integer" },
|
||||
"cur_state_code": { "type": "integer" },
|
||||
"idx2": { "type": "integer" },
|
||||
"new_ver_list": { "type": "array", "items": { "type": "string" } }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/common/upload.schema.json",
|
||||
"title": "Upload",
|
||||
"description": "File upload progress",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"idle",
|
||||
"IDLE"
|
||||
]
|
||||
},
|
||||
"progress": {
|
||||
"type": "integer"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file_size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"finish_size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"oss_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"progress": {
|
||||
"type": "integer"
|
||||
},
|
||||
"speed": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"idle",
|
||||
"IDLE"
|
||||
]
|
||||
},
|
||||
"task_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"time_remaining": {
|
||||
"type": "integer"
|
||||
},
|
||||
"trouble_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/common/vt_tray.schema.json",
|
||||
"title": "VtTray",
|
||||
"description": "This schema is used as Virtual Tray (external spool) or as a base for AmsTray. When empty, all values are zeroed and `tray_color` field is set to \"FFFFFF00\".",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"tag_uid": { "type": "string" },
|
||||
"tray_id_name": { "type": "string" },
|
||||
"tray_info_idx": { "type": "string" },
|
||||
"tray_type": { "type": "string" },
|
||||
"tray_sub_brands": { "type": "string" },
|
||||
"tray_color": { "$ref": "../../common/color_hex.schema.json" },
|
||||
"tray_weight": { "type": "string" },
|
||||
"tray_diameter": { "type": "string" },
|
||||
"tray_temp": { "type": "string" },
|
||||
"tray_time": { "type": "string" },
|
||||
"bed_temp_type": { "type": "string" },
|
||||
"bed_temp": { "type": "string" },
|
||||
"nozzle_temp_min": { "type": "string" },
|
||||
"nozzle_temp_max": { "type": "string" },
|
||||
"xcam_info": { "type": "string" },
|
||||
"tray_uuid": { "type": "string" },
|
||||
"remain": { "type": "integer", "minimum": 0 },
|
||||
"k": { "type": "number" },
|
||||
"n": { "type": "number" },
|
||||
"cali_idx": { "type": "number" }
|
||||
},
|
||||
"required": [
|
||||
"id", "remain", "bed_temp", "bed_temp_type",
|
||||
"nozzle_temp_max", "nozzle_temp_min", "tag_uid", "tray_color",
|
||||
"tray_diameter", "tray_id_name", "tray_info_idx", "tray_sub_brands",
|
||||
"tray_type", "tray_uuid", "tray_weight", "xcam_info", "k", "n"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/common/xcam.schema.json",
|
||||
"title": "XcamSettings",
|
||||
"description": "Camera-based AI print monitoring settings (spaghetti detection, first-layer inspection, etc.).",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allow_skip_parts": { "type": "boolean" },
|
||||
"buildplate_marker_detector": { "type": "boolean" },
|
||||
"first_layer_inspector": { "type": "boolean" },
|
||||
"halt_print_sensitivity": { "type": "string" },
|
||||
"print_halt": { "type": "boolean" },
|
||||
"printing_monitor": { "type": "boolean" },
|
||||
"spaghetti_detector": { "type": "boolean" }
|
||||
},
|
||||
"required": [
|
||||
"allow_skip_parts", "buildplate_marker_detector", "first_layer_inspector",
|
||||
"halt_print_sensitivity", "print_halt", "printing_monitor", "spaghetti_detector"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report.schema.json",
|
||||
"title": "BambuReport",
|
||||
"description": "Matches continuum-proxy's src/printer/bambu_commands::BambuReport. Note the category here is \"print\", not \"pushing\" - sending a pushall request (which does go to \"pushing\", see request.schema.json) makes the printer start/refresh the ongoing state-push stream, which itself arrives under \"print\". They're different root keys because a report category means 'what key does this arrive under', not 'what request caused it'. Each category's value is a oneOf for the same reason as request.schema.json: room for more than one shape per category without a rewrite later.",
|
||||
"type": "object",
|
||||
"minProperties": 1,
|
||||
"maxProperties": 1,
|
||||
"properties": {
|
||||
"camera": {
|
||||
"oneOf": [
|
||||
{ "$ref": "report/camera/ipcam_record_set.schema.json" },
|
||||
{ "$ref": "report/camera/ipcam_timelapse.schema.json" }
|
||||
]
|
||||
},
|
||||
"info": {
|
||||
"oneOf": [
|
||||
{ "$ref": "report/info/get_version.schema.json" }
|
||||
]
|
||||
},
|
||||
"mc_print": {
|
||||
"oneOf": [
|
||||
{ "$ref": "report/mc_print/push_info.schema.json" }
|
||||
]
|
||||
},
|
||||
"print": {
|
||||
"oneOf": [
|
||||
{ "$ref": "report/print/ams_change_filament.schema.json" },
|
||||
{ "$ref": "report/print/calibration.schema.json" },
|
||||
{ "$ref": "report/print/ams_control.schema.json" },
|
||||
{ "$ref": "report/print/ams_filament_setting.schema.json" },
|
||||
{ "$ref": "report/print/ams_get_rfid.schema.json" },
|
||||
{ "$ref": "report/print/ams_user_setting.schema.json" },
|
||||
{ "$ref": "report/print/gcode_file.schema.json" },
|
||||
{ "$ref": "report/print/gcode_line.schema.json" },
|
||||
{ "$ref": "report/print/pause.schema.json" },
|
||||
{ "$ref": "report/print/print_option.schema.json" },
|
||||
{ "$ref": "report/print/print_speed.schema.json" },
|
||||
{ "$ref": "report/print/project_file.schema.json" },
|
||||
{ "$ref": "report/print/push_status.schema.json" },
|
||||
{ "$ref": "report/print/resume.schema.json" },
|
||||
{ "$ref": "report/print/stop.schema.json" },
|
||||
{ "$ref": "report/print/unload_filament.schema.json" }
|
||||
]
|
||||
},
|
||||
"system": {
|
||||
"oneOf": [
|
||||
{ "$ref": "report/system/get_access_code.schema.json" },
|
||||
{ "$ref": "report/system/ledctrl.schema.json" },
|
||||
{ "$ref": "report/system/set_accessories.schema.json" }
|
||||
]
|
||||
},
|
||||
"upgrade": {
|
||||
"oneOf": [
|
||||
{ "$ref": "report/upgrade/consistency_confirm.schema.json" },
|
||||
{ "$ref": "report/upgrade/get_history.schema.json" },
|
||||
{ "$ref": "report/upgrade/start.schema.json" },
|
||||
{ "$ref": "report/upgrade/upgrade_confirm.schema.json" }
|
||||
]
|
||||
},
|
||||
"xcam": {
|
||||
"oneOf": [
|
||||
{ "$ref": "report/xcam/xcam_control_set.json" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/camera/ipcam_record_set.schema.json",
|
||||
"title": "IpcamRecordSetReport",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "ipcam_record_set" },
|
||||
"control": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enable",
|
||||
"ENABLE",
|
||||
"disable",
|
||||
"DISABLE"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/camera/ipcam_timelapse.schema.json",
|
||||
"title": "IpcamTimelapseReport",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "ipcam_timelapse" },
|
||||
"control": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enable",
|
||||
"ENABLE",
|
||||
"disable",
|
||||
"DISABLE"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/info/get_version.schema.json",
|
||||
"title": "GetVersionReport",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "get_version" },
|
||||
"module": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "../../common/module_info.schema.json" }
|
||||
}
|
||||
},
|
||||
"required": ["module"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/mc_print/push_info.schema.json",
|
||||
"title": "McPrintPushInfoReport",
|
||||
"description": "This message can't be requested - it's set automatically by the printer. [LINK] is for logs, [BMC] is for reported pressure advance calculated by lidar and [AMS][TASK] is per-AMS raw humidity",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "push_info" },
|
||||
"param": {
|
||||
"type": "string",
|
||||
"anyOf": [
|
||||
{ "pattern": "^\\[LINK\\] ?(?<log>.+)$" },
|
||||
{ "pattern": "^\\[BMC\\] ?M900 K(?<k>\\d+\\.\\d+), L(?<l>\\d+\\.\\d+), M(?<m>\\d+\\.\\d+)$" },
|
||||
{ "pattern": "^\\[AMS\\]\\[TASK\\] ?ams(?<ams_idx>\\d+) temp:(?<temp>\\d+\\.\\d+);humidity:(?<humidity_perc>\\d+)%;humidity_idx:(?<humidity_idx>[0-4]+)$" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["param"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/print/ams_change_filament.schema.json",
|
||||
"title": "AmsChangeFilamentReport",
|
||||
"description": "Perform filament change using AMS",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "ams_change_filament" },
|
||||
"target": {
|
||||
"type": "integer",
|
||||
"description": "Filament tray ID",
|
||||
"minimum": 0
|
||||
},
|
||||
"curr_temp": {
|
||||
"type": "integer",
|
||||
"description": "Old print temperature",
|
||||
"minimum": 0
|
||||
},
|
||||
"tar_temp": {
|
||||
"type": "integer",
|
||||
"description": "New print temperature",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"required": ["target", "curr_temp", "tar_temp"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/print/ams_control.schema.json",
|
||||
"title": "AmsControlReport",
|
||||
"description": "Control AMS Unit",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "ams_control" },
|
||||
"param": { "type": "string", "enum": ["resume", "reset", "pause"] }
|
||||
},
|
||||
"required": ["param"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/print/ams_filament_setting.schema.json",
|
||||
"title": "AmsFilamentSettingReport",
|
||||
"description": "Change AMS settings of the given unit",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "ams_filament_setting" },
|
||||
"ams_id": { "type": "integer", "minimum": 0 },
|
||||
"tray_id": { "type": "integer", "minimum": 0 },
|
||||
"tray_info_idx": { "type": "string" },
|
||||
"tray_color": { "$ref": "../../../common/color_hex.schema.json" },
|
||||
"nozzle_temp_min": { "type": "integer", "minimum": 0 },
|
||||
"nozzle_temp_max": { "type": "integer", "minimum": 0 },
|
||||
"tray_type": { "type": "string" }
|
||||
},
|
||||
"required": [
|
||||
"ams_id", "tray_id", "tray_info_idx", "tray_color",
|
||||
"nozzle_temp_min", "nozzle_temp_max", "tray_type"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/print/ams_get_rfid.schema.json",
|
||||
"title": "AmsGetRfidReport",
|
||||
"description": "Read RFID info of filament",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "ams_get_rfid" },
|
||||
"ams_id": { "type": "integer", "minimum": 0 },
|
||||
"ams_slot": { "type": "integer", "minimum": 0 }
|
||||
},
|
||||
"required": ["ams_id", "ams_slot"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/print/ams_user_setting.schema.json",
|
||||
"title": "AmsUserSettingReport",
|
||||
"description": "Change AMS settings of the given unit",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "ams_user_setting" },
|
||||
"ams_id": { "type": "integer", "minimum": 0 },
|
||||
"startup_read_option": { "type": "boolean" },
|
||||
"tray_read_option": { "type": "boolean" }
|
||||
},
|
||||
"required": ["ams_id", "startup_read_option", "tray_read_option"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/print/calibration.schema.json",
|
||||
"title": "CalibrationReport",
|
||||
"description": "The direct reply to request/print/calibration.schema.json - both BambuMQTT versions respond this way (v2 has its own identical report/print/calibration.schema.json). The printer still also sends ordinary `push_status` updates reflecting calibration progress alongside this report, e.g. `{ \"mc_print_stage\": 2, \"gcode_state\": \"RUNNING\", \"gcode_file_prepare_percent\": 100, \"subtask_name\": \"auto_cali_for_user_param.gcode\", \"gcode_file\": \"auto_cali_for_user_param.gcode\", \"print_type\": \"system\" }` - the same way any other print job in progress does, not a substitute for this report.",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "calibration" },
|
||||
"option": {
|
||||
"description": "Following bitmask is required for the `option` field: `if (lidarCalibration) bitmask |= 1; if (bedLevelling) bitmask |= 1 << 1; if (vibrationCompensation) bitmask |= 1 << 2; if (motorCancellation) bitmask |= 1 << 3;`",
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 15
|
||||
}
|
||||
},
|
||||
"required": ["option"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/print/gcode_file.schema.json",
|
||||
"title": "GcodeFileReport",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "gcode_file" },
|
||||
"param": { "description": "Full path to GCODE file in printer's FS", "type": "string" }
|
||||
},
|
||||
"required": ["param"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/print/gcode_line.schema.json",
|
||||
"title": "GcodeLineReport",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "gcode_line" },
|
||||
"param": { "description": "use \n for multiple lines", "type": "string" }
|
||||
},
|
||||
"required": ["param"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/print/pause.schema.json",
|
||||
"title": "PausePrintReport",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "pause" },
|
||||
"param": { "type": "string", "const": "" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/print/print_option.schema.json",
|
||||
"title": "PrintOptionReport",
|
||||
"description": "Modifies printer's print mode.",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "print_option" },
|
||||
"auto_recovery": { "type": "boolean" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/print/print_speed.schema.json",
|
||||
"title": "PrintSpeedReport",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "print_speed" },
|
||||
"param": {
|
||||
"description": "1 - silent (50%), 2 - standard (100%), 3 - sport (124%), 4 - ludicrous (166%)",
|
||||
"type": "string",
|
||||
"enum": ["1", "2", "3", "4"]
|
||||
}
|
||||
},
|
||||
"required": ["param"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/print/project_file.schema.json",
|
||||
"title": "PrintProjectFileReport",
|
||||
"description": "Print project file",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "project_file" },
|
||||
"param": { "type": "string" },
|
||||
"project_id": { "type": "string", "description": "\"0\" for local prints" },
|
||||
"profile_id": { "type": "string", "description": "\"0\" for local prints" },
|
||||
"task_id": { "type": "string", "description": "\"0\" for local prints" },
|
||||
"subtask_id": { "type": "string", "description": "\"0\" for local prints" },
|
||||
"subtask_name": { "type": "string", "description": "Empty string for local prints" },
|
||||
"file": { "type": "string", "description": "File name to print - may be empty string if `url` field already contains the file name" },
|
||||
"url": { "$ref": "../../../common/file_url.schema.json" },
|
||||
"md5": { "type": "string", "description": "MD5 hash of the project file. Can be empty" },
|
||||
"timelapse": { "type": "boolean" },
|
||||
"bed_type": { "$ref": "../../common/bed_type.schema.json" },
|
||||
"bed_levelling": { "type": "boolean" },
|
||||
"flow_cali": { "type": "boolean" },
|
||||
"vibration_cali": { "type": "boolean" },
|
||||
"layer_inspect": { "type": "boolean" },
|
||||
"ams_mapping": {
|
||||
"oneOf": [
|
||||
{ "$ref": "../../common/ams/ams_mapping.schema.json" },
|
||||
{ "type": "string" }
|
||||
]
|
||||
},
|
||||
"use_ams": { "type": "boolean" }
|
||||
},
|
||||
"required": ["param"],
|
||||
"anyOf": [{ "required": ["file"] }, { "required": ["url"] }]
|
||||
},
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ams_mapping": { "$ref": "../../common/ams/ams_mapping.schema.json" },
|
||||
"use_ams": true
|
||||
},
|
||||
"required": ["ams_mapping", "use_ams"]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ams_mapping": { "type": "string", "const": "" },
|
||||
"use_ams": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/print/push_status.schema.json",
|
||||
"title": "PushStatusReport",
|
||||
"description": "The ongoing print-state push (root key \"print\") - NOT a direct reply to the pushall request (that goes to \"pushing\", see request/pushing/pushall.schema.json). Sending pushall makes the printer start/refresh this stream; it isn't itself request/response, which is also why this is its own category folder rather than sitting under pushing/ - a category here is 'what root key does this arrive under', not 'what triggered it'.",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "push_status" },
|
||||
|
||||
"msg": {
|
||||
"type": "integer",
|
||||
"description": "Whether this push is the whole object or only what changed: 0 forced/full (what a pushing.pushall triggers), 1 automatic/partial. Confirmed against a real printer. A v1 consumer MUST read this - merging a partial push as if it were full leaves the picture complete, but replacing the cached object with a partial one silently unsets every field the push did not mention. It cannot be inferred any other way, because push_status does not echo sequence_id (see the module description), so there is no correlation back to the pushall that asked for it. v2 sends the whole object every time and this field is informational there."
|
||||
},
|
||||
|
||||
"upgrade_state": { "$ref": "../../common/upgrade_state.schema.json" },
|
||||
"ipcam": { "$ref": "../../common/ipcam.schema.json" },
|
||||
"upload": { "$ref": "../../common/upload.schema.json" },
|
||||
"net": { "$ref": "../../common/net.schema.json" },
|
||||
"hms": { "$ref": "../../common/hms.schema.json" },
|
||||
"online": { "$ref": "../../common/online.schema.json" },
|
||||
"ams": { "$ref": "../../common/ams/ams.schema.json" },
|
||||
"vt_tray": { "$ref": "../../common/vt_tray.schema.json" },
|
||||
"lights_report": { "$ref": "../../common/lights_report.schema.json" },
|
||||
"xcam": { "$ref": "../../common/xcam.schema.json" },
|
||||
|
||||
"nozzle_diameter": { "$ref": "../../common/nozzle_diameter.schema.json" },
|
||||
"nozzle_type": { "$ref": "../../common/nozzle_type.schema.json" },
|
||||
"nozzle_temper": { "type": "number" },
|
||||
"nozzle_target_temper": { "type": "number" },
|
||||
"bed_temper": { "type": "number" },
|
||||
"bed_target_temper": { "type": "number" },
|
||||
"chamber_temper": { "type": "number" },
|
||||
"chamber_target_temper": { "type": "number" },
|
||||
"fan_gear": { "type": "integer" },
|
||||
"heatbreak_fan_speed": { "type": "string" },
|
||||
"cooling_fan_speed": { "type": "string" },
|
||||
"big_fan1_speed": { "type": "string" },
|
||||
"big_fan2_speed": { "type": "string" },
|
||||
|
||||
"s_obj": { "type": "array", "items": { "type": "number" } },
|
||||
"filam_bak": { "type": "array", "items": {} },
|
||||
"stg": { "type": "array", "items": { "type": "number" } },
|
||||
|
||||
"mc_print_stage": { "type": "string" },
|
||||
"mc_percent": { "type": "integer" },
|
||||
"mc_remaining_time": { "type": "integer" },
|
||||
|
||||
"ams_status": { "type": "integer" },
|
||||
"ams_rfid_status": { "type": "integer" },
|
||||
"hw_switch_state": { "type": "integer" },
|
||||
"spd_mag": { "type": "integer", "minimum": 0, "maximum": 100 },
|
||||
"spd_lvl": { "type": "integer", "minimum": 1, "maximum": 4 },
|
||||
"print_error": { "type": "integer" },
|
||||
"lifecycle": { "type": "string", "enum": ["product"] },
|
||||
"wifi_signal": { "type": "string", "pattern": "^-([0-9]{1,2}|100)dBm$" },
|
||||
"gcode_state": { "type": "string", "enum": [ "idle", "IDLE", "finish", "FINISH", "running", "RUNNING" ] },
|
||||
"gcode_file_prepare_percent": { "type": "string" },
|
||||
"queue_number": { "type": "integer" },
|
||||
"queue_total": { "type": "integer" },
|
||||
"queue_est": { "type": "integer" },
|
||||
"queue_sts": { "type": "integer" },
|
||||
"project_id": { "type": "string" },
|
||||
"profile_id": { "type": "string" },
|
||||
"task_id": { "type": "string" },
|
||||
"subtask_id": { "type": "string" },
|
||||
"subtask_name": { "type": "string" },
|
||||
"gcode_file": { "type": "string" },
|
||||
"stg_cur": { "type": "integer" },
|
||||
"print_type": { "type": "string", "enum": [ "", "idle", "IDLE" ] },
|
||||
"home_flag": { "type": "integer" },
|
||||
"mc_print_line_number": { "type": "string" },
|
||||
"mc_print_sub_stage": { "type": "integer" },
|
||||
"sdcard": { "type": "boolean" },
|
||||
"force_upgrade": { "type": "boolean" },
|
||||
"mess_production_state": { "type": "string", "enum": [ "active", "inactive", "ACTIVE", "INACTIVE" ] },
|
||||
"layer_num": { "type": "integer" },
|
||||
"total_layer_num": { "type": "integer" },
|
||||
"cali_version": { "type": "integer" },
|
||||
"k": { "type": "string" },
|
||||
"flag3": { "type": "integer" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/print/resume.schema.json",
|
||||
"title": "ResumePrintReport",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "resume" },
|
||||
"param": { "type": "string", "const": "" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/print/stop.schema.json",
|
||||
"title": "StopPrintReport",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "stop" },
|
||||
"param": { "type": "string", "const": "" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/print/unload_filament.schema.json",
|
||||
"title": "UnloadFilamentReport",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "unload_filament" }
|
||||
},
|
||||
"required": ["param"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/system/get_access_code.schema.json",
|
||||
"title": "GetAccessCodeReport",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "get_access_code" },
|
||||
"access_code": { "type": "string" }
|
||||
},
|
||||
"required": ["access_code"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/system/ledctrl.schema.json",
|
||||
"title": "LedctrlReport",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "ledctrl" },
|
||||
"led_node": { "$ref": "../../common/led_node.schema.json" }
|
||||
},
|
||||
"required": ["led_node"]
|
||||
},
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"led_mode": {
|
||||
"type": "string",
|
||||
"enum": ["on", "off", "ON", "OFF"]
|
||||
}
|
||||
},
|
||||
"required": ["led_mode"]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"led_mode": {
|
||||
"type": "string",
|
||||
"enum": ["flashing", "FLASHING"]
|
||||
},
|
||||
"led_on_time": { "type": "integer", "minimum": 1 },
|
||||
"led_off_time": { "type": "integer", "minimum": 1 },
|
||||
"loop_times": { "type": "integer", "minimum": 1 },
|
||||
"interval_time": { "type": "integer", "minimum": 1 }
|
||||
},
|
||||
"required": [
|
||||
"led_mode",
|
||||
"led_on_time",
|
||||
"led_off_time",
|
||||
"loop_times",
|
||||
"interval_time"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/system/set_accessories.schema.json",
|
||||
"title": "SetAccessoriesReport",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "set_accessories" },
|
||||
"accessory_type": { "type": "string", "const": "nozzle" },
|
||||
"nozzle_diameter": {
|
||||
"$ref": "../../common/nozzle_diameter.schema.json"
|
||||
},
|
||||
"nozzle_type": { "$ref": "../../common/nozzle_type.schema.json" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/upgrade/consistency_confirm.schema.json",
|
||||
"title": "ConsistencyConfirmReport",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "consistency_confirm" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/upgrade/get_history.schema.json",
|
||||
"title": "GetUpgradeHistoryReport",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "get_history" },
|
||||
"firmware_optional": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ams": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"address": { "type": "integer" },
|
||||
"dev_model_name": { "type": "string" },
|
||||
"device_id": { "type": "string" },
|
||||
"firmware": {
|
||||
"type": ["null", "array"],
|
||||
"items": { "$ref": "../../common/firmware_details.schema.json" }
|
||||
},
|
||||
"firmware_current": {
|
||||
"type": ["null", "array"],
|
||||
"items": { "$ref": "../../common/firmware_details.schema.json" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"firmware": {
|
||||
"type": ["null", "array"],
|
||||
"items": { "$ref": "../../common/firmware_details.schema.json" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["firmware_optional"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/upgrade/start.schema.json",
|
||||
"title": "UpgradeStartReport",
|
||||
"description": "TODO: Quite possibly incorrect - validate",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "start" },
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "URL of a signed JSON with URLs to signed binaries",
|
||||
"format": "uri",
|
||||
"pattern": "^https?://"
|
||||
},
|
||||
"module": {
|
||||
"type": "string",
|
||||
"enum": ["ota", "ams"]
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "New version number that will get downloaded"
|
||||
}
|
||||
},
|
||||
"required": ["url", "module", "version"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/upgrade/upgrade_confirm.schema.json",
|
||||
"title": "UpgradeConfirmReport",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "upgrade_confirm" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/report/system/xcam_control_set.schema.json",
|
||||
"title": "XcamControlSetReport",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "xcam_control_set" },
|
||||
"control": { "type": "boolean" },
|
||||
"print_halt": { "type": "boolean" },
|
||||
"module_name": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"first_layer_inspector", "buildplate_marker_detector", "printing_monitor",
|
||||
"pileup_detector", "airprint_detector", "clump_detector", "spaghetti_detector"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["module_name", "control", "print_halt"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/request.schema.json",
|
||||
"title": "BambuRequest",
|
||||
"description": "Matches continuum-proxy's src/printer/bambu_commands::BambuRequest - one JSON key naming the category ('info', 'pushing', ...). Each category's value is a oneOf, not a single $ref: a category can have more than one possible request shape (e.g. 'info' might grow a second command besides get_version) - oneOf leaves room for that from the start instead of needing a rewrite the day it happens.",
|
||||
"type": "object",
|
||||
"minProperties": 1,
|
||||
"maxProperties": 1,
|
||||
"properties": {
|
||||
"camera": {
|
||||
"oneOf": [
|
||||
{ "$ref": "request/camera/ipcam_record_set.schema.json" },
|
||||
{ "$ref": "request/camera/ipcam_timelapse.schema.json" }
|
||||
]
|
||||
},
|
||||
"info": {
|
||||
"oneOf": [
|
||||
{ "$ref": "request/info/get_version.schema.json" }
|
||||
]
|
||||
},
|
||||
"print": {
|
||||
"oneOf": [
|
||||
{ "$ref": "request/print/ams_change_filament.schema.json" },
|
||||
{ "$ref": "request/print/ams_control.schema.json" },
|
||||
{ "$ref": "request/print/ams_filament_setting.schema.json" },
|
||||
{ "$ref": "request/print/ams_get_rfid.schema.json" },
|
||||
{ "$ref": "request/print/ams_user_setting.schema.json" },
|
||||
{ "$ref": "request/print/calibration.schema.json" },
|
||||
{ "$ref": "request/print/gcode_file.schema.json" },
|
||||
{ "$ref": "request/print/gcode_line.schema.json" },
|
||||
{ "$ref": "request/print/pause.schema.json" },
|
||||
{ "$ref": "request/print/print_option.schema.json" },
|
||||
{ "$ref": "request/print/print_speed.schema.json" },
|
||||
{ "$ref": "request/print/project_file.schema.json" },
|
||||
{ "$ref": "request/print/resume.schema.json" },
|
||||
{ "$ref": "request/print/skip_objects.schema.json" },
|
||||
{ "$ref": "request/print/stop.schema.json" },
|
||||
{ "$ref": "request/print/unload_filament.schema.json" }
|
||||
]
|
||||
},
|
||||
"system": {
|
||||
"oneOf": [
|
||||
{ "$ref": "request/system/get_access_code.schema.json" },
|
||||
{ "$ref": "request/system/ledctrl.schema.json" },
|
||||
{ "$ref": "request/system/set_accessories.schema.json" }
|
||||
]
|
||||
},
|
||||
"upgrade": {
|
||||
"oneOf": [
|
||||
{ "$ref": "request/upgrade/consistency_confirm.schema.json" },
|
||||
{ "$ref": "request/upgrade/get_history.schema.json" },
|
||||
{ "$ref": "request/upgrade/start.schema.json" },
|
||||
{ "$ref": "request/upgrade/upgrade_confirm.schema.json" }
|
||||
]
|
||||
},
|
||||
"xcam": {
|
||||
"oneOf": [
|
||||
{ "$ref": "request/xcam/xcam_control_set.json" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/request/camera/ipcam_record_set.schema.json",
|
||||
"title": "IpcamRecordSetRequest",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "ipcam_record_set" },
|
||||
"control": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enable",
|
||||
"ENABLE",
|
||||
"disable",
|
||||
"DISABLE"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/request/camera/ipcam_timelapse.schema.json",
|
||||
"title": "IpcamTimelapseRequest",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "ipcam_timelapse" },
|
||||
"control": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enable",
|
||||
"ENABLE",
|
||||
"disable",
|
||||
"DISABLE"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/request/info/get_version.schema.json",
|
||||
"title": "GetVersionRequest",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "get_version" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/request/print/ams_change_filament.schema.json",
|
||||
"title": "AmsChangeFilamentRequest",
|
||||
"description": "Perform filament change using AMS",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "ams_change_filament" },
|
||||
"target": {
|
||||
"type": "integer",
|
||||
"description": "Filament tray ID",
|
||||
"minimum": 0
|
||||
},
|
||||
"curr_temp": {
|
||||
"type": "integer",
|
||||
"description": "Old print temperature",
|
||||
"minimum": 0
|
||||
},
|
||||
"tar_temp": {
|
||||
"type": "integer",
|
||||
"description": "New print temperature",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"required": ["target", "curr_temp", "tar_temp"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/request/print/ams_control.schema.json",
|
||||
"title": "AmsControlRequest",
|
||||
"description": "Control AMS Unit",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "ams_control" },
|
||||
"param": { "type": "string", "enum": ["resume", "reset", "pause"] }
|
||||
},
|
||||
"required": ["param"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/request/print/ams_filament_setting.schema.json",
|
||||
"title": "AmsFilamentSettingRequest",
|
||||
"description": "Change AMS settings of the given unit",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "ams_filament_setting" },
|
||||
"ams_id": { "type": "integer", "minimum": 0 },
|
||||
"tray_id": { "type": "integer", "minimum": 0 },
|
||||
"tray_info_idx": { "type": "string" },
|
||||
"tray_color": { "$ref": "../../../common/color_hex.schema.json" },
|
||||
"nozzle_temp_min": { "type": "integer", "minimum": 0 },
|
||||
"nozzle_temp_max": { "type": "integer", "minimum": 0 },
|
||||
"tray_type": { "type": "string" }
|
||||
},
|
||||
"required": [
|
||||
"ams_id", "tray_id", "tray_info_idx", "tray_color",
|
||||
"nozzle_temp_min", "nozzle_temp_max", "tray_type"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/request/print/ams_get_rfid.schema.json",
|
||||
"title": "AmsGetRfidRequest",
|
||||
"description": "Read RFID info of filament",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "ams_get_rfid" },
|
||||
"ams_id": { "type": "integer", "minimum": 0 },
|
||||
"ams_slot": { "type": "integer", "minimum": 0 }
|
||||
},
|
||||
"required": ["ams_id", "ams_slot"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/request/print/ams_user_setting.schema.json",
|
||||
"title": "AmsUserSettingRequest",
|
||||
"description": "Change AMS settings of the given unit",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "ams_user_setting" },
|
||||
"ams_id": { "type": "integer", "minimum": 0 },
|
||||
"startup_read_option": { "type": "boolean" },
|
||||
"tray_read_option": { "type": "boolean" }
|
||||
},
|
||||
"required": ["ams_id", "startup_read_option", "tray_read_option"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/request/print/calibration.schema.json",
|
||||
"title": "CalibrationRequest",
|
||||
"description": "The printer responds with a dedicated `calibration` report (see request/print/calibration.schema.json's report/ counterpart) - not, as previously documented here, `push_status` in place of one. It still also sends ordinary `push_status` updates reflecting calibration progress alongside that report, e.g. `{ \"mc_print_stage\": 2, \"gcode_state\": \"RUNNING\", \"gcode_file_prepare_percent\": 100, \"subtask_name\": \"auto_cali_for_user_param.gcode\", \"gcode_file\": \"auto_cali_for_user_param.gcode\", \"print_type\": \"system\" }` - the same way any other print job in progress does, not a `calibration`-specific substitute for one.",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "calibration" },
|
||||
"option": {
|
||||
"description": "Following bitmask is required for the `option` field: `if (lidarCalibration) bitmask |= 1; if (bedLevelling) bitmask |= 1 << 1; if (vibrationCompensation) bitmask |= 1 << 2; if (motorCancellation) bitmask |= 1 << 3;`",
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 15
|
||||
}
|
||||
},
|
||||
"required": ["option"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/request/print/gcode_file.schema.json",
|
||||
"title": "GcodeFileRequest",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "gcode_file" },
|
||||
"param": { "description": "Full path to GCODE file in printer's FS", "type": "string" }
|
||||
},
|
||||
"required": ["param"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/request/print/gcode_line.schema.json",
|
||||
"title": "GcodeLineRequest",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "gcode_line" },
|
||||
"param": { "description": "use \n for multiple lines", "type": "string" }
|
||||
},
|
||||
"required": ["param"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/request/print/pause.schema.json",
|
||||
"title": "PausePrintRequest",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "pause" },
|
||||
"param": { "type": "string", "const": "" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/request/print/print_option.schema.json",
|
||||
"title": "PrintOptionRequest",
|
||||
"description": "Modifies printer's print mode.",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "print_option" },
|
||||
"auto_recovery": { "type": "boolean" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/bambulab/v1/request/print/print_speed.schema.json",
|
||||
"title": "PrintSpeedRequest",
|
||||
"allOf": [
|
||||
{ "$ref": "../../../common/envelope.schema.json" },
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": { "const": "print_speed" },
|
||||
"param": {
|
||||
"description": "1 - silent (50%), 2 - standard (100%), 3 - sport (124%), 4 - ludicrous (166%)",
|
||||
"type": "string",
|
||||
"enum": ["1", "2", "3", "4"]
|
||||
}
|
||||
},
|
||||
"required": ["param"]
|
||||
}
|
||||
]
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user