Skip to content

Testing and interop

Two kinds of OT Fabric repositories share the testing of CANopen. canopen-interop owns the reference implementations: adapters around independent CANopen stacks behind one container contract, pinned by version and hash, with a self-test that runs them against each other. Nothing in it depends on go-canopen. go-canopen and canopenctl are consumers: their interop suites drive the published images on Linux virtual CAN, own the scenarios and assertions, and write a result matrix where every scenario passes, fails, is skipped with a reason, or records a documented difference.

canopen-interop v0.2.0 (container contract 1.1) packages three stacks, each as a device (SDO server, NMT slave, heartbeat, EMCY and PDO producer, with the object dictionary of one shared EDS) and as a master (SDO client with block transfer, NMT master, heartbeat and EMCY consumers):

Adapter Implementation Version Licence Also offers
canopennode CANopenNode in CANopenLinux’s canopend (C), its object dictionary generated by CANopenEditor; the master through the CiA 309-3 ASCII gateway v4.1 Apache-2.0 LSS master and slave
lely Lely CANopen lely-core (C and C++), with Lely’s Python dcf-tools v2.3.5 Apache-2.0 LSS master and slave, PDO reception, EDS checker, MPDOs, CiA 302 boot master, concise DCF generator
pythoncanopen python canopen on python-can 4.6.1 2.4.1 MIT (python-can LGPL-3.0) PDO configuration and reception, SYNC producer, EDS importer, CiA 402 node, LSS master, RPDO sender with the SYNC window

The shared test device, interop-device.eds, holds the CiA 301 communication objects and a test area with every basic type, a 64-character string, a DOMAIN for segmented and block transfers, and read-only, write-only and range-limited objects. Every upstream is pinned in versions.yaml, the multi-architecture images (amd64 and arm64) are published to GitHub Container Registry, and a weekly workflow reports new upstream releases.

The self-test runs every master against every device before any library is tested: 389 checks pass, 15 are documented differences between the stacks and 32 are skipped where a stack lacks a capability (COMPATIBILITY.md).

  1. On a Linux host with the CAN modules (Ubuntu: linux-modules-extra-$(uname -r)), create a virtual interface:

    Terminal window
    git clone https://github.com/otfabric/canopen-interop && cd canopen-interop
    make vcan
  2. CAN interfaces belong to a network namespace and Docker networks do not carry CAN, so containers run with --network host. Start a Lely device as node 5:

    Terminal window
    docker run --rm --network host ghcr.io/otfabric/canopen-interop-lely:v0.2.0 \
    device --interface vcan0 --node-id 5 --eds /fixtures/eds/interop-device.eds --timeout-ms 60000 &
  3. Read from it with another stack, or with your own code:

    Terminal window
    docker run --rm --network host ghcr.io/otfabric/canopen-interop-canopennode:v0.2.0 \
    sdo-upload --interface vcan0 --node-id 5 --index 0x1008
    canopenctl sdo read 5 0x1008 0 -i vcan0
  4. Run the self-test, every reference against every other:

    Terminal window
    make images smoke selftest

On macOS or Docker Desktop, whose kernel has no CAN, make images smoke selftest-eds runs everything that needs no CAN interface. Every image speaks the same commands, flags, JSON documents and exit codes (container contract), and print-capabilities tells a test suite which scenarios a stack can run.

go-canopen v0.3.0 against canopen-interop v0.2.0, on Linux with vcan0 and the published images pinned by digest: 1,620 scenarios, 1,276 pass, 0 fail, 57 documented differences and 287 skips. v0.3.1 changed only the CiA 302 boot-up, and CI runs the suite on amd64 and arm64.

Area go-canopen as master Each stack as master of a go-canopen node
SDO Every readable object of the shared EDS, normal and block upload; every writable object downloaded and read back; DOMAINs of 0 to 4096 bytes; aborts for missing objects, access rights, lengths and limits Uploads and downloads byte for byte, normal and block; the same DOMAIN sizes and aborts
NMT and heartbeat Boot-up, every command and both resets, states seen by the heartbeat consumer, heartbeat loss Every command against the node’s state, boot-up and heartbeat seen by the stack
PDO, SYNC, EMCY TPDOs read, remapped and driven by SYNC; an RPDO checked by SDO and the device’s dump; emergencies decoded python canopen maps and receives the node’s TPDOs and drives a synchronous TPDO with its SYNC; EMCY seen by every stack
Configuration A DCF applied and checked –
CiA 402 – python canopen’s BaseNode402 enables, disables and fault-resets a go-canopen drive
LSS Identify, switch, inquire, bit timing, configure node-ID, store, Fastscan and AssignAll against the CANopenNode and Lely slaves The LSS masters of all three stacks against go-canopen LSS nodes, CANopenNode’s assign-all included
MPDO DAM and SAM, both directions, with Lely
CiA 302 boot-up go-canopen’s manager boots each stack’s device from a concise DCF, rejects a wrong product code (status M) and runs the LSS step Lely’s boot master boots a go-canopen node
Concise DCF Byte for byte against Lely’s dcfgen and its dictionary dump
SYNC window Late RPDOs discarded by Lely, applied by CANopenNode (CiA 301 allows both) python canopen’s RPDO sender inside and after the window of a go-canopen node
EDS and DCF The verdicts and object lists of Lely’s dcf-tools and python canopen’s importer on fourteen files, compared with go-canopen’s parser

The documented differences are behaviours where the stacks differ and CiA 301 allows both, or where a stack has a known limit: CANopenNode’s and Lely’s SDO servers refuse to upload unused error history entries, CANopenNode cannot transfer an empty DOMAIN, python canopen ignores the sub-index of a simple variable, CANopenNode v4.1’s LSS slave does not answer the optional identify services, Lely’s applies a node-ID only at the next reset communication and answers Fastscan when configured, and the EDS tools disagree on files that each reads differently. Each is listed with its evidence in INTEROPERABILITY.md. The suite found defects in go-canopen, all fixed: an empty SDO block transfer that timed out, AssignAll failing when the last device took node-ID 127, a node that could answer an SDO request from before an NMT reset, and the SYNC window measured on the wall clock instead of the monotonic clock.

canopenctl v0.2.1 runs its commands against the same images: sdo, scan, nmt, heartbeat, emcy, pdo, dcf, eds and cia402, and since v0.2.0 lss, mpdo, boot (one stack’s device at a time, and a mixed network of all three with a simulated node), cdcf byte for byte against Lely’s generators, and Lely’s boot master booting canopenctl simulate. 152 pass, 0 fail, 5 documented differences and 63 skips where a stack or the contract’s device lacks a capability.

  • Wireshark: frames of every service are written to pcap and dissected by tshark’s CANopen dissector, and its fields are compared with go-canopen’s dissector, in CI.
  • Third-party EDS files from CANopenNode and python canopen parse, validate, build dictionaries, round-trip and generate code on every run.
  • Chaos: a fault-injecting CAN bridge drops, duplicates, delays, reorders and corrupts frames, classical and CAN FD, and a nightly chaos monkey covers LSS assignment and the CiA 302 boot-up.
  • End to end: a plant with I/O modules and a drive, LSS with a network boot-up, MPDOs, a CANopen FD network, the SYNC window, store and restore across restarts and program download, on the virtual bus and on Linux vcan.
  • Fuzzing of 25 targets, the race detector, and a coverage gate of 90 % for every package.
Not validated Why it matters
Real CAN hardware Virtual CAN shows message encoding and protocol logic, not bit timing, bus load, controller differences or timing under load
CANopen FD None of the reference stacks implements CiA 1301, and no other independent USDO or complete public trace was available; the USDO layouts are partly inferred, and the FD emergency layout differs between public sources
Store and restore, program download, TIME, node guarding against another stack No device of the contract has those objects; they are tested against go-canopen only
CiA 402 beyond python canopen Only python canopen has a drive client among the references, and no reference has a drive device
Flying master, SDO manager (CiA 302-5), device profiles other than CiA 402 Not implemented
CiA conformance The conformance test is CiA’s; go-canopen has not been through it