编写新的 Planner 插件

Animated gif with gradient demo

概述

本教程展示如何创建您自己的规划器插件。

要求

  • ROS 2 (二进制或从源代码构建)

  • Nav2 (包括依赖项)

  • Gazebo

  • Turtlebot3

教程步骤

1- 创建一个新的 Planner 插件

我们将创建一个简单的直线规划器。 本教程中带注释的代码可以在 navigation_tutorials 存储库中找到,作为 nav2_straightline_planner。 此包可视为编写规划器插件的参考。

我们的示例插件继承自基类“nav2_core::GlobalPlanner”。基类提供了 5 种纯虚拟方法来实现规划器插件。规划器服务器将使用该插件来计算轨迹。 让我们进一步了解编写规划器插件所需的方法。

Virtual method

Method description

Requires override?

configure()

Method is called at when planner server enters on_configure state. Ideally this methods should perform declarations of ROS parameters and initialization of planner’s member variables. This method takes 4 input params: shared pointer to parent node, planner name, tf buffer pointer and shared pointer to costmap.

Yes

activate()

Method is called when planner server enters on_activate state. Ideally this method should implement operations which are necessary before planner goes to an active state.

Yes

deactivate()

Method is called when planner server enters on_deactivate state. Ideally this method should implement operations which are necessary before planner goes to an inactive state.

Yes

cleanup()

Method is called when planner server goes to on_cleanup state. Ideally this method should clean up resources which are created for the planner.

Yes

createPlan()

Method is called when planner server demands a global plan for specified start and goal pose. This method returns nav_msgs::msg::Path carrying global plan. This method takes 2 input params: start pose and goal pose.

Yes

在本教程中,我们将使用方法“StraightLine::configure()”和“StraightLine::createPlan()”来创建直线规划器。

在规划器中,“configure()”方法必须从 ROS 参数和任何所需的初始化中设置成员变量,

node_ = parent;
tf_ = tf;
name_ = name;
costmap_ = costmap_ros->getCostmap();
global_frame_ = costmap_ros->getGlobalFrameID();

// Parameter initialization
nav2_util::declare_parameter_if_not_declared(node_, name_ + ".interpolation_resolution", rclcpp::ParameterValue(0.1));
node_->get_parameter(name_ + ".interpolation_resolution", interpolation_resolution_);

这里,name_ + ".interpolation_resolution" 正在获取特定于我们的规划器的 ROS 参数 interpolation_resolution。Nav2 允许加载多个插件,为了保持井然有序,每个插件都映射到某个 ID/名称。现在,如果我们想要检索该特定插件的参数,我们使用 <mapped_name_of_plugin>.<name_of_parameter>,如上面的代码片段所示。例如,我们的示例规划器映射到名称“GridBased”,为了检索特定于“GridBased”的 interpolation_resolution 参数,我们使用了 Gridbased.interpolation_resolution。换句话说,GridBased 用作插件特定参数的命名空间。当我们讨论参数文件(或 params 文件)时,我们将对此有更多了解。

createPlan() 方法中,我们需要创建从给定的开始到目标姿势的路径。使用起始姿势和目标姿势调用 StraightLine::createPlan() 来解决全局路径规划问题。成功后,它将路径转换为 ​​``nav_msgs::msg::Path`` 并返回到规划器服务器。下面的注释显示了此方法的实现。

nav_msgs::msg::Path global_path;

// Checking if the goal and start state is in the global frame
if (start.header.frame_id != global_frame_) {
  RCLCPP_ERROR(
    node_->get_logger(), "Planner will only except start position from %s frame",
    global_frame_.c_str());
  return global_path;
}

if (goal.header.frame_id != global_frame_) {
  RCLCPP_INFO(
    node_->get_logger(), "Planner will only except goal position from %s frame",
    global_frame_.c_str());
  return global_path;
}

global_path.poses.clear();
global_path.header.stamp = node_->now();
global_path.header.frame_id = global_frame_;
// calculating the number of loops for current value of interpolation_resolution_
int total_number_of_loop = std::hypot(
  goal.pose.position.x - start.pose.position.x,
  goal.pose.position.y - start.pose.position.y) /
  interpolation_resolution_;
double x_increment = (goal.pose.position.x - start.pose.position.x) / total_number_of_loop;
double y_increment = (goal.pose.position.y - start.pose.position.y) / total_number_of_loop;

for (int i = 0; i < total_number_of_loop; ++i) {
  geometry_msgs::msg::PoseStamped pose;
  pose.pose.position.x = start.pose.position.x + x_increment * i;
  pose.pose.position.y = start.pose.position.y + y_increment * i;
  pose.pose.position.z = 0.0;
  pose.pose.orientation.x = 0.0;
  pose.pose.orientation.y = 0.0;
  pose.pose.orientation.z = 0.0;
  pose.pose.orientation.w = 1.0;
  pose.header.stamp = node_->now();
  pose.header.frame_id = global_frame_;
  global_path.poses.push_back(pose);
}

global_path.poses.push_back(goal);

return global_path;

其余方法未使用,但必须覆盖它们。根据规则,我们确实覆盖了所有方法,但将其留空。

2- 导出规划器插件

现在我们已经创建了自定义规划器,我们需要导出规划器插件,以便规划器服务器可以看到它。插件在运行时加载,如果它们不可见,那么我们的规划器服务器将无法加载它。在 ROS 2 中,导出和加载插件由“pluginlib”处理。

回到我们的教程,类“nav2_straightline_planner::StraightLine”被动态加载为“nav2_core::GlobalPlanner”,这是我们的基类。

  1. 要导出规划器,我们需要提供两行

#include "pluginlib/class_list_macros.hpp"
PLUGINLIB_EXPORT_CLASS(nav2_straightline_planner::StraightLine, nav2_core::GlobalPlanner)

请注意,它需要 pluginlib 来导出插件的类。Pluginlib 将提供宏“PLUGINLIB_EXPORT_CLASS”,它完成导出的所有工作。

将这些行放在文件末尾是一种很好的做法,但从技术上讲,您也可以写在顶部。

  1. 下一步是在包的根目录中创建插件的描述文件。例如,我们教程包中的“global_planner_plugin.xml”文件。该文件包含以下信息

  • library path: Plugin’s library name and its location.

  • class name: Name of the class.

  • class type: Type of class.

  • base class: Name of the base class.

  • description: Description of the plugin.

<library path="nav2_straightline_planner_plugin">
  <class type="nav2_straightline_planner::StraightLine" base_class_type="nav2_core::GlobalPlanner">
    <description>This is an example plugin which produces straight path.</description>
  </class>
</library>
  1. 下一步是使用 cmake 函数“pluginlib_export_plugin_description_file()”导出“CMakeLists.txt”插件。此函数将插件描述文件安装到“share”目录并设置索引以使其可发现。

pluginlib_export_plugin_description_file(nav2_core global_planner_plugin.xml)
  1. 插件描述文件也应该添加到“package.xml”中

<export>
  <build_type>ament_cmake</build_type>
  <nav2_core plugin="${prefix}/global_planner_plugin.xml" />
</export>
  1. 编译并注册完成,接下来我们来使用这个插件。

3-通过 params 文件传递​​插件名称

要启用该插件,我们需要修改“nav2_params.yaml”文件,如下所示,以替换以下参数

Note

对于 Galactic 或更高版本,“plugin_names”和“plugin_types”已被替换为插件名称的单个“plugins”字符串向量。类型现在在“plugin:”字段中的“plugin_name”命名空间中定义(例如“plugin: MyPlugin::Plugin”)。代码块中的内联注释将帮助您完成此操作。

planner_server:
  ros__parameters:
    plugins: ["GridBased"]
    use_sim_time: True
    GridBased:
      plugin: "nav2_navfn_planner::NavfnPlanner" # For Foxy and later. In Iron and older versions, "/" was used instead of "::"
      tolerance: 2.0
      use_astar: false
      allow_unknown: true

with

planner_server:
  ros__parameters:
    plugins: ["GridBased"]
    use_sim_time: True
    GridBased:
      plugin: "nav2_straightline_planner::StraightLine"
      interpolation_resolution: 0.1

在上面的代码片段中,您可以观察到我们的“nav2_straightline_planner::StraightLine”规划器与其 id“GridBased”的映射。为了传递特定于插件的参数,我们使用了“<plugin_id>.<plugin_specific_parameter>”。

4- 运行 StraightLine 插件

运行启用 navigation2 的 Turtlebot3 模拟。详细的操作说明写在 入门指南。以下是快捷命令:

$ ros2 launch nav2_bringup tb3_simulation_launch.py params_file:=/path/to/your_params_file.yaml

然后转到 RViz,单击顶部的“2D 姿势估计”按钮,并指向地图上的位置,如 入门指南 中所述。机器人将在地图上定位,然后单击“Navigation2 目标”,然后单击您希望规划器考虑目标姿势的姿势。之后,规划器将规划路径,机器人将开始朝目标移动。