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
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.
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.
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.
Notify users when the first layer finishes printing so they can check
adhesion remotely. Triggers once per print when layer 2 begins
(layer_num >= 2, capped at <= 5 to handle reconnects). Includes a
camera snapshot attachment. Adds the on_first_layer_complete toggle
to all notification providers, with backend/frontend/i18n support
across all 7 locales.
Start, monitor, and stop drying sessions for AMS 2 Pro (n3f) and
AMS-HT (n3s) directly from the Printers page. Flame icon in AMS card
header opens a popover with filament-based temperature/duration presets
from BambuStudio. Live countdown status bar shows time remaining.
Backend: cache module_type from MQTT get_version, expose dry_time and
module_type in both WebSocket and REST paths, add supports_drying()
with firmware gating, add server-side guard on start_drying endpoint.
Frontend: drying button, popover with filament select + temp/duration
sliders, status bar, start/stop mutations. i18n for all 7 locales.
Supported: X1/X1C (fw 01.09+), P1P/P1S (fw 01.08+), H2D (fw 01.02.30+),
H2D Pro, X1E. Not supported: P2S, A1, A1 Mini, H2S, H2C.
home_flag bit 18 is set on all printers regardless of hardware,
causing the ethernet badge to appear on WiFi-only models (A1, P1P,
etc.). The previous fix removed the feature entirely, so ethernet
printers lost their badge too.
Now only trusts bit 18 on models with an ethernet port (X1C, X1E,
P1S, P2S, H2D, H2D Pro, H2C, H2S). WiFi-only models always show
the WiFi signal badge.
Sends persistent notifications to the HA dashboard using the existing
HA connection from Settings. Zero config — just select "Home Assistant"
as provider type. Users can forward notifications to mobile via HA
automations.
Large Hueforge 3MF files (~28 MB) can't be downloaded from the
printer's FTP server within the previous 120s max timeout, especially
during active printing. Added 180s and 300s timeout options to the
FTP settings dropdown.
home_flag bit 18 was incorrectly interpreted as "wired/ethernet
connection", causing the ethernet badge to always show — even on
printers without an ethernet port (e.g. A1, P1S). This hid the
WiFi signal indicator entirely.
Removed the wired_network field and ethernet badge UI. The WiFi
signal badge now shows correctly whenever the printer reports
signal strength.
Add {eta} template variable showing wall-clock completion time
(e.g. "15:53" or "3:53 PM") to print_start, print_progress, and
queue_job_started notifications. Respects the user's time_format
setting (12h/24h). The existing {estimated_time} variable continues
to show duration ("1h 23m").
enum.StrEnum was added in Python 3.11, but the documented minimum is
3.10. Add a compatibility shim in backend/app/core/compat.py that falls
back to (str, Enum) on older versions. Updated all 5 import sites and
lowered pyproject.toml target-version to py310.
* AMS Labels addition to PrintersPage
* Added database reinitialization for schema migrations on database restore
* Bug fixes and AMS Label persistence updates
* Update database.py to resolve PR conflicts
* PR Comment Resolution
* Resolve conflicts in database.py for PR#570
* Updates to address PR#570 additional comments
* Optimize visibility handling for popup component
* Improve serial key handling in printers.py
Refactor serial key assignment to handle empty AMS serial gracefully.
* Implement error handling in serial number mapping
Add error handling for serial number mapping.
* Add migration to drop old ams_labels table
---------
Co-authored-by: MartinNYHC <mz@v8w.de>
Parse home_flag bit 18 (0x00040000) from MQTT to detect ethernet
connections. When set, the printer card shows a green "Ethernet"
badge with a cable icon instead of WiFi signal strength in dBm.
The printer info modal also displays "Ethernet" instead of WiFi
signal details.
* feat(queue): show spool grams left in filament slot mapping
* Bumped version
* Add SpoolBuddy AMS slot config, external slots, and dashboard redesign
- AMS page: external spool slots (Ext/Ext-L/Ext-R), click-to-configure
modal on all slots, temperature/humidity threshold-colored indicators,
nozzle L/R badges for dual-nozzle printers, compact AMS-HT layout
- Dashboard: two-column layout with device status + printers list (left)
and current spool card (right), state-colored scale/NFC icons, dashed
border card styling
- Daemon: suppress redundant scale reports (±2g threshold + stability
state change detection) to prevent weight display bouncing
- TopBar: auto-select online printers only, SpoolBuddy logo
* Fix SpoolBuddy daemon crash when read_tag module is missing
NFCReader.__init__ imported read_tag and instantiated PN5180() outside
the try/except block, so a missing module crashed the entire daemon.
Moved the import inside the existing try/except so the daemon gracefully
skips NFC polling — matching the scale reader's existing behavior.
* Fix SpoolBuddy daemon failing to import hardware drivers
The daemon imports read_tag and scale_diag as bare modules, but they
live in spoolbuddy/scripts/ which isn't on sys.path when systemd runs
the daemon. Added scripts/ to sys.path at startup, resolved relative
to the module file. Also moved the read_tag import inside NFCReader's
try/except (was crashing the daemon instead of skipping gracefully)
and demoted hardware-not-available messages from ERROR to INFO.
* Increase scale moving average window to reduce weight bouncing
5 samples at 100ms (500ms window) wasn't enough to smooth NAU7802 ADC
noise — the averaged value still varied by >2g between 1s report
intervals, and the stability state kept flipping, triggering a report
every cycle. Increased to 20 samples (2s window) so noise is smoothed
before reaching the reporting layer.
* Remove stability flipping as scale report trigger
When ADC noise kept the spread hovering around the 2g stability
threshold, the stable flag toggled every cycle, forcing a report with
a slightly different weight each time. Now only actual weight changes
of >=2g trigger reports. The stable flag is still included in each
report for consumers that need it.
* Fix formatting of option elements in FilamentMapping
* Make low stock threshold editable
* Add new filter for low spools
* Update bug report template to require additional fields
* Added toast for invalid imputs with locales, updated inputb field restrictions
* Minor Spoolbuddy frontend improvements
* Updated test_backend.sh
* Updated Spoolbuddy install script to strip down Raspbian
* Updated Spoolbuddy install script
* Add API key auth support to /auth/me for SpoolBuddy kiosk
When Bambuddy auth is enabled, the SpoolBuddy kiosk gets redirected to
the login page because ProtectedRoute requires a user from GET /auth/me,
which only handled JWT tokens. The kiosk daemon already has an API key
but couldn't use it to satisfy the frontend auth check.
- Backend: /auth/me now accepts API keys (Bearer bb_xxx or X-API-Key)
and returns a synthetic admin UserResponse with all permissions
- Frontend: AuthContext reads ?token= from URL on first load, stores in
localStorage, and strips from URL (prevents history/referrer leakage)
- Install script: kiosk URL now includes ?token=${API_KEY}
- Tests: 3 new integration tests (Bearer API key, X-API-Key header,
invalid key rejection)
* SpoolBuddy touch-friendly UI overhaul for 1024x600 kiosk display
Enlarge all interactive elements across 9 SpoolBuddy components to meet
44px minimum tap targets on the RPi touchscreen. Increase nav icons
(20→24px), labels (10→12px), bar heights, section headers, printer
buttons, spool visualizations, fill bars, and status indicators.
Compact the dashboard stats bar and remove the printers card. Add
fullScreen prop to ConfigureAmsSlotModal with two-column layout
(filament list left, K-profile + color right) to eliminate scrolling.
* Minor changes, CSS fixes
* Refactor usageFilter state to remove 'lowstock' option for clarity
* Move var saving to API, add test coverage
* fix: threshold validation and cleanup
* Change test input from '150' to '0'
---------
Co-authored-by: tridev <c.tripod@gmx.ch>
Co-authored-by: MartinNYHC <mz@v8w.de>
Write NTAG213/215/216 tags for third-party spools via the SpoolBuddy
kiosk UI. New "Write" page with three workflows: existing spool, new
spool creation, and tag replacement. Backend encodes 133-byte OpenTag3D
NDEF payloads (material, color, brand, weight, temp). Daemon writes
page-by-page via PN5180 NTAG WRITE command with read-back verification.
Write commands flow through heartbeat polling with WebSocket status
updates. Includes 39 new tests and translations for all 6 languages.
- Redesign settings page with tabbed layout (Device, Display, Scale, Updates)
- Add screen blank timeout: blanks after touch inactivity, tap to wake
- Add CSS brightness filter for HDMI displays (no sysfs on HDMI)
- Add backend `language` field to app settings for server-side persistence
- Sync UI language from backend on kiosk load (separate Chromium instance)
- Top bar clock respects user's time format setting (system/12h/24h)
- Add SpoolBuddy settings translations for all 6 languages (en/de/fr/ja/it/pt-BR)
- Disable Chromium swipe-to-navigate in kiosk install script
- Add `video` group for DSI backlight access
SpoolBuddy's "Link to Spool" used the generic updateSpool API which only
set tag_uid, leaving tag_type and data_origin empty. Now uses linkTagToSpool
with tag_type='generic' and data_origin='nfc_link'.
Added a "Weight Check" inventory column (hidden by default) that compares
each spool's last scale measurement against calculated gross weight with
±50g tolerance. Shows green check for match, yellow warning + sync button
for mismatch. Backend stores last_scale_weight and last_weighed_at on each
spool when weight is synced via SpoolBuddy. Includes edge case handling
when scale weight < core weight. i18n keys added for all 6 locales.
- Add lightweight GET /archives/slim endpoint with column-level SELECT
(13 fields vs 46), skipping duplicate detection for ~70-80% payload
reduction on the stats dashboard
- Add ArchiveSlim Pydantic schema and TypeScript type
- Switch StatsPage and FilamentTrends to use ArchiveSlim
- Fix critical bug in failure_analysis.py: use effective_days for week
count, separate non-date filters, build fresh week_filter per loop
- Fix busiestDay timezone bug: parse YYYY-MM-DD with split() + local
Date constructor instead of new Date() which creates UTC midnight
- Fix success streak ordering: sort by completed_at || created_at
instead of created_at alone
- Add 6 integration tests for /archives/slim endpoint
The tare and calibrate buttons on the Settings page queued commands
but never executed them due to five broken links:
1. Daemon received tare command but never called scale.tare()
2. No endpoint to persist tare offset back to backend DB
3. Heartbeat never called scale.update_calibration()
4. Heartbeat response with stale values overwrote new tare to zero
5. set-factor used DB tare_offset (stale/zero), producing wrong
calibration factor — empty scale showed ~5000g
Fixed daemon to execute tare, persist result, and propagate
calibration. Calibration step now captures raw ADC at tare time
and sends it with step 2, making factor computation self-contained.
Replaced calibration weight input with compact touch numpad for
the RPi kiosk's 1024x600 touchscreen (no physical keyboard).
The tare and calibrate buttons on the Settings page queued commands
but never executed them due to three broken links:
1. Daemon received tare command via heartbeat but never called
scale.tare() — the ScaleReader was available in shared dict
but unused
2. No API endpoint for the daemon to report the new tare offset
back to the backend DB, so tare results were lost
3. Heartbeat updated config but never called
scale.update_calibration(), so ScaleReader kept initial values
Added set-tare endpoint + API client method, and fixed heartbeat
loop to execute tare, persist the result, and propagate calibration
changes to the ScaleReader instance.
Add weight_locked flag to spools that auto-sets when weight_used is
explicitly updated via the API. Both the MQTT AMS remain% auto-sync and
the manual force-sync endpoint skip locked spools. Usage tracker delta
tracking is unaffected. Users can re-enable AMS sync by setting
weight_locked to false.
Queue endpoints now return filament_type, filament_color, layer_height,
nozzle_diameter, and sliced_for_model from the archive or library file.
Previously this data was only available via the archive API.
When scheduling a print to "any printer" (model-based assignment),
users can now override the 3MF's original filament choices per slot.
The override dropdown shows compatible filaments loaded across all
printers of the selected model, filtered by type and nozzle (H2D).
The scheduler matches against overridden type/color instead of the
original 3MF values, preferring printers with exact color matches.
Backend:
- New GET /printers/available-filaments endpoint (aggregates loaded
filaments across printers of a model, includes extruder_id for
dual-nozzle nozzle-aware filtering)
- New filament_overrides JSON column on print_queue table
- Scheduler applies overrides before AMS mapping, clears tray_info_idx
on overridden slots so color matching takes priority
- Printer selection prefers printers with most override color matches
Frontend:
- New FilamentOverride component with per-slot override dropdowns
- Type-filtered options (PLA slots only show PLA)
- Nozzle-aware filtering (H2D: only shows filaments on correct extruder)
- Integrated into PrintModal for model-based queue mode
Tests:
- 11 backend unit tests (color match counting, override application)
- 12 frontend tests (rendering, type/nozzle filtering, interactions)
i18n: All 6 locales (en, de, fr, it, ja, pt-BR)
SpoolBuddy turns a Raspberry Pi 4B with a PN5180 NFC reader and
NAU7802 scale into a filament management station that integrates
with Bambuddy via REST API and WebSocket.
Backend: SpoolBuddyDevice model, 10 REST endpoints (/spoolbuddy/*),
6 WebSocket broadcast types, background offline-detection watchdog.
RPi daemon: asyncio service with concurrent NFC polling (300ms,
MIFARE Classic + Bambu HKDF key derivation), scale reading (10 SPS,
5-sample moving average, stability detection), and 10s heartbeat
with exponential backoff reconnect.
Frontend: kiosk-optimized 1024x600 UI at /spoolbuddy with Dashboard
(live weight + NFC spool detection), AMS Overview, Inventory,
Printers, and Settings pages. useSpoolBuddyState reducer hook
driven by WebSocket CustomEvents.
i18n: all 6 locales (en, de, fr, it, ja, pt-BR).
Parse the MQTT "fun" field bit 0x20000000 to detect whether connected
printers have Developer LAN Mode enabled. Show a persistent orange
warning banner when any printer lacks it, since newer firmware silently
rejects MQTT write commands without developer mode.
- Parse fun field into developer_mode on PrinterState
- Add /printers/developer-mode-warnings lightweight polling endpoint
- Include developer_mode in printer status API and support bundle
- Orange banner with affected printer names and wiki link
- Translations for all 6 locales (en, de, fr, it, ja, pt-BR)
- 7 backend + 4 frontend tests
Add Quick Add mode to the spool form for simplified entry (material +
color + weight only), and a quantity field (1-100) for creating multiple
identical spools at once. Stock spools (no slicer profile) are computed
rather than stored — any spool without slicer_filament shows an amber
"Stock" badge. New filter chips (All/Stock/Configured) on the inventory
page. Backend bulk endpoint creates N spools in one transaction.
Backend:
- SpoolBulkCreate schema with quantity validation (1-100)
- POST /inventory/spools/bulk endpoint
Frontend:
- Quick Add toggle in SpoolFormModal (create mode only)
- Quantity field in FilamentSection (both modes)
- bulkCreateSpools API method and bulkCreateMutation
- Stock column (hidden by default) and stock filter chips
- Inline error rendering moved into FilamentSection
- CellCtx extended with t() for translatable cell content
Tests:
- 13 backend tests (schema validation + endpoint logic)
- 10 frontend tests (validateForm quickAdd + UI behavior)
i18n: 6 locales (en, de, fr, it, ja, pt-BR)
Docs: CHANGELOG, README, website features, wiki inventory
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.
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
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
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
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