< >
Home » ROS2与launch入门教程 » ROS2与launch入门教程-使用ROS2 launch文件启动大型项目

ROS2与launch入门教程-使用ROS2 launch文件启动大型项目

ROS2与launch入门教程-使用ROS2 launch文件启动大型项目

说明:
 

  • 学习使用ROS 2 launch文件管理大型项目的最佳实践

背景:

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

介绍:

  • 机器人上的大型应用程序通常涉及多个相互连接的节点,每个节点都可以有许多参数。
  • 在海龟模拟器中模拟多只海龟就是一个很好的例子。
  • 海龟模拟由多个海龟节点、world配置以及 TF 广播器和侦听器节点组成。
  • 在所有节点之间,存在大量影响这些节点行为和外观的 ROS 参数。
  • ROS 2 launch文件允许我们在一个地方启动所有节点并设置相应的参数。
  • 在教程结束时,您将在 launch_tutorial 包中构建 launch_turtlesim.launch.py 启动文件。
  • 这个启动文件会调出不同的节点,负责模拟两个turtlesim模拟,启动TF广播器和监听器,加载参数,启动一个RViz配置。
  • 在本教程中,我们将介绍这个启动文件和使用的所有相关功能。

顶层结构

  • 编写启动文件过程中的目标之一应该是使它们尽可能可重用。
  • 这可以通过将相关节点和配置集群到单独的启动文件中来完成。
  • 之后,可以编写专用于特定配置的顶级启动文件。
  • 这将允许在完全不更改启动文件的情况下在相同的机器人之间移动。
  • 即使是从真正的机器人移动到模拟机器人这样的变化,也只需进行少量更改即可完成。
  • 我们现在将讨论使这成为可能的顶级启动文件结构。
  • 首先,我们将创建一个launch文件,该文件将调用单独的启动文件。
  • 为此,让我们在 launch_tutorial 包的 /launch 文件夹中创建一个 launch_turtlesim.launch.py 文件。
cd ~/launch_ws/src/launch_tutorial/launch
vim launch_turtlesim.launch.py
  • 内容如下:
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
   ])
  • 此启动文件包括一组其他启动文件。
  • 这些包含的启动文件中的每一个都包含节点、参数和可能的嵌套包含,它们与系统的一个部分有关。
  • 确切地说,我们推出了两个turtlesim 模拟世界,TF 广播器、TF 监听器、模仿器、固定帧广播器和 RViz 节点。
  • 设计提示:顶级启动文件应该很短,包含与应用程序的子组件相对应的其他文件的包含,以及经常更改的参数。
  • 用以下方式编写启动文件可以很容易地换出系统的一部分,我们稍后会看到。
  • 但是,由于性能和使用原因,某些节点或启动文件必须单独启动。
  • 设计提示:在决定您的应用程序需要多少顶级启动文件时,请注意权衡取舍。

参数:

  • 我们将首先编写一个启动文件,该文件将启动我们的第一个 turtlesim 模拟。
  • 1.首先,创建一个名为 turtlesim_world_1.launch.py 的新文件
cd ~/launch_ws/src/launch_tutorial/launch
vim turtlesim_world_1.launch.py
  • 内容如下:
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'),
         }]
      ),
   ])
  • 此启动文件启动 turtlesim_node 节点,该节点启动 turtlesim 模拟,模拟配置参数已定义并传递给节点。
  • 2.从YAML 文件加载参数
  • 在第二次启动中,我们将使用不同的配置启动第二次 turtlesim 模拟。
  • 现在创建一个 turtlesim_world_2.launch.py 文件。
cd ~/launch_ws/src/launch_tutorial/launch
vim turtlesim_world_2.launch.py
  • 内容如下:
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]
      )
   ])
  • 此启动文件将使用直接从 YAML 配置文件加载的参数值启动相同的 turtlesim_node。
  • 在 YAML 文件中定义参数和参数可以轻松存储和加载大量变量。
  • 此外,YAML 文件可以很容易地从当前的 ros2 参数列表中导出。
  • 要了解如何执行此操作,请参阅了解 ROS 2 参数教程
  • 现在让我们在包的 /config 文件夹中创建一个配置文件,turtlesim.yaml,它将由我们的启动文件加载。
mkdir -p ~/launch_ws/src/launch_tutorial/config
cd ~/launch_ws/src/launch_tutorial/config
vim turtlesim.yaml
  • 内容如下:
/turtlesim2/sim:
   ros__parameters:
      background_b: 255
      background_g: 86
      background_r: 150
  • 如果我们现在启动 turtlesim_world_2.launch.py 启动文件,我们将使用预配置的背景颜色启动 turtlesim_node。

  • 要了解有关使用参数和使用 YAML 文件的更多信息,请查看了解 ROS 2 参数教程

  • 3.在 YAML 文件中使用通配符

  • 有时我们想在多个节点中设置相同的参数。

  • 这些节点可能具有不同的命名空间或名称,但仍具有相同的参数。

  • 定义显式定义命名空间和节点名称的单独 YAML 文件效率不高。

  • 一种解决方案是使用通配符,作为文本值中未知字符的替换,将参数应用于几个不同的节点。

  • 现在让我们创建一个类似于turtlesim_world_2.launch.py 的新turtlesim_world_3.launch.py 文件,以包含更多的turtlesim_node 节点。

cd ~/launch_ws/src/launch_tutorial/launch
vim turtlesim_world_3.launch.py
  • 增加如下内容
...
Node(
   package='turtlesim',
   executable='turtlesim_node',
   namespace='turtlesim3',
   name='sim',
   parameters=[config]
)
  • 然而,加载相同的 YAML 文件不会影响第三个 turtlesim 世界的外观。
  • 原因是它的参数存储在另一个命名空间下,如下所示:
/turtlesim3/sim:
   background_b
   background_g
   background_r
  • 因此,我们可以使用通配符语法,而不是为使用相同参数的同一节点创建新配置。
  • /** 将分配每个节点中的所有参数,尽管节点名称和命名空间不同。
  • 我们现在将按照以下方式更新 /config 文件夹中的 turtlesim.yaml:
/**:
   ros__parameters:
      background_b: 255
      background_g: 86
      background_r: 150
  • 现在在我们的主启动文件中包含 turtlesim_world_3.launch.py 启动描述。
  • 在我们的启动描述中使用该配置文件会将 background_b、background_g 和 background_r 参数分配给 turtlesim3/sim 和 turtlesim2/sim 节点中的指定值。

命名空间

  • 您可能已经注意到,我们在 turtlesim_world_2.launch.py 文件中定义了 turlesim 世界的命名空间。
  • 唯一的命名空间允许系统启动两个相似的节点,而不会出现节点名或主题名冲突。
namespace='turtlesim2',
  • 但是,如果启动文件包含大量节点,则为每个节点定义命名空间可能会变得乏味。

  • 为了解决这个问题,可以使用 PushRosNamespace 操作为每个启动文件描述定义全局命名空间。

  • 每个嵌套节点都会自动继承该命名空间。

  • 为此,首先,我们需要从 turtlesim_world_2.launch.py 文件中删除 namespace='turtlesim2' 行。

  • 之后,我们需要更新 launch_turtlesim.launch.py 以包含以下行:

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 启动描述中的每个节点都会有一个 turtlesim2 命名空间。

重用节点

  • 现在创建一个 broadcaster_listener.launch.py 文件。
cd ~/launch_ws/src/launch_tutorial/launch
vim broadcaster_listener.launch.py
  • 内容如下:
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')}
         ]
      ),
   ])

 - 在这个文件中,我们用默认值turtle1 声明了target_frame 启动参数。

  • 默认值意味着启动文件可以接收一个参数转发给它的节点,或者如果没有提供参数,它会将默认值传递给它的节点。
  • 之后,我们在启动期间使用不同的名称和参数两次使用 turtle_tf2_broadcaster 节点。
  • 这允许我们复制同一个节点而不会发生冲突。
  • 我们还启动了一个 turtle_tf2_listener 节点并设置了我们在上面声明和获取的 target_frame 参数。

参数覆盖

  • 回想一下,我们在顶级启动文件中调用了 broadcaster_listener.launch.py 文件。
  • 除此之外,我们还向它传递了 target_frame 启动参数,如下所示:
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。

重新映射

  • 现在创建一个mimic.launch.py 文件
cd ~/launch_ws/src/launch_tutorial/launch
vim mimic.launch.py
  • 内容如下:
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'),
         ]
      )
   ])
  • 该启动文件将启动模拟节点,该节点将向一个turtlesim 发出命令以跟随另一个。
  • 该节点旨在接收话题/输入/姿势上的目标姿势。
  • 在我们的例子中,我们想要从 /turtle2/pose 话题重新映射目标姿势。
  • 最后,我们将 /output/cmd_vel 话题重新映射到 /turtlesim2/turtle1/cmd_vel。
  • 这样,我们的turtlesim2 模拟世界中的turtle1 将跟随我们最初的turtlesim 世界中的turtle2。

配置文件

  • 现在让我们创建一个名为 turtlesim_rviz.launch.py 的文件
cd ~/launch_ws/src/launch_tutorial/launch
vim turtlesim_rviz.launch.py
  • 内容如下:
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]
      )
   ])
  • 此启动文件将使用在 turtle_tf2_py 包中定义的配置文件启动 RViz。
  • 此 RViz 配置将设置世界框架,启用 TF 可视化,并以自上而下的视图启动 RViz。

环境变量

  • 现在让我们在包中创建最后一个名为 fixed_broadcaster.launch.py 的启动文件
cd ~/launch_ws/src/launch_tutorial/launch
vim fixed_broadcaster.launch.py
  • 内容如下:
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'],
      ),
   ])
  • 该启动文件显示了在启动文件中可以调用环境变量的方式。
  • 环境变量可用于定义或推送命名空间,以区分不同计算机或机器人上的节点。

重新编译:

  • 打开 setup.py 并添加以下行,以便安装 launch/ 文件夹中的启动文件和 config/ 中的配置文件。
cd ~/launch_ws/src/launch_tutorial/
vim setup.py
  • data_files 字段现在应该如下所示:
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'))),
   ],
  • 指定构建launch_tutorial 包,并使用symlink的方式
cd ~/launch_ws/
colcon build --symlink-install --packages-select launch_tutorial 
  • 加载工作空间
. ~/launch_ws/install/local_setup.bash
  • 要最终查看我们的代码的结果,请构建包并使用以下命令启动顶级启动文件:
ros2 launch launch_tutorial launch_turtlesim.launch.py
  • 您现在将看到两个turtlesim 模拟已启动。
  • 第一个有两只乌龟,第二个有一只。
  • 在第一个模拟中,turtle2 生成在世界的左下角。 它的目标是到达相对于turtle1 框架在x 轴上5 米远的carrot1 框架。
  • 第二个中的turtlesim2/turtle1 旨在模仿turtle2 的行为。
  • 如果要控制turtle1,请运行teleop 节点。
ros2 run turtlesim turtle_teleop_key
  • 效果图:

请输入图片描述

  • 除此之外,RViz 应该已经启动。
  • 它将显示相对于世界框架的所有海龟框架,其原点位于左下角。

请输入图片描述

纠错,疑问,交流: 请进入讨论区点击加入Q群

获取最新文章: 扫一扫右上角的二维码加入“创客智造”公众号


标签: ros2与launch入门教程