New release: v2.3.0

7 minute read

v2.3.0 has just been released. This is a major release containing several new features and a few minor API changes (see below).

ECSS compliance

This release contains several changes necessary to make the Lely CANopen stack suitable for use in space, in particular to make it compliant with the ECSS-E-ST-50-15C - CANbus extension protocol. Full ECSS compliance requires breaking changes and will not be achieved until the v3 release (under deleveopment in the ecss branch at This release contains the necessary non-breaking changes (with the exception of a few minor changes that should not affect normal users).

Configuration options

An ECSS-compliant build can be generated by specifying the --enable-ecss-compliance option to the configure script. This option disables all functionality not covered by the ECSS-E-ST-50-15C - CANbus extension protocol, which includes the I/O libraries and all C++ APIs. Additionally, it configures the project for a freestanding environment with (almost) no support from the standard C library.

Standard C library support can also be selectively disabled with the following new configure options (or preprocessor macros)

  • --disable-errno (LELY_NO_ERRNO): disable support for errno and <errno.h>.
  • --disable-malloc (LELY_NO_MALLOC) disable support for dynamic memory allocation.
  • --disable-stdio (LELY_NO_STDIO) disable standard I/O support.

Note: LELY_NO_MALLOC support is preliminary and will not yet disable all dynamic memory allocation.

Additionally, --disable-diag (LELY_NO_DIAG) disables the diagnostic functions from liblely-util.

The NMT boot slave and configuration request services are not covered by ECSS. They can be disabled with the new --disable-nmt-boot (LELY_NO_CO_NMT_BOOT) and --disable-nmt-cfg (LELY_NO_CO_NMT_CFG) options, respectively. Node guarding is also out of scope. It can be disabled with the new --disable-ng (LELY_NO_CO_NG) option.

For more information, see the build configuration documentation.

Static object dictionary generator

Disabling dynamic memory allocation prevents the generation of the object dictionary at runtime. It also precludes the use of the static object dictionary generated by dcf2c, since that object dictionary still needs to be converted to the real object dictionary at runtime.

The new dcf2dev tool (part of the EDS/DCF tools) can generate a compile-time object dictionary that is directly usable by the CANopen stack.

Note: Because of the way array values are handled, the generated object dictionary can only be used when LELY_NO_MALLOC is defined.

Unit tests

Part of the ECSS-compliance effort is the development of a test suite that covers 100% of the code that is in scope. This test suite is based on the CppUTest and can be found in the unit-tests directory. It can be disabled with the --disable-unit-tests option.

New features

Stop token

A stop token implementation is now provided by <lely/util/stop.h> and <lely/util/stop.hpp>. The API is based on <stop_token> in C++20. Stop tokens provide a generic mechanism for users to cancel (sequences of) tasks, using (global) timeouts or any other condition. This blog post provides a good overview of why this is useful.

Support for stop tokens will be added to the asynchronous I/O library in a future release.

Transmit-PDO sampling support

CiA 301 allows for a SYNC-driven Transmit-PDO to start sampling after a SYNC message has been received (and before the synchronous window expires). It is now possible to register a callback function with a TPDO (with co_tpdo_set_sample_ind()) that implements the sampling process. Once the process is complete, the user calls co_tpdo_sample_res() to report the result, at which point the PDO is sent.

If the user does not register a sampling callback function, the current value of the mapped object is sent the moment the SYNC message is received.

Multiplex PDO support

Support for Multiplex PDOs (MPDOs) has been added to the Receive-PDO and Transmit-PDO services. Both destination address mode (DAM) and source address mode (SAM) MPDOs are supported.

The object dictionary supports event tracking for SAM-MPDOs. The user can call co_dev_sam_mpdo_event() to indicate that an event occurred for a specific sub-object. If that sub-object is mapped to a SAM-MPDO, the PDO will automatically be sent. The SetEvent() method in the C++ CANopen application library will invoke both co_dev_tpdo_event() and co_dev_sam_mpdo_event(), so the user does not need to know to which type of PDO a sub-object is mapped. DAM-MPDO events can be triggered with co_dam_mpdo_event() and the DamMpdoEvent() method.

The RpdoRead()/RpdoGet() and OnRpdoWrite() methods treat incoming SAM-MPDOs the same as regular PDOs. For DAM-MPDOs the originating node-ID and object (sub-)index are unknown. To respond to an incoming DAM-MPDO, use the OnWrite() callback for the local object dictionary. However, it is impossible to distinguish between an incoming DAM-MPDO and an SDO download request.

dcfgen does not yet create the object scanner and object dispatching lists used by SAM-MPDOs. Support for SAM-MPDOs will be added in a future release.

MPDO support can be disabled by specifying the --disable-mpdo to configure or by defining the LELY_NO_CO_MPDO preprocessor macro.

SDO download DCF requests

It is now possible for the user to issue a series of SDO download requests in the form of a concise DCF, either from a file or a memory region. Concise DCF SDO requests can be generated with dcfgen. They were already supported as part of the NMT configuration request sequence during the boot process, but now it is possible to use them manually from an application.

The co_csdo_dn_dcf_req() and co_dev_dn_dcf_req() function can be used to issue such requests to remote and local object dictionaries, respectively. In the C++ CANopen application library, use the WriteDcf(), SubmitWriteDcf() and AsyncWriteDcf() methods.

As part of the implementation, the co_val_read_file()/co_val_read_frbuf() and co_val_write_file()/co_val_write_fwbuf() helper functions were added to read/write a CANopen value directly to/from a file (buffer).


Utilities library:

  • Re-entrant versions of the error string functions (errc2str(), errno2str() and errnum2str()) are now available. Following the glibc convention, the names of the re-entrant versions end in _r.
  • The fiber implementations in the utilities library have been unified. Fibers can now also be migrated between threads on platforms that support it.
  • A contains() method has been added to all containers in the utilities library. This method allows the user to check if a node is present in a container.
  • The pheap_find() and pheap_remove() methods if the pairing heap now have a non-recursive implementation. This prevents potential stack overflows when using those methods with large heaps.
  • Several 16-bit Unicode string functions (str16len(), str16ncmp() and str16ncpy()) are now available in <lely/util/ustring.h>. These functions are equivalent to the corresponding standard library functions, except that they operate on strings of type char16_t instead of char.

Asynchronous I/O library:

  • Synchronous CAN channel operations no longer require a valid executor in the constructor of the CAN channel. This allows synchronous operations to be used in the absence of an event loop.
  • The ENOTSUP error from RTNETLINK is now ignored when creating a CAN controller on Linux. This enables support for SLCAN network interfaces, which do not provide CAN bus attributes.

CANopen and C++ CANopen application libraries:

  • All CANopen services now have start(), stop() and is_stopped() methods. This allows the user to explicitly disable or enable a service after it is created. To not break backward compatibility, services are automatically started after they are created.
  • SDO block transfers can now be explicitly requested in the C++ CANopen application library. The co_csdo_blk_dn_val_req() has been added to the C API to make block downloads more convenient.
  • The new co_nmt_chk_bootup() can be used to check if boot-up messages have been received for a specific node or for all mandatory nodes. This function is available even if LELY_NO_CO_NMT_BOOT is defined.

EDS/DCF tools:

  • Bare $NODEID values are now allowed in EDS/DCF files.
  • Keys with empty values are now ignored in EDS/DCF files.
  • Custom data types are now supported when using the EDS/DCF tools as a Python module. The command-line tools ignore unknown data types.
  • dcfgen no longer configures the master to read sub-objects from object 1018h on the slave that do not exist. Only sub-index 1 (Vendor-ID) is mandatory. Sub-indices 2 (Product code), 3 (Revision number) and 4 (Serial number) are all optional.


  • The Docker images used in the CI/CD pipeline are now documented in docker/
  • The C and C++ code style is now checked with clang-format in the CI/CD pipeline.
  • Python code style checks have been added using Black and Flake8.

API changes

The C++ interface of liblely-co is deprecated and will be removed in a future version. liblely-coapp now uses the C interface of liblely-co.

  • membuf_init() now takes two extra parameters that can be used to initialize the buffer with a pre-existing memory region. This change is necessary to support static memory buffers when LELY_NO_MALLOC is defined. membuf_init(buf, NULL, 0) is equivalent to the original membuf_init(buf).
  • can_buf_init() now also takes an extra parameter that can be used to initialize a CAN frame buffer with a pre-existing memory region. The size parameter is now required to be a power of 2, instead of automatically being rounded up. Additionally, the unused can_buf_create() and can_buf_destroy() functions have been removed.
  • Due to the new start() and stop() methods of CANopen services, the existing co_time_start() and co_time_stop() functions have been renamed to co_time_start_prod() and co_time_stop_prod(), respectively.


You can download the source from GitLab or the Ubuntu packages from our PPA.