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 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:
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-types-serde | Helper: Serde JSON/CBOR/MessagePack message wrappers for typed pub/sub |
rustecal-samples | Example 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
- Supported Message Types
- Typed Publisher
- Typed Subscriber
- Service Server
- Service Client
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