在開始今天的內容前,猶豫了很久要先講『怎麼用c++寫一個簡單的訂閱/發布者?』或是『Launch是什麼?要怎麼使用?』,同時反思著自己的學習歷程,自己曾走過得那些歪路,是不是可以被別人的學習心得導正?讓有興趣的人可以避免掉這些多餘的學習成本...應該有幫助?
最後決定先來把Launch介紹完~
在ROS1就有著Launch 的啟動機制,也就是能以XML格式的文件來一次啟動多個節點或設定參數。
ROS1的指令是長這樣的:
roslaunch <package_name> <file.launch>
聰明的你應該可以猜到ROS2版本長怎麼樣,登愣~沒錯
ros2 launch <package_name> <file.launch> #啟動package中的launch文件
ros2 launch <path_to_launch_file> #啟動指定路徑的launch文件
ros2 launch <package_name> <launch_file_name> key:=value #啟動package中的launch文件,並帶入參數
ros2 launch <path_to_launch_file> key:=value #啟動指定路徑的launch文件,並帶入參數
之後也會提到參數的部份~
經過幾天的ROS2介紹,有先接觸過ROS1的人應該有發現兩者指令的改動規則
rostopic echo ...
rosnode info ...
rosbag record ...
roslaunch ...
變成
ros2 topic echo ...
ros2 node info ...
ros2 bag record ...
ros2 launch ...
除了指令的不同外,Launch文件所支援的語法:
ROS1只能以XML格式編寫,ROS2則可以依照個人使用習慣選擇Python、XML、或YAML。
ROS1的使用者可以方便的在ROS2延用XML格式,但要注意還是有一些不太一樣的地方~
不過聽說Python比較靈活一點,也是ROS2官方默認的語法。
官方提供的完整版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,
    ])
看起來是不是落落長、要頭昏眼花了呢~~我來整理一下主要架構。
上面包含了:
簡化之後的必要架構其實滿簡單的
# example_launch.py
import os
from ament_index_python import get_package_share_directory
# 1. 依照需求import需要的函式庫,不一定每個都有用到
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():
    # 2.參數預設值的設定
    background_r_launch_arg = DeclareLaunchArgument(
        "background_r", default_value=TextSubstitution(text="0")
    )
    # 3.包含其他launch文件,或使用namespace
    launch_include = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(
                get_package_share_directory('package_name'),
                'launch/launch_name.py'))
    )
    launch_include_with_namespace = GroupAction(
        actions=[
            PushRosNamespace('namespace_name'),
            IncludeLaunchDescription(
                PythonLaunchDescriptionSource(
                    os.path.join(
                        get_package_share_directory('package_name'),
                        'launch/launch_name.py'))
            ),
        ]
    )
    # 4.啟動節點並添加參數或remapping(以turtlesim為例)
    turtlesim_node = Node(
        package='turtlesim',
        namespace='turtlesim1',
        executable='turtlesim_node',
        name='sim'
    )
    
    turtlesim_node_with_parameters = Node(
        package='turtlesim',
        namespace='turtlesim2',
        executable='turtlesim_node',
        name='sim',
        parameters=[{
            "background_r": LaunchConfiguration('background_r'),
            # ...
            # 這裡的參數在 2. 有設定,我這邊省篇幅就拿掉了
            # 一個node可以用的參數可以查詢檢查 未來會講到
        }]
    )
    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後才能被啟動
    return LaunchDescription([
        background_r_launch_arg,
        launch_include,
        launch_include_with_namespace,
        turtlesim_node,
        turtlesim_node_with_parameters,
        forward_turtlesim_commands_to_second_turtlesim_node,
    ])
我自己也有些使用經驗~明日待續!