Tools & Examples
Ready-to-run scripts in the tools/ directory demonstrating practical use of all tag families. Run from the project root.
uart <port> and tcp — for selecting the transport. Use --help to see all options.
read_uid.py Download
Purpose: The simplest example — detect all tags in the field, print their type and UID. No activation or authentication required.
Usage
python read_uid.py uart COM3 python read_uid.py uart /dev/ttyUSB0 python read_uid.py tcp --host 192.168.1.100 --port 1234
Options
| Sub-command / Option | Description |
|---|---|
| uart <port> | Connect via UART. port is e.g. COM3 or /dev/ttyUSB0. |
| uart --baudrate INT | Serial baud rate (default: 115200). |
| tcp --host STR | TCP host (default: 192.168.100.1). |
| tcp --port INT | TCP port (default: 1234). |
Sample Output
Firmware : 1.0.3 Tags found: 2 [0] MIFARE Classic 1K UID: 04a1b2c3 [1] ICODE SLIX UID: e004030200a1b2c3
How it works
with PepperC1(_make_transport(args)) as reader: tag_count = reader.get_tag_count() for i in range(tag_count): uid_info = reader.get_uid(i) type_byte = uid_info[0] # tag type identifier uid = uid_info[2:] # UID bytes
mifare_classic.py Download
Compatible tags: MIFARE Classic 1K (0x04), 4K (0x05), Mini (0x10), generic Classic (0x03).
Purpose: Demonstrates data block read/write and value block operations.
Usage
python mifare_classic.py uart COM3 python mifare_classic.py uart COM3 --block 4 --vblock 7 --key AABBCCDDEEFF
Options
| Option | Default | Description |
|---|---|---|
| --block INT | 5 | Block number for the read/write test. Must be a data block (not a sector trailer). |
| --vblock INT | 6 | Block number for the value block test. |
| --key HEX | FFFFFFFFFFFF | 6-byte MIFARE key as 12 hex characters. Used as both key A and key B. |
What it tests
- Load key into reader slot 0 (
KEY_TYPE_MIFARE, key A + key B) - Detect and activate tag
- Read block → write random 16 bytes → verify readback → restore original
- Initialise value block to 1000 → increment by 50 → verify result is 1050
Sample Output
Firmware : 1.0.3 Tag type : 0x04 UID : 04a1b2c3 Key loaded: FFFFFFFFFFFF --- Data block 5 --- original : 00000000000000000000000000000000 written : 3f8a12c491e76b205d03f8a4c2917e3b readback : 3f8a12c491e76b205d03f8a4c2917e3b PASS restored : 00000000000000000000000000000000 --- Value block 6 --- after write 1000 : 1000 after increment 50: 1050 Done.
Key code pattern
from pepper_c1.commands import KEY_TYPE_MIFARE reader.set_key(0, KEY_TYPE_MIFARE, key + key) # key A + key B reader.activate_tag(0) original = reader.mf_read_block(block) reader.mf_write_block(block, new_data) reader.mf_write_value(vblock, 1000) reader.mf_increment(vblock, 50)
mifare_ultralight.py Download
Compatible tags: MIFARE Ultralight (0x01), Ultralight-C (0x02).
Purpose: Demonstrates page read/write, NFC counter, card version, and signature operations.
Usage
python mifare_ultralight.py uart COM3 python mifare_ultralight.py uart COM3 --page 5 --counter 1
Options
| Option | Default | Description |
|---|---|---|
| --page INT | 4 | Page number for read/write test. Page 4 is the first user-writable page on Ultralight. |
| --counter INT | 0 | NFC counter index (0–2, EV1 only). |
What it tests
- Read page → write random 4 bytes → verify readback → restore original
- Read NFC counter → increment by 1 → verify new value
- Read card version bytes (EV1 only — gracefully skipped on plain Ultralight)
- Read NXP originality signature (EV1 only)
Sample Output
Firmware : 1.0.3 Tag type : 0x01 UID : 04d3e5f601234 --- Page 4 --- original : a1b2c3d4 written : 9f3e7a2b readback : 9f3e7a2b PASS restored : a1b2c3d4 --- NFC counter 0 --- current value : 42 after +1 : 43 --- Card version (EV1 only) --- not available on this card variant: Command 0x42 failed...
Key code pattern
reader.activate_tag(0) original = reader.mfu_read_page(page) reader.mfu_write_page(page, new_data) val = reader.mfu_read_counter(counter) reader.mfu_increment_counter(counter, 1) # EV1 only — use send_command for unwrapped commands ver = reader.send_command(Command.MFU_GET_VERSION) sig = reader.send_command(Command.MFU_READ_SIG)
desfire.py Download
Compatible tags: MIFARE DESFire (0x0D).
Purpose: Full DESFire feature test — data file, value file, cyclic record file.
Usage
python desfire.py uart COM3 python desfire.py tcp --host 192.168.1.100 --port 1234
Test Sequence
- Load keys — slot 0: DES all-zeros (factory PICC master); slot 1: AES-128 all-zeros; slot 2: AES-128 0x01×16
- Select & auth PICC master app —
AID=000000, DES key 0 - Format card — erase all apps/files, print free memory and card version
- Create test app —
AID=AA55AA, 4 AES-128 keys, key_settings=0xED - Data file (0x01, 32B backup) — write
"Ala ma kota"→ commit → read back → verify - Value file (0x02, range −100..100, init −5) — credit +10 → verify 5; debit −25 → verify −20
- Cyclic record file (0x03, 34B records, max 10) — write 5 records → read back (newest-first) → clear
- Cleanup — delete files 0x01/0x02/0x03 → delete app AA55AA
Sample Output
Firmware : 1.0.3 Tag type : 0x0D UID : 042e3f4a5b6c7d Keys loaded into reader slots 0–2 --- PICC master app --- auth OK formatting card (erases all apps and files) ... free memory : 7936 bytes card version : 04 01 01 01 00 16 05 04 01 01 01 05 03 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 --- Create app AID=AA55AA --- app selected and authenticated --- Data file 0x01 (backup, 32 bytes) --- wrote : b'Ala ma kota' readback: b'Ala ma kota' PASS --- Value file 0x02 (range -100..100, init -5) --- initial value : -5 after credit +10 : 5 after debit -25 : -20 --- Cyclic record file 0x03 (34B records, max 10) --- wrote record 0 ... wrote record 4 read record 0 (offset 0): nr=4, text='This is record nr 4' ... records cleared --- Cleanup --- deleted file 0x01 / 0x02 / 0x03 deleted app AA55AA Done.
Key code patterns
from pepper_c1.commands import KEY_TYPE_DES, KEY_TYPE_AES128 # 1. Load keys reader.set_key(0, KEY_TYPE_DES, bytes(16)) reader.set_key(1, KEY_TYPE_AES128, bytes(16)) # 2. Auth and format reader.mfdf_select_app(b'\x00\x00\x00') reader.mfdf_auth(0, key_slot=0) reader.mfdf_format() # 3. Create app + auth with AES reader.mfdf_create_app(TEST_AID, key_settings=0xED, num_keys=0x84) reader.mfdf_select_app(TEST_AID) reader.mfdf_auth_aes(1, key_slot=0) # 4. Backup data file reader.mfdf_create_data_file(0x01, access_rights=0xEEEE, size=32, backup=True) reader.mfdf_write_data(0x01, offset=0, data=b"Ala ma kota") reader.mfdf_commit_transaction() # 5. Value file reader.mfdf_create_value_file(0x02, 0xEEEE, -100, 100, -5) reader.mfdf_credit(0x02, 10); reader.mfdf_commit_transaction() reader.mfdf_debit(0x02, 25); reader.mfdf_commit_transaction() # 6. Cyclic record file reader.mfdf_create_record_file(0x03, 0xEEEE, record_size=34, max_records=10) reader.mfdf_write_record(0x03, record_bytes) reader.mfdf_commit_transaction() raw = reader.mfdf_read_record(0x03, offset=0, record_size=34)
icode.py Download
Compatible tags: ICODE SLI (0x21), SLI-S (0x22), SLI-L (0x23), SLIX (0x24), SLIX-S (0x25), SLIX-L (0x26), SLIX2 (0x27), DNA (0x28).
Purpose: Block read/write, system information, and block security status.
Usage
python icode.py uart COM3 python icode.py uart COM3 --block 3
Options
| Option | Default | Description |
|---|---|---|
| --block INT | 5 | Block number for the read/write test. |
What it tests
- Read ICODE system information (UID, block size, block count, AFI, DSFID)
- Read block → write random 4 bytes → verify readback → restore original
- Read block security status for blocks 0–9 (lock bits)
Sample Output
Firmware : 1.0.3 Tag type : 0x24 UID : e004030200a1b2c3 --- System information --- 0f e0 04 03 02 00 a1 b2 c3 00 1b 03 00 --- Block 5 --- original : 00000000 written : 7a2b9c1d readback : 7a2b9c1d PASS restored : 00000000 --- Block security status (blocks 0–9) --- block 0: LOCKED block 1: LOCKED block 2: unlocked ... Done.
Key code pattern
info = reader.icode_get_system_information() original = reader.icode_read_block(block) reader.icode_write_block(block, new_data) # Block security status — direct send_command bss = reader.send_command(Command.ICODE_GET_MULTIPLE_BSS, bytes([0, 10]))
example_client.py Download
Compatible tags: All supported families (auto-detected).
Purpose: Full-featured demo that auto-detects the tag type and runs the appropriate test.
Usage
python example_client.py uart COM3 python example_client.py tcp --host 192.168.100.1 --port 1234
Tag dispatch logic
uid_info = reader.get_uid(tag_index) type_byte = uid_info[0] if type_byte in ICODE_TYPES: test_icode(reader) elif type_byte in DESFIRE_TYPES: test_desfire(reader) elif type_byte in MF_CLASSIC_TYPES: test_mifare_classic(reader) elif type_byte in MF_ULTRALIGHT_TYPES: test_mifare_ultralight(reader)
Included test functions
| Function | Tag types | Operations |
|---|---|---|
| test_mifare_classic | 0x03/04/05/10 | set_key, mf_read_block, mf_write_block (read/write/verify/restore) |
| test_mifare_ultralight | 0x01/02 | mfu_read_page, mfu_write_page (read/write/verify/restore) |
| test_desfire | 0x0D | Full DESFire suite: auth, format, data file, value file, record file, cleanup |
| test_icode | 0x21–0x28 | icode_read_block, icode_write_block (read/write/verify/restore) |
TAG_NAMES mapping
The script defines a TAG_NAMES dictionary mapping type bytes to human-readable names, which is a useful pattern to copy into your own applications:
TAG_NAMES = {
0x01: "MIFARE Ultralight",
0x02: "MIFARE Ultralight-C",
0x03: "MIFARE Classic",
0x04: "MIFARE Classic 1K",
0x05: "MIFARE Classic 4K",
0x0D: "MIFARE DESFire",
0x21: "ICODE SLI",
# ...
}
tag_name = TAG_NAMES.get(type_byte, f"Unknown (0x{type_byte:02X})")
mux_example.py Download
Purpose: Continuously cycles through all antennas (1–8), detects tags on each one and prints their type and UID. Useful for multi-antenna setups and antenna multiplexer hardware.
Usage
python mux_example.py uart COM3 python mux_example.py uart /dev/ttyUSB0 --antennas 1 2 3 4 --dwell 50 python mux_example.py tcp --host 192.168.100.1 --port 1234
Options
| Option | Default | Description |
|---|---|---|
| --antennas N [N ...] | 1–8 | Antenna numbers to cycle. Pass a space-separated list to scan a subset, e.g. --antennas 1 2 3 4. |
| --dwell MS | 0 | Milliseconds to wait on each antenna after the tag check before switching to the next one. |
| uart <port> | — | Connect via UART. port is e.g. COM3 or /dev/ttyUSB0. |
| uart --baudrate INT | 115200 | Serial baud rate. |
| tcp --host STR | 192.168.100.1 | TCP host. |
| tcp --port INT | 1234 | TCP port. |
Sample Output
Connected. Firmware: v2.56.2249 Scanning antennas [1, 2, 3, 4, 5, 6, 7, 8] — press Ctrl+C to stop [ant 1] -- [ant 2] MIFARE Classic 1K UID: A1B2C3D4 [ant 3] -- [ant 4] ICODE SLIX UID: E004030200A1B2C3 [ant 5] -- [ant 6] -- [ant 7] MIFARE DESFire UID: 042E3F4A5B6C7D [ant 8] -- [ant 1] -- ...
Key code pattern
while True: for ant in antennas: reader.set_active_antenna(ant) count = reader.get_tag_count() if count: uid_info = reader.get_uid() type_byte = uid_info[0] uid = uid_info[2:].hex().upper() print(f"[ant {ant}] {TAG_NAMES.get(type_byte)} UID: {uid}")
firmware_update.py Download
Purpose: OTA (over-the-air) firmware update for the Peeper C1. Sends a .bin firmware image using the three-phase OTA protocol: OTA_BEGIN → OTA_FRAME × N → OTA_FINISH.
Usage
python firmware_update.py uart COM3 firmware.bin python firmware_update.py uart /dev/ttyUSB0 firmware.bin --chunk-size 1024 python firmware_update.py tcp 192.168.100.1 firmware.bin python firmware_update.py tcp 192.168.100.1 firmware.bin --port 1234 --chunk-size 1024
Options
| Sub-command / Option | Default | Description |
|---|---|---|
| uart <port> | — | Connect via UART. port is e.g. COM3 or /dev/ttyUSB0. |
| uart --baudrate INT | 115200 | Serial baud rate. |
| tcp <host> | — | TCP host (positional), e.g. 192.168.100.1. |
| tcp --port INT | 1234 | TCP port. |
| --chunk-size INT | 1024 | Bytes per OTA_FRAME packet. |
OTA protocol phases
| Command | Payload | ACK timeout | Notes |
|---|---|---|---|
| OTA_BEGIN (0xF0) | empty | up to 5 s | Device erases flash — ACK may arrive after ~2 s. |
| OTA_FRAME (0xF1) | raw chunk bytes | 2 s | Sent once per chunk. Last chunk is sent as-is — no padding required. |
| OTA_FINISH (0xF2) | empty | up to 5 s | Device validates image; ACK confirms success. |
Sample Output
Device firmware : 2.55.2201 File size : 114688 bytes Chunk size: 1024 bytes Frames : 112 OTA_BEGIN ... ACK (1.83 s) [####################] 100% frame 112/112 OTA_FINISH ... ACK (0.12 s) Rebooting ... done Firmware update complete in 14.2 s.
Key code pattern
import time from pepper_c1.commands import Command t_start = time.monotonic() # Phase 1 — announce transfer (long timeout for flash erase) reader._timeout = 5.0 reader.send_command(Command.OTA_BEGIN) # Phase 2 — stream frames (raw chunks, no padding) reader._timeout = 2.0 for i in range(num_frames): chunk = fw_data[i * chunk_size : (i + 1) * chunk_size] reader.send_command(Command.OTA_FRAME, chunk) # Phase 3 — signal end, reboot, print elapsed time reader._timeout = 5.0 reader.send_command(Command.OTA_FINISH) reader.reboot() print(f"Done in {time.monotonic() - t_start:.1f} s")