Launch节点启动和配置脚本
每当运行一个ROS节点,都需要打开一个新的终端运行一个新的命令,机器人系统中节点非常多,每次都重新开一个终端运行新的命令非常繁琐,Launch启动文件可以一次启动多个节点,是ROS系统中多节点启动与配置的一种脚本。
Launch介绍
ROS2中的Launch文件是基于Python描述的,Launch的核心目的是启动节点,在命令行中输入的各种参数,在Launch文件中,通过类似这样的很多代码模版,也可以进行配置,甚至还可以使用Python原有的编程功能,大大丰富了启动过程中的多样化配置。
多节点启动
启动终端,使用ros2中的launch命令来启动第一个launch文件示例:
$ ros2 launch learning_launch simple.launch.py
运行成功可以在终端中看到发布者与订阅者两个节点交替的日志信息
文件解析
learning_launch/simple.launch.py
from launch import LaunchDescription # launch文件的描述类
from launch_ros.actions import Node # 节点启动的描述类
def generate_launch_description(): # 自动生成launch文件的函数
return LaunchDescription([ # 返回launch文件的描述信息
#发布者节点
Node( # 配置一个节点的启动
package='learning_topic', # 节点所在的功能包
executable='topic_helloworld_pub', # 节点的可执行文件
),
#订阅者节点
Node( # 配置一个节点的启动
package='learning_topic', # 节点所在的功能包
executable='topic_helloworld_sub', # 节点的可执行文件名
),
])
命令行参数配置
使用ros2命令在终端启动节点时,可以在命令后配置一些传入程序的参数,使用launch文件也可以做到
使用命令行运行一个Rviz可视化上位机,并且加载一个配置文件:
-d 后面跟的是参数
$ ros2 run rviz2 rviz2 -d <PACKAGE-PATH>/rviz/turtle_rviz.rviz
可以看出命令后边跟了很长一串配置文件的路径,如果将这很长的一串配置文件的路径放在launch文件里,就会很优雅:
$ ros2 launch learning_launch rviz.launch.py
文件解析
命令行后边的参数是如何通过launch传入节点的呢?
learning_launch/rviz.launch.py
import os
from ament_index_python.packages import get_package_share_directory # 查询功能包路径的方法
from launch import LaunchDescription # launch文件的描述类
from launch_ros.actions import Node # 节点启动的描述类
def generate_launch_description(): # 自动生成launch文件的函数
rviz_config = os.path.join( # 找到配置文件的完整路径,将文件路径拼接完成后送到arguments当中加载
get_package_share_directory('learning_launch'),
'rviz',
'turtle_rviz.rviz'
)
#返回文件描述类,即节点的具体信息
return LaunchDescription([ # 返回launch文件的描述信息
Node( # 配置一个节点的启动
package='rviz2', # 节点所在的功能包
executable='rviz2', # 节点的可执行文件名
name='rviz2', # 对节点重新命名,防止节点重名
arguments=['-d', rviz_config] # 加载命令行参数 通过上面的generate_launch_description()函数来实现
)
])
资源重映射
当我们想要使用别人代码的时候,经常会发现通信的话题名称不太符合自己的要求,为了提高软件的复用性,使用ROS提供的资源重映射的机制,可以解决话题名称不符合的问题
启动一个终端,运行小海龟仿真的例程,出现两个小海龟
$ ros2 launch learning_launch remapping.launch.py
再打开一个终端进行发布话题,让海龟1运动起来,海龟2会跟随海龟1一起运动
$ ros2 topic pub --rate 1 /turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 2.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 1.8}}"
文件解析
两个海龟之所以会一起运动,是用到了turtlesim功能包里另外一个节点,叫做mimic,他的功能是订阅某一个海龟的pose位置,通过计算,变换成一个同样运动的速度指定发布出去。mimic节点订阅或者发布的话题名是通过重映射修改成对应任意海龟的名字
learning_launch/remapping.launch.py
from launch import LaunchDescription # launch文件的描述类
from launch_ros.actions import Node # 节点启动的描述类
def generate_launch_description(): # 自动生成launch文件的函数
return LaunchDescription([ # 返回launch文件的描述信息
Node( # 配置一个节点的启动
package='turtlesim', # 节点所在的功能包
namespace='turtlesim1', # 节点所在的命名空间 在节点前会自动加上命名空间,避免节点名字重复
executable='turtlesim_node', # 节点的可执行文件名
name='sim' # 对节点重新命名
),
Node( # 配置一个节点的启动
package='turtlesim', # 节点所在的功能包
namespace='turtlesim2', # 节点所在的命名空间
executable='turtlesim_node', # 节点的可执行文件名
name='sim' # 对节点重新命名
),
# 上面两个节点是启动两个小乌龟
# 给小乌龟发送运动指令,小乌龟订阅运动指令后运动起来后也会发布pose话题,mimic节点是订阅一个小乌龟节点中的/input/pose 话题名并通过重映射将话题名修改成/turtlesim1/turtle1/pose,然后通过计算转换成速度指令;mimic将/output/cmd_vel速度话题重映射成/turtlesim2/turtle1/cmd_vel并发布出去,另一个小海龟再订阅这个话题则一起运行起来 重映射话题名是因为原本的话题加上了命名空间,为了避免修改话题名,使用重映射将话题名修改成我们想要的话题名(下方图中加上命名空间的话题名)
Node( # 配置一个节点的启动
package='turtlesim', # 节点所在的功能包
executable='mimic', # 节点的可执行文件名
name='mimic', # 对节点重新命名
remappings=[ # 资源重映射列表
('/input/pose', '/turtlesim1/turtle1/pose'), # 将/input/pose话题名修改为/turtlesim1/turtle1/pose
('/output/cmd_vel', '/turtlesim2/turtle1/cmd_vel'), # 将/output/cmd_vel话题名修改为/turtlesim2/turtle1/cmd_vel
]
)
])
ROS参数设置
ROS系统中的参数,可以在launch文件中设置
$ ros2 launch learning_launch parameters.launch.py
可以看到海龟仿真器的背景颜色被改变,这个颜色参数的设置就是在launch文件中完成
文件解析
learning_launch/parameters.launch.py
from launch import LaunchDescription # launch文件的描述类
from launch.actions import DeclareLaunchArgument # 声明launch文件内使用的Argument类
from launch.substitutions import LaunchConfiguration, TextSubstitution
from launch_ros.actions import Node # 节点启动的描述类
def generate_launch_description(): # 自动生成launch文件的函数
background_r_launch_arg = DeclareLaunchArgument(
'background_r', default_value=TextSubstitution(text='0') # 创建一个Launch文件内参数(arg)background_r
)
background_g_launch_arg = DeclareLaunchArgument(
'background_g', default_value=TextSubstitution(text='84') # 创建一个Launch文件内参数(arg)background_g
)
background_b_launch_arg = DeclareLaunchArgument(
'background_b', default_value=TextSubstitution(text='122') # 创建一个Launch文件内参数(arg)background_b
)
return LaunchDescription([ # 返回launch文件的描述信息
background_r_launch_arg, # 调用以上创建的参数(arg)
background_g_launch_arg,
background_b_launch_arg,
Node( # 配置一个节点的启动
package='turtlesim',
executable='turtlesim_node', # 节点所在的功能包
name='sim', # 对节点重新命名
parameters=[{ # ROS参数列表
'background_r': LaunchConfiguration('background_r'), # 创建参数background_r
'background_g': LaunchConfiguration('background_g'), # 创建参数background_g
'background_b': LaunchConfiguration('background_b'), # 创建参数background_b
}]
),
])
launch文件中出现的argument和parameter,虽都译为“参数”,但含义不同: - argument:仅限launch文件内部使用,方便在launch中调用某些数值; - parameter:ROS系统的参数,方便在节点间使用某些数值。
加载参数文件
参数过多的时候,在launch文件中一个一个的设置,会很麻烦,可以使用参数文件来进行加载
learning_launch/parameters_yaml.launch.py
import os
from ament_index_python.packages import get_package_share_directory # 查询功能包路径的方法
from launch import LaunchDescription # launch文件的描述类
from launch_ros.actions import Node # 节点启动的描述类
def generate_launch_description(): # 自动生成launch文件的函数
config = os.path.join( # 找到参数文件的完整路径
get_package_share_directory('learning_launch'),
'config',
'turtlesim.yaml'
)
return LaunchDescription([ # 返回launch文件的描述信息
Node( # 配置一个节点的启动
package='turtlesim', # 节点所在的功能包
executable='turtlesim_node', # 节点的可执行文件名
namespace='turtlesim2', # 节点所在的命名空间
name='sim', # 对节点重新命名
parameters=[config] # 加载参数文件
)
])
turtlesim2对应于namespace sim对应于name(节点的重命名)
Launch文件包含
launch文件也会有很多,使用类似include机制,可以让launch文件互相包含
文件解析
learning_launch/namespaces.launch.py
import os
from ament_index_python.packages import get_package_share_directory # 查询功能包路径的方法
from launch import LaunchDescription # launch文件的描述类
from launch.actions import IncludeLaunchDescription # 节点启动的描述类
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.actions import GroupAction # launch文件中的执行动作
from launch_ros.actions import PushRosNamespace # ROS命名空间配置
def generate_launch_description(): # 自动生成launch文件的函数
parameter_yaml = IncludeLaunchDescription( # 包含指定路径下的另外一个launch文件
PythonLaunchDescriptionSource([os.path.join(
get_package_share_directory('learning_launch'), 'launch'), #功能包/文件夹/launch文件
'/parameters_nonamespace.launch.py'])
)
parameter_yaml_with_namespace = GroupAction( # 对指定launch文件中启动的功能加上命名空间 防止两个launch文件有内容冲突,加上命名空间将两个launch文件进行隔离
actions=[
PushRosNamespace('turtlesim2'),
parameter_yaml]
)
return LaunchDescription([ # 返回launch文件的描述信息
parameter_yaml_with_namespace
])
在setup.py中功能包编译设置
...
#告诉编译器launch,config,rviz的文件夹后面都会使用到,通配符* 表示这些文件夹下的所有这些后缀的文件都会拷贝到install安装空间当中
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
(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', '*.*'))),
],
...