警告
您正在阅读的 ROS 2 文档版本已达到 EOL(生命周期结束),不再受官方支持。如果您想了解最新信息,请访问 Jazzy.
设置机器人模拟(Webots)
目标 设置机器人仿真,并通过 ROS 2 进行控制。
辅导水平: 高级
时间 20 分钟
背景介绍
ROS 2 可以使用多种机器人模拟器,如 Gazebo、Ignition、Webots 等。与 turtlesim 不同的是,这些模拟器基于机器人、传感器、执行器和物体的物理模型,能提供相当逼真的结果。因此,您在模拟中观察到的结果与将 ROS 2 控制器转移到真实机器人上时得到的结果非常接近。在本教程中,您将使用 Webots 机器人模拟器来设置和运行一个非常简单的 ROS 2 模拟场景。
"(《世界人权宣言》) webots_ros2
软件包提供了 ROS 2 和 Webots 之间的接口。它包括多个子软件包,但在本教程中,您将只使用 webots_ros2_driver
子软件包来实现一个控制模拟机器人的 Python 插件。其他一些子软件包包含不同机器人的演示,如 TurtleBot3。它们在 演示 page.
先决条件
建议先了解初级课程中涉及的 ROS 基本原理。 教程.特别是 使用 turtlesim 和 rqt, 了解主题, 创建工作区, 创建软件包 和 创建启动文件 是有用的先决条件。
本教程中的 Linux 和 ROS 命令可在标准 Linux 终端中运行。请参见 Webots ROS 2 Linux 安装说明.
本教程中的 Linux 和 ROS 命令必须在 WSL(Windows Subsystem for Linux)环境中运行。请参见 Webots ROS 2 Windows 安装说明.
本教程中的 Linux 和 ROS 命令必须在自定义 Docker 容器中运行,该容器配置了 webots_ros2_driver
包装。参见 Webots ROS 2 macOS 安装说明.
安装 webots_ros2_driver
在终端上执行以下命令。
sudo apt update
sudo apt install ros-galactic-webots-ros2-driver
source /opt/ros/galactic/setup.bash
备注
如果您想安装整个 webots_ros2
软件包,请按照以下步骤操作 说明.
任务
1 创建软件包结构
让我们在自定义 ROS 2 软件包中组织代码。创建一个名为 我的包
从 来源
文件夹。将终端的当前目录更改为 ros2_ws/src
并运行:
ros2 pkg create --build-type ament_python --node-name my_robot_driver my_package --ependencies rclpy geometry_msgs webots_ros2_driver
"(《世界人权宣言》) --节点名 我的机器人驱动程序
选项将创建一个 my_robot_driver.py
中的 Python 模板插件 我的包
子文件夹,稍后您将对其进行修改。文件夹 --依赖 rclpy 几何参数 webots_ros2_driver
选项指定 my_robot_driver.py
插件中的 package.xml
文件。让我们添加一个 启动
和一个 世界
文件夹内的 我的包
文件夹。
cd my_package
mkdir launch
mkdir worlds
最终的文件夹结构如下:
源代码
└──我的包装
├── 发射/
├── my_package/
│ ├── __init__.py
│ └── my_robot_driver.py
资源
│ └── my_package
├── 测试/
│ ├── test_copyright.py
│ ├── test_flake8.py
│ └── test_pep257.py
├──世界/
├── package.xml
├── setup.cfg
└──setup.py
2 设置模拟世界
您需要一个包含机器人的世界文件来启动模拟。
下载 此 世界 文件
并将其移入 my_package/worlds/
.
这实际上是一个相当简单的文本文件,你可以用文本编辑器将其可视化。其中已经包含了一个简单的机器人 my_world.wbt
世界文件。
备注
如果您想了解如何在 Webots 中创建自己的机器人模型,可以查看以下内容 教程.
3 更改 my_robot_driver.py 文件
"(《世界人权宣言》) webots_ros2_driver
子软件包会自动为大多数传感器创建 ROS 2 接口。在本任务中,您将通过更改 my_robot_driver.py
锉刀
备注
本教程的目的是以最少的依赖项展示一个基本示例。不过,您可以通过使用另一个 webots_ros2
名为 webots_ros2_control
,引入了一个新的依赖关系。这个子软件包创建了一个与 ros2_control
该软件包有助于控制差动轮式机器人。
开放 my_package/my_package/my_robot_driver.py
在您喜欢的编辑器中,将其内容替换为以下内容:
舶来品 rclpy
从 几何_msgs.msg 舶来品 扭转
轮距的一半 = 0.045
WHEEL_RADIUS = 0.025
类 我的机器人驾驶员:
捍卫 启动(自我, 网络节点, 属性):
自我.机器人 = 网络节点.机器人
自我.__左发动机 = 自我.机器人.获取设备('左轮电机';)
自我.__右电机 = 自我.机器人.获取设备('右轮电机';)
自我.__左发动机.设置位置(浮动('inf';))
自我.__左发动机.设置速度(0)
自我.__右电机.设置位置(浮动('inf';))
自我.__右电机.设置速度(0)
自我.__目标扭转 = 扭转()
rclpy.启动(参数=无)
自我.节点 = rclpy.创建节点('my_robot_driver')
自我.节点.创建订阅(扭转, 'cmd_vel';, 自我.__cmd_vel_callback, 1)
捍卫 __cmd_vel_callback(自我, 绹):
自我.__目标扭转 = 绹
捍卫 步骤(自我):
rclpy.自旋一次(自我.节点, 超时秒数=0)
前进速度 = 自我.__目标扭转.线形.x
角速度 = 自我.__目标扭转.角度.z
左发动机指令 = (前进速度 - 角速度 * 轮距的一半) / WHEEL_RADIUS
右电机指令 = (前进速度 + 角速度 * 轮距的一半) / WHEEL_RADIUS
自我.__左发动机.设置速度(左发动机指令)
自我.__右电机.设置速度(右电机指令)
正如您所看到的 我的机器人驾驶员
类实现了三种方法。
第一个方法名为 init(self、 ...)
实际上是 ROS 节点与 Python __init__(self、 ...)
构造函数。它首先从模拟中获取机器人实例(可用于访问 Webots 机器人应用程序接口).然后,它会获取两个电机实例,并用目标位置和目标速度对其进行初始化。最后创建一个 ROS 节点,并为名为 /cmd_vel
将处理 扭转
留言
捍卫 启动(自我, 网络节点, 属性):
自我.机器人 = 网络节点.机器人
自我.__左发动机 = 自我.机器人.获取设备('左轮电机';)
自我.__右电机 = 自我.机器人.获取设备('右轮电机';)
自我.__左发动机.设置位置(浮动('inf';))
自我.__左发动机.设置速度(0)
自我.__右电机.设置位置(浮动('inf';))
自我.__右电机.设置速度(0)
自我.__目标扭转 = 扭转()
rclpy.启动(参数=无)
自我.节点 = rclpy.创建节点('my_robot_driver')
自我.节点.创建订阅(扭转, 'cmd_vel';, 自我.__cmd_vel_callback, 1)
然后是执行 __cmd_vel_callback(self、 扭曲)
回调私人方法,每个 扭转
上收到的信息 /cmd_vel
主题,并将其保存在 self.__target_twist
成员变量。
捍卫 __cmd_vel_callback(自我, 绹):
自我.__目标扭转 = 绹
最后 step(self)
方法在模拟的每个时间步长都会被调用。调用 rclpy.spin_once()
以保证 ROS 节点顺利运行。在每个时间步长,该方法将检索所需的 前进速度
和 角速度
从 self.__target_twist
.由于电机是用角速度控制的,因此该方法会将 前进速度
和 角速度
转换成每个轮子的单独指令。这种转换取决于机器人的结构,更具体地说,取决于轮子的半径和它们之间的距离。
捍卫 步骤(自我):
rclpy.自旋一次(自我.节点, 超时秒数=0)
前进速度 = 自我.__目标扭转.线形.x
角速度 = 自我.__目标扭转.角度.z
左发动机指令 = (前进速度 - 角速度 * 轮距的一半) / WHEEL_RADIUS
右电机指令 = (前进速度 + 角速度 * 轮距的一半) / WHEEL_RADIUS
自我.__左发动机.设置速度(左发动机指令)
自我.__右电机.设置速度(右电机指令)
4 创建 my_robot.urdf 文件
现在,您必须创建一个 URDF 文件,以声明 my_robot_driver.py
Python 插件。这将允许 webots_ros2_driver
ROS 节点来启动插件。
在 my_package/resource
文件夹中创建一个名为 my_robot.urdf
有这些内容:
<?xml version="1.0" ?>;
机器人 名称"我的机器人";>;
<webots>;
<plugin type="my_package.my_robot_driver.MyRobotDriver"; />;
</webots>;
</机器人>;
备注
这个简单的 URDF 文件不包含任何有关机器人的链接或关节信息,因为本教程不需要这些信息。不过,URDF 文件通常包含更多信息,详见 URDF.
5 创建启动文件
现在让我们创建启动文件,只需一条命令就能轻松启动模拟和 ROS 控制器。在 my_package/launch
文件夹中新建一个名为 robot_launch.py
使用此代码:
舶来品 os
舶来品 pathlib
舶来品 启动
从 launch_ros.actions 舶来品 节点
从 启动 舶来品 启动说明
从 ament_index_python.packages 舶来品 获取软件包共享目录
从 webots_ros2_driver.webots_launcher 舶来品 WebotsLauncher, Ros2SupervisorLauncher
从 webots_ros2_driver.utils 舶来品 控制器URL前缀
捍卫 生成发射描述():
软件包目录 = 获取软件包共享目录('my_package';)
机器人描述 = pathlib.路径(os.路.加入(软件包目录, '资源';, 'my_robot.urdf')).读取文本()
网点 = WebotsLauncher(
世界=os.路.加入(软件包目录, '世界';, 'my_world.wbt')
)
ros2_supervisor = Ros2SupervisorLauncher()
我的机器人驱动程序 = 节点(
包装='webots_ros2_driver',
可执行='司机';,
产量='屏幕';,
additional_env={'webots_controller_url';: 控制器URL前缀() + '我的机器人';},
参数=[
{'robot_description';: 机器人描述},
]
)
返回 启动说明([
网点,
我的机器人驱动程序,
ros2_supervisor,
启动.行动.注册事件处理程序(
事件处理程序=启动.事件处理程序.进程结束时(
target_action=网点,
on_exit=[启动.行动.发射事件(事件=启动.活动.关闭())],
)
)
])
"(《世界人权宣言》) WebotsLauncher
对象是一个自定义操作,可用于启动 Webots 模拟实例。您必须在构造函数中指定模拟器将打开哪个世界文件。
网点 = WebotsLauncher(
世界=os.路.加入(软件包目录, '世界';, 'my_world.wbt')
)
监督机器人总是通过以下方式自动添加到世界文件中 WebotsLauncher
.该机器人由自定义节点 Ros2Supervisor
也必须使用 Ros2SupervisorLauncher
.该节点可以直接在世界中生成 URDF 机器人,还可以发布有用的主题,如 /时钟
.
ros2_supervisor = Ros2SupervisorLauncher()
然后,创建与模拟机器人交互的 ROS 节点。该节点名为 驱动程序
位于 webots_ros2_driver
包装
该节点可通过基于 IPC 和共享内存的定制协议与模拟机器人进行通信。
节点(在 WSL 中)可以通过 TCP 连接与模拟机器人(在 Windows 的 Webots 中)进行通信。
节点(在 docker 容器中)可以通过 TCP 连接与模拟机器人(在 MacOS 的 Webots 中)进行通信。
在您的案例中,您只需运行该节点的一个实例,因为模拟中只有一个机器人。但如果模拟中有更多机器人,就必须为每个机器人运行一个节点实例。
webots_controller_url
用于定义驱动程序应连接的机器人名称。机器人名称 controller_url_prefix()
方法是强制性的,因为它允许 webots_ros2_driver
以根据平台添加正确的协议前缀。协议前缀 机器人描述
参数包含 URDF 文件的内容,该文件引用了 my_robot_driver.py
Python 插件。
我的机器人驱动程序 = 节点(
包装='webots_ros2_driver',
可执行='司机';,
产量='屏幕';,
additional_env={'webots_controller_url';: 控制器URL前缀() + '我的机器人';},
参数=[
{'robot_description';: 机器人描述},
]
)
之后,这三个节点被设置为在 启动说明
构造函数:
返回 启动说明([
网点,
我的机器人驱动程序,
ros2_supervisor,
最后,还添加了一个可选部分,以便在 Webots 终止时(如从图形用户界面关闭时)关闭所有节点。
启动.行动.注册事件处理程序(
事件处理程序=启动.事件处理程序.进程结束时(
target_action=网点,
on_exit=[启动.行动.发射事件(事件=启动.活动.关闭())],
)
)
6 修改 setup.py 文件
最后,在启动启动文件之前,您必须修改 setup.py
文件,以包含您添加的额外文件。打开 my_package/setup.py
并将其内容替换为
从 设置工具 舶来品 设置
包名 = 'my_package';
数据文件 = []
数据文件.追加(('share/ament_index/resource_index/packages';, ['资源/'; + 包名]))
数据文件.追加(('分享/'; + 包名 + '/launch';, ['launch/robot_launch.py';]))
数据文件.追加(('分享/'; + 包名 + '/worlds';, ['worlds/my_world.wbt';]))
数据文件.追加(('分享/'; + 包名 + '/resource';, ['resource/my_robot.urdf';]))
数据文件.追加(('分享/'; + 包名, ['package.xml';]))
设置(
名字=包名,
版本='0.0.0',
套餐=[包名],
数据文件=数据文件,
安装要求=['setuptools';],
zip_safe=正确,
维护者='用户';,
维护者电子邮件='[email protected]';,
描述='TODO:软件包描述';,
许可='TODO:许可证声明';,
测试要求=['pytest';],
入口={
'console_scripts';: [
'my_robot_driver = my_package.my_robot_driver:main';,
],
},
)
这将设置软件包并添加 数据文件
可变新添加的文件: my_world.wbt
, my_robot.urdf
和 robot_launch.py
.
7 测试代码
在 ROS 2 工作区的终端运行
Colcon build
source install/local_setup.bash
ros2 launch my_package robot_launch.py
这将启动模拟。如果 Webots 尚未安装,则将在首次运行时自动安装。
在 WSL ROS 2 工作区的终端运行
Colcon build
export WEBOTS_HOME=/mnt/c/Program\ Files/Webots
source install/local_setup.bash
ros2 launch my_package robot_launch.py
请务必使用 /mnt
前缀,以便从 WSL 访问 Windows 文件系统。
这将启动模拟。如果 Webots 尚未安装,则将在首次运行时自动安装。
在 macOS 上,必须在主机上启动本地服务器,才能从 Docker 容器启动 Webots。可下载本地服务器 上的 webots-server 存储库.
在主机终端(而不是在容器中)指定 Webots 安装文件夹(例如 /Applications/Webots.app
),并使用以下命令启动服务器:
export WEBOTS_HOME=/Applications/Webots.app
python3 local_simulation_server.py
在 Docker 容器的终端,使用以下命令构建并启动自定义软件包:
cd ~/ros2_ws
Colcon build
source install/local_setup.bash
ros2 launch my_package robot_launch.py
备注
如果您想手动安装 Webots,可以下载 这里.
然后,打开第二个终端,发送一条命令:
ros2 topic pub /cmd_vel geometry_msgs/Twist "linear: { x: 0.1 }";
机器人现在向前移动。

此时,机器人可以盲目地听从您的电机指令。但是,当你命令它向前移动时,它最终会撞到墙上。

为了避免这种情况,让我们使用机器人的传感器来检测障碍物并避开它们。关闭 Webots 窗口,同时关闭从启动器启动的 ROS 节点。同时关闭主题命令 Ctrl+C
在第二个终端。
8 更新 my_robot.urdf
您必须修改 URDF 文件才能启用传感器。在 my_robot.urdf
将全部内容替换为
<?xml version="1.0" ?>;
机器人 名称"我的机器人";>;
<webots>;
设备 参考文献="ds0"; type="距离传感器";>;
<ros>;
<主题名称>;/left_sensor</topicName>;
<始终打开>;真</alwaysOn>;
</ros>;
</device>;
设备 参考文献="ds1"; type="距离传感器";>;
<ros>;
<主题名称>;/右传感器</topicName>;
<始终打开>;真</alwaysOn>;
</ros>;
</device>;
<plugin type="my_package.my_robot_driver.MyRobotDriver"; />;
</webots>;
</机器人>;
ROS 2 接口将解析 设备
标记指向 距离传感器 节点,并使用 <ros>;
标签来启用传感器并命名其主题。
9 创建 ROS 节点以避开障碍物
机器人将使用一个标准的 ROS 节点来检测墙壁,并发送电机指令以避开墙壁。在 my_package/my_package/
文件夹,创建一个名为 obstacle_avoider.py
使用此代码:
舶来品 rclpy
从 rclpy.node 舶来品 节点
从 传感器_msgs.msg 舶来品 范围
从 几何_msgs.msg 舶来品 扭转
最大范围 = 0.15
类 障碍规避器(节点):
捍卫 启动(自我):
棒极了().启动('obstacle_avoider';)
自我.__出版商 = 自我.创建出版商(扭转, 'cmd_vel';, 1)
自我.创建订阅(范围, 'left_sensor', 自我.__左传感器回调, 1)
自我.创建订阅(范围, 'right_sensor', 自我.__右传感器回调, 1)
捍卫 __左传感器回调(自我, 信息):
自我.__左传感器值 = 信息.范围
捍卫 __右传感器回调(自我, 信息):
自我.右传感器值 = 信息.范围
命令消息 = 扭转()
命令消息.线形.x = 0.1
如果 自我.__左传感器值 <; 0.9 * 最大范围 或 自我.右传感器值 <; 0.9 * 最大范围:
命令消息.角度.z = -2.0
自我.__出版商.发布(命令消息)
捍卫 主要(参数=无):
rclpy.启动(参数=参数)
避免者 = 障碍规避器()
rclpy.后旋(避免者)
# 明确销毁节点
# (可选,否则将自动完成
# 当垃圾回收器销毁节点对象时)
避免者.destroy_node()
rclpy.关闭()
如果 姓名____ == '__main__';:
主要()
该节点将为命令创建一个发布者,并在此订阅传感器主题:
自我.__出版商 = 自我.创建出版商(扭转, 'cmd_vel';, 1)
自我.创建订阅(范围, 'left_sensor', 自我.__左传感器回调, 1)
自我.创建订阅(范围, 'right_sensor', 自我.__右传感器回调, 1)
从左侧传感器接收到的测量值将被复制到成员字段中:
捍卫 __左传感器回调(自我, 信息):
自我.__左传感器值 = 信息.范围
最后,将向 /cmd_vel
当接收到来自正确传感器的测量值时,就会出现该主题。传感器 命令消息
将至少以 线性.x
以便在没有检测到障碍物时让机器人移动。如果两个传感器中的任何一个检测到障碍物、 命令消息
也会以 angular.z
以使机器人右转。
捍卫 __右传感器回调(自我, 信息):
自我.右传感器值 = 信息.范围
命令消息 = 扭转()
命令消息.线形.x = 0.1
如果 自我.__左传感器值 <; 0.9 * 最大范围 或 自我.右传感器值 <; 0.9 * 最大范围:
命令消息.角度.z = -2.0
自我.__出版商.发布(命令消息)
10 更新 setup.py 和 robot_launch.py
您必须修改另外两个文件才能启动新节点。编辑 setup.py
并取代 控制台脚本
用:
'console_scripts';: [
'my_robot_driver = my_package.my_robot_driver:main';,
'obstacle_avoider=my_package.obstacle_avoider:main';
],
这将为 避障器
节点。
转到文件 robot_launch.py
并取代 捍卫 generate_launch_description():
用:
捍卫 生成发射描述():
软件包目录 = 获取软件包共享目录('my_package';)
机器人描述 = pathlib.路径(os.路.加入(软件包目录, '资源';, 'my_robot.urdf')).读取文本()
网点 = WebotsLauncher(
世界=os.路.加入(软件包目录, '世界';, 'my_world.wbt')
)
ros2_supervisor = Ros2SupervisorLauncher()
我的机器人驱动程序 = 节点(
包装='webots_ros2_driver',
可执行='司机';,
产量='屏幕';,
additional_env={'webots_controller_url';: 控制器URL前缀() + '我的机器人';},
参数=[
{'robot_description';: 机器人描述},
]
)
避障器 = 节点(
包装='my_package';,
可执行='obstacle_avoider';,
)
返回 启动说明([
网点,
我的机器人驱动程序,
ros2_supervisor,
避障器,
启动.行动.注册事件处理程序(
事件处理程序=启动.事件处理程序.进程结束时(
target_action=网点,
on_exit=[启动.行动.发射事件(事件=启动.活动.关闭())],
)
)
])
这将创建一个 避障器
节点,该节点将被包含在 启动说明
.
11 测试避障代码
如任务 7
在 ROS 2 工作区的终端启动模拟:
在 ROS 2 工作区的终端运行
Colcon build
source install/local_setup.bash
ros2 launch my_package robot_launch.py
在 WSL ROS 2 工作区的终端运行
Colcon build
export WEBOTS_HOME=/mnt/c/Program\ Files/Webots
source install/local_setup.bash
ros2 launch my_package robot_launch.py
请务必使用 /mnt
前缀,以便从 WSL 访问 Windows 文件系统。
如果尚未指定 Webots 安装文件夹,请在主机终端(而不是在容器中)指定该文件夹(例如:"......")。 /Applications/Webots.app
),并使用以下命令启动服务器:
export WEBOTS_HOME=/Applications/Webots.app
python3 local_simulation_server.py
请注意,一旦 ROS 2 节点结束,服务器将继续运行。您不需要每次启动新的模拟时都重新启动它。在 Docker 容器的终端,用以下命令构建并启动你的自定义软件包:
cd ~/ros2_ws
Colcon build
source install/local_setup.bash
ros2 launch my_package robot_launch.py
您的机器人应向前行驶,在撞墙之前应顺时针转动。您可以按 Ctrl+F10
或访问 查看
菜单 可选 效果图
和 显示 距离传感器 射线
来显示机器人距离传感器的测距范围。

摘要
在本教程中,您使用 Webots 建立了一个逼真的机器人模拟,实施了一个 Python 插件来控制机器人的电机,并使用传感器实施了一个 ROS 节点来避开障碍物。
下一步工作
您可能想改进插件或创建新节点,以改变机器人的行为。从以前的教程中汲取灵感可以是一个起点: