Scripting

comma.ai panda

The comma.ai panda has a Python API that makes it easy to script CAN interactions. It has support for sending and receiving CAN messages, but also implements high-level protocols such as ISO-TP, UDS, and CCP.

Sending and receiving messages can be done in the following way:

from panda import Panda
from opendbc.car.structs import CarParams

panda = Panda()

# Disable silent mode
panda.set_safety_mode(CarParams.SafetyModel.allOutput)

# Send message to Arb ID 0x1aa on bus 0
panda.can_send(0x1aa, b"\xaa\xbb\xcc\xdd", 0)

# Receive messages
for arb_id, _, data, bus in panda.can_recv():
    print(f"Arb ID {arb_id}, bus {bus}: {data.hex()}")

ISO-TP

The example below requests the Manufacturer Part Number using a hand-crafted Read Data By Identifier UDS request. In the next section, we'll show how to do this in a more abstracted way using the UDS client.

from opendbc.car.isotp import isotp_send, isotp_recv

# Request Manufacturer Spare Part Number
isotp_send(panda, b"\x22\xf1\x87", 0x7b7)
resp = isotp_recv(panda, 0x7bf)

UDS

The example below requests the Manufacturer Spare Part Number using UDS.

from opendbc.car.uds import UdsClient
from opendbc.car.uds import NegativeResponseError
from opendbc.car.uds import DATA_IDENTIFIER_TYPE

client = UdsClient(panda, 0x7b7)
try:
    resp = client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_SPARE_PART_NUMBER)
except NegativeResponseError as e:
    print(f"Negative Response: {e}")

SocketCAN can-utils

SocketCAN has some versatile CLI tools available in the can-utils package. It's also possible to use Python to talk to a SocketCAN device — see python-can below. Before we can use any of the tools we need to bring up the CAN interface:

sudo ip link set can0 type can bitrate 500000 dbitrate 2000000 fd on sample-point 0.8
sudo ip link set can0 up

After the interface is brought up, we can send a message using cansend:

cansend can0 1aa#aabbccdd

ISO-TP

can-utils contains some nice tools to work with ISO-TP. You can sniff ISO-TP messages using isotpsniffer, and send ISO-TP messages using isotpsend.

Example of sniffing ISO-TP:

$ isotpsniffer -s 7b7 -d 7bf can0
 can0  7B7  [3]  22 F1 87  - '"..'
 can0  7BF  [18]  62 F1 87 39 39 31 34 30 4C 30 30 30 30 20 20 20 20 20 \
          - 'b..99140L0000     '

Using isotpsend and isotprecv it's possible to send and receive UDS messages manually, but there are no dedicated UDS tools.

We can send the same UDS request that we sent from the panda example. Keep in mind that the ISO-TP CLI tools do not pad the CAN frame to 8 bytes by default, which is required by some implementations. To fix this, we specify a padding byte using the -p option.

In one shell, we start isotprecv to catch the reply after we send the request and handle the flow control:

isotprecv -s 7b7 -d 7bf -p AA can0

In another shell we send the request:

echo 22 F1 87 | isotpsend -s 7b7 -d 7bf -p AA can0

The reply is then shown in the first shell:

$ isotprecv -s 7b7 -d 7bf -p AA can0
  62 F1 87 39 39 31 34 30 4C 30 30 30 30 20 20 20 20 20

python-can

As mentioned previously, python-can can be used with a variety of adapters, but in these examples we will be using the SocketCAN interface on Linux. After installing the package, we can send and receive messages like this:

import can

bus = can.Bus(channel='can0', interface='socketcan')
msg = can.Message(arbitration_id=0x1aa, data=b"\xaa\xbb\xcc\xdd", is_extended_id=False)
bus.send(msg)

for msg in bus:
    print(f"Arb ID {msg.arbitration_id}: {msg.data.hex()}")

ISO-TP

python-can has no built-in support for ISO-TP, so we need to install the can-isotp package first. Then we can send the same Read Data By Identifier request that we sent in previous examples. can-isotp has an asynchronous API, so we need to keep polling the stack until the response is available.

import isotp

addr = isotp.Address(txid=0x7b7, rxid=0x7bf)
params = {"tx_padding": 0xaa}
stack = isotp.CanStack(bus, address=addr, params=params)
stack.send(b"\x22\xf1\x87")

while not stack.available():
   stack.process()

resp = stack.recv()

Scapy

Scapy is a Python library for crafting and sending packets. It also has support for CAN, ISO-TP, UDS, and more automotive protocols. It can be a very powerful tool since it supports a large variety of protocols, and it's easy to use the same protocol over different communication networks. For example, it's possible to use the same UDS code to talk over CAN or Ethernet. The downside is that the code is sometimes a bit verbose.

After installing scapy, we can send our first packets:

from scapy.all import load_layer, load_contrib

load_layer("can")
load_contrib('cansocket')

socket = CANSocket(channel='can0')

packet = CAN(identifier=0x1aa, data=b'\xaa\xbb\xcc\xdd')
socket.send(packet)

msg = socket.recv()
print(f"Arb ID {msg.identifier}: {msg.data.hex()}")

ISO-TP

By creating an ISOTPSoftSocket we can send ISO-TP messages over our existing SocketCAN socket:

load_contrib('isotp')

with ISOTPSoftSocket(socket, tx_id=0x7b7, rx_id=0x7bf, padding=True) as isotp_socket:
    resp = isotp_socket.sr1(ISOTP(b"\x22\xf1\x87"))
    print(resp.data)

UDS

By loading the automotive.uds layer we can send UDS messages over an ISO-TP socket. The UDS layer has support for many common UDS services, and can also be extended with custom services. We use UDS() / UDS_RDBI(identifiers=[0xf187]) to craft the request.

load_contrib('automotive.uds')
with ISOTPSoftSocket(socket, tx_id=0x7b7, rx_id=0x7bf, padding=True, basecls=UDS) as isotp_socket:
    pkt = UDS() / UDS_RDBI(identifiers=[0xf187])
    resp = isotp_socket.sr1(pkt)
    print(resp.load)