1 #!/usr/bin/env python2.5
3 '''mifare - a library for interacting with MIFARE readers.
4 Written by David Adam <zanchey@ucc.gu.uwa.edu.au>
7 Licensed under an MIT-style license: see LICENSE file for details.
10 import serial, logging
12 xor = lambda x, y: x ^ y
14 return chr(reduce(xor, [ord(i) for i in string]))
17 class MIFAREException(Exception):
21 class MIFARECommunicationException(MIFAREException):
25 class MIFAREAuthenticationException(MIFAREException):
30 '''An interface to a particular MIFARE reader.'''
32 def __init__(self, io):
33 '''Returns an interface to a MIFARE reader given a file-like object.
34 The file-like object is generally a pyserial Serial object.'''
36 if isinstance(self.io, serial.Serial):
37 self.io.setTimeout = 2
38 self.address = '\x00\x00'
40 def get_absolute_block(self, vector):
42 return vector[0] * 4 + vector[1]
44 # Sectors below 32 are 4 blocks
45 # Sectors above are 16 blocks
46 # Thus, sector 32 starts at block 128, 33 at 144, and so on
47 return 128 + (vector[0] - 32) * 16 + vector[1]
49 def send_packet(self, data):
50 '''Constructs a packet for the supplied data string, sends it to the
51 MIFARE reader, then returns the response (if any) to the commmand.'''
53 # Occasionally the reader inserts extra trailing characters into its
54 # responses, so flush the buffers if possible beforehand.
55 if isinstance(self.io, serial.Serial):
59 # XXX - Needs more error checking.
60 data = '\x00' + self.address + data
61 packet = '\xAA\xBB' + chr(len(data)) + data + checksum(data)
64 header = self.io.read(2)
65 if header == '\xaa\xbb':
66 length = ord(self.io.read(1))
67 data = self.io.read(length)
68 packet_xsum = self.io.read(1)
69 if checksum(data) == packet_xsum and len(data) == length:
70 # Strip off separator and address header
73 raise MIFARECommunicationException, "Invalid response received"
75 def set_antenna(self, state = True):
76 """Turn the card reader's antenna on or off (no return value)"""
77 command = '\x0C\x01' + chr(int(state))
78 response = self.send_packet(command)
79 if response == '\x0c\x01\x00':
82 raise MIFAREException, 'command failed: set_antenna (%s)' % state
84 def select_card(self, include_halted = False):
85 """Selects a card and returns a tuple of (serial number, capacity).
87 If include_halted is set, may select a card that halt() has previously
90 # Request type of card available
91 command = command = '\x01\x02'
97 card_type_response = self.send_packet(command)
99 if card_type_response == None or card_type_response[2] == '\x14':
100 raise MIFAREException, "select_card: no card available"
101 card_type = card_type_response[3:5]
103 if card_type == '\x44\x00': # MIFARE UltraLight
104 raise NotImplementedError, "UltraLight card selected - no functions available"
107 # Otherwise, must be a standard MIFARE card.
109 command = '\x02\x02\x04'
110 # No error handling on this command
111 serial = self.send_packet(command)[3:]
113 # Select the card for use
115 select_response = self.send_packet('\x03\x02' + serial)
116 capacity = ord(select_response[3])
118 logging.warning('Tried to select card but failed: card_type %s, serial %s, select_response %s' % (card_type.__repr__(), serial.__repr__(), select_response.__repr__()))
120 return (serial, capacity)
122 def sector_login(self, blockvect, key, keytype=0):
123 """Log in to a block using the six-byte key.
125 Use a keytype of 1 to use key B."""
126 sector = self.get_absolute_block((blockvect[0], 0))
129 raise ValueError, 'key must be a six-byte string'
131 keytype = 96 + keytype
133 data = chr(keytype) + chr(sector) + key
135 result = self.send_packet('\x07\x02' + data)
136 if ord(result[2]) == 22:
137 raise MIFAREAuthenticationException, "incorrect key provided"
141 def read_block(self, blockvect):
142 "Read the 16-byte block at vector (sector, block)."
143 block = self.get_absolute_block(blockvect)
145 result = self.send_packet('\x08\x02' + chr(block))
148 def write_block(self, blockvect, data):
149 """Write the 16 bytes in data to the block at vector (sector, block)."""
150 block = self.get_absolute_block(blockvect)
152 raise ValueError, "invalid data length - must be 16 bytes"
154 result = self.send_packet('\x09\x02' + chr(block) + data)
157 def write_key(self, key):
160 def value_block_increment(self, blocknum, increment):
163 def value_block_decrement(self, blocknum, decrement):
166 def copy_block(self, source, dest):
170 """Halt the current card - no further transactions will be performed with it."""
171 self.send_packet('\x04\x02')
173 def set_led(self, red = False, green = False):
179 self.send_packet('\x07\x01' + chr(led_state))
181 def beep(self, length):
182 '''Beep for a specified length of milliseconds.'''
183 length = int(round(length / 10.))
186 self.send_packet('\x06\x01' + chr(length))