Skip to content

Commit 4617c9f

Browse files
committed
Add introductory documentation and a parameter demo
Signed-off-by: Michael X. Grey <[email protected]>
1 parent f0c2e09 commit 4617c9f

File tree

12 files changed

+231
-25
lines changed

12 files changed

+231
-25
lines changed

examples/parameter_demo/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
name = "parameter_demo"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
rclrs = "0.4"
8+
example_interfaces = "*"

examples/parameter_demo/package.xml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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_parameter_demo</name>
7+
<maintainer email="[email protected]">Esteve Fernandez</maintainer>
8+
<!-- This project is not military-sponsored, Jacob's employment contract just requires him to use this email address -->
9+
<maintainer email="[email protected]">Jacob Hassold</maintainer>
10+
<version>0.4.1</version>
11+
<description>Package containing an example of how to use a worker in rclrs.</description>
12+
<license>Apache License 2.0</license>
13+
14+
<depend>rclrs</depend>
15+
<depend>rosidl_runtime_rs</depend>
16+
<depend>example_interfaces</depend>
17+
18+
<export>
19+
<build_type>ament_cargo</build_type>
20+
</export>
21+
</package>

examples/parameter_demo/src/main.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use rclrs::*;
2+
use std::sync::Arc;
3+
4+
fn main() -> Result<(), RclrsError> {
5+
let mut executor = Context::default_from_env()?.create_basic_executor();
6+
let node = executor.create_node("parameter_demo")?;
7+
8+
let greeting: MandatoryParameter<Arc<str>> = node
9+
.declare_parameter("greeting")
10+
.default("Hello".into())
11+
.mandatory()?;
12+
13+
let _subscription = node.create_subscription(
14+
"greet",
15+
move |msg: example_interfaces::msg::String| {
16+
println!("{}, {}", greeting.get(), msg.data);
17+
}
18+
)?;
19+
20+
println!(
21+
"Ready to provide a greeting. \
22+
\n\nTo see a greeting, try running\n \
23+
$ ros2 topic pub greet example_interfaces/msg/String \"data: Alice\"\
24+
\n\nTo change the kind of greeting, try running\n \
25+
$ ros2 param set parameter_demo greeting \"Guten tag\"\n"
26+
);
27+
executor.spin(SpinOptions::default()).first_error()
28+
}

examples/worker_demo/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ edition = "2021"
55

66
[dependencies]
77
rclrs = "0.4"
8-
std_msgs = "*"
8+
example_interfaces = "*"

examples/worker_demo/package.xml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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_worker_demo</name>
7+
<maintainer email="[email protected]">Esteve Fernandez</maintainer>
8+
<!-- This project is not military-sponsored, Jacob's employment contract just requires him to use this email address -->
9+
<maintainer email="[email protected]">Jacob Hassold</maintainer>
10+
<version>0.4.1</version>
11+
<description>Package containing an example of how to use a worker in rclrs.</description>
12+
<license>Apache License 2.0</license>
13+
14+
<depend>rclrs</depend>
15+
<depend>rosidl_runtime_rs</depend>
16+
<depend>example_interfaces</depend>
17+
18+
<export>
19+
<build_type>ament_cargo</build_type>
20+
</export>
21+
</package>

examples/worker_demo/src/main.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ fn main() -> Result<(), RclrsError> {
1010

1111
let _subscription = worker.create_subscription(
1212
"input_topic",
13-
move |data: &mut String, msg: std_msgs::msg::String| {
13+
move |data: &mut String, msg: example_interfaces::msg::String| {
1414
*data = msg.data;
1515
}
1616
)?;
@@ -20,7 +20,7 @@ fn main() -> Result<(), RclrsError> {
2020
// let _timer = worker.create_timer_repeating(
2121
// Duration::from_secs(1),
2222
// move |data: &mut String| {
23-
// let msg = std_msgs::msg::String {
23+
// let msg = example_interfaces::msg::String {
2424
// data: data.clone()
2525
// };
2626

@@ -33,7 +33,7 @@ fn main() -> Result<(), RclrsError> {
3333
std::thread::sleep(std::time::Duration::from_secs(1));
3434
let publisher = Arc::clone(&publisher);
3535
let _ = worker.run(move |data: &mut String| {
36-
let msg = std_msgs::msg::String {
36+
let msg = example_interfaces::msg::String {
3737
data: data.clone()
3838
};
3939
publisher.publish(msg).unwrap();

rclrs/src/error.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::{
44
fmt::{self, Display},
55
};
66

7-
use crate::rcl_bindings::*;
7+
use crate::{rcl_bindings::*, DeclarationError};
88

99
/// The main error type.
1010
#[derive(Debug, PartialEq, Eq)]
@@ -47,6 +47,8 @@ pub enum RclrsError {
4747
/// The payload type given by the worker
4848
received: std::any::TypeId,
4949
},
50+
/// An error happened while declaring a parameter.
51+
ParameterDeclarationError(crate::DeclarationError),
5052
/// A mutex used internally has been [poisoned][std::sync::PoisonError].
5153
PoisonedMutex,
5254
}
@@ -110,6 +112,12 @@ impl Display for RclrsError {
110112
"Received invalid payload: expected {expected:?}, received {received:?}",
111113
)
112114
}
115+
RclrsError::ParameterDeclarationError(err) => {
116+
write!(
117+
f,
118+
"An error occurred while declaring a parameter: {err}",
119+
)
120+
}
113121
RclrsError::PoisonedMutex => {
114122
write!(
115123
f,
@@ -153,6 +161,7 @@ impl Error for RclrsError {
153161
RclrsError::NegativeDuration(_) => None,
154162
RclrsError::UnownedGuardCondition => None,
155163
RclrsError::InvalidPayload { .. } => None,
164+
RclrsError::ParameterDeclarationError(_) => None,
156165
RclrsError::PoisonedMutex => None,
157166
}
158167
}
@@ -255,6 +264,12 @@ pub enum RclReturnCode {
255264
LifecycleStateNotRegistered = 3001,
256265
}
257266

267+
impl From<DeclarationError> for RclrsError {
268+
fn from(value: DeclarationError) -> Self {
269+
RclrsError::ParameterDeclarationError(value)
270+
}
271+
}
272+
258273
impl TryFrom<i32> for RclReturnCode {
259274
type Error = i32;
260275

rclrs/src/lib.rs

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,122 @@
11
#![warn(missing_docs)]
22
//! Rust client library for ROS 2.
33
//!
4-
//! For getting started, see the [README][1].
4+
//! Since this library depends on the ROS ecosystem, see the [README][1] for
5+
//! setup instructions.
56
//!
67
//! [1]: https://github.com/ros2-rust/ros2_rust/blob/main/README.md
8+
//!
9+
//! This client library is made to be familiar for ROS users who are used to
10+
//! the conventional client libraries `rcl`, `rclcpp`, and `rclpy`, while taking
11+
//! full advantage of the unique strengths of the Rust programming language.
12+
//!
13+
//! The library provides structs that will be familiar to ROS users:
14+
//! - [`Context`]
15+
//! - [`Executor`]
16+
//! - [`Node`]
17+
//! - [`Subscription`]
18+
//! - [`Publisher`]
19+
//! - [`Service`]
20+
//! - [`Client`]
21+
//!
22+
//! It also provides some unique utilities to help leverage Rust language features,
23+
//! such as `async` programming:
24+
//! - [`Worker`]
25+
//! - [`ExecutorCommands`]
26+
//!
27+
//! # Basic Usage
28+
//!
29+
//! To build a typical ROS application, create a [`Context`], followed by an
30+
//! [`Executor`], and then a [`Node`]. Create whatever primitives you need, and
31+
//! then tell the [`Executor`] to spin:
32+
//!
33+
//! ```no_run
34+
//! use rclrs::*;
35+
//!
36+
//! let context = Context::default_from_env()?;
37+
//! let mut executor = context.create_basic_executor();
38+
//! let node = executor.create_node("example_node")?;
39+
//!
40+
//! let subscription = node.create_subscription(
41+
//! "topic_name",
42+
//! |msg: example_interfaces::msg::String| {
43+
//! println!("Received message: {}", msg.data);
44+
//! }
45+
//! )?;
46+
//!
47+
//! executor.spin(SpinOptions::default()).first_error()?;
48+
//! # Ok::<(), RclrsError>(())
49+
//! ```
50+
//!
51+
//! If your callback needs to interact with some state data, consider using a
52+
//! [`Worker`], especially if that state data needs to be shared with other
53+
//! callbacks:
54+
//!
55+
//! ```no_run
56+
//! # use rclrs::*;
57+
//! #
58+
//! # let context = Context::default_from_env()?;
59+
//! # let mut executor = context.create_basic_executor();
60+
//! # let node = executor.create_node("example_node")?;
61+
//! #
62+
//! // This worker will manage the data for us.
63+
//! // The worker's data is called its payload.
64+
//! let worker = node.create_worker::<Option<String>>(None);
65+
//!
66+
//! // We use the worker to create a subscription.
67+
//! // This subscription's callback can borrow the worker's
68+
//! // payload with its first function argument.
69+
//! let subscription = worker.create_subscription(
70+
//! "topic_name",
71+
//! |data: &mut Option<String>, msg: example_interfaces::msg::String| {
72+
//! // Print out the previous message, if one exists.
73+
//! if let Some(previous) = data {
74+
//! println!("Previous message: {}", *previous)
75+
//! }
76+
//!
77+
//! // Save the latest message, to be printed out the
78+
//! // next time this callback is triggered.
79+
//! *data = Some(msg.data);
80+
//! }
81+
//! )?;
82+
//!
83+
//! # executor.spin(SpinOptions::default()).first_error()?;
84+
//! # Ok::<(), RclrsError>(())
85+
//! ```
86+
//!
87+
//! # Parameters
88+
//!
89+
//! `rclrs` provides an ergonomic way to declare and use node parameters. A
90+
//! parameter can be declared as [mandatory][crate::MandatoryParameter],
91+
//! [optional][crate::OptionalParameter], or [read-only][crate::ReadOnlyParameter].
92+
//! The API of each reflects their respective constraints.
93+
//! - Mandatory and read-only parameters always have a value that you can [get][MandatoryParameter::get]
94+
//! - Optional parameters will return an [`Option`] when you [get][OptionalParameter::get] from them.
95+
//! - Read-only parameters do not allow you to modify them after they have been declared.
96+
//!
97+
//! The following is a simple example of using a mandatory parameter:
98+
//! ```no_run
99+
//! use rclrs::*;
100+
//! use std::sync::Arc;
101+
//!
102+
//! let mut executor = Context::default_from_env()?.create_basic_executor();
103+
//! let node = executor.create_node("parameter_demo")?;
104+
//!
105+
//! let greeting: MandatoryParameter<Arc<str>> = node
106+
//! .declare_parameter("greeting")
107+
//! .default("Hello".into())
108+
//! .mandatory()?;
109+
//!
110+
//! let _subscription = node.create_subscription(
111+
//! "greet",
112+
//! move |msg: example_interfaces::msg::String| {
113+
//! println!("{}, {}", greeting.get(), msg.data);
114+
//! }
115+
//! )?;
116+
//!
117+
//! executor.spin(SpinOptions::default()).first_error()?;
118+
//! # Ok::<(), RclrsError>(())
119+
//! ```
7120
8121
mod arguments;
9122
mod client;

rclrs/src/parameter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,7 @@ impl std::fmt::Display for ParameterValueError {
639639
impl std::error::Error for ParameterValueError {}
640640

641641
/// Error that can be generated when doing operations on parameters.
642-
#[derive(Debug)]
642+
#[derive(Debug, PartialEq, Eq)]
643643
pub enum DeclarationError {
644644
/// Parameter was already declared and a new declaration was attempted.
645645
AlreadyDeclared,

rclrs/src/subscription/into_async_subscription_callback.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ use std::future::Future;
1010
/// A trait for async callbacks of subscriptions.
1111
///
1212
/// Async subscription callbacks support six signatures:
13-
/// - [`FnMut`] ( `Message` ) -> impl [`Future`][1]<Output=()>
14-
/// - [`FnMut`] ( `Message`, [`MessageInfo`][2] ) -> impl [`Future`][1]<Output=()>
15-
/// - [`FnMut`] ( [`Box`]<`Message`> ) -> impl [`Future`][1]<Output=()>
16-
/// - [`FnMut`] ( [`Box`]<`Message`>, [`MessageInfo`][2] ) -> impl [`Future`][1]<Output=()>
17-
/// - [`FnMut`] ( [`ReadOnlyLoanedMessage`][3]<`Message`> ) -> impl [`Future`][1]<Output=()>
18-
/// - [`FnMut`] ( [`ReadOnlyLoanedMessage`][3]<`Message`>, [`MessageInfo`][2] ) -> impl [`Future`][1]<Output=()>
13+
/// - [`FnMut`] ( `Message` ) -> impl [`Future`]<Output=()>
14+
/// - [`FnMut`] ( `Message`, [`MessageInfo`] ) -> impl [`Future`]<Output=()>
15+
/// - [`FnMut`] ( [`Box`]<`Message`> ) -> impl [`Future`]<Output=()>
16+
/// - [`FnMut`] ( [`Box`]<`Message`>, [`MessageInfo`] ) -> impl [`Future`]<Output=()>
17+
/// - [`FnMut`] ( [`ReadOnlyLoanedMessage`]<`Message`> ) -> impl [`Future`]<Output=()>
18+
/// - [`FnMut`] ( [`ReadOnlyLoanedMessage`]<`Message`>, [`MessageInfo`] ) -> impl [`Future`]<Output=()>
1919
pub trait IntoAsyncSubscriptionCallback<T, Args>: Send + 'static
2020
where
2121
T: Message,

0 commit comments

Comments
 (0)