Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ members = [
"rustecal-samples/pubsub/hello_send",
"rustecal-samples/pubsub/hello_receive",
"rustecal-samples/pubsub/person_send",
"rustecal-samples/pubsub/person_receive"]
"rustecal-samples/pubsub/person_receive",
"rustecal-samples/service/mirror_client",
"rustecal-samples/service/mirror_client_instances",
"rustecal-samples/service/mirror_server"
]
123 changes: 80 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,80 +8,117 @@ Safe and idiomatic Rust bindings for the [eCAL](https://github.com/eclipse-ecal/

## Features

- 📡 High-performance publish/subscribe middleware (based on eCAL)
- 🦀 Idiomatic Rust API over eCAL C-API
- 💬 Type-safe messaging for:
- `StringMessage`
- `BytesMessage`
- `ProtobufMessage<T>` (based on `prost`)
- 🧪 Works on Linux and Windows (via `bindgen` + `cc`)
- 📖 Modular message support via `rustecal-types-*` crates
- Idiomatic Rust interface to the eCAL C API
- Zero-copy shared memory transport
- Type-safe publish/subscribe and service communication
- Modular type support: String, Binary, Protobuf
- Fully runtime-compatible with C++ eCAL systems

---

## Usage
## Examples

Add the following to your `Cargo.toml`:

```toml
[dependencies]
rustecal = { path = "path/to/rustecal" }
rustecal-types-string = { path = "path/to/rustecal-types-string" }
```

Example (sending a string message):
### Publisher

```rust
use rustecal::{Ecal, EcalComponents};
use rustecal::pubsub::Publisher;
use rustecal::types::StringMessage;

let _ecal = rustecal::Ecal::initialize("hello_send")?;
fn main() -> Result<(), Box<dyn std::error::Error>> {
Ecal::initialize(Some("rust publisher"), EcalComponents::DEFAULT)?;
let mut pub = Publisher::<String>::new("chatter")?;

let publisher = Publisher::<StringMessage>::builder("hello_topic").create()?;
publisher.send("Hello from Rust!")?;
loop {
pub.send("Hello from Rust!")?;
std::thread::sleep(std::time::Duration::from_millis(500));
}
}
```

Example (receiving a message):
---

### Subscriber

```rust
use rustecal::{Ecal, EcalComponents};
use rustecal::pubsub::Subscriber;
use rustecal::types::StringMessage;

let _ecal = rustecal::Ecal::initialize("hello_receive")?;
fn main() -> Result<(), Box<dyn std::error::Error>> {
Ecal::initialize(Some("rust subscriber"), EcalComponents::DEFAULT)?;
let sub = Subscriber::<String>::new("chatter")?;

sub.set_callback(|msg| {
println!("Received: {}", msg.payload);
})?;

let subscriber = Subscriber::<StringMessage>::builder("hello_topic").create()?;
subscriber.set_callback(|msg| {
println!("Received: {}", msg.data());
});
loop {
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
```

---

## Crate Structure
### Service Server

- `rustecal`: core eCAL bindings and idiomatic API
- `rustecal-sys`: low-level `bindgen` generated FFI bindings
- `rustecal-types-string`, `rustecal-types-bytes`, `rustecal-types-protobuf`: message wrapper crates
```rust
use rustecal::{Ecal, EcalComponents};
use rustecal::service::server::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")?;

server.add_method("reverse", Box::new(|_info: MethodInfo, req: &[u8]| {
let mut reversed = req.to_vec();
reversed.reverse();
reversed
}))?;

loop {
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
```

---

## Documentation

📚 Full user guide: [https://rex-schilasky.github.io/rustecal](https://rex-schilasky.github.io/rustecal)
### Service Client

```bash
cd docs/
mdbook serve
```rust
use rustecal::{Ecal, EcalComponents};
use rustecal::service::client::ServiceClient;
use rustecal::service::types::ServiceRequest;

fn main() -> Result<(), Box<dyn std::error::Error>> {
Ecal::initialize(Some("mirror client"), EcalComponents::DEFAULT)?;
let client = ServiceClient::new("mirror")?;

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

if let Some(response) = client.call("reverse", request, Some(1000)) {
println!("Reversed: {}", String::from_utf8_lossy(&response.payload));
} else {
println!("No response received.");
}

Ok(())
}
```

---

## License
## Documentation

Licensed under Apache-2.0 or MIT.
- 📘 API Docs: [docs.rs/rustecal](https://docs.rs/rustecal)
- 📖 Guide & Examples: see `docs/` (mdBook)

---

## Maintainer
## License

[Rex Schilasky](https://github.com/rex-schilasky)
Licensed under the Apache License 2.0 (see [LICENSE](./LICENSE))
© 2024–2025 Eclipse Contributors / Rex Schilasky
2 changes: 2 additions & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,7 @@
- [Typed Publisher](api/publisher.md)
- [Typed Subscriber](api/subscriber.md)
- [Supported Message Types](api/message_types.md)
- [Service Server](api/service_server.md)
- [Service Client](api/service_client.md)
- [Project Status](project_status.md)
- [About](about.md)
51 changes: 51 additions & 0 deletions docs/src/api/service_client.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Service Client

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

## Connecting to a Service

```rust
use rustecal::service::client::ServiceClient;

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

## Calling Methods

```rust
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:

```rust
for instance in client.get_client_instances() {
let response = instance.call("reverse", request.clone(), Some(1000));
}
```

## Return Handling

```rust
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.
50 changes: 50 additions & 0 deletions docs/src/api/service_server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# 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:

```rust
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:

```rust
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.
9 changes: 5 additions & 4 deletions docs/src/project_status.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Roadmap

- [x] Cross-platform support (Windows, Linux)
- [x] Safe API for initialization, shutdown, and pub/sub
- [x] Typed pub/sub APIs
- [x] Safe API for initialization, shutdown
- [x] Binary publish/subscribe API
- [x] Typed publish/subscribe API
- [x] Modular type crates (string, bytes, protobuf)
- [x] Examples for all supported types
- [x] Binary server/client API
- [x] Examples for all publish/subscribe and client/server
- [ ] Protobuf descriptor introspection
- [ ] eCAL Services (RPC-style)
- [ ] Monitoring and logging support
2 changes: 1 addition & 1 deletion rustecal-samples/pubsub/blob_receive/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use rustecal_types_bytes::BytesMessage;
use rustecal::pubsub::typed_subscriber::Received;

fn main() {
Ecal::initialize(Some("bytes subscriber rust"), EcalComponents::DEFAULT)
Ecal::initialize(Some("blob receive rust"), EcalComponents::DEFAULT)
.expect("eCAL initialization failed");

let mut subscriber = TypedSubscriber::<BytesMessage>::new("blob")
Expand Down
2 changes: 1 addition & 1 deletion rustecal-samples/pubsub/blob_send/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use rustecal::{Ecal, EcalComponents, TypedPublisher};
use rustecal_types_bytes::BytesMessage;

fn main() {
Ecal::initialize(Some("bytes publisher rust"), EcalComponents::DEFAULT)
Ecal::initialize(Some("blob send rust"), EcalComponents::DEFAULT)
.expect("eCAL initialization failed");

let publisher = TypedPublisher::<BytesMessage>::new("blob")
Expand Down
2 changes: 1 addition & 1 deletion rustecal-samples/pubsub/hello_receive/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use rustecal_types_string::StringMessage;
use rustecal::pubsub::typed_subscriber::Received;

fn main() {
Ecal::initialize(Some("hello string subscriber rust"), EcalComponents::DEFAULT)
Ecal::initialize(Some("hello receive rust"), EcalComponents::DEFAULT)
.expect("eCAL initialization failed");

let mut subscriber = TypedSubscriber::<StringMessage>::new("hello")
Expand Down
2 changes: 1 addition & 1 deletion rustecal-samples/pubsub/hello_send/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use rustecal::{Ecal, EcalComponents, TypedPublisher};
use rustecal_types_string::StringMessage;

fn main() {
Ecal::initialize(Some("hello string publisher rust"), EcalComponents::DEFAULT)
Ecal::initialize(Some("hello send rust"), EcalComponents::DEFAULT)
.expect("eCAL initialization failed");

let publisher: TypedPublisher<StringMessage> = TypedPublisher::<StringMessage>::new("hello")
Expand Down
2 changes: 1 addition & 1 deletion rustecal-samples/pubsub/person_receive/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use people::Person;
impl IsProtobufType for Person {}

fn main() {
Ecal::initialize(Some("person protobuf subscriber rust"), EcalComponents::DEFAULT)
Ecal::initialize(Some("person receive rust"), EcalComponents::DEFAULT)
.expect("eCAL initialization failed");

let mut subscriber = TypedSubscriber::<ProtobufMessage<Person>>::new("person")
Expand Down
2 changes: 1 addition & 1 deletion rustecal-samples/pubsub/person_send/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use people::Person;
impl IsProtobufType for Person {}

fn main() {
Ecal::initialize(Some("person protobuf publisher rust"), EcalComponents::DEFAULT)
Ecal::initialize(Some("person send rust"), EcalComponents::DEFAULT)
.expect("eCAL initialization failed");

let publisher = TypedPublisher::<ProtobufMessage<Person>>::new("person")
Expand Down
1 change: 1 addition & 0 deletions rustecal-samples/service/mirror_client/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target
7 changes: 7 additions & 0 deletions rustecal-samples/service/mirror_client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "mirror_client"
version = "0.1.0"
edition = "2024"

[dependencies]
rustecal = { path = "../../../rustecal" }
Loading