Comprehensive and logical explanation of the organization form in ROS: launch file + meta function package (example + code + parameter analysis)

Introduction to ROS workspace

Meta function Pack: organizer of function pack

For the file structure in ROS, we focus on src. After understanding the file structure of ROS, we know that the src directory can contain multiple function packages. Suppose we need to use the robot navigation module, but this module includes map, positioning, path planning And other functional packages, and their logical relationship is as follows:

If I want to get the "navigation function", I need to start the function package one by one according to the dependencies between the function packages, which is too troublesome. In the Linux system, the concept of "meta function package" appears in order to organize the engineering project more conveniently (here, the project file, i.e. function package). This is a "virtual package", that is, there is no source file in the src directory of this function package, so it will not realize its own exclusive functions. The realization of its functions completely depends on other function packages and plays the role of organizing function packages.

Let's take the meta function package in the navigation module as an example:

All the function packages of the navigation module are shown in the above picture. Among them, the navigation function package is a meta function package. Since there is no src directory in the meta function package, there is no need to add any dependencies, because this function package does not have its own exclusive functions, and its functions are realized with the help of the functions of other function packages. The meta function pack can have two files:

One is package XML file: used to declare other function packages that the meta function package depends on; The other is cmakelist Txt file: used to specify dependencies between function packages.

CMakelist.txt (other useless parts must be deleted to prevent error reporting):

cmake_minimum_required(VERSION 3.0.2)  
project(navigation)  
find_package(catkin REQUIRED)  
catkin_metapackage() //Just add this item

package.xml:

<exec_depend>amcl</exec_depend>  
<exec_depend>base_local_planner</exec_depend>  
<exec_depend>carrot_planner</exec_depend>  
<exec_depend>clear_costmap_recovery</exec_depend>  
<exec_depend>costmap_2d</exec_depend>  
<exec_depend>dwa_local_planner</exec_depend>  
<exec_depend>fake_localization</exec_depend>  
<exec_depend>global_planner</exec_depend>  
<exec_depend>map_server</exec_depend>  
<exec_depend>move_base</exec_depend>  
<exec_depend>move_base_msgs</exec_depend>  
<exec_depend>move_slow_and_clear</exec_depend>  
<exec_depend>navfn</exec_depend>  
<exec_depend>nav_core</exec_depend>  
<exec_depend>rotate_recovery</exec_depend>  
<exec_depend>voxel_grid</exec_depend>  
  
<export>  
    <metapackage/> // Characterization: this function package is a meta function package
</export>  

Among them, < export >... < / export > indicates that this function package is a meta function package.

Launch file: the organizer of the source file

After that, let's talk about how to organize many source files in a function package. If we start one node by one, it will be time-consuming and labor-consuming. Launch can help us solve this problem. Here is an explanation of the preparation of the following launch files (taking the turtle node of the ROS system as an example):

① Node startup label

<launch>  
    <node pkg = "turtlesim" type = "turtlesim_node" name = "my_node"/>  
    <node pkg = "turtlesim" type = "turtle_teleop_key" name = "my_key"/>  
</launch>  

Note: because multi threading is used in ROS, the operation of nodes will not be carried out according to the order of nodes in the launch.

In the launch file, they are in the following form:

<launch>  
       ...  
</launch>  

< launch > is the root tag of the launch file. All tags in the file should be written between < launch >... < / launch >. One thing to remember: roslaunch package_ name launch_ file_ name. Before launching, you must save the launch file, otherwise the launch you wrote will not work, and the roslaunch command will automatically start roscore without our operation, and running the roslaunch command does not require us to compile our ROS project repeatedly. In fact, the launch tag also has a child tag deprecated, which is used for text description:

<launch deprecated="this vision is out-of-date!">  
</launch> 

The results are as follows:

In fact, if we think it's too troublesome to name many nodes, we can use the name = "$(anon node_name)" label on the node_ Add some random numbers after the name to make the node name unique in the whole catkin compilation project:

<launch deprecated="this vision is out-of-date!">  
    <!-- the topic of turtlesim_node is /turtle1/cmd_vel -->  
    <node pkg="turtlesim" type="turtlesim_node" name="$(anon my_node)"/>  
    <!-- the topic of turtle_teleop_key is /turtle1/cmd_vel -->  
    <node pkg="turtlesim" type="turtle_teleop_key" name="my_key" output="screen"/>  
</launch>  

The output results are as follows:

In fact, almost all tags can be used as child tags of < node >:

1. Child label automatically started after accidental shutdown

<launch>  
    <node pkg="turtlesim" type="turtlesim_node" name="my_node" respawn="true"/>  
    <node pkg="turtlesim" type="turtle_teleop_key" name="my_key" respawn="true"/>  
</launch>  

Respawn=true|false indicates whether to restart if the node shuts down unexpectedly.

2. Start the child label of the node on the specified machine

We know that ROS is a distributed system, that is, its various parts can be distributed on different machines. We want to start remotely on one machine, and the nodes on another machine can be implemented with this label. First, we need to clarify several parameters:

1) Name = "machine name": get the name of the machine;

2) address="blah.willowgarage.com": network address / host name of the machine;

3) Env loader = "/ opt / ROS / Fuerte / env.sh": Specifies the environment file on the remote machine. The environment file must be a shell script that sets all the necessary environment variables and then runs exec on the supplied parameters. For the sample file, see env.exe provided during debian installation of ROS Fuerte sh;

4)default="true|false|never" (optional): Sets this machine as the default to assign nodes to. The default setting only applies to nodes defined later in the same scope. NOTE: if there are no default machines, the local machine is used. You can prevent a machine from being chosen by setting default="never", in which case the machine can only be explicitly assigned;

5) user="username" (optional): SSH user name of the login machine;

6) Password = "passwhat" (strongly discovered): SSH password. It is strongly recommended that you configure SSH key and SSH proxy so that you can use certificate instead of login;

7) timeout="10.0" (optional): the maximum time allowed to start a node remotely.

Examples are as follows: (configure the machine information first and start the node with < node machine = "machine name"... / >)

<launch>  
  <machine name="foo" address="foo-address" env-loader="/opt/ros/fuerte/env.sh" user="someone"/>  
  <node machine="foo" name="footalker" pkg="test_ros" type="talker.py" />  
</launch>  

For details, see: roslaunch/XML/machine - ROS Wikihttp://wiki.ros.org/roslaunch/XML/machine

3. Child label of node delayed startup

<launch>  
    <node pkg="turtlesim" type="turtlesim_node" name="my_node" respawn_delay="10"/>  
    <node pkg="turtlesim" type="turtle_teleop_key" name="my_key" respawn_delay="10"/>  
</launch>  

Respawn_delay=”delay_time ": indicates that the node delays starting delay_time seconds.

4. Child label of "if XXX node ends running (XXX node is killed), all nodes will stop running"

<launch>  
    <node pkg="turtlesim" type="turtlesim_node" name="my_node" required="true"/>  
    <node pkg="turtlesim" type="turtle_teleop_key" name="my_key" />  
</launch>  

Required=true|false is generally used on very important nodes (once this node stops running, the whole system will be affected). Once the node process ends (the node is killed), all nodes of the whole system will stop running.

5. Add a child label prefixed to the node name (add a namespace to the node)

<launch>  
    <node pkg="turtlesim" type="turtlesim_node" name="my_node" ns="hello"/>  
    <node pkg="turtlesim" type="turtle_teleop_key" name="my_key"/>  
</launch> 

After running the launch file, we use rosnode list to find:

The topic of tortoise node starts from / my_node becomes / hello/my_node. When we started roslaunch, we found that our keyboard could no longer control the little turtle, because when we added a namespace to the node, all attributes of the node, including topic, were also placed under the namespace:

turtlesim_node subscribes to / hello / turtle 1 / CMD_ Vel topic, and turtle_teleop_key publishes / turnle1 / CMD_ Vel topics and topics are different. The server and client cannot communicate normally, so the little turtle is no longer controlled by the keyboard.

6. Child label indicating the output location

<launch>  
    <node pkg="turtlesim" type="turtlesim_node" name="my_node"/>  
    <node pkg="turtlesim" type="turtle_teleop_key" name="my_key" output="screen"/>  
</launch>  

turtle_ teleop_ The message published by key can be output to the console. Output = "screen" | "log". There are two ways to output the message: console output / log output.

7. Specify the child label of the current working directory of the node

<launch>  
    <node pkg="turtlesim" type="turtlesim_node" name="my_node" cwd="ROS_HOME"/>  
    <node pkg="turtlesim" type="turtle_teleop_key" name="my_key" output="screen"/>  
</launch>  

The full name of cwd is current working directory, cwd = "ROS"_ HOME”|”node”. If cwd = "node", the working directory of the node will be set to the same directory as the executable file of the node; Otherwise cwd = "ROS"_ Home ", the working directory of this node is shown in: ROS/EnvironmentVariables - ROS Wikihttp://wiki.ros.org/ROS/EnvironmentVariables

8. Pass the child label of the parameter to the node

<node name="add_two_ints_client" pkg="beginner_tutorials" type="add_two_ints_client" args="$(arg a) $(arg b)" />  

Add to_ two_ ints_ The client node passes two parameters a and b, just as we run the following command:

rosrun beginner_tutorials add_two_ints_client a_value b_value  

② Parameter setting label

<launch>  
    <param name="var" type="int" value="10"/>  
</launch> 

We set global global parameters. We can also set private parameters with namespaces in combination with < node > Tags:

<launch>  
    <node pkg="turtlesim" type="turtlesim_node" name="my_node"/>  
    <node pkg="turtlesim" type="turtle_teleop_key" name="my_key" output="screen">  
        <param name="var1" type="int" value="10"/>  
    </node>  
    <param name="var" type="int" value="10"/>  
</launch> 

In this case, we set / my_ For the parameters in the private namespace key / VAR1, we can see the following parameters using rosparam list:

③ label deleted from parameter packaging input and output

From Read parameters from yaml file:

<launch>  
    <rosparam command="load" file="$(find test01)/launch/params.yaml"/>  
    <node pkg="turtlesim" type="turtlesim_node" name="my_node"/>  
    <node pkg="turtlesim" type="turtle_teleop_key" name="my_key" output="screen"/>  
    <param name="var" type="int" value="10"/>  
</launch> 

Read out the parameters using rosparam list as follows:

Besides, when we will When the parameters in yaml parameter file are imported into the parameter server, we can also add a namespace to these parameters:

<launch>  
    <node pkg="turtlesim" type="turtlesim_node" name="my_node"/>  
    <node pkg="turtlesim" type="turtle_teleop_key" name="my_key" output="screen"/>  
    <param name="var" type="int" value="10"/>  
    <rosparam command="load" file="$(find test01)/launch/params.yaml" ns="hello"/>  
</launch> 

The results are as follows:

Variables are all prefixed with the namespace / hello /.

Pack the parameters into the yaml file:

<launch>  
    <rosparam command="dump" file="$(find test01)/launch/input.yaml"/>  
    <node pkg="turtlesim" type="turtlesim_node" name="my_node"/>  
    <node pkg="turtlesim" type="turtle_teleop_key" name="my_key" output="screen"/>  
    <param name="var" type="int" value="10"/>  
</launch>  

In this way, we should be able to import all the parameters of the function package into the specified yaml file, but our result is:

 Input. There are no variables in the yaml file. We imported a lonely file. This is because rosparam has the highest priority among the launch file commands. No matter where you put the rosparam command, the launch file always performs the import / export / deletion of parameters first. If we want to import parameters, we must create another one Launch file. After starting all nodes with the above launch file, export the function package parameters in another launch file:

<launch>  
    <rosparam command="dump" file="$(find test01)/launch/input.yaml"/>  
</launch> 

At this point, input The data in yaml file is:

Rosparam can not only from yaml exports and imports parameters, or you can directly delete parameters in the parameter server:

<launch>  
    <rosparam command="dump" file="$(find test01)/launch/input.yaml"/>  
    <rosparam command="delete" param="/hello/n1"/>  
</launch>  

Use the above code: delete the parameter "/ hello/n1":

<launch>  
    <rosparam command="dump" file="$(find test01)/launch/input.yaml"/>  
    <rosparam command="delete" ns="hello"/>  
</launch>  

Using the above code: all parameters under the "hello" namespace are deleted:

If there is no child tag, delete all parameters under this package:

<launch>  
    <rosparam command="dump" file="$(find test01)/launch/input.yaml"/>  
    <rosparam command="delete"/>  
</launch>  

The results are as follows:

Rosparam can directly pass array type parameters to the parameter server:

<launch>  
    <node pkg="turtlesim" type="turtlesim_node" name="my_node"/>  
    <node pkg="turtlesim" type="turtle_teleop_key" name="my_key" output="screen"/>  
    <param name="var" type="int" value="10"/>  
    <rosparam command="load" file="$(find test01)/launch/params.yaml" ns="hello"/>  
    <rosparam param="a_list">[1, 2, 3, 4]</rosparam>  
</launch>  

We call rosparam get a_list command line found:

 A_ The list parameter is resolved to an array of type int by the parameter server. You can also pass multiple and different types of parameters to the parameter server in another way:

<launch>  
    <node pkg="turtlesim" type="turtlesim_node" name="my_node"/>  
    <node pkg="turtlesim" type="turtle_teleop_key" name="my_key" output="screen"/>  
    <param name="var" type="int" value="10"/>  
    <rosparam command="load" file="$(find test01)/launch/params.yaml" ns="hello"/>  
    <rosparam param="a_list">[1, 2, 3, 4]</rosparam>  
    <rosparam>  
        a: 9  
        b: "hello"  
        c: [1,2,3,4]  
    </rosparam>  
</launch>

You can also use this method to pass multiple different types of parameters with namespaces to the parameter server:

<launch>  
    <node pkg="turtlesim" type="turtlesim_node" name="my_node"/>  
    <node pkg="turtlesim" type="turtle_teleop_key" name="my_key" output="screen"/>  
    <param name="var" type="int" value="10"/>  
    <rosparam command="load" file="$(find test01)/launch/params.yaml" ns="hello"/>  
    <rosparam param="a_list">[1, 2, 3, 4]</rosparam>  
    <rosparam>  
        a: 9  
        b: "hello"  
        c: [1,2,3,4]  
    </rosparam>  
</launch>

In addition, the value tag can be written in two forms:

<rosparam param="a_list">[1, 2, 3, 4]</rosparam>  
<rosparam param="a_list" value="[1, 2, 3, 4]"/>  

These two forms are equivalent and can be obtained:

In fact, this is the main difference between param and rosparam Tags:

Param tag: pass / modify (overwrite the original parameter is to modify) a parameter to the parameter server without deletion;

Rosparam tag: pass / modify / delete multiple parameters to and from the parameter server Load parameters in yaml file or import parameters of function package Yaml file.

④ Label for unified parameter management

<launch>  
    <arg name="car_width" default="[1,2,3,4]" doc="the width of car"/>  
    <rosparam param="a_list">$(arg car_width)</rosparam>  
    <rosparam>  
        Name:  
            a: 9  
            b: "hello"  
            c: $(arg car_width)  
    </rosparam>  
</launch>

Arg tag is usually used with param tag (not with rosparam tag), where $(arg arg_name) represents < Arg name = "Arg"_ name” default=”default_ Default in "value" doc = "comment description" >_ value. So we change the default_value, and the related value in launch will change. The sub tag format of Arg tag is as follows:

There can only be one default and value tag. They cannot coexist!

In addition, the reason why we use default instead of value tag is "when we use default, if we do not pass parameters through the command line, $(arg arg_name) is default_value; when we pass parameters through the command line, the parameters we pass will override the default parameter value in $(arg arg_name)", so our parameters become very flexible. As shown below, we use the command line to pass parameters:

The command line parameter format is arg_name:=set_value. The Launch file is as follows:

<launch>  
    <arg name="car_width" default="[1,2,3,4]" doc="the width of car"/>  
    <param name="var" type="int" value="$(arg car_width)"/>  
</launch>  

We call the rosparam get var command on the command line:

However, when you combine the arg tag with the rosparam tag, you will not get the result you want:

<launch>  
    <arg name="car_width" default="[1,2,3,4]" doc="the width of car"/>  
    <rosparam param="a_list" value="$(arg car_width)"/>  
</launch>  

We call rosparam get a on the command line_ List command:

What we want is a number "2", but this variable is assigned a string.

⑤ Change the label of topic name

<launch>  
    <!-- the topic of turtlesim_node is /turtle1/cmd_vel -->  
    <node pkg="turtlesim" type="turtlesim_node" name="my_node"/>  
    <remap from="/turtle1/cmd_vel" to="new_topic"/>  
</launch>  

The format of Remap tag is:

Remap's essence:

We use restopic info topic_name can know that topic: / turtle 1 / CMD_ Topic: / new mapped from vel_ Topic is essentially the same, but with different names. Remap tags are used to:

The premise of Remap is that the data types of topic1 and topic2 are the same, but the names are different. In this way, the two topics are remapped to new_topic topic enables the communication parties to establish contact. Remember: topic remapping is to generate a new topic with the same data type but different name, not to modify the original topic!

⑥ Node organization label

<launch deprecated="this vision is out-of-date!">  
    <group ns="family">  
        <!-- the topic of turtlesim_node is /turtle1/cmd_vel -->  
        <node pkg="turtlesim" type="turtlesim_node" name="my_node"/>  
        <!-- the topic of turtle_teleop_key is /turtle1/cmd_vel -->  
        <node pkg="turtlesim" type="turtle_teleop_key" name="my_key" output="screen"/>  
        <rosparam command="load" file="$(find test01)/launch/params.yaml" ns="hello"/>  
        <arg name="car_width" default="[1,2,3,4]" doc="the width of car"/>  
        <rosparam param="a_list" value="$(arg car_width)"/>  
        <rosparam>  
            Name:  
                a: 9  
                b: "hello"  
                c: [1,2,3,4]  
        </rosparam>  
        <param name="var" type="int" value="$(arg car_width)"/>  
    </group>  
</launch> 

The result is to add a namespace to all parameters and node attributes contained in < group >... < / group >. For parameters:

For node attributes (such as topics subscribed / published by nodes):

Another parameter of the < group > tag is:

It is used to determine whether to delete all parameters under the specified namespace before startup, which is similar to the previous < rosparam command = "delete" / > function.

⑦ Label for launching other launch files

<launch>  
    <include file="$(find test01)/launch/test01_launch.launch">  
        <arg name="car_width" default="10"/>  
    </include>  
</launch>  

Under the Include tag, there are two child tags arg and env for loading environment variables. We generally use arg to pass parameters. The launch file loaded by the launch is as follows:

<launch deprecated="this vision is out-of-date!">  
    <!-- the topic of turtlesim_node is /turtle1/cmd_vel -->  
    <node pkg="turtlesim" type="turtlesim_node" name="my_node"/>  
    <!-- the topic of turtle_teleop_key is /turtle1/cmd_vel -->  
    <node pkg="turtlesim" type="turtle_teleop_key" name="my_key" output="screen"/>  
    <rosparam command="load" file="$(find test01)/launch/params.yaml" ns="hello"/>  
    <arg name="car_width" default="[1,2,3,4]" doc="the width of car"/>  
    <rosparam param="a_list" value="$(arg car_width)"/>  
    <rosparam>  
        Name:  
            a: 9  
            b: "hello"  
            c: [1,2,3,4]  
    </rosparam>  
    <param name="var" type="int" value="$(arg car_width)"/>  
</launch>

The loaded launch file can be run only after entering parameters on the command line. We add the < Arg... / > child tag under the < include >... < / include > tag, which is actually equivalent to passing parameters on the command line. We pay attention to reuse rather than replication when coding. Therefore, the include tag helps us improve the efficiency of code reuse and makes the code more concise. It is also equivalent to declaring the dependencies between launch files, that is, the order of launch.

The include tag also has a sub tag, which is very useful. He is pass_all_args: this tag indicates whether I need to load all the parameters set with arg in the launch file into the launch included with the include tag:

<launch>  
    <arg name="car_width" default="10"/>  
    <include file="$(find test01)/launch/test01_launch.launch" pass_all_args="true"/>  
</launch> 

The effect of this is the same as that of the previous code, but if I include many launches, I just need to pass_all_args="true", then all parameters in the launch will be automatically used as the parameters of "included launch", so we don't need to add so many arg child tags under the include tag to add parameters to each included launch. How wonderful it looks!

Note: we can use "include tag + child tag" to package all launch files into one launch file according to the logical startup order, so that we can start all nodes of the whole function package by starting only one launch.

Function pack / source file / launch file organization tool

Function pack organization tool

Meta function package

Organization tool for node files (. cpp source files)

Launch file

Organization tools for Launch files

Launch file

Level by level management. The document organization form is as follows:

Level by level management. The document organization form is as follows:

 

If else in Launch

In fact, there are child tags for if logic judgment in each tag:

<launch>  
    <arg name="trueorfalse" default="[1,2,3,4]" doc="true or false"/>
    <rosparam if="$(arg trueorfalse)" param="a_list">[1, 2, 3, 4]</rosparam>    
</launch>  

If = "true|false", the parameters after if can only be bool type, and if = "true" will execute this statement. On the contrary, unless is translated as "unless...", and the format is the same as if: unless = "true|false". If unless = "true", it is equivalent to the function of if = "false":

<launch>  
    <arg name="trueorfalse" default="[1,2,3,4]" doc="true or false"/>
    <rosparam unless="$(arg trueorfalse)" param="a_list">[1, 2, 3, 4]</rosparam>    
</launch>  

If we use it for if judgment of logical blocks, we can combine if/unless and < group >... < / group > Tags:

<launch deprecated="this vision is out-of-date!">  
    <arg name="trueorfalse" default="[1,2,3,4]" doc="true or false"/>  
    <group if="$(arg trueorfalse)">  
        <!-- the topic of turtlesim_node is /turtle1/cmd_vel -->  
        <node pkg="turtlesim" type="turtlesim_node" name="$(anon my_node)"/>  
        <!-- the topic of turtle_teleop_key is /turtle1/cmd_vel -->  
        <node pkg="turtlesim" type="turtle_teleop_key" name="my_key" output="screen"/>  
        <rosparam command="load" file="$(find test01)/launch/params.yaml" ns="hello"/>  
        <rosparam param="a_list">[1, 2, 3, 4]</rosparam>  
        <rosparam>  
            Name:  
                a: 9  
                b: "hello"  
                c: [1,2,3,4]  
        </rosparam>      
    </group>  
</launch>  

In this way, multiple logical statements are included under if at the same time, which is equivalent to the "logical block with braces" in the if {...} statement in C + +.

Keywords: C++ ROS

Added by edtlov on Wed, 26 Jan 2022 01:59:32 +0200