From 7f5dd20914d8fe5036ecf29ea01f57073b45d1ac Mon Sep 17 00:00:00 2001
From: "Carlos A. Orduno" <39749557+orduno@users.noreply.github.com>
Date: Thu, 6 Jun 2019 15:13:53 -0700
Subject: [PATCH] Enable navigation goal preemption (#802)

---
 .../nav2_bt_navigator/bt_navigator.hpp        |  3 ++
 nav2_bt_navigator/src/bt_navigator.cpp        | 48 +++++++++++++------
 .../nav2_tasks/behavior_tree_engine.hpp       |  1 +
 .../include/nav2_tasks/bt_action_node.hpp     |  2 +-
 nav2_tasks/src/behavior_tree_engine.cpp       |  3 ++
 .../nav2_util/simple_action_server.hpp        | 16 +++++--
 6 files changed, 52 insertions(+), 21 deletions(-)

diff --git a/nav2_bt_navigator/include/nav2_bt_navigator/bt_navigator.hpp b/nav2_bt_navigator/include/nav2_bt_navigator/bt_navigator.hpp
index 10e449dd..0c3e849a 100644
--- a/nav2_bt_navigator/include/nav2_bt_navigator/bt_navigator.hpp
+++ b/nav2_bt_navigator/include/nav2_bt_navigator/bt_navigator.hpp
@@ -54,6 +54,9 @@ protected:
   // The action server callback
   void navigateToPose(const std::shared_ptr<GoalHandle> goal_handle);
 
+  // Goal pose initialization on the blackboard
+  void initializeGoalPose(std::shared_ptr<GoalHandle> goal_handle);
+
   // A subscription and callback to handle the topic-based goal published from rviz
   void onGoalPoseReceived(const geometry_msgs::msg::PoseStamped::SharedPtr pose);
   rclcpp::Subscription<geometry_msgs::msg::PoseStamped>::SharedPtr goal_sub_;
diff --git a/nav2_bt_navigator/src/bt_navigator.cpp b/nav2_bt_navigator/src/bt_navigator.cpp
index 6519dd22..ec97a08d 100644
--- a/nav2_bt_navigator/src/bt_navigator.cpp
+++ b/nav2_bt_navigator/src/bt_navigator.cpp
@@ -51,9 +51,10 @@ BtNavigator::on_configure(const rclcpp_lifecycle::State & /*state*/)
   self_client_ = rclcpp_action::create_client<nav2_msgs::action::NavigateToPose>(
     client_node_, "NavigateToPose");
 
-  goal_sub_ = rclcpp_node_->create_subscription<geometry_msgs::msg::PoseStamped>("/move_base_simple/goal",
-      rclcpp::SystemDefaultsQoS(),
-      std::bind(&BtNavigator::onGoalPoseReceived, this, std::placeholders::_1));
+  goal_sub_ = rclcpp_node_->create_subscription<geometry_msgs::msg::PoseStamped>(
+    "/move_base_simple/goal",
+    rclcpp::SystemDefaultsQoS(),
+    std::bind(&BtNavigator::onGoalPoseReceived, this, std::placeholders::_1));
 
   // Create an action server that we implement with our navigateToPose method
   action_server_ = std::make_unique<ActionServer>(rclcpp_node_, "NavigateToPose",
@@ -159,34 +160,39 @@ BtNavigator::on_shutdown(const rclcpp_lifecycle::State & /*state*/)
 void
 BtNavigator::navigateToPose(const std::shared_ptr<GoalHandle> goal_handle)
 {
-  // Initialize the goal and result
-  auto goal = goal_handle->get_goal();
+  // The action may be pre-empted, so keep a pointer to the current goal handle
+  std::shared_ptr<GoalHandle> current_goal_handle = goal_handle;
   auto result = std::make_shared<nav2_msgs::action::NavigateToPose::Result>();
 
-  RCLCPP_INFO(get_logger(), "Begin navigating from current location to (%.2f, %.2f)",
-    goal->pose.pose.position.x, goal->pose.pose.position.y);
-
-  // Update the goal pose on the blackboard
-  *(blackboard_->get<geometry_msgs::msg::PoseStamped::SharedPtr>("goal")) = goal->pose;
+  initializeGoalPose(current_goal_handle);
 
   // Execute the BT that was previously created in the configure step
-  auto is_canceling = [goal_handle]() -> bool {return goal_handle->is_canceling();};
-  nav2_tasks::BtStatus rc = bt_->run(tree_, is_canceling);
+  auto is_canceling = [&current_goal_handle]() -> bool
+    {return current_goal_handle->is_canceling();};
+
+  auto on_loop = [this, &current_goal_handle] {
+      if (action_server_->preempt_requested()) {
+        current_goal_handle = action_server_->get_updated_goal_handle();
+        initializeGoalPose(current_goal_handle);
+      }
+    };
+
+  nav2_tasks::BtStatus rc = bt_->run(tree_, on_loop, is_canceling);
 
   switch (rc) {
     case nav2_tasks::BtStatus::SUCCEEDED:
       RCLCPP_INFO(get_logger(), "Navigation succeeded");
-      goal_handle->succeed(result);
+      current_goal_handle->succeed(result);
       break;
 
     case nav2_tasks::BtStatus::FAILED:
       RCLCPP_ERROR(get_logger(), "Navigation failed");
-      goal_handle->abort(result);
+      current_goal_handle->abort(result);
       break;
 
     case nav2_tasks::BtStatus::CANCELED:
       RCLCPP_INFO(get_logger(), "Navigation canceled");
-      goal_handle->canceled(result);
+      current_goal_handle->canceled(result);
       // Reset the BT so that it can be run again in the future
       bt_->resetTree(tree_->root_node);
       break;
@@ -196,6 +202,18 @@ BtNavigator::navigateToPose(const std::shared_ptr<GoalHandle> goal_handle)
   }
 }
 
+void
+BtNavigator::initializeGoalPose(std::shared_ptr<GoalHandle> goal_handle)
+{
+  auto goal = goal_handle->get_goal();
+
+  RCLCPP_INFO(get_logger(), "Begin navigating from current location to (%.2f, %.2f)",
+    goal->pose.pose.position.x, goal->pose.pose.position.y);
+
+  // Update the goal pose on the blackboard
+  *(blackboard_->get<geometry_msgs::msg::PoseStamped::SharedPtr>("goal")) = goal->pose;
+}
+
 void
 BtNavigator::onGoalPoseReceived(const geometry_msgs::msg::PoseStamped::SharedPtr pose)
 {
diff --git a/nav2_tasks/include/nav2_tasks/behavior_tree_engine.hpp b/nav2_tasks/include/nav2_tasks/behavior_tree_engine.hpp
index c399183e..3c851159 100644
--- a/nav2_tasks/include/nav2_tasks/behavior_tree_engine.hpp
+++ b/nav2_tasks/include/nav2_tasks/behavior_tree_engine.hpp
@@ -42,6 +42,7 @@ public:
 
   BtStatus run(
     std::unique_ptr<BT::Tree> & tree,
+    std::function<void()> onLoop,
     std::function<bool()> cancelRequested,
     std::chrono::milliseconds loopTimeout = std::chrono::milliseconds(10));
 
diff --git a/nav2_tasks/include/nav2_tasks/bt_action_node.hpp b/nav2_tasks/include/nav2_tasks/bt_action_node.hpp
index 54565b95..1e7ddaf8 100644
--- a/nav2_tasks/include/nav2_tasks/bt_action_node.hpp
+++ b/nav2_tasks/include/nav2_tasks/bt_action_node.hpp
@@ -136,7 +136,7 @@ new_goal_received:
 
         // We can handle a new goal if we're still executing
         auto status = goal_handle_->get_status();
-        if (goal_updated_ && (status == action_msgs::msg::GoalStatus::STATUS_EXECUTING)) {        
+        if (goal_updated_ && (status == action_msgs::msg::GoalStatus::STATUS_EXECUTING)) {
           goal_updated_ = false;
           goto new_goal_received;
         }
diff --git a/nav2_tasks/src/behavior_tree_engine.cpp b/nav2_tasks/src/behavior_tree_engine.cpp
index 7c53cf8e..dc268e03 100644
--- a/nav2_tasks/src/behavior_tree_engine.cpp
+++ b/nav2_tasks/src/behavior_tree_engine.cpp
@@ -62,6 +62,7 @@ BehaviorTreeEngine::run(
 BtStatus
 BehaviorTreeEngine::run(
   std::unique_ptr<BT::Tree> & tree,
+  std::function<void()> onLoop,
   std::function<bool()> cancelRequested,
   std::chrono::milliseconds loopTimeout)
 {
@@ -70,6 +71,8 @@ BehaviorTreeEngine::run(
 
   // Loop until something happens with ROS or the node completes w/ success or failure
   while (rclcpp::ok() && result == BT::NodeStatus::RUNNING) {
+    onLoop();
+
     result = tree->root_node->executeTick();
 
     // Check if we've received a cancel message
diff --git a/nav2_util/include/nav2_util/simple_action_server.hpp b/nav2_util/include/nav2_util/simple_action_server.hpp
index 6d799280..ffa5b549 100644
--- a/nav2_util/include/nav2_util/simple_action_server.hpp
+++ b/nav2_util/include/nav2_util/simple_action_server.hpp
@@ -56,12 +56,12 @@ public:
 
         // If we're currently working on a task, set a flag so that the
         // action server can grab the pre-empting request in its loop
-        if (received_handle_ != nullptr && received_handle_->is_active()) {
+        if (current_handle_ != nullptr && current_handle_->is_active()) {
           preempt_requested_ = true;
-          received_handle_ = handle;
+          new_handle_ = handle;
         } else {
           // Otherwise, safe to start a new task
-          received_handle_ = handle;
+          current_handle_ = handle;
           std::thread{execute_callback_, handle}.detach();
         }
       };
@@ -84,8 +84,13 @@ public:
   get_updated_goal_handle()
   {
     std::lock_guard<std::mutex> lock(update_mutex_);
+
+    current_handle_->abort(std::make_shared<typename ActionT::Result>());
+    current_handle_ = new_handle_;
+    new_handle_.reset();
     preempt_requested_ = false;
-    return received_handle_;
+
+    return current_handle_;
   }
 
 protected:
@@ -96,7 +101,8 @@ protected:
 
   std::mutex update_mutex_;
   bool preempt_requested_{false};
-  std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> received_handle_;
+  std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> current_handle_;
+  std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> new_handle_;
 
   typename rclcpp_action::Server<ActionT>::SharedPtr action_server_;
 };
-- 
GitLab