Nav2 Navigation Stack - rolling  main
ROS 2 Navigation Stack
planner_server.cpp
1 // Copyright (c) 2018 Intel Corporation
2 // Copyright (c) 2019 Samsung Research America
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 
16 #include <chrono>
17 #include <cmath>
18 #include <iomanip>
19 #include <iostream>
20 #include <limits>
21 #include <iterator>
22 #include <memory>
23 #include <string>
24 #include <vector>
25 #include <utility>
26 
27 #include "lifecycle_msgs/msg/state.hpp"
28 #include "nav2_util/costmap.hpp"
29 #include "nav2_ros_common/node_utils.hpp"
30 #include "nav2_util/geometry_utils.hpp"
31 #include "nav2_costmap_2d/cost_values.hpp"
32 #include "nav2_costmap_2d/costmap_layer.hpp"
33 #include "nav2_costmap_2d/layered_costmap.hpp"
34 
35 #include "tf2/utils.hpp"
36 
37 #include "nav2_planner/planner_server.hpp"
38 
39 using namespace std::chrono_literals;
40 using rcl_interfaces::msg::ParameterType;
41 using std::placeholders::_1;
42 
43 namespace nav2_planner
44 {
45 
46 PlannerServer::PlannerServer(const rclcpp::NodeOptions & options)
47 : nav2::LifecycleNode("planner_server", "", options),
48  gp_loader_("nav2_core", "nav2_core::GlobalPlanner"),
49  costmap_(nullptr)
50 {
51  RCLCPP_INFO(get_logger(), "Creating");
52  // Setup the global costmap
53  costmap_ros_ = std::make_shared<nav2_costmap_2d::Costmap2DROS>(
54  "global_costmap", std::string{get_namespace()},
55  get_parameter("use_sim_time").as_bool(), options);
56 }
57 
59 {
60  /*
61  * Backstop ensuring this state is destroyed, even if deactivate/cleanup are
62  * never called.
63  */
64  planners_.clear();
65  costmap_thread_.reset();
66 }
67 
68 nav2::CallbackReturn
69 PlannerServer::on_configure(const rclcpp_lifecycle::State & state)
70 {
71  RCLCPP_INFO(get_logger(), "Configuring");
72  auto node = shared_from_this();
73 
74  if (costmap_ros_->configure().id() !=
75  lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE)
76  {
77  return nav2::CallbackReturn::FAILURE;
78  }
79  costmap_ = costmap_ros_->getCostmap();
80 
81  // Launch a thread to run the costmap node
82  costmap_thread_ = std::make_unique<nav2::NodeThread>(costmap_ros_);
83 
84  RCLCPP_DEBUG(
85  get_logger(), "Costmap size: %d,%d",
86  costmap_->getSizeInCellsX(), costmap_->getSizeInCellsY());
87 
88  tf_ = costmap_ros_->getTfBuffer();
89  try {
90  param_handler_ = std::make_unique<ParameterHandler>(
91  node, get_logger());
92  } catch (const std::exception & ex) {
93  RCLCPP_FATAL(get_logger(), "%s", ex.what());
94  on_cleanup(state);
95  return nav2::CallbackReturn::FAILURE;
96  }
97  params_ = param_handler_->getParams();
98 
99  for (size_t i = 0; i != params_->planner_ids.size(); i++) {
100  try {
101  nav2_core::GlobalPlanner::Ptr planner =
102  gp_loader_.createUniqueInstance(params_->planner_types[i]);
103  RCLCPP_INFO(
104  get_logger(), "Created global planner plugin %s of type %s",
105  params_->planner_ids[i].c_str(), params_->planner_types[i].c_str());
106  planner->configure(node, params_->planner_ids[i], tf_, costmap_ros_);
107  planners_.insert({params_->planner_ids[i], planner});
108  } catch (const std::exception & ex) {
109  RCLCPP_FATAL(
110  get_logger(), "Failed to create global planner. Exception: %s",
111  ex.what());
112  on_cleanup(state);
113  return nav2::CallbackReturn::FAILURE;
114  }
115  }
116 
117  for (size_t i = 0; i != params_->planner_ids.size(); i++) {
118  planner_ids_concat_ += params_->planner_ids[i] + std::string(" ");
119  }
120 
121  RCLCPP_INFO(
122  get_logger(),
123  "Planner Server has %s planners available.", planner_ids_concat_.c_str());
124 
125  // Initialize pubs & subs
126  plan_publisher_ = create_publisher<nav_msgs::msg::Path>("plan");
127 
128  // Create is path valid service
129  is_path_valid_service_ = std::make_unique<IsPathValidService>(
130  shared_from_this(), costmap_ros_, params_->costmap_update_timeout);
131 
132  // Create the action servers for path planning to a pose and through poses
133  action_server_pose_ = create_action_server<ActionToPose>(
134  "compute_path_to_pose",
135  std::bind(&PlannerServer::computePlan, this),
136  std::bind(&PlannerServer::goalReceived<ActionToPose>, this, std::placeholders::_1),
137  nullptr,
138  std::chrono::milliseconds(500),
139  true);
140 
141  action_server_poses_ = create_action_server<ActionThroughPoses>(
142  "compute_path_through_poses",
143  std::bind(&PlannerServer::computePlanThroughPoses, this),
144  std::bind(&PlannerServer::goalReceived<ActionThroughPoses>, this, std::placeholders::_1),
145  nullptr,
146  std::chrono::milliseconds(500),
147  true);
148 
149  return nav2::CallbackReturn::SUCCESS;
150 }
151 
152 nav2::CallbackReturn
153 PlannerServer::on_activate(const rclcpp_lifecycle::State & /*state*/)
154 {
155  RCLCPP_INFO(get_logger(), "Activating");
156 
157  plan_publisher_->on_activate();
158  action_server_pose_->activate();
159  action_server_poses_->activate();
160  param_handler_->activate();
161  const auto costmap_ros_state = costmap_ros_->activate();
162  if (costmap_ros_state.id() != lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE) {
163  return nav2::CallbackReturn::FAILURE;
164  }
165 
166  PlannerMap::iterator it;
167  for (it = planners_.begin(); it != planners_.end(); ++it) {
168  it->second->activate();
169  }
170 
171  is_path_valid_service_->initialize();
172 
173  // create bond connection
174  createBond();
175 
176  return nav2::CallbackReturn::SUCCESS;
177 }
178 
179 nav2::CallbackReturn
180 PlannerServer::on_deactivate(const rclcpp_lifecycle::State & /*state*/)
181 {
182  RCLCPP_INFO(get_logger(), "Deactivating");
183 
184  action_server_pose_->deactivate();
185  action_server_poses_->deactivate();
186  plan_publisher_->on_deactivate();
187  param_handler_->deactivate();
188 
189  /*
190  * The costmap is also a lifecycle node, so it may have already fired on_deactivate
191  * via rcl preshutdown cb. Despite the rclcpp docs saying on_shutdown callbacks fire
192  * in the order added, the preshutdown callbacks clearly don't per se, due to using an
193  * unordered_set iteration. Once this issue is resolved, we can maybe make a stronger
194  * ordering assumption: https://github.com/ros2/rclcpp/issues/2096
195  */
196  costmap_ros_->deactivate();
197 
198  PlannerMap::iterator it;
199  for (it = planners_.begin(); it != planners_.end(); ++it) {
200  it->second->deactivate();
201  }
202 
203  is_path_valid_service_->reset();
204 
205  // destroy bond connection
206  destroyBond();
207 
208  return nav2::CallbackReturn::SUCCESS;
209 }
210 
211 nav2::CallbackReturn
212 PlannerServer::on_cleanup(const rclcpp_lifecycle::State & /*state*/)
213 {
214  RCLCPP_INFO(get_logger(), "Cleaning up");
215 
216  action_server_pose_.reset();
217  action_server_poses_.reset();
218  plan_publisher_.reset();
219  tf_.reset();
220 
221  costmap_ros_->cleanup();
222 
223  PlannerMap::iterator it;
224  for (it = planners_.begin(); it != planners_.end(); ++it) {
225  it->second->cleanup();
226  }
227 
228  planners_.clear();
229  is_path_valid_service_.reset();
230  costmap_thread_.reset();
231  costmap_ = nullptr;
232  return nav2::CallbackReturn::SUCCESS;
233 }
234 
235 nav2::CallbackReturn
236 PlannerServer::on_shutdown(const rclcpp_lifecycle::State &)
237 {
238  RCLCPP_INFO(get_logger(), "Shutting down");
239  return nav2::CallbackReturn::SUCCESS;
240 }
241 
242 template<typename T>
243 bool PlannerServer::goalReceived(std::shared_ptr<const typename T::Goal> goal)
244 {
245  if (planners_.find(goal->planner_id) == planners_.end()) {
246  if (planners_.size() == 1 && goal->planner_id.empty()) {
247  RCLCPP_WARN_ONCE(
248  get_logger(), "No planner was specified in action call. "
249  "Server will use only plugin loaded %s. "
250  "This warning will appear once.", planner_ids_concat_.c_str());
251  return true;
252  }
253 
254  RCLCPP_ERROR(
255  get_logger(), "Action called with planner name %s, "
256  "which does not exist. Available planners are: %s.",
257  goal->planner_id.c_str(), planner_ids_concat_.c_str());
258  return false;
259  }
260 
261  RCLCPP_DEBUG(get_logger(), "Selected planner: %s.", goal->planner_id.c_str());
262  return true;
263 }
264 
265 template<typename T>
267  typename nav2::SimpleActionServer<T>::SharedPtr & action_server)
268 {
269  if (action_server == nullptr || !action_server->is_server_active()) {
270  RCLCPP_DEBUG(get_logger(), "Action server unavailable or inactive. Stopping.");
271  return true;
272  }
273 
274  return false;
275 }
276 
278 {
279  if (params_->costmap_update_timeout > rclcpp::Duration(0, 0)) {
280  auto waiting_start = now();
281  bool was_waiting = !costmap_ros_->isCurrent();
282  try {
283  costmap_ros_->waitUntilCurrent(params_->costmap_update_timeout);
284  } catch (const std::runtime_error & ex) {
285  throw nav2_core::PlannerTimedOut(ex.what());
286  }
287  if (was_waiting) {
288  return (now() - waiting_start).seconds();
289  }
290  }
291  return 0.0;
292 }
293 
294 template<typename T>
296  typename nav2::SimpleActionServer<T>::SharedPtr & action_server)
297 {
298  if (action_server->is_cancel_requested()) {
299  RCLCPP_INFO(get_logger(), "Goal was canceled. Canceling planning action.");
300  action_server->terminate_all();
301  return true;
302  }
303 
304  return false;
305 }
306 
307 template<typename T>
309  typename nav2::SimpleActionServer<T>::SharedPtr & action_server,
310  typename std::shared_ptr<const typename T::Goal> & goal)
311 {
312  if (action_server->is_preempt_requested()) {
313  goal = action_server->accept_pending_goal();
314  }
315 }
316 
317 template<typename T>
319  typename std::shared_ptr<const typename T::Goal> goal,
320  geometry_msgs::msg::PoseStamped & start)
321 {
322  if (goal->use_start) {
323  start = goal->start;
324  } else if (!costmap_ros_->getRobotPose(start)) {
325  return false;
326  }
327 
328  return true;
329 }
330 
332  geometry_msgs::msg::PoseStamped & curr_start,
333  geometry_msgs::msg::PoseStamped & curr_goal)
334 {
335  if (!costmap_ros_->transformPoseToGlobalFrame(curr_start, curr_start) ||
336  !costmap_ros_->transformPoseToGlobalFrame(curr_goal, curr_goal))
337  {
338  return false;
339  }
340 
341  return true;
342 }
343 
344 template<typename T>
346  const geometry_msgs::msg::PoseStamped & goal,
347  const nav_msgs::msg::Path & path,
348  const std::string & planner_id)
349 {
350  if (path.poses.empty()) {
351  RCLCPP_WARN(
352  get_logger(), "Planning algorithm %s failed to generate a valid"
353  " path to (%.2f, %.2f)", planner_id.c_str(),
354  goal.pose.position.x, goal.pose.position.y);
355  return false;
356  }
357 
358  RCLCPP_DEBUG(
359  get_logger(),
360  "Found valid path of size %zu to (%.2f, %.2f)",
361  path.poses.size(), goal.pose.position.x,
362  goal.pose.position.y);
363 
364  return true;
365 }
366 
368 {
369  std::lock_guard<std::mutex> lock_reinit(param_handler_->getMutex());
370 
371  auto start_time = this->now();
372 
373  // Initialize the ComputePathThroughPoses goal and result
374  auto goal = action_server_poses_->get_current_goal();
375  auto result = std::make_shared<ActionThroughPoses::Result>();
376  nav_msgs::msg::Path concat_path;
377  RCLCPP_INFO(get_logger(), "Computing path through poses to goal.");
378 
379  geometry_msgs::msg::PoseStamped curr_start, curr_goal;
380 
381  try {
382  if (isServerInactive<ActionThroughPoses>(action_server_poses_) ||
383  isCancelRequested<ActionThroughPoses>(action_server_poses_))
384  {
385  return;
386  }
387 
388  double costmap_wait = waitForCostmap();
389 
390  getPreemptedGoalIfRequested<ActionThroughPoses>(action_server_poses_, goal);
391 
392  if (goal->goals.goals.empty()) {
393  throw nav2_core::NoViapointsGiven("No viapoints given");
394  }
395 
396  // Use start pose if provided otherwise use current robot pose
397  geometry_msgs::msg::PoseStamped start;
398  if (!getStartPose<ActionThroughPoses>(goal, start)) {
399  throw nav2_core::PlannerTFError("Unable to get start pose");
400  }
401 
402  auto cancel_checker = [this]() {
403  return action_server_poses_->is_cancel_requested();
404  };
405 
406  // Get consecutive paths through these points
407  for (unsigned int i = 0; i != goal->goals.goals.size(); i++) {
408  // Get starting point
409  if (i == 0) {
410  curr_start = start;
411  } else {
412  // pick the end of the last planning task as the start for the next one
413  // to allow for path tolerance deviations
414  curr_start = concat_path.poses.back();
415  curr_start.header = concat_path.header;
416  }
417  curr_goal = goal->goals.goals[i];
418 
419  // Transform them into the global frame
420  if (!transformPosesToGlobalFrame(curr_start, curr_goal)) {
421  throw nav2_core::PlannerTFError("Unable to transform poses to global frame");
422  }
423 
424  // Get plan from start -> goal
425  nav_msgs::msg::Path curr_path;
426  std::vector<geometry_msgs::msg::PoseStamped> viapoints;
427  try {
428  curr_path = getPlan(curr_start, curr_goal, viapoints, goal->planner_id, cancel_checker);
429  } catch (nav2_core::PlannerException & ex) {
430  if (i == 0 || !params_->partial_plan_allowed) {
431  throw;
432  }
433 
434  exceptionWarning(curr_start, curr_goal, goal->planner_id, ex, result->error_msg);
435  RCLCPP_WARN(get_logger(),
436  "Planner server failed to compute full path. Outputting partial path instead.");
437  break;
438  }
439 
440  if (!validatePath<ActionThroughPoses>(curr_goal, curr_path, goal->planner_id)) {
441  auto exception =
442  nav2_core::NoValidPathCouldBeFound(goal->planner_id + " generated a empty path");
443 
444  if (i == 0 || !params_->partial_plan_allowed) {
445  throw exception;
446  }
447 
448  exceptionWarning(curr_start, curr_goal, goal->planner_id, exception, result->error_msg);
449  RCLCPP_WARN(get_logger(),
450  "Planner server failed to compute full path. Outputting partial path instead.");
451  break;
452  }
453 
454  // Concatenate paths together, but skip the first pose of subsequent paths
455  // to avoid duplicating the connection point
456  size_t curr_path_size = curr_path.poses.size();
457  if (i == 0 || curr_path_size == 1) {
458  // First path or single-posed path: add all poses
459  concat_path.poses.insert(
460  concat_path.poses.end(), curr_path.poses.begin(), curr_path.poses.end());
461  } else if (curr_path_size > 1) {
462  // Subsequent paths: skip the first pose to avoid duplication
463  concat_path.poses.insert(
464  concat_path.poses.end(), curr_path.poses.begin() + 1, curr_path.poses.end());
465  }
466  concat_path.header = curr_path.header;
467 
468  if (i == goal->goals.goals.size() - 1) {
469  result->last_reached_index = ActionThroughPosesResult::ALL_GOALS;
470  } else {
471  result->last_reached_index = i;
472  }
473  }
474 
475  // Publish the plan for visualization purposes
476  result->path = concat_path;
477  publishPlan(result->path);
478 
479  auto cycle_duration = this->now() - start_time;
480  result->planning_time = cycle_duration;
481 
482  if (params_->max_planner_duration && cycle_duration.seconds() > params_->max_planner_duration) {
483  RCLCPP_WARN(
484  get_logger(),
485  "Planner loop missed its desired rate of %.4f Hz. Current loop rate is %.4f Hz"
486  "%s",
487  1 / params_->max_planner_duration, 1 / cycle_duration.seconds(),
488  costmap_wait > 0.0 ?
489  (" Waited " + std::to_string(costmap_wait) + "s for costmap update.").c_str() : "");
490  }
491 
492  action_server_poses_->succeeded_current(result);
493  } catch (nav2_core::InvalidPlanner & ex) {
494  exceptionWarning(curr_start, curr_goal, goal->planner_id, ex, result->error_msg);
495  result->error_code = ActionThroughPosesResult::INVALID_PLANNER;
496  action_server_poses_->terminate_current(result);
497  } catch (nav2_core::StartOccupied & ex) {
498  exceptionWarning(curr_start, curr_goal, goal->planner_id, ex, result->error_msg);
499  result->error_code = ActionThroughPosesResult::START_OCCUPIED;
500  action_server_poses_->terminate_current(result);
501  } catch (nav2_core::GoalOccupied & ex) {
502  exceptionWarning(curr_start, curr_goal, goal->planner_id, ex, result->error_msg);
503  result->error_code = ActionThroughPosesResult::GOAL_OCCUPIED;
504  action_server_poses_->terminate_current(result);
505  } catch (nav2_core::NoValidPathCouldBeFound & ex) {
506  exceptionWarning(curr_start, curr_goal, goal->planner_id, ex, result->error_msg);
507  result->error_code = ActionThroughPosesResult::NO_VALID_PATH;
508  action_server_poses_->terminate_current(result);
509  } catch (nav2_core::PlannerTimedOut & ex) {
510  exceptionWarning(curr_start, curr_goal, goal->planner_id, ex, result->error_msg);
511  result->error_code = ActionThroughPosesResult::TIMEOUT;
512  action_server_poses_->terminate_current(result);
513  } catch (nav2_core::StartOutsideMapBounds & ex) {
514  exceptionWarning(curr_start, curr_goal, goal->planner_id, ex, result->error_msg);
515  result->error_code = ActionThroughPosesResult::START_OUTSIDE_MAP;
516  action_server_poses_->terminate_current(result);
517  } catch (nav2_core::GoalOutsideMapBounds & ex) {
518  exceptionWarning(curr_start, curr_goal, goal->planner_id, ex, result->error_msg);
519  result->error_code = ActionThroughPosesResult::GOAL_OUTSIDE_MAP;
520  action_server_poses_->terminate_current(result);
521  } catch (nav2_core::PlannerTFError & ex) {
522  exceptionWarning(curr_start, curr_goal, goal->planner_id, ex, result->error_msg);
523  result->error_code = ActionThroughPosesResult::TF_ERROR;
524  action_server_poses_->terminate_current(result);
525  } catch (nav2_core::NoViapointsGiven & ex) {
526  exceptionWarning(curr_start, curr_goal, goal->planner_id, ex, result->error_msg);
527  result->error_code = ActionThroughPosesResult::NO_VIAPOINTS_GIVEN;
528  action_server_poses_->terminate_current(result);
529  } catch (nav2_core::PlannerCancelled &) {
530  result->error_msg = "Goal was canceled. Canceling planning action.";
531  RCLCPP_INFO(get_logger(), "%s", result->error_msg.c_str());
532  action_server_poses_->terminate_all();
533  } catch (std::exception & ex) {
534  exceptionWarning(curr_start, curr_goal, goal->planner_id, ex, result->error_msg);
535  result->error_code = ActionThroughPosesResult::UNKNOWN;
536  action_server_poses_->terminate_current(result);
537  }
538 }
539 
540 void
542 {
543  std::lock_guard<std::mutex> lock_reinit(param_handler_->getMutex());
544 
545  auto start_time = this->now();
546 
547  // Initialize the ComputePathToPose goal and result
548  auto goal = action_server_pose_->get_current_goal();
549  auto result = std::make_shared<ActionToPose::Result>();
550  RCLCPP_INFO(get_logger(), "Computing path to goal.");
551 
552  geometry_msgs::msg::PoseStamped start;
553 
554  try {
555  if (isServerInactive<ActionToPose>(action_server_pose_) ||
556  isCancelRequested<ActionToPose>(action_server_pose_))
557  {
558  return;
559  }
560 
561  double costmap_wait = waitForCostmap();
562 
563  getPreemptedGoalIfRequested<ActionToPose>(action_server_pose_, goal);
564 
565  // Use start pose if provided otherwise use current robot pose
566  if (!getStartPose<ActionToPose>(goal, start)) {
567  throw nav2_core::PlannerTFError("Unable to get start pose");
568  }
569 
570  // Transform them into the global frame
571  geometry_msgs::msg::PoseStamped goal_pose = goal->goal;
572  if (!transformPosesToGlobalFrame(start, goal_pose)) {
573  throw nav2_core::PlannerTFError("Unable to transform poses to global frame");
574  }
575 
576  auto cancel_checker = [this]() {
577  return action_server_pose_->is_cancel_requested();
578  };
579 
580  result->path = getPlan(start, goal_pose, goal->viapoints, goal->planner_id, cancel_checker);
581 
582  if (!validatePath<ActionThroughPoses>(goal_pose, result->path, goal->planner_id)) {
583  throw nav2_core::NoValidPathCouldBeFound(goal->planner_id + " generated a empty path");
584  }
585 
586  // Publish the plan for visualization purposes
587  publishPlan(result->path);
588 
589  auto cycle_duration = this->now() - start_time;
590  result->planning_time = cycle_duration;
591 
592  if (params_->max_planner_duration && cycle_duration.seconds() > params_->max_planner_duration) {
593  RCLCPP_WARN(
594  get_logger(),
595  "Planner loop missed its desired rate of %.4f Hz. Current loop rate is %.4f Hz"
596  "%s",
597  1 / params_->max_planner_duration, 1 / cycle_duration.seconds(),
598  costmap_wait > 0.0 ?
599  (" Waited " + std::to_string(costmap_wait) + "s for costmap update.").c_str() : "");
600  }
601  action_server_pose_->succeeded_current(result);
602  } catch (nav2_core::InvalidPlanner & ex) {
603  exceptionWarning(start, goal->goal, goal->planner_id, ex, result->error_msg);
604  result->error_code = ActionToPoseResult::INVALID_PLANNER;
605  action_server_pose_->terminate_current(result);
606  } catch (nav2_core::StartOccupied & ex) {
607  exceptionWarning(start, goal->goal, goal->planner_id, ex, result->error_msg);
608  result->error_code = ActionToPoseResult::START_OCCUPIED;
609  action_server_pose_->terminate_current(result);
610  } catch (nav2_core::GoalOccupied & ex) {
611  exceptionWarning(start, goal->goal, goal->planner_id, ex, result->error_msg);
612  result->error_code = ActionToPoseResult::GOAL_OCCUPIED;
613  action_server_pose_->terminate_current(result);
614  } catch (nav2_core::NoValidPathCouldBeFound & ex) {
615  exceptionWarning(start, goal->goal, goal->planner_id, ex, result->error_msg);
616  result->error_code = ActionToPoseResult::NO_VALID_PATH;
617  action_server_pose_->terminate_current(result);
618  } catch (nav2_core::PlannerTimedOut & ex) {
619  exceptionWarning(start, goal->goal, goal->planner_id, ex, result->error_msg);
620  result->error_code = ActionToPoseResult::TIMEOUT;
621  action_server_pose_->terminate_current(result);
622  } catch (nav2_core::StartOutsideMapBounds & ex) {
623  exceptionWarning(start, goal->goal, goal->planner_id, ex, result->error_msg);
624  result->error_code = ActionToPoseResult::START_OUTSIDE_MAP;
625  action_server_pose_->terminate_current(result);
626  } catch (nav2_core::GoalOutsideMapBounds & ex) {
627  exceptionWarning(start, goal->goal, goal->planner_id, ex, result->error_msg);
628  result->error_code = ActionToPoseResult::GOAL_OUTSIDE_MAP;
629  action_server_pose_->terminate_current(result);
630  } catch (nav2_core::PlannerTFError & ex) {
631  exceptionWarning(start, goal->goal, goal->planner_id, ex, result->error_msg);
632  result->error_code = ActionToPoseResult::TF_ERROR;
633  action_server_pose_->terminate_current(result);
634  } catch (nav2_core::PlannerCancelled &) {
635  result->error_msg = "Goal was canceled. Canceling planning action.";
636  RCLCPP_INFO(get_logger(), "%s", result->error_msg.c_str());
637  action_server_pose_->terminate_all();
638  } catch (std::exception & ex) {
639  exceptionWarning(start, goal->goal, goal->planner_id, ex, result->error_msg);
640  result->error_code = ActionToPoseResult::UNKNOWN;
641  action_server_pose_->terminate_current(result);
642  }
643 }
644 
645 nav_msgs::msg::Path
647  const geometry_msgs::msg::PoseStamped & start,
648  const geometry_msgs::msg::PoseStamped & goal,
649  const std::vector<geometry_msgs::msg::PoseStamped> & viapoints,
650  const std::string & planner_id,
651  std::function<bool()> cancel_checker)
652 {
653  RCLCPP_DEBUG(
654  get_logger(), "Attempting to a find path from (%.2f, %.2f) to "
655  "(%.2f, %.2f).", start.pose.position.x, start.pose.position.y,
656  goal.pose.position.x, goal.pose.position.y);
657 
658  if (planners_.find(planner_id) != planners_.end()) {
659  return planners_[planner_id]->createPlan(start, goal, viapoints, cancel_checker);
660  } else {
661  if (planners_.size() == 1 && planner_id.empty()) {
662  RCLCPP_WARN_ONCE(
663  get_logger(), "No planners specified in action call. "
664  "Server will use only plugin %s in server."
665  " This warning will appear once.", planner_ids_concat_.c_str());
666  return planners_[planners_.begin()->first]->createPlan(start, goal, viapoints,
667  cancel_checker);
668  } else {
669  RCLCPP_ERROR(
670  get_logger(), "planner %s is not a valid planner. "
671  "Planner names are: %s", planner_id.c_str(),
672  planner_ids_concat_.c_str());
673  throw nav2_core::InvalidPlanner("Planner id " + planner_id + " is invalid");
674  }
675  }
676 
677  return nav_msgs::msg::Path();
678 }
679 
680 void
681 PlannerServer::publishPlan(const nav_msgs::msg::Path & path)
682 {
683  auto msg = std::make_unique<nav_msgs::msg::Path>(path);
684  if (plan_publisher_->is_activated() && plan_publisher_->get_subscription_count() > 0) {
685  plan_publisher_->publish(std::move(msg));
686  }
687 }
688 
689 void PlannerServer::exceptionWarning(
690  const geometry_msgs::msg::PoseStamped & start,
691  const geometry_msgs::msg::PoseStamped & goal,
692  const std::string & planner_id,
693  const std::exception & ex,
694  std::string & error_msg)
695 {
696  std::stringstream ss;
697  ss << std::fixed << std::setprecision(2)
698  << planner_id << "plugin failed to plan from ("
699  << start.pose.position.x << ", " << start.pose.position.y
700  << ") [q: "
701  << start.pose.orientation.x << ", " << start.pose.orientation.y << ", "
702  << start.pose.orientation.z << ", " << start.pose.orientation.w
703  << "] (yaw: " << tf2::getYaw(start.pose.orientation)
704  << ") to ("
705  << goal.pose.position.x << ", " << goal.pose.position.y << ")"
706  << " [q: "
707  << goal.pose.orientation.x << ", " << goal.pose.orientation.y << ", "
708  << goal.pose.orientation.z << ", " << goal.pose.orientation.w
709  << "] (yaw: " << tf2::getYaw(goal.pose.orientation)
710  << ")"
711  << ": \"" << ex.what() << "\"";
712 
713  error_msg = ss.str();
714  RCLCPP_WARN(get_logger(), "%s", error_msg.c_str());
715 }
716 
717 } // namespace nav2_planner
718 
719 #include "rclcpp_components/register_node_macro.hpp"
720 
721 // Register the component with class_loader.
722 // This acts as a sort of entry point, allowing the component to be discoverable when its library
723 // is being loaded into a running process.
724 RCLCPP_COMPONENTS_REGISTER_NODE(nav2_planner::PlannerServer)
void destroyBond()
Destroy bond connection to lifecycle manager.
nav2::LifecycleNode::SharedPtr shared_from_this()
Get a shared pointer of this.
void createBond()
Create bond connection to lifecycle manager.
bool is_cancel_requested() const
Whether or not a cancel command has come in.
void terminate_all(typename std::shared_ptr< typename ActionT::Result > result=std::make_shared< typename ActionT::Result >())
Terminate all pending and active actions.
bool is_preempt_requested() const
Whether the action server has been asked to be preempted with a new goal.
bool is_server_active()
Whether the action server is active or not.
const std::shared_ptr< const typename ActionT::Goal > accept_pending_goal()
Accept pending goals.
unsigned int getSizeInCellsX() const
Accessor for the x size of the costmap in cells.
Definition: costmap_2d.cpp:548
unsigned int getSizeInCellsY() const
Accessor for the y size of the costmap in cells.
Definition: costmap_2d.cpp:553
An action server implements the behavior tree's ComputePathToPose interface and hosts various plugins...
nav2::CallbackReturn on_configure(const rclcpp_lifecycle::State &state) override
Configure member variables and initializes planner.
void publishPlan(const nav_msgs::msg::Path &path)
Publish a path for visualization purposes.
void computePlan()
The action server callback which calls planner to get the path ComputePathToPose.
nav2::CallbackReturn on_deactivate(const rclcpp_lifecycle::State &state) override
Deactivate member variables.
void getPreemptedGoalIfRequested(typename nav2::SimpleActionServer< T >::SharedPtr &action_server, typename std::shared_ptr< const typename T::Goal > &goal)
Check if an action server has a preemption request and replaces the goal with the new preemption goal...
bool isServerInactive(typename nav2::SimpleActionServer< T >::SharedPtr &action_server)
Check if an action server is valid / active.
bool getStartPose(typename std::shared_ptr< const typename T::Goal > goal, geometry_msgs::msg::PoseStamped &start)
Get the starting pose from costmap or message, if valid.
~PlannerServer()
A destructor for nav2_planner::PlannerServer.
bool goalReceived(std::shared_ptr< const typename T::Goal > goal)
Goal received callback to validate a new goal before acceptance.
nav2::CallbackReturn on_shutdown(const rclcpp_lifecycle::State &state) override
Called when in shutdown state.
void computePlanThroughPoses()
The action server callback which calls planner to get the path ComputePathThroughPoses.
bool isCancelRequested(typename nav2::SimpleActionServer< T >::SharedPtr &action_server)
Check if an action server has a cancellation request pending.
nav2::CallbackReturn on_cleanup(const rclcpp_lifecycle::State &state) override
Reset member variables.
double waitForCostmap()
Wait for costmap to be valid with updated sensor data or repopulate after a clearing recovery....
bool validatePath(const geometry_msgs::msg::PoseStamped &curr_goal, const nav_msgs::msg::Path &path, const std::string &planner_id)
Validate that the path contains a meaningful path.
nav_msgs::msg::Path getPlan(const geometry_msgs::msg::PoseStamped &start, const geometry_msgs::msg::PoseStamped &goal, const std::vector< geometry_msgs::msg::PoseStamped > &viapoints, const std::string &planner_id, std::function< bool()> cancel_checker)
Method to get plan from the desired plugin.
nav2::CallbackReturn on_activate(const rclcpp_lifecycle::State &state) override
Activate member variables.
bool transformPosesToGlobalFrame(geometry_msgs::msg::PoseStamped &curr_start, geometry_msgs::msg::PoseStamped &curr_goal)
Transform start and goal poses into the costmap global frame for path planning plugins to utilize.