ROS2经验:如何编写一个启动文件(launch file)启动大型的ROS2系统

如何编写一个启动文件(launch file)启动大型的ROS2系统


我们已经学会了如何用ros2 run去运行一个ROS2的可执行程序。可是当你需要同时运行多个程序时,就不得不开多个终端来启动程序。比如一个SLAM小车,需要开一个终端启动激光雷达驱动程序发布laserscan,另外需要开一个终端启动底盘发布里程计和imu,还需一个终端启动slam程序订阅激光和传感器数据并发布地图和定位。整个操作就会变得十分繁琐。

所以ROS提供了启动系统(Launch System)。ROS中的启动系统负责帮助用户描述系统的配置,然后按照描述执行。系统的配置包括运行什么程序,在哪里运行,传递什么参数,以及特定于ROS的约定,这些约定通过给组件提供不同的配置,使得在整个系统中重用组件变得容易。此外,因为启动系统是执行用户进程的进程(或一组进程),所以它负责监视它启动的流程的状态,以及对这些进程的状态变化进行报告或反应。

我们需要编写启动文件(launch file)。ROS2的启动文件可以用Python、XML和YAML三种方法编写,如果有用过ROS1的话可能最熟悉的就是XML方式。但是XML或YAML无法实现Python的灵活性。因为使用Python有以下两个优点:

  • Python是一种脚本语言,因此可以在launch file中使用该语言的特性以及支持它的各种库
  • ros2/launch(通用启动功能)和ros2/launch_ros(ROS2特定启动功能)是用Python编写的,因此你可以有较低的权限来访问XML和YAML没有公开的启动功能。

本教程的launch file是用Python编写的,它可以启动和停止不同的节点,以及触发和处理各种事件。

本教程适用于最新的ROS2 Galactic版本,而且仅介绍C++ package。

我将由简到烦逐步教大家写出一个可以用于运行复杂的ROS2系统的launch file。


编写个简单的launch file

1. 创建launch file

可以在任何你喜欢的目录下创建启动文件,但正规的方式为了能让ros2管理到launch file,我们把launch file创建在包目录里面。

先创建一个包,比如在工作空间的src目录下创建一个包叫my_launch_test

ros2 pkg create --build-type ament_cmake my_launch_test

然后在包目录里创建launch目录来存储launch file。由于是python launch,所以我们需要创建一个.py结尾的文件,主流方式命名为xxx.launch.py或者xxx_launch.py。这里我们把它叫做test.launch.py

mkdir launch
cd launch
touch test.launch.py
2. 编写launch file

可以用任何你喜欢的文本编辑器来打开编辑launch file。

我们以运行海龟模拟器turtlesim为例,来编写启动文件。

在test.launch.py中加入以下代码:

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='turtlesim',
            executable='turtlesim_node',
            name='sim1'
        ),
        Node(
            package='turtlesim',
            executable='turtlesim_node',
            name='sim2'
        )
    ])

来看看代码,这个launch file的作用是启动两个turtlesim窗口

首先需要import两个模块。

from launch import LaunchDescription
from launch_ros.actions import Node

启动描述,launch file最基本的就是将内容编写在启动描述中。

def generate_launch_description():
   return LaunchDescription([

   ])

launch file最基本的功能就是启动node,LaunchDescription中包含了两个Node,

Node(
    package='turtlesim',
    executable='turtlesim_node',
    name='sim1'
),
Node(
    package='turtlesim',
    executable='turtlesim_node',
    name='sim2'
)

Node中一般需要3个属性,package、executable和name,package为包的名称,executable为包内的可执行文件名,name为节点运行时的节点名。可能还会用到:

namespace: 命名空间。使节点名称前增加命名空间前缀,命名空间不同使系统允许两个相同节点名和主题名不冲突。如果没有唯一的命名空间,当topic消息相同时就无法区分是哪个节点的。

respawn: 复位。设为‘true’时,节点停止时自动重启。默认为‘false’

output: 输出。设为‘screen’时,将节点的输出打印到中终端屏幕。

arguments: 节点需要输入的参数。

remappings: 重映射,将默认节点属性(如节点名称、主题名称、服务名称等),重映射为其它名称。

3. 运行launch file

可以直接在launch目录下启动launch file

ros2 launch test.launch.py

但我们一般对包进行编译后调用。返回到my_launch_test目录,

在package.xml中添加依赖:

<exec_depend>ros2launch</exec_depend>

在CMakeListe.txt中添加:

# Install launch files.
install(DIRECTORY
  launch
  DESTINATION share/${PROJECT_NAME}/
)

然后返回工作空间根目录进行colcon build。之后只要source 设置文件. install/setup.bash,就能使用ros2 launch <package_name> <launch_file_name>的方式来启动了。

在这里插入图片描述

出现两个turtlesim窗口。


命名空间

当启动了上一节的示例时,我们查看一下当前的话题列表

在这里插入图片描述

会看到,虽然运行了两个海龟模拟器,但看到的话题列表跟只有一个模拟器一样。因为两个模拟器都发布和订阅同样的话题,混在一起了。

这时候运行海龟操控键盘turtle_teleop_key,试着操作一下箭头,发现两只海龟同时都会动,因为两个节点都订阅了/turtle1/cmdvel

如果要区分两个海龟模拟器的话,就得加入命名空间(namespace)属性,将launch file代码改为:

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='turtlesim',
            namespace='turtlesim1',
            executable='turtlesim_node',
            name='sim1'
        ),
        Node(
            package='turtlesim',
            namespace='turtlesim2',
            executable='turtlesim_node',
            name='sim2'
        )
    ])

重新启动launch file。再查看话题列表:

在这里插入图片描述

可以看到两个模拟器节点的话题就区分出来了。


重映射

增加了命名空间后我们再次运行turtle_teleop_key,操作一下方向按键,发现海龟动不了了。来看看话题列表:

在这里插入图片描述

马上就发现,虽然话题类型都是geometry_msgs/msg/Twist,但turtle_teleop_key发布的话题是/turtle1/cmd_vel,而两个turtlesim节点订阅的话题是有带命名空间前缀的。所以并不能兼容。

这时候我们就可以使用重映射,给话题重映射个名字,比如把/turtlesim1/turtle1/cmd_vel重映射为/turtle1/cmd_vel,来改一下launch file:

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='turtlesim',
            namespace='turtlesim1',
            executable='turtlesim_node',
            name='sim1',
            remappings=[
	      		('/turtlesim1/turtle1/cmd_vel', '/turtle1/cmd_vel'),
	    	]
        ),
        Node(
            package='turtlesim',
            namespace='turtlesim2',
            executable='turtlesim_node',
            name='sim2'
        ),
        Node(
            package='turtlesim',
            executable='mimic',
            name='mimic',
            remappings=[
              ('/input/pose', '/turtlesim1/turtle1/pose'),
              ('/output/cmd_vel', '/turtlesim2/turtle1/cmd_vel'),
            ]
		)
    ])

turtlesim1节点的/turtlesim1/turtle1/cmd_vel话题,映射为/turtle1/cmd_vel

我们还增加了mimic节点,把mimic/input/pose 话题映射为 /turtlesim1/turtle1/pose ,把 /output/cmd_vel 话题映射为 /turtlesim2/turtle1/cmd_velmimic节点的作用就是将会订阅turtlesim1节点发布的位姿(pose)话题,并重新发布为turtlesim2节点的速度控制。也就是说turtlesim2将会模仿 turtlesim1的动作。

在这里插入图片描述

turtle_teleop_key控制turtlesim1,而turtlesim2模仿turtlesim1,所以操作方向键时,两只海龟一起动了起来。


在launch file里调用shell命令

turtlesim节点提供背景颜色参数可以让我们设置,我们可以在终端里输入命令去设置修改某一个参数。launch file也提供了可以在文件里直接调用命令的功能。见如下代码:

from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import ExecuteProcess

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='turtlesim',
            namespace='turtlesim1',
            executable='turtlesim_node',
            name='sim1',
            remappings=[
	      ('/turtlesim1/turtle1/cmd_vel', '/turtle1/cmd_vel'),
	    ]
        ),
        Node(
            package='turtlesim',
            namespace='turtlesim2',
            executable='turtlesim_node',
            name='sim2'
        ),
        Node(
            package='turtlesim',
            executable='mimic',
            name='mimic',
            remappings=[
              ('/input/pose', '/turtlesim1/turtle1/pose'),
              ('/output/cmd_vel', '/turtlesim2/turtle1/cmd_vel'),
            ]
		),
        ExecuteProcess(
            cmd=[[
                'ros2 param set ',
                '/turtlesim1/sim1 ',
                'background_b ',
                '0'
            ]],
            shell=True
        )
    ])

首先需要import一个模块:

from launch.actions import ExecuteProcess

然后就可以使用了:

ExecuteProcess(
    cmd=[[
        'ros2 param set ',
        '/turtlesim1/sim1 ',
        'background_b ',
        '0'
    ]],
    shell=True
)

我们调用了ros2 param set /turtlesim1/sim1 background_b 0这个命令,将/turtlesim1/sim1background_b设置为0。来看下效果:

在这里插入图片描述

可以看到turtlesim1的背景色变了。


给launch file增加接收参数配置的功能

前面几节我们在launch file里的参数都是直接写死的。但为了使启动更灵活,比如我们想在启动时改变某个参数,而无需每次去修改launch file,就可以使用参数配置的功能。

一般需要用到两个模块:launch.substitutions.LaunchConfigurationlaunch.actions.DeclareLaunchArgument

LaunchConfiguration用来增加一个启动文件的传递参数,而DeclareLaunchArgument用于定义可以从上述启动文件或控制台传递的启动参数。

我们将上一节的修改背景颜色改为使用参数配置的方式。见以下代码:

from launch import LaunchDescription
from launch_ros.actions import Node
from launch.substitutions import LaunchConfiguration
from launch.actions import ExecuteProcess, DeclareLaunchArgument

def generate_launch_description():
    background_blue_arg = LaunchConfiguration('background_blue_arg')
    background_blue_launch_arg = DeclareLaunchArgument(
        'background_blue_arg',
        default_value='255',
        description='Configure the blue value of turtlesim1 background color.'
    )
    return LaunchDescription([
        background_blue_launch_arg,
        Node(
            package='turtlesim',
            namespace='turtlesim1',
            executable='turtlesim_node',
            name='sim1',
            remappings=[
                ('/turtlesim1/turtle1/cmd_vel', '/turtle1/cmd_vel'),
            ]
        ),
        Node(
            package='turtlesim',
            namespace='turtlesim2',
            executable='turtlesim_node',
            name='sim2'
        ),
        Node(
            package='turtlesim',
            executable='mimic',
            name='mimic',
            remappings=[
                ('/input/pose', '/turtlesim1/turtle1/pose'),
                ('/output/cmd_vel', '/turtlesim2/turtle1/cmd_vel'),
            ]
        ),
        ExecuteProcess(
            cmd=[[
                'ros2 param set ',
                '/turtlesim1/sim1 ',
                'background_b ',
                background_blue_arg
            ]],
            shell=True
        )
    ])

定义了background_blue_arg,默认值为255。

可以用以下命令来查看launch file提供了哪些参数及其默认值:

ros2 launch my_launch_test test.launch.py --show-args

返回:

在这里插入图片描述

现在可以将所需的参数传递给启动文件,例如调用以下命令将背景色蓝色值设置为150:

ros2 launch my_launch_test test.launch.py background_blue_arg:=150

在这里插入图片描述


嵌套复用功能IncludeLaunchDescription

当我们有了一个个完善的launch file,比如这launch file用于启动深度相机,另一个launch file用于启动导航,而我们需要写一个launch file同时包含深度相机和导航的启动功能。launch系统为我们提供给了嵌套复用的功能,直接可以launch file中包含其它launch file,这就使得launch file更灵活,更方便。

编写launch file过程中的目标之一应该是使它们尽可能可重用。这可以通过将相关节点和配置集群到单独的启动文件中来完成。之后,可以编写专用于特定配置的顶层launch file。这将允许在完全不更改启动文件的情况下在相同的机器人之间移动。即使是从真正的机器人移动到模拟机器人这样的变化,也只需进行少量更改即可完成。

我们另外编写个launch file简单命名为top_level_test.launch.py。里面将直接复用前一节的launch file,同时运行一个命令发布速度让乌龟转圈。代码如下:

import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch.actions import ExecuteProcess, IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource

def generate_launch_description():
    two_turtle_sim = IncludeLaunchDescription(
        PythonLaunchDescriptionSource([os.path.join(
            get_package_share_directory('my_launch_test'), 'launch'),
            '/test.launch.py']),
        launch_arguments={'background_blue_arg': '150'}.items()
    )
    cmd_execute = ExecuteProcess(
        cmd=[[
            'ros2 topic pub -r 1 /turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 2.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: -1.8}}"'
        ]],
        shell=True
    )

    return LaunchDescription([
        two_turtle_sim,
        cmd_execute
    ])

运行效果如下:

在这里插入图片描述


总结

本篇介绍了一下launch比较常用的功能,描述了为大型项目编写启动文件的一些技巧。重点是如何构建启动文件,以便在不同情况下尽可能多地重用它们。可以看到ROS2使用python实现的launch非常实用且灵活。

当我们编写一个大型的ROS2项目的launch file时,主要就是进行模块化launch file的实现,把每个模块的launch file做到尽可能可重用,最后再编写个顶层的launch file来调用各个模块launch file。

其实还有很多的功能并没有在文档中描述出来,大家可以在实践中多多参考别人的代码,不断完善相关知识。

  • 19
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
ROS2中,可以使用`launch`包来编写和管理启动多个包的脚本。 1. 首先,在你的ROS2工作空间中创建一个新的包,例如`my_launch_pkg`。 2. 在`my_launch_pkg`中创建一个名为`launch`的文件夹,并在该文件夹中创建一个名为`my_launch.py`的Python脚本文件。 3. 在`my_launch.py`中,你可以使用`launch.actions.IncludeLaunchDescription`来引用其他包中的launch文件,例如: ```python from launch import LaunchDescription from launch.actions import IncludeLaunchDescription from launch.launch_description_sources import PythonLaunchDescriptionSource def generate_launch_description(): ld = LaunchDescription() # 包1 pkg1_launch_file_dir = LaunchConfiguration('pkg1_launch_file_dir') pkg1_launch_file = os.path.join(pkg1_launch_file_dir, 'pkg1_launch.py') pkg1_launch = IncludeLaunchDescription( PythonLaunchDescriptionSource(pkg1_launch_file) ) ld.add_action(pkg1_launch) # 包2 pkg2_launch_file_dir = LaunchConfiguration('pkg2_launch_file_dir') pkg2_launch_file = os.path.join(pkg2_launch_file_dir, 'pkg2_launch.py') pkg2_launch = IncludeLaunchDescription( PythonLaunchDescriptionSource(pkg2_launch_file) ) ld.add_action(pkg2_launch) return ld ``` 在上面的代码中,我们使用`IncludeLaunchDescription`来引用其他包中的launch文件,具体路径需要通过`LaunchConfiguration`来参数化。 4. 最后,你需要在`my_launch_pkg`中创建一个名为`my_launch_launch.py`的启动文件,用于启动`my_launch.py`。在启动文件中,你需要设置`ROS_PACKAGE_PATH`环境变量,以便ROS2可以找到其他包。例如: ```python import os from launch import LaunchDescription from launch_ros.actions import Node from launch.actions import SetEnvironmentVariable def generate_launch_description(): # 设置ROS_PACKAGE_PATH环境变量 os.environ['ROS_PACKAGE_PATH'] = os.pathsep.join([ '/path/to/pkg1', '/path/to/pkg2', '/path/to/my_launch_pkg' ]) # 启动my_launch.py my_launch = IncludeLaunchDescription( PythonLaunchDescriptionSource(os.path.join( get_package_share_directory('my_launch_pkg'), 'launch', 'my_launch.py' )) ) return LaunchDescription([ SetEnvironmentVariable('PYTHONUNBUFFERED', '1'), my_launch ]) ``` 在上面的代码中,我们首先设置`ROS_PACKAGE_PATH`环境变量,然后启动`my_launch.py`。 5. 最后,在终端中运行以下命令以启动`my_launch_launch.py`: ``` $ ros2 launch my_launch_pkg my_launch_launch.py ``` 这将启动`my_launch.py`,同时启动包1和包2中的`launch.py`文件

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kal-Lai

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值