Record what a real sliced project did, and widen the start guard

A second live run, this time against a project the owner sliced in Bambu
Studio rather than a file already on the card, and through
`start_uploaded_file` itself rather than a hand-built body - so the
root-vs-cache fix is confirmed end to end. `gcode_file` in the printer's own
status came back as `cube-test-2026-10-11.gcode.3mf`, 150 layers, print_type
`local`.

It also found a defect the first run could not. The print heated to 250C,
reached 1%, and then the printer paused itself: `print_error: 50364420`
(`0x03008004`), `hms: []`, `tray_now: 255`. 255 is the external spool, which
is exactly what the hardcoded `use_ams: false` selects - so that pair
overrode the slicer's own filament choice and pointed an AMS-sliced project
at an external spool. The first run looked fine only because it was stopped
during PREPARE, before filament mattered. The doc comment said that pair was
"accepted, and the print ran"; it now says what it actually does, because
silently changing which filament a print uses is worse than not supporting
AMS at all.

The `start` guard demanded IDLE, which meant the harness refused every print
following a cancelled one - a Bambu reports an operator cancellation as
FAILED. IDLE, FINISH and FAILED are all terminal; PREPARE, RUNNING, PAUSE and
SLICING mean the machine is committed, and anything unrecognised is still
refused rather than assumed idle.

`bambu_cache_list` now prints nozzle/bed temperatures and remaining time.
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.
This commit is contained in:
2026-10-11 17:55:01 +00:00
parent 939c35fb91
commit 0077e15490
3 changed files with 44 additions and 8 deletions
+16
View File
@@ -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;
}
}
+12 -3
View File
@@ -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 {
+16 -5
View File
@@ -670,11 +670,22 @@ impl BambuPrinter {
/// - **`url` spelling.** `ftp:///<name>` - 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