On this page

How to Communicate With CAN bus Devices

This section explains the steps necessary to send and receive CAN bus messages in Squish tests.

CAN is a message bus standard that allows microcontrollers and other devices (collectively called ECUs) to communicate without a central host or bus manager. It originated in the automotive industry but is now used in many other applications.

The detailed description of the Squish CAN API can be found in the CAN bus support API documentation. If testing requires complex interactions or detailed modeling of ECUs, it may be more beneficial to use third-party software that specializes in CAN bus simulations and can be coupled with Squish using the FMI Interface support.

In a typical test setup, an embedded device that hosts the AUT would be connected to a CAN bus. To allow interaction with the bus from the test script, the test driver must have a compatible CAN controller connected to the same bus.

{}

Diagram of a Squish CAN bus test setup

CAN bus device

All interactions with the CAN interface should take place in a dedicated application context that identifies the system connected to the bus. An application context that supports the CAN bus API can be created using the ApplicationContext startCAN(options) function. You can then establish a connection to the CAN controller and start sending and receiving messages.

var canContext = startCAN();
var device = new CanBusDevice("socketcan", "can0");
var frame = new CanBusFrame(0x100, "4121999a");
device.writeFrame(frame);
canContext = startCAN()
device = CanBusDevice("socketcan", "can0")
frame = CanBusFrame(0x100, "4121999a")
device.writeFrame(frame)
my $canContext = startCAN();
my $device = CanBusDevice->new("socketcan", "can0");
my $frame = CanBusFrame->new(0x100, "4121999a");
$device->writeFrame($frame);
canContext = startCAN();
device = CanBusDevice.new("socketcan", "can0");
frame = CanBusFrame.new(0x100, "4121999a");
device.writeFrame(frame);
set canContext [startCAN]
set device [CanBusDevice new socketcan can0]
set frame [CanBusFrame new 0x100 4121999a]
CanBusDevice invoke $device writeFrame $frame

The supported CAN drivers and the devices available through them can be enumerated using the List CanBusDevice.pluginNames() and List CanBusDevice.availableDevices(driver) static methods.

var canContext = startCAN();
var plugins = CanBusDevice.pluginNames();
for (var i in plugins) {
    var plugin = plugins[i];
    test.startSection(plugin);
    try {
        var devices = CanBusDevice.availableDevices(plugin);
        for (var j in devices) {
            test.log("Device: " + devices[j].deviceName);
        }
    } catch (e) {
        test.log("Failed: " + e.message);
    }
    test.endSection();
}
canContext = startCAN()
plugins = CanBusDevice.pluginNames()
for plugin in plugins:
    test.startSection(plugin)
    try:
        devices = CanBusDevice.availableDevices(plugin)
        for device in devices:
            test.log("Device: %s" % device.deviceName)
    except Exception as e:
        test.log("Failed: %s" % str(e))
    test.endSection()
my $canContext = startCAN();
my @plugins = CanBusDevice->pluginNames();
foreach (@plugins) {
    my $plugin = $_;
    test::startSection($plugin);
    eval {
        my @devices = CanBusDevice->availableDevices($plugin);
        foreach (@devices) {
            test::log("Device: " . $_->deviceName);
        }
    };
    if ($@) {
        test::log("Failed: $@");
    }
    test::endSection();
}
canContext = Squish.startCAN()
plugins = CanBusDevice.pluginNames()
plugins.each { |plugin|
    Test.startSection(plugin)
    begin
        devices = CanBusDevice.availableDevices(plugin)
        devices.each { |device|
            Test.log("Device: " + device.deviceName)
        }
    rescue Exception => e
        Test.log("Failed: " + e.message)
    end
    Test.endSection()
}
startCAN
set plugins [invoke CanBusDevice pluginNames]
foreach plugin $plugins {
    test startSection $plugin
    if {[catch {
        set devices [invoke CanBusDevice availableDevices $plugin]
        foreach device $devices {
            test log [concat "Device: " [property get $device deviceName]]
        }
    } err]} {
        test log [concat "Error: " $err]
    }
    test endSection
}

Frame contents

The CAN standard does not define the contents of frame payloads - this is left to the designers of CAN networks and ECUs. Because of that, without any additional information Squish can only interpret frame payloads as a hexadecimal string. Since using such a frame representation is cumbersome, Squish offers a way to describe the contents of chosen frame types. To use this feature, create a descriptor file and pass it to the ApplicationContext startCAN(options) function.

<canschema version="1">
  <frames>
    <frame id="0x100" name="Thermometer">
      <fields>
        <field name="temperature" type="floating" size="32"/>
      </fields>
    </frame>
    <frame id="0x200" name="AirConditioning">
      <fields>
        <field name="targetTemp" type="integral" size="32"/>
        <field name="cooler" type="integral" size="1"/>
        <field name="heater" type="integral" size="1"/>
      </fields>
    </frame>
  </frames>
</canschema>

Using the above descriptor file, the frame fields can be accessed in the test script.

var canContext = startCAN({schema: File.open(fileName,"r").read()});
var device = new CanBusDevice("socketcan", "can0");
var frame = new ThermometerFrame();
frame.temperature = 10.1;
test.log(frame.hexPayload); // Logs "4121999a"
device.writeFrame(frame);
canContext = startCAN({"schema": open(fileName, "r").read()})
device = CanBusDevice("socketcan", "can0")
frame = ThermometerFrame()
frame.temperature = 10.1
test.log(frame.hexPayload) # Logs "4121999a"
device.writeFrame(frame)
my $canContext = startCAN();
my $device = CanBusDevice->new("socketcan", "can0");
my $frame = ThermometerFrame->new();
$frame->temperature = 10.1;
test::log($frame->hexPayload); # Logs "4121999a"
$device->writeFrame($frame);
canContext = startCAN();
device = CanBusDevice.new("socketcan", "can0");
frame = ThermometerFrame.new();
frame.temperature = 10.1;
Test.log(frame.hexPayload); # Logs "4121999a"
device.writeFrame(frame);
set canContext [startCAN]
set device [CanBusDevice new socketcan can0]
set frame [ThermometerFrame new]
ThermometerFrame set $frame temperature 10.1
test log [ThermometerFrame get $frame hexPayload] ;# Logs "4121999a"
CanBusDevice invoke $device writeFrame $frame

The detailed description of the allowed field types can be found in CAN frame schema documentation.

Sending CAN frames

Frames can be sent to a CAN device using the CanBusDevice.writeFrame(frame) function. However, it is common practice to send important CAN frames repeatedly at short intervals. To simulate this behavior for a particular ECU, you can create a CanBusFrameRepeater class object. Such an object will send copies of the specified frame repeatedly for as long as it is enabled.

var canContext = startCAN({schema: File.open(fileName,"r").read()});
var device = new CanBusDevice("socketcan", "can0");
var frame = new ThermometerFrame();
frame.temperature = 10.1;
var repeater = new CanBusFrameRepeater(device, frame);
repeater.interval = 200; // 200 ms interval
canContext = startCAN({"schema": open(fileName, "r").read()})
device = CanBusDevice("socketcan", "can0")
frame = ThermometerFrame()
frame.temperature = 10.1
repeater = CanBusFrameRepeater(device, frame)
repeater.interval = 200 # 200 ms interval
my $canContext = startCAN();
my $device = CanBusDevice->new("socketcan", "can0");
my $frame = ThermometerFrame->new();
$frame->temperature = 10.1;
my $repeater = CanBusFrameRepeater->new($device, $frame);
$repeater->interval = 200; # 200 ms interval
canContext = startCAN();
device = CanBusDevice.new("socketcan", "can0");
frame = ThermometerFrame.new();
frame.temperature = 10.1;
repeater = CanBusFrameRepeater.new(device, frame);
repeater.interval = 200; # 200 ms interval
set canContext [startCAN]
set device [CanBusDevice new socketcan can0]
set frame [ThermometerFrame new]
ThermometerFrame set $frame temperature 10.1
set repeater [CanBusFrameRepeater new $device $frame]
CanBusFrameRepeater set $repeater interval 200 ;# 200 ms interval

You can modify the original frame object at any time during the test. The change will be immediately reflected in the repeater output.

[...]
// The measured temperature changes
frame.temperature = 12.1;
// or repeater.frame.temperature = 12.1;
[...]
# The measured temperature changes
frame.temperature = 12.1
# or repeater.frame.temperature = 12.1
[...]
# The measured temperature changes
$frame->temperature = 12.1;
# or $repeater->frame->temperature = 12.1;
[...]
# The measured temperature changes
frame.temperature = 12.1;
# or repeater.frame.temperature = 12.1;
[...]
# The measured temperature changes
ThermometerFrame set $frame temperature 12.1
# or ThermometerFrame set [CanBusFrameRepeater get $repeater frame] temperature 12.1

Receiving frames

Receiving frames is possible using the CanBusFrame CanBusDevice.readFrame(timeout) function. However, using it requires the test writer to inspect all incoming frames in a timely manner. While this approach offers the greatest versatility, it is not the most convenient option for a typical test. Instead, you can use a CanBusFrameReceiver class object. This object drains incoming frames from the CAN device and keeps a history of received frames with specified IDs.

var canContext = startCAN({schema: File.open(fileName,"r").read()});
var device = new CanBusDevice("socketcan", "can0");
var receiver = new CanBusFrameReceiver(device);
receiver.setHistorySize(AirConditioningFrame.frameId, 1);
snooze(5);
// Logs the last-set temperature
test.log(receiver.latestFrame(AirConditioningFrame.frameId).targetTemp);
canContext = startCAN()
device = CanBusDevice("socketcan", "can0")
receiver = CanBusFrameReceiver(device)
receiver.setHistorySize(AirConditioningFrame().frameId, 1)
snooze(5)
# Logs the last-set temperature
test.log(receiver.latestFrame(AirConditioningFrame().frameId).targetTemp)
my $canContext = startCAN();
my $device = CanBusDevice->new("socketcan", "can0");
my $receiver = CanBusFrameReceiver->new($device);
$receiver->setHistorySize(Squish::AirConditioningFrame->frameId, 1);
snooze(5);
# Logs the last-set temperature
test::log($receiver->latestFrame(Squish::AirConditioningFrame->frameId)->targetTemp);
canContext = startCAN();
device = CanBusDevice.new("socketcan", "can0");
receiver = CanBusFrameReceiver.new(device);
receiver.setHistorySize(AirConditioningFrame.frameId, 1);
snooze(5);
# Logs the last-set temperature
Test.log(receiver.latestFrame(AirConditioningFrame.frameId).targetTemp)
set canContext [startCAN]
set device [CanBusDevice new socketcan can0]
set receiver [CanBusFrameReceiver new $device]
CanBusFrameReceiver invoke $receiver setHistorySize [AirConditioningFrame get frameId] 1
snooze 5
# Logs the last-set temperature
set latestFrame [CanBusFrameReceiver invoke $receiver latestFrame [AirConditioningFrame get frameId]]
test log [AirConditioningFrame get $latestFrame targetTemp]

It is also possible to wait for a frame with a specific ID and specific field values.

[...]
receiver.setHistorySize(AirConditioningFrame.frameId, 1);
var frame = receiver.waitForFrame({frameId: AirConditioningFrame.frameId, targetTemp: 18});
test.log("Expected frame received");
[...]
receiver.setHistorySize(AirConditioningFrame().frameId, 1)
frame = receiver.waitForFrame({"frameId": AirConditioningFrame().frameId, "targetTemp": 18})
test.log("Expected frame received")
[...]
$receiver->setHistorySize(Squish::AirConditioningFrame->frameId, 1);
my %query = (frameId => Squish::AirConditioningFrame->frameId, targetTemp => 18);
my $frame = $receiver->waitForFrame(%query);
test::log("Expected frame received");
[...]
receiver.setHistorySize(AirConditioningFrame.frameId, 1);
frame = receiver.waitForFrame({"frameId"=>AirConditioningFrame.frameId, "targetTemp"=>18});
Test.log("Expected frame received");
[...]
set frameId [AirConditioningFrame get frameId]
CanBusFrameReceiver invoke $receiver setHistorySize $frameId 1
set frame [CanBusFrameReceiver invoke $receiver waitForFrame [list frameId $frameId targetTemp 18]]
test log "Expected frame received"

The CanBusFrameReceiver.waitForFrame(filter, timeout) function searches the current history for a matching frame. If none is found, it waits until a matching frame is received. This prevents a situation where the waiting API is called too late and misses a frame that has just been received.

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners.
The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation.
Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.

Search Results