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

系列文章目录


文章目录

  • 系列文章目录
  • 前言
  • 一、Launch 文件示例
    • 1.1 Python 版本
    • 1.2 XML 版本
    • 1.3 YAML 版本
  • 二、从命令行使用 Launch 文件
    • 1. Launching
    • 2. 设置参数
    • 3. 控制海龟
  • 三、Python、XML 或 YAML: 我应该使用哪种语言?
  • `如果觉得内容不错,请点赞、收藏、关注`


前言

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


一、Launch 文件示例

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

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

  • 包含另一个 launch 文件

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

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

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

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

1.1 Python 版本

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

  1. get_package_share_directory(package_name, print_warning=True)
    查找并返回给定 ROS 包的 share 文件夹路径
  2. LaunchDescription(LaunchDescriptionEntity)
    Launch 系统的描述。
    描述由一组实体表达,这些实体代表了系统架构师的意图。
    该描述可能还有参数,参数由 :py:class:launch.actions.DeclareLaunchArgument 操作在该启动描述中声明。
  3. DeclareLaunchArgument(Action)
    声明一个新的 launch 参数。
    启动参数存储在同名的 "启动配置 "中。
    请参阅 :py:class:launch.actions.SetLaunchConfiguration 和 :py:class:launch.substitutions.LaunchConfiguration
    在 :py:class:launch.LaunchDescription 中声明的任何启动参数将在包含该启动描述时作为参数显示,例如作为 :py:class:launch.actions.IncludeLaunchDescription 操作中的附加参数,或作为使用 ros2 launch ...启动时的命令行参数。
  4. GroupAction(Action)
    产生其他操作的操作。
    该操作用于嵌套其他操作,而无需包含单独的启动说明,同时还可选择具有条件(与所有其他操作一样)、范围和转发启动配置和环境变量,以及/或只为组及其产生的操作声明启动配置。
  5. IncludeLaunchDescription(Action)
    包含启动描述源并在访问时生成其实体的操作。
    可以通过 :py:class:launch.actions.DeclareLaunchArgument 动作向启动描述传递参数。
  6. PythonLaunchDescriptionSource(LaunchDescriptionSource)
    创建 PythonLaunchDescriptionSource。
    给定的文件路径应该是一个 .launch.py 样式的文件。该路径可能应该是绝对路径,因为当前工作目录将是运行启动文件的位置,这可能会根据情况而改变。路径可以由 Substitution 实例组成,这些实例会在调用 :py:meth:get_launch_description() 时展开。
  7. LaunchConfiguration(Substitution)
    可访问启动配置变量的替代变量。
  8. TextSubstitution(Substitution)
    可对单个字符串文本进行替换。
  9. PushROSNamespace(Action)
    推送 ros 命名空间的动作。
    在有作用域的 GroupAction 中使用时,它会自动弹出。没有其他方法可以弹出它。
  10. XMLLaunchDescriptionSource(FrontendLaunchDescriptionSource)
    封装 XML 启动文件,可在启动过程中加载。
  11. YAMLLaunchDescriptionSource(FrontendLaunchDescriptionSource)
    封装 YAML 启动文件,可在启动过程中加载。
  12. Node(ExecuteProcess)
    执行一个 ROS 节点的操作。
# 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 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.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"

二、从命令行使用 Launch 文件

1. Launching

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

ros2 launch <package_name> <launch_file_name>

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

ros2 launch <path_to_launch_file>

2. 设置参数

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

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

3. 控制海龟

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

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

三、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 编写的文件更复杂、更冗长。


如果觉得内容不错,请点赞、收藏、关注

你可能感兴趣的:(ROS2学习专栏,机器人,自动驾驶,ROS,ROS2,python,单片机,stm32)