forked from hyperair/gnetplus
-
Notifications
You must be signed in to change notification settings - Fork 5
/
gnetplus.py
executable file
·338 lines (276 loc) · 9.15 KB
/
gnetplus.py
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
#!/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>
# Harish Pillay <hpillay@redhat.com>
#
# Initial release: July 31, 2013
# Minor edits: July 31, 2013: giving an example of how to invoke it
#
# 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
class InvalidMessage(Exception):
pass
class Message(object):
"""
Base message class for representing a message
"""
SOH = 0x01
def __init__(self, address, function, data):
"""
@arg address 8-bit int containing device address (Use 0 unless you know
what you're doing)
@arg function 8-bit int containing the function of this message
@arg data String containing payload of this message
"""
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))
def sendto(self, serial):
"""
Sends this message to `serial´
"""
serial.write(str(self))
@classmethod
def readfrom(cls, serial):
"""
Reads one message from `serial' and constructs a message from `cls'
@arg serial serial.Serial interface to read message from
@returns Constructed 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
def gencrc(msgstr):
"""
Generate CRC for the string `msgstr'
@arg msgstr string containing data to be checksummed
@returns 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
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
class GNetPlusError(Exception):
"""
Exception thrown when receiving a ResponseMessage with function=NAK
"""
pass
class ResponseMessage(Message):
"""
Message received from card reader
"""
ACK = 0x06
NAK = 0x15
EVN = 0x12
def to_error(self):
"""
Construct a GNetPlusError for NAK response.
@returns Constructed instance of GNetPlusError for this response
"""
if self.function != self.NAK:
return None
return GNetPlusError("Error: " + repr(self.data))
class Handle(object):
"""
Main class used for interfacing with the card reader.
"""
def __init__(self, port, baudrate=19200, deviceaddr=0):
"""
Initializes a Handle instance.
@arg port String containing name of serial port, e.g. /dev/ttyUSB0
@arg baudrate Baudrate for interfacing with the device. Don't change
this unless you know what you're doing.
@arg deviceaddr Integer containing the device address. Defaults to 0.
"""
self.baudrate = baudrate
self.port = port
self.serial = serial.Serial(port, baudrate=baudrate)
self.deviceaddr = deviceaddr
def sendmsg(self, function, data=''):
"""
Constructs and sends a QueryMessage to the device
@arg function @see Message.function
@arg data @see Message.data
"""
QueryMessage(self.deviceaddr, function, data).sendto(self.serial)
def readmsg(self, sink_events=False):
"""
Reads a message, optionally ignoring event (EVN) messages which are
device-driven.
@arg 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
def get_sn(self):
"""
Get serial number of the card currently scanned.
@returns 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]
def get_version(self):
"""
Get product version string. May contain null bytes, so be careful when
using it.
@returns Product version string of the device connected to this handle.
"""
self.sendmsg(QueryMessage.GET_VERSION)
return self.readmsg().data
def set_auto_mode(self, enabled=True):
"""
Toggle auto mode, i.e. whether the device emits events when a card
comes close.
@arg enabled Whether to enable or disable auto mode.
"""
self.sendmsg(QueryMessage.AUTO_MODE, chr(enabled))
self.readmsg(sink_events=True)
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>, example /dev/ttyUSB0\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."