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
)
Platform | Install |
---|---|
Windows | choco install llvm or LLVM releases |
Linux | sudo 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:
Crate | Description |
---|---|
rustecal | Meta‑crate: re‑exports core, pub/sub, and service APIs via feature flags (pubsub , service ) |
rustecal-core | Core lifecycle management, logging, monitoring, error handling, and shared type definitions |
rustecal-pubsub | Typed and untyped Publisher/Subscriber API |
rustecal-service | RPC Service server & client API |
rustecal-sys | Low‑level FFI bindings to the eCAL C API |
rustecal-types-string | Helper: UTF‑8 string message wrapper for typed pub/sub |
rustecal-types-bytes | Helper: raw byte vector message wrapper |
rustecal-types-protobuf | Helper: Protobuf message wrapper (using prost ) |
rustecal-samples | Example 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 dataStringMessage
– UTF-8 encoded stringsProtobufMessage<T>
– Protobuf messages (viaprost
)
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
Ecal
— Lifecycle manager for eCAL initialization and finalization.Publisher<T>
— Generic typed publisher used to send messages on a topic.Subscriber<T>
— Generic typed subscriber used to receive messages.MessageType
— Trait for enabling custom serialization of types.- Message Wrappers:
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