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 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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.43.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ mifarepy