Pure Python library for the CRT C100-B11 bill acceptor (Creator Technology).
Implements the CCNet (CashCode NET) protocol directly over serial using
pyserial. No proprietary .so or .dll
dependency is required.
- Full CCNet protocol implementation (RESET, POLL, ENABLE, STACK, RETURN, HOLD, IDENTIFICATION, GET_BILL_TABLE)
- High-level
BillAcceptorclass with context manager support - Synchronous polling or background threaded polling with callbacks
- Thread-safe serial I/O
- Bill table parsing with denomination lookup
- Escrow mode (manual accept/reject) and auto-stack mode
- CRC-16 (KERMIT) validation on all frames
- No C library or proprietary SDK needed
| Model | Firmware | Denominations |
|---|---|---|
| CRT C100-B11-UZS | UZS (Uzbek Som) | 1,000 -- 200,000 UZS |
The library should work with other C100-B11 firmware variants (USD, EUR, RUB, CNY, etc.) since the CCNet protocol is the same. Only the bill table contents differ per variant.
- Python 3.7+
- pyserial >= 3.4
- Linux (tested on Ubuntu 20.04+, should work on any POSIX system)
pip install .Or install directly in development mode:
pip install -e .from crt_c100 import BillAcceptor, DeviceState
with BillAcceptor("/dev/ttyUSB0") as ba:
ba.initialize()
while True:
resp = ba.poll()
if resp.state == DeviceState.ESCROW_POSITION:
print("Bill in escrow:", ba.get_denomination_str(resp.bill_type))
ba.stack() # Accept the bill
elif resp.state == DeviceState.BILL_STACKED:
print("Accepted:", ba.get_denomination_str(resp.bill_type))from crt_c100 import BillAcceptor
with BillAcceptor("/dev/ttyUSB0") as ba:
ba.initialize()
ba.auto_accept = True
ba.on_bill_stacked = lambda e: print("Accepted:", e.bill_type)
ba.start_polling()
input("Press Enter to stop...")
ba.stop_polling()from crt_c100 import BillAcceptor
with BillAcceptor("/dev/ttyUSB0") as ba:
info = ba.identify()
print("Model:", info.model)
print("HW:", info.hardware_version)
print("SW:", info.software_version)
table = ba.get_bill_table()
for bill in table.bills:
print(f" Slot {bill.index}: {bill}")The main high-level interface.
BillAcceptor(port, timeout=1.0, poll_interval=0.25)Parameters:
port-- Serial port path (e.g.,/dev/ttyUSB0,/dev/ttyS1)timeout-- Serial read timeout in secondspoll_interval-- Interval between poll commands in seconds
Methods:
| Method | Description |
|---|---|
open() |
Open the serial connection |
close() |
Stop polling and close the connection |
initialize(bill_mask=0xFF, escrow_mode=True, reset_wait=5.0) |
Full init sequence: identify, reset, wait, load bill table, enable |
poll() |
Single synchronous POLL command, returns PollResponse |
stack() |
Accept the bill in escrow |
return_bill() |
Reject the bill in escrow |
hold() |
Extend escrow timeout |
enable(bill_mask=0xFF, escrow_mode=True) |
Enable bill acceptance |
disable() |
Disable bill acceptance |
reset() |
Reset the device |
identify() |
Query device identification |
get_bill_table() |
Read the bill table from the device |
get_denomination(bill_type) |
Look up denomination value by bill type index |
get_denomination_str(bill_type) |
Get formatted denomination string |
start_polling() |
Start background polling thread |
stop_polling() |
Stop background polling thread |
Callbacks:
| Callback | Triggered When |
|---|---|
on_escrow |
Bill enters escrow position |
on_bill_stacked |
Bill accepted and stacked |
on_bill_returned |
Bill returned to customer |
on_bill_rejected |
Bill rejected (with reason code) |
on_state_change |
Any device state transition |
on_error |
Cassette full/removed, jam, or cheat detected |
on_failure |
Hardware failure |
Properties:
| Property | Description |
|---|---|
auto_accept |
Set to True to auto-stack bills in escrow (default False) |
is_connected |
Whether the serial port is open |
bill_table |
Loaded BillTable, or None |
device_info |
Loaded DeviceIdentification, or None |
Enum of all device states reported by POLL:
POWER_UP, POWER_UP_WITH_BILL, INITIALIZE, IDLING, ACCEPTING,
STACKING, RETURNING, UNIT_DISABLED, HOLDING, DEVICE_BUSY, REJECTING,
CASSETTE_FULL, CASSETTE_REMOVED, JAM_IN_ACCEPTOR, JAM_IN_STACKER,
CHEATED, PAUSE, FAILURE, ESCROW_POSITION, BILL_STACKED, BILL_RETURNED
-
Escrow mode (
escrow_mode=True, default): Bills are held in escrow. The host must callstack()to accept orreturn_bill()to reject. Use this when you need to validate the bill amount before accepting. -
Auto-stack (
escrow_mode=False): Valid bills are stacked automatically. The host is notified after the fact viaBILL_STACKEDstate.
The bill_mask parameter controls which denomination slots are enabled.
Each bit corresponds to a slot index (0-7) in the bill table.
# Enable all 8 denominations:
ba.enable(bill_mask=0xFF)
# Enable only slots 0-6 (exclude slot 7):
ba.enable(bill_mask=0x7F)
# Compute mask from bill table (bills >= 10,000 only):
mask = ba.bill_table.get_enable_mask(min_value=10000)
ba.enable(bill_mask=mask)The CRT C100-B11 uses the following serial parameters:
| Parameter | Value |
|---|---|
| Baud rate | 9600 |
| Data bits | 8 |
| Parity | None |
| Stop bits | 1 |
On Linux, the device typically appears as /dev/ttyUSB0 (USB-to-serial
adapter) or /dev/ttyS1 (onboard RS-232). The serial port requires
root access or membership in the dialout group:
# Run as root:
sudo python my_script.py
# Or add user to dialout group (permanent):
sudo usermod -aG dialout $USER
# Log out and back in for the group change to take effect.The library implements the CCNet (CashCode NET) protocol as used by Creator Technology's C100-B11 bill acceptors. Key protocol details:
Frame format:
[SYNC=0x02] [ADR=0x03] [LNG] [CMD/DATA...] [CRC_L] [CRC_H]
SYNC: Always 0x02ADR: Device address, always 0x03 for bill acceptorsLNG: Total frame length (including SYNC, ADR, LNG, and CRC bytes)CRC: CRC-16/KERMIT (polynomial 0x08408), low byte first
Two-stage read: The receiver first reads 3 bytes (SYNC + ADR + LNG), then
reads LNG - 3 remaining bytes. This matches the behavior of the
manufacturer's C library.
See the examples/ directory:
basic_poll.py-- Synchronous polling loopescrow_mode.py-- Manual accept/reject per billauto_accept.py-- Background thread with callbacksdevice_info.py-- Query device info and bill table
pip install -e ".[dev]"
python -m pytest tests/ -vcrt_c100/
__init__.py Public API exports
crc.py CRC-16/KERMIT implementation
constants.py Protocol constants, status codes, timing
exceptions.py Exception hierarchy
types.py Data types (DeviceState, PollResponse, BillTable, ...)
transport.py Serial I/O, frame build/parse, CRC validation
protocol.py CCNet command layer
acceptor.py High-level BillAcceptor API
BillAcceptor (high-level, threads, callbacks)
|
CCNetProtocol (commands: poll, reset, enable, stack, ...)
|
CCNetTransport (serial I/O, frame build/parse, CRC)
|
pyserial (raw serial port access)
MIT License. See LICENSE for details.