Skip to content
hllerdenPublic

About

realtime c++ canbus lib

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

12 Commits

Folders and files

Repository files navigation

rtcan

A header-only, real-time-conscious C++20 CAN bus library for Linux.

rtcan is designed for applications where a CAN interface has to run alongside deterministic, latency-sensitive control loops: vehicle ECUs, industrial controllers, robotics platforms, and other embedded Linux systems where "usually fast" is not good enough. It separates raw frame I/O from message decoding, moves all steady-state work onto fixed-capacity lock-free queues, and gives every lifecycle transition (startup, fault, shutdown) an explicit, race-tested state machine instead of best-effort threading.

License: MIT C++20 Platform

Why rtcan

Most CAN libraries either give you a thin SocketCAN wrapper (fast, but you build the lifecycle, threading, and codec layer yourself) or a heavyweight framework (safe, but full of allocations, exceptions, and virtual dispatch in the hot path). rtcan targets the space between: a small, auditable core with an explicit concurrency contract, built so a strict real-time consumer can opt into a compiler profile that removes exceptions, RTTI, and non-lock-free atomics at compile time — while a generic Linux application can use the same API with none of those restrictions.

  • Zero-allocation hot path. send(), update(), and the RX/TX/consumer worker loops use fixed-capacity SPSC queues; no heap allocation, no virtual dispatch, no exceptions once workers are running.
  • Explicit lifecycle, not best-effort threading. Constructed -> Open -> Starting -> Running -> Stopping -> Stopped, with Faulted reachable from any running state. A worker fault can never be silently overwritten by a later "success" state, and stop/close/recover always wait for already admitted work before touching shared resources.
  • Deadline-based periodic TX, not tick-drift scheduling: every periodic slot tracks its own absolute CLOCK_MONOTONIC deadline.
  • Compile-time custom codecs. Give BasicCanEndpoint a concrete codec type and the whole encode/decode/dispatch path is static — no runtime message registry, no std::function.
  • A strict RT compiler profile you can opt into, not one forced on every consumer: rtcan::rt_profile adds -fno-exceptions -fno-rtti -fno-threadsafe-statics and a compile-time guarantee that every hot-path atomic on the target platform is lock-free — unsupported platforms fail to compile instead of silently falling back to a lock.
  • Hardware-free testing. MockCanBus and a SocketCAN backend share the same ICanBus interface, so lifecycle, fault-injection, and concurrency tests run without a CAN interface.

Architecture

flowchart TB
    App["Application messages\n(EngineStatus, VehicleSpeed, custom types)"]
    Codec["ICanCodec / compile-time CodecT"]
    Frame["RawCanFrame\n(fixed size, trivially copyable)"]
    Bus["ICanBus"]
    Socket["SocketCanBus"]
    Mock["MockCanBus"]

    App --> Codec --> Frame --> Bus
    Bus --> Socket
    Bus --> Mock

    subgraph Endpoint["BasicCanEndpoint"]
        RX["RX worker\n(bounded poll + receive)"]
        Q["SPSC queue"]
        Consumer["Consumer worker\n(decode + callback)"]
        TX["TX worker\n(deadline scheduler + immediate queue)"]
    end

    Bus -.-> RX --> Q --> Consumer
    TX -.-> Bus
Loading

Raw I/O and message decoding run on separate threads connected by a fixed-capacity SPSC queue, so a slow application callback cannot stall frame reception, and neither RX nor TX ever blocks on the other.

Quick start

cmake -S . -B build
cmake --build build
ctest --test-dir build --output-on-failure

The default build is a portable generic profile: optimization, exceptions, RTTI, and CPU architecture choices are left to the CMake build type and the consuming application. Sanitizers are off by default.

# Package-only build: compiles no executable, only the header-only target
cmake -S . -B build-minimal \
  -DRTCAN_BUILD_TESTS=OFF -DRTCAN_BUILD_EXAMPLES=OFF -DRTCAN_BUILD_APPS=OFF
cmake --build build-minimal

# Opt-in strict real-time compiler profile
cmake -S . -B build-rt -DCMAKE_BUILD_TYPE=Release -DRTCAN_ENABLE_RT_FLAGS=ON
cmake --build build-rt

Consume as an installed package

cmake --install build --prefix /opt/rtcan
find_package(rtcan 0.3 CONFIG REQUIRED)
target_link_libraries(my_consumer PRIVATE rtcan::rtcan)

# Optional: opt into the strict RT compiler profile
target_link_libraries(my_consumer PRIVATE rtcan::rtcan rtcan::rt_profile)

Usage example

#include <rtcan/rtcan.hpp>

void on_message(const rtcan::DecodedMessage& msg, void*) {
    if (const auto* engine = std::get_if<rtcan::EngineStatus>(&msg)) {
        // engine->rpm, engine->coolant_c, engine->throttle_pct
    }
}

int main() {
    rtcan::CanEndpoint endpoint("can0", /*bus_id=*/0, on_message, nullptr);
    if (!endpoint.open()) return 1;

    const auto handle = endpoint.add_periodic(
        rtcan::DecodedMessage{rtcan::EngineStatus{.rpm = 1500.0f}},
        /*period_ns=*/20'000'000);

    const rtcan::StartResult result = endpoint.start();
    if (!result.started) return 1;

    endpoint.update(handle, rtcan::DecodedMessage{rtcan::EngineStatus{.rpm = 1800.0f}});

    const rtcan::EndpointSnapshot view = endpoint.snapshot();
    // view.endpoint_state, view.bus_health, view.tx_sent, ...

    endpoint.stop();
    endpoint.close();
}

A compile-time custom codec, hardware-free examples, and a full SocketCAN application skeleton are in examples/.

RT design boundary

rtcan guarantees its own hot path is allocation-free, lock-free, and (under the strict profile) exception/RTTI-free. It does not silently change process-wide scheduling policy: SCHED_FIFO/SCHED_OTHER, CPU affinity, mlockall, and capability configuration (CAP_SYS_NICE, CAP_IPC_LOCK) remain explicit deployment decisions, made through RtConfig/ThreadConfig or the surrounding environment.

The strict profile rejects unsupported platforms at compile time rather than falling back to a locking atomic:

target_link_libraries(my_consumer PRIVATE rtcan::rtcan rtcan::rt_profile)

Application code stays responsible for the parts that are inherently deployment-specific: CPU isolation (isolcpus, nohz_full), IRQ affinity, RT throttling policy, and — for a hard real-time claim — measuring on the actual target hardware. See Project status below.

Testing and verification

The test suite runs the same source tree through five independent profiles in CI:

Profile What it proves
Debug + AddressSanitizer/UBSan No memory errors, no undefined behavior
Release Correctness under optimization
Strict RT profile Compiles and passes with exceptions/RTTI removed; strict-atomic guard rejects non-lock-free platforms
ThreadSanitizer No data races across RX/consumer/TX worker threads and lifecycle transitions
required-vcan Real SocketCAN integration on a kernel vcan interface, no skips allowed

Concurrency-sensitive behavior — second-start ownership, fault-vs-stop races, admitted-work accounting during shutdown, worker-context stop requests — is exercised with deterministic barrier tests (atomic wait/notify handshakes), not timing-dependent sleeps.

# Full suite
ctest --test-dir build --output-on-failure

# ThreadSanitizer only (matches the CI job)
cmake -S . -B build-tsan -DRTCAN_ENABLE_SANITIZERS=OFF -DRTCAN_ENABLE_TSAN=ON
cmake --build build-tsan
setarch "$(uname -m)" -R ctest --test-dir build-tsan -L tsan --output-on-failure

Project status

rtcan has gone through several rounds of independent, adversarial concurrency review — each round re-verifying the previous round's fixes against a fresh build rather than trusting prior claims, and deliberately hunting for new races in whatever code the last round touched.

  • Generic Linux: release-candidate quality. The full sanitizer, strict RT, and concurrency test matrix passes cleanly, including ThreadSanitizer runs with no filtering.
  • Soft real-time: the architecture and lifecycle contracts described above are in place and tested; this is not yet backed by a signed, versioned release artifact from a remote CI run.
  • Hard real-time: source- and binary-level guarantees (no allocation, no exceptions/RTTI, lock-free atomics) are established and verified. Target-platform qualification — cyclictest latency distribution, page-fault-free steady state, a 24-hour soak on an authorized PREEMPT_RT host with a defined deadline budget — has not been performed and is not claimed. A generic development machine cannot stand in for that measurement, so this repository does not claim it.

Layout

include/rtcan/   Public headers (header-only library)
examples/        Standalone demo programs
apps/            End-to-end SocketCAN application skeleton
tests/           ctest suite: unit, lifecycle, concurrency, fault-injection
src/             Reserved for future non-header-only sources
cmake/           Package export/config helpers

License

MIT — see LICENSE.

About

realtime c++ canbus lib

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages