CAN bus support

CAN (Controller Area Network) bus is a standard for communication between microcontrollers and other devices without a central bus controller. It was designed for use in the automotive industry, but it is increasingly adopted in other areas as well.

Overview

Squish 6.7 and later allow testing CAN messages sent and received by a device running an application. To facilitate this, the test setup must include a PC with the supported CAN hardware attached and connected to the same CAN bus as the tested system, and with the corresponding driver package installed.

{}

Diagram of a Squish CAN bus test setup

Device vendorDriver nameUsed with
GenericsocketcanDevices using standard SocketCAN interface. Only available on Linux.
SYS TEC electronicsysteccanDevices using SYS TEC CAN interface. Only available on Windows.
PEAK-SystempeakcanDevices using PEAK CAN interface.
MHS ElektroniktinycanDevices using MHS CAN interface.
Vector InformatikvectorcanDevices using Vector CAN interface. Only available on Windows.

CAN bus types are available only in the application context created with the ApplicationContext startCAN(options).

ApplicationContext startCAN(options)

ApplicationContext startCAN(options, host)

ApplicationContext startCAN(options, host, port)

ApplicationContext startCAN(options, host, port, timeoutSecs)

Creates, activates and returns a new application context which provides types and utilities related to CAN bus. All access to CAN objects need to be done while that context is active.

The first argument is a dictionary which allows specifying additional options for the new context. Currently the only supported key is schema. If provided, the associated value should be a valid CAN frame schema.

Optionally, as seconds and third parameters a host and port can be specified. If these parameters are used, instead of connecting to the default host and port (as specified in the Squish IDE's settings or on squishrunner's command line), a connection to the squishserver on the specified host and listening to the specified port will be established.

The fourth parameter, timeoutSecs (an integer number of seconds) can also be specified. This tells Squish how long it should be prepared to wait for the context to initialize before throwing an error. If specified, this value overrides squishrunner's default timeout. If not specified, the squishserver's default of 20 seconds is used, unless it has been changed. See Squish Server Settings dialog.

If you want to specify a timeout, but don't want to change the host or port, you can do so by passing an empty string as the host (which will make Squish use the configured host—localhost by default), and by passing -1 as the port.

CanBusDeviceInfo class

CanBusDeviceInfo.name

The device name.

CanBusDeviceInfo.hasFlexibleDataRate

A boolean which indicates whether the device supports flexible data rate.

CanBusDeviceInfo.isVirtual

A boolean which indicates whether the device is virtual.

CanBusDevice class

The CanBusDevice class is the core of the Squish CAN bus support. It represents a connection to a specific CAN device.

CanBusDevice CanBusDevice(driver, device)

This constructor opens a connection to the CAN hardware using specified driver and device.

List CanBusDevice.pluginNames()

This static method returns the list of driver names supported on current platform.

List CanBusDevice.availableDevices(driver)

This static method enumerates the available devices using the specified driver and returns the list of CanBusDeviceInfo class objects.

CanBusFrame CanBusDevice.readFrame(timeout)

This method waits until a new CAN frame is received and returns is a CanBusFrame object. If no frame is received within the specified timeout, an exception is thrown.

List CanBusDevice.readAllFrames()

Returns all the frames accumulates in the input buffer of the CAN device as a list of CanBusFrame objects.

CanBusDevice.writeFrame(frame)

Schedules the frame to be sent to the CAN bus. The frame will be sent as soon as possible, but it happen some time after this method returns.

CanBusDevice.disconnectDevice()

Temporarily disconnects from the device. All frames which arrive after this call will not be added to the internal queue and will not be available using the CanBusFrame CanBusDevice.readFrame(timeout) and List CanBusDevice.readAllFrames() methods.

CanBusDevice.connectDevice()

Restores the connection to the device which was previously suspended using the CanBusDevice.disconnectDevice() method.

CanBusFrame class

The CanBusFrame object represents a single CAN frame.

CanBusFrame.frameId

The ID of the frame.

CanBusFrame.isValid

A boolean which specifies whether the frame is valid.

CanBusFrame.frameType

The type of the frame.

CanBusFrame.hexPayload

The payload of the frame as a hexadecimal string.

CanBusFrame.extendedFrameFormat

A boolean which indicates whether the frame uses the extended 29-bit frame ID.

CanBusFrame.bitrateSwitch

A boolean which indicates if the frame uses the higher bitrate for transmission on CAN hardware which supports that feature.

CanBusFrame CanBusFrame()

This constructor creates a new invalid frame.

CanBusFrame CanBusFrame(frameId)

CanBusFrame CanBusFrame(frameId, payload)

These constructors create a new frame with the specified ID. Optionally, the data payload for the frame can be specified in form of a hexadecimal string.

String CanBusFrame.toString()()

This method returns a human readable description of the frame which includes the frame ID and the binary payload.

CanBusFrameRepeater class

The CanBusFrameRepeater object sends the specified frame to the CAN bus repeatedly, in configurable intervals. It can be used to simulate CAN devices which send their frames in such a manner.

CanBusFrameRepeater.device

The CanBusDevice class object used to send frames to the bus. This property cannot be modified.

CanBusFrame.interval

The interval between CAN frame is sent to the bus, in milliseconds. The default interval is 500ms.

CanBusFrame.enabled

A boolean which indicates whether the repeater should send its frame to the bus. It can be used to temporarily suspend its operation.

CanBusFrameRepeater CanBusFrameRepeater(device)

CanBusFrameRepeater CanBusFrameRepeater(device, frame)

These constructors create a new frame repeater for specified device. Optionally, the frame to send out can be specified as well.

CanBusFrameReceiver class

The CanBusFrameReceiver object receives incoming CAN frames and keeps a history of received frames. The maximal number of received frames can be configured.

Once a receiver has been created for a CanBusDevice class object, frames will not be available using the CanBusFrame CanBusDevice.readFrame(timeout) and List CanBusDevice.readAllFrames() methods. Once created, the receiver takes over the task of managing incoming frames.

CanBusFrameReceiver.device

The CanBusDevice class object used to receive frames. This property cannot be modified.

CanBusFrameReceiver CanBusFrameReceiver(device)

Creates a new CanBusFrameReceiver instance for the specified CanBusDevice class.

CanBusFrameReceiver CanBusFrameReceiver(driver, device)

Creates a new CanBusDevice class object for the specified driver and device name and a new CanBusFrameReceiver instance for that device.

CanBusFrameReceiver.setHistorySize(frameId, length)

Changes the history length for the specified frame ID to the specified value.

Integer CanBusFrameReceiver.frameCount(frameId)

Returns the number of frames with the specified ID which are currently accumulated in the receiver.

CanBusFrame CanBusFrameReceiver.latestFrame(frameId)

Retrieves the latest captured frame with the specified ID.

CanBusFrame CanBusFrameReceiver.pastFrame(frameId, index)

Retrieves one of the past frames accumulated in the receiver. The index must lie in the [0, frameCount()) range, with 0 being the latest frame received.

CanBusFrame CanBusFrameReceiver.pastFrames(frameId)

Retrieves all of the past frames accumulated in the receiver.

CanBusFrameReceiver.clearHistory(frameId)

Clear all the frames with the specified ID accumulated in the receiver.

CanBusFrameReceiver.waitForFrame(filter, timeout)

Waits for frame matching the specified filter and returns it. The filter must be a dictionary with the frameId field. Additionally, for known frame types the dictionary can contain expected values for the frame fields. If no frame matching the filter arrives until the timeout elapses, an exception is raised.

The timeout argument is specified in milliseconds. The timeout is optional, the default value is 30 seconds.

In order to avoid concurrency issues, the frames already accumulated in the receiver are considered first. This may lead to a past frame being found by mistake, in order to avoid that the receiver buffer may need to be cleared with the CanBusFrameReceiver.clearHistory(frameId) method.

CAN frame schema

The contents of CAN frame payload is not standardized. Because of that, Squish cannot offer any insight into the contents of the payload buffer beyond its hexadecimal representation. Since it is very inconvenient to access payload members this way, the ApplicationContext startCAN(options) can accept a schema which defines the contents of frame payload based on the frame ID.

The CAN schema is an XML file with a canschema root element and version="1" attribute. The frame contents are defined in a frames element. Each frame type is defined as a frame element. Each frame element must have id and name attributes, which contain the numeric frame ID and the name for the frame type, respectively. Each frame should contain a fields element which defines known fields within the frame. Each field element defines a single field. The attributes of the field element can be:

  • name — the name of the field. This attribute is mandatory;
  • type — the type of the field. Currently the supported types are integral and floating. The default value for this field is integral.
  • signed — a boolean which indicates whether the integral type is signed. The default value for this field is false. This attribute is ignored for field types other than integral.
  • size — The size of the field in bits. Integral fields can specify any size between 1 and 64. Floating point fields can be either 32 or 64-bit long. The default value for this field is 32.

For each frame type in the schema, Squish will create a CanBusFrame class subclass with the specified name and Frame suffix. The frame class defines payload fields as frame object properties. Additionally, the static field called frameId is available on the type for easy access.

The following file demonstrates an example CAN schema:

<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 schema, the fields defined for the frame type can be accessed as follows:

var th = new ThermometerFrame();
th.temperature = 10.1;
test.log( th.hexPayload ); // Logs "4121999a"

test.log( AirConditioningFrame.frameId ); // Logs "512"
var ac = new AirConditioningFrame({targetTemp: 10, cooler: 0, heater: 1});
test.log( ac.targetTemp ); // Logs "10"
th = ThermometerFrame();
th.temperature = 10.1;
test.log( th.hexPayload ); # Logs "4121999a"

test.log( AirConditioningFrame.frameId ); # Logs "512"
ac = AirConditioningFrame({"targetTemp": 10, "cooler": 0, "heater": 1});
test.log( ac.targetTemp ); # Logs "10"
my $th = Squish::ThermometerFrame->new();
$th->temperature = 10.1;
test::log( $th->hexPayload ); # Logs "4121999a"

test::log( Squish::AirConditioningFrame->frameId ); # Logs "512"
my %args = ( targetTemp => 10, cooler => 0, heater => 1);
my $ac = Squish::AirConditioningFrame->new(%args);
test::log( $ac->targetTemp ); # Logs "10"
th = ThermometerFrame.new();
th.temperature = 10.1;
Test.log( th.hexPayload ); # Logs "4121999a"

Test.log( AirConditioningFrame.frameId ); # Logs "512"
ac = AirConditioningFrame.new(( "targetTemp" => 10, "cooler" => 0, "heater" => 1));
Test.log( ac.targetTemp ); # Logs "10"
set th [ThermometerFrame new]
ThermometerFrame set th temperature 10.1
test log [ThermometerFrame get hexPayload $th] # Logs "4121999a"

test log [AirConditioningFrame get frameId] # Logs "512"
set ac [ AirConditioningFrame new (targetTemp, 10, cooler, 0, heater, 1) ]
test log [ AirConditioningFrame get targetTemp $ac ] # Logs "10"