The /filaments/ endpoint manages material type definitions (cost, temps,
density) but shares its name with the UI's "Filament" page which shows
the spool inventory (/inventory/spools/). This caused users to expect
the API to return their spool inventory.
Rename to /filament-catalog/ to make the distinction clear. No frontend
behavior change — these API methods are defined but unused in the UI.
On dual-nozzle printers (H2C, H2D Pro) with multiple AMS units, usage
tracking attributed filament consumption to the wrong spools. The MQTT
mapping field — a snow-encoded array mapping slicer slots to physical
AMS trays — was preserved in state but never parsed.
Add _decode_mqtt_mapping() to convert snow encoding (ams_hw_id * 256 +
slot) to bambuddy global tray IDs. Insert as priority 2 in the mapping
resolution chain, after print_cmd ams_mapping but before queue/tray_now
fallbacks. Works for all printer models and print sources.
AMS-HT trays (IDs 128-135) are single-slot units, but the snow_slot
computation used `snow_tray % 4 if snow_tray < 128 else -1`, yielding
-1 for AMS HT. This caused a misleading "slot mismatch" debug log on
every MQTT update despite producing the correct tray_now value. Now
returns slot 0 for AMS-HT IDs (128-135), matching the firmware's
tray_now=0 for these single-slot units.
After clicking "Clear Plate & Start Next", refreshing the page showed
the button again because the frontend determined the state purely from
the printer's FINISH/FAILED status. The backend already tracked a
plate_cleared flag in memory but never exposed it to the frontend.
- Add plate_cleared field to PrinterStatus schema and API response
- Pass plateCleared prop to PrinterQueueWidget
- Skip clear plate UI when plate is already acknowledged
- Add 2 tests for plateCleared=true behavior
Recent BambuStudio/OrcaSlicer updates require a bind/detect handshake on
port 3000 before connecting via MQTT/FTP. Without this, slicers cannot
discover or connect to the virtual printer in any mode.
- Add BindServer for server modes (immediate/review/print_queue)
- Add TCPProxy for raw TCP forwarding (proxy mode)
- Update Dockerfile (EXPOSE 3000) and docker-compose.yml (bridge port)
- Add 10 new tests for BindServer protocol and integration
Some printer MQTT brokers (e.g. P1S) reject subscriptions to the request
topic by killing the TCP connection, causing a disconnect loop. The request
topic subscription for ams_mapping capture is now fail-safe with two
detection paths:
- SUBACK rejection: on_subscribe callback detects failure code 0x80
- Broker kills connection: on_disconnect detects disconnect within 10s of
unconfirmed subscription attempt
After detection, the request topic is disabled for that printer and
reconnection succeeds with only the report topic. Printers that support
the request topic (e.g. X1E, H2D) get ams_mapping capture; printers that
don't (e.g. P1S) gracefully degrade to tray_now-based tracking.
When the auto-scan attached the wrong timelapse (e.g. from a different
print), there was no way to fix it — the file couldn't be removed and
re-scanning was disabled once a timelapse was attached.
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.
The "Next in queue" widget on the printer card only checked
archive_name/archive_id, so file-manager-sourced queue items
displayed "Archive #null". Now falls back to library_file_name
and library_file_id, matching the Queue page display logic.
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.
The small 208px image panel in the skip objects modal made it hard to
distinguish object markers when parts are close together. Clicking the
image now opens a fullscreen lightbox overlay (up to 600px) with the
same markers at a proportionally smaller size, solving the overlap
problem. Close via X button, Escape key, or backdrop click. Escape
cascades correctly — closes lightbox first, then the modal.
When scheduling a multi-plate file from the file manager, the modal
allowed submission without selecting a plate. Now auto-selects the
first plate by default and enforces plate selection for both archive
and library files.
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.
Add scrollbar-gutter: stable to spool form modal to prevent scrollbar
layout thrashing on Windows Edge at non-default zoom levels.
Delete uploaded .3mf from printer SD card root after print completion
to prevent phantom prints on power cycle (reported on P1S).
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.
When editing a spool (changing note, color, etc.), the form always sent
weight_used back to the server. If the React Query cache held a stale
pre-print value, saving would silently reset remaining weight to full.
Track whether the user explicitly touched the weight field and only
include weight_used in the PATCH request when creating a new spool or
when the weight was intentionally changed.
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.
The archive grid view displayed a red status badge for failed and
aborted prints, but the list view had no equivalent indicator. Added
an inline badge next to the print name in ArchiveListRow, reusing the
existing i18n keys and status-error styling from the grid view.
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.
The color pre-population logic skipped hex 000000 (black) to guard
against empty/uninitialized slots, but empty slots already don't pass
trayColor to the modal. Removed the unnecessary check so black filament
color is now pre-populated like any other color.
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.
Prevent accidentally skipping the wrong object during a print by showing
a warning confirmation dialog with the object name and an irreversible
action warning. Uses the existing ConfirmModal component with warning
variant. Translations added for all 4 locales (en, de, ja, it).
When the same file was queued multiple times from the archive, all jobs
after the first were automatically skipped with "already printed X hours
ago". The scheduler's 4-hour safety check compared the shared archive's
completed_at timestamp, incorrectly treating legitimate repeat prints as
phantom reprints from crash recovery. This also affected single queue
items created from recently completed archives.
Remove the check entirely — the crash scenario it guarded against is
already prevented by committing item.status="printing" before sending
the print command.