diff --git a/examples/bambu_cache_list.rs b/examples/bambu_cache_list.rs index b3b34d2..03905af 100644 --- a/examples/bambu_cache_list.rs +++ b/examples/bambu_cache_list.rs @@ -63,6 +63,22 @@ async fn main() -> anyhow::Result<()> { report.total_layer_num.unwrap_or_default() ); println!(" percent: {}", report.mc_percent.unwrap_or_default()); + // Temperatures and remaining time, because state plus layer + // number cannot tell a print that is working from one that is + // stalled: a P1P sits at layer 0 for minutes during bed + // levelling and purge, and the only thing moving in that + // window is the heat. + println!( + " nozzle: {:.1} -> {:.1} C", + report.nozzle_temper.unwrap_or_default(), + report.nozzle_target_temper.unwrap_or_default() + ); + println!( + " bed: {:.1} -> {:.1} C", + report.bed_temper.unwrap_or_default(), + report.bed_target_temper.unwrap_or_default() + ); + println!(" remaining: {} min", report.mc_remaining_time.unwrap_or_default()); break; } } diff --git a/examples/bambu_print_probe.rs b/examples/bambu_print_probe.rs index 8d9fd82..8d4ead7 100644 --- a/examples/bambu_print_probe.rs +++ b/examples/bambu_print_probe.rs @@ -118,10 +118,19 @@ async fn main() -> anyhow::Result<()> { let Some(file) = args.get(3).filter(|a| !a.starts_with("--")).cloned() else { anyhow::bail!("`start` needs a file name that is already in cache/ - run `status` to list them"); }; - if state != "IDLE" { + // The guard's job is "don't start a print on a machine that may + // already be working", and IDLE is not the only state that + // satisfies it. FINISH and FAILED are both terminal - FAILED is + // what a Bambu reports after an operator cancellation, so + // demanding IDLE made the harness refuse every print following a + // cancelled one, which is the normal case when testing. PREPARE, + // RUNNING, PAUSE and SLICING all mean the machine is committed, + // and anything unrecognised is refused rather than assumed idle. + const READY_TO_START: [&str; 3] = ["IDLE", "FINISH", "FAILED"]; + if !READY_TO_START.contains(&state.as_str()) { anyhow::bail!( - "printer reports '{state}', not IDLE - refusing to start a print on a machine \ - that may already be working" + "printer reports '{state}', which is not one of {READY_TO_START:?} - \ + refusing to start a print on a machine that may already be working" ); } if !confirmed { diff --git a/src/printer/bambu.rs b/src/printer/bambu.rs index b0d8789..b22c960 100644 --- a/src/printer/bambu.rs +++ b/src/printer/bambu.rs @@ -670,11 +670,22 @@ impl BambuPrinter { /// - **`url` spelling.** `ftp:///` - empty host, triple slash, /// resolved against the card's root - is accepted. `file:///` was /// never needed and remains untested. - /// - **`use_ams: false` / `ams_mapping: ""`.** Accepted, and the print - /// ran. Note the limit of that evidence: the print was stopped during - /// `PREPARE`/early `RUNNING`, so the pair was accepted but filament - /// was never actually drawn. Real AMS support still means filling - /// both fields together, not flipping the flag. + /// - **`use_ams: false` / `ams_mapping: ""`.** Accepted by the printer - + /// and *wrong* on an AMS-equipped machine. Confirmed on a second run + /// the same day, against a project the owner had sliced in Bambu + /// Studio: the print started, heated to 250C, reached 1% and then the + /// printer **paused itself** with `print_error: 50364420` + /// (`0x03008004`), `hms: []`, `tray_now: 255`. 255 is the external + /// spool, which is what `use_ams: false` selects - so this hardcoded + /// pair overrode the slicer's own filament choice and pointed a + /// project sliced for the AMS at an external spool instead. + /// + /// So this is not merely "the only case wired up", as this comment + /// used to say: it silently changes which filament a print uses. + /// Until it is a real parameter, `start_uploaded_file` can only be + /// trusted for projects that genuinely print from the external spool. + /// Fixing it means reading the plate's filament mapping out of the + /// `.3mf` and filling `use_ams` and `ams_mapping` together. /// /// Still a guess: **`param` is hardcoded to `Metadata/plate_1.gcode`**, /// i.e. always the first plate. Correct for the single-plate slice this