Lab 6.2. ROS Launchfile and Param
Estimated time to complete this lab: 2 hours
Objectives
At the end of this self-learning lab, you should be able to:
- create and use a ROS launchfile
- use ROS parameters in nodes
Preparations
We will continue to use the lab5 package used in the previous lab. If you have downloaded the package into your workspace, you can skip the preparation.
See 05 L1 :: Preparations for the preparations.
Motivation. Launchfile: Why do we need a launchfile?
- A large application on a robot (e.g. Robocon) typically involves several interconnected nodes, each of which may have many parameters.
- It is tedious for us to rosrun each node individually as many commands are needed.
- By using a launchfile which contains the nodes and parameters to be run, we just have to run a single launchfile with a single command which runs all the nodes in the file.
Section 1. Using a launchfile to run multiple nodes at once
- A launchfile is structured as an XML file, and have the extension
.launch
- A package may have multiple launchfiles, and they are typically placed in the
/launch
subdirectory (as opposed to/src
for nodes) - Let’s have a look at a launchfile used to launch two nodes at once:
lab5/launch/demo1.launch
<launch>
<node name="random_gen" pkg="lab5" type="caller.py" output="screen" respawn="true"/>
<node name="adder" pkg="lab5" type="adder.py"/>
</launch>
Explanations
In this lab, we assume that you know how XML is structured. Please make sure that you know XML (elements/tags and attributes) before reading the following. If you have written HTML before, it will be enough.
<launch>
- You need to wrap the file with the
<launch>
tag if you are writing a launchfile. <node>
-
The
<launch>
tag is used to run a node (similar to rosrun).name="node_name"
(compulsory): Specifies the name of the node, overriding that ofrospy.init_node(...)
. Note that you have to state a node name in both the node source file and launchfile.- Here, we set the node name of
caller.py
torandom_gen
, and the node name ofadder.py
toadder
.
- Here, we set the node name of
pkg="package_name"
(compulsory): Specifies the package that the node resides in.type="node_type"
(compulsory): For Python nodes, write the filename of the script (ending in.py
). For C++ nodes, write the filename of the executable after catkin_make.-
output="log|screen"
(optional, default: log): Ifscreen
, stdout/stderr will be displayed on the screen. Iflog
, the stdout/stderr output will be sent to a log file in$ROS_HOME/log
, and stderr will continue to be sent to screen.Warning
Note that setting this to log (or omitting this tag so it is log by default) means that there will be no console output for
rospy.loginfo()
. -
respawn="true|false"
(optional, default:false
): Iftrue
, the node is restarted when it quits (such as running to the end withoutrospy.spin()
or runtime error).Warning
If respawn is set to true, you might find it difficult to kill the node using
rosnode kill
if the node malfunctions (e.g. unintended infinite loop).
Try it yourself: Launching a launchfile
To launch a launchfile in the command line, use roslaunch [package] [launchfile_name]
.
Let’s launch demo1.launch
.
Actually, you can even use roslaunch without running roscore on a separate terminal. This is because roslaunch will run a roscore if there is no roscore detected.
# kill roscore first
$ roslaunch lab5 demo1.launch
... logging to /home/m2/.ros/log/a4e60548-ac71-11e9-ac27-080027ca7c17/roslaunch-m2-null-11351.log
Checking log directory for disk usage. This may take awhile.
Press Ctrl-C to interrupt
Done checking log file disk usage. Usage is <1GB.
started roslaunch server http://m2-null:34399/
SUMMARY
========
PARAMETERS
* /rosdistro: kinetic
* /rosversion: 1.12.14
NODES
/
adder (lab5/adder.py)
random_gen (lab5/caller.py)
ROS_MASTER_URI=http://localhost:11311
process[random_gen-1]: started with pid [11373]
process[adder-2]: started with pid [11374]
[INFO] [1563793985.683732]: Generated [8, 9], sending addition request...
[INFO] [1563793985.699416]: Received response: 17
[INFO] [1563793986.685175]: Generated [3, 3], sending addition request...
[INFO] [1563793986.711000]: Received response: 6
^C[adder-2] killing on exit
[random_gen-1] killing on exit
shutting down processing monitor...
... shutting down processing monitor complete
done
Note that the launchfile will run the nodes adder
and random_gen
. These nodes are obtained from the <node>
tag in the demo1.launch
file.
- If you get an error of
service [/calc] unavailable
, you can fix this error by adding therospy.wait_for_service('calc')
line incaller.py
.- This error occurred because the nodes are launched “simultaneously”, but with some small delay and the service has not been advertised in time.
- However, due to the
respawn="true"
attribute, caller will restart by itself automatically and there should be no error after restarting.
- You should see that only the output of
caller.py
is printed. This is because we haveoutput=screen
for this node. Foradder.py
, the output is in the ROS log.
We can verify that both nodes are running using rosnode list
:
$ rosnode list
/adder
/random_gen
/rosout
- We can kill a node using
rosnode kill [node]
. If you try to killrandom_gen
this way, thenrandom_gen
will restart by itself due to therespawn
attribute.
$ rosnode kill random_gen
killing /random_gen
killed
$ rosnode list
/adder
/random_gen
/rosout
[INFO] [1587198241.506430]: Generated [2, 6], sending addition request...
[INFO] [1587198241.512730]: Received response: 8
shutdown request: user request
[random_gen-2] process has finished cleanly
log file: /home/m2/.ros/log/b561d1a2-814d-11ea-a3a3-1c1b0d98dfee/random_gen-2*.log
[random_gen-2] restarting process
process[random_gen-2]: started with pid [18883]
[INFO] [1587198242.843338]: Generated [6, 7], sending addition request...
[INFO] [1587198242.851440]: Received response: 13
- However, if you try to kill adder without adding the
wait_for_service()
line, then both nodes will die. adder is killed by the user, while random_gen terminates and respawns repeatedly due to a runtime error of not being able to call the service.
$ rosnode kill adder
killing /random_gen
killed
$ rosnode list
/rosout
[INFO] [1587198515.829192]: Generated [10, 1], sending addition request...
[INFO] [1587198515.835323]: Received response: 11
[adder-3] process has finished cleanly
log file: /home/m2/.ros/log/96b4d0d2-814e-11ea-a3a3-1c1b0d98dfee/adder-3*.log
[INFO] [1587198516.829340]: Generated [2, 5], sending addition request...
... (error messages)
[random_gen-2] restarting process
process[random_gen-2]: started with pid [19418]
[INFO] [1587198517.324029]: Generated [6, 7], sending addition request...
... (error messages)
... (restarts and errors repeatedly)
Section Check Box:
- What is a launchfile
- How to run multiple nodes at once in a launchfile:
name
,pkg
,type
,output
,respawn
attributes - To launch a launchfile in the command line, use
roslaunch [package] [launchfile_name]
-
rosnode kill
Section 2. Nested launchfiles, Parameters and Arguments in launchfile
Sometimes we want a node to run with some information passed into it.
For example, we need to specify the port of a sensor when we run a node that handles sensor communication.
- If we “hardcode” the port in the sensor source code (.py), we need to change the code every time we change to a new port. This is not a good practice under version control, as the node may be used in other situations on other ports as well.
- A better alternative to be to pass in the parameter as a string parameter into the sensor node, and change the string parameter for different port names.
Example arguments and parameters in launchfile
Let’s look at lab5/launch/demo2.launch
and lab5/launch/demo3.launch
:
lab5/launch/demo2.launch
<launch>
<param name="global_example" value="global value" />
<node name="param_talker" pkg="lab5" type="param_talker.py" output="screen">
<param name="utterance" value="Hello World!" />
<param name="gains/kP" value="1.0" />
<param name="gains/kI" value="2.0" />
<param name="gains/kD" value="3.0" />
</node>
<include file="$(find lab5)/launch/demo3.launch">
<arg name="port" value="12:34:56:78" />
</include>
</launch>
lab5/launch/demo3.launch
<launch>
<arg name="port" default="00:00:00:00" />
<node name="arg_talker" pkg="lab5" type="arg_talker.py" output="screen">
<param name="port" type="str" value="$(arg port)" />
</node>
</launch>
If we launch demo2.launch, you should see some parameters in PARAMETERS
, and also the nodes in demo3.launch
are also run.
$ roslaunch lab5 demo2.launch
... (omitted)
SUMMARY
========
PARAMETERS
* /arg_talker/port: 12:34:56:78
* /global_example: global value
* /param_talker/gains/kD: 3.0
* /param_talker/gains/kI: 2.0
* /param_talker/gains/kP: 1.0
* /param_talker/utterance: Hello World!
* /rosdistro: kinetic
* /rosversion: 1.12.14
...(omitted)
Explanations
ROS Parameter Server
Info
See also: http://wiki.ros.org/rospy/Overview/Parameter%20Server
- A parameter server is used by nodes to store and retrieve parameters at runtime.
- As it is not designed for high-performance, it is best used for static, non-binary data such as configuration parameters.
- Parameters are stored in ROS with a hierarchical scheme that allows parameters to be accessed individually or as a tree.
- E.g. There is a parameter at
/param_talker/utterance
which stores the value Hello World!
- E.g. There is a parameter at
- Parameters are implemented as a dictionary, so in Python you can access a whole subtree of parameters as a dictionary (of dictionaries). More details below.
- Common parameter types include: 32-bit integers, Booleans, strings, doubles, lists
- Try to compare the parameters and values in the launchfile with the console output to see how the parameter tree corresponds to the launchfile.
- In particular, notice that if we define the parameter within a
<node>
tag, the tree goes down 1 level. - In ROS, this “level” is called a “namespace”.
- If the
<param>
tag is placed inside a<node>
tag, then that parameter is also called a private parameter. However, these parameters can also be accessed by other nodes by specifying the correct parameter path. - Specifying the correct namespace is very important when accessing the parameters in code (see next section).
- In particular, notice that if we define the parameter within a
Tags
<param>
-
This tag defines a parameter to be set on the Parameter Server.
For details, see http://wiki.ros.org/roslaunch/XML/param.
Details
name="namespace/name"
- Parameter name. Namespaces can be included in the parameter name, but globally specified names should be avoided.
value="value"
type="str|int|double|bool|yaml"
- Specifies the type of the parameter. If you don't specify the type, roslaunch will attempt to automatically determine the type.
- If you want to use a list parameter, use the
<rosparam>
tag instead. For details, see http://wiki.ros.org/roslaunch/XML/rosparam.
<include>
-
This tag is commonly used to include another “nested” launchfile within the current launchfile. You can also pass arguments (
<arg>
) into the nested launchfiles.For details, see http://wiki.ros.org/roslaunch/XML/include.
Details
file="$(find lab5)/launch/demo3.launch"
:- Find the absolute path of the lab5 package, and then the launchfile.
<arg name="port" value="12:34:56:78"/>
:- This passes an argument to the included launchfile. Here, it passes a string port address to the nested launchfile via the argument joy.
<arg>
-
This tag defines an argument to be used in specifically to this single launchfile. Args are not global, and you must explicitly pass arg values to an included file.
For details, see http://wiki.ros.org/roslaunch/XML/arg.
Details
name="arg_name"
default="default value"
(optional)- Default value of argument. Cannot be combined with
value
attribute. - This value can be overridden by the terminal or
<include>
.
- Default value of argument. Cannot be combined with
value="value"
(optional)- Argument value. Cannot be combined with
default
attribute. - This value cannot be overridden. It is designed to be fixed.
- Argument value. Cannot be combined with
$(arg arg_name)
- This is a substitution arg that can be used in a string. It substitutes the whole string for the value in arg_name.
Section Check Box:
- Why we need to use parameters
- Arguments and parameters in launchfile
- Nested launchfiles
Section 3. Parameters in rospy
Once a parameter is passed into a node, we will want some way to retrieve its value inside the node source code.
We can use rospy.get_param('[path name]')
to get a value from the parameter server.
lab5/src/param_talker.py
#!/usr/bin/env python
import rospy
rospy.init_node('param_talker')
global_example = rospy.get_param('/global_example') # global value
utterance = rospy.get_param('~utterance') # Hello World!
default_param = rospy.get_param('default_param', "default_value") # default_value
# fetch a group (dictionary) of parameters
gains = rospy.get_param('~gains')
p, i, d = gains['kP'], gains['kI'], gains['kD']
rospy.loginfo("gains are %s, %s, %s", p, i, d)
rospy.spin()
global_example = rospy.get_param('/global_example')
-
- If the path starts with
/
, then it will find the param from the root of the parameter server (global namespace).- In this launch, it will find
/global_example
.
- In this launch, it will find
- If the path starts with
utterance = rospy.get_param('~utterance')
-
- If the path starts with
~
, then it will find the param from the private namespace of the node.- In this launch, it will find
/param_talker/utterance
.
- In this launch, it will find
- If the path starts with
default_param = rospy.get_param('default_param', "default_value")
-
-
If the path doesn’t start with
/
or~
, then it will find the param from the parent namespace.- In this launch, it will find from the root namespace (
/default_param
).
Bug
As the parent parameter namespace can be confusing, in M2, please avoid using this setting for param.
- In this launch, it will find from the root namespace (
-
Here, we also demonstrate how to use the default value of a parameter. We can also set a default value to the param if it is not found in the parameter server.
-
gains = rospy.get_param('~gains')
- An example of how to use the “dictionary” behavior of the parameter server namespace (tree) implementation.
Section Check Box:
- We can use
rospy.get_param('[path name]')
to get a value from the parameter server. - Difference between different parameter namespaces.
Section 4. Parameters and Arguments in command line
In rosrun, we can pass a ~parameter
using the following syntax (replace the ~
with _
): rosrun package node _parameter:=value
$ rosrun lab5 arg_talker.py _port:=123
[INFO] [1563874522.652596]: /arg_talker/port is 123
In roslaunch, we can pass an argument using the following syntax: roslaunch package launchfile argument:=value
$ roslaunch lab5 demo3.launch port:=246
... (omitted)
[INFO] [1563874923.881609]: /arg_talker/port is 246
Section Check Box:
-
rosrun package node _parameter:=value
-
roslaunch package launchfile argument:=value
Checklist and Assignment
Congratulations! You have successfully learnt how to use a launchfile and param!
- Create a launchfile
- Use params in Python
This marks the end of the ROS labs. The basic ROS techniques have already been covered, but there is still a lot to learn in M2!
If you are interested, you can also read this article on how to write good roslaunch files.
Reference
- ROS tutorial: Understanding rqt_console and roslaunch: http://wiki.ros.org/ROS/Tutorials/UsingRqtconsoleRoslaunch#Using_roslaunch
- ROS documentation: roslaunch/Commandline Tools: http://wiki.ros.org/roslaunch/Commandline%20Tools
-
ROS documentation: roslaunch/XML: http://wiki.ros.org/roslaunch/XML
-
ROS wiki: Parameter server: http://wiki.ros.org/Parameter%20Server
- ROS documentation: rospy/overview: Parameter server: http://wiki.ros.org/rospy/Overview/Parameter%20Server
-
rospy tutorials: Using Parameters in rospy: http://wiki.ros.org/rospy_tutorials/Tutorials/Parameters
-
m2robocon 2020 training materials: https://github.com/m2robocon/m2prog_training_2020/blob/master/05%20ROS%20Labs/05%20L4%20ROS%20Launchfile%20and%20Param.pdf