Users on the Docker `latest` tag were seeing update notifications for beta
releases (e.g. v0.2.1b) they couldn't install. The update checker now fetches
/releases instead of /releases/latest and filters by parse_version() prerelease
detection. A new toggle in Settings (default: off) lets users opt in to beta
notifications.
When a print was queued with "Any [Model]", the queue widget only
queried items with printer_id=X after dispatch, missing the next
pending model-based item (printer_id IS NULL, target_model="P1S").
The "Clear Plate & Start Next" button never appeared, leaving the
scheduler stuck reporting "Busy".
Add optional target_model query parameter to GET /queue/. When
combined with printer_id, uses OR logic to also return unassigned
items whose target_model matches. The frontend now passes the
printer's model through PrinterQueueWidget to this query.
BambuStudio actively resets AMS slots configured with unrecognized PFUS*
(user-local) preset IDs. Replace PFUS* with generic Bambu filament IDs
(e.g. GFL99 for PLA) in both the slot configure and inventory assignment
endpoints. When the slot already has a recognized cloud-synced preset for
the same material, reuse it to preserve K-profile calibration.
Also fix fill level bar not showing for brand new spools (weight_used=0)
by changing the condition from weight_used > 0 to weight_used != null.
When the snow field hadn't arrived yet, the ams_extruder_map fallback
computed ams_id * 4 + slot for AMS-HT units (128-135), producing
invalid tray IDs like 512+. AMS-HT has a single slot; global ID = unit
ID.
- Single-AMS fallback: return ams_id directly for AMS-HT (128-135)
- Multi-AMS matching: decompose AMS-HT trays correctly (slot=0)
- Multi-AMS narrowing: slot > 0 eliminates AMS-HT candidates
- last_loaded_tray: tighten to valid IDs only (0-15, 128-135, 254)
- Add 10 tests for AMS-HT fallback and last_loaded_tray validation
Multiple Virtual Printers:
- Each VP gets a dedicated bind IP with independent FTP, MQTT, SSDP, and Bind services
- New VirtualPrinter DB model, CRUD API (/api/virtual-printers), React UI
- VirtualPrinterList, VirtualPrinterCard, VirtualPrinterAddDialog components
- Per-instance TLS certificates (shared CA), 11 printer models, all 4 modes
- Auto-incremented serial suffixes, network interface override per VP
Dual Bind/Detect Ports (#445):
- Listen on both ports 3000 and 3002 for slicer bind/detect handshake
- Different BambuStudio/OrcaSlicer versions use different ports
- Applies to BindServer (server mode) and SlicerProxyManager (proxy mode)
- Updated Dockerfile, docker-compose.yml, firewall rules in wiki
Also:
- Rewrote VP test suite for new multi-instance architecture (75 tests)
- Rewritten "How it works" section with 3-step workflow explanation
- Updated all 5 locales (en, de, ja, fr, it)
- Updated wiki and website for multi-VP + dual ports
- New multi-VP screenshot
The printer only sends PAUSE via MQTT gcode_state, never PAUSED.
Removed all unreachable PAUSED comparisons from frontend (4 files)
and backend (2 files).
Add a new `printers:clear_plate` permission so admins can grant
plate-clearing ability without full `printers:control` access.
Existing groups with `printers:control` automatically receive the
new permission on startup via migration.
Replace the cramped permission modal in Settings with a dedicated
full-page group editor (`/groups/new`, `/groups/:id/edit`) featuring
search filtering, select all/clear all, category-level toggles,
and a responsive 2-column permission grid. Remove dead GroupsPage
code that was never routed.
- Backend: new Permission enum, category, defaults, endpoint guard, migration
- Frontend: GroupEditPage, updated routes, removed modal from SettingsPage
- Tests: 10 backend + 10 frontend tests for new permission and editor
- Docs: updated wiki (auth, printer-control, API), website, changelog
- i18n: added group editor keys to all 6 locale files (en/de/ja/fr/pt-BR/it)
* Debounce HMS error tracking to prevent flapping notifications
HMS errors on newer printers (e.g. H2S) can flicker on and off
every few seconds as conditions fluctuate around thresholds. For
example, chamber temperature regulation during PETG prints sends
HMS code 0300A70000030001 repeatedly as temps oscillate. Previously
each reappearance after a brief hms:[] gap was treated as a new
error, triggering duplicate notifications. This adds a 30-second
grace period before clearing the notification tracking state so
flapping errors are only notified once per episode.
* build(deps): bump aquasecurity/trivy-action
Bumps the github_actions group with 1 update in the /.github/workflows directory: [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action).
Updates `aquasecurity/trivy-action` from 0.33.1 to 0.34.0
- [Release notes](https://github.com/aquasecurity/trivy-action/releases)
- [Commits](https://github.com/aquasecurity/trivy-action/compare/0.33.1...0.34.0)
---
updated-dependencies:
- dependency-name: aquasecurity/trivy-action
dependency-version: 0.34.0
dependency-type: direct:production
dependency-group: github_actions
...
Signed-off-by: dependabot[bot] <support@github.com>
* Updated CI
* build(deps): bump aquasecurity/trivy-action
Bumps the github_actions group with 1 update in the /.github/workflows directory: [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action).
Updates `aquasecurity/trivy-action` from 0.33.1 to 0.34.0
- [Release notes](https://github.com/aquasecurity/trivy-action/releases)
- [Commits](https://github.com/aquasecurity/trivy-action/compare/0.33.1...0.34.0)
---
updated-dependencies:
- dependency-name: aquasecurity/trivy-action
dependency-version: 0.34.0
dependency-type: direct:production
dependency-group: github_actions
...
Signed-off-by: dependabot[bot] <support@github.com>
* Updated CI
* Updated CI
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: MartinNYHC <mz@v8w.de>
Library files are stored on disk with UUID-hex filenames for uniqueness,
but archive_print() used the disk path as the display name. Pass the
original LibraryFile.filename through from both the print scheduler and
direct-print-from-library flow so the archive's filename, print_name,
and directory name all use the human-readable name.
Add color-matching fallback for slot-to-tray mapping on printers that
don't provide the MQTT mapping field (snow encoding) or reject request
topic subscription. Matches 3MF filament slot colors against AMS tray
colors to resolve the correct physical tray. Returns no match when
colors are ambiguous (duplicate tray colors) to safely fall through
to existing fallbacks.
Mapping priority chain: print_cmd → MQTT snow → queue → color match →
tray_now → slot_id-1.
Slicer protocol handlers (bambustudio://, orcaslicer://) launch the
slicer app which fetches files via HTTP but cannot send auth headers.
The global auth middleware returned 401, causing "importing failed."
Add short-lived, single-use download tokens: the frontend fetches a
token via authenticated POST, then builds a /dl/{token}/{filename} URL
the slicer can access without auth headers. Tokens are validated
server-side (5-min expiry, single-use). Applies to archive files,
source 3MFs, and library files.
Also fix platform-specific URL formats to match slicer source code:
- macOS: bambustudioopen:// with encodeURIComponent
- Windows/Linux: bambustudio://open?file= (was wrongly using macOS scheme on Linux)
- OrcaSlicer: orcaslicer://open?file=
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.
P2S (and X1C) with multiple AMS units incorrectly entered the H2D
dual-nozzle tray_now disambiguation code path, causing warning log
spam. The check `tray_now <= 3` matched any printer loading from
AMS 0. Now uses a persistent `_is_dual_nozzle` flag detected from
device.extruder.info (>= 2 entries), which only dual-nozzle printers
(H2D, H2D Pro) report. Flag is set early in message processing so
it's available before AMS data is parsed.
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.
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.