Table of Contents
This section explains the steps necessary to send and receive CAN bus messages in Squish tests.
CAN is a message bus standard which allows microcontroller and other devices (collectively called ECUs) to communicate without a central host or bus manager. It originates from automotive industry, but has been adopted for many other applications since.
The detailed description of the Squish CAN API can be found in the CAN bus support (Section 6.21) API documentation. In case testing requires complex interactions or require detailed modeling of ECUs, it may be more beneficial to use third party software which specializes in CAN bus simulations and can be coupled with Squish using the FMI Interface support (Section 6.20).
In a typical test setup an embedded device which hosts the AUT would be connected to a CAN bus. In order to allow interaction with the bus in the test script, the test driver must have a compatible CAN controller connected to the same bus.
All references to the CAN interface should take place in its own application
context which identifies the particular system connected to the bus.
An application context which supports CAN bus API can be created using the
startCAN
function. Then you can 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 00deadbeef00] CanBusDevice invoke $device writeFrame $frame
The supported CAN drivers and the devices using it can be enumerated using the
CanBusDevice.pluginNames
and
CanBusDevice.availableDevices
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 { @devices = CanBusDevice->availableDevices($plugin); foreach ( @devices ) { test::log("Device: $_->deviceName" ); } } or { 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 }
The CAN standard does not define the contents of the 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 frame representation is very cumbersome, Squish offers
a way to describe the contents of chosen frame types. In order to make use of it
you can create a descriptor file which can be passed to the
startCAN
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 members 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(device.hexPayload); // Logs "4121999a" device.writeFrame(frame);
canContext = startCAN() device = CanBusDevice("socketcan", "can0") frame = ThermometerFrame() frame.temperature = 10.1 test.log(device.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 hexPayload $th] # Logs "4121999a" invoke [$device writeFrame $frame]
The detailed desription of the allowed field types can be found in CAN frame schema (Section 6.21.7) documentation.
Frames can be sent to a CAN device using the
CanBusDevice.writeFrame
function. However, it is a
common practice to send important CAN frames repeatedly in short intervals.
In order to simulate that behavior of particular ECU, you can create a
CanBusFrameRepeater class (Section 6.21.5) object. Such 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; // 200ms interval
canContext = startCAN(open(fileName, "r").read()) device = CanBusDevice("socketcan", "can0") frame = ThermometerFrame() frame.temperature = 10.1 repeater = CanBusFrameRepeater(device, frame) repeater.interval = 200 # 200ms interval
my $canContext = startCAN(); my $device = CanBusDevice->new("socketcan", "can0"); my $frame = ThermometerFrame->new(); $frame->temperature = 10.1; repeater = CanBusFrameRepeater->new(device, frame) repeater.interval = 200 # 200ms interval
canContext = startCAN(); device = CanBusDevice.new("socketcan", "can0"); frame = ThermometerFrame.new(); frame.temperature = 10.1; repeater = CanBusFrameRepeater.new(device, frame); repeater.interval = 200; # 200ms 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 # 200ms 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 is possible using
CanBusDevice.readFrame
function. However, using it
requires the test writer to inspect all incoming frames in a timely manner.
While it offers the most versatility, it is not the most convenient tool in a
typical test. Instead you can use a
CanBusFrameReceiver class (Section 6.21.6) object. That object drains the
incoming frames from the CAN device and keeps a history of received frames of
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.lastFrame(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.lastFrame(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->lastFrame(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.lastFrame(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 lastFrame [CanBusFrameReceiver invoke $receiver lastFrame [AirConditioningFrame get frameId]] test log [AirConditioningFrame get $lastFrame targetTemp]
It is also possible to wait for a frame of specific ID and with 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) var 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); var frame = $receiver->waitForFrame(%query); test::log("Expected frame received");
[...] receiver.setHistorySize(AirConditioningFrame.frameId, 1); frame = receiver.waitForFrame(("frameId"=>Squish::AirConditioningFrame->frameId, "targetTemp"=>18)); Test.log("Expected frame received");
[...] set frameId [AirConditioningFrame get frameId] CanBusFrameReceiver invoke $receiver setHistorySize $frameId 1 set frame [CanBusFrameReceiver invoke waitForFrame (frameId $frameId targetTemp 18)] test log "Expected frame received"
The CanBusFrameReceiver.waitForFrame
function searches
the current history to find a matching frame, and 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 just-received frame.