为Navigation 2创建自定义behavior tree plugin

2023-05-16

系列文章目录

思岚激光雷达rplidar从ROS 1到ROS 2的移植
ROS 2下navigation 2 stack的构建
订阅rviz2的导航目标位置消息“/goal_pose”
打断behavior tree的异步动作节点,并执行其他节点动作


文章目录

  • 系列文章目录
  • 背景
  • 一. 实现思想
  • 二. behavior tree plugins
  • 三. 基于lifecycleNode创建自定义action server
    • 1. 创建action server的package
    • 2. 构建自定义action plugin
    • 3. 构建自定义的action server
    • 4. 建立action server node
    • 5. 创建action plugin的描述文件
    • 6. 修改package.xml
    • 7. 修改CMakeLists.txt
    • 8. 编译并运行
    • 9. 利用ros2 lifecycle command line interface验证
    • 10. 关键环节说明
  • 四. 构建自定义behavior tree plugin
    • 1. 创建自定义的action nodes
    • 2. 创建behavior tree
    • 3. 修改package.xml
    • 4. 修改CMakeLists.txt
    • 5. 重载navigation_launch.py
    • 6. 重载bringup_launch.py
    • 7. 修改启动navigation stack的launch文件
    • 8. 编译
  • 五. 测试验证
  • 总结


背景

ROS 1下苇航智能的NaviBOT A0通过底盘驱动代码,利用actionLib的cancelGoal和setGoal实现导航目标在完成旋转运动后的内部流转。迁移到ROS 2的初期,NaviBOT A0依然沿用了ROS 1下的实现机制,但碰到了很多实际问题,运行效果相比ROS 1下要差,且需要底盘驱动代码自行维护旋转和导航之间的状态机,造成了代码的可维护性较差。ROS 2相比ROS 1其中的一个较大变化就是behavior tree状态控制,通过bt能够实现状态逻辑的便利扩展和修改,而不需要修改原有代码。因此,转而考虑利用bt机制实现对导航目标的原地旋转逻辑

ROS 2的behavior tree通过库BehaviorTree.CPP实现,其机制和概念是本文后续实现的基础,请参考官网链接:
https://www.behaviortree.dev/


一. 实现思想

navigation 2导航任务的逻辑是通过package “nav2_bt_navigator”控制和维护的,包括路径计算,脱困恢复等,该package基于behavior tree实现对导航任务的逻辑状态维护,详细内容参看官网链接:
https://navigation.ros.org/configuration/packages/configuring-bt-navigator.html
https://index.ros.org/p/nav2_bt_navigator/

通过查看其默认调用的bt,“navigate_w_replanning_and_recovery.xml”,可以发现在顺序执行的control node “NavigateWithReplanning”节点下的两个action nodes即为接收导航目标,计算路径,并开始遵循路径运行的导航过程,如下图:
在这里插入图片描述

由于该过程由顺序执行的control node控制,即PipelineSequence,所以完全可以在路径计算和遵循路径两个节点中间加入原地旋转调整导航目标航向角的过程,如下图:
在这里插入图片描述

TIP:利用behavior tree的可视化工具Groot可以实现对bt的预览和编辑。Groot的使用超出本内容范围,不在此累述,详细内容可以参看官网链接:
https://github.com/BehaviorTree/Groot

在nav2_bt_navigator的behavior tree中,所有的action node即上图中蓝色节点,为该package的plugin,因此,希望能实现面向导航目标的原地旋转功能,即是创建一个能够被nav2_bt_navigator加载的自定义plugin

二. behavior tree plugins

bt plugin本质是一个action server的client,运行navigation stack后,可以在终端输入命令“ros2 action list”罗列所有action server提供的action服务,再通过命令“ros2 action info /”查看该服务的服务端,如下图中服务“/spin”的服务端由“/recoveries_server”提供:
在这里插入图片描述

上图中服务“/spinGO”是基于plugin “spin”构建的原地旋转的action plugin,该服务只是简单的复制了spin的代码,只修改了plugin的ID为“spinGO”,运行后能看到该服务,但却没有服务端,即“Action servers: 0”,这是因为服务端“recoveries_server”只有三个注册服务,即“backup”,“wait”和“spin”,因此,自定义的bt plugin首先需要注册到服务端

recoveries_server是一个现成的服务端,自定义的plugin可以选择注册到该服务端,即参照“backup”,“wait”和“spin”三个plugins的实现方式,为recoveries_server创建一个自定义的plugin “spinGO”,这是最为便利和简单的方式。但是,从模块逻辑结构上看,原地旋转到导航目标点航向角的动作,不应该属于脱困恢复的逻辑。从更利于后续维护和拓展的角度出发,因此考虑创建一个类似于recoveries_server的服务端,同时具备动态加载plugin的能力,通过加载自定义的plugin模块来实现控制原地旋转的功能

三. 基于lifecycleNode创建自定义action server

参考nav2_recoveries server,该server用于机器人的脱困恢复,有三个plugins:Backup,Spin,Wait组成。Nav2_recoveries server采用了managed node的方式构建,该方式能够很好的适应launch的nodes组合运行,自动解决nodes间的运行依赖及顺序,manage nodes的详细说明,可以参考官网链接:
https://design.ros2.org/articles/node_lifecycle.html

如果采用简单省力的方式,是可以基于nav2_recoveries server构建自己的spin to orientate the goal插件,然后集成到nav2_recoveries server。苇航智能从功能逻辑和后期维护角度触发,选择构建和nav2_recoveries类似的action server,这样功能逻辑更加清晰,同时还能支持后期扩展的action plugin

由于过程中涉及到的内容相对较多,先将所有步骤完整罗列,再对关键环节做进一步的说明

1. 创建action server的package

在终端输入命令:

cd ~/dev_ws/src
ros2 pkg create --build-type ament_cmake motion_ctrl_btnav_action --dependencies rclcpp

创建完毕后,修改package.xml中版本、维护、版权等信息

2. 构建自定义action plugin

本说明的目标是构建使机器人在规划路径完成后,能够依据所规划的路径和当前航向角,进行原地旋转以调整到和规划路径一致的航向角上,因此,所构建的plugin的动作即是旋转。可以看到在nav2_msgs中有一个spin的action,该action也被nav2_recoveries作为脱困时的旋转动作。查看“Spin.action”,可以看到goal为目标yaw,结果反馈为旋转过程的耗时,过程反馈为已经旋转的角度,因此,该action完全可以满足即将构建的旋转动作,自定义的旋转plugin可以基于该action,如下图:
在这里插入图片描述

首先,需要创建plugin的基类

在“include/<package>/”目录下,建立文件“btnavAction.hpp”,添加如下代码:

/******************************************************************
base class of action plugin for navigation bt actions

Features:
- base class without ROS action
- base class with ROS action
- xxx

Written by Xinjue Zou, xinjue.zou.whi@gmail.com

GNU General Public License, check LICENSE for more information.
All text above must be included in any redistribution.

Changelog:
2020-10-30: Initial version
2020-xx-xx: xxx
******************************************************************/
#pragma once
#include "rclcpp/rclcpp.hpp"
#include "tf2_ros/transform_listener.h"
#include "tf2_ros/create_timer_ros.h"
#include "geometry_msgs/msg/twist.hpp"
#include "nav2_util/simple_action_server.hpp"
#include "nav2_costmap_2d/costmap_topic_collision_checker.hpp"


namespace btnav_actions
{

/**
 * @class action base
* @brief Abstract interface for recoveries to adhere to with pluginlib
 */
class btnavActionBase
{
public:
  using Ptr = std::shared_ptr<btnavActionBase>;

  /**
   * @brief Virtual destructor
   */
  virtual ~btnavActionBase() {}

  /**
   * @param  parent pointer to user's node
   * @param  name The name of this planner
   * @param  tf A pointer to a TF buffer
   * @param  costmap_ros A pointer to the costmap
   */
  virtual void configure(
    const rclcpp_lifecycle::LifecycleNode::SharedPtr parent,
    const std::string & name, std::shared_ptr<tf2_ros::Buffer> tf,
    std::shared_ptr<nav2_costmap_2d::CostmapTopicCollisionChecker> collision_checker) = 0;

  /**
   * @brief Method to cleanup resources used on shutdown.
   */
  virtual void cleanup() = 0;

  /**
   * @brief Method to active action and any threads involved in execution.
   */
  virtual void activate() = 0;

  /**
   * @brief Method to deactive action and any threads involved in execution.
   */
  virtual void deactivate() = 0;

  /**
   * @brief Method Execute action behavior
   * @param  name The name of this planner
   * @return true if successful, false is failed to execute fully
   */
  // TODO(stevemacenski) evaluate design for recoveries to not host
  // their own servers and utilize a action server exposed action.
  // virtual bool executeAction() = 0;
};


enum class Status : int8_t
{
  SUCCEEDED = 1,
  FAILED = 2,
  RUNNING = 3,
};

using namespace std::chrono_literals;  //NOLINT

template<typename ActionT>
class btnavAction : public btnavActionBase
{
public:
  using ActionServer = nav2_util::SimpleActionServer<ActionT, rclcpp_lifecycle::LifecycleNode>;

  btnavAction()
  : action_server_(nullptr),
    cycle_frequency_(10.0),
    enabled_(false)
  {
  }

  virtual ~btnavAction()
  {
  }

  // Derived classes can override this method to catch the command and perform some checks
  // before getting into the main loop. The method will only be called
  // once and should return SUCCEEDED otherwise behavior will return FAILED.
  virtual Status onRun(const std::shared_ptr<const typename ActionT::Goal> command) = 0;


  // This is the method derived classes should mainly implement
  // and will be called cyclically while it returns RUNNING.
  // Implement the behavior such that it runs some unit of work on each call
  // and provides a status. The action will finish once SUCCEEDED is returned
  // It's up to the derived class to define the final commanded velocity.
  virtual Status onCycleUpdate() = 0;

  // an opportunity for derived classes to do something on configuration
  // if they chose
  virtual void onConfigure()
  {
  }

  // an opportunity for derived classes to do something on cleanup
  // if they chose
  virtual void onCleanup()
  {
  }

  void configure(
    const rclcpp_lifecycle::LifecycleNode::SharedPtr parent,
    const std::string & name, std::shared_ptr<tf2_ros::Buffer> tf,
    std::shared_ptr<nav2_costmap_2d::CostmapTopicCollisionChecker> collision_checker) override
  {
    RCLCPP_INFO(parent->get_logger(), "Configuring %s", name.c_str());

    node_ = parent;
    action_name_ = name;
    tf_ = tf;

    node_->get_parameter("cycle_frequency", cycle_frequency_);
    node_->get_parameter("global_frame", global_frame_);
    node_->get_parameter("robot_base_frame", robot_base_frame_);
    node_->get_parameter("transform_tolerance", transform_tolerance_);

    action_server_ = std::make_shared<ActionServer>(
      node_, action_name_,
      std::bind(&btnavAction::execute, this));
      
    collision_checker_ = collision_checker;

    vel_pub_ = node_->create_publisher<geometry_msgs::msg::Twist>("cmd_vel", 1);

    onConfigure();
  }

  void cleanup() override
  {
    action_server_.reset();
    vel_pub_.reset();
    onCleanup();
  }

  void activate() override
  {
    RCLCPP_INFO(node_->get_logger(), "Activating %s", action_name_.c_str());

    vel_pub_->on_activate();
    enabled_ = true;
  }

  void deactivate() override
  {
    vel_pub_->on_deactivate();
    enabled_ = false;
  }

protected:
  rclcpp_lifecycle::LifecycleNode::SharedPtr node_;
  std::string action_name_;
  rclcpp_lifecycle::LifecyclePublisher<geometry_msgs::msg::Twist>::SharedPtr vel_pub_;
  std::shared_ptr<ActionServer> action_server_;
  std::shared_ptr<tf2_ros::Buffer> tf_;
  std::shared_ptr<nav2_costmap_2d::CostmapTopicCollisionChecker> collision_checker_;

  double cycle_frequency_;
  double enabled_;
  std::string global_frame_;
  std::string robot_base_frame_;
  double transform_tolerance_;

  // Clock
  rclcpp::Clock steady_clock_{RCL_STEADY_TIME};

  void execute()
  {
    RCLCPP_INFO(node_->get_logger(), "Attempting %s", action_name_.c_str());

    if (!enabled_)
    {
      RCLCPP_WARN(node_->get_logger(), "Called while inactive, ignoring request.");
      return;
    }

    if (onRun(action_server_->get_current_goal()) != Status::SUCCEEDED)
    {
      RCLCPP_INFO(node_->get_logger(), "Initial checks failed for %s", action_name_.c_str());
      action_server_->terminate_current();
      return;
    }

    // Log a message every second
    auto timer = node_->create_wall_timer(
      1s,
      [&]() {RCLCPP_INFO(node_->get_logger(), "%s running...", action_name_.c_str());});

    auto start_time = steady_clock_.now();

    // Initialize the ActionT result
    auto result = std::make_shared<typename ActionT::Result>();

    rclcpp::Rate loop_rate(cycle_frequency_);

    while (rclcpp::ok())
    {
      if (action_server_->is_cancel_requested())
      {
        RCLCPP_INFO(node_->get_logger(), "Canceling %s", action_name_.c_str());
        stopRobot();
        result->total_elapsed_time = steady_clock_.now() - start_time;
        action_server_->terminate_all(result);
        return;
      }

      if (action_server_->is_preempt_requested())
      {
        RCLCPP_ERROR(
          node_->get_logger(), "Received a preemption request for %s,"
          " however feature is currently not implemented. Aborting and stopping.",
          action_name_.c_str());
        stopRobot();
        result->total_elapsed_time = steady_clock_.now() - start_time;
        action_server_->terminate_current(result);
        return;
      }

      switch (onCycleUpdate())
      {
        case Status::SUCCEEDED:
      RCLCPP_INFO(node_->get_logger(), "%s completed successfully", action_name_.c_str());
          result->total_elapsed_time = steady_clock_.now() - start_time;
          action_server_->succeeded_current(result);
          return;

        case Status::FAILED:
          RCLCPP_WARN(node_->get_logger(), "%s failed", action_name_.c_str());
          result->total_elapsed_time = steady_clock_.now() - start_time;
          action_server_->terminate_current(result);
          return;

        case Status::RUNNING:

        default:
          loop_rate.sleep();
          break;
      }
    }
  }

  void stopRobot()
  {
    auto cmd_vel = std::make_unique<geometry_msgs::msg::Twist>();
    cmd_vel->linear.x = 0.0;
    cmd_vel->linear.y = 0.0;
    cmd_vel->angular.z = 0.0;

    vel_pub_->publish(std::move(cmd_vel));
  }
};

}  // namespace btnav_actions

其次,创建具体的action plugin

在plugins中创建文件“spinGoalOrientation.hpp”,“spinGoalOrientation.cpp”,添加如下代码:

HPP

/******************************************************************
spin to target yaw action plugin for navigation bt actions

Features:
- spin to target yaw with collision check
- publish cmd_vel message
- xxx

Written by Xinjue Zou, xinjue.zou.whi@gmail.com

GNU General Public License, check LICENSE for more information.
All text above must be included in any redistribution.

Changelog:
2020-10-30: Initial version
2020-xx-xx: xxx
******************************************************************/
#pragma once
#include "motion_ctrl_btnav_action/btnavAction.hpp"
#include "nav2_msgs/action/spin.hpp"
#include "geometry_msgs/msg/quaternion.hpp"

namespace btnav_action_server
{
using SpinGOAction = nav2_msgs::action::Spin;

class SpinGO : public btnav_actions::btnavAction<SpinGOAction>
{
public:
  SpinGO();
  ~SpinGO();

  btnav_actions::Status onRun(const std::shared_ptr<const SpinGOAction::Goal> command) override;
  void onConfigure() override;
  btnav_actions::Status onCycleUpdate() override;

protected:
  bool isCollisionFree(
    const double& distance,
    geometry_msgs::msg::Twist* cmd_vel,
    geometry_msgs::msg::Pose2D& pose2d);

  SpinGOAction::Feedback::SharedPtr feedback_;

  double rotate_in_place_vel_;
  double cmd_yaw_;
  double prev_yaw_;
  double relative_yaw_;
  double simulate_ahead_time_;
};

}  // namespace btnav_action_server

CPP

/******************************************************************
spin to target yaw action plugin for navigation bt actions

Features:
- spin to target yaw with collision check
- publish cmd_vel message
- xxx

Written by Xinjue Zou, xinjue.zou.whi@gmail.com

GNU General Public License, check LICENSE for more information.
All text above must be included in any redistribution.

******************************************************************/
#include "nav2_util/node_utils.hpp"
#include "tf2_geometry_msgs/tf2_geometry_msgs.h"
#include "tf2/utils.h"
#include "spinGoalOrientation.hpp"

using namespace std::chrono_literals;

namespace btnav_action_server
{

SpinGO::SpinGO()
: btnavAction<SpinGOAction>(),
  feedback_(std::make_shared<SpinGOAction::Feedback>()),
  prev_yaw_(0.0)
{
}

SpinGO::~SpinGO()
{
}

void SpinGO::onConfigure()
{
  nav2_util::declare_parameter_if_not_declared(
    node_,
    "simulate_ahead_time", rclcpp::ParameterValue(2.0));
  node_->get_parameter("simulate_ahead_time", simulate_ahead_time_);

  nav2_util::declare_parameter_if_not_declared(
    node_,
    "rotate_in_place_vel", rclcpp::ParameterValue(2.0));
  node_->get_parameter("rotate_in_place_vel", rotate_in_place_vel_);
}

btnav_actions::Status SpinGO::onRun(const std::shared_ptr<const SpinGOAction::Goal> command)
{
  geometry_msgs::msg::PoseStamped current_pose;
  if (!nav2_util::getCurrentPose(
      current_pose, *tf_, global_frame_, robot_base_frame_,
      transform_tolerance_))
  {
    RCLCPP_ERROR(node_->get_logger(), "Current robot pose is not available.");
    return btnav_actions::Status::FAILED;
  }

  prev_yaw_ = tf2::getYaw(current_pose.pose.orientation);
  relative_yaw_ = 0.0;

  cmd_yaw_ = command->target_yaw;
  RCLCPP_INFO(
    node_->get_logger(), "Turning %0.2f for spin recovery.",
    cmd_yaw_);
  return btnav_actions::Status::SUCCEEDED;
}

btnav_actions::Status SpinGO::onCycleUpdate()
{
  geometry_msgs::msg::PoseStamped current_pose;
  if (!nav2_util::getCurrentPose(
      current_pose, *tf_, global_frame_, robot_base_frame_,
      transform_tolerance_))
  {
    RCLCPP_ERROR(node_->get_logger(), "Current robot pose is not available.");
    return btnav_actions::Status::FAILED;
  }

  const double current_yaw = tf2::getYaw(current_pose.pose.orientation);

  double delta_yaw = current_yaw - prev_yaw_;
  if (abs(delta_yaw) > M_PI) {
    delta_yaw = copysign(2 * M_PI - abs(delta_yaw), prev_yaw_);
  }

  relative_yaw_ += delta_yaw;
  prev_yaw_ = current_yaw;

  feedback_->angular_distance_traveled = relative_yaw_;
  action_server_->publish_feedback(feedback_);

  double remaining_yaw = abs(cmd_yaw_) - abs(relative_yaw_);
  if (remaining_yaw <= 0)
  {
    stopRobot();
    return btnav_actions::Status::SUCCEEDED;
  }

  auto cmd_vel = std::make_unique<geometry_msgs::msg::Twist>();
  cmd_vel->angular.z = cmd_yaw_;

  geometry_msgs::msg::Pose2D pose2d;
  pose2d.x = current_pose.pose.position.x;
  pose2d.y = current_pose.pose.position.y;
  pose2d.theta = tf2::getYaw(current_pose.pose.orientation);

  if (!isCollisionFree(relative_yaw_, cmd_vel.get(), pose2d))
  {
    stopRobot();
    RCLCPP_WARN(node_->get_logger(), "Collision Ahead - Exiting Spin");
    return btnav_actions::Status::SUCCEEDED;
  }

  vel_pub_->publish(std::move(cmd_vel));

  return btnav_actions::Status::RUNNING;
}

bool SpinGO::isCollisionFree(
  const double& relative_yaw,
  geometry_msgs::msg::Twist* cmd_vel,
  geometry_msgs::msg::Pose2D& pose2d)
{
  // Simulate ahead by simulate_ahead_time_ in cycle_frequency_ increments
  int cycle_count = 0;
  double sim_position_change;
  const int max_cycle_count = static_cast<int>(cycle_frequency_ * simulate_ahead_time_);

  while (cycle_count < max_cycle_count)
  {
    sim_position_change = cmd_vel->angular.z * (cycle_count / cycle_frequency_);
    pose2d.theta += sim_position_change;
    cycle_count++;

    if (abs(relative_yaw) - abs(sim_position_change) <= 0.)
    {
      break;
    }

    if (!collision_checker_->isCollisionFree(pose2d))
    {
      return false;
    }
  }
  return true;
}

}  // namespace btnav_action_server

#include "pluginlib/class_list_macros.hpp"
PLUGINLIB_EXPORT_CLASS(btnav_action_server::SpinGO, btnav_actions::btnavActionBase)

3. 构建自定义的action server

分别在package的“src/”和“include/<package>/”目录下创建文件“action_server.cpp”,“action_server.hpp”,添加如下代码:

HPP

/******************************************************************
lifecycleNode action server for navigation bt actions

Features:
- lifecycleNode action server
- dynamic and configurable custom action plugins
- xxx

Written by Xinjue Zou, xinjue.zou.whi@gmail.com

GNU General Public License, check LICENSE for more information.
All text above must be included in any redistribution.

Changelog:
2020-10-30: Initial version
2020-xx-xx: xxx
******************************************************************/
#pragma once
#include "nav2_util/lifecycle_node.hpp"
#include "pluginlib/class_loader.hpp"
#include "pluginlib/class_list_macros.hpp"
#include "btnavAction.hpp"

namespace btnav_action_server
{

class actionServer : public nav2_util::LifecycleNode
{
public:
	actionServer();
	~actionServer();
	
	void loadPlugins();

protected:
	// Implement the lifecycle interface
	nav2_util::CallbackReturn on_configure(const rclcpp_lifecycle::State& state) override;
	nav2_util::CallbackReturn on_activate(const rclcpp_lifecycle::State& state) override;
	nav2_util::CallbackReturn on_deactivate(const rclcpp_lifecycle::State& state) override;
	nav2_util::CallbackReturn on_cleanup(const rclcpp_lifecycle::State& state) override;
	nav2_util::CallbackReturn on_shutdown(const rclcpp_lifecycle::State& state) override;
	
	std::shared_ptr<tf2_ros::Buffer> tf_;
	std::shared_ptr<tf2_ros::TransformListener> transform_listener_;
	
	// Plugins
	std::vector<pluginlib::UniquePtr<btnav_actions::btnavActionBase>> actions_;
	pluginlib::ClassLoader<btnav_actions::btnavActionBase> plugin_loader_;
	std::vector<std::string> default_ids_;
	std::vector<std::string> default_types_;
	std::vector<std::string> action_ids_;
	std::vector<std::string> action_types_;
	
	// Utilities
	std::unique_ptr<nav2_costmap_2d::CostmapSubscriber> costmap_sub_;
	std::unique_ptr<nav2_costmap_2d::FootprintSubscriber> footprint_sub_;
	std::shared_ptr<nav2_costmap_2d::CostmapTopicCollisionChecker> collision_checker_;
	double transform_tolerance_;
};

}  // namespace btnav_action_server

CPP

/******************************************************************
lifecycleNode action server for navigation bt actions

Features:
- lifecycleNode action server
- dynamic and configurable custom action plugins
- xxx

Written by Xinjue Zou, xinjue.zou.whi@gmail.com

GNU General Public License, check LICENSE for more information.
All text above must be included in any redistribution.

******************************************************************/
#include "nav2_util/node_utils.hpp"
#include "motion_ctrl_btnav_action/action_server.hpp"

namespace btnav_action_server
{

actionServer::actionServer()
	: LifecycleNode("btnav_action_server", "", true),
	  plugin_loader_("motion_ctrl_btnav_action", "btnav_actions::btnavActionBase"),
	  default_ids_{"SpinGO"},
	  default_types_{"btnav_action_server/SpinGO"}
{
	declare_parameter(
		"costmap_topic",
		rclcpp::ParameterValue(std::string("local_costmap/costmap_raw")));
	declare_parameter(
		"footprint_topic",
		rclcpp::ParameterValue(std::string("local_costmap/published_footprint")));
	declare_parameter("cycle_frequency", rclcpp::ParameterValue(10.0));
	declare_parameter("btnav_action_plugins", default_ids_);
	
	declare_parameter(
		"global_frame",
		rclcpp::ParameterValue(std::string("odom")));
	declare_parameter(
		"robot_base_frame",
		rclcpp::ParameterValue(std::string("base_link")));
	declare_parameter(
		"transform_tolerance",
		rclcpp::ParameterValue(0.1));
}

actionServer::~actionServer()
{
}

nav2_util::CallbackReturn actionServer::on_configure(const rclcpp_lifecycle::State& /*state*/)
{
	RCLCPP_INFO(get_logger(), "Configuring");
	
	tf_ = std::make_shared<tf2_ros::Buffer>(get_clock());
	auto timer_interface = std::make_shared<tf2_ros::CreateTimerROS>(
		get_node_base_interface(),
		get_node_timers_interface());
	tf_->setCreateTimerInterface(timer_interface);
	transform_listener_ = std::make_shared<tf2_ros::TransformListener>(*tf_);
	
	std::string costmap_topic, footprint_topic;
	get_parameter("costmap_topic", costmap_topic);
	get_parameter("footprint_topic", footprint_topic);
	get_parameter("transform_tolerance", transform_tolerance_);
	costmap_sub_ = std::make_unique<nav2_costmap_2d::CostmapSubscriber>(
		shared_from_this(), costmap_topic);
	footprint_sub_ = std::make_unique<nav2_costmap_2d::FootprintSubscriber>(
		shared_from_this(), footprint_topic, 1.0);
	
	std::string global_frame, robot_base_frame;
	get_parameter("global_frame", global_frame);
	get_parameter("robot_base_frame", robot_base_frame);
	collision_checker_ = std::make_shared<nav2_costmap_2d::CostmapTopicCollisionChecker>(
		*costmap_sub_, *footprint_sub_, *tf_, this->get_name(),
		global_frame, robot_base_frame, transform_tolerance_);
	
	get_parameter("btnav_action_plugins", action_ids_);
	if (action_ids_ == default_ids_)
	{
		for (size_t i = 0; i < default_ids_.size(); ++i)
		{
			declare_parameter(default_ids_[i] + ".plugin", default_types_[i]);
		}
	}
	action_types_.resize(action_ids_.size());
	loadPlugins();
	
	return nav2_util::CallbackReturn::SUCCESS;
}


void actionServer::loadPlugins()
{
	auto node = shared_from_this();
	
	for (size_t i = 0; i != action_ids_.size(); i++)
	{
		action_types_[i] = nav2_util::get_plugin_type_param(node, action_ids_[i]);
		try
		{
			RCLCPP_INFO(
				get_logger(), "Creating btnav action plugin %s of type %s",
				action_ids_[i].c_str(), action_types_[i].c_str());
			actions_.push_back(plugin_loader_.createUniqueInstance(action_types_[i]));
			actions_.back()->configure(node, action_ids_[i], tf_, collision_checker_);
		}
		catch (const pluginlib::PluginlibException & ex)
		{
			RCLCPP_FATAL(
				get_logger(), "Failed to create btnav action %s of type %s."
				" Exception: %s", action_ids_[i].c_str(), action_types_[i].c_str(),
			ex.what());
			exit(-1);
		}
	}
}

nav2_util::CallbackReturn actionServer::on_activate(const rclcpp_lifecycle::State& /*state*/)
{
	RCLCPP_INFO(get_logger(), "Activating");
	std::vector<pluginlib::UniquePtr<btnav_actions::btnavActionBase>>::iterator iter;
	for (iter = actions_.begin(); iter != actions_.end(); ++iter)
	{
		(*iter)->activate();
	}
	
	return nav2_util::CallbackReturn::SUCCESS;
}

nav2_util::CallbackReturn actionServer::on_deactivate(const rclcpp_lifecycle::State& /*state*/)
{
	RCLCPP_INFO(get_logger(), "Deactivating");
	
	std::vector<pluginlib::UniquePtr<btnav_actions::btnavActionBase>>::iterator iter;
	for (iter = actions_.begin(); iter != actions_.end(); ++iter)
	{
		(*iter)->deactivate();
	}
	
	return nav2_util::CallbackReturn::SUCCESS;
}

nav2_util::CallbackReturn actionServer::on_cleanup(const rclcpp_lifecycle::State& /*state*/)
{
	RCLCPP_INFO(get_logger(), "Cleaning up");
	
	std::vector<pluginlib::UniquePtr<btnav_actions::btnavActionBase>>::iterator iter;
	for (iter = actions_.begin(); iter != actions_.end(); ++iter)
	{
		(*iter)->cleanup();
	}
	
	actions_.clear();
	transform_listener_.reset();
	tf_.reset();
	footprint_sub_.reset();
	costmap_sub_.reset();
	collision_checker_.reset();
	
	return nav2_util::CallbackReturn::SUCCESS;
}

nav2_util::CallbackReturn actionServer::on_shutdown(const rclcpp_lifecycle::State&)
{
	RCLCPP_INFO(get_logger(), "Shutting down");
	return nav2_util::CallbackReturn::SUCCESS;
}

}  // end namespace btnav_action_server

4. 建立action server node

自定义的action server以独立进程的形式存在,因此需要建立其node,在package的“src/”目录下创建文件“main.cpp”,添加如下代码:

/******************************************************************
lifecycleNode action server node

Features:
- lifecycleNode action server node
- xxx

Written by Xinjue Zou, xinjue.zou.whi@gmail.com

GNU General Public License, check LICENSE for more information.
All text above must be included in any redistribution.

Changelog:
2020-10-30: Initial version
2020-xx-xx: xxx
******************************************************************/
#include "rclcpp/rclcpp.hpp"
#include "motion_ctrl_btnav_action/action_server.hpp"

int main(int argc, char ** argv)
{
	rclcpp::init(argc, argv);
	auto actions_node = std::make_shared<btnav_action_server::actionServer>();
	
	rclcpp::spin(actions_node->get_node_base_interface());
	rclcpp::shutdown();
	
	return 0;
}

5. 创建action plugin的描述文件

在package的根目录创建文件“btnav_action_plugin.xml”,添加如下内容:

<class_libraries>
	<library path="btnav_action_spinGO">
	  <class name="btnav_action_server/SpinGO" type="btnav_action_server::SpinGO" base_class_type="btnav_actions::btnavActionBase">
	    <description></description>
	  </class>
	</library>
</class_libraries>

6. 修改package.xml

在原来基础上增加lib的依赖,以及plugin.xml文件的导出,添加如下代码:

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>motion_ctrl_btnav_action</name>
  <version>0.0.1</version>
  <description>plugins for bt_navigator</description>
  <maintainer email="xinjue.zou.whi@gmail.com">Xinjue</maintainer>
  <license>BSD</license>

  <buildtool_depend>ament_cmake</buildtool_depend>
  
  <depend>rclcpp</depend>
  <depend>rclcpp_action</depend>
  <depend>rclcpp_lifecycle</depend>
  <depend>nav2_util</depend>
  <depend>nav2_costmap_2d</depend>
  <depend>nav2_msgs</depend>
  <depend>pluginlib</depend>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
    <motion_ctrl_btnav_action plugin="${prefix}/btnav_action_plugin.xml" />
  </export>
</package>

7. 修改CMakeLists.txt

也是在原来的基础上添加lib的依赖,自定义plugin的编译目标,以及自定义plugin的导出,添加如下代码:

cmake_minimum_required(VERSION 3.5)
project(motion_ctrl_btnav_action)

# Default to C99
if(NOT CMAKE_C_STANDARD)
  set(CMAKE_C_STANDARD 99)
endif()

# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 14)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_action REQUIRED)
find_package(rclcpp_lifecycle REQUIRED)
find_package(nav2_util REQUIRED)
find_package(nav2_costmap_2d REQUIRED)
find_package(nav2_msgs REQUIRED)
find_package(pluginlib REQUIRED)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)

include_directories(
  include
)

set(library_name btnav_action_server_core)
set(executable_name btnav_action_server)

set(dependencies
  rclcpp
  rclcpp_action
  rclcpp_lifecycle
  nav2_util
  nav2_costmap_2d
  nav2_msgs
  pluginlib)
  
# plugins
add_library(btnav_action_spinGO SHARED
  plugins/spinGoalOrientation.cpp
)

ament_target_dependencies(btnav_action_spinGO
  ${dependencies}
)

pluginlib_export_plugin_description_file(motion_ctrl_btnav_actionbtnav_action_plugin.xml)

# library
add_library(${library_name}
  src/action_server.cpp)

ament_target_dependencies(${library_name}
  ${dependencies})

# executable
add_executable(${executable_name}
  src/main.cpp)
    
target_link_libraries(${executable_name} ${library_name})

ament_target_dependencies(${executable_name}
  ${dependencies})
  
install(TARGETS ${library_name}
                btnav_action_spinGO
  ARCHIVE DESTINATION lib
  LIBRARY DESTINATION lib
  RUNTIME DESTINATION bin
)

install(TARGETS ${executable_name}
  RUNTIME DESTINATION lib/${PROJECT_NAME}
)

install(DIRECTORY include/
  DESTINATION include/
)

install(FILES btnav_action_plugin.xml
  DESTINATION share/${PROJECT_NAME}
)

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # uncomment the line when a copyright and license is not present in all source files
  #set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # uncomment the line when this package is not in a git repo
  #set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()

8. 编译并运行

在终端输入命令:

cd ~/dev_ws
colcon build --packages-select motion_ctrl_btnav_action
ros2 run motion_ctrl_btnav_action btnav_action_server

成功运行后如下图:
在这里插入图片描述

9. 利用ros2 lifecycle command line interface验证

ros2 lifecycle command line interface的详细说明可以参考官网链接:
https://index.ros.org/p/lifecycle/

在终端中先运行btnav_action_server,然后打开一个新终端,输入lifecycle command line interface验证action server

查看btnav_action_server当前可用状态:
在终端输入命令:

ros2 lifecycle list btnav_action_server

将显示自定义action server当前可用的状态,比如当前可以transit的两个状态“configure”和“shutdown”,如下图:
在这里插入图片描述

查看btnav_action_server当前状态:
在终端输入命令:

ros2 lifecycle get /btnav_action_server

将显示自定义action server当前的状态,比如当前状态“unconfigured”,如下图:
在这里插入图片描述

查看状态迁移的ID:
在终端输入命令:

ros2 interface show lifecycle_msgs/msg/Transition

将显示自定义action server可迁移状态的ID,如下图:
在这里插入图片描述

执行configure:
在终端输入命令:

ros2 lifecycle set /btnav_action_server configure

将显示状态迁移成功,同时,运行action server终端显示创建并正在配置自定义的插件“spinGO”,如下图:
在这里插入图片描述
在这里插入图片描述

激活插件:
通过上述查看到的可迁移状态ID,比如“activate=3”,尝试切换状态到active,在终端输入命令:

ros2 service call /btnav_action_server/change_state lifecycle_msgs/srv/ChangeState "{transition: {id: 3}}"

将显示状态迁移成功,同时,运行action server终端显示正在激活自定义的插件“spinGO”,如下图:
在这里插入图片描述
在这里插入图片描述

10. 关键环节说明

navigation 2官网,有自定义plugin的说明,涉及到了costmap2D,planner,controller,behavior tree,recoveries,详细内容及其对应概念可以参考官网链接:
https://navigation.ros.org/plugin_tutorials/index.html

但涉及到具体实施环节,有些关键点的说明不是特别明确,尤其是自定义plugin在各个环节的识别和衔接,苇航智能将重点对该部分做说明

action server能够动态的识别plugins并对其进行调用,依赖于plugin的描述文件<xxx_plugin>.xml文件,如下图:
在这里插入图片描述

该文件是action server所需要动态装载plugins的描述,包含了plugins的名称,类名称,以及基类名称,如下图:
在这里插入图片描述

该文件被读取依赖于package.xml中对该文件路径的申明以及CMakeLists中对该文件的导出,通常将原文件存储于package根目下,并导出到“install<package>/share/<package>”目录下,如下图:
在这里插入图片描述

该文件中所声明plugins,在action server动态装载时的识别由自定义的action server中plugin load定义,如下图:
在这里插入图片描述

上述动态加载的识别如果出现不匹配的情况,则可能会出现下图中不能正确加载插件的错误,如下图:
在这里插入图片描述

如果插件描述文件plugin.xml没有正确导出,或者路径有误则会出现下图中不能识别插件声明类型的错误:
在这里插入图片描述

NOTE:注意观察最后语句“Declared types are ”,在未正确找到plugin.xml和找到情况下的区别

四. 构建自定义behavior tree plugin

1. 创建自定义的action nodes

需要注意区别的是:这里的action nodes不是通常ROS概念中的node,而是behavior tree里的action node 概念,关于behavior tree的基本概念,请参考官网链接:
https://www.behaviortree.dev/

本说明基于苇航智能的导航package,即“motion_ctrl_nav”,创建action nodes,也可以单独创建package。在“motion_ctrl_nav”的根目录下,创建文件夹“plugins”,用于存储behavior tree的plugins,如下图:
在这里插入图片描述

分别在目录“include/<package>”和“plugins”中创建文件“spin_goal_orientation_action.hpp”和“spin_goal_orientation_action.cpp”,添加如下代码:

HPP

/******************************************************************
spin to the goal orientation, an action plugin for bt_navigator

Features:
- get current position from blackboard
- calculate spin dist toward the navigating goal
- interact with ROS 2 action interfaces
- xxx

Written by Xinjue Zou, xinjue.zou@outlook.com

GNU General Public License, check LICENSE for more information.
All text above must be included in any redistribution.

Changelog:
2020-11-01: Initial version
2020-xx-xx: xxx
******************************************************************/
#pragma once
#include <string>
#include "nav2_behavior_tree/bt_action_node.hpp"
#include "nav2_msgs/action/spin.hpp"
#include "behaviortree_cpp_v3/action_node.h"

namespace nav2_behavior_tree
{

class SpinGOAction : public BtActionNode<nav2_msgs::action::Spin>
{
public:
  SpinGOAction(
    const std::string & xml_tag_name,
    const std::string & action_name,
    const BT::NodeConfiguration & conf);

  void on_tick() override;
  
  static BT::PortsList providedPorts()
  {
    return providedBasicPorts(
      {
        BT::InputPort<double>("spin_dist", 1.57, "Spin distance")
      });
  }
};

}  // namespace nav2_behavior_tree

CPP

/******************************************************************
spin to the goal orientation, an action plugin for bt_navigator

Features:
- get current position from blackboard
- calculate spin dist toward the navigating goal
- interact with ROS 2 action interfaces
- xxx

Written by Xinjue Zou, xinjue.zou@outlook.com

GNU General Public License, check LICENSE for more information.
All text above must be included in any redistribution.

******************************************************************/
#include "motion_ctrl_nav/plugins/action/spin_goal_orientation_action.hpp"
#include "geometry_msgs/msg/quaternion.hpp"
#include "tf2/LinearMath/Quaternion.h"
#include "tf2_geometry_msgs/tf2_geometry_msgs.h"

namespace nav2_behavior_tree
{

SpinGOAction::SpinGOAction(
    const std::string & xml_tag_name,
    const std::string & action_name,
    const BT::NodeConfiguration & conf)
  : BtActionNode<nav2_msgs::action::Spin>(xml_tag_name, action_name, conf)
{
}

void SpinGOAction::on_tick()
{
    RCLCPP_INFO(node_->get_logger(), "spinnnnnnnnnnnnnnnnnnnnnnnnnnn");
}

}  // namespace nav2_behavior_tree

#include "behaviortree_cpp_v3/bt_factory.h"
BT_REGISTER_NODES(factory)
{
    BT::NodeBuilder builder =
        [](const std::string & name, const BT::NodeConfiguration & config)
        {
            return std::make_unique<nav2_behavior_tree::SpinGOAction>(name, "SpinGO", config);
        };

    factory.registerBuilder<nav2_behavior_tree::SpinGOAction>("SpinGO", builder);
}

2. 创建behavior tree

在package的根目录创建文件夹“behavior_trees”,在该目录下继续创建文件“navigate_w_replanning_and_recovery_spin_goal.xml”,添加如下代码:

<!--
  This Behavior Tree replans the global path periodically at 1 Hz and it also has
  recovery actions.
-->
<root main_tree_to_execute="MainTree">
  <BehaviorTree ID="MainTree">
    <RecoveryNode number_of_retries="6" name="NavigateRecovery">
      <PipelineSequence name="NavigateWithReplanning">
        <RateController hz="1.0">
          <RecoveryNode number_of_retries="1" name="ComputePathToPose">
            <ComputePathToPose goal="{goal}" path="{path}" planner_id="GridBased"/>
            <ClearEntireCostmap name="ClearGlobalCostmap-Context" service_name="global_costmap/clear_entirely_global_costmap"/>
          </RecoveryNode>
        </RateController>
        <SpinGO/>
        <!--<Spin spin_dist="1.57"/>-->
        <RecoveryNode number_of_retries="1" name="FollowPath">
          <FollowPath path="{path}" controller_id="FollowPath"/>
          <ClearEntireCostmap name="ClearLocalCostmap-Context" service_name="local_costmap/clear_entirely_local_costmap"/>
        </RecoveryNode>
      </PipelineSequence>
      <ReactiveFallback name="RecoveryFallback">
        <GoalUpdated/>
        <SequenceStar name="RecoveryActions">
          <ClearEntireCostmap name="ClearLocalCostmap-Subtree" service_name="local_costmap/clear_entirely_local_costmap"/>
          <ClearEntireCostmap name="ClearGlobalCostmap-Subtree" service_name="global_costmap/clear_entirely_global_costmap"/>
          <Spin spin_dist="1.57"/>
          <Wait wait_duration="5"/>
        </SequenceStar>
      </ReactiveFallback>
    </RecoveryNode>
  </BehaviorTree>
</root>

该behavior tree修改于navigation 2默认的behavior tree文件“navigate_w_replanning_and_recovery.xml”,区别在于在“PipelineSequence”的两个子节点中插入了自定义action node的节点“SpinGO”

3. 修改package.xml

在原来基础上增加lib的依赖,添加如下代码:

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>motion_ctrl_nav</name>
  <version>0.0.1</version>
  <description>navigation on motion_ctrl</description>
  <maintainer email="xinjue.zou.whi@gmail.com">Xinjue</maintainer>
  <license>BSD</license>

  <buildtool_depend>ament_cmake</buildtool_depend>
  
  <depend>rclcpp</depend>
  <depend>rclcpp_action</depend>
  <depend>rclcpp_lifecycle</depend>
  <depend>behaviortree_cpp_v3</depend>
  <depend>builtin_interfaces</depend>
  <depend>geometry_msgs</depend>
  <depend>nav2_msgs</depend>
  <depend>nav_msgs</depend>
  <depend>tf2</depend>
  <depend>tf2_ros</depend>
  <depend>tf2_geometry_msgs</depend>
  <depend>std_msgs</depend>
  <depend>std_srvs</depend>
  <depend>nav2_util</depend>
  <depend>lifecycle_msgs</depend>
  <depend>nav2_common</depend>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

4. 修改CMakeLists.txt

也是在原来的基础上添加lib的依赖,自定义plugin的编译目标,以及自定义plugin的导出,添加如下代码:

cmake_minimum_required(VERSION 3.5)
project(motion_ctrl_nav)

# Default to C99
if(NOT CMAKE_C_STANDARD)
  set(CMAKE_C_STANDARD 99)
endif()

# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 14)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(nav2_common REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_action REQUIRED)
find_package(rclcpp_lifecycle REQUIRED)
find_package(std_msgs REQUIRED)
find_package(builtin_interfaces REQUIRED)
find_package(geometry_msgs REQUIRED)
find_package(nav2_msgs REQUIRED)
find_package(nav_msgs REQUIRED)
find_package(behaviortree_cpp_v3 REQUIRED)
find_package(tf2_ros REQUIRED)
find_package(tf2_geometry_msgs REQUIRED)
find_package(std_msgs REQUIRED)
find_package(std_srvs REQUIRED)
find_package(nav2_util REQUIRED)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)

include_directories(
  include
)

set(dependencies
  rclcpp
  rclcpp_action
  rclcpp_lifecycle
  geometry_msgs
  nav2_msgs
  nav_msgs
  behaviortree_cpp_v3
  tf2
  tf2_ros
  tf2_geometry_msgs
  std_msgs
  std_srvs
  nav2_util
)
    
add_library(nav2_spinGO_action_bt_node SHARED plugins/action/spin_goal_orientation_action.cpp)
list(APPEND plugin_libs nav2_spinGO_action_bt_node)

foreach(bt_plugin ${plugin_libs})
  ament_target_dependencies(${bt_plugin} ${dependencies})
  target_compile_definitions(${bt_plugin} PRIVATE BT_PLUGIN_EXPORT)
endforeach()

install(TARGETS ${plugin_libs}
  ARCHIVE DESTINATION lib
  LIBRARY DESTINATION lib
  RUNTIME DESTINATION bin
)

install(DIRECTORY include/
  DESTINATION include/
)

# install others
install(DIRECTORY
  launch
  param
  rviz
  behavior_trees
  DESTINATION share/${PROJECT_NAME}
)

ament_export_libraries(
  ${plugin_libs}
)

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # uncomment the line when a copyright and license is not present in all source files
  #set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # uncomment the line when this package is not in a git repo
  #set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()

5. 重载navigation_launch.py

navigation_launch负责启动各类lifecycle nodes,因此,上述自定义的btnav_action_server也应由该launch文件负责

首先,在“lifecycle_nodes”中添加自定义的“btnav_action_server”,如下图:
在这里插入图片描述

其次,添加运行自定义lifecycle node的代码,如下图:
在这里插入图片描述

6. 重载bringup_launch.py

如果navigation stack之前使用的是navigation 2默认的bringup_launch.py,则需要对其进行重载,以调用重载后的navigation_launch.py

首先,添加重载后navigation_launch.py的路径,如下图:
在这里插入图片描述

其次,修改include navigation_launch.py的参数,如下图:
在这里插入图片描述

7. 修改启动navigation stack的launch文件

本文依据苇航智能的navigation stack文件为例,请根据实际情况做适应性修改

navigation stack的launch文件需要为上述重载的两个launch文件,提供behavior tree的路径,同时,需要调用重载后的bringup_launch

首先,指定bringup_launch的路径,如下图:
在这里插入图片描述

其次,修改include bringup_launch.py的参数,以及为bringup_launch提供behavior tree路径的参数。如果使用的是turtlebot3的navigation stack launch文件,默认是不提供behavior tree路径参数的,需要自行添加,如下图:
在这里插入图片描述

8. 编译

完成上述步骤后,编译,在终端输入命令:

cd ~/dev_ws
colcon build --packages-select motion_ctrl_nav

五. 测试验证

现在可以验证自定义的bt plugin是否生效。本文目的为构建bt plugin的路径说明,因此上述action node SpinGO的on_tick函数没有具体的旋转执行代码,而是通过输出文本“spinnnnnnnnnnnnnnnnnnnnnnnnnnn”用于验证

首先,运行机器人,这里以苇航智能NaviBOT A0为例,在终端输入命令:

cd ~/dev_ws
sudo su
. install/setup.bash
ros2 launch motion_ctrl NaviBOT_A0.launch.py

其次,运行remote导航stack,在新终端输入命令:

cd ~/dev_ws
. install/setup.bash
ros2 launch motion_ctrl_nav navigation_remote_NaviBOT_A0.launch.py map:=/home/whi/dev_ws/whiMap_h.yaml

导航stack运行后,查看终端输出,可以看到自定义的lifecycle node “btnav_action_server”运行,如下图:
在这里插入图片描述

在bt_navigator中,可以看到添加的自定义plugin “SpinGO”被初始化,如下图:
在这里插入图片描述

最后,在rviz中为机器人设置初始位置以激活导航,并设置导航目标点,查看运行导航的终端,将看到自定义plugin “SpinGO”输出的字符串“spinnnnnnnnnnnnnnnnnnnnnnnnnnn”,表明自定义behavior tree节点SpinGO成功运行,如下图:
在这里插入图片描述


总结

至此,自定义behavior tree的plugin已经构建完毕,当修改了behavior tree的描述文件,插入了自定义plugin后,bt_navigator将能够动态的加载自定义的action plugin,能够使得机器人完成自定义动作和逻辑,并且后续动作逻辑的变更和扩展不会影响到原有代码,大大提高了机器人代码的扩展性

本文所述内容以整个过程为主线,意在介绍创建自定义bt plugin的宏观过程,整个过程涉及到了ROS 2和Navigation 2新构架下的很多关键知识点,限于篇幅文中没有具体展开,只给出了链接,读者可以通过文中连接详细了解

本文第三部分的自定义action server,没有介绍在param.yaml中对server的参数配置关联的内容,其方式可以参考nav2_recoveries实现,留给读者练习实践

为Navigation Stack加入自定义的逻辑插件不限于本文所述方式,在ROS 2的架构下提供了很多灵活的方式,比如构建自定义的controller,planner等等,但基本过程和思想与本文描述类似,留给读者去探索

如果文中有错误和不明确的地方,欢迎指正,共同交流,xinjue.zou.whi@gmail.com

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为Navigation 2创建自定义behavior tree plugin 的相关文章

  • 一个既有趣又简单的整人代码——关机代码

    这一篇博客来的比我的预计时间要长啊 xff0c 在这一周多的时间里 xff0c 我几乎很少有休息和出去玩耍的时间 说实话 xff0c 这样忙碌的生活给我的感觉还是蛮好的 xff0c 让我有一种很充实的感觉 xff0c 有种自己在与时间赛跑的
  • 【CMake】CMakeList编写整理

    什么是CMake 如果软件想跨平台 xff0c 必须要保证能够在不同平台编译 而如果使用 Make 工具 xff0c 就得为每一种标准写一次 Makefile CMake 就是针对上面问题所设计的工具 xff1a 它首先允许开发者编写一种平
  • 解决 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform...警告

    解决 WARN util NativeCodeLoader Unable to load native hadoop library for your platform using builtin java classes where ap
  • Vue获取数组的数组数据

    Q xff1a 如何在vue获取数组的数组 xff1f A xff1a 用到js的map对象方法 一 data里要先定义好有两个数组 二 主要代码 这样就可以获取到数组的子数组数据
  • Ubuntu18.04 GAAS学习笔记

    GAAS学习笔记 1 环境构建1 1 依赖项安装1 2 ros安装1 3 MAVROS安装1 4 PX4 Firmware安装 全程参考官方文档 xff0c 总结遇见的错误 xff1a https gaas gitbook io guide
  • ArUco标定板生成与打印

    链接如下 xff1a https span class token punctuation span span class token operator span chev span class token punctuation span
  • ROS工作空间与功能包

    工作空间 工作空间 xff08 workspace xff09 是一个存放工程开发相关文件的文件夹 xff0c 其目录下有 xff1a src xff1a 代码空间 xff08 Source Space xff09 build xff1a
  • Ubuntu20.04安装UHD及GUN Radio3.9

    目录 1 安装UHD依赖库及UHD 2 安装GNU Radio3 9 3 1 安装UHD依赖库及UHD 总结自 xff1a USRP Hardware Driver and USRP Manual Building and Installi
  • ros安装的依赖问题

    问题描述 xff1a ros kinetic desktop full 依赖 ros kinetic desktop 但是它将不会被安装 依赖 ros kinetic perception 但是它将不会被安装 依赖 ros kinetic
  • STM32MP157驱动开发——字符设备驱动

    一 简介 字符设备是 Linux 驱动中最基本的一类设备驱动 xff0c 字符设备就是一个一个字节 xff0c 按照字节 流进行读写操作的设备 xff0c 读写数据是分先后顺序的 比如我们最常见的点灯 按键 IIC SPI xff0c LC
  • Java样卷

    一 问答题 请解释一下Java语言的主要特点 至少说明五个特点 进程和线程的概念是什么 xff1f 两者有什么区别和联系 什么是流 xff1f 什么是字节流 xff1f 什么是字符流 xff1f 字节流和字符流的差别是什么 xff1f 二
  • CodeBlocks如何将英文环境改为中文

    一 下载汉化包 xff08 链接如下 xff09 链接 xff1a https pan baidu com s 1U FMZuFvFQ9 70whXcIwQ 提取码 xff1a 2333 二 选择路径 将汉化包中的文件 xff08 Code
  • 浅谈多任务学习

    目录 一 前言及定义 二 多任务学习 xff08 MTL xff09 的两种方法 2 1 参数的硬共享机制 xff08 hard parameter sharing xff09 2 2 参数的软共享机制 xff08 soft paramet
  • PyTorch在GPU上跑代码需要迁移哪些东西?

    一 数据 模型 损失函数需要迁移到GPU上 使用GPU训练时 xff0c 数据 函数和模型都必须同时放在GPU上 xff0c 否则会出错 xff08 1 xff09 判断GPU是否可用 if torch cuda is available
  • C++ Primer 第五版学习 第一、二章

    一 标准输入输出 cin是标准输入 xff08 istream对象 xff09 cout是标准输出 xff08 ostream xff09 cerr是标准错误 xff0c 用来输出警告和错误消息 clog用来输出程序运行时的一般性信息 二
  • C++ Primer第五版学习 第四章第五章

    补第三章 size t size t是一些C C 43 43 标准在stddef h cstddef中定义的 这个类型足以用来表示对象的大小 size t的真实类型与操作系统有关 在32位架构中被普遍定义为 xff1a typedef un
  • C++笔试题

    1 用预处理指令 define声明一个常数 xff0c 用以表明1年中有多少秒 xff1f define SECONDS PER YEAR 60 60 24 365 UL 2 写一个标准宏MIN xff0c 这个宏输入两个参数并返回较小的一
  • C++ Primer第五版学习 第十章

    泛型算法为什么叫泛型 可以运用在多种容器类型之上 xff0c 而容器内的元素类型也可以多样化 标准库算法对迭代器而不是容器进行操作 因此 xff0c 算法不能直接添加或删除元素 find iter1 iter2 value 搜索算法 前两个
  • kindle操作:传输下载的书籍、更改书籍封面

    kindle 可以使用 calibre 软件进行电子书的管理 xff0c 官网下载地址为 xff1a https calibre ebook com download calibre 是一款电子书管理的开源软件 xff0c 支持Window
  • C++ Primer第五版学习 第十一章

    一 关联容器类型 按关键字有序保存元素 map关联数组 xff0c 保存关键字 值对set关键字即值 xff0c 即只保存关键字的容器multimap关键字可重复出现的mapmultiset关键字可重复出现的set 无序集合 unorder

随机推荐