Conversation
- 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
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.
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.
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.
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.
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)
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.
The timezone fix (ed36eaf) replaced datetime.utcnow() with datetime.now(timezone.utc) across ~80 call sites, but SQLAlchemy's SQLite DateTime columns strip tzinfo on read, returning naive datetimes. Any Python-side comparison (subtraction, <, >) between an aware "now" and a naive DB value raises TypeError. Add `if value.tzinfo is None: value = value.replace(tzinfo=timezone.utc)` guards at all 8 affected comparison sites: - maintenance.py: last_performed_at subtraction (500 on /maintenance/overview) - auth.py: API key expires_at check (2 locations) - print_scheduler.py: scheduled_time comparison - smart_plug_manager.py: auto_off_pending_since elapsed calc - smart_plugs.py: power_alert_last_triggered cooldown - main.py: last_runtime_update elapsed calc - archives.py: timelapse completed_at fallback
The timezone fix (ed36eaf) replaced datetime.utcnow() with datetime.now(timezone.utc) across ~80 call sites, but SQLAlchemy's SQLite DateTime columns strip tzinfo on read, returning naive datetimes. Any Python-side comparison (subtraction, <, >) between an aware "now" and a naive DB value raises TypeError. Add `if value.tzinfo is None: value = value.replace(tzinfo=timezone.utc)` guards at all 9 affected comparison sites: - maintenance.py: last_performed_at subtraction (2 code paths — days-based and hours-based intervals both crashed on /maintenance/overview) - auth.py: API key expires_at check (2 locations) - print_scheduler.py: scheduled_time comparison - smart_plug_manager.py: auto_off_pending_since elapsed calc - smart_plugs.py: power_alert_last_triggered cooldown - main.py: last_runtime_update elapsed calc - archives.py: timelapse completed_at fallback
The purple queue counter badge in the printer card header used the raw unfiltered queue item count, so it appeared on ALL printers of the same model when a job was scheduled for "any [model]" — even printers without the matching filament color. The PrinterQueueWidget below it (which shows "Clear Plate & Start") already filtered by filament type + color. Apply the same filament compatibility filter (type check + color override check) to the badge count so it only shows on printers that can actually run the queued jobs.
The start_bambuddy.bat launcher had Unix (LF) line endings. When a user's git is configured with core.autocrlf=false or input, the file is checked out with LF endings and cmd.exe fails with "the syntax of the command is incorrect" before reaching the hash check. Add .gitattributes forcing CRLF for *.bat files so they always get correct line endings on checkout regardless of git config.
Frontend: Replace inline SpoolInfoCard/UnknownTagCard with full-screen TagDetectedModal that auto-opens on NFC tag detection. Known spools show remaining weight, fill bar, and offer "Assign to AMS" (new sub-modal with printer selector + AMS slot grid) and "Sync Weight". Unknown tags offer "Add to Inventory" and "Link to Spool". Modal stays open on tag removal, won't re-open for dismissed tags, reopens on re-place after removal. Daemon: Fix PN5180 NFC reader silently stopping tag detection when the reader drifts into a stuck state. Add auto-recovery (full hardware reset after 10 consecutive errors), preventive RF cycling every 60s when idle, and periodic status logging. Heartbeat now reports actual nfc_ok/scale_ok from reader instances instead of hardcoded True.
Frontend: Replace inline SpoolInfoCard/UnknownTagCard with full-screen TagDetectedModal that auto-opens on NFC tag detection. Known spools show remaining weight, fill bar, and offer "Assign to AMS" (new sub-modal with printer selector + AMS slot grid) and "Sync Weight". Unknown tags offer "Add to Inventory" and "Link to Spool". Modal stays open on tag removal, won't re-open for dismissed tags, reopens on re-place after removal. Daemon: Fix PN5180 NFC reader silently stopping tag detection. The reader drifts into a stuck state where activate_type_a() returns None without raising exceptions, making error-based recovery useless. Changed the preventive idle maintenance from a lightweight RF off/on cycle to a full hardware reset (RST pin toggle + RF re-init) every 60s — the same reset that a service restart performs. Also added error-based auto-recovery after 10 consecutive exceptions, promoted poll errors to WARNING, added periodic status logging, and fixed heartbeat to report actual nfc_ok/ scale_ok instead of hardcoded True.
Frontend: Replace inline SpoolInfoCard/UnknownTagCard with full-screen TagDetectedModal that auto-opens on NFC tag detection. Known spools show remaining weight, fill bar, and offer "Assign to AMS" (new sub-modal with printer selector + AMS slot grid) and "Sync Weight". Unknown tags offer "Add to Inventory" and "Link to Spool". Modal stays open on tag removal, won't re-open for dismissed tags, reopens on re-place after removal. Daemon: Fix PN5180 NFC reader failing to maintain tag detection. After a successful SELECT the card stays in ACTIVE state and ignores subsequent WUPA/REQA, causing immediate false "tag removed" events. Added a brief RF off/on cycle (13ms) before each poll to force cards back to IDLE state. Also added a preventive full hardware reset every 60s when idle to recover from deeper stuck states where activate_type_a() silently returns None without exceptions. Heartbeat now reports actual nfc_ok/ scale_ok instead of hardcoded True.
Frontend: Replace inline SpoolInfoCard/UnknownTagCard with full-screen TagDetectedModal that auto-opens on NFC tag detection. Known spools show remaining weight, fill bar, and offer "Assign to AMS" (new sub-modal with printer selector + AMS slot grid) and "Sync Weight". Unknown tags offer "Add to Inventory" and "Link to Spool". Modal stays open on tag removal, won't re-open for dismissed tags, reopens on re-place after removal. Daemon: Fix PN5180 NFC reader failing to maintain tag detection. After a successful SELECT the card stays in ACTIVE state and ignores subsequent WUPA/REQA. Added a conditional RF off/on cycle (13ms) before each poll, but only when a tag is present — resets card from ACTIVE to IDLE for re-selection. The cycle is skipped when idle to avoid degrading reader state with continuous RF toggling, which prevented new tags from being detected after removal. Also added a preventive full hardware reset every 60s when idle to recover from deeper stuck states, error-based recovery after 10 consecutive exceptions, and accurate heartbeat reporting of NFC/scale health.
Frontend: Replace inline SpoolInfoCard/UnknownTagCard with full-screen TagDetectedModal that auto-opens on NFC tag detection. Known spools show remaining weight, fill bar, and offer "Assign to AMS" (new sub-modal with printer selector + AMS slot grid) and "Sync Weight". Unknown tags offer "Add to Inventory" and "Link to Spool". Modal stays open on tag removal, won't re-open for dismissed tags, reopens on re-place after removal. Daemon: Fix PN5180 NFC reader failing to detect tags. Each activate_type_a() call returning None corrupts the PN5180 transceive state, silently preventing all subsequent tag detection. Fixed by performing a full hardware reset (RST pin toggle + RF re-init) before every idle poll (~240ms, ~1.8 Hz rate). When a tag is present, a light RF off/on cycle (13ms) resets the card from ACTIVE to IDLE state for continuous re-selection. Also added error-based recovery, periodic status logging, and accurate heartbeat NFC/scale health reporting.
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.
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. Skip calibration sync on the
heartbeat cycle that delivers a tare command, since the response
predates the tare and would overwrite it with stale values.
The tare and calibrate buttons on the Settings page queued commands
but never executed them due to four 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
4. The heartbeat response delivering the tare command still had
pre-tare values, immediately overwriting the new offset to zero
Added set-tare endpoint + API client method, and fixed heartbeat
loop to execute tare, persist the result, propagate calibration
changes to the ScaleReader, and skip calibration sync on the
heartbeat cycle that delivers a tare command.
Also replaced the calibration weight input with a touch-friendly
numpad since the RPi kiosk has no physical keyboard.
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).
Some firmware versions send MQTT payloads with non-UTF-8 bytes. UnicodeDecodeError was uncaught, silently dropping the entire message and leaving printer status as "unknown" with 0°C temps and no AMS. Fall back to decode(errors="replace") to keep JSON parseable.
The H2C dual nozzle variant reports model code O1C2 via MQTT, but only O1C was recognized. This caused the camera to use the wrong protocol (chamber image on port 6000 instead of RTSP on port 322), producing a reconnect loop. Added O1C2 to all model ID maps across 8 files.
Fix camera button permissions & ffmpeg process leak (#550) Camera button on printer card was clickable without camera:view permission. ffmpeg processes (~240MB each) accumulated after closing camera streams because: (1) stop endpoint called terminate() without wait()/kill(), (2) HTTP disconnect detection only ran between frames so was blocked when the generator was stuck on stdout read, and (3) no mechanism caught processes orphaned by generator abandonment or app restarts. - Add camera:view permission check + tooltip to camera button - Fix stop endpoint: terminate() → wait(2s) → kill() → wait() - Add background disconnect monitor (polls every 2s, kills ffmpeg directly on disconnect) - Add periodic /proc scan (every 60s) that SIGKILLs any ffmpeg with rtsps://bblp: not in an active stream - Add noCamera i18n key to all 6 locales - Fix camera API test mocks for async wait() and pid attribute
…er card (#569) * Add printer card print flow with drag-drop and reusable LibraryUploadModal Extract inline upload modal from FileManagerPage into a reusable LibraryUploadModal component. Add a Print button and drag-drop zone to printer cards that upload files to the library and open PrintModal with the printer pre-selected. Only .gcode/.gcode.3mf files are accepted for printing. PrintModal hides printer selector when a printer is provided via initialSelectedPrinterIds prop. * Add printer compatibility check for drag-drop and upload-to-print flows Validates sliced_for_model from file metadata against the target printer model before opening the print modal, preventing users from printing with incompatible files. * Improve FileUploadModal: auto-close on success, inline errors, file validation, and i18n - Close modal automatically after successful upload instead of showing summary - Show printer compatibility errors inline in the modal instead of closing - Add validateFile and accept props to restrict file types for print flow - Add onFileUploaded error return to prevent modal close on incompatible files - Internationalize all hardcoded error/warning strings across 6 locales * Clean up incompatible files from library and fix print button i18n - Delete uploaded file from library when printer compatibility check fails in both drag-drop and modal upload flows - Fix print button tooltip to use existing common.print i18n key - Fix accept attribute to use .gcode,.3mf for proper browser support * Add printer information modal accessible from card 3-dot menu Replace inline IP/serial display at bottom of printer card with a dedicated "Printer Information" modal opened via the card menu. The modal shows model, status, state, IP address (copyable), serial number (copyable), WiFi signal, firmware, developer mode, nozzle count, SD card, auto-archive, total print hours, location, and added date, along with the printer image. Includes full i18n support across all 6 locales and accessibility attributes (role="dialog", aria-modal). * Extract getPrinterImage and getWifiStrength into shared printer utils --------- Co-authored-by: MartinNYHC <mz@v8w.de>
Inline callback refs on preset buttons called scrollIntoView on every re-render, creating a scroll → re-render → scrollIntoView loop on Windows. Replace with a useEffect + data attribute approach that only scrolls when the selected preset actually changes.
* Add energy cost to archive card * Replace Disc3 icon with Coins in ArchivesPage --------- Co-authored-by: MartinNYHC <mz@v8w.de>
[Fix]: AMS slot modal infinite scroll loop on Windows
The slicer_binary_path key was left in the settings table from earlier slicer integration research but is no longer referenced anywhere in the codebase. Add a startup migration step that deletes orphaned settings keys so they don't persist in backups.
Closes #579 — adds HUF with symbol "Ft" to the supported currencies list so Hungarian users can track filament costs in their local currency.
Profiles like "PLA Support for PETG PETG Basic @bambu Lab H2D" have filament_type PETG, but both the frontend and backend name parsers found "PLA" first (iterating material types in order). The MQTT command sent tray_type=PLA and tray_info_idx=GFL99 (PLA generic), so the slicer displayed PLA instead of PETG. - Frontend parsePresetName(): detect "X Support for Y" pattern, extract material after "Support for" - Frontend ConfigureAmsSlotModal: prefer corrected parsed material over stored localPreset.filament_type for tray_type, tray_info_idx, and temperature fallback - Backend _parse_material_from_name(): same "Support for" handling for future profile imports - Backend assign_spool: prioritize spool.material over lp.filament_type for generic filament ID lookup
FTP upload: reduce chunk size from 1MB to 64KB for smooth progress bar updates (~1s intervals instead of 20+ second gaps). Skip voidresp() for all printer models — H2D delays the 226 response by 30+ seconds after data transfer, causing a hang at 100%. Add transfer speed and TLS handshake timing to logs for diagnosing slow connections. Print/Schedule modal: widen from max-w-lg (512px) to max-w-2xl (672px) to accommodate long filament profile names like "PLA Support for PETG PETG Basic @bambu Lab H2D 0.4 nozzle".
The isSlicedFile() check only matched .gcode or .gcode.3mf extensions, so archives from .3mf prints (the standard Bambu slicer output) showed a "SOURCE" badge instead of "GCODE". Since .3mf can be either sliced or a raw CAD export, now checks the archive's total_layers and print_time_seconds metadata to distinguish them. Also passes the original filename when creating archives from file manager prints.
- Extract shared detectSystemDateFormat() helper to eliminate duplicated system locale detection in date.ts - Derive SUPPORTED_CURRENCIES from CURRENCY_SYMBOLS map instead of maintaining a duplicate hardcoded list - Consolidate redundant time-parsing regex paths into one - Use applyTimeFormat() consistently in formatETA() - Add exhaustive switch default for type safety
Extract repeated helper functions from components into shared utils (colors.ts, date.ts, file.ts) to reduce duplication: - isLightColor, parseFilamentColor, hexToColorName → utils/colors.ts - formatMediaTime, formatDurationFromHours → utils/date.ts - formatFileSize guard hardened for NaN/negative → utils/file.ts - formatBytes (ToastContext), formatTime (Timelapse*) deduplicated Functions with subtly different behavior are intentionally kept local (formatFilament, formatStorageSize, formatBytes, formatUptime, formatDateTime, formatDate, formatTimeAgo) to preserve exact UI output.
The previous fix only filtered status codes (< 0x4000) from the print_error field. Firmware can also send the same false positive through the hms array in MQTT, which had no such filter. Apply the same < 0x4000 check to the HMS parser so status/phase indicators are skipped regardless of which MQTT field carries them.
The firmware check only mapped display names (e.g., "H2D Pro") to API keys but not raw SSDP device codes (e.g., "O1E", "O2D"). If a printer's model was stored as the raw SSDP code, the firmware check either failed or matched the wrong model — H2D Pro matched against H2D firmware, showing a false update-available badge. Add all known SSDP device codes to MODEL_TO_API_KEY so firmware versions resolve correctly regardless of how the model was stored.
Refactor frontend utility functions to reduce duplication
…cy controls Floating bug report button submits issues via bambuddy.cool relay (no GitHub token needed locally). Collects 30s debug logs with printer push_all, sanitizes all sensitive data, uploads logs as files to GitHub. Screenshot upload/paste/drag with JPEG compression. Translated into all 7 languages. Includes 21 tests.
* Fix AMS slot modal not scrolling to preselected filament The scroll useEffect fired before the loading spinner was replaced by the preset list, so the DOM element didn't exist yet. The ref guard was then set, preventing any retry once buttons appeared. - Move isLoading computation before the scroll effect and add it as a dependency so the effect re-runs when loading completes - Gate scroll on !isLoading so it only runs when buttons exist - Only mark scrolledToRef after the element is actually found - Use requestAnimationFrame to scroll after browser layout - Use block:'center' so the selected item is clearly visible Preselect filament for printer-configured AMS slots When an AMS slot was configured directly on the printer (no savedPresetId), the modal didn't preselect the filament even though it was in the list. Add a third fallback that matches trayInfoIdx against the full filteredPresets list (cloud and builtin sources). * Restore scrolledToRef lost during merge The useRef import, scrolledToRef declaration, and its reset were dropped during the 0.2.2b1 merge, causing a ReferenceError in the scroll-to-preselected-filament effect. * Fix preset scroll loop and wire up useEffect scroll logic - Remove inline ref callbacks with scrollIntoView on both preset button lists (fullscreen + standard) to stop the infinite scroll loop on Windows - Add data-preset-id attribute so the existing useEffect/querySelector scroll logic can actually find the elements - Replace filteredPresets dependency with builtinFilaments in the selection useEffect to prevent search keystrokes from overriding manual preset selection * Adjust scroll behavior in ConfigureAmsSlotModal --------- Co-authored-by: MartinNYHC <mz@v8w.de>
| defaults = { | ||
| "filename": f"abort_test_{counter}.3mf", | ||
| "print_name": f"Abort Test Print {counter}", | ||
| "file_path": f"/tmp/abort_test_{counter}.3mf", |
Check warning
Code scanning / Bandit
Probable insecure usage of temp file/directory. Warning test
Owner
Author
There was a problem hiding this comment.
Test-only warning with no real security impact
Merged
maziggy
added a commit
that referenced
this pull request
Mar 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
v0.2.2b1
Highlights
New Features
Bug Fixes
Contributed by @aneopsy.
Improved
Community Contributors
Thank you to everyone who contributed to this release: