Browse Source

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.
maziggy 2 months ago
parent
commit
464c5969ca
2 changed files with 8 additions and 3 deletions
  1. 1 1
      CHANGELOG.md
  2. 7 2
      spoolbuddy/daemon/nfc_reader.py

+ 1 - 1
CHANGELOG.md

@@ -17,7 +17,7 @@ All notable changes to Bambuddy will be documented in this file.
 - **Windows Install Fails With "Syntax of the Command Is Incorrect"** ([#544](https://github.com/maziggy/bambuddy/issues/544)) — The `start_bambuddy.bat` launcher had Unix (LF) line endings instead of Windows (CRLF). When a user's git config has `core.autocrlf=false` or `input`, the file is checked out with LF endings and `cmd.exe` cannot parse it. Added a `.gitattributes` file that forces CRLF for all `.bat` files regardless of git config.
 - **Queue Badge Shows on Incompatible Printers** ([#486](https://github.com/maziggy/bambuddy/issues/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 NFC Reader Silently Stops Detecting Tags** — The PN5180 NFC reader could drift into a stuck state where `activate_type_a()` silently returned `None` on every poll, indistinguishable from "no tag present". The daemon continued running with no errors logged (poll failures were at DEBUG level), making the problem invisible. The heartbeat also always reported `nfc_ok=True` regardless of actual reader health. Added auto-recovery: after 10 consecutive poll errors the reader performs a full hardware reset (SPI reset + RF re-init). A preventive RF off/on cycle runs every 60 seconds when idle to prevent reader drift. Poll errors are now logged at WARNING level on first occurrence, and a periodic status line logs every 60 seconds showing poll count, error count, and state. The heartbeat now reports actual `nfc.ok` and `scale.ok` status from the reader instances instead of hardcoded `True`.
+- **SpoolBuddy NFC Reader Silently Stops Detecting Tags** — The PN5180 NFC reader could drift into a stuck state where `activate_type_a()` silently returned `None` on every poll, indistinguishable from "no tag present". The daemon continued running with no errors logged (poll failures were at DEBUG level), making the problem invisible. The heartbeat also always reported `nfc_ok=True` regardless of actual reader health. The stuck state doesn't raise exceptions — the reader just returns `None` perpetually — so error-based recovery alone is insufficient. Added a preventive full hardware reset (RST pin toggle + RF re-init) every 60 seconds when idle, which is the same reset sequence that a service restart performs. Also added error-based auto-recovery (full hardware reset after 10 consecutive poll exceptions) for a different class of failure. Poll errors are now logged at WARNING level on first occurrence, and a periodic status line logs every 60 seconds showing poll count, error count, and state. The heartbeat now reports actual `nfc.ok` and `scale.ok` status from the reader instances instead of hardcoded `True`.
 
 ### 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.

+ 7 - 2
spoolbuddy/daemon/nfc_reader.py

@@ -114,9 +114,14 @@ class NFCReader:
             )
             self._last_status_log = now
 
-        # Preventive RF cycle when idle (prevents reader drift)
+        # Preventive full hardware reset when idle (prevents reader drift into
+        # stuck state where activate_type_a() silently returns None without errors)
         if self._state == NFCState.IDLE and now - self._last_rf_cycle >= RF_CYCLE_INTERVAL:
-            self._rf_cycle()
+            try:
+                self._init_rf()
+                logger.debug("Preventive NFC hardware reset completed")
+            except Exception as e:
+                logger.warning("Preventive NFC reset failed: %s", e)
 
         try:
             result = self._nfc.activate_type_a()