在大型项目中使用ROS 2launch [待校准@7674]

Goal目标: Learn学习使用ROS 2launch文件管理大型项目的最佳实践 [待校准@7675]

Tutorial教程级别: Intermediate中级 [待校准@6713]

Time时间: 20 20分钟 [待校准@7181]

背景

本教程介绍了为大型项目编写launch文件的一些技巧。重点是如何构建launch文件,以便在不同情况下尽可能多地重复使用。此外,它还涵盖了不同ROS 2launch工具的使用示例,如参数、YAML文件、重映射、命名空间、默认参数和RViz配置。 [待校准@7612]

先决条件

本教程使用 turtlesim and turtle_tf2_py packages. This tutorial also assumes you have created a new package 构建类型 ament_python 调用ed launch_tutorial[待校准@7676]

简介

典型调用y机器人上的大型应用程序涉及几个互连节点,每个节点可以有许多参数。海龟模拟器中多只海龟的仿真可以作为一个很好的例子。turtle仿真由多个turtle节点、world配置以及TF广播器和监听节点组成。在所有节点之间,有大量的ROS参数会影响这些节点的行为和外观。ROS 2launch文件允许我们启动所有节点并在一个地方设置相应的参数。在教程结束时,您将在 launch_tutorial 包中构建 launch_turtlesim.launch.py launch文件。这launch文件将带来不同节点负责仿真两turtlesim仿真,启动TF广播器s和听众,加载参数,launch成为RViz配置。在本教程中,我们将介绍这个launch文件和所有使用的相关功能。 [待校准@7678]

编写launch文件 [待校准@7679]

1顶级组织 [待校准@7680]

编写launch文件的目的之一应该是使它们尽可能可重用。这可以通过将相关节点和配置聚类到单独的launch文件中来实现。之后,可以编写专用于特定配置的顶级launch文件。这将允许在完全相同的机器人之间移动,而不需要改变launch文件。即使是改变,比如从真正的机器人移动到模拟机器人,也只需做一些改变。 [待校准@7681]

我们现在将讨论使这成为可能的顶级launch文件结构。首先,我们将创建一个launch文件,该文件将调用单独的launch文件。为此,让我们在 launch_tutorial 包的 /launch 文件夹中创建一个 launch_turtlesim.launch.py 文件。 [待校准@7682]

import os

from ament_index_python.packages import get_package_share_directory

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


def generate_launch_description():
   turtlesim_world_1 = IncludeLaunchDescription(
      PythonLaunchDescriptionSource([os.path.join(
         get_package_share_directory('launch_tutorial'), 'launch'),
         '/turtlesim_world_1.launch.py'])
      )
   turtlesim_world_2 = IncludeLaunchDescription(
      PythonLaunchDescriptionSource([os.path.join(
         get_package_share_directory('launch_tutorial'), 'launch'),
         '/turtlesim_world_2.launch.py'])
      )
   broadcaster_listener_nodes = IncludeLaunchDescription(
      PythonLaunchDescriptionSource([os.path.join(
         get_package_share_directory('launch_tutorial'), 'launch'),
         '/broadcaster_listener.launch.py']),
      launch_arguments={'target_frame': 'carrot1'}.items(),
      )
   mimic_node = IncludeLaunchDescription(
      PythonLaunchDescriptionSource([os.path.join(
         get_package_share_directory('launch_tutorial'), 'launch'),
         '/mimic.launch.py'])
      )
   fixed_frame_node = IncludeLaunchDescription(
      PythonLaunchDescriptionSource([os.path.join(
         get_package_share_directory('launch_tutorial'), 'launch'),
         '/fixed_broadcaster.launch.py'])
      )
   rviz_node = IncludeLaunchDescription(
      PythonLaunchDescriptionSource([os.path.join(
         get_package_share_directory('launch_tutorial'), 'launch'),
         '/turtlesim_rviz.launch.py'])
      )

   return LaunchDescription([
      turtlesim_world_1,
      turtlesim_world_2,
      broadcaster_listener_nodes,
      mimic_node,
      fixed_frame_node,
      rviz_node
   ])

这个launch文件包括一组其他launch文件。每个包含的launch文件都包含节点、参数和可能的嵌套包含,它们属于系统的一部分。准确,我们launch两个turtlesim仿真世界,TF广播器,TF监听器,模仿,固定帧广播器,RViz节点。 [待校准@7683]

注解

设计提示: 顶级launch文件应简短,包括与应用程序子组件相对应的其他文件,以及通常更改的参数。 [待校准@7684]

按照以下方式编写launch文件可以很容易地换出系统的一部分,我们将在后面看到。然而,有些情况下,由于性能和使用的原因,一些节点或launch文件必须分别被launch。 [待校准@7685]

注解

设计提示: 在决定你的应用程序需要多少顶级launch文件时,要注意权衡。 [待校准@7686]

2参数 [待校准@7687]

2.1launch文件中的设置参数 [待校准@7688]

我们将从编写一个launch文件开始,该文件将开始我们的第一个turtlesim仿真。首先,创建一个调用ed turtlesim_world_1.launch.py 的新文件。 [待校准@7689]

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration, TextSubstitution

from launch_ros.actions import Node


def generate_launch_description():
   background_r_launch_arg = DeclareLaunchArgument(
      'background_r', default_value=TextSubstitution(text='0')
   )
   background_g_launch_arg = DeclareLaunchArgument(
      'background_g', default_value=TextSubstitution(text='84')
   )
   background_b_launch_arg = DeclareLaunchArgument(
      'background_b', default_value=TextSubstitution(text='122')
   )

   return LaunchDescription([
      background_r_launch_arg,
      background_g_launch_arg,
      background_b_launch_arg,
      Node(
         package='turtlesim',
         executable='turtlesim_node',
         name='sim',
         parameters=[{
            'background_r': LaunchConfiguration('background_r'),
            'background_g': LaunchConfiguration('background_g'),
            'background_b': LaunchConfiguration('background_b'),
         }]
      ),
   ])

这个launch文件启动 turtlesim_node 节点,它启动turtlesim仿真,仿真配置参数被定义并传递给节点。 [待校准@7690]

2.2从YAML文件加载参数 [待校准@7691]

在第二个launch中,我们将使用不同的配置开始第二个turtlesim仿真。现在创建一个 turtlesim_world_2.launch.py 文件。 [待校准@7692]

import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch_ros.actions import Node


def generate_launch_description():
   config = os.path.join(
      get_package_share_directory('launch_tutorial'),
      'config',
      'turtlesim.yaml'
      )

   return LaunchDescription([
      Node(
         package='turtlesim',
         executable='turtlesim_node',
         namespace='turtlesim2',
         name='sim',
         parameters=[config]
      )
   ])

这launch文件将launch相同 turtlesim_node 参数值直接加载从YAML配置文件。在YAML文件中定义参数和参数使存储和加载大量变量变得容易。此外,YAML文件可以很容易地从当前的 ros2 param 列表中导出。要了解如何做到这一点,请参考 Understanding ROS 2 parameters 教程。 [待校准@7693]

现在让我们在我们包的 /config 文件夹中创建一个配置文件, turtlesim.yaml ,它将由我们的launch文件加载。 [待校准@7694]

/turtlesim2/sim:
   ros__parameters:
      background_b: 255
      background_g: 86
      background_r: 150

如果我们现在启动 turtlesim_world_2.launch.py launch文件,我们将使用预先配置的背景色启动 turtlesim_node[待校准@7695]

要了解有关使用参数和使用YAML文件的更多信息,请查看 Understanding ROS 2 parameters 教程。 [待校准@7696]

2.3在YAML文件中使用通配符 [待校准@7697]

在某些情况下,我们希望在多个节点中设置相同的参数。这些节点可以具有不同的命名空间或名称,但仍具有相同的参数。定义明确定义命名空间和节点名称的单独YAML文件效率不高。一种解决方案是使用通配符作为文本值中未知字符的替换,将参数应用于几个不同的节点。 [待校准@7698]

现在让我们创建一个新 turtlesim_world_3.launch.py 文件类似 turtlesim_world_2.launch.py 包括一个 turtlesim_node 节点。 [待校准@7699]

...
Node(
   package='turtlesim',
   executable='turtlesim_node',
   namespace='turtlesim3',
   name='sim',
   parameters=[config]
)

但是,加载相同的YAML文件不会影响第三个turtlesim世界的外观。原因是其参数存储在另一个命名空间下,如下所示: [待校准@7700]

/turtlesim3/sim:
   background_b
   background_g
   background_r

因此,我们可以使用通配符语法,而不是为使用相同参数的同一节点创建新配置。尽管节点名称和命名空间不同,但 /** 将分配每个节点中的所有参数。 [待校准@7701]

我们现在将更新 turtlesim.yaml/config 文件夹在以下方式: [待校准@7702]

/**:
   ros__parameters:
      background_b: 255
      background_g: 86
      background_r: 150

现在包括 turtlesim_world_3.launch.py launch描述我们主要launch文件。使用配置文件在我们launch描述将分配 background_bbackground_gbackground_r 参数指定值 turtlesim3/simturtlesim2/sim 节点。 [待校准@7703]

3命名空间 [待校准@7704]

您可能已经注意到,我们已经在 turtlesim_world_2.launch.py 文件中定义了turlesim世界的命名空间。唯一命名空间允许系统启动两个相似的节点,而没有节点名称或话题名称冲突。 [待校准@7705]

namespace='turtlesim2',

但是,如果launch文件包含大量节点,则为每个节点定义命名空间可能会变得乏味。为了解决这个问题,可以使用 PushRosNamespace 动作为每个launch文件描述定义全局命名空间。每个嵌套节点将继承该命名空间自动调用y。 [待校准@7706]

为此,首先,我们需要从 turtlesim_world_2.launch.py 文件中删除 “命名空间 = 'turtlesim2'” 行。之后,我们需要更新 launch_turtlesim.launch.py 以包括以下几行: [待校准@7707]

from launch.actions import GroupAction
from launch_ros.actions import PushRosNamespace

   ...
   turtlesim_world_2 = IncludeLaunchDescription(
      PythonLaunchDescriptionSource([os.path.join(
         get_package_share_directory('launch_tutorial'), 'launch'),
         '/turtlesim_world_2.launch.py'])
      )
   turtlesim_world_2_with_namespace = GroupAction(
     actions=[
         PushRosNamespace('turtlesim2'),
         turtlesim_world_2,
      ]
   )

最后,我们在 return LaunchDescription 的声明中将 turtlesim_world_2 改为 turtlesim_world_2_with_namespace 。因此, turtlesim_world_2.launch.py launch描述中的每个节点都将有一个 turtlesim2 命名空间。 [待校准@7708]

4重用节点 [待校准@7709]

现在创建一个 broadcaster_listener.launch.py 文件。 [待校准@7710]

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration

from launch_ros.actions import Node


def generate_launch_description():
   return LaunchDescription([
      DeclareLaunchArgument(
         'target_frame', default_value='turtle1',
         description='Target frame name.'
      ),
      Node(
         package='turtle_tf2_py',
         executable='turtle_tf2_broadcaster',
         name='broadcaster1',
         parameters=[
            {'turtlename': 'turtle1'}
         ]
      ),
      Node(
         package='turtle_tf2_py',
         executable='turtle_tf2_broadcaster',
         name='broadcaster2',
         parameters=[
            {'turtlename': 'turtle2'}
         ]
      ),
      Node(
         package='turtle_tf2_py',
         executable='turtle_tf2_listener',
         name='listener',
         parameters=[
            {'target_frame': LaunchConfiguration('target_frame')}
         ]
      ),
   ])

在这个文件中,我们声明了 target_frame launch参数的默认值为 turtle1 。默认值意味着launch文件可以接收参数转发到其节点,或者如果没有提供参数,它将把默认值传递给其节点。 [待校准@7711]

之后,我们在launch期间使用不同的名称和参数两次使用 turtle_tf2_broadcaster 节点。这允许我们复制同一个节点而没有冲突。 [待校准@7712]

我们还启动了一个 turtle_tf2_listener 节点,并设置了我们在上面声明并获得的 target_frame 参数。 [待校准@7713]

5参数覆盖 [待校准@7714]

重新调用我们调用了 broadcaster_listener.launch.py 文件我们级launch文件。除此之外,我们通过了 target_frame launch的论点,如下所示: [待校准@7715]

broadcaster_listener_nodes = IncludeLaunchDescription(
   PythonLaunchDescriptionSource([os.path.join(
      get_package_share_directory('launch_tutorial'), 'launch'),
      '/broadcaster_listener.launch.py']),
   launch_arguments={'target_frame': 'carrot1'}.items(),
   )

这种语法允许我们将默认目标帧更改为 carrot1 。如果你想让 turtle2 遵循 turtle1 而不是 carrot1 ,只需去掉定义 launch_arguments 的线。这将为 target_frame 分配其默认值,即 turtle1[待校准@7716]

6 重新映射 [Alyssa@7717]

现在创建一个 mimic.launch.py 文件。 [待校准@7718]

from launch import LaunchDescription
from launch_ros.actions import Node


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

该launch文件将启动 mimic 节点,该节点将向一个turtlesim发出命令以跟随另一个。节点被设计用来接收话题 /input/pose 上的目标姿势。在我们的案例中,我们想要从 /turtle2/pose 话题重新映射目标姿势。最后,我们将 /output/cmd_vel 话题重新映射到 /turtlesim2/turtle1/cmd_vel 。这样,我们 turtlesim2 仿真世界中的 turtle1 将在我们最初的turtlesim世界中跟随 turtle2[待校准@7719]

7配置文件 [待校准@7720]

现在让我们创建一个调用ed turtlesim_rviz.launch.py 的文件。 [待校准@7721]

import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch_ros.actions import Node


def generate_launch_description():
   rviz_config = os.path.join(
      get_package_share_directory('turtle_tf2_py'),
      'rviz',
      'turtle_rviz.rviz'
      )

   return LaunchDescription([
      Node(
         package='rviz2',
         executable='rviz2',
         name='rviz2',
         arguments=['-d', rviz_config]
      )
   ])

这launch文件将启动RViz配置文件中定义的 turtle_tf2_py 包。此RViz配置将设置世界帧,启用TF可视化,并以自上而下的视图启动RViz。 [待校准@7722]

8环境变量 [待校准@7723]

现在,让我们在包中创建调用ed fixed_broadcaster.launch.py 的最后一个launch文件。 [待校准@7724]

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


def generate_launch_description():
   return LaunchDescription([
      DeclareLaunchArgument(
            'node_prefix',
            default_value=[EnvironmentVariable('USER'), '_'],
            description='prefix for node name'
      ),
      Node(
            package='turtle_tf2_py',
            executable='fixed_frame_tf2_broadcaster',
            name=[LaunchConfiguration('node_prefix'), 'fixed_broadcaster'],
      ),
   ])

这个launch文件显示了如何在launch文件中调用环境变量。环境变量可用于定义或推送命名空间,以区分不同计算机或机器人上的节点。 [待校准@7725]

运行launch文件 [待校准@7726]

1更新设置 [待校准@7727]

打开 setup.py 并添加以下几行,以便安装 launch/ 文件夹中的launch文件和 config/ 文件夹中的配置文件。 data_files 田现在应该是这样的: [待校准@7728]

data_files=[
      ...
      (os.path.join('share', package_name, 'launch'),
         glob(os.path.join('launch', '*.launch.py'))),
      (os.path.join('share', package_name, 'config'),
         glob(os.path.join('config', '*.yaml'))),
   ],

2构建和运行 [待校准@7729]

终于看到结果代码,构建软件包和launch顶级launch文件使用以下命令: [待校准@7730]

ros2 launch launch_tutorial launch_turtlesim.launch.py

现在,您将看到两个turtlesim仿真已启动。第一个有两只海龟,第二个有一只。在第一次仿真中, turtle2 在世界的左下角产生。它的目的是达到相对于 turtle1 帧在x轴上5米远的 carrot1 帧。 [待校准@7731]

第二种中的 turtlesim2/turtle1 被设计成模仿 turtle2 的行为。 [待校准@7732]

如果要控制 turtle1 ,请运行teleop节点。 [待校准@7733]

ros2 run turtlesim turtle_teleop_key

因此,您将看到类似的图片: [待校准@7734]

../../_images/turtlesim_worlds.png

除此之外,RViz应该已经开始了。它将显示所有海龟帧相对于 world 帧,其原点在左下角。 [待校准@7735]

../../_images/turtlesim_rviz.png

总结

在本教程中,您学习了使用ROS 2launch文件管理大型项目的各种技巧和实践。 [待校准@7736]