0%

Launch启动脚本(py)

鸟向檐上飞,云从窗里出。

ROS2可以用launch文件启动、管理多个节点 ROS2的launch文件推荐用python格式

创建launch文件

首先在一个功能包下创建launch文件夹,在该文件夹下创建.launch.py,并(cpp功能包)在CMakeLists.txt添加

1
2
install(DIRECTORY launch
DESTINATION share/${PROJECT_NAME})
A包的launch文件可以启动B包的节点

以下是一个简单示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
return LaunchDescription([
# 添加节点与配置
Node(
package = '包名',
executable = '可执行文件名',
name = '节点名称', # 可选,覆盖原节点名
namespace = '命名空间', # 可选,节点的命名空间
parameters = [ # 可选,参数配置
{'parameters_name' : value}, # 直接传参
],
remapping = [
('/before_topic','/after_topic')
], # 重映射主题与服务
output = 'screen' # 输出到控制台,或'log'输出到日志文件
)
])
启动launch文件
1
ros2 launch <package_name> <your_name>.launch.py

如何用yaml传参

yaml文件格式

1
2
3
4
my_node: # 节点名,一个文件中可以给多个节点传参
ros__parameters: # 固定关键字,注意中间有两个下划线
param1: 3.14
param2: "helloworld"

一般放置在功能包的config或params文件夹下

动态加载yaml文件示例

1
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
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration,PathJoinSubstitution
from ament_index_python.packages import get_package_share_directory

def generate_launch_description():
# 声明启动参数
config_file_arg = DeclareLaunchArgument(# 动态加载yaml文件,启动时选择合适的yaml文件
name = 'config_file',
default_value = 'default_params.yaml',# 默认值
description = 'Path to the YAML configuration file'
)

# 获取 YAML 文件路径
params_file = PathJoinSubstitution(
get_package_share_directory('my_package'), # 获取指定包的share目录
'config', # 所在文件夹
LaunchConfiguration('config_file') # yaml文件名,这里是动态加载 yaml文件
)

return LaunchDescription([
config_file_arg,
Node(
package = 'my_package',
executable = 'my_node',
name = 'my_node',
output = 'screen',
parameters = [params_file]
),
])

启动launch文件

1
ros2 launch my_package my_launch.py config_file:=custom_params.yaml

补充1:动态传参

1
2
3
4
5
6
7
8
9
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
# ...
config_file_arg = DeclareLaunchArgument(
name = '动参名',
default_value = '动参默认值',
description = '描述,可选'
)
LaunchConfiguration('动参名') # 调用动参

这样,我们就可以在启动时修改要传递的参数,即

1
ros2 launch my_package my_launch.py <动参名>:=<动参值>
这极大提高了launch文件的灵活性,而下面的实例中多次使用了这种方法,这当然是不必要的,但ai就这么写的,就懒得改了

补充2:Substitution的常见子类

Substitution 是 ROS 2 Launch 系统中用于延迟求值的机制,允许在运行时动态解析参数值,以下是常见子类: - TextSubstitution:表示一个文本值。 - PathJoinSubstitution:表示路径拼接操作,可拼接含LaunchConfiguration的路径。 - PythonExpression:表示一个 Python 表达式。 - LaunchConfiguration:表示一个启动参数的值,会返回一个Substitution类型对象而非实际值,故在路径拼接时不能用os.path.join

如何包含其他launch文件

以下是main.launch.py包含上一个launch文件的一个实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import os

from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from ament_index_python.packages import get_package_share_directory

def generate_launch_description():
# 获取被包含 Launch 文件的路径
another_launch_file = os.path.join(
get_package_share_directory('another_package'),
'launch',
'another_launch.py'
)

return LaunchDescription([
# 包含另一个 Launch 文件并传递参数
IncludeLaunchDescription(
# 将指定的launch文件加载为一个LaunchDescription对象
PythonLaunchDescriptionSource(another_launch_file),
launch_arguments={'config_file': 'custom_params.yaml'}.items() # 亦可以直接传参
),
])

如何根据条件启动节点

简单条件

1
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
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch.conditions import IfCondition

def generate_launch_description():
# 声明启动参数
start_node_arg = DeclareLaunchArgument(
name='start_node',
default_value='true',
description='Whether to start the node'
)

# 获取启动参数的值
start_node = LaunchConfiguration('start_node')

return LaunchDescription([
start_node_arg, # 声明启动参数
Node(
package='my_package',
executable='my_node',
name='my_node',
output='screen',
condition=IfCondition(start_node) # 条件启动
),
])

启动launch文件

1
2
# 启动节点,不启动则false
ros2 launch my_package my_launch.py start_node:=true
IfCondition会在true时启动节点,而另有函数UnlessCondition则会在false时启动节点。

复杂启动

1
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
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration, PythonExpression
from launch.conditions import IfCondition

def generate_launch_description():
# 声明启动参数
param1_arg = DeclareLaunchArgument(
name='param1',
default_value='value1',
description='Description of param1'
)
param2_arg = DeclareLaunchArgument(
name='param2',
default_value='value2',
description='Description of param2'
)

# 获取启动参数的值
param1 = LaunchConfiguration('param1')
param2 = LaunchConfiguration('param2')

# 构建条件表达式
condition = PythonExpression(['"', param1, '" == "value1" and "', param2, '" == "value2"'])

return LaunchDescription([
param1_arg, # 声明启动参数
param2_arg, # 声明启动参数
Node(
package='my_package',
executable='my_node',
name='my_node',
output='screen',
condition=IfCondition(condition) # 条件启动
),
])

补充:结合条件判断的信息打印

launch文件中提供了LogInfo函数来打印信息,而LogInfo中提供了condition参量

1
2
3
4
5
6
7
8
9
10
11
12
# ...
from launch.actions import LogInfo

def generate_launch_description():
# ...
return LaunchDescription([
# ...
LogInfo(
msg='Debug mode is enabled.', # 打印的信息
condition=IfCondition(condition) # 条件打印
),
])

如何在launch中执行外部命令

1
2
3
4
5
6
from launch.actions import ExecuteProcess
# ...
ExecuteProcess(
cmd=['ros2', 'run', 'my_package', node1_name], # 这里的node1_name为动态参数
output='screen'
),

cmd中为要执行的命令列表,我们亦可以将一个完整的命令作为一个字符串,但这需要在shell中实现

1
2
3
4
5
6
7
8
9
10
ExecuteProcess(
cmd=['ros2 run my_package my_node1'],
output='screen'
shell = true # 默认为false
),
ExecuteProcess(
cmd=['./my_bash.sh'],
output='screen'
shell = true
),

如何统一管理多节点

1
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
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import GroupAction, DeclareLaunchArgument
from launch.conditions import IfCondition
from launch.substitutions import LaunchConfiguration

def generate_launch_description():
# 声明启动参数
start_group_arg = DeclareLaunchArgument(
name='start_group',
default_value='true',
description='Whether to start the group'
)

# 获取启动参数的值
start_group = LaunchConfiguration('start_group')

return LaunchDescription([
start_group_arg, # 声明启动参数
GroupAction(
actions=[
Node(
package='my_package',
executable='my_node1',
name='my_node1',
output='screen'
),
Node(
package='my_package',
executable='my_node2',
name='my_node2',
output='screen'
),
],
condition=IfCondition(start_group), # 统一条件
namespace='my_namespace', # 统一命名空间
scoped=True # 作用域限定在组内
),
])