Nav2 Navigation Stack - rolling  main
ROS 2 Navigation Stack
bt_action_server_impl.hpp
1 // Copyright (c) 2020 Sarthak Mittal
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 #ifndef NAV2_BEHAVIOR_TREE__BT_ACTION_SERVER_IMPL_HPP_
16 #define NAV2_BEHAVIOR_TREE__BT_ACTION_SERVER_IMPL_HPP_
17 
18 #include <chrono>
19 #include <exception>
20 #include <fstream>
21 #include <limits>
22 #include <memory>
23 #include <set>
24 #include <utility>
25 #include <string>
26 #include <vector>
27 
28 #include "nav2_msgs/action/navigate_to_pose.hpp"
29 #include "nav2_behavior_tree/bt_action_server.hpp"
30 #include "nav2_ros_common/node_utils.hpp"
31 #include "rcl_action/action_server.h"
32 #include "nav2_ros_common/lifecycle_node.hpp"
33 
34 namespace nav2_behavior_tree
35 {
36 
37 template<class ActionT, class NodeT>
39  const typename NodeT::WeakPtr & parent,
40  const std::string & action_name,
41  const std::vector<std::string> & plugin_lib_names,
42  const std::string & default_bt_xml_filename,
43  OnGoalReceivedCallback on_goal_received_callback,
44  OnLoopCallback on_loop_callback,
45  OnPreemptCallback on_preempt_callback,
46  OnCompletionCallback on_completion_callback,
47  const std::vector<std::string> & search_directories)
48 : action_name_(action_name),
49  default_bt_xml_filename_or_id_(default_bt_xml_filename),
50  search_directories_(search_directories),
51  plugin_lib_names_(plugin_lib_names),
52  node_(parent),
53  on_goal_received_callback_(on_goal_received_callback),
54  on_loop_callback_(on_loop_callback),
55  on_preempt_callback_(on_preempt_callback),
56  on_completion_callback_(on_completion_callback),
57  internal_error_code_(0),
58  internal_error_msg_()
59 {
60  auto node = node_.lock();
61  logger_ = node->get_logger();
62  clock_ = node->get_clock();
63 
64  std::vector<std::string> default_error_code_name_prefixes = {
65  "assisted_teleop",
66  "backup",
67  "compute_path",
68  "dock_robot",
69  "drive_on_heading",
70  "follow_object",
71  "follow_path",
72  "nav_thru_poses",
73  "nav_to_pose",
74  "spin",
75  "undock_robot",
76  "wait",
77  };
78 
79  if (node->has_parameter("error_code_names")) {
80  throw std::runtime_error("parameter 'error_code_names' has been replaced by "
81  " 'error_code_name_prefixes' and MUST be removed.\n"
82  " Please review migration guide and update your configuration.");
83  }
84 
85  // Declare and get error code name prefixes parameter
86  error_code_name_prefixes_ = node->declare_or_get_parameter(
87  "error_code_name_prefixes",
88  default_error_code_name_prefixes);
89 
90  // Provide informative logging about error code prefixes
91  std::string error_code_name_prefixes_str;
92  for (const auto & error_code_name_prefix : error_code_name_prefixes_) {
93  error_code_name_prefixes_str += " " + error_code_name_prefix;
94  }
95 
96  if (error_code_name_prefixes_ == default_error_code_name_prefixes) {
97  RCLCPP_WARN_STREAM(
98  logger_, "error_code_name_prefixes parameters were not set. Using default values of:"
99  << error_code_name_prefixes_str + "\n"
100  << "Make sure these match your BT and there are not other sources of error codes you"
101  << "reported to your application");
102  } else {
103  RCLCPP_INFO_STREAM(logger_, "Error_code parameters were set to:"
104  << error_code_name_prefixes_str);
105  }
106 }
107 
108 template<class ActionT, class NodeT>
110 {}
111 
112 template<class ActionT, class NodeT>
114 {
115  auto node = node_.lock();
116  if (!node) {
117  throw std::runtime_error{"Failed to lock node"};
118  }
119 
120  // Name client node after action name
121  std::string client_node_name = action_name_;
122  std::replace(client_node_name.begin(), client_node_name.end(), '/', '_');
123  // Use suffix '_rclcpp_node' to keep parameter file consistency #1773
124 
125  auto new_arguments = node->get_node_options().arguments();
126  nav2::replaceOrAddArgument(new_arguments, "-r", "__node", std::string("__node:=") +
127  std::string(node->get_name()) + "_" + client_node_name + "_rclcpp_node");
128  auto options = node->get_node_options();
129  options = options.arguments(new_arguments);
130 
131  // Support for handling the topic-based goal pose from rviz
132  client_node_ = std::make_shared<nav2::LifecycleNode>("_", options);
133  client_node_->configure();
134  client_node_->activate();
135 
136  // Declare parameters for common client node applications to share with BT nodes
137  // Declare if not declared in case being used an external application, then copying
138  // all of the main node's parameters to the client for BT nodes to obtain
139  nav2::declare_parameter_if_not_declared(
140  node, "global_frame", rclcpp::ParameterValue(std::string("map")));
141  nav2::declare_parameter_if_not_declared(
142  node, "robot_base_frame", rclcpp::ParameterValue(std::string("base_link")));
143  nav2::declare_parameter_if_not_declared(
144  node, "transform_tolerance", rclcpp::ParameterValue(0.1));
145  rclcpp::copy_all_parameter_values(node, client_node_);
146 
147  // Could be using a user rclcpp::Node, so need to use the Nav2 factory to create the subscription
148  // to convert nav2::LifecycleNode, rclcpp::Node or rclcpp_lifecycle::LifecycleNode
149  action_server_ = nav2::interfaces::create_action_server<ActionT>(
150  node, action_name_, std::bind(&BtActionServer<ActionT, NodeT>::executeCallback, this),
151  nullptr, std::chrono::milliseconds(500), false);
152 
153  // Get parameters for BT timeouts
154  bt_loop_duration_ = std::chrono::milliseconds(
155  node->declare_or_get_parameter("bt_loop_duration", 10));
156 
157  default_server_timeout_ = std::chrono::milliseconds(
158  node->declare_or_get_parameter("default_server_timeout", 20));
159 
160  wait_for_service_timeout_ = std::chrono::milliseconds(
161  node->declare_or_get_parameter("wait_for_service_timeout", 1000));
162 
163  always_reload_bt_ = node->declare_or_get_parameter(
164  "always_reload_bt_xml", false);
165 
166  // Get error code id names to grab off of the blackboard
167  error_code_name_prefixes_ = node->get_parameter("error_code_name_prefixes").as_string_array();
168 
169  // Create the class that registers our custom nodes and executes the BT
170  bt_ = std::make_unique<nav2_behavior_tree::BehaviorTreeEngine>(plugin_lib_names_, client_node_);
171 
172  // Create the blackboard that will be shared by all of the nodes in the tree
173  blackboard_ = BT::Blackboard::create();
174 
175  // Put items on the blackboard
176  blackboard_->set<nav2::LifecycleNode::SharedPtr>("node", client_node_); // NOLINT
177  blackboard_->set<std::chrono::milliseconds>("server_timeout", default_server_timeout_); // NOLINT
178  blackboard_->set<std::chrono::milliseconds>("bt_loop_duration", bt_loop_duration_); // NOLINT
179  blackboard_->set<std::chrono::milliseconds>(
180  "wait_for_service_timeout",
181  wait_for_service_timeout_);
182 
183  return true;
184 }
185 
186 template<class ActionT, class NodeT>
188 {
189  resetInternalError();
190  if (!loadBehaviorTree(default_bt_xml_filename_or_id_)) {
191  RCLCPP_ERROR(logger_, "Error loading BT: %s", default_bt_xml_filename_or_id_.c_str());
192  return false;
193  }
194  action_server_->activate();
195  return true;
196 }
197 
198 template<class ActionT, class NodeT>
200 {
201  action_server_->deactivate();
202  return true;
203 }
204 
205 template<class ActionT, class NodeT>
207 {
208  client_node_->deactivate();
209  client_node_->cleanup();
210  client_node_.reset();
211  action_server_.reset();
212  topic_logger_.reset();
213  plugin_lib_names_.clear();
214  current_bt_file_or_id_.clear();
215  blackboard_.reset();
216  bt_->haltAllActions(tree_);
217  bt_->resetGrootMonitor();
218  bt_.reset();
219  return true;
220 }
221 
222 template<class ActionT, class NodeT>
224  const bool enable,
225  const unsigned server_port)
226 {
227  enable_groot_monitoring_ = enable;
228  groot_server_port_ = server_port;
229 }
230 
231 template<class ActionT, class NodeT>
232 bool BtActionServer<ActionT, NodeT>::loadBehaviorTree(const std::string & bt_xml_filename_or_id)
233 {
234  namespace fs = std::filesystem;
235 
236  // Empty argument is default for backward compatibility
237  auto file_or_id =
238  bt_xml_filename_or_id.empty() ? default_bt_xml_filename_or_id_ : bt_xml_filename_or_id;
239 
240  // Use previous BT if it is the existing one and always reload flag is not set to true
241  if (!always_reload_bt_ && current_bt_file_or_id_ == file_or_id) {
242  RCLCPP_DEBUG(logger_, "BT will not be reloaded as the given xml or ID is already loaded");
243  return true;
244  }
245 
246  // Reset any existing Groot2 monitoring
247  bt_->resetGrootMonitor();
248 
249  bool is_bt_id = false;
250  if ((file_or_id.length() < 4) ||
251  file_or_id.substr(file_or_id.length() - 4) != ".xml")
252  {
253  is_bt_id = true;
254  }
255 
256  std::set<std::string> registered_ids;
257  std::string main_id;
258  auto register_all_bt_files = [&](const std::string & skip_file = "") {
259  for (const auto & directory : search_directories_) {
260  for (const auto & entry : fs::directory_iterator(directory)) {
261  if (entry.path().extension() != ".xml") {
262  continue;
263  }
264  if (!skip_file.empty() && entry.path().string() == skip_file) {
265  continue;
266  }
267 
268  auto id = bt_->extractBehaviorTreeID(entry.path().string());
269  if (id.empty()) {
270  RCLCPP_ERROR(logger_, "Skipping BT file %s (missing ID)", entry.path().c_str());
271  continue;
272  }
273  if (registered_ids.count(id)) {
274  RCLCPP_WARN(logger_, "Skipping conflicting BT file %s (duplicate ID %s)",
275  entry.path().c_str(), id.c_str());
276  continue;
277  }
278 
279  RCLCPP_DEBUG(logger_, "Registering Tree from File: %s", entry.path().string().c_str());
280  bt_->registerTreeFromFile(entry.path().string());
281  registered_ids.insert(id);
282  }
283  }
284  };
285 
286  try {
287  if (!is_bt_id) {
288  // file_or_id is a filename: register it first
289  std::string main_file = file_or_id;
290  main_id = bt_->extractBehaviorTreeID(main_file);
291  if (main_id.empty()) {
292  RCLCPP_ERROR(logger_, "Failed to extract ID from %s", main_file.c_str());
293  return false;
294  }
295  RCLCPP_DEBUG(logger_, "Registering Tree from File: %s", main_file.c_str());
296  bt_->registerTreeFromFile(main_file);
297  registered_ids.insert(main_id);
298 
299  // When a filename is specified, it must be register first
300  // and treat it as the "main" tree to execute.
301  // This ensures the requested tree is always available
302  // and prioritized, even if other files in the directory have duplicate IDs.
303  // The lambda then skips this main file to avoid
304  // re-registering it or logging a duplicate warning.
305  // In contrast, when an ID is specified, it's unknown which file is "main"
306  // so all files are registered and conflicts are handled in the lambda.
307  register_all_bt_files(main_file);
308  } else {
309  // file_or_id is an ID: register all files, skipping conflicts
310  main_id = file_or_id;
311  register_all_bt_files();
312  }
313  } catch (const std::exception & e) {
314  setInternalError(ActionT::Result::FAILED_TO_LOAD_BEHAVIOR_TREE,
315  "Exception registering behavior trees: " + std::string(e.what()));
316  return false;
317  }
318 
319  // Create the tree with the specified ID
320  try {
321  tree_ = bt_->createTree(main_id, blackboard_);
322  RCLCPP_INFO(logger_, "Created BT from ID: %s", main_id.c_str());
323 
324  for (auto & subtree : tree_.subtrees) {
325  auto & blackboard = subtree->blackboard;
326  blackboard->set("node", client_node_);
327  blackboard->set<std::chrono::milliseconds>("server_timeout", default_server_timeout_);
328  blackboard->set<std::chrono::milliseconds>("bt_loop_duration", bt_loop_duration_);
329  blackboard->set<std::chrono::milliseconds>(
330  "wait_for_service_timeout", wait_for_service_timeout_);
331  }
332  } catch (const std::exception & e) {
333  setInternalError(ActionT::Result::FAILED_TO_LOAD_BEHAVIOR_TREE,
334  std::string("Exception when creating BT tree from ID: ") + e.what());
335  return false;
336  }
337 
338  // Optional logging and monitoring
339  topic_logger_ = std::make_unique<RosTopicLogger>(client_node_, tree_);
340  current_bt_file_or_id_ = file_or_id;
341 
342  if (enable_groot_monitoring_) {
343  bt_->addGrootMonitoring(&tree_, groot_server_port_);
344  RCLCPP_DEBUG(
345  logger_, "Enabling Groot2 monitoring for %s: %d",
346  action_name_.c_str(), groot_server_port_);
347  }
348 
349  return true;
350 }
351 
352 template<class ActionT, class NodeT>
354 {
355  if (!on_goal_received_callback_(action_server_->get_current_goal())) {
356  // Give server an opportunity to populate the result message
357  // if the goal is not accepted
358  auto result = std::make_shared<typename ActionT::Result>();
359  populateErrorCode(result);
360  action_server_->terminate_current(result);
361  cleanErrorCodes();
362  return;
363  }
364 
365  auto is_canceling = [&]() {
366  if (action_server_ == nullptr) {
367  RCLCPP_DEBUG(logger_, "Action server unavailable. Canceling.");
368  return true;
369  }
370  if (!action_server_->is_server_active()) {
371  RCLCPP_DEBUG(logger_, "Action server is inactive. Canceling.");
372  return true;
373  }
374  return action_server_->is_cancel_requested();
375  };
376 
377  auto on_loop = [&]() {
378  if (action_server_->is_preempt_requested() && on_preempt_callback_) {
379  on_preempt_callback_(action_server_->get_pending_goal());
380  }
381  topic_logger_->flush();
382  on_loop_callback_();
383  };
384 
385  // Execute the BT that was previously created in the configure step
386  nav2_behavior_tree::BtStatus rc = bt_->run(&tree_, on_loop, is_canceling, bt_loop_duration_);
387 
388  // Make sure that the Bt is not in a running state from a previous execution
389  // note: if all the ControlNodes are implemented correctly, this is not needed.
390  bt_->haltAllActions(tree_);
391 
392  // Give server an opportunity to populate the result message or simple give
393  // an indication that the action is complete.
394  auto result = std::make_shared<typename ActionT::Result>();
395 
396  populateErrorCode(result);
397 
398  on_completion_callback_(result, rc);
399 
400  switch (rc) {
401  case nav2_behavior_tree::BtStatus::SUCCEEDED:
402  action_server_->succeeded_current(result);
403  RCLCPP_INFO(logger_, "Goal succeeded");
404  break;
405 
406  case nav2_behavior_tree::BtStatus::FAILED:
407  action_server_->terminate_current(result);
408  RCLCPP_ERROR(logger_, "Goal failed error_code:%d error_msg:'%s'", result->error_code,
409  result->error_msg.c_str());
410  break;
411 
412  case nav2_behavior_tree::BtStatus::CANCELED:
413  action_server_->terminate_all(result);
414  RCLCPP_INFO(logger_, "Goal canceled");
415  break;
416  }
417 
418  cleanErrorCodes();
419 }
420 
421 template<class ActionT, class NodeT>
423  uint16_t error_code,
424  const std::string & error_msg)
425 {
426  internal_error_code_ = error_code;
427  internal_error_msg_ = error_msg;
428  RCLCPP_ERROR(logger_, "Setting internal error error_code:%d, error_msg:%s",
429  internal_error_code_, internal_error_msg_.c_str());
430 }
431 
432 template<class ActionT, class NodeT>
434 {
435  internal_error_code_ = ActionT::Result::NONE;
436  internal_error_msg_ = "";
437 }
438 
439 template<class ActionT, class NodeT>
441  typename std::shared_ptr<typename ActionT::Result> result)
442 {
443  if (internal_error_code_ != ActionT::Result::NONE) {
444  result->error_code = internal_error_code_;
445  result->error_msg = internal_error_msg_;
446  return true;
447  }
448  return false;
449 }
450 
451 template<class ActionT, class NodeT>
453  typename std::shared_ptr<typename ActionT::Result> result)
454 {
455  int highest_priority_error_code = std::numeric_limits<int>::max();
456  std::string highest_priority_error_msg = "";
457  std::string name;
458 
459  if (internal_error_code_ != 0) {
460  highest_priority_error_code = internal_error_code_;
461  highest_priority_error_msg = internal_error_msg_;
462  }
463 
464  for (const auto & error_code_name_prefix : error_code_name_prefixes_) {
465  try {
466  name = error_code_name_prefix + "_error_code";
467  int current_error_code = blackboard_->get<int>(name);
468  if (current_error_code != 0 && current_error_code < highest_priority_error_code) {
469  highest_priority_error_code = current_error_code;
470  name = error_code_name_prefix + "_error_msg";
471  highest_priority_error_msg = blackboard_->get<std::string>(name);
472  }
473  } catch (...) {
474  RCLCPP_DEBUG(
475  logger_,
476  "Failed to get error code name: %s from blackboard",
477  name.c_str());
478  }
479  }
480 
481  if (highest_priority_error_code != std::numeric_limits<int>::max()) {
482  result->error_code = highest_priority_error_code;
483  result->error_msg = highest_priority_error_msg;
484  }
485 }
486 
487 template<class ActionT, class NodeT>
489 {
490  std::string name;
491  for (const auto & error_code_name_prefix : error_code_name_prefixes_) {
492  name = error_code_name_prefix + "_error_code";
493  blackboard_->set<unsigned short>(name, 0); //NOLINT
494  name = error_code_name_prefix + "_error_msg";
495  blackboard_->set<std::string>(name, "");
496  }
497  resetInternalError();
498 }
499 
500 } // namespace nav2_behavior_tree
501 
502 #endif // NAV2_BEHAVIOR_TREE__BT_ACTION_SERVER_IMPL_HPP_
An action server that uses behavior tree to execute an action.
BtActionServer(const typename NodeT::WeakPtr &parent, const std::string &action_name, const std::vector< std::string > &plugin_lib_names, const std::string &default_bt_xml_filename, OnGoalReceivedCallback on_goal_received_callback, OnLoopCallback on_loop_callback, OnPreemptCallback on_preempt_callback, OnCompletionCallback on_completion_callback, const std::vector< std::string > &search_directories=std::vector< std::string >{})
A constructor for nav2_behavior_tree::BtActionServer class.
bool on_cleanup()
Resets member variables.
void setGrootMonitoring(const bool enable, const unsigned server_port)
Enable (or disable) Groot2 monitoring of BT.
bool loadBehaviorTree(const std::string &bt_xml_filename_or_id="")
Replace current BT with another one.
bool populateInternalError(typename std::shared_ptr< typename ActionT::Result > result)
populate result with internal error code and error_msg if not NONE
bool on_deactivate()
Deactivates action server.
~BtActionServer()
A destructor for nav2_behavior_tree::BtActionServer class.
bool on_configure()
Configures member variables Initializes action server for, builds behavior tree from xml file,...
void resetInternalError(void)
reset internal error code and message
void executeCallback()
Action server callback.
void cleanErrorCodes()
Setting BT error codes to success. Used to clean blackboard between different BT runs.
void populateErrorCode(typename std::shared_ptr< typename ActionT::Result > result)
updates the action server result to the highest priority error code posted on the blackboard
bool on_activate()
Activates action server.
void setInternalError(uint16_t error_code, const std::string &error_msg)
Set internal error code and message.