The queue item status update (printing → completed/failed) was placed
after `if not archive_id: return` in on_print_complete. When archive
lookup failed (app restart mid-print, manual deletion), the function
returned early and the queue item stayed stuck in "printing" forever.
Over multiple print cycles, stale items accumulated — showing double
the actual printer count in the "Printing" stat and completed prints
remaining in the "Currently Printing" section.
Move the entire queue item status update block (status transition,
MQTT relay, queue-completed notification, auto-power-off) to before
the archive_id early return, matching the pattern used for SD card
cleanup.
The SD card file cleanup (which prevents phantom prints on power cycle)
was placed after the archive_id early-return in on_print_complete. Users
with auto-archiving disabled never reached the cleanup code, so ghost
prints persisted on every reboot.
- Move cleanup before the archive_id check so it always runs
- Add retry loop (3 attempts, 2s delay) for printers that briefly lock
the filesystem after a print ends
- Try both .3mf and .gcode extensions
Reported by @DIYofThings in #374.
Subscribe to the MQTT request topic (device/{serial}/request) to intercept
print commands from any source (BambuStudio, OrcaSlicer, Bambu Handy, or
Bambuddy itself). Captures ams_mapping from project_file commands and passes
it through print start/complete callbacks. Fixes wrong tray attribution on
H2D Pro when prints are started from external slicers.
Usage tracking works on X1E but fails on H2D Pro — the wrong tray
is identified, attributing usage to the wrong spool. The root cause
is unknown because we don't know what MQTT mapping data each printer
model reports.
Adds INFO-level logging at print start and completion:
- state.raw_data["mapping"] (unknown format, preserved but never parsed)
- tray_now, last_loaded_tray values
- All raw_data keys containing "map" (discovery)
- Per-AMS-tray summary: tray_type, tray_color, tray_now, tray_tar
This enables comparing MQTT behavior across printer models without
requiring DEBUG mode, so testers can send normal logs.
Three fixes for inventory spool usage not updating after prints:
1. Store ams_mapping from print command (reprint, library print, queue)
so the usage tracker maps 3MF slots to the actual physical trays
the user selected, not the default slicer mapping.
2. Track last_loaded_tray on printer state — the last valid tray_now
(0-253) seen during printing. On H2D printers, tray_now is always
255 in AMS data; the real tray resolves via snow field ~44s after
print start but reverts to "unloaded" at completion. The fallback
chain is: tray_now_at_start > current tray_now > last_loaded_tray.
3. Use color similarity (Euclidean RGB distance, threshold 50) instead
of exact hex match for auto-unlink fingerprint checks. RFID sensors
report slightly different shades across reads (e.g. distance ~43.6
for the same spool), causing false assignment unlinks after prints.
When a print was queued from the file manager (library_file_id path),
the scheduler never created an archive or called register_expected_print().
This forced on_print_start to re-download the 3MF via FTP, and if that
failed, a fallback archive with no file was created — breaking 3MF-based
filament usage tracking entirely. The queue item's archive_id also stayed
NULL, so the usage tracker couldn't find the AMS slot mapping.
Now the scheduler creates an archive from the library file before uploading,
sets item.archive_id, and the existing register_expected_print() call fires
since archive is no longer None.
Add a "Clear Errors" button to the HMS error modal that sends
clean_print_error via MQTT and locally clears hms_errors for
immediate UI feedback. Useful for dismissing stale print_error
values that persist after print cancellation or transient events.
Pushover and Discord already received print thumbnails, but Telegram
and ntfy only got text. Telegram now uses sendPhoto with the image as
a caption attachment. ntfy sends the image as a binary PUT with
Filename/Message headers. No config changes needed.
The print scheduler uploads .3mf files to the printer's SD card root
but never cleans them up. Some printers (e.g. P1S) auto-start files
found in root on power cycle, causing phantom/ghost prints on every
reboot. Now deletes the uploaded file via FTP after print completion
(best-effort, non-blocking).
Three bugs fixed and one recovery feature added:
1. AMS remain=0 zeroed all spools on printer power-off. The weight sync
treated 0% remain as "fully consumed," setting weight_used to
label_weight for every assigned spool. Fix: skip remain=0 in AMS
weight sync — empty spools are tracked by the usage tracker.
2. Editing any spool field sent stale weight_used from the React Query
cache back to the server, resetting usage-tracked weight. Fix: only
include weight_used in PATCH when the user explicitly changes it.
3. K-profile auto-select crashed on dual-nozzle printers for non-BL
spools with 'SpoolKProfile has no attribute extruder_id'. The model
attribute is 'extruder', not 'extruder_id'.
4. New "Sync Weights from AMS" button in Settings > Filament Tracking
(built-in inventory mode) to force-recover spool weights from live
AMS sensor data. Bypasses the "only increase" guard for explicit
user-initiated recovery.
The duplicate badge on archive cards only matched by content hash,
so re-sliced prints of the same model (different GCODE, same name)
were not flagged. Now also matches by print name (case-insensitive),
consistent with the detail view's find_duplicates() logic.
The firmware update cache stored downloaded files under a mangled name
(e.g. X1C_01_09_00_10.bin) instead of the original filename from
Bambu Lab's CDN. On cache hit the wrong filename was uploaded to the
printer's SD card, so the printer couldn't find the firmware file.
Now caches with the original filename so it's always correct.
Add a chronological, table-based print log as a 4th view mode in the
Archives page. Log entries are stored in a separate print_log_entries
table — clearing the log never touches archives or queue items.
Backend:
- New PrintLogEntry model with independent table
- GET /print-log/ endpoint with search, printer, user, status, date filters
- DELETE /print-log/ clears only log entries
- Thumbnail serving endpoint for log entries
- write_log_entry() service called on print completion
- Auth: ARCHIVES_READ for viewing, ARCHIVES_DELETE_ALL for clearing
Frontend:
- New 'log' ViewMode with ClipboardList icon toggle
- Filterable/searchable table with pagination (10/25/50/100 rows)
- Colored status badges, duration formatting, filament color swatches
- Clear button with confirmation modal
- All filter state persisted to localStorage
- i18n: EN, DE, JA, FR, IT translations
Suppress SQL/aiosqlite debug noise (~90% log volume reduction), add
caller-traced PRINT COMMAND logging to start_print(), log scheduler
queue checks, tighten stale archive ilike match to exact match, and
warn on multiple queue items in "printing" status. Includes 18 new
unit tests.
Notify users when the print bed cools below a configurable threshold
(default 35°C) after a print finishes, so they know when to remove parts.
- Backend: DB migration, model, schemas, notification template, service
method, background cooldown monitor (polls every 15s, 30min timeout)
- Frontend: event toggle in provider card/modal, threshold setting in
Settings > Notifications, i18n keys for all 5 locales
- Tests: 4 backend + 4 frontend tests
- Docs: README, website, wiki updated
When a print is sent from an external slicer and Bambuddy can't
download the 3MF during auto-archiving, the fallback archive has no
file. Reprinting such archives tried to upload the data directory,
causing a confusing SD card error. Backend now returns a clear 404
for empty file_path and checks is_file() instead of exists(). Frontend
disables Print/Schedule/Open in Slicer for file-less archives with an
explanatory tooltip. Added i18n key in all 5 locales.
Backend /updates/check endpoint now returns early without calling
GitHub API when check_updates is disabled. Settings page no longer
auto-fetches update status when the setting is off. Printer card
firmware badge falls back to showing the current version from MQTT
instead of disappearing when firmware update checks are disabled.
Downgrade 58 diagnostic logger.info calls to logger.debug in
bambu_mqtt.py — payload dumps, detector state changes, field
discovery, H2D disambiguation, and periodic status updates no longer
flood logs at the default INFO level. User-initiated actions (print,
stop, calibration, AMS load/unload) remain at INFO. Also suppress
paho-mqtt library INFO messages in production mode.
When enabling Spoolman, stale built-in inventory slot assignments
persisted in the database and the printer card UI still showed
assign/unassign buttons from the internal inventory. Now bulk-deletes
all SpoolAssignment records on mode switch, invalidates the frontend
spool-assignments cache, and hides inventory UI on printer cards while
Spoolman is active.
- Replace browser confirm() with styled ConfirmModal for inventory
delete (danger) and archive (warning) actions
- Expand default color catalog from 258 to 638 entries (6 → 20 brands)
with measured hex codes from FilamentColors.xyz
- Expand eSUN from 10 generic placeholders to 79 measured colors
across 10 material lines
- Add built-in inventory note about third-party spool assignment
- Increase Spool/Color Catalog settings card height (400px → 600px)
- Add i18n keys for all new strings (en, de, fr, it, ja)
- Update wiki inventory docs with catalog management details
The on_ams_change() handler recalculated weight_used from the AMS
remain% sensor on every status update, overwriting precise values
from the usage tracker. For small prints on large spools (e.g. 1.6g
on 1000g), the integer-resolution sensor (10g steps) still reported
100% remain, resetting weight_used back to 0. The sync now only
increases weight_used, never decreases it.
Backend:
- Add dual external spool support for H2D (vt_tray as list: Ext-L/Ext-R)
- Add cloud filament ID map endpoint (/cloud/filament-id-map)
- Fix RFID spool data erased by periodic AMS updates (skip tag matcher
for RFID-tagged trays)
- Fix AMS slot config overwrites RFID spool state
- Fix K-profile selection corrupts existing profiles on X1C/P1S
- Resolve K-profiles filament name via cloud filament ID map
- Update print scheduler and usage tracker for dual external spools
Frontend:
- Add printer model filtering to ConfigureAmsSlotModal (cloud/local/builtin
presets filtered by @BBL model suffix and compatible_printers)
- Add pre-population for configured slots (preset, color, K-profile)
- Add K-Profiles view with accurate filament name resolution
- Internationalize all ConfigureAmsSlotModal strings (en/de/fr/it/ja — 21 keys)
- Add 5 new ConfigureAmsSlotModal tests (model filtering, pre-selection,
color pre-population, i18n)
- Update PrintersPage for dual external spool rendering
Docs:
- Update CHANGELOG, README, website features, and wiki AMS docs
filament_details now shows which AMS unit and tray each filament came
from (e.g. "AMS-A T1 PLA: 12.4g | AMS-A T3 PETG: 2.8g"). External
spool holders show "Ext". Falls back to type-only format when usage
tracking data is unavailable (e.g. Spoolman mode).
- usage_tracker: include spool material in result dicts
- main.py: pass usage_results to notification flow via archive_data
- notification_service: build filament_details with AMS labels when
usage_results available, fall back to existing slot-based format
- Update sample data and changelog
3MF slicer estimates are now the primary tracking source for ALL spools
(BL and non-BL), with AMS remain% delta as fallback. Per-layer G-code
analysis provides accurate partial usage for failed/cancelled prints.
Fix wrong-spool tracking bug: 3MF slot_id was mapped directly to AMS
tray position, but the printer remaps slicer slots at print time. Now
uses queue ams_mapping for queue prints and tray_now from printer state
for single-filament non-queue prints.
Add filament_grams, filament_details, and progress template variables
to print_complete, print_failed, and print_stopped notification events.
Webhook payloads include per-slot filament breakdown. Per-slot data
stored in archive extra_data during 3MF parsing.
Status summary bar on the Printers page now shows availability count
("X available") and a "Next available" indicator with printer name,
progress bar, percentage, and remaining time for the printer finishing
soonest. Always visible when printers are printing; hidden otherwise.
Fix race condition in bulk archive deletion where files were removed
from disk before the database commit. Concurrent SQLite writes could
cause a lock timeout, rolling back the DB delete while files were
already gone — leaving orphaned records with broken thumbnails. Now
deletes the DB record first and only removes files after a successful
commit.
H2/A1 series have linear rails, not carbon rods. X1/P1/P2S have carbon
rods, not linear rails. Split rod-specific maintenance into model-aware
pairs: "Lubricate/Clean Carbon Rods" for X1/P1/P2S and "Lubricate/Clean
Linear Rails" for A1/H2. Added rod type classification to printer_models
and startup cleanup for stale/duplicate system maintenance types.
Dual-nozzle H2D/H2D Pro: filament matching now respects nozzle assignments
from the 3MF file. Each AMS unit feeds a specific nozzle (L/R), and the
scheduler/frontend constrain matching to only trays on the correct nozzle.
Falls back to unfiltered matching when no trays exist on the target nozzle.
L/R badges shown in the filament mapping UI. Translated in en/de/ja/it.
Fix AMS slot config overwritten on startup: on_ams_change unconditionally
unlinked BL spool assignments on every MQTT pushall, then re-assigned them
sending ams_filament_setting without setting_id — clearing the printer's
filament preset. Now compares spool RFID identifiers before unlinking.
Fix BL spool detection false positives: removed tray_info_idx from detection
logic in both backend is_bambu_lab_spool() and frontend isBambuLabSpool().
Third-party spools using Bambu generic presets had GF-prefixed tray_info_idx
values, causing misidentification. Now uses only tray_uuid and tag_uid.
SQLite WAL mode with 5s busy timeout reduces "database is locked" errors.
Existing code did not allow the MQTT relay to be shut down gracefully.
Moved _client.loop_stop() after _client.disconnect() call to allow
Paho to be notified to disconnect and shut down without reconnecting.
Added _client._disconnection_event to help prevent a race condition where
a disconnection is requested but the loop is immediately stopped.
Modify _client.disconnect() to take a timeout parameter (default: 0).
Wait in that routine for the disconnection event with the given timeout.
Apply same logic to MQTTSmartPlugService and add to shutdown sequence.
Shutdown the MQTT relay last in lifespan() with a hard-coded 2 sec timeout.
The disconnect timeout is also available to printer MQTT clients.
It is left to the default of 0 at this time. If later enabled,
it should be a configuration parameters, especially for large farms.
Fixes: #327
Signed-off-by: Jeff Kletsky <git-commits@allycomm.com>