Skip to content

Add Sony RC-S956 support and multi-device PN53x discovery#224

Merged
codebutler merged 9 commits intomasterfrom
feature/pn533-rcs956
Feb 15, 2026
Merged

Add Sony RC-S956 support and multi-device PN53x discovery#224
codebutler merged 9 commits intomasterfrom
feature/pn533-rcs956

Conversation

@codebutler
Copy link
Owner

Summary

  • Handle PN53x error frames (0x7F) in transport layer with PN533CommandException, so unsupported commands fail gracefully instead of crashing
  • RC-S956 devices (Sony RC-S370/P, VID 054C PID 02E1) use an alternative init: resetMode + setParameters + RF configuration instead of samConfiguration
  • Add PN533Device.openAll() for multi-device discovery — all connected PN53x readers scan independently
  • Dynamic backend creation in DesktopCardScanner.discoverBackends() replaces fixed backend list

Test plan

  • Connect only SCL3711 → still works as before (regression check)
  • Connect only RC-S370/P → console shows firmware v1.48, RC-S956 init path, polls and reads cards
  • Connect both → console shows two PN53x backends starting, both scan independently
  • Tap DESFire card on RC-S370/P → reads successfully
  • Tap FeliCa card on RC-S370/P → reads successfully

🤖 Generated with Claude Code

@codebutler codebutler force-pushed the feature/pn533-reader branch 4 times, most recently from 6f41a72 to 5d3e358 Compare February 15, 2026 06:40
Base automatically changed from feature/pn533-reader to master February 15, 2026 06:51
codebutler and others added 8 commits February 15, 2026 01:52
Handle PN53x error frames (0x7F) in transport layer by throwing
PN533CommandException, enabling graceful fallback when SAMConfiguration
is unsupported. RC-S956 devices (Sony RC-S370/P) use an alternative
init sequence: resetMode + setParameters + RF configuration.

Add openAll() to PN533Device for multi-device discovery, and wire
dynamic backend creation in DesktopCardScanner so all connected
PN53x readers scan independently.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
resetMode (cmd 0x18) returns error frame 0x7F on the RC-S370/P —
the device is already in reader mode over USB so it's not needed.
writeRegister (cmd 0x08) may also be unsupported on some firmware
versions, so wrap it in try/catch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wrap every init command in tryCommand() so unsupported commands
are logged and skipped rather than crashing the backend. This lets
us see exactly which commands the RC-S956 accepts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Key fixes based on reading nfcpy source:
- resetMode() must send ACK frame + 10ms delay after command
- Never try SAMConfiguration on RC-S956 (corrupts state machine)
- Detect RC-S956 from firmware version (version < 2)
- Use nfcpy's exact init order: resetMode, rfFieldOff, RF config,
  setParameters, resetMode again, writeRegister
- Fix 106A RF config first byte: 0x5A not 0x59
- Use nfcpy's MaxRetries values: [0x00, 0x00, 0x01]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract shared poll/read logic into abstract PN53xReaderBackend.
PN533ReaderBackend now only contains the SAM-based init (untouched
from the PN533 PR). RCS956ReaderBackend has the nfcpy-based init
sequence in complete isolation.

DesktopCardScanner probes firmware version to create the right
backend type for each discovered transport.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
nfcpy's mute() sends rf_configuration(0x01, [0x02]) which enables
auto RF collision avoidance — the field activates on demand when
InListPassiveTarget polls. Our rfFieldOff() was sending 0x00 which
fully disables the field, causing InListPassiveTarget to return
error frame 0x7F.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
nfcpy init(transport) sends ACK as the very first action before
any commands. Device.__init__ then does:
  resetMode #1 → getFirmwareVersion → mute (resetMode #2 + auto
  RFCA) → RF config → setParameters → resetMode #3 → writeRegister

Previously we were probing firmware before sending ACK, and only
had 2 resetMode calls instead of 3 (mute calls resetMode + rfConfig,
not just rfConfig).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
RC-S956 requires explicit SENSF_REQ initiator data (00 FF FF 01 00) for
FeliCa InListPassiveTarget, unlike PN533 which generates defaults
internally. Without it, RC-S956 returns error frame 0x7F.

- Pass SENSF_REQ in both pollLoop and waitForRemoval FeliCa polls
- Catch PN533CommandException in inListPassiveTarget as fallback
- Add wire-level debug logging (disabled by default)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
RC-S956 firmware does not support InDataExchange (0x40), returning error
frame 0x7F. nfcpy uses InCommunicateThru (0x42) for all target
communication on PN53x devices.

Add PN533CommunicateThruTransceiver that wraps APDUs in ISO-DEP I-blocks
(alternating block numbers) and handles S(WTX) waiting time extensions.
RC-S956 backend overrides createTransceiver to use it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@codebutler codebutler merged commit 18e1fb7 into master Feb 15, 2026
2 checks passed
@codebutler codebutler deleted the feature/pn533-rcs956 branch February 15, 2026 07:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant