赞
踩
在学习古月居ROS2入门教程时,启动一个小海龟仿真示例程序,需要启动多个节点(小海龟、键盘控制等),每次都需要打开一个新的终端,然后使用下面这种方式来启动节点:
ros2 run package-name execute-name
在小海龟仿真示例中,我们运行了以下两个命令:
ros2 run turtlesim turtlesim_node
ros2 run turtlesim turtle_teleop_key
如果我们需要启动更多的节点,就需要打开非常多的窗口,非常的不优雅。此时就轮到launch文件发挥作用啦:
以下代码片段是每个launch启动文件所需的基本框架:
from launch import LaunchDescription
def generate_launch_description():
return LaunchDescription([
# add your actions here...
])
generate_launch_description
函数,返回LaunchDescription
对象。为了使launch启动文件执行特定操作,我们需要向LaunchDescription
对象执行节点填充操作。
以小海龟仿真为例,需要向其中添加turtlesim_node
小海龟仿真器节点和turtle_teleop_key
键盘控制节点,完整的launch文件如下:
这块,我是直接在古月居的ros2_21_tutorials/learning_launch/launch
下面新建了simple_turtlesim.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import ExecuteProcess
def generate_launch_description():
return LaunchDescription([
Node(
package='turtlesim',
executable='turtlesim_node',
name='sim'
),
ExecuteProcess(
cmd=['xterm', '-e', 'ros2', 'run', 'turtlesim', 'turtle_teleop_key'],
name='teleop_key',
),
])
Node
用于在启动文件中定义一个节点。它的参数包括:
package
:这是一个字符串,表示节点所在的ROS包的名称。executable
:这是一个字符串,表示在给定包中的可执行文件的名称。name
:这是一个可选的字符串,用于指定节点的名称。如果未指定,将使用可执行文件的名称。namespace
:这是一个可选的字符串,用于指定节点的命名空间。命名空间是一种组织节点的方式,可以帮助避免节点名称的冲突。output
:这是一个可选的字符串,用于指定节点的输出应该如何处理。例如,可以将其设置为"screen"
,以便将节点的输出打印到屏幕上。parameters
:这是一个可选的列表,用于指定节点的参数。每个参数都是一个字典,其中包含参数的名称和值。remappings
:这是一个可选的列表,用于指定主题的重新映射。每个重新映射都是一个元组,其中包含原始主题的名称和新主题的名称。arguments
:这是一个可选的列表,用于指定传递给可执行文件的命令行参数。ExecuteProcess
用于在launch文件中启动一个进程。
创建启动文件的另一种方式是创建一个 LaunchDescription
对象ld
,然后通过add_action
方法填充节点:
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import ExecuteProcess
def generate_launch_description():
ld = LaunchDescription()
turtlesim_node = Node(
package='turtlesim',
executable='turtlesim_node',
name='sim'
)
teleop_key = ExecuteProcess(
cmd=['xterm', '-e', 'ros2', 'run', 'turtlesim', 'turtle_teleop_key'],
name='teleop_key',
)
ld.add_action(turtlesim_node)
ld.add_action(teleop_key)
return ld
添加launch启动文件和配置文件(三里面会讲到),这块我是直接在配置好的古月居的ros2_21_tutorials/learning_launch/setup.py
里面修改的,这里面直接用了*
通配符,所以不需要进行修改。
cd ~/dev_ws/src/ros2_21_tutorials/learning_launch/
vim setup.py
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'))),
],
编译
cd ~/dev_ws/
colcon build
source install/local_setup.bash
启动launch文件
ros2 launch learning_launch simple_turtlesim.launch.py
机器人上的大型应用通常涉及多个互连的节点,每个节点可以有许多参数。海龟模拟器中模拟多只海龟就是一个很好的例子。海龟模拟由多个海龟节点、世界配置以及 TF 广播器和监听器节点组成。在所有节点之间,存在大量影响这些节点的行为和外观的 ROS 参数。ROS2启动文件允许我们在一个地方启动所有节点并设置相应的参数。在教程结束时,运行launch_turtlesim.launch.py
启动文件将调出不同的节点,负责模拟两个turtlesim 模拟、启动 TF 广播器和监听器、加载参数以及启动 RViz 配置。
编写启动文件过程的目标之一应该是使它们尽可能可重用。这可以通过将相关节点和配置聚集到单独的启动文件中来完成。之后,可以编写专用于特定配置的顶级启动文件。这将允许在相同的机器人之间移动而无需更改启动文件。即使是从真实机器人转移到模拟机器人等改变也只需进行一些更改即可完成。
首先,我们将创建一个顶层启动文件,该文件将调用单独的启动文件。此处我还是在古月居的ros2_21_tutorials/learning_launch/launch
下面新建一个名为launch_turtlesim.launch.py
的launch文件。
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('learning_launch'), 'launch'),
'/turtlesim_world_1.launch.py'])
)
turtlesim_world_2 = IncludeLaunchDescription(
PythonLaunchDescriptionSource([os.path.join(
get_package_share_directory('learning_launch'), 'launch'),
'/turtlesim_world_2.launch.py'])
)
broadcaster_listener_nodes = IncludeLaunchDescription(
PythonLaunchDescriptionSource([os.path.join(
get_package_share_directory('learning_launch'), 'launch'),
'/broadcaster_listener.launch.py']),
launch_arguments={'target_frame': 'carrot1'}.items(),
)
mimic_node = IncludeLaunchDescription(
PythonLaunchDescriptionSource([os.path.join(
get_package_share_directory('learning_launch'), 'launch'),
'/mimic.launch.py'])
)
fixed_frame_node = IncludeLaunchDescription(
PythonLaunchDescriptionSource([os.path.join(
get_package_share_directory('learning_launch'), 'launch'),
'/fixed_broadcaster.launch.py'])
)
rviz_node = IncludeLaunchDescription(
PythonLaunchDescriptionSource([os.path.join(
get_package_share_directory('learning_launch'), '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 broadcaster, TF listener, mimic, fixed frame broadcaster, and RViz nodes
。首先编写一个启动文件来启动第一个turtlesim模拟器。首先,创建一个名为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_node节点。
在第二个模拟器launch启动文件中,我们将使用不同的参数配置方式。现在创建一个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('learning_launch'),
'config',
'turtlesim.yaml'
)
return LaunchDescription([
Node(
package='turtlesim',
executable='turtlesim_node',
namespace='turtlesim2',
name='sim',
parameters=[config]
)
])
此启动文件使用从 YAML 配置文件中加载的参数启动turtlesim_node节点。
在 YAML 文件中定义Parameter
和Argument
,可以轻松存储和加载大量变量。
Parameter
是形参,是函数声明时写的参数;而Argument
是实参,是调用函数时传递的参数值。此外,还可以轻松地从当前列表导出 YAML 文件。要了解如何执行此操作,请参阅了解参数教程。
现在在文件夹ros2_21_tutorials/learning_launch/config
中创建一个配置文件turtlesim.yaml
,它将由上述launch启动文件加载。
/turtlesim2/sim:
ros__parameters:
background_b: 255
background_g: 86
background_r: 150
此时启动turtlesim_world_2.launch.py
文件,将以预先配置的背景颜色启动turtlesim_node
。
有时我们想要在多个节点中设置相同的参数。这些节点具有不同的命名空间或名称,但仍然具有相同的参数。此时创建多个YAML文件显得十分低效,解决方案是使用通配符(其充当文本中未知字符的替换)将参数应用于多个不同的节点。
/**:
ros__parameters:
background_b: 255
background_g: 86
background_r: 150
我们在turtlesim_world_2.launch.py
文件中定义了 turlesim 世界模拟器的命名空间。独一无二的命名空间允许系统启动两个相同的节点,而不会出现节点名称或主题名称冲突。可以看到我们在turtlesim_world_1.launch.py
和turtlesim_world_2.launch.py
中的都使用了name='sim'
,就是因为命名空间的差异,才不会出现冲突。
但是,如果启动文件包含大量节点,则为每个节点定义命名空间同样十分低效,也不太方便管理。为了解决这个问题,可以使用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('learning_launch'), 'launch'),
'/turtlesim_world_2.launch.py'])
)
turtlesim_world_2_with_namespace = GroupAction(
actions=[
PushRosNamespace('turtlesim2'),
turtlesim_world_2,
]
)
最后,将LaunchDescription语句中的turtlesim_world_2
替换为turtlesim_world_2_with_namespacereturn
。As a result, each node in the turtlesim_world_2.launch.py
launch description will have a turtlesim2
namespace。
现在创建一个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')}
]
),
])
target_frame
参数,其默认值为 turtle1
。该参数会被传递给turtle_tf2_listener节点
;turtle_tf2_broadcaster
节点被使用不同的名称和参数两次调用。回想一下,我们在顶层启动文件中调用了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(),
)
target_frame
默认参数更改为carrot1
。现在创建一个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'),
]
)
])
mimic
节点,该节点将向一个turtlesim
发出命令以跟随另一个。/input/pose
. In our case, we want to remap the target pose from /turtle2/pose
topic. Finally, we remap the /output/cmd_vel
topic to /turtlesim2/turtle1/cmd_vel
. This way turtle1
in our turtlesim2 simulation world will follow turtle2
in our initial turtlesim world.现在让我们创建一个名为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
。现在让我们创建最后一个启动文件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'],
),
])
按照《二、4. 编译测试》中的内容进行编译,并运行如下启动命令:
ros2 launch learning_launch launch_turtlesim.launch.py
现在将看到两个turtlesim 模拟已启动。第一个有两只乌龟,第二个有一只乌龟。Its aim is to reach the carrot1 frame which is five meters away on the x-axis relative to the turtle1 frame.
如果您想控制turtle1,请运行 teleop 节点。
ros2 run turtlesim turtle_teleop_key
结果,你会看到类似的图片:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。