You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/writing_a_simple_publisher_and_subscriber.md
+271-6Lines changed: 271 additions & 6 deletions
Original file line number
Diff line number
Diff line change
@@ -4,10 +4,12 @@
4
4
* Time: 20 minutes
5
5
<details><summary>Background</summary>
6
6
7
-
In this tutorial you will create
7
+
In this tutorial you will create a
8
8
[nodes](https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Nodes/Understanding-ROS2-Nodes.html) that pass information to each other via a
9
9
[topic](https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Topics/Understanding-ROS2-Topics.html) in the form of string messages. The example used here is a simple "talker" and "listener" system; one node publishes data and the other subscribes to the topic to receive that data.
10
10
11
+
Since Rust doesn't have inheritance, it's not possible to inherit from `Node` as is common practice in [`rclcpp`](https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Cpp-Publisher-And-Subscriber.html) or [`rclpy`](https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Py-Publisher-And-Subscriber.html).
12
+
11
13
The code used in these examples can be found [here](https://gitlab.com/ros21923912/simple_ros2_node)
12
14
<divstyle="margin-left:20px;">
13
15
<details><summary>Sidenode to dependencies</summary>
@@ -31,8 +33,7 @@ Before developing ROS2 RUST nodes, you must follow the
31
33
</details>
32
34
33
35
<details><summary>Tasks </summary>
34
-
<divstyle="margin-left:20px;">
35
-
<details><summary>Create a Package</summary>
36
+
<divstyle="margin-left:20px;"><details><summary>Create a Package</summary>
36
37
37
38
Currently, building a package for ROS2 RUST is different
38
39
from building packages for Python or C/C++.
@@ -42,7 +43,19 @@ project as follows:
42
43
```
43
44
cargo new your_project_name && your_project_name
44
45
```
45
-
In the [`Cargo.toml`](https://doc.rust-lang.org/book/ch01-03-hello-cargo.html) file, add a dependency on `rclrs = "*"` and `std_msgs = "*"` by editing this file. For a full Introduction into RUST, please read the very good [RUST book](https://doc.rust-lang.org/book/title-page.html)
46
+
In the [`Cargo.toml`](https://doc.rust-lang.org/book/ch01-03-hello-cargo.html) file, add a dependency on `rclrs = "*"` and `std_msgs = "*"` by editing this file. For a full Introduction into RUST, please read the very good [RUST book](https://doc.rust-lang.org/book/title-page.html). Your `Cargo.toml` could now look like this:
47
+
```
48
+
[package]
49
+
name = "your_package_name"
50
+
version = "0.1.0"
51
+
edition = "2021"
52
+
53
+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
54
+
55
+
[dependencies]
56
+
rclrs = "*"
57
+
std_msgs = "*"
58
+
```
46
59
47
60
48
61
Additionally, create a new `package.xml` if you want your node to be buildable with [`colcon`](https://colcon.readthedocs.io/en/released/user/installation.html). Make sure to change the build type to `ament_cargo` and to include the two packages mentioned above in the dependencies, as such:
@@ -62,9 +75,261 @@ Additionally, create a new `package.xml` if you want your node to be buildable w
62
75
</export>
63
76
</package>
64
77
```
65
-
<details><summary>Write the publisher node</summary><details>
78
+
By taking a look at your package, for example by typing [`tree .`](https://www.geeksforgeeks.org/tree-command-unixlinux/) inside your package, and you'll see a structure similar to the following:
79
+
```
80
+
├── Cargo.toml
81
+
├── package.xml
82
+
└── src
83
+
└── main.rs
66
84
85
+
2 directories, 3 files
86
+
```
87
+
Of course, you can use any capable editor or even your file explorer to do this.
67
88
68
89
</details>
69
-
</div>
90
+
91
+
<details><summary>Write the publisher node</summary>
92
+
93
+
94
+
95
+
To construct a node, replace the code in your main.rs with the [following](https://gitlab.com/ros21923912/simple_ros2_node/-/raw/more_simple_nodes/src/simple_publisher.rs?ref_type=heads):
96
+
```
97
+
/// Creates a SimplePublisherNode, initializes a node and publisher, and provides
98
+
/// methods to publish a simple "Hello World" message on a loop in separate threads.
99
+
100
+
/// Imports the Arc type from std::sync, used for thread-safe reference counting pointers,
101
+
/// and the StringMsg message type from std_msgs for publishing string messages.
102
+
use std::{sync::Arc,time::Duration,iter,thread};
103
+
use rclrs::{RclrsError,QOS_PROFILE_DEFAULT,Context,create_node,Node,Publisher};
104
+
use std_msgs::msg::String as StringMsg;
105
+
// / SimplePublisherNode struct contains node and publisher members.
106
+
// / Used to initialize a ROS 2 node and publisher, and publish messages.
107
+
struct SimplePublisherNode {
108
+
node: Arc<Node>,
109
+
_publisher: Arc<Publisher<StringMsg>>,
110
+
}
111
+
/// Creates a new SimplePublisherNode by initializing a node and publisher.
112
+
///
113
+
/// The `new` function takes a context and returns a Result containing the
114
+
/// initialized SimplePublisherNode or an error. It creates a node with the
115
+
/// given name and creates a publisher on the "publish_hello" topic.
116
+
///
117
+
/// The SimplePublisherNode contains the node and publisher members.
118
+
impl SimplePublisherNode {
119
+
/// Creates a new SimplePublisherNode by initializing a node and publisher.
120
+
///
121
+
/// This function takes a context and returns a Result containing the
122
+
/// initialized SimplePublisherNode or an error. It creates a node with the
123
+
/// given name and creates a publisher on the "publish_hello" topic.
124
+
///
125
+
/// The SimplePublisherNode contains the node and publisher members.
#### This first 3 lines of the Rust code imports tools for thread synchronization, time handling, iteration, threading, ROS 2 communication, and string message publishing. It's likely setting up a ROS 2 node that publishes string messages.
176
+
```
177
+
use std::{sync::Arc,time::Duration,iter,thread};
178
+
use rclrs::{RclrsError,QOS_PROFILE_DEFAULT,Context,create_node,Node,Publisher};
179
+
use std_msgs::msg::String as StringMsg;
180
+
```
181
+
* use std::{sync::Arc, time::Duration, iter, thread};: - Imports specific features from the standard library: - Arc is for thread-safe shared ownership of data. - Duration represents a time span. - iter provides tools for working with iterators. - thread enables creating and managing threads.
182
+
* use rclrs::{RclrsError, QOS_PROFILE_DEFAULT, Context, create_node, Node, Publisher};: - Imports elements for ROS 2 communication: - RclrsError for handling errors. - QOS_PROFILE_DEFAULT likely for default Quality of Service settings. - Context, create_node, Node, Publisher are for ROS 2 node creation and publishing.
183
+
* use std_msgs::msg::String as StringMsg;: - Imports the StringMsg type for publishing string messages.
184
+
185
+
#### Next this struct defines a SimplePublisherNode which holds references to a ROS 2 node and a publisher for string messages.
186
+
```
187
+
struct SimplePublisherNode {
188
+
node: Arc<Node>,
189
+
_publisher: Arc<Publisher<StringMsg>>,
190
+
}
191
+
```
192
+
1. Structure:
193
+
struct SimplePublisherNode: This line defines a new struct named SimplePublisherNode. It serves as a blueprint for creating objects that hold information related to a simple publisher node in ROS 2.
194
+
195
+
2. Members:
196
+
* node: Arc<Node>: This member stores a reference to a ROS 2 node, wrapped in an Arc (Atomic Reference Counted) smart pointer. This allows for safe sharing of the node reference across multiple threads.
197
+
*_publisher: Arc<Publisher<StringMsg>>: This member stores a reference to a publisher specifically for string messages (StringMsg), also wrapped in an Arc for thread safety. The publisher is responsible for sending string messages to other nodes in the ROS 2 system.
198
+
199
+
3. This code defines methods for the SimplePublisherNode struct. The new method creates a ROS 2 node and publisher, storing them in the struct. The publish_data method publishes a string message with a counter and returns the incremented counter.
`impl SimplePublisherNode { ... }`: This line indicates that methods are being defined for the `SimplePublisherNode` struct.
222
+
223
+
2. Constructor Method:
224
+
*`fn new(context: &Context) -> Result<Self, RclrsError> { ... }`: This method serves as a constructor for creating instances of SimplePublisherNode.
225
+
* It takes a Context object as input, which is necessary for interacting with the ROS 2 system.
226
+
* It returns a Result type, indicating either a successful Self (the created SimplePublisherNode object) or an RclrsError if something goes wrong.
227
+
* Inside the new method:
228
+
*`let node = create_node(context, "simple_publisher").unwrap();`: Creates a new ROS 2 node named "simple_publisher" within the given context. The unwrap() unwraps the result, handling any errors immediately.
229
+
*`let _publisher = node.create_publisher("publish_hello", QOS_PROFILE_DEFAULT).unwrap();`: Creates a publisher for string messages on the topic "publish_hello" with default quality of service settings.
230
+
*`Ok(Self { node, _publisher, })`: Returns a Result with the newly created SimplePublisherNode object, containing the node and publisher references.
231
+
232
+
3. Publishing Method:
233
+
*`fn publish_data(&self, inkrement: i32) -> Result<i32, RclrsError> { ... }`: This method publishes a string message and increments a counter.
234
+
* It takes an inkrement value (an integer) as input, which is likely used for counting purposes within the message content.
235
+
* It also returns a Result type, indicating either the incremented inkrement value or an RclrsError if publishing fails.
236
+
* Inside the publish_data method:
237
+
*`let msg: StringMsg = StringMsg { data: format!("Hello World {}", inkrement), };`:tCreates a string message with the content "Hello World" followed by the inkrement value.
238
+
* self._publisher.publish(msg).unwrap();: Publishes the created message onto the topic associated with the publisher.
239
+
* Ok(inkrement + 1_i32): Returns a Result with the incremented inkrement value.
240
+
241
+
#### The main Method creates a ROS 2 node that publishes string messages at a rate of 1 Hz.
242
+
243
+
```
244
+
fn main() -> Result<(),RclrsError> {
245
+
let context = Context::new(std::env::args()).unwrap();
246
+
let publisher = Arc::new(SimplePublisherNode::new(&context).unwrap());
247
+
let publisher_other_thread = Arc::clone(&publisher);
*`fn main() -> Result<(), RclrsError> { ... }`: This defines the main entry point of the program. It returns a Result type, indicating either successful execution or an RclrsError.
261
+
262
+
2. Context and Node Setup:
263
+
264
+
*`let context = Context::new(std::env::args()).unwrap();`: Creates a ROS 2 context using command-line arguments.
* Creates an [Arc (atomic reference counted)](https://doc.rust-lang.org/std/sync/struct.Arc.html) pointer to a `SimplePublisherNode` object.
267
+
* Calls the new method on SimplePublisherNode to construct the node and publisher within the context.
268
+
269
+
3. Thread and Iterator:
270
+
*`let publisher_other_thread = Arc::clone(&publisher);`: Clones the shared publisher pointer for use in a separate thread.
271
+
*`let mut iterator: i32 = 0;`: Initializes a counter variable for message content.
272
+
*`thread::spawn(move || -> () { ... });`: Spawns a new thread with a closure: `iter::repeat(()).for_each(|()| { ... });`: Creates an infinite loop using `iter::repeat`.
273
+
274
+
4. Publishing Loop within Thread:
275
+
276
+
*`thread::sleep(Duration::from_millis(1000));`: Pauses the thread for 1 second (1 Hz publishing rate).
277
+
*`iterator = publisher_other_thread.publish_data(iterator).unwrap();`: Calls the publish_data method on the publisher_other_thread to publish a message with the current counter value. Increments the iterator for the next message.
278
+
279
+
5. Main Thread Spin:
280
+
*`rclrs::spin(publisher.node.clone());`: Keeps the main thread running, processing ROS 2 events and messages. Uses a cloned reference to the node to ensure it remains active even with other threads.
281
+
282
+
</details>
283
+
284
+
Once you have implemented the code, you are ready to run it:
285
+
```
286
+
cd ${MainFolderOfWorkspace}
287
+
colcon build
288
+
source install/setub.bash
289
+
```
290
+
And finally run with:
291
+
```
292
+
ros2 run your_project_name your_project_name
293
+
```
294
+
(Please give your package a better name than me ;) )
70
295
</details>
296
+
<details><summary>Having several Ros2 rust nodes in one Package</summary>
297
+
298
+
Of course, you can write for each node you want to implement its own package, and that can have it's advantages. I implore you to use some cargo tricks and add some binary targets to your `cargo.toml`. this could look like this:
299
+
```
300
+
[package]
301
+
name = "your_package_name"
302
+
version = "0.1.0"
303
+
edition = "2021"
304
+
305
+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
306
+
[[bin]]
307
+
name="simple_publisher"
308
+
path="src/main.rs"
309
+
[dependencies]
310
+
rclrs = "*"
311
+
std_msgs = "*"
312
+
```
313
+
You'll find the name of your executable and the corresponding file name under the `[[bin]]` tag. As you can see, the filename and the name you want to call your node don't have to match. Please remember to include your executable name with snake_cases. The rust compiler will be a bit grumpy if you don't.
314
+
Now, by recompiling the package from the previous chapter and making it usable:
315
+
```
316
+
cd ${MainFolderOfWorkspace}
317
+
colcon build
318
+
source install/setub.bash
319
+
```
320
+
node will look like this:
321
+
```
322
+
ros2 run your_package_name simple_publisher
323
+
```
324
+
As you can see, you are now calling your node by the name declared in `[[bin]]` using the `name` variable.
325
+
</details>
326
+
<details><summary>Write the subscriber node</summary>
327
+
328
+
Of course, you can implement a new ros2 rust package for this node. You can find out how to do this in the section called 'Create a package'.
329
+
Or you can add a new binary target to your package. Then just add a new `<file>.rs` to your source directory - for simplicity I'll call this file `simple_subscriber.rs` - and add a corresponding binary target to your `Cargo.toml`:
0 commit comments