Note
This is a daily beta build (2026-03-01). It contains the latest fixes and improvements but may have undiscovered issues.
Docker users: Update by pulling the new image:
docker pull ghcr.io/maziggy/bambuddy:0.2.2b1
or
docker pull maziggy/bambuddy:0.2.2b1
**Tip:** Use [Watchtower](https://containrrr.dev/watchtower/) to automatically update when new daily builds are pushed.
Improved
- SpoolBuddy Touch-Friendly UI — Enlarged all interactive elements across the SpoolBuddy kiosk UI for comfortable finger use on the 1024×600 RPi touchscreen. Bottom nav icons and labels increased (20→24px icons, 10→12px labels, 48→56px bar height). Top bar printer selector and clock enlarged. Dashboard stats bar compacted, printers card removed (printer selection via top bar is sufficient), section headers and device status text bumped up. AMS page single-slot cards, spool visualizations, and fill bars enlarged. AMS unit cards get larger spool previews (56→64px), bigger material/slot text, and larger humidity/temperature indicators. Inventory spool cards, settings page headers, and calibration inputs all sized up to meet 44px minimum tap targets. The AMS slot configuration modal now renders in a two-column full-screen layout on the kiosk display (filament list on left, K-profile and color picker on right) instead of the standard centered dialog, eliminating scrolling.
New Features
- SpoolBuddy Inline Spool Cards — Placing an NFC-tagged spool on the SpoolBuddy reader now shows spool info directly in the dashboard's right panel instead of a separate modal overlay. Known spools display a SpoolIcon with color/brand/material, a large remaining-weight readout with fill bar, and a weight comparison grid, with action buttons for "Assign to AMS", "Sync Weight", and "Close". Unknown tags show the tag UID, scale weight, and offer "Add to Inventory" or "Link to Spool" actions. The card stays visible if the tag is removed (for continued interaction) and won't re-appear for the same tag after dismissal — but re-placing a tag after removal shows it again. The idle spool animation displays when no tag is detected.
- SpoolBuddy AMS Page: External Slots & Slot Configuration — The SpoolBuddy AMS page (
/spoolbuddy/ams) now displays external spool slots (single nozzle: "Ext", dual nozzle: "Ext-L"/"Ext-R") and AMS-HT units in a compact horizontal row below the regular AMS grid, fitting within the 1024×600 kiosk display without scrolling. Clicking any AMS, AMS-HT, or external slot opens theConfigureAmsSlotModalto configure filament type and color — the same modal used on the main Printers page. Dual-nozzle printers show L/R nozzle badges on each AMS unit. Temperature and humidity are displayed with threshold-colored SVG icons (green/gold/red) matching the Bambu Lab style on the main printer cards, using the configured AMS humidity and temperature thresholds from settings. - SpoolBuddy Dashboard Redesign — Redesigned the SpoolBuddy dashboard with a two-column layout: left column shows device connection status (scale and NFC with state-colored icons — green when device is online, gray when offline) and a compact printers list with live status indicators; right column shows the current spool card. Cards use a dashed border style for a cleaner look. The large weight display card was removed in favor of the inline scale reading in the device card.
- SpoolBuddy Kiosk Auth Bypass via API Key — When Bambuddy auth is enabled, the SpoolBuddy kiosk (Chromium on RPi) was redirected to the login page because the
ProtectedRouterequires a user object fromGET /auth/me, which only accepted JWT tokens. The/auth/meendpoint now also accepts API keys (viaAuthorization: Bearer bb_xxxorX-API-Keyheader) and returns a synthetic admin user with all permissions. The frontend'sAuthContextreads an optional?token=URL parameter on first load, stores it in localStorage, and strips it from the URL to prevent leakage via browser history or referrer. The install script now includes the API key in the kiosk URL (/spoolbuddy?token=${API_KEY}), so the device authenticates automatically on boot without manual login. - Daily Beta Builds — Added a release script (
docker-publish-daily-beta.sh) that reads the currentAPP_VERSIONfrom config, builds a multi-arch Docker image, pushes to both GHCR and Docker Hub, and creates/updates a GitHub prerelease with changelog notes. Daily builds overwrite the same beta version tag (e.g.,0.2.2b1) — users pull the latest by re-pulling the tag or using Watchtower. Beta images are never tagged aslatest.
Fixed
- Printer Card Loses Info When Print Is Paused (#562) — When a print was paused (via G-code pause command or user action), the printer card showed the print as finished — the progress bar, print name, ETA, layer count, and cover image all disappeared, replaced by the idle "Ready to Print" placeholder. The display conditions only checked for
state === 'RUNNING'but not'PAUSE', even though other parts of the same page (Skip Objects button, Stop/Resume controls) already handled both states correctly. Now shows print progress info for bothRUNNINGandPAUSEstates, and the status label correctly reads "Paused" instead of the hardcoded "Printing" fallback. - SpoolBuddy "Assign to AMS" Slot Shows Empty Fields in Slicer — After assigning a spool to an AMS slot via SpoolBuddy's "Assign to AMS" button, the slicer's slot overview showed the correct filament, but opening the slot detail card showed all fields empty/unselected. Two bugs: (1) the
assign_spoolbackend called the cloud API with the rawslicer_filamentvalue including its version suffix (e.g.,PFUS9ac902733670a9_07), which returned a 404; the silent fallback sent thesetting_idastray_info_idxinstead of the realfilament_id(e.g.,PFUS9ac902733670a9instead ofP4d64437), and the slicer couldn't resolve the preset; (2) noSlotPresetMappingwas saved, so Bambuddy's own ConfigureAmsSlotModal couldn't identify the active preset when reopened. Now strips version suffixes before the cloud lookup, resolves the realfilament_idvia the cloud API (with local preset and generic ID fallbacks), includes the brand name intray_sub_brands, and saves the slot preset mapping from the frontend after assignment. - Virtual Printer Bind Server Fails With TLS-Enabled Slicers (#559) — BambuStudio uses TLS on port 3002 for certain printer models (e.g. A1 Mini / N1), but the bind server only spoke plain TCP on both ports 3000 and 3002. The slicer's TLS ClientHello was rejected as an "invalid frame", preventing discovery and connection entirely. Port 3002 now uses TLS (using the VP's existing certificate), while port 3000 remains plain TCP for backwards compatibility. The proxy-mode bind proxy was also updated to use TLS termination on port 3002.
- Queue Returns 500 When Cancelled Print Exists (#558) — When a print was cancelled mid-print, the MQTT completion handler stored status
"aborted"on the queue item, but the response schema only accepts"pending","printing","completed","failed","skipped", or"cancelled". Listing all queue items hit a Pydantic validation error on the invalid status, returning a 500 error. Filtering by a specific status (e.g. "pending") excluded the bad row and worked fine. Now normalises"aborted"to"cancelled"before storing. A startup fixup also converts any existing"aborted"rows. - Tests Send Real Maintenance Notifications — Tests that call
on_print_complete(status="completed")created backgroundasynciotasks (maintenance check, smart plug, notifications) that outlived the test's mock context. When the event loop processed these orphaned tasks,async_sessionwas no longer patched and they queried the real production database — finding real printers with maintenance due and real notification providers, then sending real notifications. Tests now cancel spawned background tasks before the mock context exits. - Virtual Printer Config Changes Ignored Until Toggle Off/On — Changing a virtual printer's mode (e.g. proxy → archive), model, access code, bind IP, remote interface IP, or target printer via the UI updated the database but the running VP instance was never restarted.
sync_from_db()skipped any VP whose ID was already in the running instances dict without checking if config had changed. Now compares critical fields between the running instance and DB record and restarts the VP when a difference is detected. - Sidebar Navigation Ignores User Permissions — All sidebar navigation items (Archives, Queue, Stats, Profiles, Maintenance, Projects, Inventory, Files) were visible to every user regardless of their role's permissions. Only the Settings item was permission-gated. Now each nav item is hidden when the user lacks the corresponding read permission (e.g.,
archives:read,queue:read,library:read). The Printers item remains always visible as the home page. Also added the missinginventory:read|create|update|deletepermissions to the frontend Permission type (they existed in the backend but were absent from the frontend type definition). - Camera Button Clickable Without Permission & ffmpeg Process Leak (#550) — Two camera issues in multi-user environments (e.g., classrooms with multiple printers). First, the camera button on the printer card was clickable even when the user's role lacked
camera:viewpermission. Now disabled with a permission tooltip, matching the existing pattern forprinters:controlon the chamber light button. Second, ffmpeg processes (~240MB each) were never cleaned up after closing a camera stream. Thestop_camera_streamendpoint calledterminate()but neverwait()ed orkill()ed, and HTTP disconnect detection in the streaming response only checked between frames — if the generator was blocked reading from ffmpeg stdout, disconnect was never detected (due to TCP send buffer masking the closed connection). Three fixes: (1) the stop endpoint now usesterminate()→wait(2s)→kill()→wait(); (2) each stream gets a background disconnect monitor task that pollsrequest.is_disconnected()every 2 seconds independently of the frame loop, directly killing the ffmpeg process on disconnect; (3) a periodic cleanup (every 60s) scans/procfor any ffmpeg process with a Bambu RTSP URL (rtsps://bblp:) that isn't in an active stream andSIGKILLs it — catching orphans that survive app restarts or generator abandonment. - Windows Install Fails With "Syntax of the Command Is Incorrect" (#544) — The
start_bambuddy.batPython hash verification used a multi-linefor /f "usebackq"with a backtick-delimited command split across lines. Windows CMD cannot parse line breaks inside backtick-delimitedfor /fcommands, causing "The syntax of the command is incorrect" immediately after downloading Python. The entire block was also redundant — it downloaded a separate checksum file from python.org and re-verified the hash, butverify_sha256had already checked the archive against the pinned hash on the previous line. Removed the duplicate verification block. Also had a secondary bug: always downloaded theamd64checksum even onarm64systems. - Queue Badge Shows on Incompatible Printers (#486) — The purple queue counter badge in the printer card header showed on all printers of the same model when a job was scheduled for "any [model]", even if the printer didn't have the matching filament color loaded. The
PrinterQueueWidget(which shows "Clear Plate & Start") already filtered by filament type and color, but the badge count used the raw unfiltered queue length. Now applies the same filament compatibility filter to the badge count. - SpoolBuddy Daemon Can't Find Hardware Drivers — The daemon's
nfc_reader.pyandscale_reader.pyimportread_tagandscale_diagas bare modules, but these files live inspoolbuddy/scripts/which isn't on Python's module search path. The systemd service setsWorkingDirectorytospoolbuddy/and runspython -m daemon.main, so only thespoolbuddy/anddaemon/directories are onsys.path. Addedscripts/tosys.pathat daemon startup, resolved relative to the module file so it works regardless of install path. Also moved theread_tagimport insideNFCReader.__init__'s try/except block — it was previously outside, so a missing module crashed the entire daemon instead of gracefully skipping NFC polling. Demoted hardware-not-available log messages from ERROR to INFO since missing modules are expected when hardware isn't connected. - SpoolBuddy Scale Tare & Calibration Not Applied — The SpoolBuddy scale tare and calibrate buttons on the Settings page queued commands but never executed them. Five bugs in the chain: (1) the daemon received the
tarecommand via heartbeat but never calledscale.tare()— a comment said "need cross-task communication" but the ScaleReader was already available in the shared dict; (2) no API endpoint existed for the daemon to report the new tare offset back to the backend database, so tare results were lost; (3) when calibration values changed in heartbeat responses, the daemon updated its config object but never calledscale.update_calibration(), so the ScaleReader kept using its initial values forever; (4) the heartbeat response that delivered the tare command still contained pre-tare calibration values, which immediately overwrote the new tare offset back to zero; (5) theset-factorendpoint computedcalibration_factorusing the DBtare_offset, which could be stale or zero if the tare hadn't persisted yet — producing a wildly wrong factor (e.g., 5000g displayed with empty scale). Added aPOST /devices/{device_id}/calibration/set-tareendpoint andupdate_tare()API client method. The heartbeat loop now executesscale.tare()when the tare command is received, persists the result via the new endpoint, propagates calibration changes to the ScaleReader instance, and skips calibration sync on the heartbeat cycle that delivers a tare command. The calibration flow now captures the raw ADC at tare time and sends it alongside the loaded-weight ADC in step 2, so the factor is computed from the actual tare reference rather than the DB value — making calibration self-contained and independent of the tare persistence round-trip. The calibration weight input uses a compact touch-friendly numpad since the RPi kiosk has no physical keyboard. - A1 Mini Shows "Unknown" Status After MQTT Payload Decode Failure (#549) — Some printer firmware versions (observed on A1 Mini 01.07.02.00) occasionally send MQTT payloads containing non-UTF-8 bytes. The
_on_messagehandler calledmsg.payload.decode()(strict UTF-8), and the resultingUnicodeDecodeErrorwas not caught — onlyjson.JSONDecodeErrorwas handled. The entire message was silently dropped, causing printer status to show "unknown", temperatures to read 0°C, and AMS data to disappear. Now catchesUnicodeDecodeErrorand falls back todecode(errors="replace"), which substitutes invalid bytes with U+FFFD while keeping the JSON structure intact. Logs a warning for diagnostics. - H2C Dual Nozzle Variant (O1C2) Not Recognized (#489) — The H2C dual nozzle variant reports model code
O1C2via MQTT, but onlyO1Cwas in the recognized model maps. This caused the camera to use the wrong protocol (chamber image on port 6000 instead of RTSP on port 322) — the printer immediately closed the connection, producing a reconnect loop. Also affected model display names, chamber temperature support detection, linear rail classification, and virtual printer model mapping. AddedO1C2to all model ID maps across backend and frontend. - Support Package Leaks Full Subnet IPs and Misdetects Docker Network Mode — Three support package fixes. First, the network section included full subnet addresses (e.g.,
192.168.192.0/24); now masks the first two octets (x.x.192.0/24). Second,network_mode_hintusedlen(interfaces) > 2which always reported "bridge" on single-NIC hosts even withnetwork_mode: host, becauseget_network_interfaces()excludes Docker infrastructure interfaces. Now checks for the presence of Docker interfaces (docker0,br-*,veth*) viasocket.if_nameindex()— these are only visible when the container shares the host network namespace. Third,developer_modewas still null for most users because the MQTTfunfield was only parsed inside theprintkey; some firmware versions send it at the top level of the payload. Now also checks top-levelfun. Also added avirtual_printerssection with mode, model, enabled/running status, and pending file count for each configured virtual printer. - SpoolBuddy Scale Calibration Lost After Reboot — The SpoolBuddy daemon generated its device ID from the MAC address of whichever network interface
Path.iterdir()returned first, but filesystem iteration order is non-deterministic. On different boots, the daemon could picketh0(MAC ending3100) orwlan0(MAC ending3102), producing a differentdevice_ideach time. Since calibration values (tare_offset,calibration_factor) are stored per device ID in the backend database, a new ID meant registering as a brand-new uncalibrated device. Fixed by sorting network interfaces alphabetically before selection, ensuring the same interface (and thus the same device ID) is always chosen. - SpoolBuddy NFC Reader Fails to Detect Tags — The PN5180 NFC reader had two polling issues. First, each
activate_type_a()call that returnedNone(no tag) corrupted the PN5180 transceive state — subsequent calls silently failed even when a tag was physically present, making it impossible to detect tags placed after startup (only tags already on the reader during init were detected). Fixed by performing a full hardware reset (RST pin toggle + RF re-init, ~240ms) before every idle poll, giving a ~1.8 Hz effective poll rate. Second, after a successful SELECT the card stayed in ACTIVE state and ignored subsequent WUPA/REQA, causing false "tag removed" events after ~1 second. Fixed with a light RF off/on cycle (13ms) before each poll when a tag is present, resetting the card to IDLE for re-selection. Also added error-based auto-recovery (full hardware reset after 10 consecutive poll exceptions), periodic status logging every 60 seconds, and accurate heartbeat reporting of NFC/scale health.
Improved
- SpoolBuddy Scale Value Stabilization — The SpoolBuddy daemon now suppresses redundant scale weight reports: only sends updates when the weight changes by ≥2g. Previously every 1-second report interval sent a reading regardless of change, and stability state flips (stable ↔ unstable) also triggered reports — when ADC noise kept the spread hovering around the 2g stability threshold, the flag toggled every cycle, forcing a report with a slightly different weight each time. Removed stability flipping as a report trigger (the stable flag is still included in each report for consumers). Also increased the NAU7802 moving average window from 5 to 20 samples (500ms → 2s) to smooth ADC noise. The frontend also applies a 3g display threshold as defense-in-depth.
- SpoolBuddy TopBar: Online Printer Selection — The printer selector in the SpoolBuddy top bar now only shows online printers and auto-selects the first online printer. If the currently selected printer goes offline, it automatically switches to the next available online printer. Also replaced the placeholder icon with the SpoolBuddy logo. Renamed the connection status label from "Online" to "Backend" for clarity.
- SpoolBuddy Assign to AMS Redesign — The "Assign to AMS" sub-modal (opened from the spool card) is now a full-screen overlay that reuses the
AmsUnitCardcomponent from the AMS page. Regular AMS units display in a 2-column grid with the same spool visualization, fill bars, and material labels. AMS-HT and external slots (Ext / Ext-L / Ext-R on dual-nozzle printers) appear in a compact horizontal row below. Clicking any slot auto-configures the filament via a singleassignSpoolAPI call — the backend handles both the DB assignment and MQTT configuration. The printer selector was removed from the modal since the top bar already provides printer selection. Dual-nozzle printers show L/R nozzle badges on each AMS unit. - Filament ID Conversion Utility — Extracted filament_id ↔ setting_id conversion logic into a shared utility (
backend/app/utils/filament_ids.py). Theassign_spoolendpoint now normalizesslicer_filament(which can be stored in either filament_id format like "GFL05" or setting_id format like "GFSL05_07") into the correcttray_info_idxandsetting_idfor the MQTT command. Previouslysetting_idwas always sent as empty string, which could cause BambuStudio to not resolve the filament preset for the AMS slot. - Updates Card Separates Firmware and Software Settings — The Updates card on the Settings page mixed printer firmware and Bambuddy software update toggles with no visual grouping. Now splits the card into two labeled sections ("Printer Firmware" and "Bambuddy Software") separated by a divider, making it clear which toggles control what.
- SpoolBuddy Test Coverage — Added integration tests for all 12 SpoolBuddy API endpoints (21 backend tests covering device registration/re-registration, heartbeat status and pending commands, NFC tag scan/match/removal, scale reading broadcast, spool weight calculation, and scale calibration including tare, set-factor, and zero-delta error handling) and component tests for the three main SpoolBuddy frontend components (20 frontend tests covering WeightDisplay weight formatting and status indicators, SpoolInfoCard spool info rendering and action callbacks, UnknownTagCard tag display, and TagDetectedModal open/close/escape behavior with known and unknown spool views).