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 {