Introduction

rustecal is a safe and idiomatic Rust wrapper for Eclipse eCAL, designed for high-performance interprocess communication (IPC) in robotics, automotive, and embedded systems.

Prerequisites

Rust Toolchain

  • Rust >= 1.70
  • cargo, rustc

LLVM + libclang (required for bindgen)

PlatformInstall
Windowschoco install llvm or LLVM releases
Linuxsudo apt install llvm-dev clang

Environment Variable for Bindgen (Windows only)

$env:LIBCLANG_PATH = "C:\Program Files\LLVM\bin"

eCAL Library Installation

rustecal is built on the eCAL v6 API and is not compatible with earlier eCAL v5 releases. General instruction for the installation you can find here.

Windows

  • Install eCAL
  • Set the environment variable:
$env:ECAL_HOME = "C:\eCAL"

Expected structure:

%ECAL_HOME%/
├── include/ecal_c/
└── lib/ecal_core_c.lib

Linux

Install system-wide from source or package. Headers and libraries should be in:

  • /usr/include/ecal_c/ or /usr/local/include/ecal_c/
  • /usr/lib or /usr/local/lib

Project Structure

This workspace is organized into several purpose‑specific crates to provide a modular, maintainable API for eCAL:

CrateDescription
rustecalMeta‑crate: re‑exports core, pub/sub, and service APIs via feature flags (pubsub, service)
rustecal-coreCore lifecycle management, logging, monitoring, error handling, and shared type definitions
rustecal-pubsubTyped and untyped Publisher/Subscriber API
rustecal-serviceRPC Service server & client API
rustecal-sysLow‑level FFI bindings to the eCAL C API
rustecal-types-stringHelper: UTF‑8 string message wrapper for typed pub/sub
rustecal-types-bytesHelper: raw byte vector message wrapper
rustecal-types-protobufHelper: Protobuf message wrapper (using prost)
rustecal-samplesExample binaries demonstrating pub/sub, RPC, monitoring, and logging

Workspace Layout

your_workspace/
├── Cargo.toml              # workspace manifest
├── rustecal/               # meta‑crate
├── rustecal-core/          # core init, logging, monitoring, error, shared types
│   └── src/
│       ├── core.rs
│       ├── error.rs
│       ├── log.rs
│       ├── log_level.rs
│       ├── monitoring.rs
│       └── core_types/
│           ├── logging.rs
│           └── monitoring.rs
├── rustecal-pubsub/        # pub/sub API
├── rustecal-service/       # service RPC API
├── rustecal-sys/           # raw C bindings
├── rustecal-types-string/
├── rustecal-types-bytes/
├── rustecal-types-protobuf/
└── rustecal-samples/       # examples
    ├── pubsub/
    │   ├── hello_send/
    │   ├── hello_receive/
    │   ├── blob_send/
    │   ├── blob_receive/
    │   ├── person_send/
    │   └── person_receive/
    ├── service/
    │   ├── mirror_server/
    │   ├── mirror_client/
    │   └── mirror_client_instances/
    ├── monitoring_receive/
    └── logging_receive/

Supported Message Types

  • BytesMessage – Arbitrary binary data
  • StringMessage – UTF-8 encoded strings
  • ProtobufMessage<T> – Protobuf messages (via prost)

Each type is provided via a dedicated crate to avoid pulling unnecessary dependencies.

Examples

Code samples for using rustecal.

Publish / Subscribe

Service Client / Server

Monitoring

Logging

Binary Message Example

Publisher

use rustecal::{Ecal, EcalComponents, TypedPublisher};
use rustecal_types_bytes::BytesMessage;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    Ecal::initialize(Some("blob publisher"), EcalComponents::DEFAULT)?;

    let pub_ = TypedPublisher::<BytesMessage>::new("blob")?;

    let mut counter = 0u8;
    while Ecal::ok() {
        let buf = vec![counter; 1024];
        pub_.send(&BytesMessage(buf));

        counter = counter.wrapping_add(1);
        std::thread::sleep(std::time::Duration::from_millis(500));
    }

    Ecal::finalize();
    Ok(())
}

Subscriber

use rustecal::{Ecal, EcalComponents, TypedSubscriber};
use rustecal_types_bytes::BytesMessage;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    Ecal::initialize(Some("blob subscriber"), EcalComponents::DEFAULT)?;

    let mut sub = TypedSubscriber::<BytesMessage>::new("blob")?;
    sub.set_callback(|msg| {
        println!("Received blob of {} bytes", msg.msg.0.len());
    });

    while Ecal::ok() {
        std::thread::sleep(std::time::Duration::from_millis(500));
    }

    Ecal::finalize();
    Ok(())
}

String Message Example

Publisher

use rustecal::{Ecal, EcalComponents, TypedPublisher};
use rustecal_types_string::StringMessage;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    Ecal::initialize(Some("string publisher"), EcalComponents::DEFAULT)?;

    let publisher = TypedPublisher::<StringMessage>::new("hello")?;

    while Ecal::ok() {
        let msg = StringMessage(format!("Hello from Rust"));
        publisher.send(&msg);

        std::thread::sleep(std::time::Duration::from_millis(500));
    }

    Ecal::finalize();
    Ok(())
}

Subscriber

use rustecal::{Ecal, EcalComponents, TypedSubscriber};
use rustecal_types_string::StringMessage;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    Ecal::initialize(Some("string subscriber"), EcalComponents::DEFAULT)?;

    let mut sub = TypedSubscriber::<StringMessage>::new("hello")?;
    sub.set_callback(|msg| println!("Received: {}", msg.msg.0));

    while Ecal::ok() {
        std::thread::sleep(std::time::Duration::from_millis(500));
    }

    Ecal::finalize();
    Ok(())
}

Protobuf Message Example

Publisher

use rustecal::{Ecal, EcalComponents, TypedPublisher};
use rustecal_types_protobuf::{ProtobufMessage, IsProtobufType};

mod people      { include!(concat!(env!("OUT_DIR"), "/pb.people.rs")); }
mod animal      { include!(concat!(env!("OUT_DIR"), "/pb.animal.rs")); }
mod environment { include!(concat!(env!("OUT_DIR"), "/pb.environment.rs")); }

use people::Person;
impl IsProtobufType for Person {}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    Ecal::initialize(Some("protobuf publisher"), EcalComponents::DEFAULT)?;

    let pub_ = TypedPublisher::<ProtobufMessage<Person>>::new("person")?;

    while Ecal::ok() {
        let person = Person { id: 1, name: "Alice".into(), ..Default::default() };
        pub_.send(&ProtobufMessage(person));

        std::thread::sleep(std::time::Duration::from_millis(500));
    }

    Ecal::finalize();
    Ok(())
}

Subscriber

use rustecal::{Ecal, EcalComponents, TypedSubscriber};
use rustecal_types_protobuf::{ProtobufMessage, IsProtobufType};

mod people      { include!(concat!(env!("OUT_DIR"), "/pb.people.rs")); }
mod animal      { include!(concat!(env!("OUT_DIR"), "/pb.animal.rs")); }
mod environment { include!(concat!(env!("OUT_DIR"), "/pb.environment.rs")); }

use people::Person;
impl IsProtobufType for Person {}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    Ecal::initialize(Some("protobuf subscriber"), EcalComponents::DEFAULT)?;

    let mut sub = TypedSubscriber::<ProtobufMessage<Person>>::new("person")?;
    sub.set_callback(|msg| println!("Received person: {}", msg.msg.0.name));

    while Ecal::ok() {
        std::thread::sleep(std::time::Duration::from_millis(500));
    }

    Ecal::finalize();
    Ok(())
}

Service Server Example

This example shows how to implement a basic Mirror Service Server using rustecal.

The server receives a request, logs it, and sends it back as a response.

Example Code

use rustecal::{Ecal, EcalComponents, ServiceServer};
use rustecal::service::types::MethodInfo;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    Ecal::initialize(Some("mirror_server"), EcalComponents::DEFAULT)?;

    let mut server = ServiceServer::new("mirror_service")?;

    server.add_method("mirror", Box::new(|method: MethodInfo, req: &[u8]| {
        let request_str = String::from_utf8_lossy(req);
        println!("Received [{}] request: {}", method.method_name, request_str);

        // Echo (mirror) the same bytes back
        req.to_vec()
    }))?;

    println!("mirror_service is running…");

    while Ecal::ok() {
        std::thread::sleep(std::time::Duration::from_millis(500));
    }

    Ecal::finalize();
    Ok(())
}

Service Client Example

This example demonstrates how to call a Mirror Service using rustecal.

Example Code

use rustecal::{Ecal, EcalComponents, ServiceClient, ServiceRequest};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    Ecal::initialize(Some("mirror_client"), EcalComponents::DEFAULT)?;
    println!("mirror_client initialized. Sending requests…");

    let client = ServiceClient::new("mirror_service")?;

    let request_data = b"Hello, Service!";
    let timeout = Some(500);

    while Ecal::ok() {
        let request = ServiceRequest {
            payload: request_data.to_vec(),
        };

        // Call the "mirror" method
        if let Some(response) = client.call("mirror", request, timeout) {
            // Extract the echoed payload
            let echoed = String::from_utf8_lossy(&response.payload);
            println!("Received response: {}", echoed);
        } else {
            println!("Service call timed out.");
        }

        std::thread::sleep(std::time::Duration::from_millis(500));
    }

    Ecal::finalize();
    Ok(())
}

Monitoring Snapshot

This example demonstrates how to continuously poll the eCAL runtime for a monitoring snapshot using the Monitoring::get_snapshot() API.

use rustecal::{Ecal, EcalComponents};
use rustecal_core::monitoring::Monitoring;
use std::{thread, time::Duration};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize only the monitoring subsystem
    Ecal::initialize(Some("monitoring_receive_sample"), EcalComponents::MONITORING)?;
    println!("eCAL initialized. Entering monitoring loop…");

    while Ecal::ok() {
        let snap = Monitoring::get_snapshot()?;

        println!("=== Monitoring Snapshot ===\n");
        println!("Processes:\n{:#?}", snap.processes);
        println!("\nPublishers:\n{:#?}", snap.publishers);
        println!("\nSubscribers:\n{:#?}", snap.subscribers);
        println!("\nServers:\n{:#?}", snap.servers);
        println!("\nClients:\n{:#?}", snap.clients);

        thread::sleep(Duration::from_secs(1));
    }

    Ecal::finalize();
    Ok(())
}

Logging Snapshot

This example demonstrates how to continuously poll the eCAL runtime for log messages using the Log::get_logging() API.

use rustecal::{Ecal, EcalComponents};
use rustecal_core::log::Log;
use std::{thread, time::Duration};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize only the logging subsystem
    Ecal::initialize(Some("logging_receive_sample"), EcalComponents::LOGGING)?;
    println!("eCAL initialized. Entering logging loop…");

    while Ecal::ok() {
        let entries = Log::get_logging()?;
        println!("=== Logging Snapshot ===\n");
        println!("Entries:\n{:#?}", entries);

        thread::sleep(Duration::from_secs(1));
    }

    Ecal::finalize();
    Ok(())
}

API Documentation

Welcome to the rustecal API documentation. This section provides an overview of the main types and traits used to interact with the eCAL communication system through safe and idiomatic Rust APIs.

Modules Overview

Each module has its own documentation page with examples.

Usage Highlights

#![allow(unused)]
fn main() {
let _ecal = Ecal::initialize("my_app")?;
let pub = Publisher::<StringMessage>::builder("my_topic").create()?;
pub.send("Hello from Rust!")?;
}

Explore the individual components in detail:

Ecal Lifecycle

The Ecal struct manages initialization and finalization of the eCAL system.

Example

use rustecal::Ecal;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    Ecal::initialize(Some("my ecal app"), EcalComponents::DEFAULT)?;

    // use publishers, subscribers, clients, server

    Ecal::finalize();
    Ok(())
}

Typed Publisher

The Publisher<T> allows you to publish messages of type T on a topic.

Example

#![allow(unused)]
fn main() {
use rustecal::pubsub::Publisher;
use rustecal::types::StringMessage;

let pub = Publisher::<StringMessage>::builder("my_topic").create()?;
pub.send("Rust rocks!")?;
}

Typed Subscriber

The Subscriber<T> enables you to subscribe to messages of type T on a topic.

Example

#![allow(unused)]
fn main() {
use rustecal::pubsub::Subscriber;
use rustecal::types::StringMessage;

let sub = Subscriber::<StringMessage>::builder("my_topic").create()?;
sub.set_callback(|msg| {
    println!("Received: {}", msg.data());
});
}

Supported Message Types

rustecal supports message types through wrapper structs:

StringMessage

Used for UTF-8 string topics.

BytesMessage

Used for binary Vec<u8> payloads.

ProtobufMessage<T>

Supports publishing/receiving of Protobuf types that implement Message and Default.

#![allow(unused)]
fn main() {
use rustecal::types::ProtobufMessage;
use my_proto::MyProto;

let pub = Publisher::<ProtobufMessage<MyProto>>::builder("proto_topic").create()?;
}

Service Server

The ServiceServer API allows Rust applications to act as eCAL service providers using a simple, callback-based interface that mirrors the C++ and C APIs.

Registering Methods

To provide services, create a ServiceServer and register one or more methods by name:

#![allow(unused)]
fn main() {
use rustecal::service::server::ServiceServer;
use rustecal::service::types::MethodInfo;

let mut server = ServiceServer::new("mirror")?;

server.add_method("echo", Box::new(|_info: MethodInfo, request: &[u8]| {
    request.to_vec()
}))?;

server.add_method("reverse", Box::new(|_info, request| {
    let mut reversed = request.to_vec();
    reversed.reverse();
    reversed
}))?;
}

Method Signatures

The callback signature follows:

#![allow(unused)]
fn main() {
Fn(MethodInfo, &[u8]) -> Vec<u8>
}

This is safe, allocation-free on the input side, and flexible for any binary or textual payloads.

Example Output

Method   : 'echo' called
Request  : stressed
Response : stressed

Method   : 'reverse' called
Request  : stressed
Response : desserts

Runtime Compatibility

This API is fully compatible with the C++ mirror_server.cpp and C mirror_server.c examples.

Service Client

The ServiceClient API allows a Rust application to call eCAL services, either generically or per-instance.

Connecting to a Service

#![allow(unused)]
fn main() {
use rustecal::service::client::ServiceClient;

let client = ServiceClient::new("mirror")?;
}

Calling Methods

#![allow(unused)]
fn main() {
use rustecal::service::types::ServiceRequest;

let request = ServiceRequest {
    payload: b"stressed".to_vec(),
};

let response = client.call("echo", request, Some(1000));
}

To call all connected instances:

#![allow(unused)]
fn main() {
for instance in client.get_client_instances() {
    let response = instance.call("reverse", request.clone(), Some(1000));
}
}

Return Handling

#![allow(unused)]
fn main() {
match response {
    Some(res) if res.success => {
        println!("Response: {}", String::from_utf8_lossy(&res.payload));
    }
    Some(res) => {
        println!("Error: {}", res.error_msg.unwrap_or("Unknown error".into()));
    }
    None => {
        println!("No response or timeout.");
    }
}
}

Runtime Compatibility

This API matches the usage and behavior of mirror_client.cpp in the eCAL C++ samples.

Roadmap

  • Cross-platform support (Windows, Linux)
  • Safe API for initialization, shutdown
  • Binary publish/subscribe API
  • Typed publish/subscribe API
  • Modular type crates (string, bytes, protobuf)
  • Binary server/client API
  • Examples for all publish/subscribe and client/server
  • Monitoring and logging support
  • Protobuf descriptor introspection

About

Created by Rex Schilasky
🚗 Automotive | 🧠 SDV | 🛠️ Rust | 🚀 IPC

License

Licensed under the Apache License 2.0