• 使用 Python、XML 和 YAML 编写 ROS 2 Launch 文件


    系列文章目录

    ROS2 重要概念
    ament_cmake_python 用户文档
    ROS2 ament_cmake 用户文档
    使用 rosdep 管理 ROS 2 依赖项



    前言

    ROS 2 launch 文件可以用 Python、XML 和 YAML 编写。本指南介绍了如何使用这些不同的格式来完成相同的任务,并对何时使用每种格式进行了讨论。


    一、Launch 文件示例

    下面是一个用 Python、XML 和 YAML 实现的 Launch 文件。每个 Launch 文件都执行以下操作:

    • 使用默认值设置命令行参数

    • 包含另一个 launch 文件

    • 在另一个命名空间中包含另一个启动文件

    • 启动节点并设置其名称空间

    • 启动一个节点,设置其名称空间,并在该节点中设置参数(使用参数)

    • 创建一个节点,将消息从一个话题重新映射到另一个话题

    1.1 Python 版本

    首先介绍涉及到的几个 方法

    1. ament_index_python.get_package_share_path(package_name, print_warning=True)
    以 pathlib.Path 的形式返回给定软件包的共享目录。
    例如,如果您将软件包 "foo "安装到"/home/user/ros2_ws/install",并以 "foo"作为参数调用此函数,那么它将返回一个代表"/home/user/ros2_ws/install/share/foo"的路径,然后您就可以用它来构建共享文件的路径,即 get_package_share_path('foo') /'urdf/robot.urdf'
    
    • 1
    • 2
    1. launch.LaunchDescription
    基础: LaunchDescriptionEntity
    可启动系统的描述。
    该描述由一系列实体组成,这些实体代表了系统设计师的意图。
    该描述还可能有参数,参数由该启动描述中的 launch.actions.DeclareLaunchArgument 操作声明。
    该描述的参数可通过 get_launch_arguments() 方法访问。参数是通过搜索此启动描述中的实体和每个实体的描述(可能包括由这些实体产生的实体)收集的。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. launch.actions.declare_launch_argument.DeclareLaunchArgument(Action)
    基础: Action
    声明新启动参数的 Action。
    启动参数存储在同名的 "启动配置 "中。请参阅 launch.actions.SetLaunchConfiguration 和 launch.substitutions.LaunchConfiguration。
    在 launch.LaunchDescription 中声明的任何启动参数都会在包含该启动描述时作为参数显示出来,例如,在 launch.actions.IncludeLaunchDescription 动作中作为附加参数,或在使用 ros2 launch .... 启动时作为命令行参数。
    除了名称(也是参数结果的存储位置)外,启动参数还可能有一个默认值、一个有效值选择列表和一个描述。如果给出了默认值,那么该参数就变成了可选参数,默认值将被放置在启动配置中。如果没有给出默认值,并且在包含启动说明时也没有给出值,则会发生错误。如果给出了一个选择列表,而给定值不在其中,则会发生错误。
    默认值可以使用 Substitutions,但名称和描述只能是 Text,因为它们在启动前需要一个有意义的值,例如在列出命令行参数时。
    需要注意的是,声明启动参数必须在启动描述的某个部分,而这个部分在不启动的情况下是可以描述的。例如,如果你在条件组中或作为事件处理程序的回调声明了一个启动参数,那么像 ros2 launch 这样的工具可能无法在启动描述之前知道该参数。在这种情况下,该参数在命令行上将不可见,但如果该参数在访问后未满足要求(且没有默认值),则可能引发异常。
    换句话说,访问该操作的后置条件要么是同名的启动配置设置了值,要么是由于没有设置任何值且没有默认值而引发异常。但是,前置条件并不能保证在条件或情况夹杂后面的参数是可见的。
    例如;
    ld = LaunchDescription([
        DeclareLaunchArgument('simple_argument'),
        DeclareLaunchArgument('with_default_value', default_value='default'),
        DeclareLaunchArgument(
            'with_default_and_description',
            default_value='some_default',
            description='this argument is used to configure ...'),
        DeclareLaunchArgument(
            'mode',
            default_value='A',
            description='Choose between mode A and mode B',
            choices=['A', 'B']),
        # other actions here, ...
    ])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    1. launch.actions.GroupAction
    基础: Action
    可产生其他操作的 Action。
    此操作用于嵌套其他操作,而无需包含单独的启动说明,同时还可选择具有一个条件(与所有其他操作一样)、扩展和转发启动配置和环境变量,以及/或仅为组及其产生的操作声明启动配置。
    Scoped=True 时,对启动配置和环境变量的更改仅限于组操作中的操作范围。
    当 scopeed=True 和 forwarding=True 时,所有现有的启动配置和环境变量都可在作用域上下文中使用。
    当 scope=True 和 forwarding=False 时,所有现有的启动配置和环境变量都会从作用域上下文中移除。
    launch_configurations 字典中定义的任何启动配置都将在当前上下文中设置。当 scopeed=False 时,即使 GroupAction 已完成,这些配置也将持续存在。当 scoped=True 时,这些配置将仅对 GroupAction 中的动作可用。当 scope=True 和 forwarding=False 时,launch_configurations 字典将在清除前进行评估,然后在清除的 scope 上下文中重新设置。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. launch.actions.IncludeLaunchDescription
    基础: Action
    包含启动描述源并在访问时生成其实体的动作。
    可以向启动描述传递参数,这些参数是通过 launch.actions.DeclareLaunchArgument 动作声明的。
    如果给定的参数与已声明的启动参数名称不匹配,则仍会使用 launch.actions.SetLaunchConfiguration 动作将其设置为启动配置。这样做的原因是,在给定的启动描述中,并非总能检测到所有已声明启动参数类的实例。
    另一方面,如果给定的启动描述声明了启动参数,但未向此操作提供其值,有时会引发错误。不过,只有当声明的启动参数是无条件的(有时声明启动参数的操作只有在特定情况下才会被访问),并且没有默认值可供选择时,才会产生这种错误。
    有条件包含的启动参数如果没有默认值,在尽力进行参数检查后仍无法提前发现未满足的参数时,最终将引发错误。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. launch.launch_description_sources.PythonLaunchDescriptionSource(LaunchDescriptionSource)
    基础: LaunchDescriptionSource
    Python 启动文件的封装,可在启动过程中加载。
    
    • 1
    • 2
    1. launch.substitutions.LaunchConfiguration(Substitution)
    可访问启动配置变量的替代变量。
    
    • 1
    1. launch.substitutions.TextSubstitution(Substitution)
    可对单个字符串文本进行替换。
    
    • 1
    1. PushROSNamespace(Action)
    推送 ros 命名空间的动作。
    在有作用域的 `GroupAction` 中使用时,它会自动弹出。没有其他方法可以弹出它。 
    
    • 1
    • 2
    1. XMLLaunchDescriptionSource(FrontendLaunchDescriptionSource)
    封装 XML 启动文件,可在启动过程中加载。
    
    • 1
    1. YAMLLaunchDescriptionSource(FrontendLaunchDescriptionSource)
    封装 YAML 启动文件,可在启动过程中加载。
    
    • 1
    1. Node(ExecuteProcess)
    执行一个 ROS 节点的操作。
    
    • 1

    Python 代码如下

    # example_launch.py
    
    import os
    
    from ament_index_python import get_package_share_directory
    
    from launch import LaunchDescription
    from launch.actions import DeclareLaunchArgument
    from launch.actions import GroupAction
    from launch.actions import IncludeLaunchDescription
    from launch.launch_description_sources import PythonLaunchDescriptionSource
    from launch.substitutions import LaunchConfiguration
    from launch.substitutions import TextSubstitution
    from launch_ros.actions import Node
    from launch_ros.actions import PushRosNamespace
    from launch_xml.launch_description_sources import XMLLaunchDescriptionSource
    from launch_yaml.launch_description_sources import YAMLLaunchDescriptionSource
    
    
    def generate_launch_description():
    
        # args that can be set from the command line or a default will be used
        background_r_launch_arg = DeclareLaunchArgument(
            "background_r", default_value=TextSubstitution(text="0")
        )
        background_g_launch_arg = DeclareLaunchArgument(
            "background_g", default_value=TextSubstitution(text="255")
        )
        background_b_launch_arg = DeclareLaunchArgument(
            "background_b", default_value=TextSubstitution(text="0")
        )
        chatter_py_ns_launch_arg = DeclareLaunchArgument(
            "chatter_py_ns", default_value=TextSubstitution(text="chatter/py/ns")
        )
        chatter_xml_ns_launch_arg = DeclareLaunchArgument(
            "chatter_xml_ns", default_value=TextSubstitution(text="chatter/xml/ns")
        )
        chatter_yaml_ns_launch_arg = DeclareLaunchArgument(
            "chatter_yaml_ns", default_value=TextSubstitution(text="chatter/yaml/ns")
        )
    
        # include another launch file
        launch_include = IncludeLaunchDescription(
            PythonLaunchDescriptionSource(
                os.path.join(
                    get_package_share_directory('demo_nodes_cpp'),
                    'launch/topics/talker_listener_launch.py'))
        )
        # include a Python launch file in the chatter_py_ns namespace
        launch_py_include_with_namespace = GroupAction(
            actions=[
                # push_ros_namespace to set namespace of included nodes
                PushRosNamespace('chatter_py_ns'),
                IncludeLaunchDescription(
                    PythonLaunchDescriptionSource(
                        os.path.join(
                            get_package_share_directory('demo_nodes_cpp'),
                            'launch/topics/talker_listener_launch.py'))
                ),
            ]
        )
    
        # include a xml launch file in the chatter_xml_ns namespace
        launch_xml_include_with_namespace = GroupAction(
            actions=[
                # push_ros_namespace to set namespace of included nodes
                PushRosNamespace('chatter_xml_ns'),
                IncludeLaunchDescription(
                    XMLLaunchDescriptionSource(
                        os.path.join(
                            get_package_share_directory('demo_nodes_cpp'),
                            'launch/topics/talker_listener_launch.xml'))
                ),
            ]
        )
    
        # include a yaml launch file in the chatter_yaml_ns namespace
        launch_yaml_include_with_namespace = GroupAction(
            actions=[
                # push_ros_namespace to set namespace of included nodes
                PushRosNamespace('chatter_yaml_ns'),
                IncludeLaunchDescription(
                    YAMLLaunchDescriptionSource(
                        os.path.join(
                            get_package_share_directory('demo_nodes_cpp'),
                            'launch/topics/talker_listener_launch.yaml'))
                ),
            ]
        )
    
        # start a turtlesim_node in the turtlesim1 namespace
        turtlesim_node = Node(
            package='turtlesim',
            namespace='turtlesim1',
            executable='turtlesim_node',
            name='sim'
        )
    
        # start another turtlesim_node in the turtlesim2 namespace
        # and use args to set parameters
        turtlesim_node_with_parameters = Node(
            package='turtlesim',
            namespace='turtlesim2',
            executable='turtlesim_node',
            name='sim',
            parameters=[{
                "background_r": LaunchConfiguration('background_r'),
                "background_g": LaunchConfiguration('background_g'),
                "background_b": LaunchConfiguration('background_b'),
            }]
        )
    
        # perform remap so both turtles listen to the same command topic
        forward_turtlesim_commands_to_second_turtlesim_node = Node(
            package='turtlesim',
            executable='mimic',
            name='mimic',
            remappings=[
                ('/input/pose', '/turtlesim1/turtle1/pose'),
                ('/output/cmd_vel', '/turtlesim2/turtle1/cmd_vel'),
            ]
        )
    
        return LaunchDescription([
            background_r_launch_arg,
            background_g_launch_arg,
            background_b_launch_arg,
            chatter_py_ns_launch_arg,
            chatter_xml_ns_launch_arg,
            chatter_yaml_ns_launch_arg,
            launch_include,
            launch_py_include_with_namespace,
            launch_xml_include_with_namespace,
            launch_yaml_include_with_namespace,
            turtlesim_node,
            turtlesim_node_with_parameters,
            forward_turtlesim_commands_to_second_turtlesim_node,
        ])
    
    • 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
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138

    1.2 XML 版本

    
    
    <launch>
    
        
        <arg name="background_r" default="0" />
        <arg name="background_g" default="255" />
        <arg name="background_b" default="0" />
        <arg name="chatter_py_ns" default="chatter/py/ns" />
        <arg name="chatter_xml_ns" default="chatter/xml/ns" />
        <arg name="chatter_yaml_ns" default="chatter/yaml/ns" />
    
        
        <include file="$(find-pkg-share demo_nodes_cpp)/launch/topics/talker_listener_launch.py" />
        
        <group>
            
            <push_ros_namespace namespace="$(var chatter_py_ns)" />
            <include file="$(find-pkg-share demo_nodes_cpp)/launch/topics/talker_listener_launch.py" />
        group>
        
        <group>
            
            <push_ros_namespace namespace="$(var chatter_xml_ns)" />
            <include file="$(find-pkg-share demo_nodes_cpp)/launch/topics/talker_listener_launch.xml" />
        group>
        
        <group>
            
            <push_ros_namespace namespace="$(var chatter_yaml_ns)" />
            <include file="$(find-pkg-share demo_nodes_cpp)/launch/topics/talker_listener_launch.yaml" />
        group>
    
        
        <node pkg="turtlesim" exec="turtlesim_node" name="sim" namespace="turtlesim1" />
        
        <node pkg="turtlesim" exec="turtlesim_node" name="sim" namespace="turtlesim2">
            <param name="background_r" value="$(var background_r)" />
            <param name="background_g" value="$(var background_g)" />
            <param name="background_b" value="$(var background_b)" />
        node>
        
        <node pkg="turtlesim" exec="mimic" name="mimic">
            <remap from="/input/pose" to="/turtlesim1/turtle1/pose" />
            <remap from="/output/cmd_vel" to="/turtlesim2/turtle1/cmd_vel" />
        node>
    launch>
    
    • 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
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    1.3 YAML 版本

    # example_launch.yaml
    
    launch:
    
    # args that can be set from the command line or a default will be used
    - arg:
        name: "background_r"
        default: "0"
    - arg:
        name: "background_g"
        default: "255"
    - arg:
        name: "background_b"
        default: "0"
    - arg:
        name: "chatter_py_ns"
        default: "chatter/py/ns"
    - arg:
        name: "chatter_xml_ns"
        default: "chatter/xml/ns"
    - arg:
        name: "chatter_yaml_ns"
        default: "chatter/yaml/ns"
    
    
    # include another launch file
    - include:
        file: "$(find-pkg-share demo_nodes_cpp)/launch/topics/talker_listener_launch.py"
    
    # include a Python launch file in the chatter_py_ns namespace
    - group:
        - push_ros_namespace:
            namespace: "$(var chatter_py_ns)"
        - include:
            file: "$(find-pkg-share demo_nodes_cpp)/launch/topics/talker_listener_launch.py"
    
    # include a xml launch file in the chatter_xml_ns namespace
    - group:
        - push_ros_namespace:
            namespace: "$(var chatter_xml_ns)"
        - include:
            file: "$(find-pkg-share demo_nodes_cpp)/launch/topics/talker_listener_launch.xml"
    
    # include a yaml launch file in the chatter_yaml_ns namespace
    - group:
        - push_ros_namespace:
            namespace: "$(var chatter_yaml_ns)"
        - include:
            file: "$(find-pkg-share demo_nodes_cpp)/launch/topics/talker_listener_launch.yaml"
    
    # start a turtlesim_node in the turtlesim1 namespace
    - node:
        pkg: "turtlesim"
        exec: "turtlesim_node"
        name: "sim"
        namespace: "turtlesim1"
    
    # start another turtlesim_node in the turtlesim2 namespace and use args to set parameters
    - node:
        pkg: "turtlesim"
        exec: "turtlesim_node"
        name: "sim"
        namespace: "turtlesim2"
        param:
        -
          name: "background_r"
          value: "$(var background_r)"
        -
          name: "background_g"
          value: "$(var background_g)"
        -
          name: "background_b"
          value: "$(var background_b)"
    
    # perform remap so both turtles listen to the same command topic
    - node:
        pkg: "turtlesim"
        exec: "mimic"
        name: "mimic"
        remap:
        -
            from: "/input/pose"
            to: "/turtlesim1/turtle1/pose"
        -
            from: "/output/cmd_vel"
            to: "/turtlesim2/turtle1/cmd_vel"
    
    • 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
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    二、从命令行使用 Launch 文件

    1. Launching

    上述任何启动文件都可以通过 ros2 launch 运行。要在本地试用它们,可以创建一个新软件包,然后使用

    ros2 launch <package_name> <launch_file_name>
    
    • 1

    或通过指定 launch 文件的路径直接运行该文件

    ros2 launch <path_to_launch_file>
    
    • 1

    2. 设置参数

    要设置传递给启动文件的参数,应使用 key:=value 语法。例如,可以用以下方式设置 background_r 的值:

    ros2 launch <package_name> <launch_file_name> background_r:=255
    
    • 1
    ros2 launch <path_to_launch_file> background_r:=255
    
    • 1

    3. 控制海龟

    要测试重映射是否有效,可以在另一个终端运行以下命令来控制海龟:

    ros2 run turtlesim turtle_teleop_key --ros-args --remap __ns:=/turtlesim1
    
    • 1

    三、Python、XML 或 YAML: 我应该使用哪种语言?

    ROS 1 中的启动文件是用 XML 编写的,因此对于来自 ROS 1 的用户来说,XML 可能是最熟悉的。

    对于大多数应用程序来说,选择哪种 ROS 2 启动格式取决于开发人员的偏好。不过,如果你的启动文件需要有 XML 或 YAML 无法实现的灵活性,你可以使用 Python 来编写启动文件。由于以下两个原因,使用 Python 编写 ROS 2 启动文件更为灵活:

    • Python 是一种脚本语言,因此您可以在启动文件中使用该语言及其库。

    • ros2/launch(一般启动功能)和 ros2/launch_ros(ROS 2 特定启动功能)都是用 Python 编写的,因此你可以访问 XML 和 YAML 可能无法提供的较低级别的启动功能。

    尽管如此,用 Python 编写的启动文件可能比 XML 或 YAML 编写的文件更复杂、更冗长。

    四、为什么要使用 ROS 2 Launch

    ROS 2 系统通常由运行在多个不同进程(甚至不同机器)上的多个节点组成。虽然每个节点都可以单独运行,但很快就会变得非常麻烦。

    ROS 2 中的启动系统旨在通过一条命令自动运行多个节点。它可以帮助用户描述系统配置,然后按描述执行。系统配置包括运行哪些程序、在哪里运行、传递哪些参数,以及 ROS 特有的约定,通过为每个组件提供不同的配置,可以方便地在整个系统中重复使用组件。它还负责监控已启动进程的状态,并对这些进程的状态变化做出报告和/或反应。

    上述所有内容都在一个启动文件中指定,该文件可以用 Python、XML 或 YAML 编写。使用 ros2 launch 命令运行该启动文件后,所有指定的节点都将运行。

    设计文档详细介绍了 ROS 2 启动系统的设计目标(目前尚未提供所有功能)。


  • 相关阅读:
    2022-08-04 clickhouse的join子句
    net core 反射获取泛型-泛型方法method<T>(T t1)
    毕业搬砖后,依然躲不过考试,分享30道过华为机考的LeetCode高效刷题经验
    Android--碎片
    Go语学习笔记 - gorm使用 - 事务操作 Web框架Gin(十一)
    森林消防泵柱塞泵工作原理深度解析——恒峰智慧科技
    Hadoop的第二个核心组件:MapReduce框架第二节
    【Objective -- C】—— block
    学习axure都要经历哪个阶段,如何快速入门
    Zigbee物联网应用
  • 原文地址:https://blog.csdn.net/weixin_46300916/article/details/134188455