Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
bt_service_node.hpp
1 // Copyright (c) 2019 Samsung Research America
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_SERVICE_NODE_HPP_
16 #define NAV2_BEHAVIOR_TREE__BT_SERVICE_NODE_HPP_
17 
18 #include <string>
19 #include <memory>
20 #include <chrono>
21 
22 #include "behaviortree_cpp/action_node.h"
23 #include "behaviortree_cpp/json_export.h"
24 #include "nav2_ros_common/node_utils.hpp"
25 #include "nav2_ros_common/lifecycle_node.hpp"
26 #include "nav2_behavior_tree/bt_utils.hpp"
27 #include "nav2_behavior_tree/json_utils.hpp"
28 #include "nav2_ros_common/service_client.hpp"
29 
30 namespace nav2_behavior_tree
31 {
32 
33 using namespace std::chrono_literals; // NOLINT
34 
40 template<class ServiceT>
41 class BtServiceNode : public BT::ActionNodeBase
42 {
43 public:
51  const std::string & service_node_name,
52  const BT::NodeConfiguration & conf,
53  const std::string & service_name = "")
54  : BT::ActionNodeBase(service_node_name, conf), service_name_(service_name), service_node_name_(
55  service_node_name)
56  {
57  initialize();
58 
59  // Make a request for the service without parameter
60  request_ = std::make_shared<typename ServiceT::Request>();
61 
62  // Make sure the server is actually there before continuing
63  RCLCPP_DEBUG(
64  node_->get_logger(), "Waiting for \"%s\" service",
65  service_name_.c_str());
66  if (!service_client_->wait_for_service(wait_for_service_timeout_)) {
67  RCLCPP_ERROR(
68  node_->get_logger(), "\"%s\" service server not available after waiting for %.2fs",
69  service_name_.c_str(), wait_for_service_timeout_.count() / 1000.0);
70  throw std::runtime_error(
71  std::string("Service server ") + service_name_ +
72  std::string(" not available"));
73  }
74 
75  RCLCPP_DEBUG(
76  node_->get_logger(), "\"%s\" BtServiceNode initialized",
77  service_node_name_.c_str());
78  }
79 
80  BtServiceNode() = delete;
81 
82  virtual ~BtServiceNode()
83  {
84  }
85 
89  void initialize()
90  {
91  // Get the required items from the blackboard
92  auto bt_loop_duration =
93  config().blackboard->template get<std::chrono::milliseconds>("bt_loop_duration");
94  getInputOrBlackboard("server_timeout", server_timeout_);
95  wait_for_service_timeout_ =
96  config().blackboard->template get<std::chrono::milliseconds>("wait_for_service_timeout");
97 
98  // timeout should be less than bt_loop_duration to be able to finish the current tick
99  max_timeout_ = std::chrono::duration_cast<std::chrono::milliseconds>(bt_loop_duration * 0.5);
100 
101  // Now that we have node_ to use, create the service client for this BT service
102  createROSInterfaces();
103  }
104 
109  {
110  std::string service_new;
111  getInput("service_name", service_new);
112  // If port_name is empty, fallback to the service name provided in the constructor.
113  // If that is empty too, throw an error.
114  service_new = service_new.empty() ? service_name_ : service_new;
115  if (service_new.empty()) {
116  throw std::runtime_error(
117  std::string("Service name not provided for ") + service_node_name_ +
118  std::string(" BT node"));
119  }
120 
121  if (service_new != service_name_ || !service_client_) {
122  service_name_ = service_new;
123  node_ = config().blackboard->template get<nav2::LifecycleNode::SharedPtr>("node");
124  service_client_ =
125  node_->create_client<ServiceT>(
126  service_name_, true /*creates and spins an internal executor*/);
127  }
128  }
129 
136  static BT::PortsList providedBasicPorts(BT::PortsList addition)
137  {
138  BT::PortsList basic = {
139  BT::InputPort<std::string>("service_name", "please_set_service_name_in_BT_Node"),
140  BT::InputPort<std::chrono::milliseconds>("server_timeout")
141  };
142  basic.insert(addition.begin(), addition.end());
143 
144  return basic;
145  }
146 
151  static BT::PortsList providedPorts()
152  {
153  return providedBasicPorts({});
154  }
155 
160  BT::NodeStatus tick() override
161  {
162  if (!BT::isStatusActive(status())) {
163  initialize();
164  }
165 
166  if (!request_sent_) {
167  // reset the flag to send the request or not,
168  // allowing the user the option to set it in on_tick
169  should_send_request_ = true;
170 
171  // Clear the input request to make sure we have no leftover from previous calls
172  request_ = std::make_shared<typename ServiceT::Request>();
173 
174  // user defined callback, may modify "should_send_request_".
175  on_tick();
176 
177  if (!should_send_request_) {
178  return BT::NodeStatus::FAILURE;
179  }
180 
181  future_result_ = service_client_->async_call(request_);
182  sent_time_ = node_->now();
183  request_sent_ = true;
184  }
185  return check_future();
186  }
187 
191  void halt() override
192  {
193  request_sent_ = false;
194  resetStatus();
195  }
196 
201  virtual void on_tick()
202  {
203  }
204 
211  virtual BT::NodeStatus on_completion(std::shared_ptr<typename ServiceT::Response>/*response*/)
212  {
213  return BT::NodeStatus::SUCCESS;
214  }
215 
219  virtual void on_timeout()
220  {
221  return;
222  }
223 
228  virtual BT::NodeStatus check_future()
229  {
230  auto elapsed = (node_->now() - sent_time_).template to_chrono<std::chrono::milliseconds>();
231  auto remaining = server_timeout_ - elapsed;
232 
233  if (remaining > std::chrono::milliseconds(0)) {
234  auto timeout = remaining > max_timeout_ ? max_timeout_ : remaining;
235 
236  rclcpp::FutureReturnCode rc;
237  rc = service_client_->spin_until_complete(future_result_, timeout);
238  if (rc == rclcpp::FutureReturnCode::SUCCESS) {
239  request_sent_ = false;
240  BT::NodeStatus status = on_completion(future_result_.get());
241  return status;
242  }
243 
244  if (rc == rclcpp::FutureReturnCode::TIMEOUT) {
245  on_wait_for_result();
246  elapsed = (node_->now() - sent_time_).template to_chrono<std::chrono::milliseconds>();
247  if (elapsed < server_timeout_) {
248  return BT::NodeStatus::RUNNING;
249  }
250  }
251  }
252 
253  RCLCPP_WARN(
254  node_->get_logger(),
255  "Node timed out while executing service call to %s.", service_name_.c_str());
256  on_timeout();
257  request_sent_ = false;
258  return BT::NodeStatus::FAILURE;
259  }
260 
265  virtual void on_wait_for_result()
266  {
267  }
268 
269 protected:
274  {
275  int recovery_count = 0;
276  [[maybe_unused]] auto res = config().blackboard->get("number_recoveries", recovery_count); // NOLINT
277  recovery_count += 1;
278  config().blackboard->set("number_recoveries", recovery_count); // NOLINT
279  }
280 
281  std::string service_name_, service_node_name_;
282  typename nav2::ServiceClient<ServiceT>::SharedPtr service_client_;
283  std::shared_ptr<typename ServiceT::Request> request_;
284 
285  // The node that will be used for any ROS operations
286  nav2::LifecycleNode::SharedPtr node_;
287 
288  // The timeout value while to use in the tick loop while waiting for
289  // a result from the server
290  std::chrono::milliseconds server_timeout_;
291 
292  // The timeout value for BT loop execution
293  std::chrono::milliseconds max_timeout_;
294 
295  // The timeout value for waiting for a service to response
296  std::chrono::milliseconds wait_for_service_timeout_;
297 
298  // To track the server response when a new request is sent
299  std::shared_future<typename ServiceT::Response::SharedPtr> future_result_;
300  bool request_sent_{false};
301  rclcpp::Time sent_time_;
302 
303  // Can be set in on_tick or on_wait_for_result to indicate if a request should be sent.
304  bool should_send_request_;
305 };
306 
307 } // namespace nav2_behavior_tree
308 
309 #endif // NAV2_BEHAVIOR_TREE__BT_SERVICE_NODE_HPP_
Abstract class representing a service based BT node.
BT::NodeStatus tick() override
The main override required by a BT service.
virtual void on_tick()
Function to perform some user-defined operation on tick Fill in service request with information if n...
static BT::PortsList providedPorts()
Creates list of BT ports.
void initialize()
Function to read parameters and initialize class variables.
void createROSInterfaces()
Function to create ROS interfaces.
static BT::PortsList providedBasicPorts(BT::PortsList addition)
Any subclass of BtServiceNode that accepts parameters must provide a providedPorts method and call pr...
virtual BT::NodeStatus check_future()
Check the future and decide the status of BT.
virtual void on_timeout()
Function to perform work in a BT Node when the service call times out.
void halt() override
The other (optional) override required by a BT service.
void increment_recovery_count()
Function to increment recovery count on blackboard if this node wraps a recovery.
virtual void on_wait_for_result()
Function to perform some user-defined operation after a timeout waiting for a result that hasn't been...
BtServiceNode(const std::string &service_node_name, const BT::NodeConfiguration &conf, const std::string &service_name="")
A nav2_behavior_tree::BtServiceNode constructor.
virtual BT::NodeStatus on_completion(std::shared_ptr< typename ServiceT::Response >)
Function to perform some user-defined operation upon successful completion of the service....