Skip to content

Commit 58dd04a

Browse files
authored
Added support for clients and services (#146)
* Added support for clients and services
1 parent 81cb0f7 commit 58dd04a

29 files changed

+1241
-298
lines changed

.github/workflows/rust.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ jobs:
9999
run: |
100100
cd ${{ steps.build.outputs.ros-workspace-directory-name }}
101101
. /opt/ros/${{ matrix.ros_distribution }}/setup.sh
102-
for path in $(colcon list | awk '$3 == "(ament_cargo)" && $1 != "examples_rclrs_minimal_pub_sub" { print $2 }'); do
102+
for path in $(colcon list | awk '$3 == "(ament_cargo)" && $1 != "examples_rclrs_minimal_pub_sub" && $1 != "examples_rclrs_minimal_client_service" { print $2 }'); do
103103
cd $path
104104
echo "Running cargo test in $path"
105105
cargo test
@@ -110,7 +110,7 @@ jobs:
110110
run: |
111111
cd ${{ steps.build.outputs.ros-workspace-directory-name }}
112112
. /opt/ros/${{ matrix.ros_distribution }}/setup.sh
113-
for path in $(colcon list | awk '$3 == "(ament_cargo)" && $1 != "examples_rclrs_minimal_pub_sub" { print $2 }'); do
113+
for path in $(colcon list | awk '$3 == "(ament_cargo)" && $1 != "examples_rclrs_minimal_pub_sub" && $1 != "examples_rclrs_minimal_client_service" { print $2 }'); do
114114
cd $path
115115
echo "Running rustdoc check in $path"
116116
cargo rustdoc -- -D warnings

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ The current set of features include:
1818
- Message generation
1919
- Support for publishers and subscriptions
2020
- Tunable QoS settings
21+
- Clients and services
2122

2223
Lots of things are still missing however, see the [issue list](https://github.com/ros2-rust/ros2_rust/issues) for an overview. You are very welcome to [contribute](docs/CONTRIBUTING.md)!
2324

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[package]
2+
name = "examples_rclrs_minimal_client_service"
3+
version = "0.2.0"
4+
authors = ["Esteve Fernandez <[email protected]>"]
5+
edition = "2021"
6+
7+
[[bin]]
8+
name = "minimal_client"
9+
path = "src/minimal_client.rs"
10+
11+
[[bin]]
12+
name = "minimal_client_async"
13+
path = "src/minimal_client_async.rs"
14+
15+
[[bin]]
16+
name = "minimal_service"
17+
path = "src/minimal_service.rs"
18+
19+
[dependencies]
20+
anyhow = {version = "1", features = ["backtrace"]}
21+
tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread", "time"] }
22+
23+
[dependencies.rclrs]
24+
version = "*"
25+
26+
[dependencies.rosidl_runtime_rs]
27+
version = "*"
28+
29+
[dependencies.example_interfaces]
30+
version = "*"
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0"?>
2+
<?xml-model
3+
href="http://download.ros.org/schema/package_format3.xsd"
4+
schematypens="http://www.w3.org/2001/XMLSchema"?>
5+
<package format="3">
6+
<name>examples_rclrs_minimal_client_service</name>
7+
<version>0.2.0</version>
8+
<description>Package containing an example of the client-service mechanism in rclrs.</description>
9+
<maintainer email="[email protected]">Esteve Fernandez</maintainer>
10+
<license>Apache License 2.0</license>
11+
12+
<build_depend>example_interfaces</build_depend>
13+
<build_depend>rclrs</build_depend>
14+
<build_depend>rosidl_runtime_rs</build_depend>
15+
16+
<exec_depend>example_interfaces</exec_depend>
17+
<exec_depend>rclrs</exec_depend>
18+
<exec_depend>rosidl_runtime_rs</exec_depend>
19+
20+
<export>
21+
<build_type>ament_cargo</build_type>
22+
</export>
23+
</package>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use anyhow::{Error, Result};
2+
use std::env;
3+
4+
fn main() -> Result<(), Error> {
5+
let context = rclrs::Context::new(env::args())?;
6+
7+
let mut node = context.create_node("minimal_client")?;
8+
9+
let client = node.create_client::<example_interfaces::srv::AddTwoInts>("add_two_ints")?;
10+
11+
let request = example_interfaces::srv::AddTwoInts_Request { a: 41, b: 1 };
12+
13+
println!("Starting client");
14+
15+
std::thread::sleep(std::time::Duration::from_millis(500));
16+
17+
client.async_send_request_with_callback(
18+
&request,
19+
move |response: example_interfaces::srv::AddTwoInts_Response| {
20+
println!(
21+
"Result of {} + {} is: {}",
22+
request.a, request.b, response.sum
23+
);
24+
},
25+
)?;
26+
27+
std::thread::sleep(std::time::Duration::from_millis(500));
28+
29+
println!("Waiting for response");
30+
rclrs::spin(&node).map_err(|err| err.into())
31+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use anyhow::{Error, Result};
2+
use std::env;
3+
4+
#[tokio::main]
5+
async fn main() -> Result<(), Error> {
6+
let context = rclrs::Context::new(env::args())?;
7+
8+
let mut node = context.create_node("minimal_client")?;
9+
10+
let client = node.create_client::<example_interfaces::srv::AddTwoInts>("add_two_ints")?;
11+
12+
println!("Starting client");
13+
14+
std::thread::sleep(std::time::Duration::from_millis(500));
15+
16+
let request = example_interfaces::srv::AddTwoInts_Request { a: 41, b: 1 };
17+
18+
let future = client.call_async(&request);
19+
20+
println!("Waiting for response");
21+
22+
let rclrs_spin = tokio::task::spawn_blocking(move || rclrs::spin(&node));
23+
24+
let response = future.await?;
25+
println!(
26+
"Result of {} + {} is: {}",
27+
request.a, request.b, response.sum
28+
);
29+
30+
rclrs_spin.await.ok();
31+
Ok(())
32+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
use anyhow::{Error, Result};
2+
use std::env;
3+
4+
fn handle_service(
5+
_request_header: &rclrs::rmw_request_id_t,
6+
request: example_interfaces::srv::AddTwoInts_Request,
7+
) -> example_interfaces::srv::AddTwoInts_Response {
8+
println!("request: {} + {}", request.a, request.b);
9+
example_interfaces::srv::AddTwoInts_Response {
10+
sum: request.a + request.b,
11+
}
12+
}
13+
14+
fn main() -> Result<(), Error> {
15+
let context = rclrs::Context::new(env::args())?;
16+
17+
let mut node = context.create_node("minimal_service")?;
18+
19+
let _server = node
20+
.create_service::<example_interfaces::srv::AddTwoInts, _>("add_two_ints", handle_service)?;
21+
22+
println!("Starting server");
23+
rclrs::spin(&node).map_err(|err| err.into())
24+
}

examples/minimal_pub_sub/src/minimal_subscriber.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
use std::env;
2-
31
use anyhow::{Error, Result};
2+
use std::env;
43

54
fn main() -> Result<(), Error> {
65
let context = rclrs::Context::new(env::args())?;

rclrs/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ libc = "0.2.43"
1717
parking_lot = "0.11.2"
1818
# Needed for the Message trait, among others
1919
rosidl_runtime_rs = "*"
20+
# Needed for clients
21+
futures = "0.3"
2022

2123
[dev-dependencies]
2224
# Needed for e.g. writing yaml files in tests

rclrs/src/lib.rs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,31 +24,61 @@ pub use wait::*;
2424
use rcl_bindings::rcl_context_is_valid;
2525
use std::time::Duration;
2626

27+
pub use rcl_bindings::rmw_request_id_t;
28+
2729
/// Polls the node for new messages and executes the corresponding callbacks.
2830
///
2931
/// See [`WaitSet::wait`] for the meaning of the `timeout` parameter.
3032
///
3133
/// This may under some circumstances return
32-
/// [`SubscriptionTakeFailed`][1] when the wait set spuriously wakes up.
34+
/// [`SubscriptionTakeFailed`][1], [`ClientTakeFailed`][1], [`ServiceTakeFailed`][1] when the wait
35+
/// set spuriously wakes up.
3336
/// This can usually be ignored.
3437
///
3538
/// [1]: crate::RclReturnCode
3639
pub fn spin_once(node: &Node, timeout: Option<Duration>) -> Result<(), RclrsError> {
3740
let live_subscriptions = node.live_subscriptions();
41+
let live_clients = node.live_clients();
42+
let live_services = node.live_services();
3843
let ctx = Context {
3944
rcl_context_mtx: node.rcl_context_mtx.clone(),
4045
};
41-
let mut wait_set = WaitSet::new(live_subscriptions.len(), &ctx)?;
46+
let mut wait_set = WaitSet::new(
47+
live_subscriptions.len(),
48+
0,
49+
0,
50+
live_clients.len(),
51+
live_services.len(),
52+
0,
53+
&ctx,
54+
)?;
4255

4356
for live_subscription in &live_subscriptions {
4457
wait_set.add_subscription(live_subscription.clone())?;
4558
}
4659

60+
for live_client in &live_clients {
61+
wait_set.add_client(live_client.clone())?;
62+
}
63+
64+
for live_service in &live_services {
65+
wait_set.add_service(live_service.clone())?;
66+
}
67+
4768
let ready_entities = wait_set.wait(timeout)?;
69+
4870
for ready_subscription in ready_entities.subscriptions {
4971
ready_subscription.execute()?;
5072
}
5173

74+
for ready_client in ready_entities.clients {
75+
ready_client.execute()?;
76+
}
77+
78+
for ready_service in ready_entities.services {
79+
ready_service.execute()?;
80+
}
81+
5282
Ok(())
5383
}
5484

@@ -74,7 +104,6 @@ pub fn spin(node: &Node) -> Result<(), RclrsError> {
74104
error => return error,
75105
}
76106
}
77-
78107
Ok(())
79108
}
80109

rclrs/src/node.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
mod builder;
2+
mod client;
23
mod publisher;
4+
mod service;
35
mod subscription;
46
pub use self::builder::*;
7+
pub use self::client::*;
58
pub use self::publisher::*;
9+
pub use self::service::*;
610
pub use self::subscription::*;
711

812
use crate::rcl_bindings::*;
@@ -68,6 +72,8 @@ unsafe impl Send for rcl_node_t {}
6872
pub struct Node {
6973
rcl_node_mtx: Arc<Mutex<rcl_node_t>>,
7074
pub(crate) rcl_context_mtx: Arc<Mutex<rcl_context_t>>,
75+
pub(crate) clients: Vec<Weak<dyn ClientBase>>,
76+
pub(crate) services: Vec<Weak<dyn ServiceBase>>,
7177
pub(crate) subscriptions: Vec<Weak<dyn SubscriptionBase>>,
7278
_parameter_map: ParameterOverrideMap,
7379
}
@@ -174,6 +180,23 @@ impl Node {
174180
unsafe { call_string_getter_with_handle(&*self.rcl_node_mtx.lock(), getter) }
175181
}
176182

183+
/// Creates a [`Client`][1].
184+
///
185+
/// [1]: crate::Client
186+
// TODO: make client's lifetime depend on node's lifetime
187+
pub fn create_client<T>(
188+
&mut self,
189+
topic: &str,
190+
) -> Result<Arc<crate::node::client::Client<T>>, RclrsError>
191+
where
192+
T: rosidl_runtime_rs::Service,
193+
{
194+
let client = Arc::new(crate::node::client::Client::<T>::new(self, topic)?);
195+
self.clients
196+
.push(Arc::downgrade(&client) as Weak<dyn ClientBase>);
197+
Ok(client)
198+
}
199+
177200
/// Creates a [`Publisher`][1].
178201
///
179202
/// [1]: crate::Publisher
@@ -189,6 +212,27 @@ impl Node {
189212
Publisher::<T>::new(self, topic, qos)
190213
}
191214

215+
/// Creates a [`Service`][1].
216+
///
217+
/// [1]: crate::Service
218+
// TODO: make service's lifetime depend on node's lifetime
219+
pub fn create_service<T, F>(
220+
&mut self,
221+
topic: &str,
222+
callback: F,
223+
) -> Result<Arc<crate::node::service::Service<T>>, RclrsError>
224+
where
225+
T: rosidl_runtime_rs::Service,
226+
F: Fn(&rmw_request_id_t, T::Request) -> T::Response + 'static + Send,
227+
{
228+
let service = Arc::new(crate::node::service::Service::<T>::new(
229+
self, topic, callback,
230+
)?);
231+
self.services
232+
.push(Arc::downgrade(&service) as Weak<dyn ServiceBase>);
233+
Ok(service)
234+
}
235+
192236
/// Creates a [`Subscription`][1].
193237
///
194238
/// [1]: crate::Subscription
@@ -217,6 +261,14 @@ impl Node {
217261
.collect()
218262
}
219263

264+
pub(crate) fn live_clients(&self) -> Vec<Arc<dyn ClientBase>> {
265+
self.clients.iter().filter_map(Weak::upgrade).collect()
266+
}
267+
268+
pub(crate) fn live_services(&self) -> Vec<Arc<dyn ServiceBase>> {
269+
self.services.iter().filter_map(Weak::upgrade).collect()
270+
}
271+
220272
/// Returns the ROS domain ID that the node is using.
221273
///
222274
/// The domain ID controls which nodes can send messages to each other, see the [ROS 2 concept article][1].

rclrs/src/node/builder.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,9 @@ impl NodeBuilder {
277277
Ok(Node {
278278
rcl_node_mtx,
279279
rcl_context_mtx: self.context.clone(),
280-
subscriptions: std::vec![],
280+
clients: vec![],
281+
services: vec![],
282+
subscriptions: vec![],
281283
_parameter_map,
282284
})
283285
}

0 commit comments

Comments
 (0)