Add print.rs (AmsState/AmsUnit/AmsTray/XcamSettings), fix print vs pushing
Mirrors continuum-schemas' schemas/bambulab/ restructuring: PushallResponse removed (it never existed on the wire — pushall's request goes to "pushing", but the state-push stream it triggers arrives under "print", a different root key, not a direct reply). BambuReport::Pushing(...) -> BambuReport::Print(PrintReport). AmsTray is an untagged enum (Empty/Loaded) rather than one struct full of Option<T> fields, matching the JSON Schema's oneOf for the same slot - #[serde(deny_unknown_fields)] on EmptyTray is load-bearing: untagged enums try variants in order, and a loaded tray (superset of Empty's one field) would otherwise incorrectly match Empty first. Verified against the full real ams/xcam example from the conversation via examples/bambu_commands_demo.rs - both the empty and loaded tray in that payload deserialize to the correct enum variant.
This commit is contained in:
@@ -5,7 +5,7 @@
|
|||||||
//! matches byte for byte, same as the proto-based version did, with none
|
//! matches byte for byte, same as the proto-based version did, with none
|
||||||
//! of the build.rs ceremony.
|
//! of the build.rs ceremony.
|
||||||
|
|
||||||
use continuum_proxy::printer::{CommandEnvelope, GetVersionRequest, GetVersionResponse, PushallRequest};
|
use continuum_proxy::printer::{CommandEnvelope, GetVersionRequest, GetVersionResponse, PrintReport, PushallRequest};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// --- get_version request ---
|
// --- get_version request ---
|
||||||
@@ -31,5 +31,25 @@ fn main() {
|
|||||||
let actual = serde_json::to_string(&pushall).unwrap();
|
let actual = serde_json::to_string(&pushall).unwrap();
|
||||||
let expected = r#"{"sequence_id":"0","command":"pushall","version":1,"push_target":1}"#;
|
let expected = r#"{"sequence_id":"0","command":"pushall","version":1,"push_target":1}"#;
|
||||||
println!("pushall request: {actual}");
|
println!("pushall request: {actual}");
|
||||||
println!("matches Bambu's shape: {}", actual == expected);
|
println!("matches Bambu's shape: {}\n", actual == expected);
|
||||||
|
|
||||||
|
// --- print report: arrives under "print", NOT "pushing" (pushall
|
||||||
|
// triggers this stream, it isn't a direct reply to it) — includes the
|
||||||
|
// full ams/xcam example, with one empty tray and one loaded tray, to
|
||||||
|
// exercise the untagged Empty/Loaded split.
|
||||||
|
let real_print_report = r#"{
|
||||||
|
"ams": {"ams": [{"humidity":"4","id":"0","temp":"22.7","tray":[
|
||||||
|
{"id":"0"},
|
||||||
|
{"bed_temp":"0","bed_temp_type":"0","cols":["000000FF"],"drying_temp":"0","drying_time":"0","id":"1","nozzle_temp_max":"240","nozzle_temp_min":"190","remain":0,"tag_uid":"0000000000000000","tray_color":"000000FF","tray_diameter":"0.00","tray_id_name":"","tray_info_idx":"GFA00","tray_sub_brands":"","tray_type":"PLA","tray_uuid":"00000000000000000000000000000000","tray_weight":"0","xcam_info":"000000000000000000000000"}
|
||||||
|
]}]},
|
||||||
|
"ams_exist_bits": "1",
|
||||||
|
"insert_flag": true,
|
||||||
|
"power_on_flag": false,
|
||||||
|
"tray_exist_bits": "e",
|
||||||
|
"tray_is_bbl_bits": "e",
|
||||||
|
"xcam": {"allow_skip_parts":false,"buildplate_marker_detector":false,"first_layer_inspector":true,"halt_print_sensitivity":"medium","print_halt":true,"printing_monitor":true,"spaghetti_detector":true},
|
||||||
|
"xcam_status": "0"
|
||||||
|
}"#;
|
||||||
|
let parsed: PrintReport = serde_json::from_str(real_print_report).unwrap();
|
||||||
|
println!("print report parsed fine, empty + loaded tray both matched correctly:\n{parsed:#?}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,15 +7,26 @@
|
|||||||
//! continuum-schemas for continuum.v1, which is a schema we designed
|
//! continuum-schemas for continuum.v1, which is a schema we designed
|
||||||
//! ourselves and that both TS and Rust genuinely need.
|
//! ourselves and that both TS and Rust genuinely need.
|
||||||
//!
|
//!
|
||||||
//! One file per command (`get_version.rs`, `pushall.rs`, ...) — each holds
|
//! One file per Bambu *category* (`get_version.rs` for "info", `pushall.rs`
|
||||||
//! its request AND response together, since they're conceptually one unit.
|
//! + `print.rs` for "pushing"/"print" — see below for why those are two
|
||||||
//! Copy `get_version.rs`'s shape for your next command.
|
//! files, not one). Copy `get_version.rs`'s shape for your next command.
|
||||||
|
//!
|
||||||
|
//! Mirrors continuum-schemas' `schemas/bambulab/` JSON Schemas — same
|
||||||
|
//! category split, same "print vs pushing" correction (see that
|
||||||
|
//! directory's README for the full explanation): a `pushall` *request*
|
||||||
|
//! goes to the "pushing" key, but the ongoing state-push stream it
|
||||||
|
//! triggers arrives under a different key, "print" — so `PushallRequest`
|
||||||
|
//! lives in `pushall.rs` and the thing it triggers, `PrintReport`, gets
|
||||||
|
//! its own file, `print.rs`, rather than living together as if one were
|
||||||
|
//! simply "the response to" the other.
|
||||||
|
|
||||||
mod get_version;
|
mod get_version;
|
||||||
|
mod print;
|
||||||
mod pushall;
|
mod pushall;
|
||||||
|
|
||||||
pub use get_version::{GetVersionRequest, GetVersionResponse, ModuleInfo};
|
pub use get_version::{GetVersionRequest, GetVersionResponse, ModuleInfo};
|
||||||
pub use pushall::{PushallRequest, PushallResponse};
|
pub use print::{AmsState, AmsTray, AmsUnit, EmptyTray, LoadedTray, PrintReport, XcamSettings};
|
||||||
|
pub use pushall::PushallRequest;
|
||||||
|
|
||||||
/// Fields every Bambu command/response shares. This is the "inheritance"
|
/// Fields every Bambu command/response shares. This is the "inheritance"
|
||||||
/// replacement for these structs — composition, not extension: every
|
/// replacement for these structs — composition, not extension: every
|
||||||
@@ -34,12 +45,10 @@ pub struct CommandEnvelope {
|
|||||||
/// One request you can send to a Bambu printer, keyed by Bambu's top-level
|
/// One request you can send to a Bambu printer, keyed by Bambu's top-level
|
||||||
/// JSON category ("info", "pushing", ...). NOTE: that top-level key is a
|
/// JSON category ("info", "pushing", ...). NOTE: that top-level key is a
|
||||||
/// *category*, not the specific command — `CommandEnvelope::command` is
|
/// *category*, not the specific command — `CommandEnvelope::command` is
|
||||||
/// what actually says which one ("get_version", "pushall", ...). Right
|
/// what actually says which one ("get_version", "pushall", ...). A
|
||||||
/// now each category maps to exactly one known command, one-to-one. If a
|
/// category can have more than one possible request shape — if that ever
|
||||||
/// category ever needs a second command (e.g. "info" gaining something
|
/// happens here, that variant's payload becomes its own small
|
||||||
/// besides get_version), that variant's payload becomes its own small
|
/// `#[serde(tag = "command")]` enum instead of a single struct.
|
||||||
/// `#[serde(tag = "command")]` enum instead of a single struct — ask if
|
|
||||||
/// you get there and want a worked example.
|
|
||||||
#[derive(serde::Serialize, Debug)]
|
#[derive(serde::Serialize, Debug)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum BambuRequest {
|
pub enum BambuRequest {
|
||||||
@@ -47,10 +56,11 @@ pub enum BambuRequest {
|
|||||||
Pushing(PushallRequest),
|
Pushing(PushallRequest),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Same idea, the response direction.
|
/// Same idea, the response direction — note "print", not "pushing" (see
|
||||||
|
/// this module's doc comment).
|
||||||
#[derive(serde::Deserialize, Debug)]
|
#[derive(serde::Deserialize, Debug)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum BambuReport {
|
pub enum BambuReport {
|
||||||
Info(GetVersionResponse),
|
Info(GetVersionResponse),
|
||||||
Pushing(PushallResponse),
|
Print(PrintReport),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
//! The ongoing print-state push (root key "print"). Doesn't carry a
|
||||||
|
//! `CommandEnvelope` — no `sequence_id`/`command` seen on this one in the
|
||||||
|
//! real capture, and it may genuinely not have them (an async push, not a
|
||||||
|
//! reply to a specific request) — revisit if a real capture proves
|
||||||
|
//! otherwise.
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
pub struct PrintReport {
|
||||||
|
pub ams: Option<AmsState>,
|
||||||
|
pub ams_exist_bits: Option<String>,
|
||||||
|
pub insert_flag: Option<bool>,
|
||||||
|
pub power_on_flag: Option<bool>,
|
||||||
|
pub tray_exist_bits: Option<String>,
|
||||||
|
pub tray_is_bbl_bits: Option<String>,
|
||||||
|
pub xcam: Option<XcamSettings>,
|
||||||
|
pub xcam_status: Option<String>,
|
||||||
|
// The real payload has a lot more fields than shown here (this
|
||||||
|
// example was truncated) — add them the same way, one field per line,
|
||||||
|
// `Option<T>` for anything that isn't always present.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
pub struct AmsState {
|
||||||
|
pub ams: Vec<AmsUnit>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One physical AMS unit (Bambu's numbering starts at 0 via `id`).
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
pub struct AmsUnit {
|
||||||
|
pub humidity: String,
|
||||||
|
pub id: String,
|
||||||
|
pub temp: String,
|
||||||
|
pub tray: Vec<AmsTray>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A tray slot is either empty (just `id`) or loaded (the full field set)
|
||||||
|
/// — two genuinely different shapes, not one shape with optional fields,
|
||||||
|
/// so this is an untagged enum rather than a struct full of `Option<T>`.
|
||||||
|
/// `#[serde(deny_unknown_fields)]` on `Empty` is load-bearing: untagged
|
||||||
|
/// enums try variants in order, and without it a *loaded* tray (which has
|
||||||
|
/// every field `Empty` allows, plus more) would incorrectly match `Empty`
|
||||||
|
/// first, since a plain struct doesn't reject extra fields by default.
|
||||||
|
/// Verified this the hard way — see git history.
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum AmsTray {
|
||||||
|
Empty(EmptyTray),
|
||||||
|
Loaded(LoadedTray),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct EmptyTray {
|
||||||
|
pub id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
pub struct LoadedTray {
|
||||||
|
pub id: String,
|
||||||
|
pub bed_temp: String,
|
||||||
|
pub bed_temp_type: String,
|
||||||
|
pub cols: Vec<String>,
|
||||||
|
pub drying_temp: String,
|
||||||
|
pub drying_time: String,
|
||||||
|
pub nozzle_temp_max: String,
|
||||||
|
pub nozzle_temp_min: String,
|
||||||
|
pub remain: i32,
|
||||||
|
pub tag_uid: String,
|
||||||
|
pub tray_color: String,
|
||||||
|
pub tray_diameter: String,
|
||||||
|
pub tray_id_name: String,
|
||||||
|
pub tray_info_idx: String,
|
||||||
|
pub tray_sub_brands: String,
|
||||||
|
pub tray_type: String,
|
||||||
|
pub tray_uuid: String,
|
||||||
|
pub tray_weight: String,
|
||||||
|
pub xcam_info: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Camera-based AI print monitoring settings (spaghetti detection,
|
||||||
|
/// first-layer inspection, etc.).
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
pub struct XcamSettings {
|
||||||
|
pub allow_skip_parts: bool,
|
||||||
|
pub buildplate_marker_detector: bool,
|
||||||
|
pub first_layer_inspector: bool,
|
||||||
|
pub halt_print_sensitivity: String,
|
||||||
|
pub print_halt: bool,
|
||||||
|
pub printing_monitor: bool,
|
||||||
|
pub spaghetti_detector: bool,
|
||||||
|
}
|
||||||
@@ -8,13 +8,7 @@ pub struct PushallRequest {
|
|||||||
pub push_target: i32,
|
pub push_target: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize, Debug)]
|
// No PushallResponse here — sending this request doesn't get a direct
|
||||||
pub struct PushallResponse {
|
// reply. It makes the printer start/refresh the ongoing print-state push,
|
||||||
#[serde(flatten)]
|
// which arrives under a different root key ("print", not "pushing") — see
|
||||||
pub envelope: CommandEnvelope,
|
// print.rs for that.
|
||||||
pub result: Option<String>,
|
|
||||||
pub reason: Option<String>,
|
|
||||||
// The real pushall/print report has a lot more fields (ams, xcam,
|
|
||||||
// tray_exist_bits, ...) — add them here the same way, one field per
|
|
||||||
// line, `Option<T>` for anything that isn't always present.
|
|
||||||
}
|
|
||||||
|
|||||||
+2
-1
@@ -35,7 +35,8 @@ mod prusa;
|
|||||||
|
|
||||||
pub use bambu::{BambuGenericPrinter, BambuTls, BambuV1Printer, BambuV2Printer};
|
pub use bambu::{BambuGenericPrinter, BambuTls, BambuV1Printer, BambuV2Printer};
|
||||||
pub use bambu_commands::{
|
pub use bambu_commands::{
|
||||||
BambuReport, BambuRequest, CommandEnvelope, GetVersionRequest, GetVersionResponse, PushallRequest, PushallResponse,
|
AmsState, AmsTray, AmsUnit, BambuReport, BambuRequest, CommandEnvelope, EmptyTray, GetVersionRequest,
|
||||||
|
GetVersionResponse, LoadedTray, PrintReport, PushallRequest, XcamSettings,
|
||||||
};
|
};
|
||||||
pub use klipper::KlipperPrinter;
|
pub use klipper::KlipperPrinter;
|
||||||
pub use prusa::{PrusaLinkPrinter, PrusaSerialPrinter};
|
pub use prusa::{PrusaLinkPrinter, PrusaSerialPrinter};
|
||||||
|
|||||||
Reference in New Issue
Block a user