# ROS2基础教程 - 10 Launch
在 ROS 中,launch
系统用于启动和管理多个节点及其配置。
例如我们启动小海龟程序,要开启两个终端,在后面我们的程序可能要启动更多的节点,使用 ros2 run
命令,会非常的麻烦,使用 launch
我们可以同时启动多个节点,并进行一些参数的配置。
和 ROS1 不同, ROS2 中 launch 文件就是一个 XML 文件,在 ROS2 中,launch 文件可以使用 XML、YAML 或 Python 脚本,但是推荐使用 Python 脚本(通常是 .py
文件)作为启动文件。这些文件定义了要启动的节点、参数、条件等。
# 10.1 使用launch启动多个节点
# 1 创建功能包
我们可以在任意功能包下创建 launch
文件,为了方便演示,我这里单独创建一个功能包:
ros2 pkg create --build-type ament_python launch_python
launch
文件需要放在功能包下的 launch
目录下,如果没有该目录,则需要先创建。
# 2 编写launch文件
在 ROS2 中有一个发布订阅的demo,使用topic通信,可以分别使用如下的命令来分别启动发布者和订阅者:
# 启动发布者
ros2 run demo_nodes_py talker
# 启动订阅者
ros2 run demo_nodes_py listener
2
3
4
5
我们可以使用 launch 同时启动上面两个节点。
首先需要编写 launch 文件,定义需要启动的节点、参数、条件等。
在功能包的 launch
目录下创建一个 launch 文件了。
例如我创建一个 demo_launch.py
的 launch
文件,内容如下:
# demo_launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='demo_nodes_py',
executable='talker',
output='screen'
),
Node(
package='demo_nodes_py',
executable='listener',
output='screen'
),
])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
在上面的代码中,使用 Node 定义了两个节点,同时启动了发布者和订阅者。
使用
LaunchDescription
对象定义一系列要执行的启动的节点;package
表示的是功能包的名称;executable
表示的是节点的名称。output
指定节点的日志输出方式,如screen
或log
。
# 3 配置launch文件(重要)
在 setup.py
中新增 launch
相关配置:
from setuptools import find_packages, setup
import os
from glob import glob
package_name = 'launch_python'
setup(
# ...其他配置
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','*.*'))),
(os.path.join('share', package_name, 'rviz'), glob(os.path.join('rviz', '*.*'))),
],
# ...其他配置
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
上面新增了三行配置,主要作用是安装 launch、config、rviz
这三个目录中的文件到编译的 install
目录中,这样在运行的时候,能找到这些目录下的文件。(否则运行launch文件找不到)
注意,后面使用了通配符进行了文件的匹配,你需要按照规则命令launch文件,我这里launch文件需要以 _launch.py
结尾。
# 4 构建项目
在工作空间下执行如下命令:
colcon build
构建完成,会在 install
目录下生成功能包的文件。
# 5 运行launch
首先执行 source
命令,在工作空间下执行:
source install/local_setup.sh
上面的命令是让 ROS 找到我们的功能包,已经在 HelloWorld 章节说过了。
然后启动launch:
ros2 launch launch_python demo_launch.py
运行后,发现同时启动了发布者和订阅者:
# 10.2 Node的参数
# 1 name参数
为节点指定一个唯一的名称。如果不指定,ROS 2将使用可执行文件的名称作为节点的默认名称。但是,如果多个相同名称的节点被启动,可能会导致冲突。
例如下面启动两个小海龟的窗口:
# turtle_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='turtle_a',
output='screen'
),
Node(
package='turtlesim',
executable='turtlesim_node',
name='turtle_b',
output='screen'
),
])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
运行 launch 后,使用 ros2 node list
查看节点,可以看到两个节点:
原来如果不通过 name
设置节点名称,直接使用 ros2 run turtlesim turtlesim_node
, 节点名称是 turtlesim
。
# 2 namespace参数
命名空间可以用于将一组相关的节点、主题、服务等资源进行分组,避免名称冲突。例如,在一个复杂的机器人系统中,可能有多个相同类型的传感器,为它们设置不同的命名空间可以方便地区分和管理。
如果有两个相同的激光雷达节点,一个用于前部感知,一个用于后部感知,可以为它们分别设置命名空间。假设前部激光雷达节点的namespace='front_lidar'
,那么这个节点发布的主题可能将会变成 /front_lidar/scan
(假设主题名称是scan
),而后部激光雷达节点如果namespace='rear_lidar'
,那么这个节点发布的主题将会变成 /rear_lidar/scan
。
举个栗子:
# turtle_launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='turtlesim',
executable='turtlesim_node',
namespace='turtle',
name='turtle_a',
output='screen'
),
Node(
package='turtlesim',
executable='turtlesim_node',
namespace='turtle',
name='turtle_b',
output='screen'
),
])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
上面为两个节点设置了命名空间,此时查看节点列表,发现节点前面多了命名空间:
此时查看话题信息,可以看到节点发布和订阅的主题,前面也多了命名空间:
如果单独运行 ros2 run turtlesim turtlesim_node
,是没有命名空间的:
# 3 remappings参数
上面在同一个命名空间下运行了两个小海龟,但是有一个问题的,两个节点订阅和发布的 topic 是相同的,能否让两个节点发布和订阅的主题不同呢?可以使用 remappings
参数。
remappings
是用于主题(Topic)和服务(Service)的重命名的机制。比如,一个节点发布的主题名称为/image_raw
,而另一个节点期望接收的主题名称为/camera/image
。通过remappings
,可以将/image_raw
重映射为/camera/image
,使得两个节点能够正常通信。
举个栗子:
# turtle_launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='turtlesim',
executable='turtlesim_node',
namespace='turtle',
name='turtle_a',
output='screen',
remappings=[
('/turtle/turtle1/cmd_vel', '/turtle/cmd_vel_a'),
('/turtle/turtle1/pose', '/turtle/pose_a')
]
),
Node(
package='turtlesim',
executable='turtlesim_node',
namespace='turtle',
name='turtle_b',
output='screen',
remappings=[
('/turtle/turtle1/cmd_vel', '/turtle/cmd_vel_b'),
('/turtle/turtle1/pose', '/turtle/pose_b')
]
),
])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
在上面使用 remappings
分别修改了节点订阅和发布的 topic,此时使用 ros2 topic list
查看topic,显示如下:
# 4 parameters参数
节点会定义一些参数,我们在使用 launch 启动节点的时候,可以传递参数。
举个栗子:
以小海龟为例,可以传递窗口的背景颜色(当然,这些参数是节点中定义的)。
# turtle_launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='turtlesim',
executable='turtlesim_node',
namespace='turtle',
name='turtle_a',
output='screen',
parameters=[
{'background_r' : 100},
{'background_g' : 100},
{'background_b' : 100},
]
),
Node(
package='turtlesim',
executable='turtlesim_node',
namespace='turtle',
name='turtle_b',
output='screen',
parameters=[
{'background_r' : 255},
]
),
])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
在上面使用 parameters
传递参数。
但是对于上面的方式,但是是固定写死的,无法在运行 launch 文件的时候,动态的传递参数。如果想在运行 ros2 launch
命令的时候动态的传递参数,我们可以使用 DeclareLaunchArgument
和 LaunchConfiguration
来定义 ,举个栗子:
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
background_r_arg = DeclareLaunchArgument(
'background_r', default_value='100', description='Red component of the background color'
)
background_g_arg = DeclareLaunchArgument(
'background_g', default_value='100', description='Green component of the background color'
)
background_b_arg = DeclareLaunchArgument(
'background_b', default_value='100', description='Blue component of the background color'
)
node = Node(
package='turtlesim',
executable='turtlesim_node',
namespace='turtle',
name='turtle_a',
output='screen',
parameters=[{
'background_r': LaunchConfiguration('background_r'),
'background_g': LaunchConfiguration('background_g'),
'background_b': LaunchConfiguration('background_b'),
}]
)
# 声明并使用参数
return LaunchDescription([background_r_arg, background_g_arg, background_b_arg, node])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
上面 DeclareLaunchArgument
和 Node
都是 launch
中的 action
,所以在使用 LaunchDescription
启动的时候,都需要加载。
那么在运行 launch
文件时动态指定参数值,如:
ros2 launch my_package my_launch_file.launch.py background_r:=200 background_g:=150 background_b:=50
上面配置和读取参数的方式有点复杂,如果参数很多,我们可以将参数放在参数文件中尽心配置,然后读取参数文件。
举个栗子:
首先在功能包的 config
目录创建并定义参数文件:
/turtle/turtle_a: # 对应指定的命名空间和节点名称
ros__parameters: # 此部分用于定义节点的参数
background_r: 255
background_g: 0
background_b: 0
2
3
4
5
然后加载并使用参数文件,需要指定上面参数文件的路径:
# turtle_launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
import os
from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
# 得到配置文件的路径
# 查找launch_python功能包下config目录下的turtle.yaml配置文件
param_path = os.path.join(
get_package_share_directory('launch_python'),
'config',
'turtle.yaml'
)
return LaunchDescription([
Node( # 启动rviz
package='turtlesim',
executable='turtlesim_node',
namespace='turtle',
name='turtle_a',
output='screen',
parameters=[param_path] # 指定配置文件
)
])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 5 arguments参数
arguments
参数用于向节点的可执行文件传递命令行参数。
打个比方,如果 turtlesim
节点支持参数,例如 ros2 run turtlesim turtlesim_node -debug -frequency 10.0
,那么在 launch 文件中配置如下:
# turtle_launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='turtlesim',
executable='turtlesim_node',
namespace='turtle',
name='turtle_a',
output='screen',
arguments=['-debug', '-frequency 10.0']
)
])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
有些节点的启动可以自定参数,你可以在 launch 文件中使用 arguments
参数进行配置。
例如,后面在启动 rviz
软件的时候,可以通过这个 arguments
参数来指定使用的配置文件。
在 launch 文件中启动 rviz
:
# turtle_launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
import os
from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
# 得到配置文件的路径
# 查找launch_python功能包下rviz目录下的rviz_config.rviz配置文件
rviz_config_file_path = os.path.join(
get_package_share_directory('launch_python'),
'rviz',
'rviz_config.rviz'
)
return LaunchDescription([
Node( # 启动rviz
package='rviz2',
executable='rviz2',
name='rviz2',
output='screen',
arguments=['-d', rviz_config_file_path] # 指定配置文件的路径
)
])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
rviz
后面再介绍。
# 10.3 condition
ROS2 的 launch
框架支持在启动节点时使用条件判断,例如,你想满足指定条件才启动指定的节点,
那么你可以使用 launch.conditions
模块中的条件类来控制节点的启动。
launch.conditions
主要提供了两种种条件类型,比如:
IfCondition
:如果条件为真,则执行该动作。UnlessCondition
:如果条件为假,则执行该动作。
# 1 IfCondition
举个栗子:
下面使用 IfCondition
来根据某个参数的值决定是否启动节点。
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch_ros.actions import Node
from launch.conditions import IfCondition
from launch.substitutions import LaunchConfiguration
def generate_launch_description():
# 声明一个布尔参数,用于控制节点的启动
enable_node_arg = DeclareLaunchArgument('enable_node', default_value='false')
# 使用 IfCondition 判断是否启动节点
node = Node(
package='turtlesim',
executable='turtlesim_node',
namespace='turtle',
name='turtle_a',
output='screen',
condition=IfCondition(LaunchConfiguration('enable_node')) # 根据参数决定是否启动
)
return LaunchDescription([
enable_node_arg,
node
])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
使用
DeclareLaunchArgument
声明一个名为enable_node
的参数,默认值为'false'
。在定义节点时,使用
IfCondition
来判断enable_node
的值。如果这个参数为true
,则启动turtlesim_node
,所以上面不会启动节点。
# 2 UnlessCondition
如果想在某个条件不成立时启动节点,可以使用 UnlessCondition
。
举个栗子:
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch_ros.actions import Node
from launch.conditions import UnlessCondition
from launch.substitutions import LaunchConfiguration
def generate_launch_description():
# 声明一个布尔参数,用于控制节点的启动
enable_node_arg = DeclareLaunchArgument('enable_node', default_value='false')
# 使用 IfCondition 判断是否启动节点
node = Node(
package='turtlesim',
executable='turtlesim_node',
namespace='turtle',
name='turtle_a',
output='screen',
condition=UnlessCondition(LaunchConfiguration('enable_node')) # 根据参数决定是否启动
)
return LaunchDescription([
enable_node_arg,
node
])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
在上面的代码中,只有在 enable_node
为 false
时,才会启动节点。
# 10.4 包含其他launch文件
有什么功能很复杂,所有的节点都写在一个 launch 文件中,会很臃肿和难以维护,我们可以将需要启动的节点和配置放在多个 launch 文件中,这样可以将复杂的系统分解为更小、可复用的 launch
文件,提升维护性和可扩展性。
下面介绍一下如何包含其他的 launch 文件,并传递参数。
假设有两个 launch
文件:
child_launch.py
:定义一个简单的节点和一些参数。main_launch.py
:包含child_launch.py
并传递参数。
# 1 定义子launch文件
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
param_arg = DeclareLaunchArgument(
'param_value', default_value='100', description='A parameter value for the node'
)
node = Node(
package='turtlesim',
executable='turtlesim_node',
namespace='turtle',
name='turtle_b',
output='screen',
parameters=[{'background_r': LaunchConfiguration('param_value')}]
)
return LaunchDescription([param_arg, node])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
子 launch
文件和之前定义 parameters
参数一样。
# 2 定义父launch文件
在父 launch 文件中,使用 IncludeLaunchDescription
包含其他的 launch 文件。
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription, DeclareLaunchArgument
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration
import os
from ament_index_python.packages import get_package_share_directory
from launch_ros.actions import Node
def generate_launch_description():
# 获取 child_launch.py 的路径
child_launch_path = os.path.join(
get_package_share_directory('launch_python'), # 指定功能包的名字
'launch',
'child_launch.py'
)
# 声明一个参数,供 child_launch.py 使用
param_arg = DeclareLaunchArgument(
'param_value', default_value='255', description='Parameter to pass to child launch'
)
# 包含 child_launch.py 并传递参数
include_child = IncludeLaunchDescription(
PythonLaunchDescriptionSource(child_launch_path),
launch_arguments={'param_value': LaunchConfiguration('param_value')}.items()
)
# 可以再创建一个node
node = Node(
package='turtlesim',
executable='turtlesim_node',
namespace='turtle',
name='turtle_a',
output='screen',
)
return LaunchDescription([param_arg, include_child, node])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
在上面的代码中,定义了参数并传给包含的 launch 文件。
如果要包含多个 launch 文件,需要定义多个 IncludeLaunchDescription
。
举个栗子:
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration
import os
from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
# 获取子 launch 文件的路径
launch_a_path = os.path.join(
get_package_share_directory('launch_python'), # 指定功能包的名字
'launch',
'launch_a.py'
)
launch_b_path = os.path.join(
get_package_share_directory('launch_python'),
'launch',
'launch_b.py'
)
# 包含 launch_a.py
include_launch_a = IncludeLaunchDescription(
PythonLaunchDescriptionSource(launch_a_path),
launch_arguments={'param1': 'value1'}.items() # 可选:传递参数
)
# 包含 launch_b.py
include_launch_b = IncludeLaunchDescription(
PythonLaunchDescriptionSource(launch_b_path),
launch_arguments={'param2': 'value2'}.items() # 可选:传递参数
)
return LaunchDescription([include_launch_a, include_launch_b])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 10.5 环境变量
我们在实际开发的时候,可以在系统的环境变量中配置一些变量,这样可以在代码中读取,根据不同的值处理不同的逻辑,不用每次修改代码进行重新编译。
在 launch 文件中,可以读取环境变量和修改环境变量。
# 1 读取环境变量
在 launch 文件中,可以使用 EnvironmentVariable
读取环境变量的值。
举个栗子:
from launch import LaunchDescription
from launch.substitutions import EnvironmentVariable
from launch_ros.actions import Node
def generate_launch_description():
param_value = EnvironmentVariable('MY_ENV_VAR', default_value='255')
node = Node(
package='turtlesim',
executable='turtlesim_node',
output='screen',
parameters=[{'background_r': param_value}]
)
return LaunchDescription([node])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
EnvironmentVariable('MY_ENV_VAR')
:此行代码将读取环境变量 MY_ENV_VAR
的值,并将其作为参数传递给节点。
使用下面的方式也可以读取环境变量的值:
import os
my_env_value = os.getenv('MY_ENV_VAR', 'default_value')
2
两者的区别:
EnvironmentVariable()
:是 ROS 2launch
文件中用于读取环境变量值的类,适合在 ROS 2launch
文件中定义和传递参数时使用,能够在LaunchDescription
中用作参数或条件的一部分。当使用launch
文件运行时,它会动态读取环境变量的值,这意味着如果环境变量在运行时被更改,这个修改会生效。os.getenv()
:是 Python 内置的函数,用于在任何 Python 脚本中读取环境变量,如果你在launch
文件之外编写 Python 脚本并需要读取环境变量,那么使用这个。os.getenv()
在 Python 脚本启动时会读取环境变量的值。如果环境变量在脚本运行后被更改,该修改不会反映在已经启动的进程中。
# 2 修改环境变量
SetEnvironmentVariable
是 launch.actions
模块中的一个类,它可以在 launch
文件中使用,来改变启动上下文中的环境变量。
举个栗子:
from launch import LaunchDescription
from launch.actions import SetEnvironmentVariable
from launch.substitutions import EnvironmentVariable
from launch_ros.actions import Node
def generate_launch_description():
# 设置环境变量
set_env_var = SetEnvironmentVariable('MY_ENV_VAR', '100')
# 读取环境变量
param_value = EnvironmentVariable('MY_ENV_VAR', default_value='255')
# 定义节点
node = Node(
package='turtlesim',
executable='turtlesim_node',
output='screen',
parameters=[{'background_r': param_value}]
)
return LaunchDescription([
set_env_var,
node
])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
SetEnvironmentVariable
和 Node
都是 launch
中的 action
,所以都需要使用 LaunchDescription
加载,并且在 Node 之前加载,作为启动 Node
必要的环境变量,使节点能够读取这些变量并按照预期运行。
# 10.6 打印日志
在 ROS2 launch
框架中,launch.actions
提供了一组用于定义和控制启动行为的类,这些类被称为“动作” (actions
)。每个 action
在 launch
文件中用于执行特定的任务,像前面的声明参数(DeclareLaunchArgument
)、设置环境变量(SetEnvironmentVariable
)、启动节点(Node
)等都是 action
。
同样,在 launch 中打印日志,也有对应的 action 来处理。
举个栗子:
from launch import LaunchDescription
from launch.actions import LogInfo
from launch.actions import DeclareLaunchArgument
from launch.substitutions import EnvironmentVariable
from launch.substitutions import LaunchConfiguration
def generate_launch_description():
# 打印简单的值
log_action1 = LogInfo(msg="Hello")
# 打印拼接
log_action2 = LogInfo(msg=["Hello ", str(123)]) # 拼接字符串和整数
# 打印环境变量
env_var_value = EnvironmentVariable('MY_ENV_VAR', default_value='foooor')
log_action3 = LogInfo(msg=["The value of MY_ENV_VAR is: ", env_var_value])
# 打印参数值
param_arg = DeclareLaunchArgument('my_param', default_value='default_value')
log_action4 = LogInfo(msg=["The value of my_param is: ", LaunchConfiguration('my_param')])
return LaunchDescription([
param_arg,
log_action1,
log_action2,
log_action3,
log_action4
])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
在上面的代码中,通过 LogInfo
打印日志,演示了各种打印情况。
在 launch
文件中,还可以使用以下动作来打印日志:
LogInfo
:用于打印一般信息。LogWarn
:用于打印警告信息。LogError
:用于打印错误信息。LogDebug
:用于打印调试信息。
# 10.7 GroupAction
GroupAction 可以在 launch
文件中将一组动作组合在一起。它允许你对这些动作应用共享的上下文设置,如命名空间、条件、参数等。
GroupAction 的主要功能和优势:
- 组织和分组:将一组相关的
launch
动作(如节点启动、参数声明等)组合在一起。 - 共享设置:可以为组内的动作应用相同的设置,比如命名空间、条件等。
- 条件执行:可设置条件,只有在满足条件时,组内的动作才会被执行。
- 作用域管理:可以在同一作用域内执行多个动作。
# 1 基本用法示例
下面是一个使用 GroupAction
的简单示例,展示如何将多个节点放置在同一命名空间下:
from launch import LaunchDescription
from launch.actions import GroupAction
from launch_ros.actions import PushRosNamespace
from launch_ros.actions import Node
def generate_launch_description():
# 使用 GroupAction 将多个节点放置在同一命名空间中
group = GroupAction([
PushRosNamespace('turtle'),
Node(
package='turtlesim',
executable='turtlesim_node',
name='turtle_a',
output='screen'
),
Node(
package='turtlesim',
executable='turtlesim_node',
name='turtle_b',
output='screen'
)
]) # 为组内所有节点设置命名空间
return LaunchDescription([
group
])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- 在上述示例中,
turtle1
和turtle2
节点都被放置在turtle
命名空间下。
运行后查看节点信息:
# 2 使用条件执行
GroupAction
还可以用于条件执行,使组内动作只有在特定条件下才会被执行,和之前 Node
的 condition
一样。
举个栗子:
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, GroupAction, LogInfo
from launch.conditions import IfCondition
from launch_ros.actions import Node
from launch_ros.actions import PushRosNamespace
from launch.substitutions import LaunchConfiguration
def generate_launch_description():
# 声明用于控制组执行的参数
condition_arg = DeclareLaunchArgument('run_group', default_value='true')
group = GroupAction(
actions=[
PushRosNamespace('turtle'), # 设置命名空间
Node(
package='turtlesim',
executable='turtlesim_node',
name='turtle_a',
output='screen'
),
Node(
package='turtlesim',
executable='turtlesim_node',
name='turtle_b',
output='screen'
),
LogInfo(msg='Launching the turtle node group') # 打印日志
],
condition=IfCondition(LaunchConfiguration('run_group')) # 判断条件是否启动
)
return LaunchDescription([
condition_arg,
group
])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
IfCondition
:设置条件,只有当run_group
参数为true
时,组内的动作才会被执行。
LaunchConfiguration
:用于获取launch
文件中的参数值。- 同样可以使用
from launch.conditions import UnlessCondition
,和前面介绍condition
的时候一样,这里就不介绍了。
# 3 嵌套使用GroupAction
还可以嵌套使用 GroupAction
来构建更复杂的层次结构。
举个栗子:
from launch import LaunchDescription
from launch.actions import GroupAction
from launch_ros.actions import Node
from launch_ros.actions import PushRosNamespace
def generate_launch_description():
# 子分组
child_group = GroupAction([
PushRosNamespace('child_turtle'), # 设置命名空间
Node(
package='turtlesim',
executable='turtlesim_node',
name='turtle_a',
output='screen'
),
Node(
package='turtlesim',
executable='turtlesim_node',
name='turtle_b',
output='screen'
)
])
# 父分组,包含子分组
super_group = GroupAction([
PushRosNamespace('super_turtle'), # 设置命名空间
child_group,
Node(
package='turtlesim',
executable='turtlesim_node',
name='turtle_c',
output='screen'
)
])
return LaunchDescription([
super_group
])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
上面使用分组打开了三个小海龟窗口,turtle_a
和 turtle_b
被放置在 child_turtle
命名空间下,而 turtle_c
和整个 child_turtle
被放置在 super_turtle
命名空间下。
运行后,查看节点信息:
查看话题信息:
可以看到,节点和话题都在指定的命令空间下。
# 4 配合其他Launch Actions使用
GroupAction
可以和其他 launch
动作(如 IncludeLaunchDescription
、SetParameter
等)一起使用,实现更丰富的功能。
举个栗子,下面演示一下包含其他的launch文件。
首先定义 child_launch.py
:
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, LogInfo
from launch.substitutions import LaunchConfiguration, EnvironmentVariable
from launch_ros.actions import Node
def generate_launch_description():
param_arg = DeclareLaunchArgument(
'param_value', default_value='100', description='A parameter value for the node'
)
env_value = EnvironmentVariable('MY_ENV_VAR', default_value='100')
login_action = LogInfo(msg=['MY_ENV_VAR:', env_value])
node = Node(
package='turtlesim',
executable='turtlesim_node',
namespace='turtle',
name='turtle_b',
output='screen'
parameters=[{'background_r': LaunchConfiguration('param_value')}]
)
return LaunchDescription([
param_arg,
login_action,
node
])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
定义一个子 launch
文件,在启动一个节点,并接收传递的参数,读取环境变量等操作。
然后定义一个 main_launch.py
:
from launch import LaunchDescription
from launch.actions import GroupAction, IncludeLaunchDescription, SetEnvironmentVariable, DeclareLaunchArgument
from launch.launch_description_sources import PythonLaunchDescriptionSource
import os
from ament_index_python.packages import get_package_share_directory
from launch_ros.actions import Node, PushRosNamespace
def generate_launch_description():
param_arg = DeclareLaunchArgument(
'param_value', default_value='100', description='A parameter value for the node'
)
included_launch = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(get_package_share_directory('launch_python'), 'launch', 'child_launch.py')
)
)
# 创建一个node
node = Node(
package='turtlesim',
executable='turtlesim_node',
name='turtle_a',
output='screen'
)
# 通过分组设置
group = GroupAction([
PushRosNamespace('turtle'), # 设置命名空间
param_arg, # 设置参数
SetEnvironmentVariable('MY_ENV_VAR', '200'), # 设置环境变量
node, # 设置启动节点
included_launch # 包含其他launch文件
])
return LaunchDescription([
group
])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
在上面的 launch 文件中,GroupAction
中包含了设置命名空间、参数、环境变量、节点、包含另一个 launch
文件。
总结 :通过使用 GroupAction
,可以有效组织和控制 launch
文件中多个动作的执行,使其更具可维护性和可扩展性。