Nav2 Navigation Stack - rolling  main
ROS 2 Navigation Stack
navigate_through_poses.cpp
1 // Copyright (c) 2021 Samsung Research
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 #include <vector>
16 #include <string>
17 #include <set>
18 #include <memory>
19 #include <limits>
20 #include <stdexcept>
21 #include "nav2_bt_navigator/navigators/navigate_through_poses.hpp"
22 #include "nav2_util/path_utils.hpp"
23 #include "nav2_msgs/msg/tracking_feedback.hpp"
24 #include "nav2_ros_common/node_utils.hpp"
25 
26 namespace nav2_bt_navigator
27 {
28 
29 bool
31  nav2::LifecycleNode::WeakPtr parent_node,
32  std::shared_ptr<nav2_util::OdomSmoother> odom_smoother)
33 {
34  start_time_ = rclcpp::Time(0);
35  auto node = parent_node.lock();
36 
37  goals_blackboard_id_ =
38  node->declare_or_get_parameter(getName() + ".goals_blackboard_id", std::string("goals"));
39  path_blackboard_id_ =
40  node->declare_or_get_parameter(getName() + ".path_blackboard_id", std::string("path"));
41  tracking_feedback_blackboard_id_ = node->declare_or_get_parameter(
42  getName() + ".tracking_feedback_blackboard_id",
43  std::string("tracking_feedback"));
44  waypoint_statuses_blackboard_id_ =
45  node->declare_or_get_parameter(
46  getName() + ".waypoint_statuses_blackboard_id",
47  std::string("waypoint_statuses"));
48 
49  search_window_ = node->declare_or_get_parameter(getName() + "search_window", 2.0);
50  transform_staleness_threshold_ = node->declare_or_get_parameter(
51  getName() + ".transform_staleness_threshold", 0.0);
52 
53  // Odometry smoother object for getting current speed
54  odom_smoother_ = odom_smoother;
55 
56  bool enable_groot_monitoring =
57  node->declare_or_get_parameter(getName() + ".enable_groot_monitoring", false);
58  int groot_server_port =
59  node->declare_or_get_parameter(getName() + ".groot_server_port", 1669);
60 
61  bt_action_server_->setGrootMonitoring(
62  enable_groot_monitoring,
63  groot_server_port);
64 
65  return true;
66 }
67 
68 std::string
70  nav2::LifecycleNode::WeakPtr parent_node)
71 {
72  auto node = parent_node.lock();
73  std::string pkg_share_dir =
74  nav2::get_package_share_directory("nav2_bt_navigator");
75 
76  auto default_bt_xml_filename = node->declare_or_get_parameter(
77  "default_nav_through_poses_bt_xml",
78  pkg_share_dir +
79  "/behavior_trees/navigate_through_poses_w_replanning_and_recovery.xml");
80 
81  return default_bt_xml_filename;
82 }
83 
84 bool
85 NavigateThroughPosesNavigator::goalReceived(ActionT::Goal::ConstSharedPtr goal)
86 {
87  return initializeGoalPoses(goal);
88 }
89 
90 void
92  typename ActionT::Result::SharedPtr result,
93  nav2_behavior_tree::BtStatus & final_bt_status)
94 {
95  if (result->error_code == 0) {
96  if (bt_action_server_->populateInternalError(result)) {
97  RCLCPP_WARN(
98  logger_,
99  "NavigateThroughPosesNavigator::goalCompleted, internal error %d:'%s'.",
100  result->error_code,
101  result->error_msg.c_str());
102  }
103  } else {
104  RCLCPP_WARN(
105  logger_, "NavigateThroughPosesNavigator::goalCompleted error %d:'%s'.",
106  result->error_code,
107  result->error_msg.c_str());
108  }
109 
110  // populate waypoint statuses in result
111  auto blackboard = bt_action_server_->getBlackboard();
112  auto waypoint_statuses =
113  blackboard->get<std::vector<nav2_msgs::msg::WaypointStatus>>(waypoint_statuses_blackboard_id_);
114 
115  // populate remaining waypoint statuses based on final_bt_status
116  auto integrate_waypoint_status = final_bt_status == nav2_behavior_tree::BtStatus::SUCCEEDED ?
117  nav2_msgs::msg::WaypointStatus::COMPLETED : nav2_msgs::msg::WaypointStatus::FAILED;
118  for (auto & waypoint_status : waypoint_statuses) {
119  if (waypoint_status.waypoint_status == nav2_msgs::msg::WaypointStatus::PENDING) {
120  waypoint_status.waypoint_status = integrate_waypoint_status;
121  }
122  }
123 
124  result->waypoint_statuses = std::move(waypoint_statuses);
125 }
126 
127 void
129 {
130  using namespace nav2_util::geometry_utils; // NOLINT
131 
132  // action server feedback (pose, duration of task,
133  // number of recoveries, and distance remaining to goal, etc)
134  auto feedback_msg = std::make_shared<ActionT::Feedback>();
135 
136  auto blackboard = bt_action_server_->getBlackboard();
137 
138  nav_msgs::msg::Goals goal_poses;
139  [[maybe_unused]] auto res = blackboard->get(goals_blackboard_id_, goal_poses);
140 
141  feedback_msg->waypoint_statuses =
142  blackboard->get<std::vector<nav2_msgs::msg::WaypointStatus>>(waypoint_statuses_blackboard_id_);
143 
144  if (goal_poses.goals.size() == 0) {
145  bt_action_server_->publishFeedback(feedback_msg);
146  return;
147  }
148 
149  geometry_msgs::msg::PoseStamped current_pose;
150  if (!nav2_util::getFreshPose(
151  *feedback_utils_.tf, feedback_utils_.global_frame,
152  feedback_utils_.robot_frame, clock_->now(),
153  transform_staleness_threshold_, current_pose))
154  {
155  RCLCPP_ERROR(logger_, "Robot pose is not available.");
156  return;
157  }
158 
159  // Get current path points
160  nav_msgs::msg::Path current_path;
161  res = blackboard->get(path_blackboard_id_, current_path);
162  if (res && current_path.poses.size() > 0u) {
163  // Reset start index if path or goal is updated
164  if (nav2_util::isPathUpdated(current_path, previous_path_) ||
165  nav2_util::isGoalUpdated(current_path, previous_path_) ||
166  previous_path_.poses.size() == 0u)
167  {
168  start_index_ = 0;
169  previous_path_ = current_path;
170  }
171  // Find the closest pose to current pose on global path
172  const auto path_search_result = nav2_util::distance_from_path(
173  current_path, current_pose.pose, start_index_, search_window_);
174 
175  // Calculate distance on the path
176  start_index_ = path_search_result.closest_segment_index;
177  double distance_remaining =
178  nav2_util::geometry_utils::calculate_path_length(current_path, start_index_);
179 
180  // Default value for time remaining
181  rclcpp::Duration estimated_time_remaining = rclcpp::Duration::from_seconds(0.0);
182 
183  // Get current speed
184  geometry_msgs::msg::Twist current_odom = odom_smoother_->getTwist();
185  double current_linear_speed = std::hypot(current_odom.linear.x, current_odom.linear.y);
186 
187  // Calculate estimated time taken to goal if speed is higher than 1cm/s
188  // and at least 10cm to go
189  if ((std::abs(current_linear_speed) > 0.01) && (distance_remaining > 0.1)) {
190  estimated_time_remaining =
191  rclcpp::Duration::from_seconds(distance_remaining / std::abs(current_linear_speed));
192  }
193 
194  feedback_msg->distance_remaining = distance_remaining;
195  feedback_msg->estimated_time_remaining = estimated_time_remaining;
196  }
197 
198  int recovery_count = 0;
199  res = blackboard->get("number_recoveries", recovery_count);
200  feedback_msg->number_of_recoveries = recovery_count;
201  feedback_msg->current_pose = current_pose;
202  feedback_msg->navigation_time = clock_->now() - start_time_;
203  feedback_msg->number_of_poses_remaining = goal_poses.goals.size();
204  nav2_msgs::msg::TrackingFeedback tracking_feedback;
205  res = blackboard->get(
206  tracking_feedback_blackboard_id_,
207  tracking_feedback);
208  feedback_msg->position_tracking_error = tracking_feedback.position_tracking_error;
209  feedback_msg->heading_tracking_error = tracking_feedback.heading_tracking_error;
210 
211  bt_action_server_->publishFeedback(feedback_msg);
212 }
213 
214 void
215 NavigateThroughPosesNavigator::onPreempt(ActionT::Goal::ConstSharedPtr goal)
216 {
217  RCLCPP_INFO(logger_, "Received goal preemption request");
218 
219  if (goal->behavior_tree == bt_action_server_->getCurrentBTFilenameOrID() ||
220  (goal->behavior_tree.empty() &&
221  bt_action_server_->getCurrentBTFilenameOrID() == bt_action_server_->getDefaultBTFilenameOrID()))
222  {
223  // if pending goal requests the same BT as the current goal, accept the pending goal
224  // if pending goal has an empty behavior_tree field, it requests the default BT file
225  // accept the pending goal if the current goal is running the default BT file
226  if (!initializeGoalPoses(bt_action_server_->acceptPendingGoal())) {
227  throw std::runtime_error(
228  "Preemption request was rejected since the goal poses could not be "
229  "transformed.");
230  }
231  } else {
232  RCLCPP_WARN(
233  logger_,
234  "Preemption request was rejected since the requested BT XML file is not the same "
235  "as the one that the current goal is executing. Preemption with a new BT is invalid "
236  "since it would require cancellation of the previous goal instead of true preemption."
237  "\nCancel the current goal and send a new action request if you want to use a "
238  "different BT XML file. For now, continuing to track the last goal until completion.");
239  bt_action_server_->terminatePendingGoal();
240  }
241 }
242 
243 bool
244 NavigateThroughPosesNavigator::initializeGoalPoses(ActionT::Goal::ConstSharedPtr goal)
245 {
246  geometry_msgs::msg::PoseStamped current_pose;
247  if (!nav2_util::getFreshPose(
248  *feedback_utils_.tf, feedback_utils_.global_frame,
249  feedback_utils_.robot_frame, clock_->now(),
250  transform_staleness_threshold_, current_pose))
251  {
252  bt_action_server_->setInternalError(
253  ActionT::Result::TF_ERROR,
254  "Initial robot pose is not available.");
255  return false;
256  }
257 
258  nav_msgs::msg::Goals goals_array = goal->poses;
259  int i = 0;
260  for (auto & goal_pose : goals_array.goals) {
261  if (!nav2_util::transformPoseInTargetFrame(
262  goal_pose, goal_pose, *feedback_utils_.tf, feedback_utils_.global_frame,
263  feedback_utils_.transform_tolerance))
264  {
265  bt_action_server_->setInternalError(
266  ActionT::Result::TF_ERROR,
267  "Failed to transform a goal pose (" + std::to_string(i) + ") provided with frame_id '" +
268  goal_pose.header.frame_id +
269  "' to the global frame '" +
270  feedback_utils_.global_frame +
271  "'.");
272  return false;
273  }
274  i++;
275  }
276 
277  if (goals_array.goals.size() > 0) {
278  RCLCPP_INFO(
279  logger_, "Begin navigating from current location through %zu poses to (%.2f, %.2f)",
280  goals_array.goals.size(), goals_array.goals.back().pose.position.x,
281  goals_array.goals.back().pose.position.y);
282  }
283 
284  // Reset state for new action feedback
285  start_time_ = clock_->now();
286  auto blackboard = bt_action_server_->getBlackboard();
287  blackboard->set("number_recoveries", 0); // NOLINT
288  previous_path_ = nav_msgs::msg::Path();
289 
290  // Update the goal pose and path on the blackboard
291  blackboard->set<nav_msgs::msg::Goals>(
292  goals_blackboard_id_,
293  std::move(goals_array));
294  blackboard->set<nav_msgs::msg::Path>(
295  path_blackboard_id_,
296  nav_msgs::msg::Path());
297 
298  // Reset the waypoint states vector in the blackboard
299  std::vector<nav2_msgs::msg::WaypointStatus> waypoint_statuses(goals_array.goals.size());
300  for (size_t waypoint_index = 0; waypoint_index < goals_array.goals.size(); ++waypoint_index) {
301  waypoint_statuses[waypoint_index].waypoint_index = waypoint_index;
302  waypoint_statuses[waypoint_index].waypoint_pose = goals_array.goals[waypoint_index];
303  }
304  blackboard->set<decltype(waypoint_statuses)>(
305  waypoint_statuses_blackboard_id_,
306  std::move(waypoint_statuses));
307 
308  return true;
309 }
310 
311 } // namespace nav2_bt_navigator
312 
313 #include "pluginlib/class_list_macros.hpp"
314 PLUGINLIB_EXPORT_CLASS(
A navigator for navigating to a a bunch of intermediary poses.
void goalCompleted(typename ActionT::Result::SharedPtr result, nav2_behavior_tree::BtStatus &final_bt_status) override
A callback that is called when a the action is completed, can fill in action result message or indica...
bool configure(nav2::LifecycleNode::WeakPtr node, std::shared_ptr< nav2_util::OdomSmoother > odom_smoother) override
A configure state transition to configure navigator's state.
void onPreempt(ActionT::Goal::ConstSharedPtr goal) override
A callback that is called when a preempt is requested.
std::string getName() override
Get action name for this navigator.
bool goalReceived(ActionT::Goal::ConstSharedPtr goal) override
A callback to be called when a new goal is received by the BT action server Can be used to check if g...
bool initializeGoalPoses(ActionT::Goal::ConstSharedPtr goal)
Goal pose initialization on the blackboard.
std::string getDefaultBTFilepath(nav2::LifecycleNode::WeakPtr node) override
Get navigator's default BT.
void onLoop() override
A callback that defines execution that happens on one iteration through the BT Can be used to publish...
Navigator interface to allow navigators to be stored in a vector and accessed via pluginlib due to te...