iT邦幫忙

2023 iThome 鐵人賽

DAY 14
0
自我挑戰組

一個營建系學生的韌體之路-自走車篇系列 第 14

【AMR_Day14】ROS2 基本功-Launch

  • 分享至 

  • xImage
  •  

前言

在開始今天的內容前,猶豫了很久要先講『怎麼用c++寫一個簡單的訂閱/發布者?』或是『Launch是什麼?要怎麼使用?』,同時反思著自己的學習歷程,自己曾走過得那些歪路,是不是可以被別人的學習心得導正?讓有興趣的人可以避免掉這些多餘的學習成本...應該有幫助?

最後決定先來把Launch介紹完~

Launch

ros2 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 文件

除了指令的不同外,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,
    ])

看起來是不是落落長、要頭昏眼花了呢~~我來整理一下主要架構。
上面包含了:

  1. import必要函式庫
  2. 參數預設值的設定
  3. include別的launch文件
  4. 啟動節點並添加參數或remapping

簡化之後的必要架構其實滿簡單的

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

我自己也有些使用經驗~明日待續!


上一篇
【AMR_Day13】ROS2 基本功- Service & Action & Qos
下一篇
【AMR_Day15】 關於Launch的歪路
系列文
一個營建系學生的韌體之路-自走車篇30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言