Getting Started With CAN In Python

Introduction

What Is Python?

Python is a popular programming language that allows rapid development of powerful programs and is relatively quick and easy to learn. Whether you are completely new to writing code, or already know some other programming languages, the simple and consistent syntax of Python makes it easy to read and understand. Python is a high-level, general-purpose language that has a large standard library. That means that you don't need to spend a lot of time writing basic functions for common tasks, rather you can focus on the application of your code. Thanks to the popularity of the language, there are also many third-party extensions that can further simplify your code.

In this article, we will look at some code examples that use Python to send and receive CAN messages. Once you understand these fundamentals, it is possible to build complex scripts that can interact with CAN systems in a fairly intricate manner.

Why Use Python?

Most CAN interfaces come with some free software that allows you to send and receive CAN messages, but there are limitations to these that make it difficult to truly interact with a network. For example, if you need to read an incoming message, make some decision based on its data, and then transmit a message in response within a few hundred milliseconds, that could be difficult to accomplish using the basic send/receive tools that come with the freeware.

The reason I turned to Python for a CAN application was a requirement for a message counter and CRC (cyclic redundancy check) to be sent with each message. I was able to use existing libraries to write a Python script to send CAN messages with an incrementing counter and CRC in far less time than developing from scratch in C. After using this code to test a proof-of-concept and work out any bugs, I can confidently start spending the time developing an embedded C application. If the proof-of-concept is unsuccessful, I only spent hours and not days writing the code.

Supported CAN Interfaces

I have only used the Kvaser U100 CAN interface with Python so far and this article will focus on using a Kvaser CAN adapter. If you have any Kvaser CAN interface and already installed the drivers and CAN King, then you are ready to start setting up the programming environment. The following examples assume you have a Kvaser CAN interface successfully working with CAN King. If you have access to a second CAN interface, it could be helpful to use it as a way to confirm you are sending messages correctly. While it's not required, it would be helpful in building your confidence so if you have access to one, go ahead and grab it. Make a small 2-node network between the Kvaser CAN adapter and your other CAN interface so you can send messages back and forth to test your code. Don't forget the terminating resistors!

Setting Up The Programming Environment

Before we can start writing and running Python scripts, we need to get a programming environment set up on your computer. This article will not detail how to complete each of these steps, but I'll add some links and descriptions to help you get up and running.

  1. Install Python
  2. Install pip
  3. Install Kvaser CANlib SDK
  4. Install VS Code

Installing Python

Python can be installed on Windows, Linux, or macOS. Assuming you're using a fairly new version of Windows (8 or later), installation is fairly easy. Just download the installer from the Python downloads page and run. After installing, open a Windows command prompt window and run the following command.

py -V

You should get a response something like Python 3.10.X if the installation was successful. Make sure you have at least version 3 as version 2 sometimes comes pre-installed on operating systems, but is no longer supported. Visit the Python beginners guide for more information on installation.

Installing pip

If you don't already have a Windows command prompt window open, go ahead and open one. Run the following command.

py -m ensurepip --upgrade

You should see a success message or an indication that the requirement is already met when pip installation is complete.

Go here for more information on installing pip.

Installing The Kvaser CANlib SDK

Download the Kvaser CANlib SDK installer and run the installer. Check out the Kvaser Canlib SDK page or the Kvaser Canlib Manual for more information.

If you don't already have a Windows command prompt window open, go ahead and open one. Run the following command

py -m pip install canlib

Installing VS Code

VS Code is a code editor that will allow you to run Python scripts right from the code editor interface. It also has a console window so that you can interact with your programs if necessary. There are plenty of other editors out there that will do this, so feel free to use one you're more familiar/comfortable with as long as it supports Python.

Go to code.visualstudio.com and download the installer. Run the installer.

Writing A CAN Application

Our first application is going to be as simple as it gets. We are going to first verify the Kvaser is available and get its channel number. This is an important step as your application won't work if you don't set up the correct CAN channel. It will also introduce you to some basics of the canlib syntax and give you a building block you can use later in your own applications. For example, you can check for the Kvaser and automatically set the CAN channel or send an alert to the console if the Kvaser is not detected.

We will be creating these scripts in VS Code, so if it's not already running, go ahead and open a VS Code window. Create a new file by pressing Ctrl + N. A new blank tab should open with some options at the top. You should see "Select a Language" as an option, go ahead and click "Select a Language" or if you do not see that, press and release Ctrl + K and then press M. Either of these actions should make a drop down menu appear from the top center of the VS Code window. Go ahead and type "Python" and you'll see that as an option. Click "Python" to set the language. Press Ctrl + S to save the file. Give it a name like "List Channels.py" and click Save. We are now ready to start coding.

List CAN Channels

Let's start by importing canlib.


from canlib import canlib

Now we can use all the functionality of the canlib library. Next, we will get the total number of CAN channels and save it to a variable.


# Get the total number of available CAN channels
numberChannels = canlib.getNumberOfChannels()

Now we'll create a console message so that when the script is executed, you can see how many CAN channels are available.


# Print a message with the total number of available CAN channels
print("Found %d channels" % numberChannels)

At this time, we can run our script and see the console output. Look for a small triangle in the upper right hand corner of the VS Code window. If you hover on the triangle, it should display "Run Python File". Go ahead and click on the triangle. You should see a terminal window appear at the bottom of the VS Code window. The message "Found X channels" will tell you how many CAN channels were found. Typically this number is 3 (if you Kvaser is plugged in); one real channel (the Kvaser CAN interface) and 2 virtual channels.

Next, we'll get the channel number for your Kvaser so we can talk to it from our script. Like the number of channels, we'll print this information to the console so you can make a note which channel is the Kvaser. Since there are multiple channels, we'll create a loop to get the information from each channel and then print to the console.


# Loop through all of the channels and print the channel information
for channel in range(0, numberChannels):
    # Get the channel data
    channelData = canlib.ChannelData(channel)

    # Print to console
    print(channel, channelData.channel_name)

Go ahead and click Run again to run your script. You should see a console message with the number of channels and the information for each one.


Found 3 channels
0 Kvaser U100 (channel 0)
1 Kvaser Virtual CAN Driver (channel 0)
2 Kvaser Virtual CAN Driver (channel 1)

So in this example, we now know that the physical Kvaser is channel 0. Make a note of this to use in future examples.

Remember to save your script for future use. All you need to do is open in VS Code and click Run to get the channel listing.

Here's the code in its entirety.


from canlib import canlib

# Get the total number of available CAN channels
numberChannels = canlib.getNumberOfChannels()

# Print a message with the total number of available CAN channels
print("Found %d channels" % numberChannels)

# Loop through all of the channels and print the channel information
for channel in range(0, numberChannels):
    # Get the channel data
    channelData = canlib.ChannelData(channel)

    # Print to console
    print(channel, channelData.channel_name)

This script is also available for you to download here. After the file downloads, you will need to drag it into a VS Code window to view/edit. If you just double-click on the .py file, the file will just execute and you won't see anything.

Read CAN Messages

Now that we know which CAN channel to use, we can create a Python script to open the channel, read in CAN messages, and print them to the console. You will need to connect your Kvaser to a CAN bus to test this script, so if you have a CAN traffic simulator, or an actual network, make sure you can connect the Kvaser to it while this script is running.

Open VS Code and create a new Python file by pressing Ctrl + N. Press and release Ctrl + K and then press M. Type Python and press Enter. Press Ctrl + S to save the file as "CAN RX.py".

We'll start by importing the libraries we'll need.


from canlib import canlib
from canlib import Frame

Next we'll create a CAN channel variable can1 and use it to open CAN channel 0, which we know is our Kvaser from the previous step where we listed the CAN channels.


# Create a new CAN channel can1
can1 = canlib.openChannel(channel=0)

Now we will set the baud rate. In this example, I'm using 500k, but you can replace canBITRATE_500K with canBITRATE_250K if your CAN bus is operating at a 250k baud rate.


# Set the baud rate: canBITRATE_250K or canBITRATE_500k
can1.setBusParams(canlib.canBITRATE_500K)

The final step to getting our CAN channel set up is to simply enable the channel.


# Enable the new can1 channel
can1.busOn()

Now that we have a CAN channel set up, we need to start a loop that will continuously check for new CAN messages, read them into a variable, and then print them to the console. We'll do this with a forever loop. The loop will run as long as the script is running. Terminating the script will break the loop and end the program execution. We'll use a while loop for this.


while True:

The first action in our forever loop will be to read the received CAN message into a variable called frame.


while True:
    # Read the next incoming CAN frame, function will timeout if no new message within 10 seconds
    frame = can1.read(timeout=10000)

I decided to use a very long timeout value for this example in case message frequency is low or you are manually sending messages, but you can adjust this timeout to suit your application. A good rule of thumb for a timeout value is 3X the expected message rate. For example, if you expect a new message every 200 ms, then your timeout could be ~600 ms.

Finally, we'll print the CAN frame to the console and then return to the beginning of the forever loop to do it all over again.

    
# Print the CAN frame
print(frame)

Now it's time to run your script. Go ahead and click Run. You should see several new console messages with the can frame information. To stop your script, click in the console area and then press Ctrl + C. If you want to re-run your script you can just click run again.


Frame(id=419361057, data=bytearray(b'\xf7\x00\x00\x0c\xff\xff\x00\xff'), dlc=8, flags=<MessageFlag.EXT: 4>, timestamp=2687)
Frame(id=419361057, data=bytearray(b'\xf7\x00\x00\x0c\xff\xff\x00\xff'), dlc=8, flags=<MessageFlag.EXT: 4>, timestamp=2790)
Frame(id=419361057, data=bytearray(b'\xf7\x00\x00\x0c\xff\xff\x00\xff'), dlc=8, flags=<MessageFlag.EXT: 4>, timestamp=2892)
Frame(id=419361057, data=bytearray(b'\xf7\x00\x00\x0c\xff\xff\x00\xff'), dlc=8, flags=<MessageFlag.EXT: 4>, timestamp=2994)
Frame(id=419361057, data=bytearray(b'\xf7\x00\x00\x0c\xff\xff\x00\xff'), dlc=8, flags=<MessageFlag.EXT: 4>, timestamp=3096)
Frame(id=419361057, data=bytearray(b'\xf7\x00\x00\x0c\xff\xff\x00\xff'), dlc=8, flags=<MessageFlag.EXT: 4>, timestamp=3198)
Frame(id=419361057, data=bytearray(b'\xf7\x00\x00\x0c\xff\xff\x00\xff'), dlc=8, flags=<MessageFlag.EXT: 4>, timestamp=3402)

We can be finished here, but if we want to format the console messages into something more readable, we can replace the basic print(frame) line with something a little better.


print(hex(frame.id).upper(), 
    frame.dlc, 
    "0x" + hex(frame.data[0])[2:].zfill(2).upper(),
    "0x" + hex(frame.data[1])[2:].zfill(2).upper(),
    "0x" + hex(frame.data[2])[2:].zfill(2).upper(),
    "0x" + hex(frame.data[3])[2:].zfill(2).upper(),
    "0x" + hex(frame.data[4])[2:].zfill(2).upper(),
    "0x" + hex(frame.data[5])[2:].zfill(2).upper(),
    "0x" + hex(frame.data[6])[2:].zfill(2).upper(),
    "0x" + hex(frame.data[7])[2:].zfill(2).upper(),
    frame.timestamp, "ms")

This will format the incoming CAN messages in a way that's a little more readable.


0X18FEF121 8 0xF7 0x00 0x00 0x0C 0xFF 0xFF 0x00 0xFF 16775 ms
0X18FEF121 8 0xF7 0x00 0x00 0x0C 0xFF 0xFF 0x00 0xFF 16878 ms
0X18FEF121 8 0xF7 0x00 0x00 0x0C 0xFF 0xFF 0x00 0xFF 16979 ms
0X18FEF121 8 0xF7 0x00 0x00 0x0C 0xFF 0xFF 0x00 0xFF 17082 ms
0X18FEF121 8 0xF7 0x00 0x00 0x0C 0xFF 0xFF 0x00 0xFF 17184 ms
0X18FEF121 8 0xF7 0x00 0x00 0x0C 0xFF 0xFF 0x00 0xFF 17286 ms
0X18FEF121 8 0xF7 0x00 0x00 0x0C 0xFF 0xFF 0x00 0xFF 17388 ms

Remember to save your script so you can use it as a base to build into applications. All you need to do is open the script in VS Code and click Run to start seeing CAN messages in the console. Of course, this is pretty much what CAN King does anyway, but this allows you to build some pretty complex logic to read and react to incoming CAN messages using this building block. The CAN Read script is available for you to download here. After the file downloads, you will need to drag it into a VS Code window to view/edit. If you just double-click on the .py file, the file will just execute and you won't see anything.

Finally, if you only want to receive certain messages, you can set up filters so that your application will ignore messages that don't match the filter. In this example, we'll accept all messages that have standard ID's, but only accept extended ID messages with ID 0x18FEF121. If you want to accept all messages, then just don't set filters, or set both the filter and mask to all zeroes. Check out this page for more details on how masks/filters work.


# Set standard message ID filters if necessary
can1.canAccept(0x000, canlib.AcceptFilterFlag.SET_MASK_STD)
can1.canAccept(0x000, canlib.AcceptFilterFlag.SET_CODE_STD)

# Set extended message ID filters if necessary
can1.canAccept(0x1FFFFFFF, canlib.AcceptFilterFlag.SET_MASK_EXT)
can1.canAccept(0x18FEF121, canlib.AcceptFilterFlag.SET_CODE_EXT)

Here is the script in its entirety.


from canlib import canlib
from canlib import Frame
#from datetime import datetime

# Create a new CAN channel can1
can1 = canlib.openChannel(channel=0)

# Set the baud rate: canBITRATE_250K or canBITRATE_500k
can1.setBusParams(canlib.canBITRATE_500K)

# Set standard message ID filters if necessary
can1.canAccept(0x000, canlib.AcceptFilterFlag.SET_MASK_STD)
can1.canAccept(0x000, canlib.AcceptFilterFlag.SET_CODE_STD)

# Set extended message ID filters if necessary
can1.canAccept(0x1FFFFFFF, canlib.AcceptFilterFlag.SET_MASK_EXT)
can1.canAccept(0x18FEF121, canlib.AcceptFilterFlag.SET_CODE_EXT)

# Enable the new can1 channel
can1.busOn()

while True:
    # Read the next incoming CAN frame, function will timeout if no new message within 10 seconds
    frame = can1.read(timeout=10000)

    # Print the CAN frame
    print(hex(frame.id).upper(), 
            frame.dlc, 
            "0x" + hex(frame.data[0])[2:].zfill(2).upper(),
            "0x" + hex(frame.data[1])[2:].zfill(2).upper(),
            "0x" + hex(frame.data[2])[2:].zfill(2).upper(),
            "0x" + hex(frame.data[3])[2:].zfill(2).upper(),
            "0x" + hex(frame.data[4])[2:].zfill(2).upper(),
            "0x" + hex(frame.data[5])[2:].zfill(2).upper(),
            "0x" + hex(frame.data[6])[2:].zfill(2).upper(),
            "0x" + hex(frame.data[7])[2:].zfill(2).upper(),
            frame.timestamp, "ms")

Send CAN Messages

The first half of this script will look very similar to the CAN Read script, but to send messages periodically we'll need to add some timing features to our forever loop.

Open VS Code and create a new Python file by pressing Ctrl + N. Press and release Ctrl + K and then press M. Type Python and press Enter. Press Ctrl + S to save the file as "CAN TX.py".

We'll start by importing canlib and datetime to help us with our message transmission timing.


from canlib import canlib
from canlib import Frame
from datetime import datetime
from datetime import timedelta

Similar to the CAN Read script, we'll create a CAN channel, set the baud rate, then enable the channel.


# Create a new CAN channel can1
can1 = canlib.openChannel(channel=0)

# Set the baud rate: canBITRATE_250K or canBITRATE_500k
can1.setBusParams(canlib.canBITRATE_500K)

# Enable the new can1 channel
can1.busOn()

Next we will set up some variables to manage the message send rate timing. Our script will need to know our desired transmit rate and keep track of the last time a message was sent.


# Set the transmit rate
txRate = timedelta(milliseconds=200)

# Set up a variable to keep track of the last TX timestamp
lastTx = datetime.now()

Now we can initiate our forever loop. The first step in our forever loop is to get the current time so we can compare it to the last time a message was sent.


while True:
    # Get the current time
    now = datetime.now()

This if statement will compare the current time to the last time the message was sent and see if it's greater than our desired transmit rate. If it is, then we can go on to send the message.


# Check to see if it's time to send another message.
if(now - lastTx > txRate):
    # Define the frame to be sent.
    frame = Frame(id_=0x18FEF007, dlc=8, data=[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], flags=canlib.canMSG_EXT)

    # Send the message.
    can1.write(frame)

    # Update the last TX timestamp.
    lastTx = datetime.now()

After the message is sent, it is important to update our last transmit variable with the current time.

At this time, you can run your script and check your network traffic to see if your message is being sent.

Here is the entire script.


from canlib import canlib
from canlib import Frame
from datetime import datetime
from datetime import timedelta

# Create a new CAN channel can1
can1 = canlib.openChannel(channel=0)

# Set the baud rate: canBITRATE_250K or canBITRATE_500k
can1.setBusParams(canlib.canBITRATE_500K)

# Enable the new can1 channel
can1.busOn()

# Set the transmit rate
txRate = timedelta(milliseconds=200)

# Set up a variable to keep track of the last TX timestamp
lastTx = datetime.now()

# Create a loop to periodically send the message.
while True:
    # Get the current time
    now = datetime.now()

    # Check to see if it's time to send another message.
    if(now - lastTx > txRate):
        # Define the frame to be sent.
        frame = Frame(id_=0x18FEF007, dlc=8, data=[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], flags=canlib.canMSG_EXT)

        # Send the message.
        can1.write(frame)

        # Update the last TX timestamp.
        lastTx = datetime.now()

If you want to package the transmit rate and message sending into a single function, you can pass the frame and the desired transmit rate into a function that will calculate the timing each time it is called. Just remember to call the function more frequently than your desired message send rate. Here is what that would look like. Notice there's a lot less clutter in the forever loop. Also note the lastTx variable is initialized outside the function and brought into the scope of the function using global.


from canlib import canlib
from canlib import Frame
from datetime import datetime
from datetime import timedelta

# Create a new CAN channel can1
can1 = canlib.openChannel(channel=0)

# Set the baud rate: canBITRATE_250K or canBITRATE_500k
can1.setBusParams(canlib.canBITRATE_500K)

# Enable the new can1 channel
can1.busOn()

# Set the transmit rate
txRate = timedelta(milliseconds=200)

# Set up a variable to keep track of the last TX timestamp
lastTx = datetime.now()

# Create a function to send a CAN message when called.
def sendCanMessage(frame, rate):
    # Bring the global variable lastTx into the function scope.
    global lastTx

    # Get the current time
    now = datetime.now()

    # Check to see if it's time to send another message.
    if(now - lastTx > rate):
        # Send the CAN message
        can1.write(frame)

        # Update the last TX timestamp.
        lastTx = datetime.now()
        

# Create a loop to periodically send the message.
while True:
    # Define the frame with the message ID, DLC, data, and STD or EXT message ID.
    frame = Frame(id_=0x18FEF007, dlc=8, data=[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], flags=canlib.canMSG_EXT)

    # Send the message.
    sendCanMessage(frame, txRate)

Remember to save your script so you can use it as a base to build into applications. All you need to do is open the script in VS Code and click Run to start sending periodic CAN messages. This script is available for you to download here. After the file downloads, you will need to drag it into a VS Code window to view/edit. If you just double-click on the .py file, the file will just execute and you won't see anything.

Conclusion

These examples should be enough to get you started with the basics of understanding how to interact with CANs using Python. You can combine these basics into more complicated scripts that send and receive multiple messages and even interact with real systems. There are several resources available that will help you understand how to accomplish various tasks with Python, and in my experience, trying to figure out how to make what you want in Python is usually fairly easy. If you want a more comprehensive Python course, check out the W3 Schools Python Tutorial or the Python Beginner's Guide for more resources on learning Python.