Source code for gnetplus

#!/usr/bin/python
# -*- coding: utf-8 -*-

# gnetplus -- Python module for interfacing with PROMAG RFID card reader
# Copyright © 2013 Red Hat Inc.
#
# Authors:
#
#     Chow Loong Jin <lchow@redhat.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

"""
Module for interfacing with the PROMAG card reader using the GNetPlus®
Protocol.

Usage:

>>> from gnetplus import Handle
>>> handle = Handle("/dev/ttyUSB0")
>>> print "S/N: " + hex(handle.get_sn())
"""

import collections
import serial
import struct
import sys
import time


[docs]class InvalidMessage(Exception): pass
[docs]class Message(object): """ Base message class for representing a message :param address: Device Address (0-255, default 0) :type address: int :param function: Function of this message :type function: int :param data: Payload of this message :type data: str """ SOH = 0x01 def __init__(self, address=0, function=None, data=''): self.address = address self.function = function self.data = data def __str__(self): """ Converts Message to raw binary form suitable for transmission """ msgstr = struct.pack('BBB', self.address, self.function, len(self.data)) + self.data crc = self.gencrc(msgstr) return chr(self.SOH) + msgstr + struct.pack('>H', crc) def __repr__(self): return ("{name}(address={address}, " "function={function}, " "data={data})").format(name=self.__class__.__name__, address=hex(self.address), function=hex(self.function), data=repr(self.data))
[docs] def sendto(self, serial): """ Sends this message to `serial` :param serial: Serial interface to send this message to :type serial: :py:class:`serial.Serial` """ serial.write(str(self))
@classmethod
[docs] def readfrom(cls, serial): """ Constructs one message from `serial` :param serial: serial.Serial interface to read message from :rtype: `Message` instance """ header = serial.read(4) soh, address, function, length = struct.unpack('BBBB', header) if soh != cls.SOH: raise InvalidMessage("SOH does not match") data = serial.read(length) crc = serial.read(2) msg = cls(address=address, function=function, data=data) if str(msg)[-2:] != crc: raise InvalidMessage("CRC does not match") return msg
@staticmethod
[docs] def gencrc(msgstr): """ Generate CRC for the string `msgstr` :param msgstr: string containing data to be checksummed :return: 16-bit integer containing CRC checksum """ crc = 0xFFFF for char in msgstr: crc ^= ord(char) for i in xrange(8): if (crc & 1) == 1: crc = (crc >> 1) ^ 0xA001 else: crc >>= 1 return crc
[docs]class QueryMessage(Message): """ A query message to be sent from host machine to card reader device. Magical constants taken from protocol documentation. """ POLLING = 0x00 GET_VERSION = 0x01 SET_SLAVE_ADDR = 0x02 LOGON = 0x03 LOGOFF = 0x04 SET_PASSWORD = 0x05 CLASSNAME = 0x06 SET_DATETIME = 0x07 GET_DATETIME = 0x08 GET_REGISTER = 0x09 SET_REGISTER = 0x0A RECORD_COUNT = 0x0B GET_FIRST_RECORD = 0x0C GET_NEXT_RECORD = 0x0D ERASE_ALL_RECORDS = 0x0E ADD_RECORD = 0x0F RECOVER_ALL_RECORDS = 0x10 DO = 0x11 DI = 0x12 ANALOG_INPUT = 0x13 THERMOMETER = 0x14 GET_NODE = 0x15 GET_SN = 0x16 SILENT_MODE = 0x17 RESERVE = 0x18 ENABLE_AUTO_MODE = 0x19 GET_TIME_ADJUST = 0x1A ECHO = 0x18 SET_TIME_ADJUST = 0x1C DEBUG = 0x1D RESET = 0x1E GO_TO_ISP = 0x1F REQUEST = 0x20 ANTI_COLLISION = 0x21 SELECT_CARD = 0x22 AUTHENTICATE = 0x23 READ_BLOCK = 0x24 WRITE_BLOCk = 0x25 SET_VALUE = 0x26 READ_VALUE = 0x27 CREATE_VALUE_BLOCK = 0x28 ACCESS_CONDITION = 0x29 HALT = 0x2A SAVE_KEY = 0x2B GET_SECOND_SN = 0x2C GET_ACCESS_CONDITION = 0x2D AUTHENTICATE_KEY = 0x2E REQUEST_ALL = 0x2F SET_VALUEEX = 0x32 TRANSFER = 0x33 RESTORE = 0x34 GET_SECTOR = 0x3D RF_POWER_ONOFF = 0x3E AUTO_MODE = 0x3F
[docs]class GNetPlusError(Exception): """ Exception thrown when receiving a :py:class:`ResponseMessage` with `function` = :py:data:`ResponseMessage.NAK` """ pass
[docs]class ResponseMessage(Message): """ Message received from card reader """ ACK = 0x06 NAK = 0x15 EVN = 0x12
[docs] def to_error(self): """ Construct a GNetPlusError for :py:data:`NAK` response. :return: Constructed instance of :py:class:`GNetPlusError` for this response """ if self.function != self.NAK: return None return GNetPlusError("Error: " + repr(self.data))
[docs]class Handle(object): """ Main class used for interfacing with the card reader. :param port: String containing name of serial port, e.g. /dev/ttyUSB0 :param baudrate: Baudrate for interfacing with the device. Don't change this unless you know what you're doing. :param deviceaddr: Integer containing the device address. Defaults to 0. """ def __init__(self, port, baudrate=19200, deviceaddr=0): """ Initializes a Handle instance. """ self.baudrate = baudrate self.port = port self.serial = serial.Serial(port, baudrate=baudrate) self.deviceaddr = deviceaddr
[docs] def sendmsg(self, function, data=''): """ Constructs and sends a :py:class:`QueryMessage` to the device :param function: message function (0-255, see `Message.function`) :type function: int :param data: message data :type data: str """ QueryMessage(self.deviceaddr, function, data).sendto(self.serial)
[docs] def readmsg(self, sink_events=False): """ Reads a message, optionally ignoring event (EVN) messages which are device-driven. :param sink_events: Boolean dictating whether or not events should be ignored. """ while True: response = ResponseMessage.readfrom(self.serial) # skip over events. spec doesn't say what to do with them if sink_events and response.function == ResponseMessage.EVN: continue break if response.function == ResponseMessage.NAK: raise response.to_error() return response
[docs] def get_sn(self): """ Get serial number of the card currently scanned. :return: 16-bit integer containing serial number of the scanned card. """ self.sendmsg(QueryMessage.REQUEST) self.readmsg(sink_events=True) self.sendmsg(QueryMessage.ANTI_COLLISION) response = self.readmsg(sink_events=True) return struct.unpack('>L', response.data)[0]
[docs] def get_version(self): """ Get product version string. May contain null bytes, so be careful when using it. :return: Product version string of the device connected to this handle. """ self.sendmsg(QueryMessage.GET_VERSION) return self.readmsg().data
[docs] def set_auto_mode(self, enabled=True): """ Toggle auto mode, i.e. whether the device emits events when a card comes close. :param enabled: Whether to enable or disable auto mode. """ self.sendmsg(QueryMessage.AUTO_MODE, chr(enabled)) self.readmsg(sink_events=True)
[docs] def wait_for_card(self): """ Block until card is present at the device. Does not check if a card is already present before entering the function. """ self.set_auto_mode() while True: response = self.readmsg() if ((response.function == ResponseMessage.EVN and response.data == 'I')): return
if __name__ == '__main__': try: port = sys.argv[1] except IndexError: sys.stderr.write("Usage: {0} <serial port>\n".format(sys.argv[0])) handle = Handle(port) while True: handle.wait_for_card() try: print "Found card: {0}".format(hex(handle.get_sn())) except GNetPlusError: print "Tap card again."

Project Versions

This Page