在大型项目中使用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_b
, background_g
, background_r
参数指定值 turtlesim3/sim
和 turtlesim2/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]
除此之外,RViz应该已经开始了。它将显示所有海龟帧相对于 world
帧,其原点在左下角。 [待校准@7735]
总结
在本教程中,您学习了使用ROS 2launch文件管理大型项目的各种技巧和实践。 [待校准@7736]