A python-can backend that connects to a cannelloni TCP server (client side).
It speaks exactly the same minimal stream as the server codec:
[ CANID u32 (big-endian, with EFF/RTR/ERR bits) ][ LEN u8 (0..8) ][ DATA LEN bytes ]
- No outer packet header, no extra flag bytes
- Classic CAN only (DLC ≤ 8; CAN‑FD is intentionally rejected on TX)
- On connect both peers send the banner
CANNELLONIv1(without\0) - TCP socket uses TCP_NODELAY and keepalive by default
- Background RX loop with optional auto‑reconnect
python-can-cannelloni/
├─ pyproject.toml
├─ README.md
└─ can_cannelloni/
├─ __init__.py # exposes CannelloniBus
├─ bus.py # python-can BusABC implementation + RX loop
└─ protocol.py # encode/decode for the wire format + handshake
(Optional tests:)
tests/
└─ test_bus.py
- Python 3.10+
python-can >= 4.3
Install in editable mode to register the plugin entry point:
pip install -e .pyproject.toml:
[project.entry-points."can.interface"]
cannelloni = "can_cannelloni.bus:CannelloniBus"import can
# Option A: pass "host:port" in channel
bus = can.Bus(interface="cannelloni", channel="172.31.11.43:20000")
# Option B: use host/port kwargs
# bus = can.Bus(interface="cannelloni", host="172.31.11.43", port=20000)
# Send a classic CAN frame (extended-id example)
bus.send(
can.Message(
arbitration_id=0x6F4A,
data=b"\xFE\x21\x78\x28",
is_extended_id=True,
)
)
# Receive (1.0 s timeout)
msg = bus.recv(1.0)
print(msg)
bus.shutdown()You can use python-can utilities like Notifier, Logger, BufferedReader, etc.
All options go to can.Bus(interface="cannelloni", ...):
| Option | Type | Default | Description |
|---|---|---|---|
channel |
str | – | "host:port" (alt. to host/port) |
host |
str | – | Server IP/hostname |
port |
int | – | Server TCP port |
nodelay |
bool | True |
Set TCP_NODELAY |
keepalive |
bool | True |
Enable TCP keepalive |
handshake_timeout |
float | 2.0 |
Timeout awaiting banner |
reconnect |
bool | True |
Auto‑reconnect on drop |
reconnect_interval |
float | 1.0 |
Seconds between attempts |
rx_queue_size |
int | 10000 |
Bounded RX queue size |
# Only receive 0x100 (standard 11-bit)
bus.set_filters([{"can_id": 0x100, "can_mask": 0x7FF, "extended": False}])Filtering is applied locally in the client for maximum compatibility across
python-canversions.
- Handshake: both ends immediately send
CANNELLONIv1(no terminator). - Frame on the TCP stream:
CANIDu32 big‑endian with SocketCAN‑style bits:0x80000000→ EFF (extended id)0x40000000→ RTR (remote)0x20000000→ ERR (error)
LENu8; valid range0..8(top bit, if set on the wire, is ignored; parser masks with& 0x7F)DATALEN bytes
- No CAN‑FD: TX with
is_fd=Trueis rejected; RX withLEN>8is considered invalid and the parser attempts a 1‑byte resync.
- Weird DLC (e.g., 254) or garbage IDs
This indicates a header mismatch. Ensure the layout is exactly!IB(big‑endianu32 CANID+u8 LEN) and there are no extra bytes in the header. - Handshake errors
The server must expect/emitCANNELLONIv1(no\0); transparent TCP path is required (no TLS/HTTP proxies). - No frames
Verify reachability (telnet host port), server logs, and that frames are classic CAN (DLC ≤ 8).
Minimal pytest to validate sending, receiving, and client‑side filtering.
Run:
pytest -qPRs and issues welcome. Please include tests for protocol changes and keep the wire format exactly as documented (or make it configurable).
MIT
# SPDX-License-Identifier: MIT
# Copyright (c) 2025 Klaudiusz Staniek