The SpoolBuddy layout now auto-checks for daemon updates every 5
minutes and shows "Update available: v{version}" in the status bar.
Removed the beta toggle since SpoolBuddy follows Bambuddy's release
channel. The daemon version is now read from backend APP_VERSION
instead of a stale hardcoded string.
SpoolBuddy devices can now be updated from Settings → Updates without
SSH access. The daemon picks up an "update" command via its existing
heartbeat, runs git fetch/reset + pip install, reports progress back
to the backend, then exits for systemd to restart with the new code.
Backend: update_status/update_message fields, trigger + status endpoints
Daemon: _perform_update() handler, report_update_status() API method
Frontend: "Apply Update" button with live progress in UpdatesTab
Multi-plate 3MF files now support selecting a subset of plates to queue
via checkboxes, instead of the binary "one plate" or "all plates" toggle.
In add-to-queue mode, each plate gets a checkbox for multi-select with a
Select All / Deselect All toggle. Reprint and edit modes remain single-select.
The saved preset bypassed the search filter entirely, so when a slot's
DB mapping was stale (e.g. previously Matte, now physically Silk), the
old preset always appeared regardless of search query. Remove the search
bypass — saved/current presets still bypass the printer model filter but
must match the search text like all other presets.
Add visual indicators so printers with HMS errors stand out in large
print farms:
- Red "Problem" counter in the status summary bar
- Status pip turns red (fatal/serious) or amber (warning) for HMS errors
- Progress bar turns amber when a print is paused
- Status sort prioritizes printers with HMS errors at the top
Replace fixed 30-second debug log collection with an interactive
3-step flow: start logging, reproduce the issue, stop & submit.
Users now control timing instead of racing a countdown.
Backend: split _collect_debug_logs() into POST /start-logging and
POST /stop-logging endpoints; add debug_logs field to submit request.
Frontend: 3-step progress indicator with elapsed timer, pulsing
active state, and 5-minute auto-stop. Updated all 7 locale files.
Add a "Rotate spool during drying" checkbox to the manual drying popover
for AMS 2 Pro and AMS-HT units. The firmware-level rotate_tray field was
already sent (hardcoded to false) — this makes it user-configurable.
The checkbox defaults to unchecked and resets each time the popover opens.
Firmware silently disables rotation if filament is currently loaded.
When all matching printers were busy and a job was queued with ASAP
timing, the scheduler immediately fired a "Job Waiting for Filament"
notification even though the job was just waiting for a printer to
finish — no user action required.
Added _is_busy_only() check to skip the waiting notification when the
only reason is "Busy". Notifications still fire for actionable reasons
(missing filament, offline, wrong color). Also renamed the default
notification template title to "Queue Job Waiting" and updated
descriptions across all 7 locales.
Renaming a file in the File Manager included the extension in the
editable text, letting users accidentally strip it and break the file.
The rename modal now separates the base name from the extension
(.3mf, .gcode, .gcode.3mf), showing the extension as a non-editable
gray suffix and re-appending it on save.
Carbon rods use plain bearings — lubricating them degrades print quality.
Removed the lubrication task from defaults; only "Clean Carbon Rods"
remains. Existing entries are auto-removed on next startup via
ensure_default_types(). Updated wiki link mapping and tests.
When searching for a non-existent profile in the AMS slot config modal,
presets matching the current slot's tray_info_idx bypassed the search
filter, showing e.g. all "Generic PLA" variants instead of no results.
Narrow the search bypass to only the exact saved preset — the broader
trayIdx match still bypasses the model filter as intended.
Add a speed control badge to the printer monitoring card controls row
that lets users switch between Silent (50%), Standard (100%), Sport
(124%), and Ludicrous (166%) presets during active prints. The badge
displays a gauge icon with the current speed percentage, always visible
but disabled when idle. Includes backend endpoint, optimistic UI
updates, i18n for all 7 locales, and full test coverage.
Add a "Spool" column to the filament inventory table that displays
the spool catalog entry name associated with each spool. The column
is hidden by default and can be enabled via the column visibility
menu. Also add a spool name filter dropdown next to the brand filter,
shown when any spools have catalog entries assigned. No backend
changes needed — catalog data is fetched and joined on the frontend.
When running multiple virtual printers with different access codes on
separate bind IPs, FTP connections were always routed to the wrong VP.
Root cause: the iptables REDIRECT rule (990→9990) rewrites the
destination IP to the incoming interface's primary address. With Linux's
weak host model (arp_filter=0), packets for secondary IPs arrive on the
primary interface, and REDIRECT sends them all to the first VP's FTP
server. MQTT was unaffected because port 8883 had no redirect.
Fix: FTP server now binds directly to port 990 (standard implicit FTPS),
eliminating the iptables redirect entirely. Requires CAP_NET_BIND_SERVICE
(already set in the systemd service file and Docker image).
Also removed a global asyncio set_exception_handler() in the MQTT server
that was overwritten by each VP instance, causing spurious "Unhandled
exception in client_connected_cb" errors on startup.
Changes:
- FTP_PORT: 9990 → 990 (ftp_server.py)
- Removed set_exception_handler() from MQTT server
- Updated Dockerfile, docker-compose.yml port mappings
- Deprecated --redirect-990 in install script
- Updated wiki: removed iptables instructions for all platforms
- Added migration guide (docs/migration-vp-ftp-port.md)
- Added unit tests for port constant and no-global-state invariant
After assigning a spool to an AMS slot, the Bambuddy UI could show the
wrong filament preset (e.g. "Bambu PLA Matte" instead of "Bambu PLA
Silk") even though the printer was configured correctly.
Two bugs:
1. AssignSpoolModal (PrintersPage hover card path) never saved the slot
preset mapping to the DB, so the display fell back to the old/stale
mapping from a previous manual configuration.
2. AssignToAmsModal (SpoolBuddy path) constructed the preset name from
spool.material + spool.subtype ("PLA Silk") instead of using the
authoritative spool.slicer_filament_name ("Bambu PLA Silk").
Fix: the backend now saves the slot preset mapping in assign_spool()
after successful MQTT configuration, using slicer_filament_name as the
display name. This covers both frontend paths and ensures the correct
name is always stored.
Floating camera viewer used z-50, same as all modals, causing it to
render on top of dialogs like Assign Spool. Lowered to z-40 so modals
always stack above the camera window.
X1C and X1 virtual printers used legacy SSDP model codes
(3DPrinter-X1-Carbon, 3DPrinter-X1) that BambuStudio doesn't
recognize, causing "incompatible printer preset" errors when
sending prints. Changed to the correct codes (BL-P001, BL-P002)
that real printers report via SSDP.
Also fixed proxy mode auto-inherit storing printer display names
(e.g. "X1C") instead of SSDP codes, by adding a resolution layer
that maps display names to model codes.
DB migration auto-converts existing VPs on startup.
Per-printer camera rotation (0°/90°/180°/270°) for cameras mounted
in portrait or upside-down. CSS rotation for live views, Pillow
rotation for notification snapshots. Setting visible in external
camera config when enabled.
Changed filament color circle borders from border-white/20 to
border-black/20 across all views so white spools are distinguishable
against light backgrounds.
- Change spool list from vertical to 3-column grid (2-col on small screens)
- Widen modal from max-w-md to max-w-2xl
- Increase scroll area from max-h-64 to max-h-96
- Show compact cards with name, color dot, and remaining/total weight
Add tests, docs, and ruff fixes for per-user email notifications
- Fix missing timezone import in email_service.py (F821)
- Fix unused lambda arg in main.py asyncio done_callback (ARG005)
- Fix E302 blank line spacing for mark_printer_stopped_by_user
- Fix F821/UP037 forward reference in user_email_pref model
- Fix SettingsPage test for duplicate "Notifications" text
- Add backend unit tests for permissions, schemas, and templates
- Add backend integration tests for user-notifications API
- Add frontend tests for NotificationsPage
- Add user_email_pref model import to test conftest
- Update CHANGELOG and README
Previously the "Assign Spool" button only appeared on configured slots,
forcing users to manually configure first — redundant since assignment
auto-configures the slot. Now shows on empty slots too. Also fixed the
AMS hover card showing generic material type instead of the spool's
slicer preset name after assignment.
- Sanitize project notes with DOMPurify before rendering via
dangerouslySetInnerHTML (ProjectDetailPage.tsx)
- Replace hand-rolled HTML sanitizer with DOMPurify in ProjectPageModal
to prevent attribute injection via crafted 3MF href values
- Block /api/v1/auth/setup when auth is already enabled to prevent
unauthenticated clients from disabling authentication remotely
The Debian ffmpeg package uses GnuTLS, whose hardened defaults reject
TLS renegotiation and legacy ciphers that some Bambu printer firmwares
(notably P2S) rely on — causing RTSP sessions to drop after a few
seconds.
Add a local TLS termination proxy (Python ssl/OpenSSL) that handles
the TLS connection to the printer and exposes a plain RTSP port to
ffmpeg. The proxy rewrites RTSP request-line URLs (rtsp://proxy →
rtsps://printer) while preserving Authorization headers so Digest
auth hashes remain valid.
Also:
- Reduce RTSP reconnect delay from 1.0s to 0.2s
- Add ffmpeg fast-start flags (-probesize 32, -analyzeduration 0,
-fflags nobuffer, -flags low_delay)
- Fix external camera double rate-limiting causing choppy streams
- Apply TLS proxy to external camera rtsps:// URLs and snapshot capture
- Update orphan ffmpeg cleanup to match rtsp:// (proxied) URLs
- Add unit tests for RTSP URL rewriting and proxy lifecycle
Printer File Manager Doesn't Auto-Refresh ([#704](https://github.com/maziggy/bambuddy/issues/704)) — The printer file manager (SD card browser) only fetched the file list once when opened. Files uploaded from BambuStudio/OrcaSlicer while the modal was open wouldn't appear until the user clicked the refresh button or reopened the modal. Now auto-refreshes every 30 seconds while open. Reported by @shadowjig.
Database Connection Pool Exhaustion Under Load ([#704](https://github.com/maziggy/bambuddy/issues/704)) — Background tasks (print scheduler FTP uploads, camera captures, notification sends, timelapse stitching) held database sessions open during slow network I/O, consuming connection pool slots for seconds at a time. With the default pool of 15 connections (size 5 + overflow 10), concurrent operations during print start/complete events could exhaust the pool, causing `QueuePool limit reached` errors and `greenlet_spawn` failures in RFID spool auto-assignment. Doubled the pool to 30 connections (size 10 + overflow 20). Reported by @shadowjig.
Ambient drying: automatically dry filament on idle printers when
humidity exceeds threshold, regardless of queue state. Separate toggle
from queue auto-drying — both can run simultaneously. Uses the same
presets, humidity threshold, and power constraint detection.
Fix: block mode (wait for drying) previously skipped the humidity
auto-stop check for already-drying printers, causing drying to
continue indefinitely. Now only prevents starting new drying.
Queue auto-drying: scheduler automatically starts drying on idle printers
with scheduled queue prints when AMS humidity exceeds the configured
threshold. Uses conservative parameters (lowest temp, longest duration)
for mixed filaments. Drying stops when humidity drops below threshold
(30-minute minimum prevents oscillation), when scheduled items are
removed, or when the feature is disabled. Optional "block queue" mode
delays the next print until drying completes.
Configurable presets: temperature and duration per filament type,
editable in Settings → Print Queue, used by both manual drying popover
and queue auto-drying. Separate presets for AMS 2 Pro (n3f) and AMS-HT
(n3s) reflecting different heating capabilities.
PSU detection: drying button disabled with tooltip when dry_sf_reason
indicates insufficient power. Parses drying status bits and dry_sf_reason
from AMS info hex string via MQTT.
Backend: print_scheduler.py (+316 lines), bambu_mqtt.py, printer_manager,
schemas, settings route. Frontend: PrintersPage drying presets prop,
SettingsPage drying config UI, i18n (7 locales). Tests: 27 new tests in
test_scheduler_auto_drying.py covering conservative params, presets,
state sync, stop logic, minimum drying time, and auto-stop regressions.
Non-script HA entities (switch/light/input_boolean) on the printer card
always sent turn_on when clicked, which is a no-op when the switch is
already on. Now sends toggle for non-script entities so the badge click
actually changes the switch state. Script entities still use turn_on.
Also fix SmartPlugManager._get_plug_for_printer() using
scalar_one_or_none() which crashes with MultipleResultsFound when
multiple plugs are assigned to the same printer (e.g., Tasmota + HA
switch). Now fetches all plugs and returns the main non-script plug.
Add tests for both fixes: _get_plug_for_printer multi-plug handling
(5 tests) and canonicalFilamentType/filamentTypesCompatible/autoMatch
PA-CF equivalence (8 tests).
Bambu Lab firmware treats PA-CF, PA12-CF, and PAHT-CF as interchangeable,
but the print scheduler and filament override UI used exact string matching.
A 3MF requiring PA-CF wouldn't match PA12-CF in the AMS, leaving the
override dropdown disabled and preventing scheduler assignment.
Add a filament type equivalence system (both backend and frontend) so
these PA variants are treated as compatible in scheduler assignment,
AMS slot matching, force color match validation, and the override dropdown.
Also fix the "Force color match" checkbox click target being too wide
(entire row instead of just the checkbox/label) by changing flex to
inline-flex on the label element.
The embedded camera viewer only handled mouse events (mousedown,
mousemove, mouseup) for window dragging and resizing. On iOS/iPadOS,
touch input doesn't trigger mouse events, so the camera window
couldn't be repositioned — touch just scrolled the page underneath.
Add touch event handlers (touchstart, touchmove, touchend, touchcancel)
alongside mouse handlers for both the header drag handle and the
bottom-right resize handle. Uses preventDefault on touchmove to
prevent page scrolling during drag.
When inventory spool assignments had stale weight_used values, the fill
level fallback chain (Spoolman → Inventory → AMS remain) used nullish
coalescing (??), which doesn't fall through on 0. A stale inventory
fill of 0% permanently shadowed the correct real-time AMS remain value.
Viewer users were unaffected because their group lacked the
inventory:view_assignments permission, so the query never fired.
Now when inventory says 0% but AMS hardware reports positive remain,
the inventory value is bypassed in favor of the live AMS data.