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