22 Commits
Author SHA1 Message Date
maziggy 66a3604d9b Draft commit message:
fix(spoolbuddy): gate display wake on stable scale reading

  A noisy load cell that bounced ≥50 g around its midpoint kept the kiosk
  screen permanently lit. The wake threshold check ran against last_wake_grams
  which itself advanced to noisy values, so every bounce back across 50 g
  re-fired display.wake(), keeping the FIFO reader pumping wlopm --on
  faster than swayidle could trigger wlopm --off.

  The fix gates wake on the scale's `stable` flag — only readings that
  have settled within 2 g over a 1 s window count as a real spool
  placement / removal. Unstable noise can't fire wake and can't advance
  last_wake_grams, so the next genuine settled change is still measured
  against the right baseline.

  Three regression tests pin the contract: noisy ±60 g unstable readings
  never wake, a settled >50 g jump wakes, a noise burst between two
  settled readings doesn't poison last_wake_grams.
2026-05-03 09:22:35 +02:00
maziggy 2aabbe5d37 fix(spoolbuddy): sync SSH key over heartbeat to survive Bambuddy keypair rotation
Bambuddy's SSH keypair under <DATA_DIR>/spoolbuddy/ssh/ regenerates whenever
  the data dir is recreated (volume remount, container recreate, fresh deploy).
  The daemon previously only fetched the pubkey at registration, so any
  rotation after a successful boot left ~/.ssh/authorized_keys pointing at
  a stale public half — every Update click then failed with "Connection
  closed by authenticating user spoolbuddy [preauth]" until the daemon was
  restarted by hand. Each prior registration also appended a fresh entry
  without pruning, accumulating stale Bambuddy-tagged keys indefinitely.

  - HeartbeatResponse now carries ssh_public_key; the heartbeat route reads
    it via the same try/except shape as the register route so a missing or
    unreadable backend key doesn't break telemetry.
  - _deploy_ssh_key() strips lines tagged bambuddy-spoolbuddy and writes
    the current key once. No-op when already in sync (no mtime churn on
    every heartbeat). User-managed entries are preserved.
  - Daemon heartbeat handler calls _deploy_ssh_key when the response
    carries a key, so rotations propagate within one heartbeat instead
    of requiring a service restart.

  Tests: 5 unit (creates-when-missing, replace-stale-pileup, preserve-user-keys,
  idempotent, swallows-write-errors) + 2 backend integration (heartbeat carries
  the key; backend key-read failure leaves ssh_public_key None but the
  heartbeat still 200s).
2026-05-01 10:44:01 +02:00
maziggy 25203b3491 feat(spoolbuddy): auto-wake display on NFC tag scan or scale activity (#945)
Display now powers on via wlopm when the daemon detects an NFC tag or
  a significant weight change (>=50g, i.e. spool placed/removed) while
  the screen is blanked.  Minor scale fluctuations no longer wake the
  display or prevent blanking.  The daemon only re-blanks screens it woke
  itself — touch-based wake/blank stays with swayidle so the two don't
  conflict.  Daemon discovers the Wayland session from the shared runtime
  dir since it runs as a systemd service outside the compositor.
2026-04-12 10:51:11 +02:00
maziggy b76d6210cf Add SpoolBuddy quick menu with power control and system commands (#893)
Swipe down from the top of the SpoolBuddy display to open a quick-access
  menu for toggling printer smart plugs and managing the device (restart
  daemon, restart browser, reboot, shutdown). All destructive actions
  require confirmation.

  Backend: new POST /spoolbuddy/devices/{id}/system/command endpoint
  queuing reboot/shutdown/restart_daemon/restart_browser commands.
  Daemon: handles commands via subprocess (sudo reboot, systemctl restart).
  Frontend: SpoolBuddyQuickMenu component, swipe-down gesture detection,
  i18n keys for all 7 locales.
2026-04-08 11:29:44 +02:00
maziggy 77cb7158d2 Add SpoolBuddy System tab with live OS stats from Raspberry Pi
The daemon now collects CPU temp, core count, load average, memory/disk
  usage, OS info, and system uptime every heartbeat using stdlib-only reads
  from /proc and /sys. Stats are sent as a JSON blob in the heartbeat
  payload, stored in a new system_stats TEXT column, and displayed in a
  new "System" tab in SpoolBuddy Settings with color-coded usage bars.
2026-03-26 09:57:09 +01:00
maziggy bae394d277 Add debug logging to NTAG write/verify path
Temporary diagnostics to identify which step of the NTAG write
  fails: per-page ACK status, reactivation, read-back, or data
  mismatch. Enable DEBUG level for pn5180 module.
2026-03-25 13:21:27 +01:00
maziggy f71367bb4e Fix SpoolBuddy NFC write rejecting NTAG 215 tags with SAK=0x04
NTAG 213/215/216 chips can report SAK 0x04 (MIFARE Ultralight family)
  instead of 0x00 during anticollision. Accept both values for NTAG
  detection and write operations in nfc_reader.py and main.py.
2026-03-25 13:10:11 +01:00
maziggy 2b1fe03159 Fix SpoolBuddy scale first reading and extract hardware drivers
The NAU7802 ADC returns a stale max-scale value (0x7FFFFF) on its
  first conversion after power-up, polluting the moving average and
  making the initial weight report wildly inaccurate. Flush the first
  reading during init().

  Also extract both hardware drivers out of diagnostic scripts into
  proper daemon modules:
  - NAU7802 scale driver: scripts/scale_diag.py -> daemon/nau7802.py
  - PN5180 NFC driver: scripts/read_tag.py -> daemon/pn5180.py

  The production daemon was importing driver classes from test scripts
  since the original SpoolBuddy commit. Diagnostic scripts now import
  from the driver modules. Removed the sys.path hack from main.py.
2026-03-25 11:57:28 +01:00
Keybored 6e648804fc [Feature] Spoolbuddy Fixes and Improvements (#787)
[Feature] Spoolbuddy Fixes and Improvements (#787)
2026-03-24 11:56:33 +01:00
maziggy 5e9481859b Replace SpoolBuddy self-update with SSH-based updates from Bambuddy
The daemon's self-update mechanism (git fetch/reset on its own code) was
  fragile: .git permission errors, self-modifying code mid-run, hardcoded
  main branch. Bambuddy now SSHes into the SpoolBuddy Pi and drives the
  update remotely — matching its own branch, with step-by-step progress
  via WebSocket.

  SSH key pairing is automatic: Bambuddy generates an ED25519 keypair and
  returns the public key in the registration response. The daemon deploys
  it to authorized_keys on first connect — no manual setup needed.

  - New: backend/app/services/spoolbuddy_ssh.py (keypair, SSH commands, update orchestration)
  - Rewritten: trigger_daemon_update endpoint uses SSH instead of pending_command
  - New: GET /spoolbuddy/ssh/public-key endpoint for manual pairing
  - Removed: daemon _perform_update() and cmd=="update" heartbeat handler
  - Updated: install.sh — bash shell, sudoers for systemctl restart, .ssh/ setup
  - Updated: Dockerfile — added openssh-client
  - Updated: frontend — SSH key display, force update button
  - Fixed: update check now compares against APP_VERSION, not GitHub releases
2026-03-23 13:32:42 +01:00
maziggy bc1bba078b Replace SpoolBuddy self-update with SSH-based updates from Bambuddy
The daemon's self-update mechanism (git fetch/reset on its own code) was
  fragile: .git permission errors, self-modifying code mid-run, hardcoded
  main branch. Bambuddy now SSHes into the SpoolBuddy Pi and drives the
  update remotely — matching its own branch, with step-by-step progress
  via WebSocket. Install script updated with SSH access, sudoers entry,
  and --ssh-pubkey flag for pairing.
2026-03-23 12:51:21 +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 f2d28a0ef8 Spoolbuddy frontend fixes 2026-03-02 11:22:52 +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 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 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 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 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 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