Skip to content

Commit 9e7c148

Browse files
Merge pull request #1 from rex-schilasky/service-client-api
server client api - first binary version
2 parents 24c8e93 + 151b5a5 commit 9e7c148

File tree

39 files changed

+932
-225
lines changed

39 files changed

+932
-225
lines changed

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,8 @@ members = [
1010
"rustecal-samples/pubsub/hello_send",
1111
"rustecal-samples/pubsub/hello_receive",
1212
"rustecal-samples/pubsub/person_send",
13-
"rustecal-samples/pubsub/person_receive"]
13+
"rustecal-samples/pubsub/person_receive",
14+
"rustecal-samples/service/mirror_client",
15+
"rustecal-samples/service/mirror_client_instances",
16+
"rustecal-samples/service/mirror_server"
17+
]

README.md

Lines changed: 80 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -8,80 +8,117 @@ Safe and idiomatic Rust bindings for the [eCAL](https://github.com/eclipse-ecal/
88

99
## Features
1010

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

2017
---
2118

22-
## Usage
19+
## Examples
2320

24-
Add the following to your `Cargo.toml`:
25-
26-
```toml
27-
[dependencies]
28-
rustecal = { path = "path/to/rustecal" }
29-
rustecal-types-string = { path = "path/to/rustecal-types-string" }
30-
```
31-
32-
Example (sending a string message):
21+
### Publisher
3322

3423
```rust
24+
use rustecal::{Ecal, EcalComponents};
3525
use rustecal::pubsub::Publisher;
36-
use rustecal::types::StringMessage;
3726

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

40-
let publisher = Publisher::<StringMessage>::builder("hello_topic").create()?;
41-
publisher.send("Hello from Rust!")?;
31+
loop {
32+
pub.send("Hello from Rust!")?;
33+
std::thread::sleep(std::time::Duration::from_millis(500));
34+
}
35+
}
4236
```
4337

44-
Example (receiving a message):
38+
---
39+
40+
### Subscriber
4541

4642
```rust
43+
use rustecal::{Ecal, EcalComponents};
4744
use rustecal::pubsub::Subscriber;
48-
use rustecal::types::StringMessage;
4945

50-
let _ecal = rustecal::Ecal::initialize("hello_receive")?;
46+
fn main() -> Result<(), Box<dyn std::error::Error>> {
47+
Ecal::initialize(Some("rust subscriber"), EcalComponents::DEFAULT)?;
48+
let sub = Subscriber::<String>::new("chatter")?;
49+
50+
sub.set_callback(|msg| {
51+
println!("Received: {}", msg.payload);
52+
})?;
5153

52-
let subscriber = Subscriber::<StringMessage>::builder("hello_topic").create()?;
53-
subscriber.set_callback(|msg| {
54-
println!("Received: {}", msg.data());
55-
});
54+
loop {
55+
std::thread::sleep(std::time::Duration::from_millis(100));
56+
}
57+
}
5658
```
5759

5860
---
5961

60-
## Crate Structure
62+
### Service Server
6163

62-
- `rustecal`: core eCAL bindings and idiomatic API
63-
- `rustecal-sys`: low-level `bindgen` generated FFI bindings
64-
- `rustecal-types-string`, `rustecal-types-bytes`, `rustecal-types-protobuf`: message wrapper crates
64+
```rust
65+
use rustecal::{Ecal, EcalComponents};
66+
use rustecal::service::server::ServiceServer;
67+
use rustecal::service::types::MethodInfo;
68+
69+
fn main() -> Result<(), Box<dyn std::error::Error>> {
70+
Ecal::initialize(Some("mirror server"), EcalComponents::DEFAULT)?;
71+
let mut server = ServiceServer::new("mirror")?;
72+
73+
server.add_method("reverse", Box::new(|_info: MethodInfo, req: &[u8]| {
74+
let mut reversed = req.to_vec();
75+
reversed.reverse();
76+
reversed
77+
}))?;
78+
79+
loop {
80+
std::thread::sleep(std::time::Duration::from_millis(100));
81+
}
82+
}
83+
```
6584

6685
---
6786

68-
## Documentation
69-
70-
📚 Full user guide: [https://rex-schilasky.github.io/rustecal](https://rex-schilasky.github.io/rustecal)
87+
### Service Client
7188

72-
```bash
73-
cd docs/
74-
mdbook serve
89+
```rust
90+
use rustecal::{Ecal, EcalComponents};
91+
use rustecal::service::client::ServiceClient;
92+
use rustecal::service::types::ServiceRequest;
93+
94+
fn main() -> Result<(), Box<dyn std::error::Error>> {
95+
Ecal::initialize(Some("mirror client"), EcalComponents::DEFAULT)?;
96+
let client = ServiceClient::new("mirror")?;
97+
98+
let request = ServiceRequest {
99+
payload: b"stressed".to_vec(),
100+
};
101+
102+
if let Some(response) = client.call("reverse", request, Some(1000)) {
103+
println!("Reversed: {}", String::from_utf8_lossy(&response.payload));
104+
} else {
105+
println!("No response received.");
106+
}
107+
108+
Ok(())
109+
}
75110
```
76111

77112
---
78113

79-
## License
114+
## Documentation
80115

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

83119
---
84120

85-
## Maintainer
121+
## License
86122

87-
[Rex Schilasky](https://github.com/rex-schilasky)
123+
Licensed under the Apache License 2.0 (see [LICENSE](./LICENSE))
124+
© 2024–2025 Eclipse Contributors / Rex Schilasky

docs/src/SUMMARY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,7 @@
1414
- [Typed Publisher](api/publisher.md)
1515
- [Typed Subscriber](api/subscriber.md)
1616
- [Supported Message Types](api/message_types.md)
17+
- [Service Server](api/service_server.md)
18+
- [Service Client](api/service_client.md)
1719
- [Project Status](project_status.md)
1820
- [About](about.md)

docs/src/api/service_client.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Service Client
2+
3+
The `ServiceClient` API allows a Rust application to call eCAL services, either generically or per-instance.
4+
5+
## Connecting to a Service
6+
7+
```rust
8+
use rustecal::service::client::ServiceClient;
9+
10+
let client = ServiceClient::new("mirror")?;
11+
```
12+
13+
## Calling Methods
14+
15+
```rust
16+
use rustecal::service::types::ServiceRequest;
17+
18+
let request = ServiceRequest {
19+
payload: b"stressed".to_vec(),
20+
};
21+
22+
let response = client.call("echo", request, Some(1000));
23+
```
24+
25+
To call all connected instances:
26+
27+
```rust
28+
for instance in client.get_client_instances() {
29+
let response = instance.call("reverse", request.clone(), Some(1000));
30+
}
31+
```
32+
33+
## Return Handling
34+
35+
```rust
36+
match response {
37+
Some(res) if res.success => {
38+
println!("Response: {}", String::from_utf8_lossy(&res.payload));
39+
}
40+
Some(res) => {
41+
println!("Error: {}", res.error_msg.unwrap_or("Unknown error".into()));
42+
}
43+
None => {
44+
println!("No response or timeout.");
45+
}
46+
}
47+
```
48+
49+
## Runtime Compatibility
50+
51+
This API matches the usage and behavior of `mirror_client.cpp` in the eCAL C++ samples.

docs/src/api/service_server.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Service Server
2+
3+
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.
4+
5+
## Registering Methods
6+
7+
To provide services, create a `ServiceServer` and register one or more methods by name:
8+
9+
```rust
10+
use rustecal::service::server::ServiceServer;
11+
use rustecal::service::types::MethodInfo;
12+
13+
let mut server = ServiceServer::new("mirror")?;
14+
15+
server.add_method("echo", Box::new(|_info: MethodInfo, request: &[u8]| {
16+
request.to_vec()
17+
}))?;
18+
19+
server.add_method("reverse", Box::new(|_info, request| {
20+
let mut reversed = request.to_vec();
21+
reversed.reverse();
22+
reversed
23+
}))?;
24+
```
25+
26+
## Method Signatures
27+
28+
The callback signature follows:
29+
30+
```rust
31+
Fn(MethodInfo, &[u8]) -> Vec<u8>
32+
```
33+
34+
This is safe, allocation-free on the input side, and flexible for any binary or textual payloads.
35+
36+
## Example Output
37+
38+
```
39+
Method : 'echo' called
40+
Request : stressed
41+
Response : stressed
42+
43+
Method : 'reverse' called
44+
Request : stressed
45+
Response : desserts
46+
```
47+
48+
## Runtime Compatibility
49+
50+
This API is fully compatible with the C++ `mirror_server.cpp` and C `mirror_server.c` examples.

docs/src/project_status.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# Roadmap
22

33
- [x] Cross-platform support (Windows, Linux)
4-
- [x] Safe API for initialization, shutdown, and pub/sub
5-
- [x] Typed pub/sub APIs
4+
- [x] Safe API for initialization, shutdown
5+
- [x] Binary publish/subscribe API
6+
- [x] Typed publish/subscribe API
67
- [x] Modular type crates (string, bytes, protobuf)
7-
- [x] Examples for all supported types
8+
- [x] Binary server/client API
9+
- [x] Examples for all publish/subscribe and client/server
810
- [ ] Protobuf descriptor introspection
9-
- [ ] eCAL Services (RPC-style)
1011
- [ ] Monitoring and logging support

rustecal-samples/pubsub/blob_receive/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use rustecal_types_bytes::BytesMessage;
33
use rustecal::pubsub::typed_subscriber::Received;
44

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

99
let mut subscriber = TypedSubscriber::<BytesMessage>::new("blob")

rustecal-samples/pubsub/blob_send/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use rustecal::{Ecal, EcalComponents, TypedPublisher};
22
use rustecal_types_bytes::BytesMessage;
33

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

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

rustecal-samples/pubsub/hello_receive/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use rustecal_types_string::StringMessage;
33
use rustecal::pubsub::typed_subscriber::Received;
44

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

99
let mut subscriber = TypedSubscriber::<StringMessage>::new("hello")

rustecal-samples/pubsub/hello_send/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use rustecal::{Ecal, EcalComponents, TypedPublisher};
22
use rustecal_types_string::StringMessage;
33

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

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

0 commit comments

Comments
 (0)