Skip the manual K calibration the way the automatic one is skipped

Bambuddy already recognises the printer's automatic pressure-advance run,
auto_pa_line_calib_mode, and leaves no archive and sends no notification
for it. Started by hand rather than automatically before a print, the
same calibration reports under a different name: it prints a pattern
where the automatic one prints a line, and carries no auto_ prefix, so
pa_pattern_calib_mode matched nothing.

It arrives exactly the way the automatic one does -- a bare subtask name
with no /usr/ path -- so it hit the same outcome the module was written
to prevent: an FTP sweep for a 3MF that cannot exist, six candidate names
across five directories with retries, and then a no-3MF archive named
after the calibration, on a printer in the middle of calibrating.

One entry on INTERNAL_JOB_NAMES covers it. That set is the single place
both the print-start and print-complete callbacks consult, so archiving,
the 3MF sweep and the notifications are all handled by the one line.

Matching stays exact after normalising path, suffix and case. The
negative cases are extended alongside the positive ones, so a file
somebody deliberately named pa_pattern_calib_mode_v2.3mf is still
archived as the print it is.

The manual PA *line* method, if it reports its own name, is not covered
here -- the list is deliberately limited to names that have actually been
observed rather than ones that seem likely.
This commit is contained in:
maziggy
2026-08-28 12:24:39 +02:00
parent 0d21239e18
commit a4a1f4c58b
3 changed files with 28 additions and 0 deletions
+1
View File
@@ -25,6 +25,7 @@ All notable changes to Bambuddy will be documented in this file.
- **The Windows installer build is split in two so a signing request can wait for a human (SignPath Foundation)** — Release tags are Authenticode-signed through the SignPath Foundation OSS programme, and the production certificate does not sign on demand the way the self-signed test certificate does: every request has to be approved by hand in the SignPath UI, because the Foundation verifies what is being signed and which build it came from. The submitting action waits for that approval with a default timeout of 600 seconds, which is ample when the test policy approves automatically in seconds and far too short once the wait is a person noticing a tag went out. A tag pushed at night would have failed the run ten minutes later with the installer already compiled and thrown away. The compile now ends in its own job that uploads the unsigned artifact and stops; a second job downloads it, signs it, and does the release-facing work, with the wait raised to an hour. Because the artifact is uploaded before the wait begins and is addressed by id, a missed approval window is recovered by re-running the second job alone rather than rebuilding the installer — which is the reason to separate them rather than simply raise the timeout in place. The second job runs for unsigned builds too, so the daily prereleases that are deliberately left unsigned to preserve the signing quota keep going out through exactly one set of alias, artifact and release steps. The property that matters is unchanged and now recorded next to the steps that depend on it: none of the alias, upload or release-attach steps carry `always()`, so GitHub skips all three when signing fails or times out, and an unsigned `.exe` cannot reach a release. Nothing about the signed output changes, and the restructure behaves identically under the test policy — the request simply completes immediately instead of waiting — so it can be proven green before the production certificate arrives.
### Fixed
- **A manual K-profile calibration left a print in your archive** — Bambuddy already recognises the printer's automatic pressure-advance run, `auto_pa_line_calib_mode`, and skips archiving and notifying for it. Started by hand instead of automatically before a print, the same calibration reports under a different name: it prints a *pattern* where the automatic one prints a *line*, and carries no `auto_` prefix, so `pa_pattern_calib_mode` matched nothing. It arrives exactly the way the automatic one does — a bare subtask name with no `/usr/` path — which meant the archive path swept FTP for a 3MF that cannot exist and then wrote a no-3MF archive named after the calibration, on a printer that was in the middle of calibrating. It is now on the same list, which is the one place both the print-start and print-complete callbacks consult. Matching stays exact after normalising path, suffix and case, so a file you deliberately named `pa_pattern_calib_mode_v2.3mf` is still archived as the print it is. Wiki updated.
- **A spool you assigned to an AMS slot unassigned itself seconds later (#2987, reported by @frethop)** — and the slot's colour changed at the same time. It looked like Bambu Studio and Bambuddy fighting over the slot; the reporter's log shows Bambuddy losing to itself. P1S firmware 01.10.00.00 reads every **lowercase** hex letter in an AMS `tray_color` as a zero, and hides it completely: the command response echoes back the value you sent and reports `result: "success"`, so only the next AMS push reveals what was really stored. The spool-assign path sent `spool.rgba` verbatim, and that column stores lowercase — so `09ff00ff` became `09000000` on the printer and `ff5100ff` became `00510000`, while the one uppercase write in the same window round-tripped intact. That is the visible colour change. It is also what deleted the assignment: the auto-unlink sweep asks whether the slot still matches the spool it is assigned to, the mangled colour no longer did, and the assignment Bambuddy had created four seconds earlier was removed. Re-assigning could not help, because the **Configure Slot** dialog seeds its colour from whatever the printer currently reports — so it wrote the mangled colour straight back and cemented it, which is the loop in the report's steps 4 and 5. Colours are now uppercased at the single point the MQTT command is assembled rather than in each of the four routes that configure a slot, because a caller that forgets is exactly how this arrived. Nothing else about the command changes: no padding, no invented alpha, and `tray_type` / `tray_sub_brands` keep their case, where it carries meaning. **Two more things found in the same log.** A spool with a brand but no subtype was configured with the literal string `None` in its name — `"Sunlu PLA Matte None"` went on the wire, because the branded branch interpolated the subtype without checking it while the unbranded branch guarded it. And the FTP log is now readable: a `426` whose bytes Bambuddy has verified against the printer is how Bambu's FTPS normally ends a transfer, not a fault, so it is logged at INFO instead of WARNING. It fired 54 times in this one bundle, every single one followed by a completed upload, and it was burying the 26 TLS handshake failures in the same log that actually cost the reporter two prints. A `426` whose bytes do **not** verify is still an error and still fails the upload. 24 regression tests.
- **One ASA spool parked in the AMS added 20 minutes to every PLA print (#2886, reported by @FirstRulez)** — Preheat's chamber target was the maximum across *every loaded AMS tray*, with no reference to the job. The reporter's P2S holds PETG Pro, PLA, ASA and PETG; the ASA row of the filament map says 45°C, so a PLA-only plate was dispatched with `chamber_target=45°C`, the bed driven to 90°C to reach it, and the full 900s max-wait plus 300s soak burned before the upload even started — every time, because a P2S has no chamber heater and the chamber tops out around 33°C, so the wait can only ever end on the timeout. Their log carries fifteen of these. The intent was never in doubt: the resolution order documented one screen above reads "PLA-only print derives 0 → chamber phase auto-skips", but it was implemented as PLA-only **AMS** rather than PLA-only **print**, and only misfires on a mixed-material load. The derivation now reads the trays the item's `ams_mapping` actually names — the same array the print command puts on the wire, `[-1, -1, -1, 1]` in their case, addressing exactly the PLA slot — so the ASA two slots over contributes nothing and the stage skips outright. Multi-material prints are unaffected: the maximum is still taken, just across the trays the plate loads, so an ASA the print really does use is still the binding constraint. An item whose mapping is missing or still unresolved keeps the whole-unit scan, since that is the only signal left and narrowing to nothing would disable preheat for prints that need it. The bed hold between jobs (`queue_keep_bed_warm`) is gated on the same derivation and was holding beds at 90°C for the same wrong reason; it now reads the next item's mapping too. **The external spool is no longer invisible to this**: the scan only ever looked at `raw_data['ams']`, so an ASA print fed from the external feed derived 0 and got no preheat at all — a mapping that names 254/255 is now honoured, while an item with no mapping still derives from the AMS alone so nothing starts preheating that did not before. That covers the mappings Bambuddy builds itself, from the print dialog or the dispatcher's own matcher. It does not cover a mapping captured from a slicer through a Virtual Printer: BambuStudio writes the external spool as `-1` there, which is the same value it writes for a slot the plate does not use, so the two cannot be told apart. A wholly external one carries no usable mapping and falls back to the whole-unit scan as before; a mixed one derives from its AMS trays and the external half stays unread — which is exactly what it did before this change, since nothing ever read `vt_tray`. 29 regression tests, built from the trays and mapping in the reporter's own support bundle.
- **Spoolman reset your renamed extra fields on every restart (#2983, reported by @ngreatorex)** — Bambuddy checked whether one of its four custom spool fields existed by calling `GET /field/spool/{name}`. Spoolman has never served that: its API declares only `POST` and `DELETE` at that path, so the check answered **405 Method Not Allowed** every single time and could never succeed. Each call then fell through to `POST /field/spool/{name}` — and that endpoint is an *upsert*, not a create. It answers 200 whether or not the field is already there, so a field you had renamed, retyped or given a default to in Spoolman's own UI was silently reset to Bambuddy's version of it, and an untrue `Created Spoolman extra field` was logged alongside. The reporter's log carried 60 of those lines in three days. Existence now comes from the documented `GET /field/spool` listing, matched on the field's `key` rather than its display `name` — a rename is the same field, and treating it as a missing one is what caused the overwrite. An existing field is left completely alone. **You can now rename these fields in Spoolman and the name will stick.** Registering all four also costs one request instead of four, and none at all on a client that has already looked once. If the listing can't be read at all, Bambuddy falls back to attempting the write exactly as before, so an unexpected Spoolman build is no worse off than today.
+7
View File
@@ -29,10 +29,17 @@ print-complete callbacks can share one answer.
# ``auto_pa_line_calib_mode`` is the pressure-advance (K profile) line. This one
# is reported as a *subtask name* with no ``/usr/`` path at all, which is why
# the path rule alone was never enough.
#
# ``pa_pattern_calib_mode`` is the same calibration started by hand rather than
# automatically before a print -- the manual flow-dynamics run prints a pattern
# where the automatic one prints a line, and it carries its own name with no
# ``auto_`` prefix. It reaches Bambuddy exactly the way the automatic one does,
# so leaving it off the list produced the same no-3MF archive.
INTERNAL_JOB_NAMES = frozenset(
{
"auto_cali_for_user",
"auto_pa_line_calib_mode",
"pa_pattern_calib_mode",
}
)
@@ -11,6 +11,10 @@ a no-3MF archive named after itself in the user's history.
The same name is already known to the completion guard: #2829's capture of
queue item 649 has ``auto_pa_line_calib_mode`` arriving as the subtask name of a
completion that had to be refused against a running job.
The manual flow-dynamics run reaches Bambuddy the same way under a different
name -- ``pa_pattern_calib_mode``, a pattern rather than a line and with no
``auto_`` prefix -- so the automatic entry never covered it.
"""
from unittest.mock import AsyncMock, MagicMock, patch
@@ -29,6 +33,16 @@ class TestTheCalibrationIsRecognised:
"""Both fields are tested, because which one carries it is not fixed."""
assert is_internal_printer_job("auto_pa_line_calib_mode", None)
def test_the_manual_pressure_advance_pattern_by_subtask_name(self):
"""The same calibration run by hand rather than before a print. It
prints a pattern where the automatic one prints a line, and carries its
own name with no ``auto_`` prefix -- so the automatic entry never
covered it and it left the same no-3MF archive."""
assert is_internal_printer_job("", "pa_pattern_calib_mode")
def test_the_manual_pressure_advance_pattern_by_filename(self):
assert is_internal_printer_job("pa_pattern_calib_mode", None)
def test_the_levelling_run_by_its_system_path(self):
assert is_internal_printer_job("/usr/etc/print/auto_cali_for_user.gcode", "auto_cali_for_user")
@@ -45,6 +59,10 @@ class TestTheCalibrationIsRecognised:
"auto_pa_line_calib_mode.gcode.3mf",
"AUTO_PA_LINE_CALIB_MODE",
"/data/auto_pa_line_calib_mode.gcode.3mf",
"pa_pattern_calib_mode",
"pa_pattern_calib_mode.gcode.3mf",
"PA_Pattern_Calib_Mode",
"/data/pa_pattern_calib_mode.gcode",
],
)
def test_however_the_name_is_dressed_up(self, reported):
@@ -72,6 +90,8 @@ class TestItLeavesRealPrintsAlone:
"auto_pa_line_calib_mode_v2.3mf",
"my_auto_pa_line_calib_mode.3mf",
"auto_cali_for_user_test.gcode.3mf",
"pa_pattern_calib_mode_v2.3mf",
"my_pa_pattern_calib_mode.3mf",
],
)
def test_a_users_file_that_merely_contains_the_name(self, reported):