fix(nfc): auto-recover a wedged CCID reader via USB power-cycle #85

Merged
padreug merged 1 commit from fix/nfc-reader-auto-recovery into dev 2026-08-09 16:36:47 +00:00
Owner

The problem

The Feitian R502-CL (and cheap CCID readers generally) periodically wedge: the reader keeps detecting a card (status=reading) but every APDU returns "card absent or mute" (SCARD_E_NO_SMARTCARD / ICC_MUTE). We confirmed on-device that only a USB power-cycle clears it — restarting pcscd or the app does not. Until now this left cash-out/cash-in taps dead until someone physically replugged the reader.

The fix — self-healing

Detect the wedge and power-cycle the reader's USB automatically:

  • nfc-service.ts: count consecutive read failures; after 3 (gated by a 30 s cooldown so a still-wedged reader can't reset-loop) trigger nfc-reader-reset.service. A completed read (Bolt Card or not) clears the count, so normal fumbles don't trip it. nfc-pcsc then re-detects the reader on USB hotplug with no app restart.
  • batm3.nix: nfc-reader-reset.service (oneshot, root) re-binds the reader's USB device (a software replug). It's reader-agnostic — matches the USB CCID interface class (0x0B) — so it also covers a future ACR1252U swap with no config change. A polkit rule lets the unprivileged bitspire app start just that one unit (mirrors the existing pcscd polkit grant).

Validated live on the batm3

  • Mechanism: USB unbind/bind alone → running app (pid unchanged) logs reader disconnected → status=ready. ✅
  • Polkit: bitspire (non-root) successfully starts nfc-reader-reset.service. ✅
  • Full chain: reset service finds the reader by class, power-cycles it, app auto-recovers. ✅
  • Deployed app's nfc-service.js carries the trigger; pnpm typecheck + 7 nfc tests + prettier all green.

The durable fix is a better reader — the research points to the ACS ACR1252U (large antenna for behind-panel mounting, firmware-upgradable, PC/SC drop-in) or the reference Identiv uTrust 3700 F / HID Omnikey 5022 CL. This PR makes any reader's wedge a ~2 s self-heal in the meantime, and the reset helper already works with those readers too.

🤖 Generated with Claude Code

## The problem The Feitian R502-CL (and cheap CCID readers generally) periodically **wedge**: the reader keeps detecting a card (`status=reading`) but every APDU returns **"card absent or mute"** (`SCARD_E_NO_SMARTCARD` / ICC_MUTE). We confirmed on-device that **only a USB power-cycle clears it** — restarting `pcscd` or the app does **not**. Until now this left cash-out/cash-in taps dead until someone physically replugged the reader. ## The fix — self-healing Detect the wedge and power-cycle the reader's USB automatically: - **`nfc-service.ts`**: count consecutive read failures; after **3** (gated by a **30 s cooldown** so a still-wedged reader can't reset-loop) trigger `nfc-reader-reset.service`. A completed read (Bolt Card or not) clears the count, so normal fumbles don't trip it. `nfc-pcsc` then re-detects the reader on USB hotplug **with no app restart**. - **`batm3.nix`**: `nfc-reader-reset.service` (oneshot, root) re-binds the reader's USB device (a software replug). It's **reader-agnostic** — matches the USB **CCID interface class (0x0B)** — so it also covers a future **ACR1252U** swap with no config change. A **polkit** rule lets the unprivileged `bitspire` app start just that one unit (mirrors the existing pcscd polkit grant). ## Validated live on the batm3 - Mechanism: USB unbind/bind alone → running app (pid unchanged) logs `reader disconnected` → `status=ready`. ✅ - Polkit: `bitspire` (non-root) successfully starts `nfc-reader-reset.service`. ✅ - Full chain: reset service finds the reader by class, power-cycles it, app auto-recovers. ✅ - Deployed app's `nfc-service.js` carries the trigger; `pnpm typecheck` + 7 nfc tests + prettier all green. ## Hardware track (separate, recommended) The durable fix is a **better reader** — the research points to the **ACS ACR1252U** (large antenna for behind-panel mounting, firmware-upgradable, PC/SC drop-in) or the reference **Identiv uTrust 3700 F** / **HID Omnikey 5022 CL**. This PR makes any reader's wedge a ~2 s self-heal in the meantime, and the reset helper already works with those readers too. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The Feitian R502-CL (and cheap CCID readers generally) can wedge: it keeps
detecting a card but every APDU returns "card absent or mute", and ONLY a
USB power-cycle clears it — restarting pcscd or the app does not (confirmed
on-device). Until now that left cash-out/cash-in taps dead until a manual
replug.

- nfc-service.ts: count consecutive read failures; after 3 (gated by a 30s
  cooldown so a still-wedged reader can't reset-loop) trigger
  nfc-reader-reset.service. nfc-pcsc then re-detects the reader on USB
  hotplug with no app restart (verified live).
- batm3.nix: nfc-reader-reset.service (oneshot, root) re-binds the reader's
  USB device (a software replug); reader-agnostic via the CCID interface
  class (0x0B) so it also covers a future ACR1252U. A polkit rule lets the
  unprivileged `bitspire` app start just that one unit.

Hardware track (separate): the durable fix is a better reader (ACR1252U —
large antenna for behind-panel, firmware-upgradable). This change makes any
reader's wedge a ~2s self-heal in the meantime.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
padreug deleted branch fix/nfc-reader-auto-recovery 2026-08-09 16:36:47 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/bitspire!85
No description provided.