Commit Graph
5 Commits
Author SHA1 Message Date
octoturgeandClaude Opus 5 3d907d0443 Confirm the light and the upload on a real P1P
Four things were shipped unverified because the printer was off when they
were written. Three are now confirmed first-hand (P1P, 2026-10-15), and one
is settled in the opposite direction from what was assumed.

**`set_light` had never once reached a printer**, and `completion::run`
blinks a finished print's chamber light with it. It works - but "accepted" is
not "it changed": `ledctrl` answers `result: "success"` as soon as the
command parses, exactly like the print-control verbs. So `lights_report` is
now modelled on `PrintReport`, which is the only read-back there is, and the
probe polls it rather than printing a reassuring message. Setting the light
on and off reads `[{"node":"chamber_light","mode":"on"}]` / `"off"` back,
about 3s later. The field was observed on v1 even though only the v2 schema
declares it.

**The blink itself works at the rate it ships at.** `completion::celebrate`
is now public so the probe runs the real function instead of a copy: twenty
`ledctrl` commands half a second apart, which a printer is entitled to
refuse. None were refused, it returned after 10.35s, and the light ended
`off` as specified - a blink that leaves the light on would look like a
printer still working.

**The firmware's own `flashing` mode is not the better option.** It is
accepted, and the reply echoes all four parameters back
(`led_on_time`, `led_off_time`, `loop_times`, `interval_time`), so the
printer does parse them. But `lights_report` never reports `flashing` - it
stayed on the previous mode for 12s of polling. A firmware blink is therefore
unobservable: nothing can confirm it happened or tell when it ended, while
the self-driven blink is built from a call that reads back. That is a better
reason to keep `celebrate` than the one in the comment it replaces, which was
that `loop_times` semantics were unconfirmed. Whether the light physically
blinks is still unwitnessed - nobody was watching the machine.

**Multi-MB FTPS upload works, and is slow.** A 4.96 MB project archive went
up with an exact byte count and came back byte-identical (sha256). But it
took 31.3s - about 0.2 MiB/s - so the 62 MB project already on that card
would take six and a half minutes. Anything that uploads a job needs a
timeout budgeted in minutes, not seconds. The claim in `download`'s comment
that these files are "a few hundred KB" is gone; the card disproves it.

Also adds `delete`, because the probe left a 5 MB file on a real SD card and
there was no way to remove it. One explicit path, no globbing: the convenient
version of this deletes something somebody printed from last week. Used to
clean up, and the card is back as it was.

New probe steps: `light on|off|flash`, `blink`, `upload <local> [name]`,
`delete <path>`. 51 tests still pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-15 22:23:41 +00:00
octoturgeandClaude Opus 5 6766ebbe80 Clear the clippy warnings, except the two that are not cosmetic
Down from 29 warnings to 2, in the spirit of 4ab95ff.

The doc warnings were all one mistake made repeatedly: a paragraph wrapped so
that a line *began* with `- ` or `+ `, which rustdoc reads as a markdown list
marker and the following lines as unindented continuations. The prose always
meant an em-dash. Fixed by moving the dash to the end of the previous line and
re-wrapping; not one word changed, which is the point - these comments record
things confirmed against real printers and are worth more than the warning.
The genuine lists just had their continuations de-indented and stayed lists.

The code ones are `match x { V(v) => .., _ => {} }` to `if let`, and nested
`if let Some(x) = .. { if cond { .. } }` to a let-chain. None had an `else`,
so each is the same control flow written once instead of twice.

Two warnings are left deliberately, because fixing them is not cosmetic:
`AmsTray` (544 bytes against 32) and `BambuReport` (984 against 120) both have
one variant far larger than the rest. The fix clippy wants is a `Box`, which
changes how every reader of those enums is written and puts an allocation on
the push_status path that currently has none. Worth measuring before doing,
not worth doing to silence a warning.

51 tests still pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-15 21:43:06 +00:00
octoturgeandClaude Opus 5 7d68e8e069 Make the filament source explicit, and print from the AMS
`start_uploaded_file` hardcoded `use_ams: false` / `ams_mapping: ""`, which
silently overrode whatever the slicer had chosen. On a P1P with an AMS and a
project sliced for it, that is not a missing feature - it points the print at
the external spool. Confirmed on 2026-10-11: the print started, heated to
250C, reached 1%, then the printer paused itself with `print_error: 50364420`
and `tray_now: 255`. Nothing in the command's reply said so; `result` was
`success`, because the command had been accepted.

So `FilamentSource` is now a required argument with no default, because there
is no safe default - picking wrong draws the wrong filament or none. The
`ExternalSpool` variant sends the empty *string* the schema's trailing
`oneOf` demands, and `ams` takes slots per project filament index and pads to
every tray the printer has, per the padding rule in
`schemas/bambulab/v1/common/ams/ams_mapping.schema.json`. It refuses an empty
mapping (padding `[]` would mean `use_ams: true` with nothing mapped), one
longer than the printer has trays, and any negative that is not -1.

Confirmed live with the real mapping: `ams_mapping: [3,-1,-1,-1,-1]`,
`use_ams: true` took the printer to RUNNING, `tray_now` became 3 - the slot
the project was sliced for - and it ran past 1% to 13% with `print_error: 0`,
where the external-spool attempt had paused.

Finding the mapping needed reading the project back off the card, so FTPS
gains `download`/`download_file` and the probe a `fetch` step. The archive's
`Metadata/slice_info.config` lists each plate's filaments with
`tray_info_idx` and colour - here one PETG GFG99 at `#2850E0`, matching AMS
slot 3's `2850E0FF` - and `Metadata/plate_1.gcode` is present, which also
confirms the hardcoded `param`.

`upload_project_file` is left on the external spool and is the one remaining
place that can pick wrong, because it is vendor-generic and an AMS mapping is
a Bambu concept. It is unreachable - there is no start-print command on the
uplink - and the fix is for it to derive the mapping from the `.3mf` it
already holds, which needs a zip reader and so a dependency decision.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-11 22:35:51 +00:00
octoturgeandClaude Opus 5 79df3e7153 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.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-11 17:55:01 +00:00
octoturgeandClaude Opus 5 4d644937af Confirm the print path on a real P1P, and stop discarding its answer
Print control (pause/resume/stop) and print start are now both confirmed
against the P1P on the LAN, and doing that turned up three real defects.

`start_uploaded_file` published fire-and-forget, so the printer's reply was
thrown away. Asking it to print a file it does not have returned `Ok(())`,
and watching `print.push_status` could not tell that apart from a successful
start, since both leave the machine IDLE for a while. The printer does in
fact answer - `{"command":"gcode_file","param":"...","reason":"error
string","result":"fail"}` - and it echoes the `sequence_id` we send, so
`request` correlates it. Both this and `control_print` now go out as
correlated requests and run the reply through `print_command_outcome`.

The upload and the print command disagreed about where files live. `upload`
cwd'd into `cache/` while the printable projects are in the card's root: the
root holds 43 `<name>.gcode.3mf` archives, `cache/` holds the printer's own
extracted `<project>_plate_N.gcode` working copies with `.gcode.bbl`
sidecars. Those extracted copies are not independently printable -
`gcode_file` with `param: "/cache/Cube_plate_1.gcode"` was rejected for a
file the FTPS listing shows is right there - while `project_file` with
`url: "ftp:///Cube.gcode.3mf"` started a print. So the destination is now a
parameter and both halves point at `SD_ROOT`.

What the hardware settled, recorded in the doc comments: the `ftp:///` url
spelling is right, `param: "Metadata/plate_1.gcode"` works for a
single-plate project, `use_ams: false` + `ams_mapping: ""` is accepted, and
a stop during PREPARE/RUNNING moves the printer to FAILED, which is how
Bambu reports an operator cancellation. What it did not settle: all three
control verbs answer `result: "success"` on an *idle* printer, so the reply
means "accepted", not "the state changed" - callers still have to read
`gcode_state` back.

`PrinterCommand::ControlPrint` makes this reachable from the cloud at all;
until now pause/resume/stop existed in the proxy with no way to send them.
Still no start-print variant, now for a different reason: the path around it
is missing, not the hardware proof.

Also: `list_dir` for any directory (how the layout above was found, without
sending a print command to guess), `request_raw` for probing an unconfirmed
command shape, and two examples - `bambu_cache_list` (read-only) and
`bambu_print_probe` (gated steps, `start` needing both IDLE and
--yes-start-a-real-print).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-11 17:38:27 +00:00