Nav2 Navigation Stack - rolling  main
ROS 2 Navigation Stack
pause_resume_controller.cpp
1 // Copyright (c) 2025 Enjoy Robotics
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 // Other includes
16 #include <functional>
17 
18 // ROS includes
19 #include "nav2_behavior_tree/plugins/control/pause_resume_controller.hpp"
20 
21 // Other includes
22 #include "behaviortree_cpp/bt_factory.h"
23 
24 namespace nav2_behavior_tree
25 {
26 
27 using namespace std::placeholders;
28 
30  const std::string & xml_tag_name,
31  const BT::NodeConfiguration & conf)
32 : BT::ControlNode(xml_tag_name, conf)
33 {
34  auto node = this->config().blackboard->get<rclcpp::Node::SharedPtr>("node");
35  state_ = RESUMED;
36 
37  // Create a separate cb group with a separate executor to spin
38  cb_group_ = node->create_callback_group(
39  rclcpp::CallbackGroupType::MutuallyExclusive, false);
40 
41  executor_ =
42  std::make_shared<rclcpp::executors::SingleThreadedExecutor>();
43 
44  executor_->add_callback_group(cb_group_, node->get_node_base_interface());
45 
46  std::string pause_service_name;
47  getInput("pause_service_name", pause_service_name);
48  pause_srv_ = std::make_shared<nav2::ServiceServer<Trigger>>(
49  pause_service_name,
50  node,
51  std::bind(&PauseResumeController::pauseServiceCallback, this, _1, _2, _3),
52  cb_group_);
53 
54  std::string resume_service_name;
55  getInput("resume_service_name", resume_service_name);
56  resume_srv_ = std::make_shared<nav2::ServiceServer<Trigger>>(
57  resume_service_name,
58  node,
59  std::bind(&PauseResumeController::resumeServiceCallback, this, _1, _2, _3),
60  cb_group_);
61 }
62 
64 {
65  unsigned int children_count = children_nodes_.size();
66  if (children_count < 1 || children_count > 4) {
67  throw std::runtime_error(
68  "PauseNode must have at least one and at most four children "
69  "(currently has " + std::to_string(children_count) + ")");
70  }
71 
72  if (status() == BT::NodeStatus::IDLE) {
73  state_ = RESUMED;
74  }
75  setStatus(BT::NodeStatus::RUNNING);
76 
77  executor_->spin_some();
78 
79  // If pause / resume requested, reset children and switch to transient child
80  if (state_ == PAUSE_REQUESTED || state_ == RESUME_REQUESTED) {
81  resetChildren();
82  switchToNextState();
83  }
84 
85  // Return RUNNING and do nothing if specific child is not used
86  const uint16_t child_idx = child_indices.at(state_);
87  if (children_nodes_.size() <= child_idx) {
88  switchToNextState();
89  return BT::NodeStatus::RUNNING;
90  }
91 
92  // If child is used, tick it
93  const BT::NodeStatus child_status =
94  children_nodes_[child_indices.at(state_)]->executeTick();
95 
96  switch (child_status) {
97  case BT::NodeStatus::RUNNING:
98  return BT::NodeStatus::RUNNING;
99  case BT::NodeStatus::SUCCESS:
100  case BT::NodeStatus::SKIPPED:
101  if (state_ == RESUMED) {
102  // Resumed child returned SUCCESS, we're done
103  return BT::NodeStatus::SUCCESS;
104  }
105  switchToNextState();
106  // If any branch other than RESUMED returned SUCCESS, keep ticking
107  return BT::NodeStatus::RUNNING;
108  case BT::NodeStatus::FAILURE:
109  RCLCPP_ERROR(
110  logger_, "%s child returned FAILURE", state_names.at(state_).c_str());
111  return BT::NodeStatus::FAILURE;
112  default:
113  throw std::runtime_error("A child node must never return IDLE");
114  }
115 }
116 
117 void PauseResumeController::switchToNextState()
118 {
119  static const std::map<state_t, state_t> next_states = {
120  {PAUSE_REQUESTED, ON_PAUSE},
121  {ON_PAUSE, PAUSED},
122  {RESUME_REQUESTED, ON_RESUME},
123  {ON_RESUME, RESUMED}
124  };
125 
126  if (state_ == PAUSED || state_ == RESUMED) {
127  // No next state, do nothing
128  return;
129  }
130 
131  state_ = next_states.at(state_);
132  RCLCPP_INFO(logger_, "Switched to state: %s", state_names.at(state_).c_str());
133 }
134 
135 void PauseResumeController::pauseServiceCallback(
136  const std::shared_ptr<rmw_request_id_t>/*request_header*/,
137  const std::shared_ptr<Trigger::Request>/*request*/,
138  std::shared_ptr<Trigger::Response> response)
139 {
140  if (state_ != PAUSED) {
141  RCLCPP_INFO(logger_, "Received pause request");
142  response->success = true;
143  state_ = PAUSE_REQUESTED;
144  return;
145  }
146 
147  std::string warn_message = "PauseResumeController BT node already in state PAUSED";
148  RCLCPP_WARN(logger_, "%s", warn_message.c_str());
149  response->success = false;
150  response->message = warn_message;
151 }
152 
153 void PauseResumeController::resumeServiceCallback(
154  const std::shared_ptr<rmw_request_id_t>/*request_header*/,
155  const std::shared_ptr<Trigger::Request>/*request*/,
156  std::shared_ptr<Trigger::Response> response)
157 {
158  if (state_ == PAUSED) {
159  RCLCPP_INFO(logger_, "Received resume request");
160  response->success = true;
161  state_ = RESUME_REQUESTED;
162  return;
163  }
164 
165  std::string warn_message = "PauseResumeController BT node not in state PAUSED";
166  RCLCPP_WARN(logger_, "%s", warn_message.c_str());
167  response->success = false;
168  response->message = warn_message;
169 }
170 
172 {
173  state_ = RESUMED;
174  ControlNode::halt();
175 }
176 
177 } // namespace nav2_behavior_tree
178 
179 BT_REGISTER_NODES(factory)
180 {
181  BT::NodeBuilder builder =
182  [](const std::string & name, const BT::NodeConfiguration & config)
183  {
184  return std::make_unique<nav2_behavior_tree::PauseResumeController>(
185  name, config);
186  };
187 
188  factory.registerBuilder<nav2_behavior_tree::PauseResumeController>(
189  "PauseResumeController", builder);
190 }
PauseResumeController(const std::string &xml_tag_name, const BT::NodeConfiguration &conf)
Constructor.
BT::NodeStatus tick() override
Handle transitions if requested and tick child related to the actual state.
void halt() override
Reset state and go to Idle.