Browse Source

Fix Read Tag diagnostic failing on NTAG tags

  The read_tag.py diagnostic script had stale PN5180 NTAG methods: TX CRC
  was off (should be on), no Crypto1 clear, no IDLE→TRANSCEIVE state
  reset, and the PN5180 drops the card after each READ batch requiring
  reactivation between 4-page reads. Also rejected SAK 0x04 as
  unsupported. Synced register setup with daemon and added per-batch
  card reactivation.
maziggy 2 months ago
parent
commit
a321a709b5
3 changed files with 38 additions and 28 deletions
  1. 1 1
      CHANGELOG.md
  2. 1 1
      spoolbuddy/daemon/pn5180.py
  3. 36 26
      spoolbuddy/scripts/read_tag.py

+ 1 - 1
CHANGELOG.md

@@ -22,7 +22,7 @@ All notable changes to Bambuddy will be documented in this file.
 - **SpoolBuddy System Tab** — Added a "System" tab to SpoolBuddy Settings showing live OS stats from the Raspberry Pi: CPU temperature, core count, load average, memory usage, disk usage, OS distro/kernel/architecture, Python version, and system uptime. Stats are collected by the daemon every heartbeat (10s) using stdlib-only reads from `/proc` and `/sys` — no additional dependencies required. Usage bars turn amber at 70% and red at 90%; CPU temperature is color-coded green/amber/red.
 - **SpoolBuddy System Tab** — Added a "System" tab to SpoolBuddy Settings showing live OS stats from the Raspberry Pi: CPU temperature, core count, load average, memory usage, disk usage, OS distro/kernel/architecture, Python version, and system uptime. Stats are collected by the daemon every heartbeat (10s) using stdlib-only reads from `/proc` and `/sys` — no additional dependencies required. Usage bars turn amber at 70% and red at 90%; CPU temperature is color-coded green/amber/red.
 
 
 ### Fixed
 ### Fixed
-- **SpoolBuddy Read Tag Diagnostic Fails on NTAG Tags** — The `read_tag.py` diagnostic script had three issues preventing NTAG reads: (1) SAK `0x04` (MIFARE Ultralight family) was rejected as "unsupported tag type" — now accepts both `0x00` and `0x04`. (2) `ntag_read_pages` had TX CRC off (should be on per NTAG spec), no Crypto1 clear, and no IDLE→TRANSCEIVE state reset — synced with the daemon's working implementation. (3) `ntag_write_page`/`ntag_write_pages` had the same stale CRC/state issues plus unreliable ACK checking and post-write verification that fails on the PN5180 — synced with daemon.
+- **SpoolBuddy Read Tag Diagnostic Fails on NTAG Tags** — The `read_tag.py` diagnostic script had four issues preventing NTAG reads: (1) SAK `0x04` (MIFARE Ultralight family) was rejected as "unsupported tag type" — now accepts both `0x00` and `0x04`. (2) `ntag_read_pages` had TX CRC off (should be on per NTAG spec), no Crypto1 clear, and no IDLE→TRANSCEIVE state reset. (3) The PN5180 drops the NTAG card after each 4-page READ batch — added card reactivation between batches so multi-batch reads (pages 4-20) succeed. (4) `ntag_write_page`/`ntag_write_pages` had the same stale CRC/state issues plus unreliable ACK checking and post-write verification — synced with daemon.
 - **Delete Tag Leaves Stale Tag Type** — The "Delete Tag" button in the spool edit modal only cleared `tag_uid` but left `tray_uuid`, `tag_type`, and `data_origin` intact. All tag-related fields are now cleared together.
 - **Delete Tag Leaves Stale Tag Type** — The "Delete Tag" button in the spool edit modal only cleared `tag_uid` but left `tray_uuid`, `tag_type`, and `data_origin` intact. All tag-related fields are now cleared together.
 - **SpoolBuddy NFC Write Fails on NTAG Tags** — Multiple issues prevented writing to NTAG 213/215/216 tags. (1) Some chips report SAK `0x04` (MIFARE Ultralight family) instead of `0x00` during anticollision — both `0x00` and `0x04` are now accepted. (2) TX CRC was disabled for NTAG commands but the spec requires it — enabled for both WRITE and READ. (3) The PN5180 state machine needed IDLE→TRANSCEIVE resets (not just `set_transceive_mode()`) and Crypto1 cleared before NTAG operations. (4) The 4-bit WRITE ACK cannot be captured by the PN5180 (SOF detected but no RX_IRQ) — removed per-page ACK checking. (5) Post-write read-back verification also failed (second READ command gets no response from the PN5180) — removed verification since the tag reliably ACKs each write.
 - **SpoolBuddy NFC Write Fails on NTAG Tags** — Multiple issues prevented writing to NTAG 213/215/216 tags. (1) Some chips report SAK `0x04` (MIFARE Ultralight family) instead of `0x00` during anticollision — both `0x00` and `0x04` are now accepted. (2) TX CRC was disabled for NTAG commands but the spec requires it — enabled for both WRITE and READ. (3) The PN5180 state machine needed IDLE→TRANSCEIVE resets (not just `set_transceive_mode()`) and Crypto1 cleared before NTAG operations. (4) The 4-bit WRITE ACK cannot be captured by the PN5180 (SOF detected but no RX_IRQ) — removed per-page ACK checking. (5) Post-write read-back verification also failed (second READ command gets no response from the PN5180) — removed verification since the tag reliably ACKs each write.
 - **Database Connection Pool Exhaustion on Large Printer Farms** — Users with 100+ printers connected simultaneously experienced `QueuePool limit of size 10 overflow 20 reached, connection timed out` errors. Increased the SQLAlchemy connection pool from 30 total (10 base + 20 overflow) to 220 (20 base + 200 overflow), and raised the SQLite busy_timeout from 5 to 15 seconds to reduce write contention under heavy concurrent MQTT updates.
 - **Database Connection Pool Exhaustion on Large Printer Farms** — Users with 100+ printers connected simultaneously experienced `QueuePool limit of size 10 overflow 20 reached, connection timed out` errors. Increased the SQLAlchemy connection pool from 30 total (10 base + 20 overflow) to 220 (20 base + 200 overflow), and raised the SQLite busy_timeout from 5 to 15 seconds to reduce write contention under heavy concurrent MQTT updates.

+ 1 - 1
spoolbuddy/daemon/pn5180.py

@@ -417,7 +417,7 @@ class PN5180:
             data = self.read_data(16)
             data = self.read_data(16)
             pages_to_copy = min(4, num_pages - pages_read)
             pages_to_copy = min(4, num_pages - pages_read)
             result.extend(data[: pages_to_copy * 4])
             result.extend(data[: pages_to_copy * 4])
-            pages_read += 4  # Always advances by 4 (READ returns 4 pages)
+            pages_read += 4
 
 
         return bytes(result)
         return bytes(result)
 
 

+ 36 - 26
spoolbuddy/scripts/read_tag.py

@@ -372,46 +372,56 @@ class PN5180:
 
 
         return self.read_data(16)
         return self.read_data(16)
 
 
-    def ntag_read_pages(self, start_page: int, num_pages: int) -> bytes | None:
-        """Read NTAG pages (4 bytes each). No authentication required.
-
-        Uses NTAG READ command (0x30) which returns 4 pages (16 bytes) at a time.
-        """
-        # One-time setup: Crypto1 off, TX CRC on, RX CRC off, IDLE→TRANSCEIVE
-        self.write_reg_and(0x00, 0xFFFFFFBF)  # Crypto1 off
-        self.write_reg_or(0x19, 0x01)  # TX CRC on
-        self.write_reg_and(0x12, 0xFFFFFFFE)  # RX CRC off
-        self.write_reg(0x03, 0xFFFFFFFF)  # Clear IRQs
+    def _ntag_setup_and_read_one(self, page: int) -> bytes | None:
+        """Setup registers and read 4 NTAG pages (16 bytes) starting at page."""
+        # Crypto1 off, TX CRC on, RX CRC off
+        self.write_reg_and(0x00, 0xFFFFFFBF)
+        self.write_reg_or(0x19, 0x01)
+        self.write_reg_and(0x12, 0xFFFFFFFE)
+        self.write_reg(0x03, 0xFFFFFFFF)
 
 
+        # IDLE → TRANSCEIVE
         sys_cfg = self.read_reg(0x00)
         sys_cfg = self.read_reg(0x00)
-        self.write_reg(0x00, sys_cfg & 0xFFFFFFF8)  # IDLE
+        self.write_reg(0x00, sys_cfg & 0xFFFFFFF8)
         time.sleep(0.001)
         time.sleep(0.001)
-        self.write_reg(0x00, (sys_cfg & 0xFFFFFFF8) | 0x03)  # TRANSCEIVE
+        self.write_reg(0x00, (sys_cfg & 0xFFFFFFF8) | 0x03)
         time.sleep(0.002)
         time.sleep(0.002)
 
 
+        # READ command: 0x30 + page → returns 16 bytes (4 pages)
+        self.send_data([0x30, page])
+        time.sleep(0.010)
+
+        rx_status = self.read_reg(0x13)
+        rx_len = rx_status & 0x1FF
+        if rx_len < 16:
+            print(f"    NTAG read page {page}: rx_len={rx_len} (expected >=16)")
+            return None
+
+        return self.read_data(16)
+
+    def ntag_read_pages(self, start_page: int, num_pages: int) -> bytes | None:
+        """Read NTAG pages (4 bytes each). No authentication required.
+
+        Uses NTAG READ command (0x30) which returns 4 pages (16 bytes) at a time.
+        The PN5180 drops the NTAG card after each READ, so we reactivate between
+        each 4-page batch.
+        """
         result = bytearray()
         result = bytearray()
         pages_read = 0
         pages_read = 0
         while pages_read < num_pages:
         while pages_read < num_pages:
             if pages_read > 0:
             if pages_read > 0:
-                # Subsequent iterations: just clear IRQs and re-enter TRANSCEIVE
-                self.write_reg(0x03, 0xFFFFFFFF)
-                self.set_transceive_mode()
-                time.sleep(0.001)
-
-            # READ command: 0x30 + page number → returns 16 bytes (4 pages)
-            self.send_data([0x30, start_page + pages_read])
-            time.sleep(0.010)
+                # PN5180 loses the card after each READ — must reactivate
+                if self.reactivate_card() is None:
+                    print(f"    Failed to reactivate card before page {start_page + pages_read}")
+                    return None
 
 
-            rx_status = self.read_reg(0x13)
-            rx_len = rx_status & 0x1FF
-            if rx_len < 16:
-                print(f"    NTAG read page {start_page + pages_read}: rx_len={rx_len} (expected >=16)")
+            data = self._ntag_setup_and_read_one(start_page + pages_read)
+            if data is None:
                 return None
                 return None
 
 
-            data = self.read_data(16)
             pages_to_copy = min(4, num_pages - pages_read)
             pages_to_copy = min(4, num_pages - pages_read)
             result.extend(data[: pages_to_copy * 4])
             result.extend(data[: pages_to_copy * 4])
-            pages_read += 4  # Always advances by 4 (READ returns 4 pages)
+            pages_read += 4
 
 
         return bytes(result)
         return bytes(result)