目标:使用重置处理程序扩展机器人模拟,以便在按下 Webots 的重置按钮时重新启动节点。
教程级别:高级
时间:10分钟
内容
背景
先决条件
简单情况的重置处理程序(仅限控制器)
重置多个节点的处理程序(无需关闭)
重置需要节点关闭的处理程序
概括
背景
在本教程中,您将学习如何使用 Webots 在机器人模拟中实现重置处理程序。Webots 重置按钮将世界恢复到初始状态并重新启动控制器。它很方便,因为它可以快速重置模拟,但在 ROS 2 的上下文中,机器人控制器不会再次启动,从而导致模拟停止。重置处理程序允许您在按下 Webots 中的重置按钮时重新启动特定节点或执行其他操作。这对于需要重置模拟状态或重新启动特定组件而不完全重新启动整个 ROS 系统的情况非常有用。
先决条件
在继续本教程之前,请确保您已完成以下操作:
了解初学者教程中涵盖的 ROS 2 节点和主题。
了解 Webots 和 ROS 2 及其接口包。
熟悉设置机器人模拟(基础)。
简单情况的重置处理程序(仅限控制器)
在包的启动文件中,添加 respawn
参数。
import os # 导入os模块,用于处理操作系统相关的功能
import launch # 导入launch模块,用于创建Launch描述符
from launch_ros.actions import Node # 从launch_ros.actions模块导入Node类,用于创建ROS 2节点
from launch import LaunchDescription # 从launch模块导入LaunchDescription类,用于定义Launch描述符
from ament_index_python.packages import get_package_share_directory # 从ament_index_python.packages模块导入get_package_share_directory函数,用于获取包共享目录
from webots_ros2_driver.webots_launcher import WebotsLauncher # 从webots_ros2_driver.webots_launcher模块导入WebotsLauncher类,用于启动Webots仿真器
from webots_ros2_driver.webots_controller import WebotsController # 从webots_ros2_driver.webots_controller模块导入WebotsController类,用于控制Webots中的机器人
from launch.substitutions import PathJoinSubstitution # 从launch.substitutions模块导入PathJoinSubstitution类,用于路径拼接
def generate_launch_description():
package_dir = get_package_share_directory('my_package') # 获取my_package包的共享目录
robot_description_path = os.path.join(package_dir, 'resource', 'my_robot.urdf') # 生成机器人描述文件的路径
world = 'my_world.wbt' # 指定世界文件名称
robot_driver = WebotsController(
robot_name='my_robot', # 指定机器人名称为my_robot
parameters=[
{'robot_description': robot_description_path} # 设置机器人描述参数
],
# 每次重置仿真时,控制器会自动重启
respawn=True
)
# 启动Webots仿真器
webots = WebotsLauncher(
world=PathJoinSubstitution([package_dir, 'worlds', world]) # 拼接路径以获取世界文件的完整路径
)
return LaunchDescription([
webots, # 启动Webots仿真器
robot_driver # 启动机器人控制器
])
重置时,Webots 会杀死所有驱动程序节点。因此,要在重置后再次启动它们,您应该将驱动程序节点的 respawn
属性设置为 True
。它将确保驱动程序节点在重置后启动并运行。
重置多个节点的处理程序(无需关闭)
如果您有一些其他节点必须与驱动程序节点一起启动(例如 ros2_control
节点),那么您可以使用 OnProcessExit
事件处理程序:
import os # 导入os模块,用于处理操作系统相关的功能
import launch # 导入launch模块,用于创建Launch描述符
from launch_ros.actions import Node # 从launch_ros.actions模块导入Node类,用于创建ROS 2节点
from launch import LaunchDescription # 从launch模块导入LaunchDescription类,用于定义Launch描述符
from ament_index_python.packages import get_package_share_directory # 从ament_index_python.packages模块导入get_package_share_directory函数,用于获取包共享目录
from webots_ros2_driver.webots_launcher import WebotsLauncher # 从webots_ros2_driver.webots_launcher模块导入WebotsLauncher类,用于启动Webots仿真器
from webots_ros2_driver.webots_controller import WebotsController # 从webots_ros2_driver.webots_controller模块导入WebotsController类,用于控制Webots中的机器人
from launch.substitutions import PathJoinSubstitution # 从launch.substitutions模块导入PathJoinSubstitution类,用于路径拼接
def get_ros2_control_spawners(*args):
# 声明在仿真重置时必须重新启动的节点
ros_control_node = Node(
package='controller_manager', # 指定节点所属的包
executable='spawner', # 指定可执行文件
arguments=['diffdrive_controller'] # 传递给可执行文件的参数
)
return [
ros_control_node # 返回需要重新启动的节点列表
]
def generate_launch_description():
package_dir = get_package_share_directory('my_package') # 获取my_package包的共享目录
robot_description_path = os.path.join(package_dir, 'resource', 'my_robot.urdf') # 生成机器人描述文件的路径
world = 'my_world.wbt' # 指定世界文件名称
robot_driver = WebotsController(
robot_name='my_robot', # 指定机器人名称为my_robot
parameters=[
{'robot_description': robot_description_path} # 设置机器人描述参数
],
# 每次重置仿真时,控制器会自动重启
respawn=True
)
# 启动Webots仿真器
webots = WebotsLauncher(
world=PathJoinSubstitution([package_dir, 'worlds', world]) # 拼接路径以获取世界文件的完整路径
)
# 声明重置处理程序,当robot_driver退出时重新启动节点
reset_handler = launch.actions.RegisterEventHandler(
event_handler=launch.event_handlers.OnProcessExit(
target_action=robot_driver, # 目标动作是robot_driver
on_exit=get_ros2_control_spawners, # 当robot_driver进程退出时,调用get_ros2_control_spawners函数重新启动节点
)
)
return LaunchDescription([
webots, # 启动Webots仿真器
robot_driver, # 启动机器人控制器
reset_handler # 注册重置处理程序
] + get_ros2_control_spawners()) # 添加需要重新启动的节点
无法在 ros2_control
节点上使用 respawn
属性,因为生成器在启动时退出,而不是在重置模拟时退出。相反,我们应该在函数中声明一个节点列表(例如 get_ros2_control_spawners
)。当执行启动文件时,该列表的节点与其他节点一起启动。对于 reset_handler
,该函数还被声明为在 robot_driver
节点退出时启动的操作,这对应于在 Webots 界面中重置模拟的时刻。 robot_driver
节点的 respawn
属性仍设置为 True
,以便它与 ros2_control
节点一起重新启动。
重置处理程序需要关闭节点
使用当前的 ROS 2 启动 API,无法在启动文件中进行重置,其中节点需要在重新启动之前关闭(例如 Nav2
或 RViz
)。原因是目前 ROS 2 不允许从启动文件中关闭特定节点。有一个解决方案,但需要在按下重置按钮后手动重新启动节点。
Webots 需要在特定的启动文件中启动,无需其他节点。
def generate_launch_description():
# Starts Webots
webots = WebotsLauncher(world=PathJoinSubstitution([package_dir, 'worlds', world]))
return LaunchDescription([
webots
])
第二个启动文件必须从另一个进程启动。该启动文件包含所有其他节点,包括机器人控制器/插件、Navigation2 节点、RViz、状态发布者等。
def generate_launch_description():
# 创建Webots控制器节点
robot_driver = WebotsController(
robot_name='my_robot', # 机器人名称
parameters=[
{'robot_description': robot_description_path} # 机器人描述文件路径参数
]
)
# 创建ROS控制管理器节点
ros_control_node = Node(
package='controller_manager', # 包名称
executable='spawner', # 可执行文件名称
arguments=['diffdrive_controller'] # 参数:差动驱动控制器
)
# 包含导航2启动文件
nav2_node = IncludeLaunchDescription(
PythonLaunchDescriptionSource(os.path.join(
get_package_share_directory('nav2_bringup'), 'launch', 'bringup_launch.py')), # 导航2启动文件路径
launch_arguments=[
('map', nav2_map), # 地图文件参数
('params_file', nav2_params), # 参数文件参数
],
)
# 创建rviz2节点
rviz = Node(
package='rviz2', # 包名称
executable='rviz2', # 可执行文件名称
output='screen' # 输出到屏幕
)
# 声明一个处理程序,当robot_driver退出时关闭所有节点
shutdown_handler = launch.actions.RegisterEventHandler(
event_handler=launch.event_handlers.OnProcessExit(
target_action=robot_driver, # 目标动作:机器人驱动
on_exit=[launch.actions.EmitEvent(event=launch.events.Shutdown())], # 退出时触发关闭事件
)
)
# 返回LaunchDescription对象,包含所有节点和处理程序
return LaunchDescription([
robot_driver,
ros_control_node,
nav2_node,
rviz,
shutdown_handler
])
第二个启动文件包含一个处理程序,当驱动程序节点退出时(即模拟重置时),该处理程序会触发关闭事件。按下重置按钮后,必须从命令行手动重新启动第二个启动文件。
摘要
在本教程中,您学习了如何在使用 Webots 的机器人仿真中实现重置处理程序。重置处理程序允许您在按下 Webots 中的重置按钮时重新启动特定节点或执行其他操作。您根据仿真的复杂性和节点的要求探索了不同的方法。