# 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. ## 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//` or `report//` 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/` is the correct Gitea pattern, not `raw/` or `raw//`), 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"}} ) ```