PepperC1 Client
Main driver class for the Eccel Peeper C1 RFID reader. Located in pepper_c1/client.py.
| Parameter | Type | Default | Description |
|---|---|---|---|
| transport | Transport | — | A UARTTransport or TCPTransport instance. |
| response_timeout | float | 2.0 | Maximum seconds to wait for a device response before raising TimeoutError. |
from pepper_c1 import PepperC1, UARTTransport # as context manager (recommended) with PepperC1(UARTTransport('COM3')) as reader: print(reader.get_version()) # manual lifecycle reader = PepperC1(UARTTransport('COM3'), response_timeout=5.0) try: print(reader.get_version()) finally: reader.close()
Low-Level Interface
self.async_events.| Parameter | Type | Default | Description |
|---|---|---|---|
| cmd | Command | — | Command opcode from the Command enum. |
| data | bytes | b"" | Optional payload bytes appended after the command byte. |
response_timeout.from pepper_c1.commands import Command # direct use of a command not wrapped by a convenience method version_raw = reader.send_command(Command.MFU_GET_VERSION) sig = reader.send_command(Command.MFU_READ_SIG)
General Commands
"1.0.3".print(reader.get_version()) # e.g. "1.0.3"
count = reader.get_tag_count() if count == 0: print("No tag in field")
payload[0]— tag type byte (see type table)payload[1]— parameter bytepayload[2:]— UID bytes
| Parameter | Type | Default | Description |
|---|---|---|---|
| tag_index | int | 0 | Zero-based index of the tag (up to get_tag_count() - 1). |
[type, param, uid_byte...].uid_info = reader.get_uid(0) type_byte = uid_info[0] uid = uid_info[2:] print(f"Type: 0x{type_byte:02X}, UID: {uid.hex()}")
| Parameter | Type | Default | Description |
|---|---|---|---|
| tag_index | int | 0 | Zero-based index of the tag to activate. |
reader.activate_tag(0) # now MIFARE operations target this tag
ASYNC events (stored in reader.async_events) when a tag is detected or removed.| Parameter | Type | Default | Description |
|---|---|---|---|
| enable | bool | — | True to start polling, False to stop. |
| interval_ms | int | 500 | Polling interval in milliseconds (0–65535). |
reader.set_polling(True, interval_ms=250) # ... do other work; async events accumulate in reader.async_events reader.set_polling(False)
| Parameter | Type | Description |
|---|---|---|
| antenna | int | Antenna number, 1–8. |
ValueError if antenna is outside the range 1–8.reader.set_active_antenna(1) # switch to antenna 1 reader.set_active_antenna(4) # switch to antenna 4 # cycle through all antennas for ant in range(1, 9): reader.set_active_antenna(ant) count = reader.get_tag_count() if count: print(f"antenna {ant}: tag found")
__exit__.Key Management
Keys are stored in numbered slots in the reader's volatile memory. Use save_keys() to persist them to non-volatile storage. Key type constants are available in pepper_c1.commands.
| Constant | Value | Key size |
|---|---|---|
| KEY_TYPE_AES128 | 0x00 | 16 bytes |
| KEY_TYPE_AES192 | 0x01 | 24 bytes |
| KEY_TYPE_AES256 | 0x02 | 32 bytes |
| KEY_TYPE_DES | 0x03 | 8 bytes |
| KEY_TYPE_2K3DES | 0x04 | 16 bytes |
| KEY_TYPE_3K3DES | 0x05 | 24 bytes |
| KEY_TYPE_MIFARE | 0x06 | 12 bytes (key A + key B, 6+6) |
save_keys() to persist them.| Parameter | Type | Description |
|---|---|---|
| key_slot | int | Slot index (0-based) to store the key in. |
| key_type | int | One of the KEY_TYPE_* constants (see table above). |
| key_data | bytes | Raw key bytes. For KEY_TYPE_MIFARE, concatenate key A (6 bytes) + key B (6 bytes). |
from pepper_c1.commands import KEY_TYPE_MIFARE, KEY_TYPE_AES128, KEY_TYPE_DES # MIFARE Classic: factory default key FFFFFFFFFFFF for both A and B reader.set_key(0, KEY_TYPE_MIFARE, bytes([0xFF] * 12)) # DESFire: DES all-zeros (factory default PICC master key) reader.set_key(0, KEY_TYPE_DES, bytes(16)) # DESFire: AES-128 all-zeros reader.set_key(1, KEY_TYPE_AES128, bytes(16))
MIFARE Classic
Supports MIFARE Classic 1K, 4K, and Mini tags. Call activate_tag() and set_key() (with KEY_TYPE_MIFARE) before any Classic operation.
| Parameter | Type | Default | Description |
|---|---|---|---|
| block | int | — | First block number to read. |
| count | int | 1 | Number of consecutive blocks to read. |
| auth_param | int | 0x0A | Firmware authentication parameter (empirically 0x0A; selects key A). |
| key_no | int | 0 | Key slot index (set via set_key()). |
count × 16 bytes.data = reader.mf_read_block(5) # 16 bytes two_blocks = reader.mf_read_block(4, count=2) # 32 bytes
| Parameter | Type | Default | Description |
|---|---|---|---|
| block | int | — | First block number to write. |
| data | bytes | — | Raw bytes, must be exactly count × 16 bytes. |
| count | int | 1 | Number of consecutive blocks. |
| auth_param | int | 0x0A | Firmware authentication parameter. |
| key_no | int | 0 | Key slot index. |
len(data) != count * 16.reader.mf_write_block(5, b'\x00' * 16) # clear block 5
val = reader.mf_read_value(6) print(f"Value: {val}")
| Parameter | Type | Description |
|---|---|---|
| block | int | Block number. |
| value | int | Signed 32-bit initial value. |
reader.mf_write_value(6, 1000) # set value block to 1000
delta. The firmware also executes the TRANSFER step internally.| Parameter | Type | Description |
|---|---|---|
| block | int | Value block number. |
| delta | int | Unsigned 32-bit increment amount. |
reader.mf_increment(6, 50) val = reader.mf_read_value(6) # 1050
MIFARE Ultralight
Supports MIFARE Ultralight, Ultralight-C, and Ultralight EV1. Call activate_tag() before page operations.
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | int | — | First page number. User data starts at page 4. |
| count | int | 1 | Number of consecutive 4-byte pages to read. |
count × 4 bytes.page_data = reader.mfu_read_page(4) # 4 bytes eight_bytes = reader.mfu_read_page(4, count=2)
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | int | — | First page number to write. |
| data | bytes | — | Raw bytes, must be exactly count × 4 bytes. |
| count | int | 1 | Number of consecutive pages. |
len(data) != count * 4.reader.mfu_write_page(4, b'\xDE\xAD\xBE\xEF')
| Parameter | Type | Description |
|---|---|---|
| counter | int | Counter index (0–2). |
val = reader.mfu_read_counter(0) print(f"Counter 0: {val}")
delta (Ultralight EV1 only). NFC counters are one-way — they cannot be decremented or reset.| Parameter | Type | Description |
|---|---|---|
| counter | int | Counter index (0–2). |
| delta | int | Amount to add (24-bit unsigned). |
reader.mfu_increment_counter(0, 1)
| Parameter | Type | Default | Description |
|---|---|---|---|
| password | bytes | — | 4-byte password. Factory default: b'\xFF\xFF\xFF\xFF'. |
| pack | bytes | b"\x00\x00" | 2-byte expected PACK response. |
pack = reader.mfu_passwd_auth(b'\xFF\xFF\xFF\xFF') print(f"PACK: {pack.hex()}")
MIFARE DESFire
Full support for DESFire EV1/EV2. Typical workflow: select application → authenticate → file operations → commit transaction. DESFire operations do not require activate_tag().
mfdf_commit_transaction(). Use mfdf_abort_transaction() to discard.b'\x00\x00\x00' to select the PICC master application.| Parameter | Type | Description |
|---|---|---|
| aid | bytes | 3-byte AID. Master app: b'\x00\x00\x00'. |
reader.mfdf_select_app(b'\x00\x00\x00') # PICC master reader.mfdf_select_app(bytes([0xAA, 0x55, 0xAA])) # custom app
aids = reader.mfdf_get_app_ids() for aid in aids: print(aid.hex())
| Parameter | Type | Default | Description |
|---|---|---|---|
| key_no | int | — | Key number within the selected DESFire application. |
| key_slot | int | 0 | Reader key slot that holds the key bytes. |
reader.mfdf_select_app(b'\x00\x00\x00') reader.mfdf_auth(0, key_slot=0) # DES key 0 from reader slot 0
| Parameter | Type | Default | Description |
|---|---|---|---|
| key_no | int | — | Key number within the selected DESFire application. |
| key_slot | int | 0 | Reader key slot (must contain an AES key). |
reader.mfdf_auth_aes(1, key_slot=1) # AES key 1 from reader slot 1
| Parameter | Type | Description |
|---|---|---|
| aid | bytes | 3-byte Application Identifier. |
| key_settings | int | Key settings byte (e.g. 0xED: master key changeable, config changeable, free dir access). |
| num_keys | int | Number and type of keys. Upper nibble: key type (0x8=AES). Lower nibble: number of keys. |
# Create app with 4 AES-128 keys (0x84 = 0x8«AES» | 4«keys») reader.mfdf_create_app(bytes([0xAA, 0x55, 0xAA]), key_settings=0xED, num_keys=0x84)
| Parameter | Type | Description |
|---|---|---|
| aid | bytes | 3-byte AID of the application to delete. |
| Parameter | Type | Default | Description |
|---|---|---|---|
| file_no | int | — | File number (0–14). |
| access_rights | int | — | 16-bit access rights field. 0xEEEE = free access for all operations. |
| size | int | — | File size in bytes. |
| backup | bool | False | If True, creates a backup data file (changes require commit_transaction()). |
reader.mfdf_create_data_file(0x01, access_rights=0xEEEE, size=32, backup=True)
mfdf_commit_transaction() afterwards to persist the data.| Parameter | Type | Default | Description |
|---|---|---|---|
| file_no | int | — | File number. |
| offset | int | — | Byte offset within the file to start writing. |
| data | bytes | — | Data bytes to write. |
| comm_mode | int | 0x00 | Communication mode: 0x00=plain, 0x01=MAC, 0x03=encrypted. |
reader.mfdf_write_data(0x01, offset=0, data=b"Hello, DESFire!") reader.mfdf_commit_transaction()
| Parameter | Type | Default | Description |
|---|---|---|---|
| file_no | int | — | File number. |
| offset | int | — | Byte offset to start reading from. |
| length | int | — | Number of bytes to read. |
| comm_mode | int | 0x00 | Communication mode. |
data = reader.mfdf_read_data(0x01, offset=0, length=15) print(data.rstrip(b'\x00'))
| Parameter | Type | Default | Description |
|---|---|---|---|
| file_no | int | — | File number. |
| access_rights | int | — | 16-bit access rights. |
| lower_limit | int | — | Minimum allowed value (signed 32-bit). |
| upper_limit | int | — | Maximum allowed value (signed 32-bit). |
| init_value | int | — | Initial value stored in the file. |
| limited_credit | bool | False | Enable limited credit feature. |
reader.mfdf_create_value_file(0x02, access_rights=0xEEEE, lower_limit=-100, upper_limit=100, init_value=-5, limited_credit=True)
val = reader.mfdf_get_value(0x02) # e.g. -5
amount to a value file. Call mfdf_commit_transaction() to persist. The result must not exceed upper_limit.reader.mfdf_credit(0x02, 10) reader.mfdf_commit_transaction()
amount from a value file. Call mfdf_commit_transaction() to persist. The result must not go below lower_limit.reader.mfdf_debit(0x02, 25) reader.mfdf_commit_transaction()
| Parameter | Type | Default | Description |
|---|---|---|---|
| file_no | int | — | File number. |
| access_rights | int | — | 16-bit access rights. |
| record_size | int | — | Size of each record in bytes. |
| max_records | int | — | Maximum number of records the file can hold. |
| cyclic | bool | True | If True, cyclic (ring buffer). If False, linear. |
mfdf_commit_transaction() after each write.record = b"\x01\x00" + b"My record data".ljust(32, b'\x00') reader.mfdf_write_record(0x03, record) reader.mfdf_commit_transaction()
offset skips N newest records. The full response contains all records from offset concatenated — slice [:record_size] to extract one record.| Parameter | Type | Description |
|---|---|---|
| file_no | int | File number. |
| offset | int | Number of newest records to skip. |
| record_size | int | Record size as configured when creating the file. |
offset.# Read the newest record raw = reader.mfdf_read_record(0x03, offset=0, record_size=34) first_record = raw[:34]
mfdf_commit_transaction().ICODE
Supports ISO 15693 tags: ICODE SLI, SLI-S, SLI-L, SLIX, SLIX-S, SLIX-L, SLIX2, and DNA. ICODE tags do not require activate_tag().
| Parameter | Type | Default | Description |
|---|---|---|---|
| block | int | — | First block number to read. |
| count | int | 1 | Number of consecutive 4-byte blocks to read. |
count × 4 bytes.data = reader.icode_read_block(5) # 4 bytes
| Parameter | Type | Default | Description |
|---|---|---|---|
| block | int | — | First block number to write. |
| data | bytes | — | Raw bytes, must be exactly count × 4 bytes. |
| count | int | 1 | Number of consecutive blocks. |
len(data) != count * 4.reader.icode_write_block(5, b'\x01\x02\x03\x04')
info = reader.icode_get_system_information() print(info.hex(' '))