警告

您正在阅读的 ROS 2 文档版本已达到 EOL(生命周期结束),不再受官方支持。如果您想了解最新信息,请访问 Jazzy.

设置机器人模拟(基础)

目标 设置机器人仿真,并通过 ROS 2 进行控制。

辅导水平: 高级

时间 30 分钟

背景介绍

在本教程中,您将使用 Webots 机器人模拟器设置并运行一个非常简单的 ROS 2 模拟场景。

"(《世界人权宣言》) webots_ros2 软件包提供了 ROS 2 和 Webots 之间的接口。它包括多个子软件包,但在本教程中,您将只使用 webots_ros2_driver 子软件包来实现控制模拟机器人的 Python 或 C++ 插件。其他一些子软件包包含不同机器人的演示,如 TurtleBot3。它们在 Webots ROS 2 示例 page.

先决条件

建议先了解初级课程中涉及的 ROS 基本原理。 教程.特别是 使用 turtlesim、ros2 和 rqt, 了解主题, 创建工作区, 创建软件包创建启动文件 是有用的先决条件。

本教程中的 Linux 和 ROS 命令可在标准 Linux 终端中运行。以下页面 安装(Ubuntu) 解释了如何安装 webots_ros2 软件包。

任务

1 创建软件包结构

让我们在自定义 ROS 2 软件包中组织代码。创建一个名为 我的包来源 文件夹。将终端的当前目录更改为 ros2_ws/src 并运行:

ros2 pkg create --build-type ament_python --license Apache-2.0 --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 编辑 我的机器人驱动程序 插头

"(《世界人权宣言》) webots_ros2_driver 子软件包会自动为大多数传感器创建 ROS 2 接口。关于现有设备接口以及如何配置这些接口的更多详情,请参阅教程的第二部分: 设置机器人模拟(高级).在本任务中,您将通过创建自己的自定义插件来扩展该接口。自定义插件是一个 ROS 节点,相当于机器人控制器。您可以用它访问 Webots 机器人应用程序接口 并创建您自己的主题和服务来控制您的机器人。

备注

本教程的目的是展示一个使用最少依赖项的基本示例。不过,您可以通过使用另一个 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 实例的引用。

  • "(《世界人权宣言》) 属性 参数是由 URDF 文件中给出的 XML 标记创建的字典 (4 创建 my_robot.urdf 文件) 并允许您向控制器传递参数。

模拟出的机器人实例 self.__robot 可用于访问 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 文件,以声明 我的机器人驾驶员 插件。这将允许 webots_ros2_driver ROS 节点,以启动插件并将其连接到目标机器人。

my_package/resource 文件夹中创建一个名为 my_robot.urdf 有这些内容:

<?xml version="1.0" ?>;
机器人 名称"我的机器人";>;
    <webots>;
        <plugin type="my_package.my_robot_driver.MyRobotDriver"; />;
    </webots>;
</机器人>;

"(《世界人权宣言》) 类型 属性指定了文件分层结构给出的类的路径。 webots_ros2_driver 负责根据指定的软件包和模块加载类。

备注

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

备注

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

<plugin type="my_package.my_robot_driver.MyRobotDriver";>;
    <参数名称>;someValue</参数名称>;
/插件>;

即用于向现有 Webots 设备插件传递参数(参见 设置机器人模拟(高级)).

5 创建启动文件

让我们创建一个启动文件,只需一条命令就能轻松启动模拟和 ROS 控制器。在 my_package/launch 文件夹中新建一个名为 robot_launch.py 使用此代码:

舶来品 os
舶来品 pathlib
舶来品 启动
 launch_ros.actions 舶来品 节点
 启动 舶来品 启动说明
 ament_index_python.packages 舶来品 获取软件包共享目录
 webots_ros2_driver.webots_launcher 舶来品 WebotsLauncher
 webots_ros2_driver.utils 舶来品 控制器URL前缀


捍卫 生成发射描述():
    软件包目录 = 获取软件包共享目录('my_package';)
    机器人描述 = pathlib.路径(os..加入(软件包目录, '资源';, 'my_robot.urdf')).读取文本()

    网点 = WebotsLauncher(
        世界=os..加入(软件包目录, '世界';, 'my_world.wbt')
    )

    我的机器人驱动程序 = 节点(
        包装='webots_ros2_driver',
        可执行='司机';,
        产量='屏幕';,
        additional_env={'webots_controller_url';: 控制器URL前缀() + '我的机器人';},
        参数=[
            {'robot_description';: 机器人描述},
        ]
    )

    返回 启动说明([
        网点,
        我的机器人驱动程序,
        启动.行动.注册事件处理程序(
            事件处理程序=启动.事件处理程序.进程结束时(
                target_action=网点,
                on_exit=[启动.行动.发射事件(事件=启动.活动.关闭())],
            )
        )
    ])

"(《世界人权宣言》) WebotsLauncher 对象是一个自定义操作,可用于启动 Webots 模拟实例。您必须在构造函数中指定模拟器将打开哪个世界文件。

网点 = WebotsLauncher(
    世界=os..加入(软件包目录, '世界';, 'my_world.wbt')
)

然后,创建与模拟机器人交互的 ROS 节点。该节点名为 驱动程序位于 webots_ros2_driver 包装

该节点可通过基于 IPC 和共享内存的定制协议与模拟机器人进行通信。

在您的案例中,您只需运行该节点的一个实例,因为模拟中只有一个机器人。但如果模拟中有更多机器人,就必须为每个机器人运行一个节点实例。 webots_controller_url 用于定义驱动程序应连接的机器人名称。机器人名称 controller_url_prefix() 方法是强制性的,因为它允许 webots_ros2_driver 以根据平台添加正确的协议前缀。协议前缀 机器人描述 参数包含 URDF 文件的内容,该文件引用了 我的机器人驾驶员 插件。您可以看到 驱动程序 节点作为连接控制器插件和目标机器人的接口。

我的机器人驱动程序 = 节点(
    包装='webots_ros2_driver',
    可执行='司机';,
    产量='屏幕';,
    additional_env={'webots_controller_url';: 控制器URL前缀() + '我的机器人';},
    参数=[
        {'robot_description';: 机器人描述},
    ]
)

之后,这两个节点将被设置为在 启动说明 构造函数:

返回 启动说明([
    网点,
    我的机器人驱动程序,

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

启动.行动.注册事件处理程序(
    事件处理程序=启动.事件处理程序.进程结束时(
        target_action=网点,
        on_exit=[启动.行动.发射事件(事件=启动.活动.关闭())],
    )
)

备注

更多详情 webots_ros2_driverWebotsLauncher 可以发现 在节点参考页面上.

6 编辑附加文件

在启动启动文件之前,您必须修改 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 在第二个终端。

摘要

在本教程中,您使用 Webots 建立了一个逼真的机器人模拟,并实施了一个自定义插件来控制机器人的电机。

下一步工作

为了改进模拟效果,机器人的传感器可以用来探测障碍物并避开它们。教程的第二部分展示了如何实现这种行为: