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