Skip to content

Webots 机器人仿真设置(基础)

目标: 设置机器人仿真并从 ROS 2 控制它。

教程级别: 进阶

预计时间: 30 分钟

在本教程中,你将使用 Webots 机器人仿真器来搭建和运行一个非常简单的 ROS 2 仿真场景。

webots_ros2 包提供了 ROS 2 与 Webots 之间的接口。

它包含多个子包,但本教程只使用 webots_ros2_driver 子包来实现一个控制仿真机器人的 Python 或 C++ 插件。

其他子包包含不同机器人的演示,例如 TurtleBot3。

相关说明详见 Webots ROS 2 示例 页面。

建议先阅读以下前置教程:初识 Turtlesim、理解 ROS 2 话题、创建工作空间、创建你的第一个 ROS 2 包 和 创建 Launch 文件。

本教程的 Linux 和 ROS 命令可以在标准 Linux 终端中运行。

Webots 安装(Ubuntu) 页面介绍了如何在 Linux 上安装 webots_ros2 包。

本教程的 Linux 和 ROS 命令需要在 WSL(Windows Subsystem for Linux)环境中运行。

Webots 安装(Windows) 页面介绍了如何在 Windows 上安装 webots_ros2 包。

本教程的 Linux 和 ROS 命令需要在预配置的 Linux 虚拟机(VM)中运行。

Webots 安装(macOS) 页面介绍了如何在 macOS 上安装 webots_ros2 包。

本教程兼容 webots_ros2 版本 2023.1.0 和 Webots R2023b 及后续版本。

首先将代码组织在一个自定义的 ROS 2 包中。

在你的 ROS 2 工作空间的 src 文件夹中创建一个名为 my_package 的新包。

将终端切换到 ros2_ws/src 目录并运行:

Terminal window
ros2 pkg create --build-type ament_python --license Apache-2.0 --node-name my_robot_driver my_package --dependencies rclpy geometry_msgs webots_ros2_driver

--node-name my_robot_driver 选项会在 my_package 子文件夹中创建一个 my_robot_driver.py 模板 Python 插件,稍后再对其进行修改。

--dependencies rclpy geometry_msgs webots_ros2_driver 选项会在 package.xml 文件中声明 my_robot_driver.py 插件所需的依赖包。

在 my_package 文件夹中添加 launch 和 worlds 文件夹。

Terminal window
cd my_package
mkdir launch
mkdir worlds

最终应得到以下文件夹结构:

Terminal window
src/
└── my_package/
├── launch/
├── my_package/
│ ├── __init__.py
│ └── my_robot_driver.py
├── resource/
│ └── my_package
├── test/
│ ├── test_copyright.py
│ ├── test_flake8.py
│ └── test_pep257.py
├── worlds/
├── package.xml
├── setup.cfg
└── setup.py
Terminal window
ros2 pkg create --build-type ament_cmake --license Apache-2.0 --node-name MyRobotDriver my_package --dependencies rclcpp geometry_msgs webots_ros2_driver pluginlib

--node-name MyRobotDriver 选项会在 my_package/src 子文件夹中创建一个 MyRobotDriver.cpp 模板 C++ 插件,稍后再对其进行修改。

--dependencies rclcpp geometry_msgs webots_ros2_driver pluginlib 选项会在 package.xml 文件中声明 MyRobotDriver 插件所需的依赖包。

在 my_package 文件夹中添加 launch、worlds 和 resource 文件夹。

Terminal window
cd my_package
mkdir launch
mkdir worlds
mkdir resource

还需要创建两个文件:MyRobotDriver 的头文件和 my_robot_driver.xml pluginlib 描述文件。

Terminal window
touch my_robot_driver.xml
touch include/my_package/MyRobotDriver.hpp

最终应得到以下文件夹结构:

Terminal window
src/
└── my_package/
├── include/
│ └── my_package/
│ └── MyRobotDriver.hpp
├── launch/
├── resource/
├── src/
│ └── MyRobotDriver.cpp
├── worlds/
├── CMakeList.txt
├── my_robot_driver.xml
└── package.xml

需要一个包含机器人的世界文件来启动仿真。

下载文件 my_world.wbt,并将其移动到 my_package/worlds/ 中。

这实际上是一个相当简单的文本文件,你可以在文本编辑器中查看。

这个 my_world.wbt 世界文件中已经包含了一个简单的机器人。

注意: 如果想学习如何在 Webots 中创建自己的机器人模型,可以参考此教程。

webots_ros2_driver 子包会自动为大多数传感器创建 ROS 2 接口。

关于现有设备接口及其配置方法的更多细节,请参见教程第二部分:Webots 机器人仿真设置(进阶)。

在本任务中,你将通过创建自己的自定义插件来扩展此接口。

自定义插件相当于一个机器人控制器的 ROS 节点。

你可以使用它来访问 Webots 机器人 API 并创建自己的话题和服务来控制机器人。

注意: 本教程旨在展示一个依赖最少的简单示例。不过,你也可以使用另一个名为 webots_ros2_control 的 webots_ros2 子包(需要引入新的依赖)来避免手写此插件。该子包创建了与 ros2_control 包的接口,方便控制差速轮式机器人。

在编辑器中打开 my_package/my_package/my_robot_driver.py,将其内容替换为以下代码:

import rclpy
from geometry_msgs.msg import Twist
HALF_DISTANCE_BETWEEN_WHEELS = 0.045
WHEEL_RADIUS = 0.025
class MyRobotDriver:
def init(self, webots_node, properties):
self.__robot = webots_node.robot
self.__left_motor = self.__robot.getDevice('left wheel motor')
self.__right_motor = self.__robot.getDevice('right wheel motor')
self.__left_motor.setPosition(float('inf'))
self.__left_motor.setVelocity(0)
self.__right_motor.setPosition(float('inf'))
self.__right_motor.setVelocity(0)
self.__target_twist = Twist()
rclpy.init(args=None)
self.__node = rclpy.create_node('my_robot_driver')
self.__node.create_subscription(Twist, 'cmd_vel', self.__cmd_vel_callback, 1)
def __cmd_vel_callback(self, twist):
self.__target_twist = twist
def step(self):
rclpy.spin_once(self.__node, timeout_sec=0)
forward_speed = self.__target_twist.linear.x
angular_speed = self.__target_twist.angular.z
command_motor_left = (forward_speed - angular_speed * HALF_DISTANCE_BETWEEN_WHEELS) / WHEEL_RADIUS
command_motor_right = (forward_speed + angular_speed * HALF_DISTANCE_BETWEEN_WHEELS) / WHEEL_RADIUS
self.__left_motor.setVelocity(command_motor_left)
self.__right_motor.setVelocity(command_motor_right)

如你所见,MyRobotDriver 类实现了三个方法。

第一个方法 init(self, ...),相当于 ROS 节点版的 Python __init__(self, ...) 构造函数。

init 方法始终接受两个参数:

  • webots_node 参数包含对 Webots 实例的引用。
  • properties 参数是从 URDF 文件中的 XML 标签创建的字典(参见第 4 步:创建 my_robot.urdf 文件),用于向控制器传递参数。

仿真中的机器人实例 self.__robot 可用于访问 Webots 机器人 API。

随后获取两个电机实例,并设置其目标位置和目标速度。

最后,创建一个 ROS 节点,并为 /cmd_vel 话题注册一个回调方法来处理 Twist 消息。

def init(self, webots_node, properties):
self.__robot = webots_node.robot
self.__left_motor = self.__robot.getDevice('left wheel motor')
self.__right_motor = self.__robot.getDevice('right wheel motor')
self.__left_motor.setPosition(float('inf'))
self.__left_motor.setVelocity(0)
self.__right_motor.setPosition(float('inf'))
self.__right_motor.setVelocity(0)
self.__target_twist = Twist()
rclpy.init(args=None)
self.__node = rclpy.create_node('my_robot_driver')
self.__node.create_subscription(Twist, 'cmd_vel', self.__cmd_vel_callback, 1)

接下来是 __cmd_vel_callback(self, twist) 私有回调方法的实现。每当 /cmd_vel 话题上收到一条 Twist 消息时,该方法就会被调用,并将消息保存到 self.__target_twist 成员变量中。

def __cmd_vel_callback(self, twist):
self.__target_twist = twist

最后,step(self) 方法在仿真的每个时间步被调用。

调用 rclpy.spin_once() 是为了保持 ROS 节点正常运行。

在每个时间步,该方法会从 self.__target_twist 中获取所需的 forward_speed 和 angular_speed。

由于电机是通过角速度控制的,该方法随后会将 forward_speed 和 angular_speed 转换为每个车轮的独立命令。

此转换取决于机器人的结构,具体来说是车轮的半径和两轮之间的距离。

def step(self):
rclpy.spin_once(self.__node, timeout_sec=0)
forward_speed = self.__target_twist.linear.x
angular_speed = self.__target_twist.angular.z
command_motor_left = (forward_speed - angular_speed * HALF_DISTANCE_BETWEEN_WHEELS) / WHEEL_RADIUS
command_motor_right = (forward_speed + angular_speed * HALF_DISTANCE_BETWEEN_WHEELS) / WHEEL_RADIUS
self.__left_motor.setVelocity(command_motor_left)
self.__right_motor.setVelocity(command_motor_right)

在编辑器中打开 my_package/include/my_package/MyRobotDriver.hpp,将其内容替换为以下代码:

#ifndef WEBOTS_ROS2_PLUGIN_EXAMPLE_HPP
#define WEBOTS_ROS2_PLUGIN_EXAMPLE_HPP
#include "rclcpp/macros.hpp"
#include "webots_ros2_driver/PluginInterface.hpp"
#include "webots_ros2_driver/WebotsNode.hpp"
#include "geometry_msgs/msg/twist.hpp"
#include "rclcpp/rclcpp.hpp"
namespace my_robot_driver {
class MyRobotDriver : public webots_ros2_driver::PluginInterface {
public:
void step() override;
void init(webots_ros2_driver::WebotsNode *node,
std::unordered_map<std::string, std::string> &parameters) override;
private:
rclcpp::Subscription<geometry_msgs::msg::Twist>::SharedPtr
cmd_vel_subscription_;
geometry_msgs::msg::Twist cmd_vel_msg;
WbDeviceTag right_motor;
WbDeviceTag left_motor;
};
} // namespace my_robot_driver
#endif

这里定义了 MyRobotDriver 类,它继承自 webots_ros2_driver::PluginInterface 类。

插件必须重写 step(...) 和 init(...) 函数。

更多细节在 MyRobotDriver.cpp 文件中给出。

几个辅助方法、回调和成员变量被声明为私有,供插件内部使用。

然后,在编辑器中打开 my_package/src/MyRobotDriver.cpp,将其内容替换为以下代码:

#include "my_package/MyRobotDriver.hpp"
#include "rclcpp/rclcpp.hpp"
#include <cstdio>
#include <functional>
#include <webots/motor.h>
#include <webots/robot.h>
#define HALF_DISTANCE_BETWEEN_WHEELS 0.045
#define WHEEL_RADIUS 0.025
namespace my_robot_driver {
void MyRobotDriver::init(
webots_ros2_driver::WebotsNode *node,
std::unordered_map<std::string, std::string> &parameters) {
right_motor = wb_robot_get_device("right wheel motor");
left_motor = wb_robot_get_device("left wheel motor");
wb_motor_set_position(left_motor, INFINITY);
wb_motor_set_velocity(left_motor, 0.0);
wb_motor_set_position(right_motor, INFINITY);
wb_motor_set_velocity(right_motor, 0.0);
cmd_vel_subscription_ = node->create_subscription<geometry_msgs::msg::Twist>(
"/cmd_vel", rclcpp::SensorDataQoS().reliable(),
[this](const geometry_msgs::msg::Twist::ConstSharedPtr msg){
this->cmd_vel_msg.linear = msg->linear;
this->cmd_vel_msg.angular = msg->angular;
}
);
}
void MyRobotDriver::step() {
auto forward_speed = cmd_vel_msg.linear.x;
auto angular_speed = cmd_vel_msg.angular.z;
auto command_motor_left =
(forward_speed - angular_speed * HALF_DISTANCE_BETWEEN_WHEELS) /
WHEEL_RADIUS;
auto command_motor_right =
(forward_speed + angular_speed * HALF_DISTANCE_BETWEEN_WHEELS) /
WHEEL_RADIUS;
wb_motor_set_velocity(left_motor, command_motor_left);
wb_motor_set_velocity(right_motor, command_motor_right);
}
} // namespace my_robot_driver
#include "pluginlib/class_list_macros.hpp"
PLUGINLIB_EXPORT_CLASS(my_robot_driver::MyRobotDriver,
webots_ros2_driver::PluginInterface)

MyRobotDriver::init 方法在插件被 webots_ros2_driver 包加载时执行一次。

它接受两个参数:

  • 一个指向由 webots_ros2_driver 定义的 WebotsNode 的指针,可用于访问 ROS 2 节点功能。
  • parameters 参数是一个字符串的无序映射,从 URDF 文件中的 XML 标签创建(参见第 4 步:创建 my_robot.urdf 文件),用于向控制器传递参数。本例中未使用。

该方法通过设置机器人电机的位置和速度,以及订阅 /cmd_vel 话题来初始化插件。

void MyRobotDriver::init(
webots_ros2_driver::WebotsNode *node,
std::unordered_map<std::string, std::string> &parameters) {
right_motor = wb_robot_get_device("right wheel motor");
left_motor = wb_robot_get_device("left wheel motor");
wb_motor_set_position(left_motor, INFINITY);
wb_motor_set_velocity(left_motor, 0.0);
wb_motor_set_position(right_motor, INFINITY);
wb_motor_set_velocity(right_motor, 0.0);
cmd_vel_subscription_ = node->create_subscription<geometry_msgs::msg::Twist>(
"/cmd_vel", rclcpp::SensorDataQoS().reliable(),
[this](const geometry_msgs::msg::Twist::ConstSharedPtr msg){
this->cmd_vel_msg.linear = msg->linear;
this->cmd_vel_msg.angular = msg->angular;
}
);
}

订阅回调以 lambda 函数的形式声明,每当 /cmd_vel 话题上收到一条 Twist 消息时就会调用它,并将消息保存到 cmd_vel_msg 成员变量中。

[this](const geometry_msgs::msg::Twist::ConstSharedPtr msg){
this->cmd_vel_msg.linear = msg->linear;
this->cmd_vel_msg.angular = msg->angular;
}

step() 方法在仿真的每个时间步被调用。

在每个时间步,该方法会从 cmd_vel_msg 中获取所需的 forward_speed 和 angular_speed。

由于电机是通过角速度控制的,该方法随后会将 forward_speed 和 angular_speed 转换为每个车轮的独立命令。

此转换取决于机器人的结构,具体来说是车轮的半径和两轮之间的距离。

void MyRobotDriver::step() {
auto forward_speed = cmd_vel_msg.linear.x;
auto angular_speed = cmd_vel_msg.angular.z;
auto command_motor_left =
(forward_speed - angular_speed * HALF_DISTANCE_BETWEEN_WHEELS) /
WHEEL_RADIUS;
auto command_motor_right =
(forward_speed + angular_speed * HALF_DISTANCE_BETWEEN_WHEELS) /
WHEEL_RADIUS;
wb_motor_set_velocity(left_motor, command_motor_left);
wb_motor_set_velocity(right_motor, command_motor_right);
}

文件的最后几行定义了 my_robot_driver 命名空间的结束,并通过 PLUGINLIB_EXPORT_CLASS 宏将 MyRobotDriver 类导出为插件。

这样插件就能在运行时被 Webots ROS 2 驱动程序加载。

#include "pluginlib/class_list_macros.hpp"
PLUGINLIB_EXPORT_CLASS(my_robot_driver::MyRobotDriver,
webots_ros2_driver::PluginInterface)

注意: 虽然插件是用 C++ 实现的,但必须使用 C API 与 Webots 控制器库交互。

现在需要创建一个 URDF 文件来声明 MyRobotDriver 插件。

这样 webots_ros2_driver ROS 节点就能启动该插件并将其连接到目标机器人。

在 my_package/resource 文件夹中创建一个名为 my_robot.urdf 的文本文件,内容如下:

<?xml version="1.0" ?>
<robot name="My robot">
<webots>
<plugin type="my_package.my_robot_driver.MyRobotDriver" />
</webots>
</robot>

type 属性指定了由文件层次结构确定的类路径。

webots_ros2_driver 会根据指定的包和模块来加载该类。

<?xml version="1.0" ?>
<robot name="My robot">
<webots>
<plugin type="my_robot_driver::MyRobotDriver" />
</webots>
</robot>

type 属性指定了要加载的命名空间和类名。

pluginlib 会根据这些信息来加载该类。

注意: 这个简单的 URDF 文件不包含任何机器人的链接或关节信息,因为本教程中不需要。但 URDF 文件通常包含更多信息,详见 URDF 教程。

注意: 这里插件不接受任何输入参数,但可以通过包含参数名称的标签来实现。

<plugin type="my_package.my_robot_driver.MyRobotDriver"> <parameterName>someValue</parameterName> </plugin>

<plugin type="my_robot_driver::MyRobotDriver"> <parameterName>someValue</parameterName> </plugin>

这通常用于向现有的 Webots 设备插件传递参数(参见 Webots 机器人仿真设置(进阶))。

接下来创建一个 launch 文件,以便用一条命令就能启动仿真和 ROS 控制器。

在 my_package/launch 文件夹中创建一个名为 robot_launch.py 的新文本文件,代码如下:

import os
import launch
from launch import LaunchDescription
from ament_index_python.packages import get_package_share_directory
from webots_ros2_driver.webots_launcher import WebotsLauncher
from webots_ros2_driver.webots_controller import WebotsController
def generate_launch_description():
package_dir = get_package_share_directory('my_package')
robot_description_path = os.path.join(package_dir, 'resource', 'my_robot.urdf')
webots = WebotsLauncher(
world=os.path.join(package_dir, 'worlds', 'my_world.wbt')
)
my_robot_driver = WebotsController(
robot_name='my_robot',
parameters=[
{'robot_description': robot_description_path},
]
)
return LaunchDescription([
webots,
my_robot_driver,
launch.actions.RegisterEventHandler(
event_handler=launch.event_handlers.OnProcessExit(
target_action=webots,
on_exit=[launch.actions.EmitEvent(event=launch.events.Shutdown())],
)
)
])

WebotsLauncher 对象是一个自定义操作,用于启动 Webots 仿真实例。

需要在构造函数中指定仿真器要打开的世界文件。

webots = WebotsLauncher(
world=os.path.join(package_dir, 'worlds', 'my_world.wbt')
)

然后创建与仿真机器人交互的 ROS 节点。

这个名为 WebotsController 的节点位于 webots_ros2_driver 包中。

该节点将能够使用基于 IPC 和共享内存的自定义协议与仿真机器人通信。

该节点(在 WSL 中)将通过 TCP 连接与仿真机器人(在原生 Windows 上的 Webots 中)通信。

该节点(在 docker 容器中)将通过 TCP 连接与仿真机器人(在原生 macOS 上的 Webots 中)通信。

本例中只需运行该节点的一个实例,因为仿真中只有一个机器人。

如果仿真中有更多机器人,则需要为每个机器人运行一个该节点的实例。

robot_name 参数用于指定驱动程序应连接的机器人名称。

robot_description 参数保存指向引用 MyRobotDriver 插件的 URDF 文件的路径。

可以将 WebotsController 节点视为将控制器插件连接到目标机器人的接口。

my_robot_driver = WebotsController(
robot_name='my_robot',
parameters=[
{'robot_description': robot_description_path},
]
)

之后,两个节点被添加到 LaunchDescription 构造函数中启动:

return LaunchDescription([
webots,
my_robot_driver,

最后,添加了一个可选部分,以便在 Webots 终止时(例如从图形界面关闭时)关闭所有节点。

launch.actions.RegisterEventHandler(
event_handler=launch.event_handlers.OnProcessExit(
target_action=webots,
on_exit=[launch.actions.EmitEvent(event=launch.events.Shutdown())],
)
)

注意: 关于 WebotsController 和 WebotsLauncher 参数的更多细节,请参见节点参考页面。

在启动 launch 文件之前,需要修改 setup.py 文件以包含新添加的文件。

打开 my_package/setup.py,将其内容替换为:

from setuptools import find_packages, setup
package_name = 'my_package'
data_files = []
data_files.append(('share/ament_index/resource_index/packages', ['resource/' + package_name]))
data_files.append(('share/' + package_name + '/launch', ['launch/robot_launch.py']))
data_files.append(('share/' + package_name + '/worlds', ['worlds/my_world.wbt']))
data_files.append(('share/' + package_name + '/resource', ['resource/my_robot.urdf']))
data_files.append(('share/' + package_name, ['package.xml']))
setup(
name=package_name,
version='0.0.0',
packages=find_packages(exclude=['test']),
data_files=data_files,
install_requires=['setuptools'],
zip_safe=True,
maintainer='user',
maintainer_email='user.name@mail.com',
description='TODO: Package description',
license='TODO: License declaration',
extras_require={
'test': ['pytest'],
},
entry_points={
'console_scripts': [
'my_robot_driver = my_package.my_robot_driver:main',
],
},
)

这将完成包的设置,并在 data_files 变量中添加新文件:my_world.wbt、my_robot.urdf 和 robot_launch.py。

在启动 launch 文件之前,需要修改 CMakeLists.txt 和 my_robot_driver.xml 文件:

  • CMakeLists.txt 定义了插件的编译规则。
  • my_robot_driver.xml 是 pluginlib 查找 Webots ROS 2 插件所必需的。

打开 my_package/my_robot_driver.xml,将其内容替换为:

<library path="my_package">
<!-- The `type` attribute is a reference to the plugin class. -->
<!-- The `base_class_type` attribute is always `webots_ros2_driver::PluginInterface`. -->
<class type="my_robot_driver::MyRobotDriver" base_class_type="webots_ros2_driver::PluginInterface">
<description>
This is a Webots ROS 2 plugin example
</description>
</class>
</library>

打开 my_package/CMakeLists.txt,将其内容替换为:

cmake_minimum_required(VERSION 3.20)
project(my_package)
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()
# Besides the package specific dependencies we also need the `pluginlib` and `webots_ros2_driver`
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
find_package(geometry_msgs REQUIRED)
find_package(pluginlib REQUIRED)
find_package(webots_ros2_driver REQUIRED)
# Export the plugin configuration file
pluginlib_export_plugin_description_file(webots_ros2_driver my_robot_driver.xml)
# MyRobotDriver library
add_library(
${PROJECT_NAME}
SHARED
src/MyRobotDriver.cpp
)
target_include_directories(
${PROJECT_NAME}
PRIVATE
include
)
ament_target_dependencies(
${PROJECT_NAME}
pluginlib
rclcpp
webots_ros2_driver
)
install(TARGETS
${PROJECT_NAME}
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)
# Install additional directories.
install(DIRECTORY
launch
resource
worlds
DESTINATION share/${PROJECT_NAME}/
)
ament_export_include_directories(
include
)
ament_export_libraries(
${PROJECT_NAME}
)
ament_package()

CMakeLists.txt 使用 pluginlib_export_plugin_description_file() 导出插件配置文件,定义了 C++ 插件 src/MyRobotDriver.cpp 的共享库,并通过 target_link_libraries() 设置包含目录和库依赖。

然后,该文件将库、launch、resource 和 worlds 目录安装到 share/my_package 目录中。

最后,分别使用 ament_export_include_directories() 和 ament_export_libraries() 导出包含目录和库,并通过 ament_package() 声明该包。

在你的 ROS 2 工作空间的终端中运行:

Terminal window
colcon build
source install/local_setup.bash
ros2 launch my_package robot_launch.py

这将启动仿真。如果 Webots 尚未安装,首次运行时会自动安装。

在你的 WSL ROS 2 工作空间的终端中运行:

Terminal window
colcon build
export WEBOTS_HOME=/mnt/c/Program\ Files/Webots
source install/local_setup.bash
ros2 launch my_package robot_launch.py

确保在 Webots 安装文件夹路径前使用 /mnt 前缀,以便从 WSL 访问 Windows 文件系统。

这将启动仿真。如果 Webots 尚未安装,首次运行时会自动安装。

在 macOS 上,需要在主机上启动本地服务器才能从虚拟机启动 Webots。

可以在 webots-server 仓库 中下载本地服务器。

在主机(不是虚拟机)的终端中,指定 Webots 安装文件夹(例如 /Applications/Webots.app)并运行以下命令启动服务器:

Terminal window
export WEBOTS_HOME=/Applications/Webots.app
python3 local_simulation_server.py

在 Linux 虚拟机的 ROS 2 工作空间的终端中,构建并启动自定义包:

Terminal window
colcon build
source install/local_setup.bash
ros2 launch my_package robot_launch.py

注意: 如果想手动安装 Webots,可以在这里下载。

然后,打开第二个终端并发送命令:

Terminal window
ros2 topic pub /cmd_vel geometry_msgs/Twist "linear: { x: 0.1 }"

机器人现在正在前进。

{/截图:机器人前进/}

此时,机器人能够盲目跟随你的电机命令。

但当你命令它前进时,它最终会撞到墙上。

{/截图:机器人撞墙/}

关闭 Webots 窗口,这也会关闭从 launcher 启动的 ROS 节点。

同时在第二个终端中使用 Ctrl+C 终止话题命令。

在本教程中,你使用 Webots 搭建了一个机器人仿真场景,并实现了自定义插件来控制机器人电机。

为了改进仿真,可以利用机器人的传感器来检测和避开障碍物。

教程的第二部分展示了如何实现这种行为: