ROS2机器人编程简述humble-第二章-Publishing and Subscribing .3.2
ros2 run一次只能开启一个node,如果一次开启一组相关node,需要使用ros2 launch。
支持Python, XML, 和 YAML。
推荐Python。
zhangrelay@LAPTOP-5REQ7K1L:~$ ros2 run -h
usage: ros2 run [-h] [--prefix PREFIX] package_name executable_name ...
Run a package specific executable
positional arguments:
package_name Name of the ROS package
executable_name Name of the executable
argv Pass arbitrary arguments to the executable
options:
-h, --help show this help message and exit
--prefix PREFIX Prefix command, which should go before the executable. Command
must be wrapped in quotes if it contains spaces (e.g. --prefix
'gdb -ex run --args').
zhangrelay@LAPTOP-5REQ7K1L:~$ ros2 launch -h
usage: ros2 launch [-h] [-n] [-d] [-p | -s] [-a] [--launch-prefix LAUNCH_PREFIX]
[--launch-prefix-filter LAUNCH_PREFIX_FILTER]
package_name [launch_file_name] [launch_arguments ...]
Run a launch file
positional arguments:
package_name Name of the ROS package which contains the launch file
launch_file_name Name of the launch file
launch_arguments Arguments to the launch file; '<name>:=<value>' (for
duplicates, last one wins)
options:
-h, --help show this help message and exit
-n, --noninteractive Run the launch system non-interactively, with no terminal
associated
-d, --debug Put the launch system in debug mode, provides more verbose
output.
-p, --print, --print-description
Print the launch description to the console without
launching it.
-s, --show-args, --show-arguments
Show arguments that may be given to the launch file.
-a, --show-all-subprocesses-output
Show all launched subprocesses' output by overriding their
output configuration using the
OVERRIDE_LAUNCH_PROCESS_OUTPUT envvar.
--launch-prefix LAUNCH_PREFIX
Prefix command, which should go before all executables.
Command must be wrapped in quotes if it contains spaces
(e.g. --launch-prefix 'xterm -e gdb -ex run --args').
--launch-prefix-filter LAUNCH_PREFIX_FILTER
Regex pattern for filtering which executables the --launch-
prefix is applied to by matching the executable name.
zhangrelay@LAPTOP-5REQ7K1L:~$
启动程序launch文件是用Python编写的,其功能是声明使用哪些选项或参数执行哪些程序。一个启动器可以包含另一个启动器,允许重用现有的启动器。之所以需要launch,是因为一个机器人应用程序有许多节点,它们都应该同时启动。逐个启动并调整每个节点的特定参数,使节点进行协作可能会很乏味。
软件包的启动器位于软件包的启动launch目录中,其名称通常以launch.py结尾。正如ros2运行时使用软件包中可用的程序一样,ros2启动时也使用可用的启动器。从实现的角度来看,启动器是一个python程序,它包含一个返回LaunchDescription对象的generatelaunchdescription()函数。
LaunchDescription对象包含操作,其中突出显示:
•node操作:运行程序。
•IncludeLaunchDescription操作:包括另一个启动器。
•DeclareLaunchArgument操作:声明启动器参数。
•SetEnvironmentVariable操作:设置环境变量。
书中案例如下:
v1
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
pub_cmd = Node(
package='br2_basics',
executable='publisher',
output='screen'
)
sub_cmd = Node(
package='br2_basics',
executable='subscriber_class',
output='screen'
)
ld = LaunchDescription()
ld.add_action(pub_cmd)
ld.add_action(sub_cmd)
return ld
v2
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='br2_basics',
executable='publisher',
output='screen'
),
Node(
package='br2_basics',
executable='subscriber_class',
output='screen'
)
])
CMakeLists
install(DIRECTORY launch DESTINATION share/${PROJECT_NAME})
效果如:
ros2 launch br2_basics pub_sub_v2_launch.py
[INFO] [launch]: All log files can be found below /home/zhangrelay/.ros/log/2023-01-20-09-06-49-444970-LAPTOP-5REQ7K1L-7896
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [publisher-1]: process started with pid [7897]
[INFO] [subscriber_class-2]: process started with pid [7899]
[subscriber_class-2] [INFO] [1674176809.694879700] [subscriber_node]: Hello 1
[subscriber_class-2] [INFO] [1674176810.195718900] [subscriber_node]: Hello 2
[subscriber_class-2] [INFO] [1674176810.695717000] [subscriber_node]: Hello 3
[subscriber_class-2] [INFO] [1674176811.195712100] [subscriber_node]: Hello 4
[subscriber_class-2] [INFO] [1674176811.695709800] [subscriber_node]: Hello 5
[subscriber_class-2] [INFO] [1674176812.195715200] [subscriber_node]: Hello 6
[subscriber_class-2] [INFO] [1674176812.695273100] [subscriber_node]: Hello 7
[subscriber_class-2] [INFO] [1674176813.195665000] [subscriber_node]: Hello 8
[subscriber_class-2] [INFO] [1674176813.695714300] [subscriber_node]: Hello 9
[subscriber_class-2] [INFO] [1674176814.195719200] [subscriber_node]: Hello 10
[subscriber_class-2] [INFO] [1674176814.695718400] [subscriber_node]: Hello 11
[subscriber_class-2] [INFO] [1674176815.195444600] [subscriber_node]: Hello 12
[subscriber_class-2] [INFO] [1674176815.695682400] [subscriber_node]: Hello 13
[subscriber_class-2] [INFO] [1674176816.195665100] [subscriber_node]: Hello 14
[subscriber_class-2] [INFO] [1674176816.695713200] [subscriber_node]: Hello 15
[subscriber_class-2] [INFO] [1674176817.195601200] [subscriber_node]: Hello 16
[subscriber_class-2] [INFO] [1674176817.695310700] [subscriber_node]: Hello 17
[subscriber_class-2] [INFO] [1674176818.195711000] [subscriber_node]: Hello 18
[subscriber_class-2] [INFO] [1674176818.695683800] [subscriber_node]: Hello 19
[subscriber_class-2] [INFO] [1674176819.195543700] [subscriber_node]: Hello 20
[subscriber_class-2] [INFO] [1674176819.695711400] [subscriber_node]: Hello 21
[subscriber_class-2] [INFO] [1674176820.195726000] [subscriber_node]: Hello 22
[subscriber_class-2] [INFO] [1674176820.695710100] [subscriber_node]: Hello 23
[subscriber_class-2] [INFO] [1674176821.195487200] [subscriber_node]: Hello 24
[subscriber_class-2] [INFO] [1674176821.695709500] [subscriber_node]: Hello 25
[subscriber_class-2] [INFO] [1674176822.195718500] [subscriber_node]: Hello 26
[subscriber_class-2] [INFO] [1674176822.695726100] [subscriber_node]: Hello 27
[subscriber_class-2] [INFO] [1674176823.195704600] [subscriber_node]: Hello 28
[subscriber_class-2] [INFO] [1674176823.695726800] [subscriber_node]: Hello 29
[subscriber_class-2] [INFO] [1674176824.195718300] [subscriber_node]: Hello 30
[subscriber_class-2] [INFO] [1674176824.695579500] [subscriber_node]: Hello 31
[subscriber_class-2] [INFO] [1674176825.195705000] [subscriber_node]: Hello 32
[subscriber_class-2] [INFO] [1674176825.695724000] [subscriber_node]: Hello 33
[subscriber_class-2] [INFO] [1674176826.195724200] [subscriber_node]: Hello 34
[subscriber_class-2] [INFO] [1674176826.695665700] [subscriber_node]: Hello 35
更多内容参考官网:
launch
ROS 2启动文件允许同时启动和配置包含ROS 2节点的多个可执行文件。
1 创建启动文件
了解如何创建一个启动文件,该文件将同时启动节点及其配置。
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='turtlesim',
namespace='turtlesim1',
executable='turtlesim_node',
name='sim'
),
Node(
package='turtlesim',
namespace='turtlesim2',
executable='turtlesim_node',
name='sim'
),
Node(
package='turtlesim',
executable='mimic',
name='mimic',
remappings=[
('/input/pose', '/turtlesim1/turtle1/pose'),
('/output/cmd_vel', '/turtlesim2/turtle1/cmd_vel'),
]
)
])
package
<exec_depend>ros2launch</exec_depend>
2 启动和监视多个节点
获取有关启动文件工作方式的更高级概述。
import launch
import launch_ros.actions
def generate_launch_description():
return launch.LaunchDescription([
launch_ros.actions.Node(
package='demo_nodes_cpp',
executable='talker',
name='talker'),
])
3 使用替换
在描述可重用的启动文件时,使用替换来提供更大的灵活性。
from launch_ros.actions import Node
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, ExecuteProcess, TimerAction
from launch.conditions import IfCondition
from launch.substitutions import LaunchConfiguration, PythonExpression
def generate_launch_description():
turtlesim_ns = LaunchConfiguration('turtlesim_ns')
use_provided_red = LaunchConfiguration('use_provided_red')
new_background_r = LaunchConfiguration('new_background_r')
turtlesim_ns_launch_arg = DeclareLaunchArgument(
'turtlesim_ns',
default_value='turtlesim1'
)
use_provided_red_launch_arg = DeclareLaunchArgument(
'use_provided_red',
default_value='False'
)
new_background_r_launch_arg = DeclareLaunchArgument(
'new_background_r',
default_value='200'
)
turtlesim_node = Node(
package='turtlesim',
namespace=turtlesim_ns,
executable='turtlesim_node',
name='sim'
)
spawn_turtle = ExecuteProcess(
cmd=[[
'ros2 service call ',
turtlesim_ns,
'/spawn ',
'turtlesim/srv/Spawn ',
'"{x: 2, y: 2, theta: 0.2}"'
]],
shell=True
)
change_background_r = ExecuteProcess(
cmd=[[
'ros2 param set ',
turtlesim_ns,
'/sim background_r ',
'120'
]],
shell=True
)
change_background_r_conditioned = ExecuteProcess(
condition=IfCondition(
PythonExpression([
new_background_r,
' == 200',
' and ',
use_provided_red
])
),
cmd=[[
'ros2 param set ',
turtlesim_ns,
'/sim background_r ',
new_background_r
]],
shell=True
)
return LaunchDescription([
turtlesim_ns_launch_arg,
use_provided_red_launch_arg,
new_background_r_launch_arg,
turtlesim_node,
spawn_turtle,
change_background_r,
TimerAction(
period=2.0,
actions=[change_background_r_conditioned],
)
])
4 使用事件处理程序
使用事件处理程序监视进程的状态或定义一组复杂的规则,这些规则可用于动态修改启动文件。
from launch_ros.actions import Node
from launch import LaunchDescription
from launch.actions import (DeclareLaunchArgument, EmitEvent, ExecuteProcess,
LogInfo, RegisterEventHandler, TimerAction)
from launch.conditions import IfCondition
from launch.event_handlers import (OnExecutionComplete, OnProcessExit,
OnProcessIO, OnProcessStart, OnShutdown)
from launch.events import Shutdown
from launch.substitutions import (EnvironmentVariable, FindExecutable,
LaunchConfiguration, LocalSubstitution,
PythonExpression)
def generate_launch_description():
turtlesim_ns = LaunchConfiguration('turtlesim_ns')
use_provided_red = LaunchConfiguration('use_provided_red')
new_background_r = LaunchConfiguration('new_background_r')
turtlesim_ns_launch_arg = DeclareLaunchArgument(
'turtlesim_ns',
default_value='turtlesim1'
)
use_provided_red_launch_arg = DeclareLaunchArgument(
'use_provided_red',
default_value='False'
)
new_background_r_launch_arg = DeclareLaunchArgument(
'new_background_r',
default_value='200'
)
turtlesim_node = Node(
package='turtlesim',
namespace=turtlesim_ns,
executable='turtlesim_node',
name='sim'
)
spawn_turtle = ExecuteProcess(
cmd=[[
FindExecutable(name='ros2'),
' service call ',
turtlesim_ns,
'/spawn ',
'turtlesim/srv/Spawn ',
'"{x: 2, y: 2, theta: 0.2}"'
]],
shell=True
)
change_background_r = ExecuteProcess(
cmd=[[
FindExecutable(name='ros2'),
' param set ',
turtlesim_ns,
'/sim background_r ',
'120'
]],
shell=True
)
change_background_r_conditioned = ExecuteProcess(
condition=IfCondition(
PythonExpression([
new_background_r,
' == 200',
' and ',
use_provided_red
])
),
cmd=[[
FindExecutable(name='ros2'),
' param set ',
turtlesim_ns,
'/sim background_r ',
new_background_r
]],
shell=True
)
return LaunchDescription([
turtlesim_ns_launch_arg,
use_provided_red_launch_arg,
new_background_r_launch_arg,
turtlesim_node,
RegisterEventHandler(
OnProcessStart(
target_action=turtlesim_node,
on_start=[
LogInfo(msg='Turtlesim started, spawning turtle'),
spawn_turtle
]
)
),
RegisterEventHandler(
OnProcessIO(
target_action=spawn_turtle,
on_stdout=lambda event: LogInfo(
msg='Spawn request says "{}"'.format(
event.text.decode().strip())
)
)
),
RegisterEventHandler(
OnExecutionComplete(
target_action=spawn_turtle,
on_completion=[
LogInfo(msg='Spawn finished'),
change_background_r,
TimerAction(
period=2.0,
actions=[change_background_r_conditioned],
)
]
)
),
RegisterEventHandler(
OnProcessExit(
target_action=turtlesim_node,
on_exit=[
LogInfo(msg=(EnvironmentVariable(name='USER'),
' closed the turtlesim window')),
EmitEvent(event=Shutdown(
reason='Window closed'))
]
)
),
RegisterEventHandler(
OnShutdown(
on_shutdown=[LogInfo(
msg=['Launch was asked to shutdown: ',
LocalSubstitution('event.reason')]
)]
)
),
])
5 管理大型项目
为大型项目构建启动文件,以便在不同的情况下尽可能地重用它们。请参阅不同启动工具的用法示例,如参数、YAML文件、重映射、名称空间、默认参数和RViz配置。
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
def generate_launch_description():
turtlesim_world_1 = IncludeLaunchDescription(
PythonLaunchDescriptionSource([os.path.join(
get_package_share_directory('launch_tutorial'), 'launch'),
'/turtlesim_world_1.launch.py'])
)
turtlesim_world_2 = IncludeLaunchDescription(
PythonLaunchDescriptionSource([os.path.join(
get_package_share_directory('launch_tutorial'), 'launch'),
'/turtlesim_world_2.launch.py'])
)
broadcaster_listener_nodes = IncludeLaunchDescription(
PythonLaunchDescriptionSource([os.path.join(
get_package_share_directory('launch_tutorial'), 'launch'),
'/broadcaster_listener.launch.py']),
launch_arguments={'target_frame': 'carrot1'}.items(),
)
mimic_node = IncludeLaunchDescription(
PythonLaunchDescriptionSource([os.path.join(
get_package_share_directory('launch_tutorial'), 'launch'),
'/mimic.launch.py'])
)
fixed_frame_node = IncludeLaunchDescription(
PythonLaunchDescriptionSource([os.path.join(
get_package_share_directory('launch_tutorial'), 'launch'),
'/fixed_broadcaster.launch.py'])
)
rviz_node = IncludeLaunchDescription(
PythonLaunchDescriptionSource([os.path.join(
get_package_share_directory('launch_tutorial'), 'launch'),
'/turtlesim_rviz.launch.py'])
)
return LaunchDescription([
turtlesim_world_1,
turtlesim_world_2,
broadcaster_listener_nodes,
mimic_node,
fixed_frame_node,
rviz_node
])