What this example does

This guide uses the python-can package to:

  • open a SocketCAN interface
  • send one standard CAN frame
  • wait for a response
  • filter received frames
  • run without hardware on vcan0

The code works on Linux with SocketCAN. Python-can also supports other interfaces, but their channel settings and drivers differ.

Install python-can

Terminal
python -m pip install python-can

For a physical interface at 500 kbit/s:

Terminal
sudo ip link set can0 down
sudo ip link set can0 type can bitrate 500000
sudo ip link set can0 up

Create a virtual CAN bus for testing

Use vcan0 when you want to test code without sending anything to a real network.

Terminal
sudo modprobe vcan
sudo ip link add dev vcan0 type vcan
sudo ip link set vcan0 up

Open a monitor in another terminal:

Terminal
candump vcan0

Small send example

Python
import can

message = can.Message(
    arbitration_id=0x123,
    data=[0x40, 0x1F, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00],
    is_extended_id=False,
)

with can.Bus(interface="socketcan", channel="vcan0") as bus:
    try:
        bus.send(message, timeout=1.0)
        print(f"Sent {message}")
    except can.CanError as error:
        print(f"Send failed: {error}")

For a physical bus, replace vcan0 with can0 after the interface is configured.

Small receive example

Python
import can

with can.Bus(interface="socketcan", channel="vcan0") as bus:
    message = bus.recv(timeout=5.0)

    if message is None:
        print("No CAN frame received in 5 seconds")
    else:
        print(f"ID: 0x{message.arbitration_id:X}")
        print(f"Data: {message.data.hex(' ')}")
        print(f"Time: {message.timestamp:.6f}")

Always use a timeout unless the application is designed to block forever.

Complete send and receive program

This example sends a request on ID 0x700 and waits for a response on ID 0x708.

Python
import can

REQUEST_ID = 0x700
RESPONSE_ID = 0x708

filters = [
    {"can_id": RESPONSE_ID, "can_mask": 0x7FF, "extended": False},
]

request = can.Message(
    arbitration_id=REQUEST_ID,
    data=[0x01, 0x02, 0x03, 0x04],
    is_extended_id=False,
)

with can.Bus(
    interface="socketcan",
    channel="can0",
    can_filters=filters,
) as bus:
    try:
        bus.send(request, timeout=1.0)
    except can.CanError as error:
        raise SystemExit(f"Could not send request: {error}")

    response = bus.recv(timeout=2.0)
    if response is None:
        raise SystemExit("Response timeout")

    print(f"RX 0x{response.arbitration_id:X}: {response.data.hex(' ')}")

The IDs and payload are examples. Replace them with values from your network specification.

Test two Python programs on vcan0

Run the receive script first. Then run the send script. You should see the same frame in the receiver and in candump.

If nothing appears:

Terminal
ip -details link show vcan0

Confirm that the interface exists and is up.

Send an extended 29-bit frame

Set is_extended_id=True and use an ID no larger than 0x1FFFFFFF.

Python
message = can.Message(
    arbitration_id=0x18FEEE01,
    data=[0xFF, 0xFF, 0x40, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF],
    is_extended_id=True,
)

J1939 uses extended identifiers, but a J1939 application also needs higher-level rules for PGNs, addresses, requests, and multi-packet messages.

Send a message periodically

Python
import time
import can

message = can.Message(
    arbitration_id=0x123,
    data=[0x00] * 8,
    is_extended_id=False,
)

with can.Bus(interface="socketcan", channel="vcan0") as bus:
    task = bus.send_periodic(message, period=0.100)
    try:
        time.sleep(5)
    finally:
        task.stop()

This sends the frame every 100 milliseconds for five seconds.

Decode with a DBC file

Python-can moves frames. Cantools can decode their payloads.

Terminal
python -m pip install cantools
Python
import cantools

database = cantools.database.load_file("vehicle.dbc")
values = database.decode_message(message.arbitration_id, message.data)
print(values)

Use the DBC version that matches the ECU software and vehicle variant.

Common Python CAN mistakes

  • Opening can0 before the Linux interface is up
  • Using the wrong bitrate on a physical bus
  • Setting the wrong is_extended_id value
  • Waiting forever without a receive timeout
  • Forgetting to close the bus
  • Sending to a live network before validating IDs and payloads
  • Catching every exception and hiding useful errors

The simple summary

With python-can, create a bus, build a Message, call send(), and use recv() with a timeout. Start on vcan0, add filters, and move to physical hardware only after the software path works.

References