cantools API

Warning

The examples on this page are provided without support

The Python package cantools extends python-can with support for databases (containing message interpretations) and support for message payload encoding/decoding. Refer to the cantools documentation for more information https://cantools.readthedocs.io/.

This page contains some basic examples on how to use cantools.


Manually define database

A database of message interpretations can be manually constructed directly in Python.

from cantools.database import Database, Message, Signal
from cantools.database.conversion import BaseConversion

# Define signals
db_msg1_signals = [
    Signal(name="A1", start=0,  length=16, conversion=BaseConversion.factory(scale=0.625, offset=0)),
    Signal(name="A2", start=16, length=16, conversion=BaseConversion.factory(scale=0.625, offset=0)),
    Signal(name="A3", start=32, length=16, conversion=BaseConversion.factory(scale=0.625, offset=0)),
    Signal(name="A4", start=48, length=16, conversion=BaseConversion.factory(scale=0.625, offset=0))]

# Define messages
db_msgs = [
    Message(name="A1ToA4", is_extended_frame=False, frame_id=2, length=8, signals=db_msg1_signals)]

# Define database
db = Database(messages=db_msgs)

Load existing database

cantools supports a range of database file formats. e.g. *.DBC.

from cantools import database

# Load database from file
db = database.load_file("<DBC_FILE_PATH>")

Decode messages

Below example demonstrates how cantools can be used to decode messages.

import can
from cantools import database
from collections import defaultdict

# Load database from file
db = database.load_file("<DBC_FILE_PATH>")

# Decoded signals
results = defaultdict(list)

# Open log file
with can.LogReader("<LOG_FILE_PATH>") as reader:

    # Read messages
    for msg in reader:

        # Get message name from database
        try:
            db_msg = db.get_message_by_frame_id(msg.arbitration_id)
        except KeyError:
            # Message not defined in database
            continue

        # Decode using database
        result = db.decode_message(db_msg.name, data=msg.data)

        # Add timestamp
        result.update({"t": msg.timestamp})

        # Append to results
        results[db_msg.name].append(result)

cantools has weak support for J1939 PGNs. However, by splitting the database into two (one for PDU format 1 and one for PDU format 2), decoding of PGNs can be done as demonstrated below.

Note

For better J1939 support, consider using the dedicated python-can-j1939 package.

import can
import math
from cantools import database, j1939
from collections import defaultdict

# Create a J1939 PDU1 and PDU2 database (with matching PGN ID masks)
db_pdu1 = database.Database(frame_id_mask=0x3FF0000)
db_pdu2 = database.Database(frame_id_mask=0x3FFFF00)

# Load database from file
for db_msg in database.load_file("<DBC_FILE_PATH>").messages:
    if j1939.is_pdu_format_1(j1939.frame_id_unpack(db_msg.frame_id).pdu_format):
        db_pdu1.messages.append(db_msg)
    else:
        db_pdu2.messages.append(db_msg)
db_pdu1.refresh()
db_pdu2.refresh()

# Decoded signals
results = defaultdict(list)

# Open log file
with can.LogReader("<LOG_FILE_PATH>") as reader:

    # Read messages
    for msg in reader:

        # J1939 is extended only
        if not msg.is_extended_id:
            continue

        # Select database based on PDU format
        if j1939.is_pdu_format_1(j1939.frame_id_unpack(msg.arbitration_id).pdu_format):
            db = db_pdu1
        else:
            db = db_pdu2

        # Get message name from database
        try:
            db_msg = db.get_message_by_frame_id(msg.arbitration_id)
        except KeyError:
            # Message not defined in database
            continue

        # Decode using database
        result = db.decode_message(db_msg.name, data=msg.data)

        # Ensure that signals satisfy min/max
        for name, value in result.items():
            db_sig = db_msg.get_signal_by_name(name)
            if not db_sig.maximum > value > db_sig.minimum:
                result[name] = math.nan

        # Add timestamp
        result.update({"t": msg.timestamp})

        # Append to results
        results[db_msg.name].append(result)

The decoded results can now easily be used in Python, e.g. with pandas and matplotlib as demonstrated below.

import pandas
from matplotlib import pyplot as plt

...

# Results to dataframes
dfs = {name: pandas.DataFrame.from_dict(value).set_index('t') for name, value in results.items()}

# Plot all dataframes
for name, df in dfs.items():
    df.plot(title=name)
    plt.show()
../../../_images/pyplot1.svg
../../../_images/pyplot2.svg