ROS Package
In this section you will:
create a new ros package
write a node in
python
run a node via
ros2 run
use the tools
ros2 node info
andros2 topic echo
Attention
The following tutorial is not meant as a step-by-step solution for the first assignment. These are just toy examples to demonstrate how to use ROS and interact with the simulated BlueROV in an easy to follow manner. Therefore, we do not claim that these code snippets are complete and we use some funny names at times. Please do not copy-paste them.
Hint
The ROS documentation contains detailed tutorials and descriptions if you need them.
Create A Package
If you have completed all the installation instructions in Install ROS, you have already created a Catkin workspace at ~/fav/ros2
.
The workspace contains several directories:
~/fav/ros2
├── build
├── install
├── log
└── src
Probably the only one you will be working with is the src
directory. This is the place where the so called packages are.
For our code directories to be recognized as packages, two files are required:
package.xml
CMakeLists.txt
Otherwise a package is nothing but a normal directory containing arbitrary files and subdirectories.
Packages can also be in arbitrary subdirectories, since the build system will look for directories containing a package.xml
recursively to identify packages.
An example for this is the fav
repository you cloned during the setup instructions.
The fav
directory itself is not a package, but contains packages as subdirectories like keyboard_control
or scenario_msgs
.
For this guide it is not necessary to go into details too much. But if you like to know more about packages, you can read the article about packages in the ROS Docs.
Hint
In ROS2, there is the option to create native Python packages. For this class, we stick to the CMake way of organizing packages. Even if we write pure Python packages. So in this regard, our instructions differ from the official documentation.
Go to the src
directory
$ cd ~/fav/ros2/src
and create the package directory
$ mkdir awesome_package
Remember, we need at least package.xml
and CMakeLists.txt
.
Almost minimal examples are presented in the following.
Take a look at the highlighted lines.
Replace the project’s name with your own package name.
1cmake_minimum_required(VERSION 3.5)
2project(awesome_package)
3find_package(ament_cmake REQUIRED)
4find_package(ament_cmake_python REQUIRED)
5find_package(rclpy REQUIRED)
6
7install(PROGRAMS
8 DESTINATION lib/${PROJECT_NAME}
9)
10
11ament_package()
1<?xml version="1.0"?>
2<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
3<package format="3">
4 <name>awesome_package</name>
5 <version>0.0.0</version>
6 <description>Our super awesome package</description>
7
8 <maintainer email="someones.mail.address@tuhh.de">Someones name</maintainer>
9
10 <!-- One license tag required, multiple allowed, one license per tag -->
11 <!-- Commonly used license strings: -->
12 <!-- BSD, MIT, Boost Software License, GPLv2, GPLv3, LGPLv2.1, LGPLv3 -->
13 <license>GPLv2</license>
14
15 <url type="website">hippocampusrobotics.github.io/docs</url>
16
17 <author email="someones.mail@tuhh.de">Someones name</author>
18
19 <buildtool_depend>ament_cmake</buildtool_depend>
20 <buildtool_depend>ament_cmake_python</buildtool_depend>
21
22 <depend>rclpy</depend>
23
24 <!-- The export tag contains other, unspecified, tags -->
25 <export>
26 <build_type>ament_cmake</build_type>
27 </export>
28</package>
That’s it. You have just created your first package. Your package structure should look similar to:
~/fav/ros2/src
└── awesome_package
├── CMakeLists.txt
└── package.xml
We can now build our workspace
$ build_ros
and source the newly created package.
$ . ~/.zshrc
This only needs to be done once a new package is created.
Nothing bad happens if we are a bit overly cautious regarding sourcing our .zshrc
.
But it does nothing good either.
So we might want to save it up for the cases where it is actually required.
Note
Remeber to either close and reopen all terminals or source in each terminal individually.
If the commands mentioned above completed without errors, we can check if our newly created is detected correctly. The following command should give as the installation path of our package.
$ ros2 pkg prefix awesome_package
In case things did not work out as expected, we might get Package not found
as response.
This indicates that we (most likely) messed something up while following the instructions above.
Double check everything and if this does not fix the problem ask your favorite research associate.
Write A Node
In general, you have the choice to write nodes either in Python or in C++. For the sake of simplicity we recommend Python. If you haven’t already worked with one of these languages, in some regards Python might feel similiar to Matlab.
Before we can write a node, we create a nodes/
directory to keep things neat and clean.
It is not strictly required (ROS will find your node as long as it is in your package, no matter in which subdirectory it is), but it complies with conventions.
Right click awesome_package
and choose New Folder and name it nodes
. Right click nodes
and choose New File. Name it setpoint_publisher.py
. It should open automatically.
We have to make the Python file executable. To do so, enter the following command in your terminal (for example the integrated one in VS Code):
$ chmod +x ~/fav/ros2/src/awesome_package/nodes/setpoint_publisher.py
Hint
Just in case the integrated terminal is not open: You can open it with Ctrl + Shift + `.
The general syntax is chmod +x PATH_TO_THE_FILE
.
Note
Each time you create a new node, make sure you have made it executable.
The first line of your node needs to be:
1#!/usr/bin/env python3
so your system knows your file should be executed as a Python file.
Your first node could look like:
1#!/usr/bin/env python3
2
3import math
4
5import rclpy
6from hippo_control_msgs.msg import ActuatorSetpoint
7from rclpy.node import Node
8
9
10class MyFirstNode(Node):
11
12 # The __init__ function gets called when we create the object, i.e.
13 # when we run something like:
14 # node = MyFirstNode()
15 def __init__(self):
16 # Nodes need unique names, therefore we initialize the node
17 # with a name not used yet.
18 super().__init__(node_name='my_first_node')
19
20 # Create publishers. We need to specify the message type and
21 # the topic name. The last argument specifies the queue length.
22 self.thrust_pub = self.create_publisher(ActuatorSetpoint,
23 'thrust_setpoint', 1)
24 self.torque_pub = self.create_publisher(ActuatorSetpoint,
25 'torque_setpoint', 1)
26
27 # Create a timer. We use it to call a function with a defined rate.
28 # In this case we want to publish the setpoints with 50 Hz.
29 # The name of the function we want to call is given as the second
30 # argument.
31 self.timer = self.create_timer(1 / 50, self.on_timer)
32
33 def on_timer(self):
34 self.publish_setpoints()
35
36 def publish_setpoints(self):
37 # create the message object
38 thrust_msg = ActuatorSetpoint()
39
40 # get the current time for the message's timestamp
41 now = self.get_clock().now()
42 thrust_msg.header.stamp = now.to_msg()
43
44 # get the time as floating point number in seconds to use for
45 # calculating thrust and torque values in this toy example
46 t = now.nanoseconds * 1e-9
47
48 # fill the message object's fields
49 thrust_msg.x = 0.5 * math.sin(t)
50 thrust_msg.y = -0.5 * math.sin(t)
51 thrust_msg.z = 0.5 * math.cos(t)
52
53 # repeat for torque setpoint message
54 torque_msg = ActuatorSetpoint()
55 torque_msg.header.stamp = now.to_msg()
56 torque_msg.x = 0.4 * math.sin(t)
57 torque_msg.y = -0.4 * math.sin(t)
58 torque_msg.z = 0.4 * math.cos(t)
59
60 # publish the messages using the publishers we created during
61 # the object initialization
62 self.thrust_pub.publish(thrust_msg)
63 self.torque_pub.publish(torque_msg)
64
65
66def main():
67 rclpy.init()
68 node = MyFirstNode()
69 rclpy.spin(node)
70
71
72if __name__ == '__main__':
73 main()
Run A Node
Attention
For each node we have to modify the CMakeLists.txt
of the corresponding package. Add the node’s path relative to the package’s root to the install()
call.
For our first node we add the highlighted line to the CMakeLists.txt
.
install(PROGRAMS
nodes/setpoint_publisher.py
DESTINATION lib/${PROJECT_NAME}
)
Every time you modify the CMakeLists.txt
rebuild your workspace with build_ros
and to be super save you might also want to resource your workspace setup with . ~/.zshrc
.
The latter is only required if we added a new node.
If you want to run a Python program, normally you would use a command like python3 /path/to/your/file/python_file.py
.
This would work for our node, too.
But instead of running our node by entering python ~/fav/ros2/src/awesome_package/nodes/setpoint_publisher.py
, where we have to explicitly tell Python where it can find our file setpoint_publisher.py
, we can use ros2 run
. One of the advantages of ros2 run
is that we do not have to know where the program/node is that we want to run.
The command finds the source file on its own.
The general usage of the ros2 run
command is ros2 run <package_name> <executable_name>
. So for our awesome_package
and its setpoint_publisher.py
it would be:
$ ros2 run awesome_package setpoint_publisher.py
If you try to do so right now, you will likely get an error message No executable found
.
We created a package, but we haven’t built our workspace since we modified CMakeLists.txt
(remember, that we are supposed to rebuild our workspace each time we modify this file?).
$ build_ros
Note
Every time we create a new package, or create a new node in an existing package, we need to build our workspace with build_ros
and apply the updated package paths with . ~/.zshrc
.
Now, we should be ready to finally run our code
$ ros2 run awesome_package setpoint_publisher.py
Hint
You can use Tab to use the shell’s ability to auto-complete your commands. If the auto-completion is unambigous, a single hit will suffice. If there is more than one auto-complete option, hit Tab twice to show the different options.
Hint
Just to remind you: you stop running programs in a terminal by the shortcut Ctrl + C.
In the node’s source code you can see that the sent setpoints are sin
and cos
signals.
We have started the setpoint_publisher.py
node.
But since it just publishes ROS messages we can’t see any output in the terminals.
We can use command line tools ros2 node
and ros2 topic
to get some insights on what is going on in the background hidden from our curious eyes.
With ros2 node info /name/of/our/node
we can get various information on our node. For example what publications and what subscriptions it has.
Or in other words: what are the topics the node wants to receive data on and what are the topics it ouputs data on.
To get a list of all nodes, we run
$ ros2 node list
which in our case should yield:
/my_first_node
To get more information on this node, we run
$ ros2 node info /my_first_node
which in turn yields:
/my_first_node
Subscribers:
Publishers:
/parameter_events: rcl_interfaces/msg/ParameterEvent
/rosout: rcl_interfaces/msg/Log
/thrust_setpoint: hippo_control_msgs/msg/ActuatorSetpoint
/torque_setpoint: hippo_control_msgs/msg/ActuatorSetpoint
Service Servers:
/my_first_node/describe_parameters: rcl_interfaces/srv/DescribeParameters
/my_first_node/get_parameter_types: rcl_interfaces/srv/GetParameterTypes
/my_first_node/get_parameters: rcl_interfaces/srv/GetParameters
/my_first_node/get_type_description: type_description_interfaces/srv/GetTypeDescription
/my_first_node/list_parameters: rcl_interfaces/srv/ListParameters
/my_first_node/set_parameters: rcl_interfaces/srv/SetParameters
/my_first_node/set_parameters_atomically: rcl_interfaces/srv/SetParametersAtomically
Service Clients:
Action Servers:
Action Clients:
Hint
Again, we can use Tab to auto-complete the node name after we have started writing the first few characters. Start using this feature if you haven’t already!
The first two publishers are internally created by ROS2. We do not care about them for now. The other publishers are the ones we have created with the program that we have written.
To see what messages the node is actually publishing, we could use ros2 topic echo /the/topic/name/to/echo
.
Note
We ad --once
at the end of the command to echo only a single message. If you omit this argument, ros2 topic echo
will continue to print messages until you stop it with Ctrl + C.
These two commands are great to get at least some insights on what is going on during the execution of our node. But most of us will find it rather cumbersome to evaluate the echoed data in realtime. I mean, would you claim to be able to see that the echoed data is actually the output of a sine function? So, some proper plotting tool might come in handy here.
We can use plotjuggler
to visualize the data.
General information to plotjuggler
can be found on the GitHub Page and some step-by-step instructions in the section Real-Time Plotting.