Skip to content

CAN Codec

The CAN codec is included in the zeloscloud package, and requires extras can to be installed.

Introduction

The CAN codec is designed to streamline communication over CAN interfaces. It simplifies interactions with CAN buses for operations like transmitting and/or receiving CAN messages via signal/message objects. https://en.wikipedia.org/wiki/CAN_bus

The key components are:

  • CanCodec: Manages the encoding and decoding of data sent over a CAN link.
  • CanLink: Represents the physical connection over which data is transmitted.
  • CanMessage: Abstractions for messages
  • CanSignal: Abstractions for signals

Read these!

Checkout the CAN link documentation for additional context can link.

How the Codec Works

The CanCodec operates by generating a set of predefined messages/signals from a configuration file like a DBC. Instances of messages and signals are represented by the CanMessage and CanSignal classes respectively.

When you initialize the codec, you provide a configuration that defines the set of messages and signals for a given bus.

Quick Start Guide

This section will walk through creating and using a CanCodec, a CanLink, and the corresponding Signals/ Messages

import can
from zeloscloud.links.can_link import CanLink
from zeloscloud.codecs.can import CanCodec


link_config = {
    "channel": "vcan0",     # Or the name of a 'real' CAN dongle
    "interface": "virtual", # Or 'socketcan' if using Linux + CAN dongle
}
codec_config = {
    "allow_duplicates": True,    # Allow messages/signals with the same name
    "suppress_errors": True,     # Suppress error (messages) when CAN bus errors and/or unknown frames are encountered.
}
dbc = "/path/to/my.dbc"
with CanLink(name="can-link", config=link_config) as link, \
     CanCodec(name="can-codec", config=codec_config, link=link, dbc=dbc) as codec:

    # Send raw messages:
    message = can.Message()
    link.send(message)

    # Send named messages defined in your DBC:
    codec.MyMessage.MySignal.set(123.0)
    codec.MyMessage.send_periodic()

Basic Pytest Setup

To set up a basic codec, you need to initialize it with a name, link, and configuration file. Here's a quick example to get you started:

conftest.py

import pytest
from zeloscloud.codecs.can import CanCodec
from zeloscloud.links.can_link import CanLink


@pytest.fixture(scope="session")
def link():
    """Create a CAN link that provides message send/recv capability"""
    config = {
        "channel": "vcan0",     # Or the name of a 'real' CAN dongle
        "interface": "virtual", # Or 'socketcan' if using Linux + CAN dongle
    }
    with CanLink(name="vcan0-link", config=config) as link:
        yield link


@pytest.fixture(scope="session")
def codec(link):
    """Create a CAN codec that monitors the CAN bus and encodes/decodes frames into messages/signals"""
    with CanCodec(name="vcan0-codec", link=link, dbc="/path/to/file.dbc") as codec:
        yield codec
See more supported CanLink examples: here

Pytest Fixtures

We are utilizing pytest fixtures to inject dependencies into our tests and setups. This method ensures that our components are modular and can be tested independently. The can_link and can_codec fixtures can also be used as standalone components.

Codec Configuration

@pytest.fixture(scope="session")
def codec(link):
    """Create a CAN codec that monitors the CAN bus and encodes/decodes frames into messages/signals"""
    with CanCodec(name="vcan0-codec", link=link, dbc=/path/to/file.dbc) as codec:
        yield codec

Configuration Details

The CanCodec accepts a config parameter which is a dictionary that enables / disables certain features. The default configuration is: - allow_duplicates: False : raise errors when there are duplicate signal / message names. - suppress_errors: True : suppress error (messages) when CAN bus errors and/or unknown frames are encountered. - emit_schema: False : emit the CAN schema for all messages/signals on init

Example Usage

After setting up the codec with the configuration, you can use it in your application to send and receive data. Here's an example showing how to interact with the CanCodec.

Using the Codec in an Application

import time

def test_off_to_on(codec, check):
    """
    Below example test assumes:
    - Device on the CAN bus named DUT (Design Under Test)
    - DUT sends a `Status` message periodically
      - `Status` message has a State signal
      - State signal can be "OFF" or "ON"
    - DUT accepts a `Command` message
      - `Command` message has a TargetState signal
      - When the TargetState signal = ON the DUT should move to it's ON state
    """
    # Ensure that the DUT starts the test in OFF
    check.that(codec.Status.State, '=', "OFF")

    # Send the ON `Command` message periodically
    codec.Command.TargetState.set("ON")
    codec.Command.send_periodic()

    # Wait for the DUT to transition to "ON" and send an updated Status message
    time.sleep(0.2)

    # Ensure that the DUT is now in the ON state
    check.that(codec.Status.State, "=", "ON")

Using the Codec

In this example, the CanCodec is used to turn the Device Under Test on. It also demonstrates how to use the check plugin for making assertions on the data.

Accessing Signals within Messages

Signals can be accessed through the following attributes: - codec. : directly access the signal - codec.. : access the signal via the encapsulating message

In the case of duplicated signal names, signals must be referenced through the encapsulating message.

codec.message_one.duplicate_signal.set(1)
codec.message_two.duplicate_signal.set(0)

In the case of duplicated message names, messages and signals must be referenced through the encapsulating message referenced by the identifier.

codec.messages_by_id[123].duplicate_signal.set(1)
codec.messages_by_id[456].duplicate_signal.set(0)

Iterating Through Codec and Message Signals

You can directly iterate through all signals registered with the codec

Example Usage:

for signal in codec:
    print(f"Signal Name: {signal.name}, Signal Value: {signal.get()}")

for message in codec.signals:
    print(f"Signal Name: {signal.name}, Signal Value: {signal.get()}")

for message in codec.messages:
    print(f"Message Name: {message.name}, message Value: {message.get()}")

Troubleshooting

Permissions

OSError: [Errno 19] No such device
Hint: Use ifconfig to check if the device (CAN bus) is present

Bring up (and configure) the CAN bus:

ip link set down can0
ip link set can0 type can bitrate 500000
ip link set can0 type can restart-ms 100
ip link set can0 txqueuelen 1000
ip link set up can0
ip -details -statistics link show can0
Note: Replace can0 with your actual device.

Debugging

cantools

candump can0 | python3 -m cantools decode path/to/dbc

can-utils

https://elinux.org/Can-utils

candump can0
candump can0 -t d
candump can0,123:0x7FF -t d
cangen can0

API Reference

See zeloscloud.codecs.can in the API Reference.