@@ -167,7 +167,7 @@ func (t *NTAGTag) ReadBlock(ctx context.Context, block uint8) ([]byte, error) {
167167 return nil , err
168168 }
169169
170- data , err := t .device .SendDataExchange (ctx , []byte {ntagCmdRead , block })
170+ data , err := t .device .SendDataExchangeWithRetry (ctx , []byte {ntagCmdRead , block })
171171 if err != nil {
172172 // If we get authentication error 14, try InCommunicateThru as fallback for clone devices
173173 if IsPN532AuthenticationError (err ) {
@@ -240,10 +240,9 @@ func (t *NTAGTag) ReadNDEF(ctx context.Context) (*NDEFMessage, error) {
240240 return nil , err
241241 }
242242
243- // Ensure target is selected before reading. This is critical after DetectType()
244- // which uses GetVersion() via InCommunicateThru - that command doesn't maintain
245- // target selection state. Without re-selection, InDataExchange fails with timeout
246- // error 0x01. See PN532 User Manual §7.3.9.
243+ // Ensure target is selected before reading. This is defensive against any prior
244+ // operations that may have used InCommunicateThru (0x42), which doesn't maintain
245+ // the PN532's target selection state. See PN532 User Manual §7.3.9.
247246 if err := t .device .InSelect (ctx ); err != nil {
248247 Debugln ("NTAG ReadNDEF: InSelect failed, continuing anyway:" , err )
249248 }
@@ -375,7 +374,7 @@ func (t *NTAGTag) readRawPages(ctx context.Context, startPage uint8, pageCount i
375374 }
376375
377376 // NTAG READ command returns 16 bytes (4 pages) starting from the specified page
378- data , err := t .device .SendDataExchange (ctx , []byte {ntagCmdRead , startPage })
377+ data , err := t .device .SendDataExchangeWithRetry (ctx , []byte {ntagCmdRead , startPage })
379378 if err != nil {
380379 return nil , err
381380 }
@@ -1065,7 +1064,15 @@ func (t *NTAGTag) GetTotalPages() uint8 {
10651064 }
10661065}
10671066
1068- // DetectType attempts to detect the NTAG variant using GET_VERSION command with fallback
1067+ // DetectType detects the NTAG variant using the Capability Container (CC).
1068+ //
1069+ // This function uses CC-based detection exclusively, avoiding the GET_VERSION command
1070+ // which uses InCommunicateThru (0x42). InCommunicateThru doesn't maintain the PN532's
1071+ // target selection state, causing subsequent InDataExchange calls to fail with timeout
1072+ // error 0x01 on readers with marginal RF connections.
1073+ //
1074+ // CC-based detection accurately identifies NTAG213/215/216 from the size field in the
1075+ // capability container, which is sufficient for NDEF operations.
10691076func (t * NTAGTag ) DetectType (ctx context.Context ) error {
10701077 if err := ctx .Err (); err != nil {
10711078 return err
@@ -1090,53 +1097,10 @@ func (t *NTAGTag) DetectType(ctx context.Context) error {
10901097 return errors .New ("not an NTAG tag: invalid capability container" )
10911098 }
10921099
1093- // Check if this is likely a genuine NXP tag (UID starts with 0x04)
1094- // Clone tags use different UID prefixes and typically don't support GET_VERSION.
1095- // Sending GET_VERSION (0x60) to a clone tag that doesn't understand it causes
1096- // the tag to enter IDLE state per ISO14443-3A, breaking subsequent writes.
1097- // Skip GET_VERSION for non-NXP UIDs to keep clone tags in ACTIVE state.
1098- if len (t .uid ) > 0 && t .uid [0 ] != 0x04 {
1099- Debugf ("NTAG DetectType: non-NXP UID prefix 0x%02X, skipping GET_VERSION to avoid IDLE state" , t .uid [0 ])
1100- t .tagType = t .detectTypeFromCapabilityContainer (ccData )
1101- return nil
1102- }
1103-
1104- // Now try to get the actual version information using GET_VERSION
1105- version , err := t .GetVersion ()
1106-
1107- // Re-select target after GetVersion to restore PN532 internal state.
1108- // GetVersion uses InCommunicateThru (0x42) which is a raw pass-through command
1109- // that doesn't maintain the PN532's target selection state. Without re-selection,
1110- // subsequent InDataExchange calls may fail with timeout error 01.
1111- // See PN532 User Manual §7.3.9: "The host controller has to take care of the
1112- // selection of the target it wants to reach (whereas when using the
1113- // InDataExchange command, it is done automatically)."
1114- if selectErr := t .device .InSelect (ctx ); selectErr != nil {
1115- Debugln ("NTAG DetectType: InSelect after GetVersion failed:" , selectErr )
1116- }
1117-
1118- if err != nil {
1119- // If GET_VERSION fails, we still know it's an NTAG from the CC check
1120- // Use fallback detection method based on capability container
1121- Debugf ("NTAG GET_VERSION failed, using CC-based detection fallback: %v" , err )
1122- t .tagType = t .detectTypeFromCapabilityContainer (ccData )
1123- return nil // Don't return error if CC-based detection succeeded
1124- }
1125-
1126- // Use the version information to determine the exact variant
1127- t .tagType = version .GetNTAGType ()
1128-
1129- if t .tagType == NTAGTypeUnknown {
1130- // Even if storage size is unknown from GET_VERSION, try CC-based detection
1131- Debugf ("NTAG GET_VERSION returned unknown type, trying CC-based detection fallback" )
1132- fallbackType := t .detectTypeFromCapabilityContainer (ccData )
1133- if fallbackType != NTAGTypeUnknown {
1134- t .tagType = fallbackType
1135- } else {
1136- // Final fallback to conservative choice
1137- t .tagType = NTAGType213 // Use smallest variant for safety
1138- }
1139- }
1100+ // Use CC-based detection for all tags (genuine NXP and clones alike)
1101+ // This avoids GET_VERSION which uses InCommunicateThru and can cause
1102+ // target selection issues on marginal RF connections
1103+ t .tagType = t .detectTypeFromCapabilityContainer (ccData )
11401104
11411105 return nil
11421106}
@@ -1321,21 +1285,20 @@ func (t *NTAGTag) getTagTypeName() string {
13211285}
13221286
13231287func (t * NTAGTag ) readBlockWithRetry (ctx context.Context , block uint8 ) ([]byte , error ) {
1324- maxRetries : = 3
1288+ const maxRetries = 3
13251289 for i := range maxRetries {
13261290 if err := ctx .Err (); err != nil {
13271291 return nil , err
13281292 }
13291293
1330- data , err := t .device .SendDataExchange (ctx , []byte {ntagCmdRead , block })
1294+ // SendDataExchangeWithRetry handles timeout (0x01) retries internally
1295+ data , err := t .device .SendDataExchangeWithRetry (ctx , []byte {ntagCmdRead , block })
13311296 if err != nil {
13321297 // If we get authentication error 14, try InCommunicateThru as fallback for clone devices
13331298 if IsPN532AuthenticationError (err ) {
13341299 return t .readBlockCommunicateThru (ctx , block )
13351300 }
1336- if i < maxRetries - 1 {
1337- continue
1338- }
1301+ // For other errors, fail immediately since SendDataExchangeWithRetry already retried timeouts
13391302 return nil , err
13401303 }
13411304
@@ -1344,6 +1307,7 @@ func (t *NTAGTag) readBlockWithRetry(ctx context.Context, block uint8) ([]byte,
13441307 return data [:ntagBlockSize ], nil
13451308 }
13461309
1310+ // Short response - retry
13471311 if i < maxRetries - 1 {
13481312 continue
13491313 }
@@ -1649,8 +1613,8 @@ func (t *NTAGTag) readBlockDirectFallback(ctx context.Context, block uint8) ([]b
16491613
16501614 Debugf ("NTAG attempting direct InDataExchange fallback for block %d" , block )
16511615
1652- // Try direct InDataExchange, ignoring the error 14 that originally triggered the fallback chain
1653- data , err := t .device .SendDataExchange (ctx , []byte {ntagCmdRead , block })
1616+ // Try direct InDataExchange with retry , ignoring the error 14 that originally triggered the fallback chain
1617+ data , err := t .device .SendDataExchangeWithRetry (ctx , []byte {ntagCmdRead , block })
16541618 if err != nil {
16551619 // If this also fails, the clone device simply doesn't support NTAG properly
16561620 Debugf ("NTAG direct InDataExchange fallback also failed: %v" , err )
0 commit comments