mifarepy 1.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mifarepy/__init__.py +1 -0
- mifarepy/mifarepy.py +381 -0
- mifarepy-1.0.2.dist-info/LICENSE +76 -0
- mifarepy-1.0.2.dist-info/METADATA +295 -0
- mifarepy-1.0.2.dist-info/RECORD +7 -0
- mifarepy-1.0.2.dist-info/WHEEL +5 -0
- mifarepy-1.0.2.dist-info/top_level.txt +1 -0
mifarepy/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .mifarepy import Handle, Message, QueryMessage, ResponseMessage, GNetPlusError, InvalidMessage
|
mifarepy/mifarepy.py
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
# mifarepy -- Python library for interfacing with PROMAG RFID card reader
|
|
2
|
+
# Adapted from https://github.com/harishpillay/gnetplus (initially in Python 2)
|
|
3
|
+
#
|
|
4
|
+
# Authors:
|
|
5
|
+
# Original: Chow Loong Jin <lchow@redhat.com>
|
|
6
|
+
# Original: Harish Pillay <hpillay@redhat.com>
|
|
7
|
+
# Adapted by: Spark Drago <https://github.com/SparkDrago05>
|
|
8
|
+
#
|
|
9
|
+
# This library is released under the GNU Lesser General Public License v3.0 or later.
|
|
10
|
+
# See the LICENSE file for more details.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
mifarepy: A Python library for interfacing with the PROMAG RFID card reader
|
|
15
|
+
using the GNetPlus® protocol.
|
|
16
|
+
|
|
17
|
+
Features:
|
|
18
|
+
- Communicates via serial interface (`pyserial`).
|
|
19
|
+
- Supports various RFID commands (get serial number, read/write blocks, etc.).
|
|
20
|
+
- Includes error handling for invalid messages and device errors.
|
|
21
|
+
|
|
22
|
+
Example:
|
|
23
|
+
from mifarepy import Handle
|
|
24
|
+
|
|
25
|
+
handle = Handle('/dev/ttyUSB0')
|
|
26
|
+
print('S/N:', handle.get_sn(endian='little', as_string=True))
|
|
27
|
+
|
|
28
|
+
License:
|
|
29
|
+
GNU Lesser General Public License v2.1 or later
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
import logging
|
|
33
|
+
import serial
|
|
34
|
+
import struct
|
|
35
|
+
import sys
|
|
36
|
+
import time
|
|
37
|
+
from typing import Optional, Union
|
|
38
|
+
|
|
39
|
+
logging.basicConfig(level=logging.INFO)
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class InvalidMessage(Exception):
|
|
44
|
+
"""Raised when an invalid message is received from the RFID reader."""
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class GNetPlusError(Exception):
|
|
49
|
+
"""
|
|
50
|
+
Exception thrown when receiving a NAK (negative acknowledge) response.
|
|
51
|
+
"""
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Message(object):
|
|
56
|
+
"""
|
|
57
|
+
Base class representing a message for the RFID reader.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
SOH = 0x01 # Start of Header
|
|
61
|
+
|
|
62
|
+
def __init__(self, address: int, function: int, data: Union[bytes, str]):
|
|
63
|
+
"""
|
|
64
|
+
Initialize a message.
|
|
65
|
+
|
|
66
|
+
@param address: 8-bit device address (use 0 unless specified).
|
|
67
|
+
@param function: 8-bit function code representing the message type.
|
|
68
|
+
@param data: Message payload (bytes or string).
|
|
69
|
+
"""
|
|
70
|
+
self.address = address
|
|
71
|
+
self.function = function
|
|
72
|
+
self.data = data.encode('latin1') if isinstance(data, str) else data
|
|
73
|
+
|
|
74
|
+
def __bytes__(self) -> bytes:
|
|
75
|
+
"""
|
|
76
|
+
Converts Message to raw binary form suitable for transmission.
|
|
77
|
+
|
|
78
|
+
@return: Bytes representation of the message.
|
|
79
|
+
"""
|
|
80
|
+
msg_bytes = struct.pack('BBB', self.address, self.function, len(self.data)) + self.data
|
|
81
|
+
crc = self.gencrc(msg_bytes)
|
|
82
|
+
|
|
83
|
+
return bytes([self.SOH]) + msg_bytes + struct.pack('>H', crc)
|
|
84
|
+
|
|
85
|
+
def __str__(self) -> str:
|
|
86
|
+
"""
|
|
87
|
+
Returns hex representation of the message.
|
|
88
|
+
|
|
89
|
+
@return: Hexadecimal string representation.
|
|
90
|
+
"""
|
|
91
|
+
return self.__bytes__().hex()
|
|
92
|
+
|
|
93
|
+
def __repr__(self) -> str:
|
|
94
|
+
return f'Message(address={hex(self.address)}, function={hex(self.function)}, data={self.data!r})'
|
|
95
|
+
|
|
96
|
+
def sendto(self, serial_port):
|
|
97
|
+
"""
|
|
98
|
+
Sends this message to the provided serial port.
|
|
99
|
+
|
|
100
|
+
@param serial_port: Serial port to send the message.
|
|
101
|
+
"""
|
|
102
|
+
serial_port.write(bytes(self))
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def readfrom(cls, serial_port: serial.Serial):
|
|
106
|
+
"""
|
|
107
|
+
Reads a message from the serial port and constructs a Message instance.
|
|
108
|
+
|
|
109
|
+
@param serial_port: Serial interface to read from.
|
|
110
|
+
@return: Constructed Message instance.
|
|
111
|
+
@raises InvalidMessage: If message is incomplete or invalid.
|
|
112
|
+
"""
|
|
113
|
+
header = serial_port.read(4)
|
|
114
|
+
|
|
115
|
+
if len(header) < 4:
|
|
116
|
+
raise InvalidMessage('Incomplete header')
|
|
117
|
+
|
|
118
|
+
soh, address, function, length = struct.unpack('BBBB', header)
|
|
119
|
+
|
|
120
|
+
if soh != cls.SOH:
|
|
121
|
+
raise InvalidMessage('SOH does not match')
|
|
122
|
+
|
|
123
|
+
data = serial_port.read(length)
|
|
124
|
+
crc = serial_port.read(2)
|
|
125
|
+
if len(data) < length or len(crc) < 2:
|
|
126
|
+
raise InvalidMessage('Incomplete data or CRC')
|
|
127
|
+
|
|
128
|
+
msg = cls(address=address, function=function, data=data)
|
|
129
|
+
if bytes(msg)[-2:] != crc:
|
|
130
|
+
raise InvalidMessage('CRC does not match')
|
|
131
|
+
|
|
132
|
+
return msg
|
|
133
|
+
|
|
134
|
+
@staticmethod
|
|
135
|
+
def gencrc(msg_bytes: bytes) -> int:
|
|
136
|
+
"""
|
|
137
|
+
Generate a 16-bit CRC checksum.
|
|
138
|
+
|
|
139
|
+
@param msg_bytes: bytes containing message for checksum
|
|
140
|
+
@returns 16-bit integer containing CRC checksum
|
|
141
|
+
"""
|
|
142
|
+
crc = 0xFFFF
|
|
143
|
+
|
|
144
|
+
for byte in msg_bytes:
|
|
145
|
+
crc ^= byte
|
|
146
|
+
for _ in range(8):
|
|
147
|
+
crc = (crc >> 1) ^ 0xA001 if (crc & 1) else crc >> 1
|
|
148
|
+
|
|
149
|
+
return crc
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class QueryMessage(Message):
|
|
153
|
+
"""
|
|
154
|
+
A query message to be sent from host machine to card reader device. Magical constants taken from protocol documentation.
|
|
155
|
+
"""
|
|
156
|
+
POLLING = 0x00
|
|
157
|
+
GET_VERSION = 0x01
|
|
158
|
+
SET_SLAVE_ADDR = 0x02
|
|
159
|
+
LOGON = 0x03
|
|
160
|
+
LOGOFF = 0x04
|
|
161
|
+
SET_PASSWORD = 0x05
|
|
162
|
+
CLASSNAME = 0x06
|
|
163
|
+
SET_DATETIME = 0x07
|
|
164
|
+
GET_DATETIME = 0x08
|
|
165
|
+
GET_REGISTER = 0x09
|
|
166
|
+
SET_REGISTER = 0x0A
|
|
167
|
+
RECORD_COUNT = 0x0B
|
|
168
|
+
GET_FIRST_RECORD = 0x0C
|
|
169
|
+
GET_NEXT_RECORD = 0x0D
|
|
170
|
+
ERASE_ALL_RECORDS = 0x0E
|
|
171
|
+
ADD_RECORD = 0x0F
|
|
172
|
+
RECOVER_ALL_RECORDS = 0x10
|
|
173
|
+
DO = 0x11
|
|
174
|
+
DI = 0x12
|
|
175
|
+
ANALOG_INPUT = 0x13
|
|
176
|
+
THERMOMETER = 0x14
|
|
177
|
+
GET_NODE = 0x15
|
|
178
|
+
GET_SN = 0x16
|
|
179
|
+
SILENT_MODE = 0x17
|
|
180
|
+
RESERVE = 0x18
|
|
181
|
+
ENABLE_AUTO_MODE = 0x19
|
|
182
|
+
GET_TIME_ADJUST = 0x1A
|
|
183
|
+
ECHO = 0x18
|
|
184
|
+
SET_TIME_ADJUST = 0x1C
|
|
185
|
+
DEBUG = 0x1D
|
|
186
|
+
RESET = 0x1E
|
|
187
|
+
GO_TO_ISP = 0x1F
|
|
188
|
+
REQUEST = 0x20
|
|
189
|
+
ANTI_COLLISION = 0x21
|
|
190
|
+
SELECT_CARD = 0x22
|
|
191
|
+
AUTHENTICATE = 0x23
|
|
192
|
+
READ_BLOCK = 0x24
|
|
193
|
+
WRITE_BLOCk = 0x25
|
|
194
|
+
SET_VALUE = 0x26
|
|
195
|
+
READ_VALUE = 0x27
|
|
196
|
+
CREATE_VALUE_BLOCK = 0x28
|
|
197
|
+
ACCESS_CONDITION = 0x29
|
|
198
|
+
HALT = 0x2A
|
|
199
|
+
SAVE_KEY = 0x2B
|
|
200
|
+
GET_SECOND_SN = 0x2C
|
|
201
|
+
GET_ACCESS_CONDITION = 0x2D
|
|
202
|
+
AUTHENTICATE_KEY = 0x2E
|
|
203
|
+
REQUEST_ALL = 0x2F
|
|
204
|
+
SET_VALUEEX = 0x32
|
|
205
|
+
TRANSFER = 0x33
|
|
206
|
+
RESTORE = 0x34
|
|
207
|
+
GET_SECTOR = 0x3D
|
|
208
|
+
RF_POWER_ONOFF = 0x3E
|
|
209
|
+
AUTO_MODE = 0x3F
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class ResponseMessage(Message):
|
|
213
|
+
"""
|
|
214
|
+
Message received from the RFID reader.
|
|
215
|
+
"""
|
|
216
|
+
ACK = 0x06 # Acknowledge
|
|
217
|
+
NAK = 0x15 # Negative Acknowledge
|
|
218
|
+
EVN = 0x12 # Event Notification
|
|
219
|
+
|
|
220
|
+
def to_error(self) -> Optional[GNetPlusError]:
|
|
221
|
+
"""
|
|
222
|
+
Convert a NAK response into a GNetPlusError.
|
|
223
|
+
|
|
224
|
+
@returns Constructed instance of GNetPlusError for this response
|
|
225
|
+
"""
|
|
226
|
+
if self.function == self.NAK:
|
|
227
|
+
return GNetPlusError(f'Error: {repr(self.data)}')
|
|
228
|
+
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class Handle(object):
|
|
233
|
+
"""
|
|
234
|
+
Class for interfacing with the RFID card reader.
|
|
235
|
+
"""
|
|
236
|
+
|
|
237
|
+
def __init__(self, port: str = '/dev/ttyUSB0', baudrate: int = 19200, deviceaddr: int = 0, **kwargs):
|
|
238
|
+
"""
|
|
239
|
+
Initialize the RFID reader connection.
|
|
240
|
+
|
|
241
|
+
@params port: Serial port name (e.g., '/dev/ttyUSB0').
|
|
242
|
+
@params baudrate: Baudrate for interfacing with the device. Don't change this unless you know what you're doing.
|
|
243
|
+
@params deviceaddr: Device address (default: 0).
|
|
244
|
+
"""
|
|
245
|
+
self.port = port
|
|
246
|
+
self.baudrate = baudrate
|
|
247
|
+
self.deviceaddr = deviceaddr
|
|
248
|
+
|
|
249
|
+
try:
|
|
250
|
+
self.serial = serial.Serial(port, baudrate=baudrate, **kwargs)
|
|
251
|
+
except serial.SerialException as pe:
|
|
252
|
+
raise RuntimeError(f'Unable to open port {port}: {pe}')
|
|
253
|
+
|
|
254
|
+
def sendmsg(self, function: int, data: bytes = b'') -> None:
|
|
255
|
+
"""
|
|
256
|
+
Constructs and sends a QueryMessage to the RFID reader.
|
|
257
|
+
|
|
258
|
+
@param function: @see Message.function
|
|
259
|
+
@param data: @see Message.data
|
|
260
|
+
"""
|
|
261
|
+
QueryMessage(self.deviceaddr, function, data).sendto(self.serial)
|
|
262
|
+
|
|
263
|
+
def readmsg(self, sink_events: bool = False) -> ResponseMessage:
|
|
264
|
+
"""
|
|
265
|
+
Reads a message, optionally ignoring event (EVN) messages which are
|
|
266
|
+
device-driven.
|
|
267
|
+
|
|
268
|
+
@param sink_events Boolean dictating whether events should be ignored.
|
|
269
|
+
@returns: Constructed ResponseMessage instance.
|
|
270
|
+
@raises GNetPlusError: If a NAK response is received.
|
|
271
|
+
"""
|
|
272
|
+
while True:
|
|
273
|
+
response = ResponseMessage.readfrom(self.serial)
|
|
274
|
+
|
|
275
|
+
# skip over events. spec doesn't say what to do with them
|
|
276
|
+
if sink_events and response.function == ResponseMessage.EVN:
|
|
277
|
+
continue
|
|
278
|
+
|
|
279
|
+
break
|
|
280
|
+
|
|
281
|
+
if response.function == ResponseMessage.NAK:
|
|
282
|
+
raise response.to_error()
|
|
283
|
+
|
|
284
|
+
return response
|
|
285
|
+
|
|
286
|
+
def get_sn(self, endian: str = 'little', as_string: bool = True) -> Union[str, int]:
|
|
287
|
+
"""
|
|
288
|
+
Get the serial number of the card currently scanned.
|
|
289
|
+
|
|
290
|
+
@param endian: 'big' or 'little'. Specifies how to interpret the 4-byte UID.
|
|
291
|
+
For example, if the raw response data is b'\xE3\x0E\x27\x0E':
|
|
292
|
+
- 'big' interprets it as 0xE30E270E.
|
|
293
|
+
- 'little' interprets it as 0x0E270EE3.
|
|
294
|
+
@param as_string: If True, returns the UID as a formatted hexadecimal string (with leading zeros preserved);
|
|
295
|
+
otherwise, returns the UID as an integer.
|
|
296
|
+
@returns: The 16-byte serial number of the card currently scanned.
|
|
297
|
+
"""
|
|
298
|
+
self.sendmsg(QueryMessage.REQUEST)
|
|
299
|
+
self.readmsg(sink_events=True)
|
|
300
|
+
|
|
301
|
+
self.sendmsg(QueryMessage.ANTI_COLLISION)
|
|
302
|
+
response = self.readmsg(sink_events=True)
|
|
303
|
+
|
|
304
|
+
uid = struct.unpack('>L' if endian == 'big' else '<L', response.data)[0]
|
|
305
|
+
|
|
306
|
+
return f'0x{uid:08X}' if as_string else uid
|
|
307
|
+
|
|
308
|
+
def get_version(self) -> bytes:
|
|
309
|
+
"""
|
|
310
|
+
Get product version string. May contain null bytes, so be careful when using it.
|
|
311
|
+
|
|
312
|
+
@returns Product version string of the device connected to this handle.
|
|
313
|
+
"""
|
|
314
|
+
self.sendmsg(QueryMessage.GET_VERSION)
|
|
315
|
+
return self.readmsg().data
|
|
316
|
+
|
|
317
|
+
def set_auto_mode(self, enabled: bool = True) -> bytes:
|
|
318
|
+
"""
|
|
319
|
+
Toggle auto mode, i.e. whether the device emits events when a card comes close.
|
|
320
|
+
After setting verify the change.
|
|
321
|
+
|
|
322
|
+
@arg enabled Whether to enable or disable auto mode.
|
|
323
|
+
"""
|
|
324
|
+
mode = b'\x01' if enabled else b'\x00'
|
|
325
|
+
self.sendmsg(QueryMessage.AUTO_MODE, mode)
|
|
326
|
+
response = self.readmsg(sink_events=True)
|
|
327
|
+
|
|
328
|
+
if response.data != mode:
|
|
329
|
+
raise GNetPlusError('Failed to set auto mode')
|
|
330
|
+
return response.data
|
|
331
|
+
|
|
332
|
+
def wait_for_card(self, timeout: int = 10) -> Optional[str]:
|
|
333
|
+
"""
|
|
334
|
+
Check if a card is already present. If not, wait for an event.
|
|
335
|
+
|
|
336
|
+
@param timeout: Maximum time to wait in seconds (default: 10).
|
|
337
|
+
@return: Card serial number if found, else None.
|
|
338
|
+
@raises TimeoutError: If no card is detected within the timeout.
|
|
339
|
+
"""
|
|
340
|
+
self.set_auto_mode()
|
|
341
|
+
|
|
342
|
+
try:
|
|
343
|
+
card_sn = self.get_sn(as_string=True)
|
|
344
|
+
if card_sn:
|
|
345
|
+
logger.info(f'Card already present: {card_sn}')
|
|
346
|
+
return card_sn # Exit early if a card is already present
|
|
347
|
+
except GNetPlusError:
|
|
348
|
+
pass # Ignore errors, we'll wait for the card event
|
|
349
|
+
|
|
350
|
+
start_time = time.time()
|
|
351
|
+
while time.time() - start_time < timeout:
|
|
352
|
+
response = self.readmsg()
|
|
353
|
+
if response.function == ResponseMessage.EVN and b'I' in response.data:
|
|
354
|
+
logger.info(f'Card detected!')
|
|
355
|
+
return self.get_sn(as_string=True)
|
|
356
|
+
|
|
357
|
+
time.sleep(0.1)
|
|
358
|
+
|
|
359
|
+
raise TimeoutError('No card detected within the time limit')
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
if __name__ == '__main__':
|
|
363
|
+
try:
|
|
364
|
+
port = '/dev/ttyUSB0'
|
|
365
|
+
except IndexError:
|
|
366
|
+
sys.stderr.write('Usage: {0} <serial port>, example /dev/ttyUSB0\n'.format(sys.argv[0]))
|
|
367
|
+
sys.exit(1)
|
|
368
|
+
|
|
369
|
+
handle = Handle(port)
|
|
370
|
+
|
|
371
|
+
handle.wait_for_card()
|
|
372
|
+
|
|
373
|
+
try:
|
|
374
|
+
# Example: choose little-endian output as a formatted hex string.
|
|
375
|
+
print('Little endian format as string')
|
|
376
|
+
print('Found card: {0}'.format(handle.get_sn(endian='little', as_string=True)))
|
|
377
|
+
# Example: choose big-endian output as an integer.
|
|
378
|
+
print('Big endian format as integer')
|
|
379
|
+
print('Found card: {0}'.format(handle.get_sn(endian='big', as_string=False)))
|
|
380
|
+
except GNetPlusError:
|
|
381
|
+
print('Tap card again.')
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
GNU LESSER GENERAL PUBLIC LICENSE
|
|
2
|
+
Version 3, 29 June 2007
|
|
3
|
+
|
|
4
|
+
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
|
5
|
+
Everyone is permitted to copy and distribute verbatim copies
|
|
6
|
+
of this license document, but changing it is not allowed.
|
|
7
|
+
|
|
8
|
+
This version of the GNU Lesser General Public License incorporates
|
|
9
|
+
the terms and conditions of version 3 of the GNU General Public
|
|
10
|
+
License, supplemented by the additional permissions listed below.
|
|
11
|
+
|
|
12
|
+
### Additional Definitions ###
|
|
13
|
+
|
|
14
|
+
1. "Library" refers to a covered work governed by this License,
|
|
15
|
+
other than an Application or a Combined Work as defined below.
|
|
16
|
+
|
|
17
|
+
2. An "Application" is any work that makes use of an interface
|
|
18
|
+
provided by the Library, but which is not otherwise based on
|
|
19
|
+
the Library.
|
|
20
|
+
|
|
21
|
+
3. A "Combined Work" is a work produced by combining or linking an
|
|
22
|
+
Application with the Library.
|
|
23
|
+
|
|
24
|
+
4. The "Minimal Corresponding Source" for a Combined Work means the
|
|
25
|
+
Corresponding Source for the Combined Work, excluding any source
|
|
26
|
+
code for portions of the Combined Work that, considered in
|
|
27
|
+
isolation, are based on the Application, and not on the Library.
|
|
28
|
+
|
|
29
|
+
5. The "Corresponding Application Code" for a Combined Work means
|
|
30
|
+
the object code and/or source code for the Application, including
|
|
31
|
+
any data and utility programs needed for reproducing the Combined
|
|
32
|
+
Work from the Application, but excluding the System Libraries of
|
|
33
|
+
the Combined Work.
|
|
34
|
+
|
|
35
|
+
### Terms and Conditions ###
|
|
36
|
+
|
|
37
|
+
0. **Exceptions and Additional Permissions**
|
|
38
|
+
You may convey a covered work under this License without being
|
|
39
|
+
bound by Section 3 of the GNU GPL.
|
|
40
|
+
|
|
41
|
+
1. **Conveying Verbatim Copies**
|
|
42
|
+
You may copy and distribute verbatim copies of the Library’s
|
|
43
|
+
complete source code as you receive it, in any medium, provided
|
|
44
|
+
that you include a copy of this License along with the Library.
|
|
45
|
+
|
|
46
|
+
2. **Conveying Modified Versions**
|
|
47
|
+
You may modify your copy or copies of the Library, or any portion
|
|
48
|
+
of it, and distribute such modifications, provided that:
|
|
49
|
+
|
|
50
|
+
- a) The modified work must be licensed under this License.
|
|
51
|
+
- b) The work must carry prominent notices stating that you
|
|
52
|
+
changed the Library.
|
|
53
|
+
- c) You must keep any additional permissions granted.
|
|
54
|
+
|
|
55
|
+
3. **Combined Works**
|
|
56
|
+
You may convey a Combined Work under terms of your choice,
|
|
57
|
+
provided that you also meet these conditions:
|
|
58
|
+
|
|
59
|
+
- a) The Combined Work must be accompanied by a copy of this
|
|
60
|
+
License.
|
|
61
|
+
- b) You must supply the Minimal Corresponding Source.
|
|
62
|
+
- c) You must ensure that users can modify the Library’s source
|
|
63
|
+
code.
|
|
64
|
+
|
|
65
|
+
4. **Compatibility with GPL**
|
|
66
|
+
If you modify the Library, you may extend this exception to
|
|
67
|
+
modified versions.
|
|
68
|
+
|
|
69
|
+
### Disclaimer ###
|
|
70
|
+
|
|
71
|
+
THE LIBRARY IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
|
|
72
|
+
TO THE EXTENT PERMITTED BY APPLICABLE LAW, NEITHER THE COPYRIGHT
|
|
73
|
+
HOLDER NOR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE
|
|
74
|
+
LIBRARY SHALL BE LIABLE FOR ANY DAMAGES.
|
|
75
|
+
|
|
76
|
+
For more details, see <https://www.gnu.org/licenses/>.
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: mifarepy
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: Python library for interfacing with MIFARE RFID card readers using mifarepy protocol
|
|
5
|
+
Author-email: Spark Drago <huzaifa.farooq05@gmail.com>
|
|
6
|
+
License: GNU LESSER GENERAL PUBLIC LICENSE
|
|
7
|
+
Version 3, 29 June 2007
|
|
8
|
+
|
|
9
|
+
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
|
10
|
+
Everyone is permitted to copy and distribute verbatim copies
|
|
11
|
+
of this license document, but changing it is not allowed.
|
|
12
|
+
|
|
13
|
+
This version of the GNU Lesser General Public License incorporates
|
|
14
|
+
the terms and conditions of version 3 of the GNU General Public
|
|
15
|
+
License, supplemented by the additional permissions listed below.
|
|
16
|
+
|
|
17
|
+
### Additional Definitions ###
|
|
18
|
+
|
|
19
|
+
1. "Library" refers to a covered work governed by this License,
|
|
20
|
+
other than an Application or a Combined Work as defined below.
|
|
21
|
+
|
|
22
|
+
2. An "Application" is any work that makes use of an interface
|
|
23
|
+
provided by the Library, but which is not otherwise based on
|
|
24
|
+
the Library.
|
|
25
|
+
|
|
26
|
+
3. A "Combined Work" is a work produced by combining or linking an
|
|
27
|
+
Application with the Library.
|
|
28
|
+
|
|
29
|
+
4. The "Minimal Corresponding Source" for a Combined Work means the
|
|
30
|
+
Corresponding Source for the Combined Work, excluding any source
|
|
31
|
+
code for portions of the Combined Work that, considered in
|
|
32
|
+
isolation, are based on the Application, and not on the Library.
|
|
33
|
+
|
|
34
|
+
5. The "Corresponding Application Code" for a Combined Work means
|
|
35
|
+
the object code and/or source code for the Application, including
|
|
36
|
+
any data and utility programs needed for reproducing the Combined
|
|
37
|
+
Work from the Application, but excluding the System Libraries of
|
|
38
|
+
the Combined Work.
|
|
39
|
+
|
|
40
|
+
### Terms and Conditions ###
|
|
41
|
+
|
|
42
|
+
0. **Exceptions and Additional Permissions**
|
|
43
|
+
You may convey a covered work under this License without being
|
|
44
|
+
bound by Section 3 of the GNU GPL.
|
|
45
|
+
|
|
46
|
+
1. **Conveying Verbatim Copies**
|
|
47
|
+
You may copy and distribute verbatim copies of the Library’s
|
|
48
|
+
complete source code as you receive it, in any medium, provided
|
|
49
|
+
that you include a copy of this License along with the Library.
|
|
50
|
+
|
|
51
|
+
2. **Conveying Modified Versions**
|
|
52
|
+
You may modify your copy or copies of the Library, or any portion
|
|
53
|
+
of it, and distribute such modifications, provided that:
|
|
54
|
+
|
|
55
|
+
- a) The modified work must be licensed under this License.
|
|
56
|
+
- b) The work must carry prominent notices stating that you
|
|
57
|
+
changed the Library.
|
|
58
|
+
- c) You must keep any additional permissions granted.
|
|
59
|
+
|
|
60
|
+
3. **Combined Works**
|
|
61
|
+
You may convey a Combined Work under terms of your choice,
|
|
62
|
+
provided that you also meet these conditions:
|
|
63
|
+
|
|
64
|
+
- a) The Combined Work must be accompanied by a copy of this
|
|
65
|
+
License.
|
|
66
|
+
- b) You must supply the Minimal Corresponding Source.
|
|
67
|
+
- c) You must ensure that users can modify the Library’s source
|
|
68
|
+
code.
|
|
69
|
+
|
|
70
|
+
4. **Compatibility with GPL**
|
|
71
|
+
If you modify the Library, you may extend this exception to
|
|
72
|
+
modified versions.
|
|
73
|
+
|
|
74
|
+
### Disclaimer ###
|
|
75
|
+
|
|
76
|
+
THE LIBRARY IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
|
|
77
|
+
TO THE EXTENT PERMITTED BY APPLICABLE LAW, NEITHER THE COPYRIGHT
|
|
78
|
+
HOLDER NOR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE
|
|
79
|
+
LIBRARY SHALL BE LIABLE FOR ANY DAMAGES.
|
|
80
|
+
|
|
81
|
+
For more details, see <https://www.gnu.org/licenses/>.
|
|
82
|
+
|
|
83
|
+
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
|
|
84
|
+
Classifier: Programming Language :: Python :: 3
|
|
85
|
+
Classifier: Operating System :: OS Independent
|
|
86
|
+
Requires-Python: >=3.6
|
|
87
|
+
Description-Content-Type: text/markdown
|
|
88
|
+
License-File: LICENSE
|
|
89
|
+
Requires-Dist: pyserial
|
|
90
|
+
|
|
91
|
+
# **mifarepy**
|
|
92
|
+
|
|
93
|
+
## **Overview**
|
|
94
|
+
|
|
95
|
+
`mifarepy` is a **Python library** for interfacing with **MIFARE® RFID card readers** using the **GNetPlus® protocol**.
|
|
96
|
+
It enables communication with **PROMAG card readers**, specifically the **PCR310U** and other **GIGA-TMS Inc.** devices
|
|
97
|
+
that support **ISO14443A MIFARE® Ultra-Light/1K/PRO** cards.
|
|
98
|
+
|
|
99
|
+
This library provides functions for:
|
|
100
|
+
|
|
101
|
+
- **Reading serial numbers** from MIFARE® cards
|
|
102
|
+
- **Interacting via RS232 and USB-serial** interfaces
|
|
103
|
+
- **Supporting GNetPlus® commands** (Read, Write, Authenticate, Auto Mode, etc.)
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## **Attribution & Original Repository**
|
|
108
|
+
|
|
109
|
+
This project is **derived from** the original `mifarepy.py` by **Chow Loong Jin & Harish Pillay**.
|
|
110
|
+
|
|
111
|
+
- **Original Repository:** [gnetplus by harishpillay](https://github.com/harishpillay/gnetplus)
|
|
112
|
+
- **Original Authors:** Chow Loong Jin & Harish Pillay
|
|
113
|
+
- **License:** This project remains under **LGPL v3.0 or later** to comply with the original licensing terms.
|
|
114
|
+
|
|
115
|
+
This version of `gnetplus.py` includes **bug fixes, documentation improvements, and enhanced compatibility**.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## **Supported Hardware**
|
|
120
|
+
|
|
121
|
+
This library is compatible with **PROMAG** MIFARE® readers, including:
|
|
122
|
+
|
|
123
|
+
- **PCR310U** (USB-based)
|
|
124
|
+
- **MF5 OEM Read/Write Module**
|
|
125
|
+
- **MF10 MIFARE Read/Write Module**
|
|
126
|
+
- **Other devices using the GNetPlus® protocol**
|
|
127
|
+
|
|
128
|
+
These readers operate at **13.56 MHz** and support **MIFARE® 1K/4K, Ultra-Light, and PRO cards**.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## **Installation**
|
|
133
|
+
|
|
134
|
+
To install `mifarepy`, ensure **Python 3.6+** is installed, then run:
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
pip install pyserial
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Or manually include the `mifarepy.py` file in your project.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## **Usage**
|
|
145
|
+
|
|
146
|
+
### **Command-Line Usage**
|
|
147
|
+
|
|
148
|
+
You can run `mifarepy.py` directly from the command line:
|
|
149
|
+
|
|
150
|
+
```sh
|
|
151
|
+
python mifarepy.py /dev/ttyUSB0
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Replace `/dev/ttyUSB0` with the correct serial port.
|
|
155
|
+
|
|
156
|
+
### **Library Usage (Python)**
|
|
157
|
+
|
|
158
|
+
You can also use it in your Python scripts:
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from mifarepy import Handle
|
|
162
|
+
|
|
163
|
+
handle = Handle('/dev/ttyUSB0')
|
|
164
|
+
serial_number = handle.get_sn(as_string=True)
|
|
165
|
+
print(f'Found card: {serial_number}')
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## **Communicating with the Reader**
|
|
171
|
+
|
|
172
|
+
### **Detecting the Device**
|
|
173
|
+
|
|
174
|
+
When plugged into a **Linux** system (such as **Raspberry Pi** or **Fedora**), the reader is detected as:
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
Prolific Technology, Inc. PL2303 Serial Port
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
To find the assigned port, check:
|
|
181
|
+
|
|
182
|
+
```sh
|
|
183
|
+
dmesg | grep ttyUSB
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Example output:
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
usb 6-1: pl2303 converter now attached to ttyUSB3
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
This means the device is at `/dev/ttyUSB3`.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## **Supported Commands**
|
|
197
|
+
|
|
198
|
+
This library supports the following **GNetPlus® protocol commands**:
|
|
199
|
+
|
|
200
|
+
| Command | Functionality |
|
|
201
|
+
|-----------------------|----------------------------------------------|
|
|
202
|
+
| **Polling** | Check if a reader is connected |
|
|
203
|
+
| **Get Version** | Retrieve firmware version |
|
|
204
|
+
| **Logon/Logoff** | Secure access |
|
|
205
|
+
| **Get Serial Number** | Retrieve MIFARE® card serial number |
|
|
206
|
+
| **Read Block** | Read memory block from MIFARE® 1K card |
|
|
207
|
+
| **Write Block** | Write to a specific block |
|
|
208
|
+
| **Authenticate** | Perform authentication with Key A/Key B |
|
|
209
|
+
| **Set Auto Mode** | Enable/Disable automatic event notifications |
|
|
210
|
+
| **Request All** | Detect multiple cards in the field |
|
|
211
|
+
|
|
212
|
+
For a full list of commands, refer to the *
|
|
213
|
+
*[mifarepy Communication Protocol](./TM970013_GNetPlusCommunicationProtocol_REV_D.pdf)**.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## **Example Output**
|
|
218
|
+
|
|
219
|
+
When a card is detected, you will see:
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
Found card: 0x19593d65
|
|
223
|
+
Tap card again.
|
|
224
|
+
Found card: 0x19593d65
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
If no card is found, the script prompts:
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
Tap card again.
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## **MIFARE® 1K Card Structure**
|
|
236
|
+
|
|
237
|
+
The **MIFARE® 1K card** consists of **16 sectors**, each with **4 blocks** (16 bytes each).
|
|
238
|
+
Memory layout:
|
|
239
|
+
|
|
240
|
+
- **Blocks 0-3**: Sector 0 (First block stores manufacturer data)
|
|
241
|
+
- **Blocks 4-7**: Sector 1
|
|
242
|
+
- **Blocks 8-11**: Sector 2
|
|
243
|
+
- **...**
|
|
244
|
+
- **Blocks 60-63**: Sector 15 (Contains access keys & conditions)
|
|
245
|
+
|
|
246
|
+
For authentication, use **Key A** or **Key B** stored in the last block of each sector.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## **MIFARE® 1K Authentication & Security**
|
|
251
|
+
|
|
252
|
+
1. **Authenticate** before reading/writing.
|
|
253
|
+
2. Use **GNetPlus SAVE_KEY command** to store keys securely.
|
|
254
|
+
3. Blocks are **protected** by access conditions.
|
|
255
|
+
4. **Keys should not be stored in the same sector** as sensitive data.
|
|
256
|
+
|
|
257
|
+
For further details, refer to:
|
|
258
|
+
|
|
259
|
+
- **[MIFARE Application Programming Guide](./TM970014_MifareAppliactionProgrammingGuide_REV_H.pdf)**
|
|
260
|
+
- **[MIFARE Demo Quick Start](./TM970018_Mifare%20Demo%20Quick%20Start.pdf)**
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## **License**
|
|
265
|
+
|
|
266
|
+
This project is licensed under **GNU Lesser General Public License v3.0 or later (LGPL-3.0-or-later)**.
|
|
267
|
+
See [COPYING](./COPYING) for full details.
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## **Documentation & References**
|
|
272
|
+
|
|
273
|
+
For more details, refer to:
|
|
274
|
+
|
|
275
|
+
- **[GNetPlus Communication Protocol](./TM970013_GNetPlusCommunicationProtocol_REV_D.pdf)**
|
|
276
|
+
- **[MIFARE Application Guide](./TM970014_MifareAppliactionProgrammingGuide_REV_H.pdf)**
|
|
277
|
+
- **[MIFARE RWD Specification](./TM970023_RWD_SPEC.pdf)**
|
|
278
|
+
- **[MF10 Instruction Sheet](./TM951179_MF10_Instruction.pdf)**
|
|
279
|
+
|
|
280
|
+
For further information, visit: [GIGA-TMS Inc.](http://www.gigatms.com.tw)
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## **Author & Credits**
|
|
285
|
+
|
|
286
|
+
**Original Authors:**
|
|
287
|
+
|
|
288
|
+
- **Chow Loong Jin** (<lchow@redhat.com>)
|
|
289
|
+
- **Harish Pillay** (<hpillay@redhat.com>)
|
|
290
|
+
|
|
291
|
+
**Adapted & Maintained by:**
|
|
292
|
+
|
|
293
|
+
- **Spark Drago** (<https://github.com/SparkDrago05>)
|
|
294
|
+
|
|
295
|
+
---
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
mifarepy/__init__.py,sha256=b3GU-wgcwNkLLRGx3wPg9mXTYPlOn9LyPNm8SqSKxXc,100
|
|
2
|
+
mifarepy/mifarepy.py,sha256=TuLJoSrWy7fTMbxFsYWSA2L7lPmrk2dsTZ4Vdl-VbT4,11987
|
|
3
|
+
mifarepy-1.0.2.dist-info/LICENSE,sha256=6lmP2pKofwSyOakqTROPpM1SeDPEZzDMNa2GKoqgju0,2986
|
|
4
|
+
mifarepy-1.0.2.dist-info/METADATA,sha256=xih5bD4FW-CCfrqmSSKQPolt9dTxDgT_2ljRa_sa2xw,9604
|
|
5
|
+
mifarepy-1.0.2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
6
|
+
mifarepy-1.0.2.dist-info/top_level.txt,sha256=gYvynjK6HDZN0ljW4VpJy5TpTZbVqgJuhAyAzZPx_Dw,9
|
|
7
|
+
mifarepy-1.0.2.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mifarepy
|