Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 build-essential llvm-dev clang libclang-dev

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 instructions for installation can be found 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-types-serdeHelper: Serde JSON/CBOR/MessagePack message wrappers for typed pub/sub
rustecal-samplesExample binaries demonstrating pub/sub, RPC, monitoring, and logging

Supported Message Types

  • BytesMessage – Arbitrary binary data (rustecal-types-bytes)
  • StringMessage – UTF-8 encoded strings (rustecal-types-string)
  • ProtobufMessage<T> – Protobuf messages (rustecal-types-protobuf)
  • JsonMessage<T> – JSON-serialized Serde types (rustecal-types-serde)
  • CborMessage<T> – CBOR-serialized Serde types (rustecal-types-serde)
  • MsgpackMessage<T> – MessagePack-serialized Serde types (rustecal-types-serde)

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::pubsub::publisher::Timestamp;
use rustecal_types_bytes::BytesMessage;

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

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

    let mut counter = 0u8;
    while Ecal::ok() {
        let buf = vec![counter; 1024];
        counter = counter.wrapping_add(1);

        let message = BytesMessage { data: buf.into() };
        publisher.send(&message, Timestamp::Auto);

        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, None)?;

    let mut subscriber = TypedSubscriber::<BytesMessage>::new("blob")?;
    subscriber.set_callback(|message| {
        println!("Received blob of {} bytes", message.payload.data.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::pubsub::publisher::Timestamp;
use rustecal_types_string::StringMessage;

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

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

    while Ecal::ok() {
        let message = StringMessage { data: "Hello from Rust".into() };
        publisher.send(&message, Timestamp::Auto);

        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, None)?;

    let mut subscriber = TypedSubscriber::<StringMessage>::new("hello")?;
    subscriber.set_callback(|message| {
        println!("Received: {}", message.payload.data)
    });

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

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

Protobuf Message Example

Publisher

use std::sync::Arc;
use rustecal::{Ecal, EcalComponents, TypedPublisher};
use rustecal::pubsub::publisher::Timestamp;
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, None)?;

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

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

        let message = ProtobufMessage { data : Arc::from(person) };
        publisher.send(&message, Timestamp::Auto);

        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, None)?;

    let mut subscriber = TypedSubscriber::<ProtobufMessage<Person>>::new("person")?;
    subscriber.set_callback(|message| {
        println!("Received person: {}", message.payload.data.name)
    });

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

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

JSON Message Example

Publisher

use serde::{Serialize, Deserialize};
use rustecal::{Ecal, EcalComponents, TypedPublisher};
use rustecal::pubsub::publisher::Timestamp;
use rustecal_types_serde::JsonMessage;

#[derive(Serialize, Deserialize, Clone, Debug)]
struct MyData {
    msg: String,
}

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

    let publisher = TypedPublisher::<JsonMessage<MyData>>::new("hello_json")?;

    while Ecal::ok() {
        let payload = MyData { msg: "Hello from Rust".into() };
        let message = JsonMessage::new(payload);
        publisher.send(&message, Timestamp::Auto);

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

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

Subscriber

use serde::{Serialize, Deserialize};
use rustecal::{Ecal, EcalComponents, TypedSubscriber};
use rustecal_types_serde::JsonMessage;

#[derive(Serialize, Deserialize, Clone, Debug)]
struct MyData {
    msg: String,
}

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

    let mut subscriber = TypedSubscriber::<JsonMessage<MyData>>::new("hello_json")?;
    subscriber.set_callback(|message| {
        println!("Received: {}", message.payload.data.msg);
    });

    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, None)?;

    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, None)?;

    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, None)?;

    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, None)?;

    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.

Explore the individual components in detail:

Ecal Lifecycle

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

Example

use rustecal::{Ecal, EcalComponents};

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

    // 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::{Ecal, EcalComponents, TypedPublisher};
use rustecal::pubsub::publisher::Timestamp;
use rustecal_types_string::StringMessage;

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

let message = StringMessage { data: "Hello from Rust".into() }
publisher.send(&message, Timestamp::Auto);
}

Typed Subscriber

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

Example

#![allow(unused)]
fn main() {
use rustecal::{Ecal, EcalComponents, TypedSubscriber};
use rustecal_types_string::StringMessage;

let mut subscriber = TypedSubscriber::<StringMessage>::new("hello")?;
subscriber.set_callback(|message| {
    println!("Received: {}", message.payload.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_protobuf::{ProtobufMessage, IsProtobufType};

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

let publisher = TypedPublisher::<ProtobufMessage<Person>>::new("person").unwrap();
}

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, the C mirror_server_c.c and the C# mirror_client_csharp.cs example.

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(),
};
}

To broadcast call all connected instances:

#![allow(unused)]
fn main() {
let responses = client.call("echo", request, Some(1000));
}

To call (and filter) all connected instances separately:

#![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 is fully compatible with the C++ mirror_client.cpp, the C mirror_client_c.c and the C# mirror_client_csharp.cs example.

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, serde (json/cbor/msgpack))
  • 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