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 <unordered_set>
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  // Declare this node's parameters
65  if (!node->has_parameter("bt_loop_duration")) {
66  node->declare_parameter("bt_loop_duration", 10);
67  }
68  if (!node->has_parameter("default_server_timeout")) {
69  node->declare_parameter("default_server_timeout", 20);
70  }
71  if (!node->has_parameter("always_reload_bt_xml")) {
72  node->declare_parameter("always_reload_bt_xml", false);
73  }
74  if (!node->has_parameter("wait_for_service_timeout")) {
75  node->declare_parameter("wait_for_service_timeout", 1000);
76  }
77 
78  std::vector<std::string> error_code_name_prefixes = {
79  "assisted_teleop",
80  "backup",
81  "compute_path",
82  "dock_robot",
83  "drive_on_heading",
84  "follow_path",
85  "nav_thru_poses",
86  "nav_to_pose",
87  "spin",
88  "undock_robot",
89  "wait",
90  };
91 
92  if (node->has_parameter("error_code_names")) {
93  throw std::runtime_error("parameter 'error_code_names' has been replaced by "
94  " 'error_code_name_prefixes' and MUST be removed.\n"
95  " Please review migration guide and update your configuration.");
96  }
97 
98  if (!node->has_parameter("error_code_name_prefixes")) {
99  const rclcpp::ParameterValue value = node->declare_parameter(
100  "error_code_name_prefixes",
101  rclcpp::PARAMETER_STRING_ARRAY);
102  if (value.get_type() == rclcpp::PARAMETER_NOT_SET) {
103  std::string error_code_name_prefixes_str;
104  for (const auto & error_code_name_prefix : error_code_name_prefixes) {
105  error_code_name_prefixes_str += " " + error_code_name_prefix;
106  }
107  RCLCPP_WARN_STREAM(
108  logger_, "error_code_name_prefixes parameters were not set. Using default values of:"
109  << error_code_name_prefixes_str + "\n"
110  << "Make sure these match your BT and there are not other sources of error codes you"
111  "reported to your application");
112  rclcpp::Parameter error_code_name_prefixes_param("error_code_name_prefixes",
113  error_code_name_prefixes);
114  node->set_parameter(error_code_name_prefixes_param);
115  } else {
116  error_code_name_prefixes = value.get<std::vector<std::string>>();
117  std::string error_code_name_prefixes_str;
118  for (const auto & error_code_name_prefix : error_code_name_prefixes) {
119  error_code_name_prefixes_str += " " + error_code_name_prefix;
120  }
121  RCLCPP_INFO_STREAM(logger_, "Error_code parameters were set to:"
122  << error_code_name_prefixes_str);
123  }
124  }
125 }
126 
127 template<class ActionT, class NodeT>
129 {}
130 
131 template<class ActionT, class NodeT>
133 {
134  auto node = node_.lock();
135  if (!node) {
136  throw std::runtime_error{"Failed to lock node"};
137  }
138 
139  // Name client node after action name
140  std::string client_node_name = action_name_;
141  std::replace(client_node_name.begin(), client_node_name.end(), '/', '_');
142  // Use suffix '_rclcpp_node' to keep parameter file consistency #1773
143 
144  auto new_arguments = node->get_node_options().arguments();
145  nav2::replaceOrAddArgument(new_arguments, "-r", "__node", std::string("__node:=") +
146  std::string(node->get_name()) + "_" + client_node_name + "_rclcpp_node");
147  auto options = node->get_node_options();
148  options = options.arguments(new_arguments);
149 
150  // Support for handling the topic-based goal pose from rviz
151  client_node_ = std::make_shared<nav2::LifecycleNode>("_", options);
152  client_node_->configure();
153  client_node_->activate();
154 
155  // Declare parameters for common client node applications to share with BT nodes
156  // Declare if not declared in case being used an external application, then copying
157  // all of the main node's parameters to the client for BT nodes to obtain
158  nav2::declare_parameter_if_not_declared(
159  node, "global_frame", rclcpp::ParameterValue(std::string("map")));
160  nav2::declare_parameter_if_not_declared(
161  node, "robot_base_frame", rclcpp::ParameterValue(std::string("base_link")));
162  nav2::declare_parameter_if_not_declared(
163  node, "transform_tolerance", rclcpp::ParameterValue(0.1));
164  rclcpp::copy_all_parameter_values(node, client_node_);
165 
166  // Could be using a user rclcpp::Node, so need to use the Nav2 factory to create the subscription
167  // to convert nav2::LifecycleNode, rclcpp::Node or rclcpp_lifecycle::LifecycleNode
168  action_server_ = nav2::interfaces::create_action_server<ActionT>(
169  node, action_name_, std::bind(&BtActionServer<ActionT, NodeT>::executeCallback, this),
170  nullptr, std::chrono::milliseconds(500), false);
171 
172  // Get parameters for BT timeouts
173  int bt_loop_duration;
174  node->get_parameter("bt_loop_duration", bt_loop_duration);
175  bt_loop_duration_ = std::chrono::milliseconds(bt_loop_duration);
176  int default_server_timeout;
177  node->get_parameter("default_server_timeout", default_server_timeout);
178  default_server_timeout_ = std::chrono::milliseconds(default_server_timeout);
179  int wait_for_service_timeout;
180  node->get_parameter("wait_for_service_timeout", wait_for_service_timeout);
181  wait_for_service_timeout_ = std::chrono::milliseconds(wait_for_service_timeout);
182  node->get_parameter("always_reload_bt_xml", always_reload_bt_);
183 
184  // Get error code id names to grab off of the blackboard
185  error_code_name_prefixes_ = node->get_parameter("error_code_name_prefixes").as_string_array();
186 
187  // Create the class that registers our custom nodes and executes the BT
188  bt_ = std::make_unique<nav2_behavior_tree::BehaviorTreeEngine>(plugin_lib_names_, client_node_);
189 
190  // Create the blackboard that will be shared by all of the nodes in the tree
191  blackboard_ = BT::Blackboard::create();
192 
193  // Put items on the blackboard
194  blackboard_->set<nav2::LifecycleNode::SharedPtr>("node", client_node_); // NOLINT
195  blackboard_->set<std::chrono::milliseconds>("server_timeout", default_server_timeout_); // NOLINT
196  blackboard_->set<std::chrono::milliseconds>("bt_loop_duration", bt_loop_duration_); // NOLINT
197  blackboard_->set<std::chrono::milliseconds>(
198  "wait_for_service_timeout",
199  wait_for_service_timeout_);
200 
201  return true;
202 }
203 
204 template<class ActionT, class NodeT>
206 {
207  resetInternalError();
208  if (!loadBehaviorTree(default_bt_xml_filename_or_id_)) {
209  RCLCPP_ERROR(logger_, "Error loading BT: %s", default_bt_xml_filename_or_id_.c_str());
210  return false;
211  }
212  action_server_->activate();
213  return true;
214 }
215 
216 template<class ActionT, class NodeT>
218 {
219  action_server_->deactivate();
220  return true;
221 }
222 
223 template<class ActionT, class NodeT>
225 {
226  client_node_->deactivate();
227  client_node_->cleanup();
228  client_node_.reset();
229  action_server_.reset();
230  topic_logger_.reset();
231  plugin_lib_names_.clear();
232  current_bt_file_or_id_.clear();
233  blackboard_.reset();
234  bt_->haltAllActions(tree_);
235  bt_->resetGrootMonitor();
236  bt_.reset();
237  return true;
238 }
239 
240 template<class ActionT, class NodeT>
242  const bool enable,
243  const unsigned server_port)
244 {
245  enable_groot_monitoring_ = enable;
246  groot_server_port_ = server_port;
247 }
248 
249 template<class ActionT, class NodeT>
250 bool BtActionServer<ActionT, NodeT>::loadBehaviorTree(const std::string & bt_xml_filename_or_id)
251 {
252  namespace fs = std::filesystem;
253 
254  // Empty argument is default for backward compatibility
255  auto file_or_id =
256  bt_xml_filename_or_id.empty() ? default_bt_xml_filename_or_id_ : bt_xml_filename_or_id;
257 
258  // Use previous BT if it is the existing one and always reload flag is not set to true
259  if (!always_reload_bt_ && current_bt_file_or_id_ == file_or_id) {
260  RCLCPP_DEBUG(logger_, "BT will not be reloaded as the given xml or ID is already loaded");
261  return true;
262  }
263 
264  // Reset any existing Groot2 monitoring
265  bt_->resetGrootMonitor();
266 
267  bool is_bt_id = false;
268  if ((file_or_id.length() < 4) ||
269  file_or_id.substr(file_or_id.length() - 4) != ".xml")
270  {
271  is_bt_id = true;
272  }
273 
274  std::unordered_set<std::string> used_bt_id;
275  for (const auto & directory : search_directories_) {
276  try {
277  for (const auto & entry : fs::directory_iterator(directory)) {
278  if (entry.path().extension() == ".xml") {
279  auto current_bt_id = bt_->extractBehaviorTreeID(entry.path().string());
280  if (current_bt_id.empty()) {
281  RCLCPP_ERROR(logger_, "Skipping BT file %s (missing ID)",
282  entry.path().string().c_str());
283  continue;
284  }
285  auto [it, inserted] = used_bt_id.insert(current_bt_id);
286  if (!inserted) {
287  RCLCPP_WARN(
288  logger_,
289  "Warning: Duplicate BT IDs found. Make sure to have all BT IDs unique! "
290  "ID: %s File: %s",
291  current_bt_id.c_str(), entry.path().string().c_str());
292  }
293  bt_->registerTreeFromFile(entry.path().string());
294  }
295  }
296  } catch (const std::exception & e) {
297  setInternalError(ActionT::Result::FAILED_TO_LOAD_BEHAVIOR_TREE,
298  "Exception reading behavior tree directory: " + std::string(e.what()));
299  return false;
300  }
301  }
302  // Try to load the main BT tree (by ID)
303  try {
304  if(!is_bt_id) {
305  tree_ = bt_->createTreeFromFile(file_or_id, blackboard_);
306  } else {
307  tree_ = bt_->createTree(file_or_id, blackboard_);
308  }
309 
310  for (auto & subtree : tree_.subtrees) {
311  auto & blackboard = subtree->blackboard;
312  blackboard->set("node", client_node_);
313  blackboard->set<std::chrono::milliseconds>("server_timeout", default_server_timeout_);
314  blackboard->set<std::chrono::milliseconds>("bt_loop_duration", bt_loop_duration_);
315  blackboard->set<std::chrono::milliseconds>(
316  "wait_for_service_timeout", wait_for_service_timeout_);
317  }
318  } catch (const std::exception & e) {
319  setInternalError(ActionT::Result::FAILED_TO_LOAD_BEHAVIOR_TREE,
320  std::string("Exception when creating BT tree from file: ") + e.what());
321  return false;
322  }
323 
324  // Optional logging and monitoring
325  topic_logger_ = std::make_unique<RosTopicLogger>(client_node_, tree_);
326  current_bt_file_or_id_ = file_or_id;
327 
328  if (enable_groot_monitoring_) {
329  bt_->addGrootMonitoring(&tree_, groot_server_port_);
330  RCLCPP_DEBUG(
331  logger_, "Enabling Groot2 monitoring for %s: %d",
332  action_name_.c_str(), groot_server_port_);
333  }
334 
335  return true;
336 }
337 
338 template<class ActionT, class NodeT>
340 {
341  if (!on_goal_received_callback_(action_server_->get_current_goal())) {
342  // Give server an opportunity to populate the result message
343  // if the goal is not accepted
344  auto result = std::make_shared<typename ActionT::Result>();
345  populateErrorCode(result);
346  action_server_->terminate_current(result);
347  cleanErrorCodes();
348  return;
349  }
350 
351  auto is_canceling = [&]() {
352  if (action_server_ == nullptr) {
353  RCLCPP_DEBUG(logger_, "Action server unavailable. Canceling.");
354  return true;
355  }
356  if (!action_server_->is_server_active()) {
357  RCLCPP_DEBUG(logger_, "Action server is inactive. Canceling.");
358  return true;
359  }
360  return action_server_->is_cancel_requested();
361  };
362 
363  auto on_loop = [&]() {
364  if (action_server_->is_preempt_requested() && on_preempt_callback_) {
365  on_preempt_callback_(action_server_->get_pending_goal());
366  }
367  topic_logger_->flush();
368  on_loop_callback_();
369  };
370 
371  // Execute the BT that was previously created in the configure step
372  nav2_behavior_tree::BtStatus rc = bt_->run(&tree_, on_loop, is_canceling, bt_loop_duration_);
373 
374  // Make sure that the Bt is not in a running state from a previous execution
375  // note: if all the ControlNodes are implemented correctly, this is not needed.
376  bt_->haltAllActions(tree_);
377 
378  // Give server an opportunity to populate the result message or simple give
379  // an indication that the action is complete.
380  auto result = std::make_shared<typename ActionT::Result>();
381 
382  populateErrorCode(result);
383 
384  on_completion_callback_(result, rc);
385 
386  switch (rc) {
387  case nav2_behavior_tree::BtStatus::SUCCEEDED:
388  action_server_->succeeded_current(result);
389  RCLCPP_INFO(logger_, "Goal succeeded");
390  break;
391 
392  case nav2_behavior_tree::BtStatus::FAILED:
393  action_server_->terminate_current(result);
394  RCLCPP_ERROR(logger_, "Goal failed error_code:%d error_msg:'%s'", result->error_code,
395  result->error_msg.c_str());
396  break;
397 
398  case nav2_behavior_tree::BtStatus::CANCELED:
399  action_server_->terminate_all(result);
400  RCLCPP_INFO(logger_, "Goal canceled");
401  break;
402  }
403 
404  cleanErrorCodes();
405 }
406 
407 template<class ActionT, class NodeT>
409  uint16_t error_code,
410  const std::string & error_msg)
411 {
412  internal_error_code_ = error_code;
413  internal_error_msg_ = error_msg;
414  RCLCPP_ERROR(logger_, "Setting internal error error_code:%d, error_msg:%s",
415  internal_error_code_, internal_error_msg_.c_str());
416 }
417 
418 template<class ActionT, class NodeT>
420 {
421  internal_error_code_ = ActionT::Result::NONE;
422  internal_error_msg_ = "";
423 }
424 
425 template<class ActionT, class NodeT>
427  typename std::shared_ptr<typename ActionT::Result> result)
428 {
429  if (internal_error_code_ != ActionT::Result::NONE) {
430  result->error_code = internal_error_code_;
431  result->error_msg = internal_error_msg_;
432  return true;
433  }
434  return false;
435 }
436 
437 template<class ActionT, class NodeT>
439  typename std::shared_ptr<typename ActionT::Result> result)
440 {
441  int highest_priority_error_code = std::numeric_limits<int>::max();
442  std::string highest_priority_error_msg = "";
443  std::string name;
444 
445  if (internal_error_code_ != 0) {
446  highest_priority_error_code = internal_error_code_;
447  highest_priority_error_msg = internal_error_msg_;
448  }
449 
450  for (const auto & error_code_name_prefix : error_code_name_prefixes_) {
451  try {
452  name = error_code_name_prefix + "_error_code";
453  int current_error_code = blackboard_->get<int>(name);
454  if (current_error_code != 0 && current_error_code < highest_priority_error_code) {
455  highest_priority_error_code = current_error_code;
456  name = error_code_name_prefix + "_error_msg";
457  highest_priority_error_msg = blackboard_->get<std::string>(name);
458  }
459  } catch (...) {
460  RCLCPP_DEBUG(
461  logger_,
462  "Failed to get error code name: %s from blackboard",
463  name.c_str());
464  }
465  }
466 
467  if (highest_priority_error_code != std::numeric_limits<int>::max()) {
468  result->error_code = highest_priority_error_code;
469  result->error_msg = highest_priority_error_msg;
470  }
471 }
472 
473 template<class ActionT, class NodeT>
475 {
476  std::string name;
477  for (const auto & error_code_name_prefix : error_code_name_prefixes_) {
478  name = error_code_name_prefix + "_error_code";
479  blackboard_->set<unsigned short>(name, 0); //NOLINT
480  name = error_code_name_prefix + "_error_msg";
481  blackboard_->set<std::string>(name, "");
482  }
483  resetInternalError();
484 }
485 
486 } // namespace nav2_behavior_tree
487 
488 #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.