PrintReport now flattens CommandEnvelope (sequence_id/command) - push_status.schema.json composes the shared envelope like every other Bambu message; the struct's old doc comment concluded otherwise from a capture that turned out to be an incomplete example (also missing dozens of other real fields), not proof the message lacks them. Added BambuMqttSession's PrintStatusCache: the in-memory print.push_status object, replaced wholesale on v2 (always full) or on a v1 message with msg != 1 (forced/full), merged field-by-field otherwise (v1's organic partial updates) via PrintReport::merge_from. Tracks last_sequence_id to warn on a gap in the organic stream. BambuPrinter gets pushall() (v1-only; pushing.pushall doesn't exist in v2's schema at all) and print_status(). Three real bugs found and fixed via live testing against an actual P1P, none of them guessable from the schema alone: - pushall's reply doesn't echo the sequence_id we send - the printer stamps its own free-running counter regardless of what triggered it. request()'s ID correlation silently can't work here; first fix attempt added request_next_of_category (wait for the next message under a category, no ID needed). - That undershot too: matching by category alone let an unrelated "print" message (that category carries many commands - gcode_line, pause, push_status, ...) satisfy the wait before the real push_status reply arrived, so pushall() returned Ok having consumed the wrong message - now keyed by (category, command). - A real ordering race: handle_incoming_report woke a waiting oneshot before updating the print_status cache, so a caller could resume and check print_status() before the write landed - pushall() returned Ok immediately followed by print_status() still showing None. Fixed by applying the cache update before sending on any matched oneshot. Superseded by real protocol knowledge before any of the above needed to ship, though: push_status carries its own msg field (1 = automatic/ partial, 0 = forced/full) - the actual, reliable, self-describing signal for which kind a message is, regardless of timing or which client (if any) triggered it. PrintStatusCache now reads that directly instead of inferring it from request correlation, and pushall() is plain fire-and- forget (publish, no wait) since there's nothing left to correlate for - confirmed live: msg reads correctly off a real P1P, and the cache populates/updates correctly with no gap warnings across several organic pushes. request_next_of_category is kept (a future case may still need "no correlation ID at all" for real), just no longer used here. Also: the poll loop now logs when it exits instead of returning silently - that silence is exactly what made a real dropped MQTT connection to a live H2C indistinguishable from "nothing happening" during testing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MWQVtBGxHYLkVw2kBupd5g
62 lines
3.5 KiB
Rust
62 lines
3.5 KiB
Rust
//! Run with: cargo run --example bambu_commands_demo
|
|
//!
|
|
//! Round-trips both commands against the exact JSON from Bambu's real
|
|
//! protocol - proves the plain hand-written struct + serde approach
|
|
//! matches byte for byte, same as the proto-based version did, with none
|
|
//! of the build.rs ceremony.
|
|
|
|
use continuum_proxy::printer::{CommandEnvelope, GetVersionRequest, GetVersionResponse, PrintReport, PushallRequest};
|
|
|
|
fn main() {
|
|
// --- get_version request ---
|
|
let req = GetVersionRequest {
|
|
envelope: CommandEnvelope { sequence_id: "0".to_string(), command: "get_version".to_string() },
|
|
};
|
|
let actual = serde_json::to_string(&req).unwrap();
|
|
let expected = r#"{"sequence_id":"0","command":"get_version"}"#;
|
|
println!("get_version request: {actual}");
|
|
println!("matches Bambu's shape: {}\n", actual == expected);
|
|
|
|
// --- get_version response, deserialized from Bambu's real payload ---
|
|
let real_response = r#"{"command":"get_version","module":[{"hw_ver":"","name":"ota","sn":"","sw_ver":"01.01.01.00"},{"hw_ver":"","name":"xm","sn":"","sw_ver":"00.00.00.00"}],"sequence_id":"0"}"#;
|
|
let parsed: GetVersionResponse = serde_json::from_str(real_response).unwrap();
|
|
println!("get_version response parsed fine, no result/reason present: {parsed:#?}\n");
|
|
|
|
// --- pushall request ---
|
|
let pushall = PushallRequest {
|
|
envelope: CommandEnvelope { sequence_id: "0".to_string(), command: "pushall".to_string() },
|
|
version: 1,
|
|
push_target: 1,
|
|
};
|
|
let actual = serde_json::to_string(&pushall).unwrap();
|
|
let expected = r#"{"sequence_id":"0","command":"pushall","version":1,"push_target":1}"#;
|
|
println!("pushall request: {actual}");
|
|
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. sequence_id/command added
|
|
// by hand here (the original real capture this was built from was
|
|
// itself a truncated example that happened to omit them, along with
|
|
// dozens of other real fields the schema defines - not proof the real
|
|
// message lacks them, see this struct's own doc comment).
|
|
let real_print_report = r#"{
|
|
"sequence_id": "1",
|
|
"command": "push_status",
|
|
"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:#?}");
|
|
}
|