# 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"/>
</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 of rospy.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 to random_gen, and the node name of adder.py to adder.
• 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): If screen, stdout/stderr will be displayed on the screen. If log, 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): If true, the node is restarted when it quits (such as running to the end without rospy.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
/
random_gen (lab5/caller.py)

ROS_MASTER_URI=http://localhost:11311

process[random_gen-1]: started with pid [11373]
[INFO] [1563793985.683732]: Generated [8, 9], sending addition request...
[INFO] [1563793986.685175]: Generated [3, 3], sending addition request...
[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 the rospy.wait_for_service('calc') line in caller.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 have output=screen for this node. For adder.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 kill random_gen this way, then random_gen will restart by itself due to the respawn 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

• 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!
• 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).

#### 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>. • value="value" (optional) • Argument value. Cannot be combined with default attribute. • This value cannot be overridden. It is designed to be fixed. • $(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.
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.
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.

• 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.