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
Section titled “canopen-interop”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).
Running the reference stacks
Section titled “Running the reference stacks”-
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-interopmake vcan -
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 & -
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 0x1008canopenctl sdo read 5 0x1008 0 -i vcan0 -
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.
What go-canopen is tested on
Section titled “What go-canopen is tested on”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.
What canopenctl is tested on
Section titled “What canopenctl is tested on”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.
Beyond the interop suite
Section titled “Beyond the interop suite”- 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.
What has not been validated
Section titled “What has not been validated”| 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 |