ROS2-018-ROS2工具:启动文件 launch 的XML/YAML实现
简介
有关 launch 文件的介绍,可以参考这一篇内容
除非你希望仅使用 C++ 进行编写,否则由于 XML & YAML 语言自身并无事件触发函数能进行适配,我个人并不推荐使用 XML/YAML 语言作为编写 launch 的首要语言(虽然他编写起来确实较为简便 = =)。若有实际需要,请结合官方文档进行代码的书写。
因为代码格式相似,在下文中我们会将 XML 与 YAML 文件放在一起描述。
XML&YAML 实现语法
以下介绍在 XML & YAML 中 launch 文件 的相关语法:
1. 节点设置-node
在 使用 XML / YAML 实现的 launch 文件 中,被执行的节点需要统一写入 node 标签。
在XML中,假设该 launch 文件的文件名为“xml02_node_launch.xml”,所在功能包名为 “package_for_launch_01”,该 launch 文件可以编写为如下配置:
<launch>
<node pkg="turtlesim" exec="turtlesim_node" name="t1" namespace="robot1" exec_name="t1_label" respawn="true"/>
<node pkg="turtlesim" exec="turtlesim_node" name="t2">
<!-- <param name="background_r" value="255" />
<param name="background_g" value="255" />
<param name="background_b" value="255" /> -->
<param from="$(find-pkg-share package_for_launch_01)/config/t2.yaml" />
</node>
<node pkg="turtlesim" exec="turtlesim_node" name="t3">
<remap from="/turtle1/cmd_vel" to="/cmd_vel" />
</node>
<node pkg="turtlesim" exec="turtlesim_node" ros_args="--remap __name:=t4 --remap __ns:=/group_2" />
<node pkg="rviz2" exec="rviz2" args="-d $(find-pkg-share package_for_launch_01)/config/my.rviz" />
</launch>在YAML中,假设该 launch 文件的文件名为“yaml02_node_launch.yaml”,所在功能包名为 “package_for_launch_01”,该 launch 文件可以编写为如下配置:
launch:
- node:
pkg: "turtlesim"
exec: "turtlesim_node"
name: "t1"
namespace: "robot1"
exec_name: "t1_label"
respawn: "false"
- node:
pkg: "turtlesim"
exec: "turtlesim_node"
name: "t2"
param:
# -
# name: "background_r"
# value: 255
# -
# name: "background_b"
# value: 255
- from: "$(find-pkg-share package_for_launch_01)/config/t2.yaml"
- node:
pkg: "turtlesim"
exec: "turtlesim_node"
remap:
-
from: "/turtle1/cmd_vel"
to: "/cmd_vel"
- node:
pkg: "turtlesim"
exec: "turtlesim_node"
ros_args: "--ros-args --remap __name:=t4 --remap __ns:=/t4"
- node:
pkg: "rviz2"
exec: "rviz2"
args: "-d $(find-pkg-share package_for_launch_01)/config/my.rviz"其中:
- exec:可执行程序
- pkg:被执行程序所属功能包的名称
- respawn:设置节点是否会自动重启 (针对特殊节点,如需要在出现异常被关闭之后自动重启的节点,可以使用)
- name:设置节点名称,可以用于解决重名问题
- namespace:设置命名空间,可以用于解决重名问题
- exec_name:设置程序运行时命令行左侧所显示的对应标签
- args:调用指令时的参数列表
- ros_args:相当于 args 前缀 --ros-args
node 标签的子级标签包含:
- remap:用于实现话题重映射,其属性包含:
- from:原话题名称
- to:新话题名称
- param:设置参数的标签,其属性包含:
- name:参数名称
- value:参数值
- from:参数文件路径
如 前文 python 实现 所述,当使用 param 进行传参时,如果需要传入的参数数量过多,则使用<param name="xxx" value="yyy" />进行的传参方式就会变得十分不妥。
因此在针对使用 param 进行传参时,我们实际上会更经常使用 yaml 文件进行传参。
为了格式统一与代码编译时其自身的可编译性,我们一般会使用以下步骤创建带特定格式的 yaml 文件:(假设bash路径在工作空间根目录下)
先将前文所述节点启动
. install/setup.bash ros2 launch py01_launch py02_node_launch.py在同一目录下另开一终端,输入:
ros2 param dump /robot1/t1 > src/package_for_launch_01/config/custom_params.yaml此时就会在工作空间根目录内的
src/package_for_launch_01/config路径下生成一个custom_params.yaml文件了,但是这个YAML文件还无法直接使用。我们需要同之前添加
launch文件一样在CMakeLists.txt中👈位置添加以下语句:...... find_package(rclcpp REQUIRED) install(DIRECTORY launch config DESTINATION share/${PROJECT_NAME}) 👈 if(BUILD_TESTING) ......进行编译:
colcon build --pakages-select package_for_launch_01最后通过导入
get_package_share_directory函数动态以获取install目录内 该功能包名下share文件夹的路径,最后与config和custom_params.yaml相关的路径信息相拼接即可:
XML 拼接方法:
...
<!-- 参数配置时动态获取路径 -->
<param from="$(find-pkg-share package_for_launch_01)/config/t2.yaml" />YAML拼接方法:
...
# 参数配置时动态获取路径
param:
- from: "$(find-pkg-share package_for_launch_01)/config/t2.yaml"2. 执行指令设置-executable
在 使用 XML / YAML 实现的 launch 文件 中,为了针对 ROS2 命令等外部进程进行封装,以实现部份功能的简化调用执行,需要使用 executable 标签。该方法相对 python 而言简单些。
在XML语言下,假设该 launch 文件的文件名为“xml03_cmd_launch.xml”,所在功能包名为 “package_for_launch_01”,该 launch 文件可以编写为如下配置:
<launch>
<node pkg="turtlesim" exec="turtlesim_node" />
<executable cmd="ros2 run turtlesim turtlesim_node" output="both" />
</launch>在YAML语言下,假设该 launch 文件的文件名为“yaml03_cmd_launch.yaml”,所在功能包名为 “package_for_launch_01”,该 launch 文件可以编写为如下配置:
launch:
- executable:
cmd: "ros2 run turtlesim turtlesim_node"
output: "both"executable 标签用于表示可执行指令,其属性包含:
- cmd:被执行的命令;
- output:日志输出目的地设置。
3. 参数设置-arg¶m
参数设置主要涉及到参数的 声明 与 调用 两部分,其中声明被封装为 arg 标签,调用则直接使用 $() 解析语法。
在XML语言下,假设该 launch 文件的文件名为“xml04_args_launch.xml”,所在功能包名为 “package_for_launch_01”,该 launch 文件可以编写为如下配置:
<launch>
<!-- 声明参数 -->
<arg name="bg_r" default="255"/>
<arg name="bg_g" default="255"/>
<arg name="bg_b" default="255"/>
<!-- 调用参数 -->
<node pkg="turtlesim" exec="turtlesim_node">
<param name="background_r" value="$(var bg_r)" />
<param name="background_g" value="$(var bg_g)" />
<param name="background_b" value="$(var bg_b)" />
</node>
</launch>在YAML语言下,假设该 launch 文件的文件名为“yaml04_args_launch.yaml”,所在功能包名为 “package_for_launch_01”,该 launch 文件可以编写为如下配置:
launch:
# 声明参数
- arg:
name: "bg_r"
default: "255"
- node:
pkg: "turtlesim"
exec: "turtlesim_node"
param:
# 调用参数
-
name: "background_r"
value: $(var bg_r)在上述代码中,为了声明参数,我们使用了arg 标签设置了多个参数,其中:
- name:参数名称;
- default:默认值。
之后我们在创建 Node 对象时向 param 标签内使用 $() 解析语法向 param.value 内传入上述 参数。这样在 launch 文件执行时,我们就可以通过 bash 动态传入参数了:
ros2 launch package_for_launch_01 xml04_args_launch.xml bg_r:=100ros2 launch package_for_launch_01 yaml04_args_launch.yaml bg_r:=50当然,如果不传入参数,则文件执行时会使用当时声明参数时所给定的默认值 default 进行相关解析。
动态传参所使用的参数名需要做区别
通过 bash 动态传入参数时需要注意的是:所传入参数名应与声明参数(变量)时 name 自变量 内所使用的参数名相同,而并非与声明参数(变量)时所使用的参数名 本身 相同。
4.文件包含-include&let
文件包含主要指的是将一个 launch 文件 包含进另一个 launch 文件的操作。其需要使用的标签为 include.
在XML语言下,假设该 launch 文件的文件名为“xml05_include_launch.xml”,所在功能包名为 “package_for_launch_01”,且希望调用之前参数设置里所使用的 launch 文件,则该 launch 文件可以编写为如下配置:
<launch>
<!-- 需求: 在当前 launch 文件中包含其他 launch 文件 -->
<include file="$(find-pkg-share package_for_launch_01)/launch/xml/xml04_args_launch.xml"/>
</launch>这样在运行时就可以调用之前的 launch 文件了。在这里 include 标签用于实现文件包含,其属性如下:
- file:被包含的
launch文件的路径。
当然,这样调用 launch 文件时,其使用的参数为原文件内所使用的默认值 default,如果需要修改参数,需要添加 let 标签。可以在上述代码的基础上修改为:
<launch>
<!-- 需求: 在当前 launch 文件中包含其他 launch 文件,并传入特定参数 -->
<let name="bg_r" value="0" />
<include file="$(find-pkg-share package_for_launch_01)/launch/xml/xml04_args_launch.xml"/>
</launch>let 标签用于向被包含的 launch 文件中导入参数,其属性如下:
- name:参数名称
- value:参数值
在YAML语言下,假设该 launch 文件的文件名为“yaml05_include_launch.yaml”,所在功能包名为 “package_for_launch_01”,且希望调用之前参数设置里所使用的 launch 文件,则该 launch 文件可以编写为如下配置:
launch:
# 需求: 在当前 launch 文件中包含其他 launch 文件
- include:
file: "$(find-pkg-share package_for_launch_01)/launch/yaml/yaml04_arg.launch.yaml"在这里 include 键同样用于实现文件包含,其属性如下:
- file:被包含的
launch文件的路径。
这样在运行时就可以调用之前的 launch 文件了。如果需要修改参数,需要添加 let 键。可以在上述代码的基础上修改为:
launch:
# 需求: 在当前 launch 文件中包含其他 launch 文件,并传入特定参数
- let:
name: "bgr"
value: "255"
- include:
file: "$(find-pkg-share package_for_launch_01)/launch/yaml/yaml04_arg.launch.yaml"let 键用于向被包含的 launch 文件中导入参数,其属性如下:
- name:参数名称
- value:参数值
设置参数名时需注意
若引用语言为 XML / YAML 的 launch 文件,务必使用原来的 launch 文件中 arg 标签下所设置的对应 name,否则无法识别。
5. Node分组-group&push_ros_namespace
在 launch 文件中为了方便节点的管理,我们可以对节点分组。其需要使用的标签为 group & push_ros_namespace。其中:
- group:分组用标签
- push_ros_namespace:为当前组设置命名空间
在XML语言下,假设该 launch 文件的文件名为“xml06_group_launch.xml”,所在功能包名为 “package_for_launch_01”,则该 launch 文件可以编写为如下配置:
<launch>
<!-- 需求: 创建 3 个 turtlesim_node, 并前两个划分为同一组,第三个单独一组 -->
<!-- 直接在 group 标签下分别创建 3 个 turtlesim_node,然后分为多组即可 -->
<group>
<!-- 在此使用 push_ros_namespace 标签设置命名空间 -->
<push_ros_namespace namespace="g1" />
<node pkg="turtlesim" exec="turtlesim_node" name="t1"/>
<node pkg="turtlesim" exec="turtlesim_node" name="t2"/>
</group>
<group>
<push_ros_namespace namespace="g2" />
<node pkg="turtlesim" exec="turtlesim_node" name="t3"/>
</group>
</launch>在YAML语言下,假设该 launch 文件的文件名为“yaml06_group_launch.yaml”,所在功能包名为 “package_for_launch_01”,则该 launch 文件可以编写为如下配置:
launch:
- group:
- push_ros_namespace:
namespace: "g1"
- node:
pkg: "turtlesim"
exec: "turtlesim_node"
name: "t1"
- node:
pkg: "turtlesim"
exec: "turtlesim_node"
name: "t2"
- group:
- push_ros_namespace:
namespace: "g2"
- node:
pkg: "turtlesim"
exec: "turtlesim_node"
name: "t3"这样当该 launch 文件运行时,只需要另开一终端,并在 bash 内输入ros2 node list, 即可查看到这几个 node 的分组情况了:

如果在创建 Node 标签时使用了 namespace 的话...
假设在原来代码的基础上,将 t1 修改为了如下配置:
XML:
... <node pkg="turtlesim" exec="turtlesim_node" name="t1" namespace="extra1"/> <!-- 👆NEW! --> ...YAML:
... - node: pkg: "turtlesim" exec: "turtlesim_node" name: "t1" namespace: "extra1" # 👈NEW! ...
则当该 launch 文件运行时,Node 的分组情况就会变为:

本质上就是在多套了一个命名空间在指定 Node 上这样 = =
6. 事件触发-没有
是的,XML和YAML并没有事件触发函数,可能觉得书写起来过于麻烦直接砍掉了吧大概。。。
