Below is the file 'mifare.py' from this revision. You can also download the file.

#!/usr/bin/env python2.5

'''mifare - a library for interacting with MIFARE readers.
Written by David Adam <zanchey@ucc.gu.uwa.edu.au>
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]) == 16:
            raise MIFAREAuthenticationError, "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, blocknum, data):
        pass

    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 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