警告

您正在阅读的 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 安装说明.

安装 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 和共享内存的定制协议与模拟机器人进行通信。

在您的案例中,您只需运行该节点的一个实例,因为模拟中只有一个机器人。但如果模拟中有更多机器人,就必须为每个机器人运行一个节点实例。 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.urdfrobot_launch.py.

7 测试代码

在 ROS 2 工作区的终端运行

Colcon build
source install/local_setup.bash
ros2 launch my_package robot_launch.py

这将启动模拟。如果 Webots 尚未安装,则将在首次运行时自动安装。

备注

如果您想手动安装 Webots,可以下载 这里.

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

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

机器人现在向前移动。

.././././_images/Robot_moving_forward.png

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

././././_images/Robot_colliding_wall.png

为了避免这种情况,让我们使用机器人的传感器来检测障碍物并避开它们。关闭 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

您的机器人应向前行驶,在撞墙之前应顺时针转动。您可以按 Ctrl+F10 或访问 查看 菜单 可选 效果图显示 距离传感器 射线 来显示机器人距离传感器的测距范围。

.././././_images/Robot_turning_clockwise.png

摘要

在本教程中,您使用 Webots 建立了一个逼真的机器人模拟,实施了一个 Python 插件来控制机器人的电机,并使用传感器实施了一个 ROS 节点来避开障碍物。

下一步工作

您可能想改进插件或创建新节点,以改变机器人的行为。从以前的教程中汲取灵感可以是一个起点: