1from dataclasses import dataclass 2from typing import Optional 3from abc import ABCMeta, abstractmethod 4 5 6@dataclass 7class TransceiveConfiguration: 8 """Defines settings used for NFC communication""" 9 type: str 10 crc: int = True 11 bits: int = 8 12 bitrate: int = 106 13 timeout: float = None 14 # Output power as a percentage of maximum supported by the reader 15 power: float = 100 16 17 def replace(self, **kwargs): 18 """Return a new instance with specific values replaced by name.""" 19 return self.__class__(**{ 20 "type": self.type, 21 "crc": self.crc, 22 "bits": self.bits, 23 "bitrate": self.bitrate, 24 "timeout": self.timeout, 25 "power": self.power, 26 **kwargs 27 }) 28 29 30CARRIER = 13.56e6 31A_TIMEOUT = (1236 + 384) / CARRIER 32CONFIGURATION_A_LONG = TransceiveConfiguration( 33 type="A", crc=True, bits=8, timeout=A_TIMEOUT 34) 35 36 37class ReaderTag(metaclass=ABCMeta): 38 """Describes a generic target which implements ISODEP protocol""" 39 40 @abstractmethod 41 def transact(self, command_apdus, response_apdus): 42 """Sends command_apdus and verifies reception of matching response_apdus 43 """ 44 45 46class Reader(metaclass=ABCMeta): 47 """Describes a generic NFC reader which can be used for running tests""" 48 49 @abstractmethod 50 def poll_a(self) -> Optional[ReaderTag]: 51 """Attempts to perform target discovery by issuing Type A WUP/REQ 52 and performing anticollision in case one is detected. 53 Returns a tag object if one was found, None otherwise 54 """ 55 56 @abstractmethod 57 def poll_b(self) -> Optional[ReaderTag]: 58 """Attempts to perform target discovery by issuing Type B WUP/REQ 59 and performing anticollision in case one is detected. 60 Returns a tag object if one was found, None otherwise 61 """ 62 63 @abstractmethod 64 def send_broadcast( 65 self, 66 data: bytes, *, 67 configuration: TransceiveConfiguration = CONFIGURATION_A_LONG 68 ): 69 """Broadcasts a custom data frame into the RF field. 70 Does not require an active target to be detected to do that. 71 By default, uses 'Long A' frame configuration, which can be overridden. 72 """ 73 74 @abstractmethod 75 def mute(self): 76 """Disables the RF field generated by the reader""" 77 78 @abstractmethod 79 def unmute(self): 80 """Enables the RF field generated by the reader""" 81 82 @abstractmethod 83 def reset(self): 84 """Auxiliary function to reset reader to starting conditions""" 85 86 def reset_buffers(self): 87 """Forwards a call to .reset() for compatibility reasons""" 88 self.reset() 89