github maziggy/bambuddy v0.2.2b1
Daily Beta Build v0.2.2b1 (2026-03-01)

pre-release8 hours ago

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 the ConfigureAmsSlotModal to 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 ProtectedRoute requires a user object from GET /auth/me, which only accepted JWT tokens. The /auth/me endpoint now also accepts API keys (via Authorization: Bearer bb_xxx or X-API-Key header) and returns a synthetic admin user with all permissions. The frontend's AuthContext reads 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 current APP_VERSION from 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 as latest.

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 both RUNNING and PAUSE states, 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_spool backend called the cloud API with the raw slicer_filament value including its version suffix (e.g., PFUS9ac902733670a9_07), which returned a 404; the silent fallback sent the setting_id as tray_info_idx instead of the real filament_id (e.g., PFUS9ac902733670a9 instead of P4d64437), and the slicer couldn't resolve the preset; (2) no SlotPresetMapping was saved, so Bambuddy's own ConfigureAmsSlotModal couldn't identify the active preset when reopened. Now strips version suffixes before the cloud lookup, resolves the real filament_id via the cloud API (with local preset and generic ID fallbacks), includes the brand name in tray_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 background asyncio tasks (maintenance check, smart plug, notifications) that outlived the test's mock context. When the event loop processed these orphaned tasks, async_session was 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 missing inventory:read|create|update|delete permissions 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:view permission. Now disabled with a permission tooltip, matching the existing pattern for printers:control on the chamber light button. Second, ffmpeg processes (~240MB each) were never cleaned up after closing a camera stream. The stop_camera_stream endpoint called terminate() but never wait()ed or kill()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 uses terminate()wait(2s)kill()wait(); (2) each stream gets a background disconnect monitor task that polls request.is_disconnected() every 2 seconds independently of the frame loop, directly killing the ffmpeg process on disconnect; (3) a periodic cleanup (every 60s) scans /proc for any ffmpeg process with a Bambu RTSP URL (rtsps://bblp:) that isn't in an active stream and SIGKILLs it — catching orphans that survive app restarts or generator abandonment.
  • Windows Install Fails With "Syntax of the Command Is Incorrect" (#544) — The start_bambuddy.bat Python hash verification used a multi-line for /f "usebackq" with a backtick-delimited command split across lines. Windows CMD cannot parse line breaks inside backtick-delimited for /f commands, 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, but verify_sha256 had already checked the archive against the pinned hash on the previous line. Removed the duplicate verification block. Also had a secondary bug: always downloaded the amd64 checksum even on arm64 systems.
  • 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.py and scale_reader.py import read_tag and scale_diag as bare modules, but these files live in spoolbuddy/scripts/ which isn't on Python's module search path. The systemd service sets WorkingDirectory to spoolbuddy/ and runs python -m daemon.main, so only the spoolbuddy/ and daemon/ directories are on sys.path. Added scripts/ to sys.path at daemon startup, resolved relative to the module file so it works regardless of install path. Also moved the read_tag import inside NFCReader.__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 tare command via heartbeat but never called scale.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 called scale.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) the set-factor endpoint computed calibration_factor using the DB tare_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 a POST /devices/{device_id}/calibration/set-tare endpoint and update_tare() API client method. The heartbeat loop now executes scale.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_message handler called msg.payload.decode() (strict UTF-8), and the resulting UnicodeDecodeError was not caught — only json.JSONDecodeError was handled. The entire message was silently dropped, causing printer status to show "unknown", temperatures to read 0°C, and AMS data to disappear. Now catches UnicodeDecodeError and falls back to decode(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 O1C2 via MQTT, but only O1C was 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. Added O1C2 to 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_hint used len(interfaces) > 2 which always reported "bridge" on single-NIC hosts even with network_mode: host, because get_network_interfaces() excludes Docker infrastructure interfaces. Now checks for the presence of Docker interfaces (docker0, br-*, veth*) via socket.if_nameindex() — these are only visible when the container shares the host network namespace. Third, developer_mode was still null for most users because the MQTT fun field was only parsed inside the print key; some firmware versions send it at the top level of the payload. Now also checks top-level fun. Also added a virtual_printers section 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 pick eth0 (MAC ending 3100) or wlan0 (MAC ending 3102), producing a different device_id each 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 returned None (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 AmsUnitCard component 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 single assignSpool API 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). The assign_spool endpoint now normalizes slicer_filament (which can be stored in either filament_id format like "GFL05" or setting_id format like "GFSL05_07") into the correct tray_info_idx and setting_id for the MQTT command. Previously setting_id was 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).

Don't miss a new bambuddy release

NewReleases is sending notifications on new releases.