ros2 launch 用法以及一些基础功能函数的示例

launch

##前言

ros2 launch文件中最主要的概念是action,ros2 launch把每一个要执行的节点,文件,脚本,功能等全部抽象成action,用统一的接口来控制其启动,最主要的结构是:

def generate_launch_description():
    return LaunchDescription([
        action_1,
        action_2,
        ...
        action_n
    ])

要启动的节点或其他launch文件全部都传入LaunchDescription()函数中,该函数中接受一个或多launch.actionslaunch_ros.actions类型的对象,以下列举一下常用的action:

launch_ros.actions.Node
	· 此函数功能是启动一个ros2节点;
launch_ros.actions.PushRosNamespace
	· 此函数功能是给一个节点或组设置命名空间;
launch.actions.IncludeLaunchDescription
	· 此函数功能是直接引用另一个launch文件;
launch.actions.SetLaunchConfiguration
	· 此函数功能是在launch文件内声明一个参数,并给定参数值;
launch.actions.SetEnvironmentVariable
	· 此函数功能是声明一个环境变量并给定环境变量的值;
launch.actions.AppendEnvironmentVariable
	· 此函数将对一个环境变量追加一个值,如果不存在则创建;
launch.actions.DeclareLaunchArgument
	· 此函数功能是声明一个启动描述参数,该参数具有名称、默认值和文档;
launch.actions.TimerAction
	· 此函数功能是在一段时间后执行一个或多个action;
launch.actions.GroupAction
	· 此函数功能是将action分组,同组内的action可以统一设定参数方便集中管理;
launch.actions.ExecuteProcess
	· 此函数功能是根据输入执行一个进程或脚本;
launch.actions.EmitEvent
	· 此函数功能是发出一个事件,触发以注册的事件函数被调用;
launch.actions.RegisterEventHandler
	· 此函数功能是注册一个事件;
launch.actions.UnregisterEventHandler
	· 此函数功能是删除一个注册过的事件;

启动一个节点的launch示例

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='ros2_test',
            executable='ros2_test_publisher_node',
            name='ros2_test_publisher_node',
            output='screen'
        )
    ])

或者:

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    action_1 = Node(
    			package='ros2_test',
            	 executable='ros2_test_publisher_node',
            	 name='ros2_test_publisher_node',
            	 output='screen'
               )
    return LaunchDescription([
        action_1
    ])

launch文件名为test.launch.py,调用launch文件启动节点:

ros2 launch ros2_test test.launch.py

launch文件中添加节点的namespace

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='ros2_test',
            executable='ros2_test_publisher_node',
            name='ros2_test_publisher_node',
            output='screen',
            namespace='my_ros2_test'
        )
    ])

设置了namespace后,此节点的话题前会加上namespace的前缀,例如原来的话题名称’/test’,设置后变为’/my_ros2_test/test’

注意,有以下情况namespace无效:

节点本来就已经有了namespace了,后续会介绍将节点分组,整组节点可以设置统一的namespace,此时如果有个节点已经设置过namespace,则在此设置不会生效;
定义发布节点的时候,topic名字的前面加上了符号/;

launch文件中的话题名称映射

话题映射的字段是remappings,语法示例如下:

remappings=[
	('src1', 'dest1'),
	('src2', 'dest2'),
	...
]

如果需要话题名称映射的话要将该字段加入到节点的函数内,假如节点内有个test1话题要映射到test2,示例如下:

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='ros2_test',
            executable='ros2_test_publisher_node',
            name='ros2_test_publisher_node',
            output="screen",
            remappings=[("test1", "test2")]
        )
    ])

launch文件中向节点内传入命令行参数

参数字段是arguments,语法示例如下:

arguments=['arg1', 'arg2', ...]

如果需要向ros2_test_publisher_node节点中传入命令行参数,示例如下:

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='ros2_test',
            executable='ros2_test_publisher_node',
            name='ros2_test_publisher_node',
            output="screen",
            arguments=['arg1', 'arg2']
        )
    ])

传入的参数可以从节点启动时main函数中的argc参数获取到参数个数,argv参数获取到参数内容,详情参考c++的命令行参数获取;

launch文件中向节点内传入rosparam

字段是parameter,语法示例如下:

parameters=[
	{'port': '/dev/ttyUsb0'},
	...
]

如果需要在launch文件中发布某个节点空间下的parameter,示例如下:

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='ros2_test',
            executable='ros2_test_publisher_node',
            name='ros2_test_publisher_node',
            output="screen",
            parameter=[{'port': '/dev/ttyUSB0'}]
        )
    ])

如果需要参数的值可以从外部调用launch文件时灵活更改,则可以使用变量的形式作为参数的值,在文件中给定默认值,若外部给定则以外部给定的值为准,若外部不给则以默认值为准:

port_var = LaunchConfiguration('port', default='/dev/ttyS1')

同时还可以使用DeclareLaunchArgument函数增加对参数的描述,增加描述后可以通过ros2 launch的命令行工具展示出每个launch文件中有哪些rosparam以及他的默认值和具体含义:

DeclareLaunchArgument(
	'port',
	default_value=port_var,
	description='This is the port address value'
)

如果文件中加入了参数介绍则可以通过以下指令看到launch文件中有哪些可以自定义的参数以及参数含义:

ros2 launch ros2_test test.launch.py --show-args

所以如果launch文件中存在可配置参数,应该在LaunchDescription中添加DeclareLaunchArgument,让其他使用者明白变量名,以及他的含义是什么;

完整的launch文件如下:

from launch import LaunchDescription
from launch_ros.actions import Node
from launch.substitutions import LaunchConfiguration

def generate_launch_description():
    port_var = LaunchConfiguration('port', default='/dev/ttyS1')
    return LaunchDescription([
        DeclareLaunchArgument(
	        'port',
	        default_value=port_var,
	        description='This is the port address value'
        ),
        Node(
            package='ros2_test',
            executable='ros2_test_publisher_node',
            name='ros2_test_publisher_node',
            output="screen",
            parameter=[{'port': port_var}]
        )
    ])

此时我们可以把参数通过调用launch的时候传入到节点中:

ros2 launch ros2_test test.launch.py port_var:=/dev/ttyUSB2

在ros2_test_publisher_node节点中如果要想获取该参数,需要先声明parameter,然后再进行get:

this->declare_parameter<std::string>("port");
string port_str;
this->get_parameter_or<std::string>("port", port_str, "null");
if (port_str != "null") 
	RCLCPP_INFO(this->get_logger(), "get ros2param port value : %s", port_str.c_str());

使用方法

官方推荐用python编写launch,如果launch文件不在功能包中,随便建立以.launch.py结尾的文件后,在当前目录直接运行即可,如果在功能包中可采用下面方式运行。如下代码。

ros2 launch turtlesim_mimic_launch.py  # 在文件中直接启动launch
ros2 launch <package_name> <launch_file_name> # 在功能包中启动launch

文件内容实例如下:

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', # 节点的可执行文件名
        ),
    ])

多节点启动

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', # 节点的可执行文件名
        ),
    ])

命令行参数配置

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(          # 找到配置文件的完整路径
      get_package_share_directory('learning_launch'),
      'rviz',
      'turtle_rviz.rviz'
      )

   return LaunchDescription([           # 返回launch文件的描述信息
      Node(                             # 配置一个节点的启动
         package='rviz2',               # 节点所在的功能包
         executable='rviz2',            # 节点的可执行文件名
         name='rviz2',                  # 对节点重新命名
         arguments=['-d', rviz_config]  # 加载命令行参数
      )
   ])

资源重映射

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'                    # 对节点重新命名
        ),
        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参数设置

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
         }]
      ),
   ])

加载参数文件

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]           # 加载参数文件
      )
   ])

在launch文件中使用条件变量

字段是condition,语法示例如下:

condition=IfCondition(variable)

IfCondition(variable)函数内只接受true, false, 0, 1参数,如果节点启动的描述中加入该字段并且参数值为false或0则节点不启动

完整的launch文件如下:

from launch import LaunchDescription
from launch_ros.actions import Node
from launch.substitutions import LaunchConfiguration
from launch.conditions import IfCondition

def generate_launch_description():
    variable = LaunchConfiguration('start_flag', default='true')
    return LaunchDescription([
        DeclareLaunchArgument(
	        'start_flag',
	        default_value=variable,
	        description='This is the ros2_test_publisher_node start flag'
        ),
        Node(
            package='ros2_test',
            executable='ros2_test_publisher_node',
            name='ros2_test_publisher_node',
            output="screen",
            condition=IfCondition(variable)
        )
    ])

此时

运行:ros2 launch ros2_test test.launch.py 节点会正常启动

运行:ros2 launch ros2_test test.launch.py start_flag:=false 节点不会启动

进阶的IfCondition函数使用方式:

由于LaunchConfiguration()函数的返回是一个对象,所以我们不可以拿来直接做运算,但是可以使用PythonExpression()做参数的表达式运算,语法如下:

PythonExpression([variable, '== 1']) 或
PythonExpression([variable, '+ 1 == 2']) 或
PythonExpression([variable, "== 'test'"]) 等等

完整的launch文件如下:

from launch import LaunchDescription
from launch_ros.actions import Node
from launch.substitutions import LaunchConfiguration
from launch.conditions import IfCondition

def generate_launch_description():
	port_var = LaunchConfiguration('port', default='/dev/ttyS1')
	return LaunchDescription([
        DeclareLaunchArgument(
	        'port',
	        default_value=port_var,
	        description='This is the port address value'
        ),
        Node(
            package='ros2_test',
            executable='ros2_test_publisher_node',
            name='ros2_test_publisher_node',
            output="screen",
            condition=IfCondition(PythonExpression([port_var, "== '/dev/ttyUSB0'"]))
        )
    ])

此时

运行:ros2 launch ros2_test test.launch.py 节点不会启动

运行:ros2 launch ros2_test test.launch.py port_var:=/dev/ttyUSB0 节点正常启动

action的分组

GroupAction():函数可以将一个或多个action加入到一个组中,组内可以共用参数,控制整组节点全部启动或全部不启动等,方便action的管理,需要接受的部分参数有:

  • actions:接受一个list, [action_1, action_2,…],列表中装要执行的action
  • condition:条件变量参数
  • launch_configuration:参数

PushRosNamespace()函数的作用是向test_group中设置组内的namespace,但是action_1中已经设置过了namespace,所以此时不会生效,该namespace只会在action_2中生效;

实例一:

from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import GroupAction
from launch_ros.actions import PushRosNamespace

def generate_launch_description():
    action_1 = Node(
            package='ros2_test',
            executable='ros2_test_publisher_node',
            name='ros2_test_publisher_node',
            output='screen',
            namespace='my_ros2_test'
        )
    action_2 = Node(
            package='ros2_test',
            executable='ros2_test_subscriber_node',
            name='ros2_test_subscriber_node',
            output='screen'
        )
    test_group = GroupAction(
            actions=[
                PushRosNamespace("my_group_test"),
                action_1, 
                action_2
            ]
        )
    return LaunchDescription([
        test_group
    ])

action的启动延时控制

TimerAction():函数可以在指定的时间后执行一个action,需要接受参数有

  • period:接受一个float, 延迟的时间
  • actions:接受一个list, [action_1, action_2,…],列表中装要执行的action

实例一:

from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import TimerAction

def generate_launch_description():
    action_1 = Node(
            package='ros2_test',
            executable='ros2_test_publisher_node',
            name='ros2_test_publisher_node',
            output='screen'
        )
    return LaunchDescription([
        TimerAction(period=5.0, actions=[action_1])
    ])

Launch文件包含,在launch文件中调用另一个launch文件

PythonLaunchDescriptionSource() :在launch文件中调用其他launch需要调用函数,参数是被调用launch的路径。

实例一:

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'),
         '/parameters_nonamespace.launch.py'])
      )

   parameter_yaml_with_namespace = GroupAction(      # 对指定launch文件中启动的功能加上命名空间
      actions=[
         PushRosNamespace('turtlesim2'),
         parameter_yaml]
      )

   return LaunchDescription([                        # 返回launch文件的描述信息
      parameter_yaml_with_namespace
   ])

实例二:

import os
from ament_index_python import get_package_share_directory
from launch import LaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='ros2_test',
            executable='ros2_test_publisher_node',
            name='ros2_test_publisher_node',
            output='screen'
        ),
        PythonLaunchDescriptionSource(
        	os.path.join(
        		get_package_share_directory('ros2_test'),
        		'launch/test_subscriber.launch.py'
        	)
        )
    ])

实例三:

from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import ThisLaunchFileDir
from launch.conditions import IfCondition
from launch.substitutions import LaunchConfiguration
from launch.actions import IncludeLaunchDescription

def generate_launch_description():
    inlucde_other_file = LaunchConfiguration('inlucde_other_file', default='true')
    test_var = LaunchConfiguration('test_var', default='test_var_2')
    return launch.LaunchDescription([
        IncludeLaunchDescription(
            PythonLaunchDescriptionSource(
                [ThisLaunchFileDir(), '/test_subscriber.launch.py']
            ),
            condition=IfCondition(inlucde_other_file),
            launch_arguments={'test_var': test_var}.items()
        )
    ])

在launch文件中运行脚本

ExecuteProcess():运行脚本同样可实现启动某个节点,同时该函数还支持condition、additional_env等参数的设置。

实例一:

from launch import LaunchDescription
from launch.actions import ExecuteProcess

def generate_launch_description():
    return LaunchDescription([
        ExecuteProcess(
            cmd=['ros2', 'run', 'ros2_test', 'ros2_test_publisher_node'],
            output="screen"
        )
    ])

在launch文件中设置环境变量或读取环境变量

SetEnvironmentVariable(var_name, var_value):函数用于设置环境变量,接受两个参数分别是要设置的环境变量名称和环境变量的值;

EnvironmentVariable(var_name):函数用于获取环境变量的值,返回值类型是一个对象,输入参数是要获取的环境变量名称;

完整launch文件实例:

from launch import LaunchDescription
from launch.actions import SetEnvironmentVariable, ExecuteProcess
from launch.substitutions import EnvironmentVariable

def generate_launch_description():
    return LaunchDescription([
        SetEnvironmentVariable('PRODUCT_MODEL', 'spray_robot'),
        ExecuteProcess(
            cmd=['echo', EnvironmentVariable('PRODUCT_MODEL')],
            output='screen')
    ])

运行看到以下输出

[INFO] [echo-1]: process started with pid [195168]
[echo-1] spray_robot
[INFO] [echo-1]: process has finished cleanly [pid 195168]

launch 用法以及一些基础功能函数的示例

ros2 launch文件中最主要的概念是action,ros2 launch把每一个要执行的节点,文件,脚本,功能等全部抽象成action,用统一的接口来控制其启动,最主要的结构是:

def generate_launch_description():
    return LaunchDescription([
        action_1,
        action_2,
        ...
        action_n
    ])

要启动的节点或其他launch文件全部都传入LaunchDescription()函数中,该函数中接受一个或多launch.actionslaunch_ros.actions类型的对象,以下列举一下常用的action:

launch_ros.actions.Node
	· 此函数功能是启动一个ros2节点;
launch_ros.actions.PushRosNamespace
	· 此函数功能是给一个节点或组设置命名空间;
launch.actions.IncludeLaunchDescription
	· 此函数功能是直接引用另一个launch文件;
launch.actions.SetLaunchConfiguration
	· 此函数功能是在launch文件内声明一个参数,并给定参数值;
launch.actions.SetEnvironmentVariable
	· 此函数功能是声明一个环境变量并给定环境变量的值;
launch.actions.AppendEnvironmentVariable
	· 此函数将对一个环境变量追加一个值,如果不存在则创建;
launch.actions.DeclareLaunchArgument
	· 此函数功能是声明一个启动描述参数,该参数具有名称、默认值和文档;
launch.actions.TimerAction
	· 此函数功能是在一段时间后执行一个或多个action;
launch.actions.GroupAction
	· 此函数功能是将action分组,同组内的action可以统一设定参数方便集中管理;
launch.actions.ExecuteProcess
	· 此函数功能是根据输入执行一个进程或脚本;
launch.actions.EmitEvent
	· 此函数功能是发出一个事件,触发以注册的事件函数被调用;
launch.actions.RegisterEventHandler
	· 此函数功能是注册一个事件;
launch.actions.UnregisterEventHandler
	· 此函数功能是删除一个注册过的事件;

参数文件编译配置

功能包里面的参数文件,需要在setup.py里面做以下配置,编译的时候才能拷贝到include里面去

from setuptools import setup
import os
from glob import glob

package_name = 'learning_launch'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    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', '*.*'))),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='hcx',
    maintainer_email='huchunxu@guyuehome.com',
    description='TODO: Package description',
    license='TODO: License declaration',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
        ],
    },
)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值