39 Commits
Author SHA1 Message Date
maziggy 18700cf5da Fix SpoolBuddy daemon reporting stale version in update check
The daemon had a hardcoded __version__ = "0.2.2b1" that was never
  bumped, causing the update check to always show an update available.
  Changed to read APP_VERSION from backend/app/core/config.py at import
  time so the daemon version stays in sync automatically.
2026-03-22 13:08:45 +01:00
maziggy d750e8f0e0 Added Spoolbuddy frontend/backend tests 2026-03-22 12:55:35 +01:00
maziggy cf2203914d [Feature] SpoolBuddy OTA updates via Bambuddy UI
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
2026-03-22 12:40:25 +01:00
maziggy f943420ea2 Add SpoolBuddy NFC tag writing with OpenTag3D format
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.
2026-03-02 13:56:15 +01:00
maziggy 628a814271 Redesign SpoolBuddy settings page, add language/time format support
- 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
2026-03-02 12:44:45 +01:00
maziggy 48b492f619 Spoolbuddy
1. Device tab — repo URL unlinked

  Replaced the <a> tag with a <span> so the GitHub URL is displayed as plain text (no clickable link in kiosk mode).

  2. Display controls — moved to frontend

  Root cause: The daemon runs as a system user without Wayland access, and the kiosk uses an HDMI display (no sysfs backlight). Neither wlopm, vcgencmd, nor sysfs writes
  can work in this setup.

  Fix — frontend-controlled display in SpoolBuddyLayout.tsx:
  - Brightness: CSS filter: brightness(X) applied to the layout root. Immediate visual dimming that works on any display type.
  - Screen blanking: Full-screen black overlay (z-[9999]) shown after the configured inactivity timeout. Touch anywhere to wake.
  - Activity tracking: Resets on pointerdown/keydown events AND on WebSocket-driven NFC/scale changes (weight update, tag scan).
  - Queries device data every 15s to read the current brightness and blank timeout values.

  Daemon display_control.py — Simplified:
  - Removed vcgencmd/wlopm/subprocess usage entirely
  - _blank/_unblank are now state-tracking no-ops (log only)
  - DSI backlight brightness via sysfs is preserved as a bonus for DSI displays
  - Updated docstring to explain the architecture

  3. Scale tab — redesigned with step indicator

  Idle state: Same as before — weight card with tare/calibrate buttons.

  Calibration wizard: Two-column layout:
  - Left column (~64px): Vertical step indicator with numbered circles, checkmark for completed steps, and a connecting line
  - Right column: Live weight bar (with stable/settling indicator), step-specific content (instructions or numpad), and action buttons pinned to bottom via mt-auto

  The numpad buttons are min-h-[52px] with text-lg — large enough for touch on 1024x600. The weight info card is hidden during the weight entry step to maximize vertical
  space.
2026-03-02 11:58:10 +01:00
maziggy 28fda7b1a0 Display Controls (brightness + blanking)
Root cause: The daemon used wlopm for screen blanking, but wlopm was never installed. Additionally, the daemon runs as the spoolbuddy system user which has no access to
   the Wayland socket, so Wayland-based tools can't work.

  Fix in display_control.py:
  - Replaced wlopm --off/--on with vcgencmd display_power 0/1 — this is a Raspberry Pi firmware-level command that's pre-installed and works without Wayland socket access
  - Added shutil.which("vcgencmd") check at init to avoid repeated failures on non-RPi hardware
  - Added explicit PermissionError handling for brightness writes with a helpful message about the video group

  Fix in install.sh:
  - Added video group to the spoolbuddy service user's groups (was: gpio, spi, i2c → now: gpio, spi, i2c, video), which grants access to both vcgencmd and sysfs backlight
   files

  Scale Tab Numpad

  Root cause: On the 1024x600 kiosk screen (~376px available content height), the weight info card + numpad + action buttons exceeded the space, causing tiny buttons and
  overlapping.

  Fix in SpoolBuddySettingsPage.tsx:
  - Hide the weight info card during weight entry step (calStep !== 'weight'), reclaiming ~70px
  - Compact inline weight reading in the step header (small dot + monospace text) so users can still see the live scale value
  - Larger numpad buttons: min-h-[56px] with text-lg font size (was no min-height, text-sm)
  - Added active:scale-95 for tactile touch feedback
  - mt-auto on action buttons to push them to the bottom, preventing overlap
  - Removed the wrapping card around the calibration flow to save vertical padding
2026-03-02 11:40:45 +01:00
maziggy f2d28a0ef8 Spoolbuddy frontend fixes 2026-03-02 11:22:52 +01:00
maziggy 9f6acd6e77 Updated Spoolbuddy install script 2026-03-02 09:53:31 +01:00
maziggy 4fb4e5faac Fix SpoolBuddy scale calibration lost after reboot
_get_mac_id() used Path.iterdir() to find a network interface for the
  device ID, but iteration order is non-deterministic. On different boots
  it could pick eth0 (MAC …3100) or wlan0 (MAC …3102), producing a
  different device_id each time. Since calibration is stored per device ID
  in the backend DB, a new ID meant registering as an uncalibrated device.

  Sort interfaces alphabetically so the same one is always selected.
2026-03-01 13:27:04 +01:00
maziggy bf33dccfb4 Fix SpoolBuddy scale tare & calibration not being applied
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.
2026-02-28 09:35:16 +01:00
maziggy 7bc549e985 Fix SpoolBuddy scale tare & calibration not being applied
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.
2026-02-28 09:31:39 +01:00
maziggy d466a4a86e Add SpoolBuddy tag detection modal and fix NFC reader polling
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.
2026-02-28 09:17:04 +01:00
maziggy 7a5c0b7a82 Add SpoolBuddy tag detection modal and fix NFC reader polling
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.
2026-02-28 09:12:41 +01:00
maziggy 2d814ea5f4 Add SpoolBuddy tag detection modal and fix NFC reader polling
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.
2026-02-28 09:07:31 +01:00
maziggy 464c5969ca Add SpoolBuddy tag detection modal and fix NFC reader silent failure
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.
2026-02-28 09:02:46 +01:00
maziggy 7767851975 Add SpoolBuddy tag detection modal and fix NFC reader silent failure
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.
2026-02-28 08:54:39 +01:00
maziggy 42b07d8bfd 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)
2026-02-27 13:34:19 +01:00
maziggy cc13166fea Updated Spoolbuddy install script 2026-02-27 13:33:47 +01:00
maziggy f7e65aa8e3 Updated Spoolbuddy install script to strip down Raspbian 2026-02-27 13:33:47 +01:00
maziggy 2811e2ec34 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.
2026-02-27 13:31:51 +01:00
maziggy 12fe0741b5 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.
2026-02-27 13:31:51 +01:00
maziggy 541f8926b4 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.
2026-02-27 13:31:51 +01:00
maziggy 8f3d194596 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.
2026-02-27 13:31:51 +01:00
maziggy 46f209e2ac 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
2026-02-27 13:31:48 +01:00
maziggy 0597392a9d Cleaned up spoolbuddy folder 2026-02-23 12:39:24 +01:00
maziggy 414dc4a511 Updated RPI install script 2026-02-23 11:34:43 +01:00
maziggy eba4184097 Updated RPI install script 2026-02-23 11:29:17 +01:00
maziggy 09e6bfd121 moved from scale_diag import NAU7802 and NAU7802() inside the try/except block in ScaleReader.__init__. Previously, NAU7802() opened /dev/i2c-0 in its
constructor before the try/except, so the FileNotFoundError was unhandled and crashed the entire daemon via asyncio.gather().

Now when the scale hardware isn't available (no /dev/i2c-0), it logs "Scale init failed" and sets self._ok = False, so scale_poll_loop will skip polling with "Scale not
available, skipping scale polling" — same graceful degradation the NFC reader already has.
2026-02-23 11:19:58 +01:00
maziggy 851b039dd8 Added Spoolbuddy RPI install script 2026-02-23 10:30:48 +01:00
maziggy 7a1b22cff0 Fixed imports 2026-02-22 15:43:53 +01:00
maziggy a7d60d94ae Add systemd script for Spoolbuddy daemon 2026-02-22 15:42:05 +01:00
maziggy dbcfed7a52 Add systemd script for Spoolbuddy daemon 2026-02-22 15:38:59 +01:00
maziggy 04b9e6c0aa Add systemd script for Spoolbuddy daemon 2026-02-22 15:33:41 +01:00
maziggy aeca15b585 Add SpoolBuddy integration as optional filament management add-on
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).
2026-02-22 15:27:54 +01:00
maziggy a71e5db2bc Set up scale readings 2026-02-22 14:28:24 +01:00
maziggy 1260851961 Set up PN5180 NFC reader 2026-02-22 13:51:13 +01:00
maziggy 9b51db2748 Changed PN5180 pinout 2026-02-22 11:24:52 +01:00
maziggy 5de9adb940 Added Spoolbuddy cabling plan 2026-02-22 10:51:43 +01:00