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>
Hammering a flaky CCID reader with rapid re-reads wedges it into a
present↔empty storm (only a USB replug clears it). After a failed read,
ignore card re-detections for 1.5s; successful reads don't cool down.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Retrying a read hammered the cheap CCID reader into a stuck present↔empty
loop (only cleared by a reboot), so drop the retry: a read is a single
attempt and the user re-taps if the RF link drops mid-read. Also skip the
Capability-Container round-trip in the common case — NTAG424 Bolt Cards use
NDEF FileID E104, so try E104/0004 directly and only read the CC to discover
the id if both fail. Fewer APDUs → a read completes inside a shorter stable
window.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First real-card tap read the NDEF file with id 0004 and got "not a Bolt
Card" — NTAG424 (Bolt Cards) use FileID E104. Read the Capability Container
(EF E103) after selecting the NDEF app to learn the advertised NDEF FileID,
then read that file; fall back to E104/0004. Tolerates a transient transmit
error (surfaced as a retryable status; the next tap re-reads).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Main-process driver over nfc-pcsc (PC/SC). On tap it reads the NTAG424
Type-4 NDEF file via ISO7816 APDUs (select NDEF app D2760000850101 →
select file → ReadBinary NLEN + message) and extracts the lnurlw voucher
(fresh SUN p/c per tap), forwarding it to the renderer on `nfc:card-tapped`
(+ `nfc:status`). Lazy, guarded import — a missing reader/pcscd just
reports 'unavailable', never breaking the cash-out QR path. Preload
removeAllListeners guards against a double payment-trigger on renderer reload.
- electron/nfc-service.ts: startNfcReader() + readNdefLnurlw()/extractLnurlw().
- electron/nfc-service.test.ts: 7 tests (NDEF URI extraction, Type-4 read
sequence incl. AID select, empty-file + select-fail handling).
- main.ts start + IPC forward; preload + electron.d.ts listeners.
- add nfc-pcsc dep (native @pokusew/pcsclite; nix build handling next).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>