Library overview
C11 and POSIX compatibility library
The C11 and POSIX compatibility library (liblely-libc) makes a selection of C11 features available in C99 and implements certain C99 and POSIX features that may be missing on some platforms. The library is intended to provide platform independence to C99- and C11-compliant users.
The library provides:
- compiler feature definitions:
<lely/features.h>
- atomics:
<lely/libc/stdatomic.h>
max_align_t
:<lely/libc/stddef.h>
SSIZE_MAX
:<lely/libc/stdint.h>
getdelim()
,getline()
,asprintf()
andvasprintf()
:<lely/libc/stdio.h>
aligned_alloc()
,aligned_free()
andsetenv()
:<lely/libc/stdlib.h>
strdup()
,strndup()
andstrnlen()
:<lely/libc/string.h>
ffs()
,strcasecmp()
andstrncasecmp()
:<lely/libc/strings.h>
clockid_t
,ssize_t
:<lely/libc/sys/types.h>
- threads:
<lely/libc/threads.h>
struct timespec
,nanosleep()
,timespec_get()
and POSIX Realtime Extensions:<lely/libc/time.h>
char16_t
andchar32_t
:<lely/libc/uchar.h>
getopt()
andsleep()
:<lely/libc/unistd.h>
Additionally the following C++ definitions have been backported to C++11 (and
are available in the lely::compat
namespace):
std::clock_cast()
from C++20:<lely/libc/chrono.hpp>
std::invoke()
from C++17:<lely/libc/functional.hpp>
std::bool_constant
,std::invoke_result<>
,std::invoke_result_t<>
,std::is_invocable<>
,std::is_invocable_r<>
andstd::void_t<>
from C++17, andstd::remove_cvref<>
from C++20:<lely/libc/type_traits.hpp>
std::index_sequence<>
,std::index_sequence_for<>
,std::integer_sequence<>
,std::make_index_sequence<>
andstd::make_integer_sequence<>
from C++14:<lely/libc/utility.hpp>
Since the liblely-libc library now contains C++ features, it may be renamed to liblely-compat in the future.
Test Anything Protocol (TAP) library
The TAP library (liblely-tap) provides an implementation of the Test Anything Protocol.
The TAP library is used by all tests in the test suite (which can be run with
make check
).
Utilities library
The utilities library (liblely-util) provides a variety of functions and data structures used by, and shared between, other Lely core libraries.
Data structures
The utilities library implements the following data structures:
- bidirectional map:
<lely/util/bimap.h>
- doubly linked list:
<lely/util/dllist.h>
- pairing heap:
<lely/util/pheap.h>
- red-black tree:
<lely/util/rbtree.h>
- singly linked list:
<lely/util/sllist.h>
All implementations are intrusive. The user must embed a node in the struct containing the value to be stored. This is generally more performant than a non-intrusive implementation. Moreover, it is trivial to build a non-intrusive version on top of the intrusive implementation if necessary.
System support
Functions to keep track of platform-dependent and platform-independent error
numbers can be found in <lely/util/errnum.h>
, as well as functions to convert
error numbers to human-readable strings. Convenience functions to create
std::error_code
error codes or throw std::system_error
exceptions can be
found in <lely/util/error.hpp>
and <lely/util/exception.hpp>
.
Diagnostic messages can be generated with the functions in <lely/util/diag.h>
.
These functions support user-defined message handlers and several handlers are
predefined for printing diagnostic messages to screen, log files or Windows
dialog boxes.
On POSIX and Windows platforms, functions are provided to run a process in the
background as a daemon/service (see <lely/util/daemon.h>
). Note that this
functionality can be disabled when liblely-util is
built.
The byte order of integers and floating-point values is platform specific.
Functions to convert between native, big-endian and little-endian, and network
byte order can be found in <lely/util/endian.h>
.
Definitions for IEEE 754
floating-point format types are provided in <lely/util/float.h>
.
File and memory management
Accessing files is generally most convenient using memory maps. I/O performance
is improved and reading/writing is simplified, since the user can rely on
pointer manipulation instead of repeated calls to read()
/write()
and
seek()
. Two file buffers are provided, one for reading (<lely/util/frbuf.h>
)
and one for writing (<lely/util/fwbuf.h>
). Both are implemented using native
memory maps, but provide a standard C fallback implementation if necessary. The
write buffer supports atomic access: the final file is only created (or
replaced) if all operations completed successfully.
A lightweight in-memory buffer with a similar API is provided in
<lely/util/membuf.h>
.
A generic, lock-free, single-producer, single-consumer ring buffer is provided
in <lely/util/spscring.h>
.
Text handling
According to the Unix philosophy, text streams are the universal interface. To
ease the implementation of parsers, several lexing functions are provided in
<lely/util/lex.h>
. Corresponding printing functions can be found in
<lely/util/print.h>
.
These functions are used to implement the INI parser and printer of the
configuration struct (<lely/util/config.h>
), a simple string-based key-value
store with sections.
The configuration struct is used to parse EDS/DCF files in the CANopen library.
Coroutines
Stackless coroutines are provided by <lely/util/coroutine.h>
(and
<lely/util/coroutine.hpp>
).
Stackfull coroutines, or
fibers, are
provided by <lely/util/fiber.h>
(and <lely/util/fiber.hpp>
). On Windows, the
implementation uses native
fibers. On
other platforms, the implementation is based on
setjmp()
and longjmp()
. Calling
environments with a separate step can be created with mkjmp()
from
<lely/util/mkjmp.h>
.
Other
The ubiquitous ABS()
, MIN()
, MAX()
and ALIGN()
macros are provided by
<lely/util/util.h>
. This header also contains a powerof2()
macro, and the
countof()
and structof()
macros (similar to ARRAY_SIZE()
and
container_of()
in the Linux kernel).
Bit counting and manipulation functions are provided in <lely/util/bits.h>
and
<lely/util/bitset.h>
.
Comparison functions for integers, pointers and strings can be found in
<lely/util/cmp.h>
.
<lely/util/invoker.hpp>
defines make_invoker()
, which can be used to create
function objects containing a
Callable and its
arguments.
This is used by the event library to wrap function calls in asynchronous tasks.
An abstract class providing the C++
BasicLockable
interface is defined in <lely/util/mutex.hpp>
. This header also contains
UnlockGuard
, a class similar to
std::lock_guard
, but
used to ensure a mutex is unlocked for the duration of a scoped block.
A generic type that can represent both the result of a successful operation or
the reason for failure is provided in <lely/util/result.hpp>
.
This type is used to hold the result of futures in the event library.
Stop tokens, which provide a generic mechanism for cancelling (sequences of)
tasks, are provided by <lely/util/stop.h>
and <lely/util/stop.hpp>
. The API
is based on
<stop_token>
in C++20.
Convenience functions to compare and manipulate timespec
structs can be found
in <lely/util/time.h>
.
16-bit Unicode string functions are provided in <lely/util/string.h>
. These
functions are equivalent to the corresponding standard library functions, except
that they operate on strings of type char16_t
instead of char
.
Event library
The event library (liblely-ev) provides an event loop and asynchronous futures and promises. It is based on the asynchronous model from Boost.Asio and the proposed C++ Extensions for Networking.
New I/O library
The I/O library (liblely-io2) provides asynchronous I/O.
Note: Currently only timers, signal handlers and CAN devices are supported. Once it reaches feature parity with the old I/O library (liblely-io), the old library will be removed. The new library may be renamed to liblely-io in the future.
CANopen library
The CANopen library (liblely-co) is a CANopen implementation for both masters and slaves. Most of the functionality of CiA 301, 302, 305, 306, 309 and 315 is provided.
Note: The CANopen library provides a C++ interface, but it is deprecated. Please use the C++ CANopen application library (liblely-coapp) when writing CANopen applications in C++.
Contrary to most other CANopen stacks, this implementation is completely passive; the library does not perform any I/O (besides maybe reading some files from disk), it does not create threads nor does it access the system clock. Instead, it relies on the user to send and receive CAN frames and update the clock. This allows the library to be easily embedded in a wide variety of applications.
The library is also asynchronous. Issuing a request is always a non-blocking operation. If the request is confirmed, the API accepts a callback function which is invoked once the request completes (with success or failure). This allows the stack to run in a single thread, even when processing dozens of simultaneous requests (not uncommon for an NMT master).
CAN network object
The interface between the CANopen stack and the CAN bus (and system clock) is
provided by the CAN network object (can_net_t
) from the CAN library
(liblely-can). When the CANopen stack needs to send a CAN frame, it hands it
over to a CAN network object, which in turn invokes a user-defined callback
function to write the frame to the bus. Similarly, when a user reads a CAN frame
from the bus, he gives it to a CAN network object, which in turn distributes it
to the registered receivers in the CANopen stack. Additionally, the user
periodically checks the current time and tells the CAN network object, which
then executes the actions for timers that have elapsed.
Object dictionary
The object dictionary is the central concept of the CANopen protocol. It contains almost the entire state of a CANopen device, including process data and communication parameters. Communication between nodes consists primarily of reading from and writing to each others object dictionary. Even writing CANopen applications is mostly a matter of configuring the object dictionary.
Together with the node-ID, the object dictionary is managed by a CANopen device
object (co_dev_
, defined in <lely/co/dev.h>
). Although it is possible to
construct the object dictionary from scratch in C, it is more convenient to read
it from an Electronic Data Sheet (EDS) or Device Configuration File (DCF) (see
CiA 306). Functions to parse EDS/DCF files can be found in <lely/co/dcf.h>
.
Embedded devices often do not have the resources to parse an EDS/DCF file at
runtime. Using the DCF-to-C tool it is
possible to create a C file containing a static device descriptipn, which can be
compiled with the application. The static object dictionary can then be
converted to a dynamic one at runtime (see <lely/co/sdev.h>
).
Service objects
The CANopen stack implements the following services:
- Process data object (PDO), including Multiplex PDO (MPDO)
- Receive-PDO (RPDO): (
co_rpdo_t
, defined in<lely/co/rpdo.h>
) - Transmit-PDO (TPDO): (
co_tpdo_t
, defined in<lely/co/tpdo.h>
)
- Receive-PDO (RPDO): (
- Service data object (SDO)
- Server-SDO (CSDO): (
co_ssdo_t
, defined in<lely/co/ssdo.h>
) - Client-SDO (CSDO): (
co_csdo_t
, defined in<lely/co/csdo.h>
)
- Server-SDO (CSDO): (
- Synchronization object (SYNC): (
co_sync_t
, defined in<lely/co/sync.h>
) - Time stamp object (TIME): (
co_time_t
, defined in<lely/co/time.h>
) - Emergency object (EMCY): (
co_emcy_t
, defined in<lely/co/emcy.h>
) - Network management (NMT): (
co_nmt_t
, defined in<lely/co/nmt.h>
) - Layer setting services (LSS): (
co_lss_t
, defined in<lely/co/lss.h>
)
While it is possible to create these services by hand, it is almost always more convenient to let them be managed by a single Network Management (NMT) service object. An NMT object manages the state of a CANopen device and creates and destroys the other services as needed.
Like all CANopen services, the NMT service gets its configuration from the object dictionary. A typical CANopen application therefore consists of
- creating the CAN network object;
- loading the object dictionary form an EDS/DCF file;
- creating an NMT service;
- and, finally, processing CAN frames in an event loop.
Note, however, that a newly created NMT service starts out in the ‘Initialisation’ state and does not create any services or perform any communication. This allows the application to register callback functions before the node becomes operational. The NMT service, including the entire boot-up sequence, can be started by giving it the ‘reset node’ command.
CAN frame tunneling
Sometimes a CANopen application runs on a device which does not have direct
access to a CAN bus. CiA 315 defines a protocol for tunneling CAN frames over
wired or Wireless Transmission Media (WTM). The interface for this protocol can
be found in <lely/co/wtm.h>
. Additionally, the
CAN-to-UDP tool can be run as a daemon
or service to act as a proxy between a CAN bus and UDP.
C++ CANopen application library
The C++ CANopen application library (liblely-coapp) provides a high-level application interface in C++ for liblely-co. It is intended to simplify the development of CANopen master applications (and, to a lesser extent, slaves) by providing a driver model and combining it with an event loop (from liblely-ev).
CAN library
The CAN library (lely-can provides) generic CAN frame definitions, conversion functions for different drivers and an interface designed to simplify CAN frame handling.
The generic CAN message type is struct can_msg
(defined in
<lely/can/msg.h>
). This struct can represent both CAN and CAN FD frames (if
CAN FD support is enabled). The can_msg
struct does
not correspond to any specific driver or interface. Conversion functions to and
from IXXAT VCI and SocketCAN can be found in <lely/can/vci.h>
and
<lely/can/socket.h>
, respectively.
The CAN bus is a broadcast bus, so each node receives all messages. Applications
typically only listen to messages with a specific CAN identifier. The CAN
network interface (can_net_t
, defined in <lely/can/net.h>
) assists the user
with processing CAN frames by allowing callback functions to be registered for
individual CAN identifiers. Additionally, callback functions can be registered
to be invoked at specific times. The CAN network interface is passive, relying
on the application to feed it CAN frames or notify it of the current time. A C++
interface can be found in <lely/can/net.hpp>
.
When processing CAN frames it is often necessary to buffer incoming or outgoing
messages. A thread-safe, lock-free circular buffer is provided in
<lely/can/buf.h>
(and <lely/can/buf.hpp>
).
Old I/O library (deprecated)
Note: The old I/O library is deprecated and should not be used in new projects. It will be removed once the new I/O library (liblely-io2) reaches feature parity.
The I/O library (liblely-io) provides a uniform, platform-independent I/O interface for CAN devices, regular files, serial I/O devices and network sockets. Depending on the platform, some or all of these devices can be polled for events, allowing the use of the reactor pattern.
All I/O devices are represented by the same handle (io_handle_t
, defined in
<lely/io/io.h>
), providing a uniform interface. This handle is a
reference-counted wrapper around a platform-specific file descriptor or handle.
A C++ interface for I/O device handles can be found in <lely/io/io.hpp>
.
The following I/O devices are supported:
- CAN devices (
<lely/io/can.h>
and `<lely/io/can.hpp>) - regular files (
<lely/io/file.h> and
<lely/io/file.hpp>`) - pipes (
<lely/io/pipe.h>
and<lely/io/pipe.hpp>
) - serial I/O devices (
<lely/io/serial.h>
and<lely/io/serial.hpp>
) - network sockets (
<lely/io/sock.h>
and<lely/io/sock.hpp>
)
Additionally, serial I/O device attributes can be manipulated with the functions
in <lely/io/attr.h>
, while network addresses and interfaces can be queried and
manipulated with the functions in <lely/io/addr.h> and
<lely/io/if.h>`,
respectively.
Depending on the platform, some or all of the I/O devices can be polled for
events. Combined with non-blocking access, this allows an application to use the
reactor pattern to concurrently handle multiple I/O channels. The polling
interface is provided by <lely/io/poll.h>
(and <lely/io/poll.hpp>
).