Browse Source

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.
maziggy 2 months ago
parent
commit
7a5c0b7a82
2 changed files with 13 additions and 12 deletions
  1. 1 1
      CHANGELOG.md
  2. 12 11
      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.
 - **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.
 - **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 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** — Two PN5180 NFC reader issues caused tags to stop being detected. First, after a successful `activate_type_a()` + SELECT, the card remained in ACTIVE state and would not respond to the next WUPA/REQA, causing every subsequent poll to return `None`. The tag appeared to be "removed" after ~1 second (3 consecutive misses) despite still being physically present, and was never re-detected. Added a brief RF field off/on cycle (13ms) before each poll to force all cards back to IDLE state. Second, the reader could drift into a deeper stuck state where `activate_type_a()` silently returned `None` perpetually without raising exceptions, indistinguishable from "no tag present" and invisible in logs (poll failures were at DEBUG level). Added a preventive full hardware reset (RST pin toggle + RF re-init) every 60 seconds when idle — the same reset a service restart performs. Also added error-based auto-recovery (full hardware reset after 10 consecutive poll exceptions) for exception-raising failures. Poll errors are now logged at WARNING level, a periodic status line logs every 60 seconds, and the heartbeat reports actual `nfc.ok` and `scale.ok` from the reader instances instead of hardcoded `True`.
+- **SpoolBuddy NFC Reader Silently Stops Detecting Tags** — Two PN5180 NFC reader issues caused tags to stop being detected. First, after a successful `activate_type_a()` + SELECT, the card remained in ACTIVE state and would not respond to the next WUPA/REQA, causing every subsequent poll to return `None`. The tag appeared to be "removed" after ~1 second (3 consecutive misses) despite still being physically present, and was never re-detected. Added a conditional RF field off/on cycle (13ms) before each poll, but only when a tag is present — this resets the card from ACTIVE back to IDLE for re-selection. The cycle is skipped when idle to avoid degrading the reader state with continuous unnecessary RF toggling, which prevented new tags from being detected after removal. Second, the reader could drift into a deeper stuck state where `activate_type_a()` silently returned `None` perpetually without raising exceptions, indistinguishable from "no tag present" and invisible in logs (poll failures were at DEBUG level). Added a preventive full hardware reset (RST pin toggle + RF re-init) every 60 seconds when idle — the same reset a service restart performs. Also added error-based auto-recovery (full hardware reset after 10 consecutive poll exceptions) for exception-raising failures. Poll errors are now logged at WARNING level, a periodic status line logs every 60 seconds, and the heartbeat reports actual `nfc.ok` and `scale.ok` from the reader instances instead of hardcoded `True`.
 
 
 ### Improved
 ### 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.
 - **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.

+ 12 - 11
spoolbuddy/daemon/nfc_reader.py

@@ -123,17 +123,18 @@ class NFCReader:
             except Exception as e:
             except Exception as e:
                 logger.warning("Preventive NFC reset failed: %s", e)
                 logger.warning("Preventive NFC reset failed: %s", e)
 
 
-        # Brief RF field cycle before each poll to reset card state.
-        # After a successful SELECT, the card stays in ACTIVE state and won't
-        # respond to the next WUPA/REQA. Toggling RF forces all cards back to
-        # IDLE so they respond to the next activation attempt.
-        try:
-            self._nfc.rf_off()
-            time.sleep(0.003)
-            self._nfc.rf_on()
-            time.sleep(0.010)
-        except Exception:
-            pass  # Will be caught by activate_type_a() error handling below
+        # RF field cycle only when a tag is present — after a successful SELECT,
+        # the card stays in ACTIVE state and won't respond to the next WUPA/REQA.
+        # Toggling RF forces it back to IDLE. Skip when idle (no prior SELECT)
+        # to avoid degrading the reader state with continuous unnecessary cycling.
+        if self._state == NFCState.TAG_PRESENT:
+            try:
+                self._nfc.rf_off()
+                time.sleep(0.003)
+                self._nfc.rf_on()
+                time.sleep(0.010)
+            except Exception:
+                pass  # Will be caught by activate_type_a() error handling below
 
 
         try:
         try:
             result = self._nfc.activate_type_a()
             result = self._nfc.activate_type_a()