//! The ongoing print-state push (root key "print"). The one real capture //! this struct was originally built from didn't show `sequence_id`/ //! `command` - turned out to be because that capture was itself a //! truncated example (it's also missing dozens of other fields the real //! schema defines), not because the message genuinely lacks them: //! `push_status.schema.json` composes the shared envelope schema like //! every other Bambu message, so it does carry `sequence_id` - which //! matters a lot, see `BambuMqttSession`'s doc comment on how it's used //! both to correlate a `pushall` reply and to detect a gap in the //! ongoing organic stream. use super::CommandEnvelope; #[derive(serde::Deserialize, Debug, Clone)] pub struct PrintReport { #[serde(flatten)] pub envelope: CommandEnvelope, /// BambuMQTT v1 only: `1` on an automatic/organic push (a partial /// update - only the fields that changed), `0` on a forced one (a /// `pushing.pushall`-triggered reply - the full object). This is the /// actual, reliable signal for which kind a given message is - /// confirmed against a real printer, unlike trying to infer it from /// request/reply timing (see `BambuMqttSession`'s `PrintStatusCache`, /// which used to try that and got it wrong twice). Not in /// `push_status.schema.json` yet - add it there too when you're back /// in continuum-schemas. pub msg: Option, pub ams: Option, pub ams_exist_bits: Option, pub insert_flag: Option, pub power_on_flag: Option, pub tray_exist_bits: Option, pub tray_is_bbl_bits: Option, pub xcam: Option, pub xcam_status: Option, // 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` for anything that isn't always present. Remember to // also add each new field to `merge_from` below. } impl PrintReport { /// Merges `newer`'s fields into `self`, in place - BambuMQTT v1's /// organic `push_status` pushes only carry the fields that changed, /// so any field `newer` doesn't have (`None`) leaves `self`'s /// existing value alone instead of clobbering it. Not used for v2 /// (always a full object) or for a `pushall`-triggered reply on /// either version (also guaranteed full) - see `BambuMqttSession`. pub fn merge_from(&mut self, newer: PrintReport) { self.envelope = newer.envelope; self.msg = newer.msg; if newer.ams.is_some() { self.ams = newer.ams; } if newer.ams_exist_bits.is_some() { self.ams_exist_bits = newer.ams_exist_bits; } if newer.insert_flag.is_some() { self.insert_flag = newer.insert_flag; } if newer.power_on_flag.is_some() { self.power_on_flag = newer.power_on_flag; } if newer.tray_exist_bits.is_some() { self.tray_exist_bits = newer.tray_exist_bits; } if newer.tray_is_bbl_bits.is_some() { self.tray_is_bbl_bits = newer.tray_is_bbl_bits; } if newer.xcam.is_some() { self.xcam = newer.xcam; } if newer.xcam_status.is_some() { self.xcam_status = newer.xcam_status; } } } /// 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, Clone)] pub struct AmsState { pub ams: Vec, } /// One physical AMS unit (Bambu's numbering starts at 0 via `id`). #[derive(serde::Deserialize, Debug, Clone)] pub struct AmsUnit { pub humidity: String, pub id: String, pub temp: String, pub tray: Vec, } /// 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`. /// `#[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, Clone)] #[serde(untagged)] pub enum AmsTray { Empty(EmptyTray), Loaded(LoadedTray), } #[derive(serde::Deserialize, Debug, Clone)] #[serde(deny_unknown_fields)] pub struct EmptyTray { pub id: String, } #[derive(serde::Deserialize, Debug, Clone)] pub struct LoadedTray { pub id: String, pub bed_temp: String, pub bed_temp_type: String, pub cols: Vec, 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, Clone)] 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, }