Metadata-Version: 2.1
Name: pepper_c1
Version: 0.1.8
Summary: Python driver for the Eccel Peeper C1 RFID reader (UART / TCP)
Author-email: Eccel Technology Ltd <marcin.baliniak@eccel.co.uk>
License: MIT
Project-URL: Homepage, https://www.eccel.co.uk
Project-URL: Product, https://eccel.co.uk/product/pepper-c1-usb/
Project-URL: Documentation, https://eccel.co.uk/wp-content/downloads/Pepper_C1/C1-python/
Keywords: rfid,nfc,mifare,desfire,icode,eccel,peeper,iso14443,iso15693
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Operating System :: OS Independent
Classifier: Topic :: System :: Hardware :: Hardware Drivers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Intended Audience :: Developers
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: pyserial>=3.5
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pytest-mock; extra == "dev"

# pepper_c1

Python driver for the **[Eccel Pepper C1](https://eccel.co.uk/product/pepper-c1-usb/)** RFID reader
made by [Eccel Technology Ltd](https://www.eccel.co.uk).

Supports communication over **UART** and **TCP**, and covers MIFARE Classic,
MIFARE Ultralight, MIFARE DESFire, and ICODE tag families.

## Compatibility

| Module | UART | TCP | MIFARE Classic | MIFARE Ultralight | MIFARE DESFire | ICODE |
|--------|------|-----|----------------|-------------------|----------------|-------|
| Pepper C1 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Pepper C2 | ✓ | — | ✓ | ✓ | — | ✓ |

> **Pepper C2** modules communicate over UART only and do not support the MIFARE DESFire command set (`mfdf_*` methods). All other commands work identically to the C1.

## Installation

```bash
pip install pepper_c1
```

## Quick start

```python
from pepper_c1 import PepperC1, UARTTransport

with PepperC1(UARTTransport('/dev/ttyUSB0', baudrate=115200)) as reader:
    print(reader.get_version())
    count = reader.get_tag_count()
    if count:
        uid_info = reader.get_uid()  # returns [type, param, uid_bytes...]
        print(f"Tag UID: {uid_info[2:].hex()}")
```

## MIFARE Classic example

```python
from pepper_c1 import PepperC1, UARTTransport
from pepper_c1.commands import KEY_TYPE_MIFARE

with PepperC1(UARTTransport('/dev/ttyUSB0')) as reader:
    reader.get_tag_count()
    reader.activate_tag()
    reader.set_key(0, KEY_TYPE_MIFARE, bytes([0xFF] * 12))  # key A + key B

    original = reader.mf_read_block(5)
    print(f"Block 5: {original.hex()}")

    reader.mf_write_block(5, b'\x01' * 16)
    print(f"Readback: {reader.mf_read_block(5).hex()}")

    reader.mf_write_block(5, original)  # restore
```

## MIFARE Ultralight example

```python
from pepper_c1 import PepperC1, UARTTransport

with PepperC1(UARTTransport('/dev/ttyUSB0')) as reader:
    reader.get_tag_count()
    reader.activate_tag()

    original = reader.mfu_read_page(4)
    print(f"Page 4: {original.hex()}")

    reader.mfu_write_page(4, b'\x01\x02\x03\x04')
    print(f"Readback: {reader.mfu_read_page(4).hex()}")

    reader.mfu_write_page(4, original)  # restore
```

## MIFARE DESFire example

```python
from pepper_c1 import PepperC1, UARTTransport
from pepper_c1.commands import KEY_TYPE_AES128

with PepperC1(UARTTransport('/dev/ttyUSB0')) as reader:
    # DESFire does not need activate_tag
    reader.get_tag_count()
    reader.mfdf_select_app(bytes([0x00, 0x00, 0x00]))  # select PICC master app
    reader.set_key(0, KEY_TYPE_AES128, bytes(16))       # all-zero = factory default
    reader.mfdf_auth_aes(key_no=0, key_slot=0)

    app_ids = reader.mfdf_get_app_ids()
    for aid in app_ids:
        print(f"AID: {aid.hex()}")
```

## ICODE example

```python
from pepper_c1 import PepperC1, UARTTransport

with PepperC1(UARTTransport('/dev/ttyUSB0')) as reader:
    # ICODE (ISO 15693) does not need activate_tag
    reader.get_tag_count()
    reader.get_uid()

    original = reader.icode_read_block(5)
    print(f"Block 5: {original.hex()}")

    reader.icode_write_block(5, b'\x01\x02\x03\x04')
    print(f"Readback: {reader.icode_read_block(5).hex()}")

    reader.icode_write_block(5, original)  # restore
```

## Antenna multiplexer example

For readers with multiple antennas, use `set_active_antenna()` to switch between them (1–8):

```python
import time
from pepper_c1 import PepperC1, UARTTransport

with PepperC1(UARTTransport('/dev/ttyUSB0')) as reader:
    while True:
        for antenna in range(1, 9):
            reader.set_active_antenna(antenna)
            count = reader.get_tag_count()
            if count:
                uid_info = reader.get_uid()
                tag_type = uid_info[0]
                uid = uid_info[2:].hex().upper()
                print(f"Antenna {antenna}: type=0x{tag_type:02X}  UID={uid}")
            else:
                print(f"Antenna {antenna}: --")
```

See [`tools/mux_example.py`](tools/mux_example.py) for the full CLI script with transport selection and configurable dwell time.

## Firmware update (OTA)

```python
import math
from pepper_c1 import PepperC1, TCPTransport
from pepper_c1.commands import Command

fw_data    = open("Pepper_C1_2.56.2249.bin", "rb").read()
chunk_size = 1024
num_frames = math.ceil(len(fw_data) / chunk_size)

with PepperC1(TCPTransport('192.168.100.1', 1234), response_timeout=2.0) as reader:
    reader._timeout = 5.0
    reader.send_command(Command.OTA_BEGIN)

    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)
        print(f"\r  {(i + 1) * 100 // num_frames}%  frame {i + 1}/{num_frames}", end="")

    print()
    reader._timeout = 5.0
    reader.send_command(Command.OTA_FINISH)
    reader.reboot()
```

See [`tools/firmware_update.py`](tools/firmware_update.py) for the full script with progress bar and CLI options.

## TCP transport

```python
from pepper_c1 import PepperC1, TCPTransport

# Connect via TCP before opening UART — closing UART may reset the device
with PepperC1(TCPTransport('192.168.100.1', 1234)) as reader:
    print(reader.get_version())
    count = reader.get_tag_count()
    if count:
        uid_info = reader.get_uid()
        print(f"Tag UID: {uid_info[2:].hex()}")
```

## Error handling

```python
from pepper_c1 import PepperC1, UARTTransport, CommandError, TransportError

try:
    with PepperC1(UARTTransport('/dev/ttyUSB0')) as reader:
        reader.get_tag_count()
        reader.get_uid()
except TransportError as e:
    print(f"Connection failed: {e}")
except CommandError as e:
    print(f"Device error: {e}")
```

## Protocol

The Pepper C1 uses a binary framing protocol over serial or TCP:

```
[0xF5] [LEN_L] [LEN_H] [LEN_L^0xFF] [LEN_H^0xFF] [PAYLOAD...] [CRC_L] [CRC_H]
```

CRC is CCITT-16 computed over the payload only.
Low-level access is available via `PepperC1.send_command(cmd, data)`.
