+#!/usr/bin/env python2.5
+
+'''mifare - a library for interacting with MIFARE readers.
+Requires Python 2.5.
+
+Licensed under an MIT-style license: see LICENSE file for details.
+'''
+
+import serial
+
+xor = lambda x, y: x ^ y
+def checksum(string):
+ return chr(reduce(xor, [ord(i) for i in string]))
+
+
+class MIFAREException(Exception):
+ pass
+
+
+class MIFARECommunicationException(MIFAREException):
+ pass
+
+
+class MIFAREAuthenticationException(MIFAREException):
+ pass
+
+
+class MIFAREReader:
+ '''An interface to a particular MIFARE reader.'''
+
+ def __init__(self, io):
+ '''Returns an interface to a MIFARE reader given a file-like object.
+ The file-like object is generally a pyserial Serial object.'''
+ self.io = io
+ if isinstance(self.io, serial.Serial):
+ self.io.setTimeout(2)
+ self.address = '\x00\x00'
+
+ def get_absolute_block(self, vector):
+ if vector[0] < 32:
+ return vector[0] * 4 + vector[1]
+ else:
+ # Sectors below 32 are 4 blocks
+ # Sectors above are 16 blocks
+ # Thus, sector 32 starts at block 128, 33 at 144, and so on
+ return 128 + (vector[0] - 32) * 16 + vector[1]
+
+ def send_packet(self, data):
+ '''Constructs a packet for the supplied data string, sends it to the
+ MIFARE reader, then returns the response (if any) to the commmand.'''
+
+ # Occasionally the reader inserts extra trailing characters into its
+ # responses, so flush the buffers if possible beforehand.
+ if isinstance(self.io, serial.Serial):
+ self.io.flushInput()
+ self.io.flushOutput()
+
+ # XXX - Needs more error checking.
+ data = '\x00' + self.address + data
+ packet = '\xAA\xBB' + chr(len(data)) + data + checksum(data)
+ self.io.write(packet)
+ response = ''
+ header = self.io.read(2)
+ if header == '\xaa\xbb':
+ length = ord(self.io.read(1))
+ data = self.io.read(length)
+ packet_xsum = self.io.read(1)
+ if checksum(data) == packet_xsum and len(data) == length:
+ # Strip off separator and address header
+ return data[3:]
+ else:
+ raise MIFARECommunicationException, "Invalid response received"
+
+ def set_antenna(self, state = True):
+ """Turn the card reader's antenna on or off (no return value)"""
+ command = '\x0C\x01' + chr(int(state))
+ response = self.send_packet(command)
+ if response == '\x0c\x01\x00':
+ return None
+ else:
+ raise MIFAREException, 'command failed: set_antenna (%s)' % state
+
+ def select_card(self, include_halted = False):
+ """Selects a card and returns a tuple of (serial number, capacity).
+
+ If include_halted is set, may select a card that halt() has previously
+ been called on."""
+
+ # Request type of card available
+ command = command = '\x01\x02'
+ if include_halted:
+ command += '\x52'
+ else:
+ command += '\x26'
+
+ card_type_response = self.send_packet(command)
+
+ if card_type_response[2] == '\x14':
+ raise MIFAREException, "select_card: no card available"
+ card_type = card_type_response[3:5]
+
+ if card_type == '\x44\x00': # MIFARE UltraLight
+ raise NotImplementedError, "UltraLight card selected - no functions available"
+
+ else:
+ # Otherwise, must be a standard MIFARE card.
+ # Anticollision
+ command = '\x02\x02\x04'
+ # No error handling on this command
+ serial = self.send_packet(command)[3:]
+
+ # Select the card for use
+ capacity = ord(self.send_packet('\x03\x02' + serial)[3])
+ return (serial, capacity)
+
+ def sector_login(self, blockvect, key, keytype=0):
+ """Log in to a block using the six-byte key.
+
+ Use a keytype of 1 to use key B."""
+ sector = self.get_absolute_block((blockvect[0], 0))
+
+ if len(key) != 6:
+ raise ValueError, 'key must be a six-byte string'
+
+ keytype = 96 + keytype
+
+ data = chr(keytype) + chr(sector) + key
+
+ result = self.send_packet('\x07\x02' + data)
+ if ord(result[2]) == 22:
+ raise MIFAREAuthenticationException, "incorrect key provided"
+
+ return
+
+ def read_block(self, blockvect):
+ "Read the 16-byte block at vector (sector, block)."
+ block = self.get_absolute_block(blockvect)
+
+ result = self.send_packet('\x08\x02' + chr(block))
+ return result[3:19]
+
+ def write_block(self, blockvect, data):
+ """Write the 16 bytes in data to the block at vector (sector, block)."""
+ block = self.get_absolute_block(blockvect)
+ if len(data) != 16:
+ raise ValueError, "invalid data length - must be 16 bytes"
+
+ result = self.send_packet('\x09\x02' + chr(block) + data)
+ return
+
+ def write_key(self, key):
+ pass
+
+ def value_block_increment(self, blocknum, increment):
+ pass
+
+ def value_block_decrement(self, blocknum, decrement):
+ pass
+
+ def copy_block(self, source, dest):
+ pass
+
+ def halt(self):
+ """Halt the current card - no further transactions will be performed with it."""
+ self.send_packet('\x04\x02')
+
+ def set_led(self, red = False, green = False):
+ led_state = 0
+ if red:
+ led_state += 1
+ if green:
+ led_state += 2
+ self.send_packet('\x07\x01' + chr(led_state))
+
+ def beep(self, length):
+ '''Beep for a specified length of milliseconds.'''
+ length = int(round(length / 10.))
+ if length > 255:
+ length = 255
+ self.send_packet('\x06\x01' + chr(length))
+
+ def reset(self):
+ pass