CAN-ISO-TP
can-isotp is a Python module that implements the ISO 15765-2 (ISO-TP) protocol on top of python-can. It enables communication over CAN bus for sending and receiving messages larger than 8 bytes, handling segmentation and flow control. The current version is 2.0.7, with frequent bugfix releases and major updates introducing significant API changes.
Common errors
-
AttributeError: 'TransportLayer' object has no attribute 'process'
cause Attempting to call the `process()` method on an `isotp.protocol.TransportLayer` instance after upgrading to `can-isotp` v2.0 or newer.fixRemove calls to `process()`. As of v2.0, `TransportLayer` manages its own timing internally on a dedicated thread. If using `isotp.stack.CanStack`, this is handled automatically. -
TypeError: __init__() got an unexpected keyword argument 'squash_stmin_requirement'
cause Using the deprecated `squash_stmin_requirement` parameter in the constructor of `isotp.stack.CanStack` or `isotp.protocol.TransportLayer` after upgrading to `can-isotp` v2.0 or newer.fixReplace `squash_stmin_requirement` with `override_receiver_stmin`. -
ISO-TP single-frame messages fail to send or are dropped when blocking_send=True
cause A bug in `can-isotp` versions prior to 2.0.6 prevented correct sending of single-frame messages when `blocking_send` was set to `True`.fixUpgrade `can-isotp` to version 2.0.6 or newer to resolve this sending issue. -
CAN communication becomes unresponsive or freezes when many error frames are present
cause In `can-isotp` versions prior to 2.0.7, the `NotifierBasedCanStack` could become unresponsive under high error frame rates due to an internal blocking condition.fixUpgrade `can-isotp` to version 2.0.7 or newer to fix the unresponsiveness issue.
Warnings
- breaking Version 2.0 introduced significant breaking changes. The `TransportLayer.process()` method was removed as the layer now manages its own timing on an internal thread. Additionally, the `rxfn` callback now expects a blocking function with a timeout, and `squash_stmin_requirement` was renamed to `override_receiver_stmin`.
- gotcha Sending single-frame messages (payloads fitting into a single CAN frame) may fail or behave incorrectly when `blocking_send=True` in versions prior to 2.0.6.
- gotcha The `NotifierBasedCanStack` could become unresponsive when a large number of CAN error frames were received, leading to communication stalls.
- gotcha Compatibility issues with Python 3.7 to 3.9 were present in early 2.x releases (e.g., 2.0.0).
Install
-
pip install can-isotp
Imports
- CanStack
import isotp stack = isotp.stack.CanStack(...)
- Address
import isotp addr = isotp.Address(...)
- TargetAddressType
import isotp addr = isotp.Address(isotp.TargetAddressType.N_PHYSICAL, ...)
- TransportLayer
from isotp.protocol import TransportLayer
Quickstart
import can
import isotp
import time
import os
# Configure CAN bus (uses 'virtual' bus by default for easy testing)
# For a real setup, replace 'virtual' with your bus type (e.g., 'socketcan', 'peak')
# and 'vcan0' with your channel. For Linux virtual CAN, ensure 'vcan0' is up:
# Example: sudo modprobe vcan; sudo ip link add dev vcan0 type vcan; sudo ip link set up vcan0
bus_type = os.environ.get('CAN_BUSTYPE', 'virtual')
can_channel = os.environ.get('CAN_CHANNEL', 'vcan0')
# Attempt to open the specified CAN bus. Fallback to a mock bus for script execution.
try:
bus = can.Bus(channel=can_channel, bustype=bus_type)
print(f"CAN bus opened successfully: {can_channel} ({bus_type})")
except Exception as e:
print(f"Could not open CAN bus '{can_channel}' with type '{bus_type}'. Error: {e}")
print("Falling back to a mock virtual bus for demonstration purposes.")
bus = can.interface.Bus(bustype='virtual', channel='vcan0', is_extended_id=False)
# Define ISO-TP addressing parameters for a physical communication
# rxid: The CAN ID this ISO-TP stack expects to receive (from ECU).
# txid: The CAN ID this ISO-TP stack will send (to ECU).
addr = isotp.Address(
isotp.TargetAddressType.N_PHYSICAL,
rxid=0x7E8,
txid=0x7E0
)
# Create an ISO-TP stack using the high-level CanStack interface
stack = isotp.stack.CanStack(bus=bus, address=addr)
# Send an ISO-TP message (e.g., a diagnostic service request)
payload_to_send = b'\x22\xF1\x90\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A'
print(f"Sending ISO-TP message ({len(payload_to_send)} bytes): {payload_to_send.hex()}")
stack.send(payload_to_send, timeout=0.5) # Add a timeout for sending
# Wait a short moment for bus activity / reply
time.sleep(0.1)
# Attempt to receive an ISO-TP message (e.g., an ECU response)
print("Attempting to receive ISO-TP message (timeout=1.0s)...")
received_data = stack.recv(timeout=1.0) # Blocking receive with a timeout
if received_data is not None:
print(f"Received ISO-TP message ({len(received_data)} bytes): {received_data.hex()}")
else:
print("No ISO-TP message received within the timeout.")
# Clean up the CAN bus
bus.shutdown()
print("CAN bus shut down.")