0 背景
在ROS中,launch工具可以帮助用户同时启动多个节点,以及引入多种设置如参数导入、节点名重映射等。在ROS1中,launch文件通过xml语言编写,后缀名为.launch
;而ROS2在xml的基础上(后缀名为.xml
),新增加了两种launch文件的编写方式——python(后缀名为.launch.py
)和yaml(后缀名为.yaml
)。尽管如此,官方demo以及一些比较受欢迎的ROS2项目,使用的launch文件的编写方式基本为python,而python的灵活性之大与受众之广也是得到众多开发者的认可的。
关于ROS2 launch文件的更多详细资料,笔者把一些常用链接整理如下:
- 一个很棒的github项目,对比了ROS1和ROS2的launch文件写法,提供了上手demo
- ROS2 humble版本 launch 教程
- ROS2 launch的设计文档
1 问题的提出
铺垫结束,回到本文关注的问题!
如何在ROS2 launch.py文件中,同时引入yaml和自定义变量参数?
之所以会提出这个问题,是因为在实际工程中,笔者发现当进行团队协作或者程序在不同平台上移植的时候,经常会遇到目录不统一的问题,而这些目录是作为参数存放在yaml中的。比如,某个文件存放在张三的/home/zhangsan/resource
下面,而李四要将yaml文件中的该路径修改为/home/lisi/resource
。又比如,某个文件存放在张三的/home/zhangsan/folder_A/folder_B
,而李四习惯将其存放在/home/lisi/folder_C
。如果每次从GitHub或者GitLab pull下他人的代码,都需要修改 ,显然不是长久之际。于是自然地想到,有没有这样一种方式,能够避免路径修改,使代码即拿即用呢?
2 问题的解决
通过阅读官方文档,可以知道,在launch文件中启动节点,可以直接定义ros参数:
也可以通过下面这种方式导入yaml文件:
一般我们使用yaml文件时,都是因为参数数量比较大。用yaml整理,更加简洁直观。但是,对于上述情况,笔者还是推荐将涉及路径的参数写在launch文件里,毕竟ROS2已经支持python了,使用python的优势马上就会介绍到。
-
第一个,只需知道yaml文件相对路径
from ament_index_python.packages import get_package_share_directory config = os.path.join( get_package_share_directory('launch_tutorial'), 'config', 'turtlesim.yaml' )
这个
get_package_share_memory
会获取功能包所在的路径(在install文件夹内,而不是源代码文件夹),而yaml文件在功能包内部,只需知道相对路径即可,从而避免对yaml绝对路径的编辑与修改。 -
第二个,其他资源文件可以用
~
代替主目录import os resource_path = os.path.expanduser('~/demo/resource/dataset') # 在.launch.py中可以使用~表示主目录,从而提高程序的可移植性
-
第三个,yaml文件可以和自定义参数同时使用,从而实现一般参数和特殊参数(如路径)的统一
见下面的例程,这里使用的是composable node,但是对于一般的node也是可以的,可以看上面的两个代码截图,有parameter项。import launch from launch_ros.actions import ComposableNodeContainer from launch_ros.descriptions import ComposableNode # 以下两个包是额外import进来的,用于处理目录和字符串 import os from ament_index_python.packages import get_package_share_directory def generate_launch_description(): """Generate launch description with multiple components.""" yaml_file = os.path.join(get_package_share_directory("onboard_pkg"), "config", "onboard_pkg.yaml") # yaml文件在config目录下,记得在CMakeLists.txt中写install相关内容(config目录需要加入到install目录中去) resource_path = os.path.expanduser('~/demo/resource/dataset') # 在.launch.py中可以使用~表示主目录,从而提高程序的可移植性 # 建议涉及到目录的参数都在launch文件中写,以提高程序的可移植性 individual_params = {"resource_path": resource_path} # 用字典自定义参数,key为resource_path,值为路径 lidar_node_params = [individual_params,yaml_file] # 列表元素可以是自定义参数,也可以是yaml文件的路径 container = ComposableNodeContainer( name='my_container', namespace='', package='rclcpp_components', executable='component_container', composable_node_descriptions=[ # 多个节点的情况,可以在List中写多个ComposableNode ComposableNode( # 只需要修改这个实例的内容 package='onboard_pkg', plugin='onboard_pkg::Lidar', name='lidar_node', parameters=lidar_node_params) ], output='screen', ) return launch.LaunchDescription([container])
3 多问一句为什么
为什么可以把launch文件中自定义的参数和yaml中给的参数同时传给节点?
选中Node中的parameter跳转到其定义,再在类的初始化函数中找到parameter相关的函数normalize_parameters函数进行跳转,会发现parameter list支持三种类型,并且会根据元素的类型,进行相应的处理,从而可以支持同时传入yaml和自定义参数