Bootstrap

ros2笔记-4.6使用launch启动脚本

之前4.3节例子中,需要分别启动turtle_control、patrol_client、turtlesim_node三个节点,每个节点都需要单独的终端和命令。比较麻烦,ros2 提供了简化启动过程的方法。

4.6.1 使用launch启动多个节点

ros2 支持使用python、xml、YAML 三种格式编写launch脚本。小鱼老师推荐python.

在src/demo_cpp_service下新建文件夹launch,新建文件demo.launch.py,代码如下

import launch
import launch_ros

def generate_launch_description():
    action_node_turtle_control = launch_ros.actions.Node(
        package='demo_cpp_service',
        executable="turtle_control",
        output='screen',
    )

    action_node_patrol_client = launch_ros.actions.Node(
        package='demo_cpp_service',
        executable="patrol_client",
        output='log',
    )
    action_node_turtlesim_node = launch_ros.actions.Node(
        package='turtlesim',
        executable='turtlesim_node',
        output='both',
    )

     # 合成启动描述并返回
    return launch.LaunchDescription([
        action_node_turtlesim_node,
        action_node_patrol_client,
        action_node_turtle_control,
    ])

导入依赖库launch、launch_ros。函数generate_launch_description中创建3个launch_ros.actions.Node对象,里面指定package功能包、executable可执行文件、output 指定日志输出位置。最后将三个节点合成数组,调用launch.LaunchDescription启动描述并返回。

修改 编译文件CMakeLists.txt增加install,用于吧launch目录复制到install对于功能包share目录下

cmake_minimum_required(VERSION 3.8)
project(demo_cpp_service)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(chapt4_interfaces REQUIRED)
find_package(rclcpp REQUIRED)
find_package(geometry_msgs REQUIRED)
find_package(turtlesim REQUIRED)

add_executable(turtle_control  src/turtle_control.cpp)
ament_target_dependencies(turtle_control rclcpp geometry_msgs turtlesim chapt4_interfaces)
add_executable(patrol_client  src/patrol_client.cpp)
ament_target_dependencies(patrol_client rclcpp  chapt4_interfaces)

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # comment the line when a copyright and license is added to all source files
  set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # comment the line when this package is in a git repo and when
  # a copyright and license is added to all source files
  set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

install(TARGETS
  turtle_control patrol_client
  DESTINATION lib/${PROJECT_NAME}
)

install(DIRECTORY launch
  DESTINATION share/${PROJECT_NAME}
)


ament_package()

 对应的demo_python_service 也复制一下launch文件夹。修改setup.py

from setuptools import find_packages, setup
from glob import glob
package_name = 'demo_python_service'

setup(
    name=package_name,
    version='0.0.0',
    packages=find_packages(exclude=['test']),
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        ('share/' + package_name+"/resource", ['resource/default.jpg','resource/2.jpg']),
        ('share/' + package_name+'/launch', glob('launch/*.launch.py')),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    description='TODO: Package description',
    license='Apache-2.0',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'learn_face_detect=demo_python_service.learn_face_detect:main',
            'face_detect_node=demo_python_service.face_detect_node:main',
            'face_detect_client_node=demo_python_service.face_detect_client_node:main',
        ],
    },
)

重新构建,启动launch脚本

ros2 launch demo_cpp_service demo.launch.py

效果如下,ctrl+c 中断

同样python 版本执行:ros2 launch demo_python_service demo.launch.py 启动

4.6.2 使用launch 传递参数

先声明参数,再给具体节点使用参数

import launch
import launch_ros

def generate_launch_description():
    #1声明一个launch参数
    action_declare_arg_bgcolor_g = launch.actions.DeclareLaunchArgument('launch_arg_bg',default_value="150")
    action_node_turtle_control = launch_ros.actions.Node(
        package='demo_cpp_service',
        executable="turtle_control",
        output='screen',
    )

    action_node_patrol_client = launch_ros.actions.Node(
        package='demo_cpp_service',
        executable="patrol_client",
        output='log',
    )
    #2 把launch的参数手动传递给某个节点
    action_node_turtlesim_node = launch_ros.actions.Node(
        package='turtlesim',
        executable='turtlesim_node',
        parameters=[{'background_g': launch.substitutions.LaunchConfiguration('launch_arg_bg',default="150")}],
        output='both',
    )



    return launch.LaunchDescription([
        action_declare_arg_bgcolor_g,
        action_node_turtlesim_node,
        action_node_patrol_client,
        action_node_turtle_control,
    ])

书上例子是最大速度,视频例子是修改背景颜色,就是使用launch的参数替换节点的参数。

重新构建后运行:ros2 launch demo_python_service demo.launch.py launch_arg_bg:=255

效果:

4.6.3  launch 使用进阶

Launch三大组件:动作、条件和替换
动作:除了是一个节点外,还可以是一句打印,一段终端指令,甚至是另外一个launch文件
替换:使用launch的参数替换节点的参数值
条件:利用条件可以决定哪些动作启动,那些不启。相当于if

src/demo_cpp_service/launch 文件夹下新建文件:actions.launch.py

代码如下

import launch
import launch.launch_description_sources
import launch_ros
from ament_index_python.packages import get_package_share_directory

def generate_launch_description():
    #动作1 -启动其他launch
    multisim_launch_path =  [get_package_share_directory("turtlesim"),"/launch/","multisim.launch.py"]
    action_inlude_launch = launch.actions.IncludeLaunchDescription(
        launch.launch_description_sources.PythonLaunchDescriptionSource(
            multisim_launch_path
        )
    )
    # 动作2 -打印数据
    action_log_info = launch.actions.LogInfo(msg=str(multisim_launch_path))

    #动作3 -执行进程 rqt
    action_rqt = launch.actions.ExecuteProcess(
        cmd=['rqt']
    )

    #动作4 -组织动作组,把多个动作放到一组
    action_group = launch.actions.GroupAction({
        #动作5 定时器
        launch.actions.TimerAction(period=2.0,actions=[action_inlude_launch]),
        launch.actions.TimerAction(period=4.0,actions=[action_rqt])
    })

      # 合成启动描述并返回
    return launch.LaunchDescription([
        action_log_info,
        action_group
    ])

 注意里面的语法不要写错了,不然运行报错

bohu@bohu-TM1701:~/chapt4/chapt4_ws/src/demo_cpp_service/launch$ ros2 launch actions.launch.py 
[INFO] [launch]: All log files can be found below /home/bohu/.ros/log/2025-01-10-13-02-59-751143-bohu-TM1701-392500
[INFO] [launch]: Default logging verbosity is set to INFO
[ERROR] [launch]: Caught exception in launch (see debug for traceback): Caught multiple exceptions when trying to load file of format [py]:
 - TypeError: 'module' object is not callable
 - InvalidFrontendLaunchFileError: The launch file may have a syntax error, or its format is unknown

运行日志如下

bohu@bohu-TM1701:~/chapt4/chapt4_ws/src/demo_cpp_service/launch$ ros2 launch actions.launch.py
[INFO] [launch]: All log files can be found below /home/bohu/.ros/log/2025-01-10-13-10-43-357086-bohu-TM1701-393474
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [launch.user]: ['/opt/ros/humble/share/turtlesim', '/launch/', 'multisim.launch.py']
[INFO] [turtlesim_node-1]: process started with pid [393493]
[INFO] [turtlesim_node-2]: process started with pid [393495]
[turtlesim_node-1] qt.qpa.plugin: Could not find the Qt platform plugin "wayland" in ""
[turtlesim_node-2] qt.qpa.plugin: Could not find the Qt platform plugin "wayland" in ""
[turtlesim_node-2] [INFO] [1736485845.707954083] [turtlesim2.turtlesim]: Starting turtlesim with node name /turtlesim2/turtlesim
[turtlesim_node-1] [INFO] [1736485845.708127883] [turtlesim1.turtlesim]: Starting turtlesim with node name /turtlesim1/turtlesim
[turtlesim_node-2] [INFO] [1736485845.716420053] [turtlesim2.turtlesim]: Spawning turtle [turtle1] at x=[5.544445], y=[5.544445], theta=[0.000000]
[turtlesim_node-1] [INFO] [1736485845.716598360] [turtlesim1.turtlesim]: Spawning turtle [turtle1] at x=[5.544445], y=[5.544445], theta=[0.000000]
[INFO] [rqt-3]: process started with pid [393550]
[rqt-3] Could not find the Qt platform plugin "wayland" in ""

使用参数控制

import launch
import launch.launch_description_sources
import launch_ros
from ament_index_python.packages import get_package_share_directory

def generate_launch_description():
        #1声明一个launch参数
    action_declare_startup_rqt = launch.actions.DeclareLaunchArgument('startup_rqt',default_value="False")
    startup_rqt = launch.substitutions.LaunchConfiguration('startup_rqt',default="False")

    #动作1 -启动其他launch
    multisim_launch_path =  [get_package_share_directory("turtlesim"),"/launch/","multisim.launch.py"]
    action_inlude_launch = launch.actions.IncludeLaunchDescription(
        launch.launch_description_sources.PythonLaunchDescriptionSource(
            multisim_launch_path
        )
    )
    # 动作2 -打印数据
    action_log_info = launch.actions.LogInfo(msg=str(multisim_launch_path))

    #动作3 -执行进程 rqt
    action_rqt = launch.actions.ExecuteProcess(
        condition = launch.conditions.IfCondition(startup_rqt),
        cmd=['rqt']
    )

    #动作4 -组织动作组,把多个动作放到一组
    action_group = launch.actions.GroupAction({
        #动作5 定时器
        launch.actions.TimerAction(period=2.0,actions=[action_inlude_launch]),
        launch.actions.TimerAction(period=4.0,actions=[action_rqt])
    })

      # 合成启动描述并返回
    return launch.LaunchDescription([
        action_log_info,
        action_group
    ])

可以通过参数控制rqt是否启动。

ros2 launch actions.launch.py startup_rqt:=true

ros2 launch actions.launch.py startup_rqt:=False

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;