aboutsummaryrefslogtreecommitdiffstats
path: root/cards/pn532_card.py
blob: 6bff7380845f857b344155f7e1fc222d797513e8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
import utils, binascii
from generic_card import *

class PN532_Virtual_Card(Card):
    # This is a virtual card that is enabled for the ACS ACR reader that 
    # contains a PN532 module
    DRIVER_NAME = ["PN532"]

    STATUS_WORDS = dict(Card.STATUS_WORDS)
    COMMANDS = dict(Card.COMMANDS)

    APDU_TRANSCEIVE_PN532 = C_APDU(cla=0xff, ins=0, p1=0, p2=0)
    
    # The ACR122 has a maximum response data size of 0xf8
    APDU_READ_BINARY = C_APDU(cla=0, ins=0xb0, le=0xf8)
    
    def cmd_pn532(self, *cmd):
        "Transmit a command to the PN532 and receive the response"
        result = self.pn532_transceive(binascii.unhexlify("".join("".join(cmd).split())))
        print utils.hexdump(result.data)
        
        parsed = self.pn532_parse(result.data)
        if len(parsed) > 0:
            print "\n".join(parsed) + "\n"
    
    def cmd_pn532_poll(self):
        "Poll for cards in the field"
        self.cmd_pn532("d4 4a 01 00")
    
    def pn532_transceive(self, cmd):
        if len(cmd) > 1:
            if cmd[0] == "\xd4":
                s = "pn532_prepare_command_%02X" % ord(cmd[1])
                if hasattr(self, s): 
                    cmd = getattr(self, s)(cmd)
        
        if hasattr(self.reader, "pn532_transceive_raw"):
            return R_APDU(self.reader.pn532_transceive_raw(cmd))
        else:
            apdu = C_APDU(self.APDU_TRANSCEIVE_PN532, lc=len(cmd), data=cmd)
            response = self.send_apdu(apdu)
            return response
    
    def pn532_parse(self, response):
        result = []
        type = None
        cmd = None
        
        if len(response) == 0:
            pass
        elif response[0] == "\xd4":
            type="command"
        elif response[0] == "\xd5":
            type="response"
        else:
            result.append("Invalid PN532 direction header")
        
        if len(response) > 1: cmd = ord(response[1])
        
        if type is not None:
            desc = "PN532 %s (%s)" % (type, 
                self.PN532_COMMANDS.get(cmd & 0xfe, "Unknown command") )
            result.append(desc)
        
            if len(response) > 1:
                s = "pn532_parse_%s_%02X" % (type, cmd)
                if hasattr(self, s):
                    result.extend( getattr(self, s)(response[2:]) )
                elif len(response) > 2:
                    result.append( "No detailed decoding available" )
        
        return result
    
    def pn532_parse_response_03(self, response):
        return [ "Version: PN5%02X, firmware %i.%i (cap: %02X)" % tuple(map(ord, response)) ]
    
    def pn532_parse_response_05(self, response):
        result = ["Last error: %02X" % ord(response[0]),
            "External field: %s" % ( response[1] == "\x01" and "present" or "not present" ),
            "Number of targets: %i" % ord(response[2])]
        
        for i in range( (len(response)-4)/4 ):
            t = "Target %i: %i kbps receive, %i kbps send, type: %s" % (
                    ord(response[3+i*4]),
                    self.PN532_BIT_RATES.get( ord(response[3+i*4+1]), "XXX" ),
                    self.PN532_BIT_RATES.get( ord(response[3+i*4+2]), "XXX" ),
                    self.PN532_TAG_TYPES.get( ord(response[3+i*4+3]), "unknown" ),
                )
            result.append(t)
        
        result.append( "SAM status: %02X" % ord(response[-1]) )
        return result
    
    def pn532_prepare_command_4A(self, cmd):
        if len(cmd) > 3:
            self._last_baudrate_polled = ord(cmd[3])
        else:
            self._last_baudrate_polled = None
        return cmd
    
    def pn532_parse_response_4B(self, response):
        r = utils.PN532_Response_InListPassiveTarget(data = response)
        parse_ok = r.parse_result(self._last_baudrate_polled)
        
        result = ["Targets detected: %i" % len(r.targets)]
        
        if not parse_ok:
            result.append("Parse error, results unreliable")
        
        for index, target in r.targets.items():
            s = "Target %i: %s" % (index, target.type)
            if target.type == utils.PN532_Target.TYPE_ISO14443A:
                s = s + ", SENS_RES: %02X %02X, SEL_RES: %02X" % (
                    target.sens_res[0], target.sens_res[1], target.sel_res)
                if len(target.nfcid) > 0:
                    s = s + ", NFCID (%i bytes): %s" % (
                        len(target.nfcid), " ".join(map(lambda a: "%02X" % a, target.nfcid)))
                if len(target.ats) > 0:
                    s = s + ", ATS (%i bytes): %s" % (
                        len(target.ats), " ".join(map(lambda a: "%02X" % a, target.ats)))
                
                result.append(s)
        
        return result
    
    def can_handle(cls, reader):
        """Determine whether this class can handle a given reader object."""
        if reader.name.startswith("ACS ACR 38U-CCID"):
            return True
        return False
        
    can_handle = classmethod(can_handle)

    
    STATUS_WORDS.update( { 
        '\x63\x00': "Operation failed",
        '\x63\x01': "PN532 did not respond",
        '\x63\x27': "PN532 response checksum wrong",
        '\x63\x7f': "PN532 command wrong",
    } )

    COMMANDS.update( {
        "pn532": cmd_pn532,
        "pn532_poll": cmd_pn532_poll,
        } )
    
    PN532_COMMANDS = {
        0x00: "Diagnose",
        0x02: "GetFirmwareVersion",
        0x04: "GetGeneralStatus",
        0x06: "ReadRegister",
        0x08: "WriteRegister",
        0x0c: "ReadGPIO",
        0x0e: "WriteGPIO",
        0x10: "SetSerialBaudrate",
        0x12: "SetParameters",
        0x14: "SAMConfiguration",
        0x16: "PowerDown",
        0x32: "RFConfiguration",
        0x58: "RFRegulationTest",
        0x56: "InJumpForDEP",
        0x46: "InJumpForPSL",
        0x4A: "InListPassiveTarget",
        0x50: "InATR",
        0x4E: "InPSL",
        0x40: "InDataExchange",
        0x42: "InCommunicateThru",
        0x44: "InDeselect",
        0x52: "InRelease",
        0x54: "InSelect",
        0x60: "InPoll",
        0x8C: "TgInitAsTarget",
        0x92: "TgSetGeneralBytes",
        0x86: "TgGetData",
        0x8E: "TgSetData",
        0x94: "TgSetMetaData",
        0x88: "TgGetInitiatorCommand",
        0x90: "TgResponseToInitiator",
        0x8A: "TgGetTargetStatus",
    }
    
    PN532_BIT_RATES = {
        0x0: 106,
        0x1: 212,
        0x2: 424,
    }
    
    PN532_TAG_TYPES = {
        0x00: "Mifare, ISO 14443-3 A/B or ISO 18092 passive 106 kbps",
        0x10: "FeliCa or ISO 18092 passive 212/424 kbps",
        0x01: "ISO 18092 active",
        0x02: "Innovision Jewel",
    }