Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
route_server.cpp
1 // Copyright (c) 2025, Open Navigation LLC
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. Reserved.
14 
15 #include "nav2_route/route_server.hpp"
16 
17 using std::placeholders::_1;
18 using std::placeholders::_2;
19 
20 namespace nav2_route
21 {
22 
23 RouteServer::RouteServer(const rclcpp::NodeOptions & options)
24 : nav2::LifecycleNode("route_server", "", options)
25 {}
26 
27 nav2::CallbackReturn
28 RouteServer::on_configure(const rclcpp_lifecycle::State & /*state*/)
29 {
30  RCLCPP_INFO(get_logger(), "Configuring");
31 
32  tf_ = nav2::create_transform_buffer(this);
33  transform_listener_ = nav2::create_transform_listener(*tf_, this, true);
34 
35  auto node = shared_from_this();
36  graph_vis_publisher_ =
37  node->create_publisher<visualization_msgs::msg::MarkerArray>(
38  "route_graph", nav2::qos::LatchedPublisherQoS());
39 
40  route_publisher_ = create_publisher<nav2_msgs::msg::Route>("route");
41 
42  compute_route_server_ = create_action_server<ComputeRoute>(
43  "compute_route",
44  std::bind(&RouteServer::computeRoute, this),
45  nullptr, nullptr, std::chrono::milliseconds(500), true);
46 
47  compute_and_track_route_server_ = create_action_server<ComputeAndTrackRoute>(
48  "compute_and_track_route",
49  std::bind(&RouteServer::computeAndTrackRoute, this),
50  nullptr, nullptr, std::chrono::milliseconds(500), true);
51 
52  set_graph_service_ = node->create_service<nav2_msgs::srv::SetRouteGraph>(
53  std::string(node->get_name()) + "/set_route_graph",
54  std::bind(
56  std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
57 
58  route_frame_ = this->declare_or_get_parameter(
59  "route_frame", std::string("map"));
60  base_frame_ = this->declare_or_get_parameter(
61  "base_frame", std::string("base_link"));
62  max_planning_time_ = this->declare_or_get_parameter(
63  "max_planning_time", 2.0);
64 
65  // Create costmap subscriber
66  std::string costmap_topic = this->declare_or_get_parameter(
67  "costmap_topic", std::string("global_costmap/costmap_raw"));
68  costmap_subscriber_ = std::make_shared<nav2_costmap_2d::CostmapSubscriber>(node, costmap_topic);
69 
70  try {
71  graph_loader_ = std::make_shared<GraphLoader>(node, tf_, route_frame_);
72  if (!graph_loader_->loadGraphFromParameter(graph_, id_to_graph_map_)) {
73  return nav2::CallbackReturn::FAILURE;
74  }
75 
76  goal_intent_extractor_ = std::make_shared<GoalIntentExtractor>();
77  goal_intent_extractor_->configure(
78  node, graph_, &id_to_graph_map_, tf_, costmap_subscriber_, route_frame_, base_frame_);
79 
80  route_planner_ = std::make_shared<RoutePlanner>();
81  route_planner_->configure(node, tf_, costmap_subscriber_);
82 
83  route_tracker_ = std::make_shared<RouteTracker>();
84  route_tracker_->configure(
85  node, tf_, costmap_subscriber_, compute_and_track_route_server_, route_frame_, base_frame_);
86 
87  path_converter_ = std::make_shared<PathConverter>();
88  path_converter_->configure(node);
89  } catch (std::exception & e) {
90  RCLCPP_FATAL(get_logger(), "Failed to configure route server: %s", e.what());
91  return nav2::CallbackReturn::FAILURE;
92  }
93 
94  return nav2::CallbackReturn::SUCCESS;
95 }
96 
97 nav2::CallbackReturn
98 RouteServer::on_activate(const rclcpp_lifecycle::State & /*state*/)
99 {
100  RCLCPP_INFO(get_logger(), "Activating");
101  compute_route_server_->activate();
102  compute_and_track_route_server_->activate();
103  graph_vis_publisher_->on_activate();
104  graph_vis_publisher_->publish(utils::toMsg(graph_, route_frame_, this->now()));
105  route_publisher_->on_activate();
106  createBond();
107  return nav2::CallbackReturn::SUCCESS;
108 }
109 
110 nav2::CallbackReturn
111 RouteServer::on_deactivate(const rclcpp_lifecycle::State & /*state*/)
112 {
113  RCLCPP_INFO(get_logger(), "Deactivating");
114  compute_route_server_->deactivate();
115  compute_and_track_route_server_->deactivate();
116  graph_vis_publisher_->on_deactivate();
117  route_publisher_->on_deactivate();
118  destroyBond();
119  return nav2::CallbackReturn::SUCCESS;
120 }
121 
122 nav2::CallbackReturn
123 RouteServer::on_cleanup(const rclcpp_lifecycle::State & /*state*/)
124 {
125  RCLCPP_INFO(get_logger(), "Cleaning up");
126  compute_route_server_.reset();
127  compute_and_track_route_server_.reset();
128  set_graph_service_.reset();
129  graph_loader_.reset();
130  route_planner_.reset();
131  route_tracker_.reset();
132  path_converter_.reset();
133  goal_intent_extractor_.reset();
134  graph_vis_publisher_.reset();
135  route_publisher_.reset();
136  transform_listener_.reset();
137  tf_.reset();
138  graph_.clear();
139  return nav2::CallbackReturn::SUCCESS;
140 }
141 
142 nav2::CallbackReturn
143 RouteServer::on_shutdown(const rclcpp_lifecycle::State &)
144 {
145  RCLCPP_INFO(get_logger(), "Shutting down");
146  return nav2::CallbackReturn::SUCCESS;
147 }
148 
149 rclcpp::Duration
150 RouteServer::findPlanningDuration(const rclcpp::Time & start_time)
151 {
152  auto cycle_duration = this->now() - start_time;
153  if (max_planning_time_ > 0.0 && cycle_duration.seconds() > max_planning_time_) {
154  RCLCPP_WARN(
155  get_logger(),
156  "Route planner missed its desired rate of %.4f Hz. Current loop rate is %.4f Hz",
157  1 / max_planning_time_, 1 / cycle_duration.seconds());
158  }
159 
160  return cycle_duration;
161 }
162 
163 template<typename ActionT>
164 bool
166  typename nav2::SimpleActionServer<ActionT>::SharedPtr & action_server)
167 {
168  if (!action_server || !action_server->is_server_active()) {
169  RCLCPP_DEBUG(get_logger(), "Action server unavailable or inactive. Stopping.");
170  return false;
171  }
172 
173  if (action_server->is_cancel_requested()) {
174  RCLCPP_INFO(get_logger(), "Goal was canceled. Canceling route planning action.");
175  action_server->terminate_all();
176  return false;
177  }
178 
179  if (graph_.empty()) {
180  RCLCPP_INFO(get_logger(), "No graph set! Aborting request.");
181  action_server->terminate_current();
182  return false;
183  }
184 
185  return true;
186 }
187 
189  std::shared_ptr<ComputeRoute::Result> result,
190  const Route & route,
191  const nav_msgs::msg::Path & path,
192  const rclcpp::Duration & planning_duration)
193 {
194  result->route = utils::toMsg(route, route_frame_, this->now());
195  result->path = path;
196  result->planning_time = planning_duration;
197 }
198 
200  std::shared_ptr<ComputeAndTrackRoute::Result> result,
201  const Route &,
202  const nav_msgs::msg::Path &,
203  const rclcpp::Duration & execution_duration)
204 {
205  result->execution_duration = execution_duration;
206 }
207 
208 template<typename GoalT>
210  const std::shared_ptr<const GoalT> goal,
211  ReroutingState & rerouting_info)
212 {
213  // Find the search boundaries
214  auto [start_route, end_route] = goal_intent_extractor_->findStartandGoal(goal);
215 
216  // If we're rerouting, use the rerouting start node and pose as the new start
217  if (rerouting_info.rerouting_start_id != std::numeric_limits<unsigned int>::max()) {
218  start_route = id_to_graph_map_.at(rerouting_info.rerouting_start_id);
219  goal_intent_extractor_->overrideStart(rerouting_info.rerouting_start_pose);
220  }
221 
222  Route route;
223  if (start_route == end_route) {
224  // Succeed with a single-point route
225  route.route_cost = 0.0;
226  route.start_node = &graph_.at(start_route);
227  } else {
228  // Populate request data (start & goal id, start & goal pose, if set) for routing
229  RouteRequest route_request;
230  route_request.start_nodeid = start_route;
231  route_request.goal_nodeid = end_route;
232  route_request.start_pose = goal_intent_extractor_->getStart();
233  route_request.goal_pose = goal->goal;
234  route_request.use_poses = goal->use_poses;
235 
236  // Compute the route via graph-search, returns a node-edge sequence
237  route = route_planner_->findRoute(
238  graph_, start_route, end_route, rerouting_info.blocked_ids, route_request);
239  }
240 
241  return goal_intent_extractor_->pruneStartandGoal(route, goal, rerouting_info);
242 }
243 
244 template<typename ActionT>
245 void
247  typename nav2::SimpleActionServer<ActionT>::SharedPtr & action_server)
248 {
249  auto goal = action_server->get_current_goal();
250  auto result = std::make_shared<typename ActionT::Result>();
251  ReroutingState rerouting_info;
252  auto start_time = this->now();
253 
254  try {
255  while (rclcpp::ok()) {
256  if (!isRequestValid<ActionT>(action_server)) {
257  return;
258  }
259 
260  if (action_server->is_preempt_requested()) {
261  RCLCPP_INFO(get_logger(), "Computing new preempted route to goal.");
262  goal = action_server->accept_pending_goal();
263  rerouting_info.reset();
264  }
265 
266  // Find the route
267  Route route = findRoute(goal, rerouting_info);
268  RCLCPP_INFO(
269  get_logger(), "Route found with %zu nodes and %zu edges",
270  route.edges.size() + 1u, route.edges.size());
271  publishRoute(route);
272  auto path = path_converter_->densify(route, rerouting_info, route_frame_, this->now());
273 
274  if (std::is_same<ActionT, ComputeAndTrackRoute>::value) {
275  // blocks until re-route requested or task completion, publishes feedback
276  switch (route_tracker_->trackRoute(route, path, rerouting_info)) {
277  case TrackerResult::COMPLETED:
278  populateActionResult(result, route, path, this->now() - start_time);
279  action_server->succeeded_current(result);
280  return;
281  case TrackerResult::INTERRUPTED:
282  // Reroute, cancel, or preempt requested
283  break;
284  case TrackerResult::EXITED:
285  // rclcpp::ok() is false, so just return
286  return;
287  }
288  } else {
289  // Return route if not tracking
290  populateActionResult(result, route, path, findPlanningDuration(start_time));
291  action_server->succeeded_current(result);
292  return;
293  }
294  }
295  } catch (nav2_core::NoValidRouteCouldBeFound & ex) {
296  exceptionWarning(goal, ex);
297  result->error_code = ActionT::Result::NO_VALID_ROUTE;
298  result->error_msg = ex.what();
299  action_server->terminate_current(result);
300  } catch (nav2_core::TimedOut & ex) {
301  exceptionWarning(goal, ex);
302  result->error_code = ActionT::Result::TIMEOUT;
303  result->error_msg = ex.what();
304  action_server->terminate_current(result);
305  } catch (nav2_core::RouteTFError & ex) {
306  exceptionWarning(goal, ex);
307  result->error_code = ActionT::Result::TF_ERROR;
308  result->error_msg = ex.what();
309  action_server->terminate_current(result);
310  } catch (nav2_core::NoValidGraph & ex) {
311  exceptionWarning(goal, ex);
312  result->error_code = ActionT::Result::NO_VALID_GRAPH;
313  result->error_msg = ex.what();
314  action_server->terminate_current(result);
315  } catch (nav2_core::IndeterminantNodesOnGraph & ex) {
316  exceptionWarning(goal, ex);
317  result->error_code = ActionT::Result::INDETERMINANT_NODES_ON_GRAPH;
318  result->error_msg = ex.what();
319  action_server->terminate_current(result);
320  } catch (nav2_core::InvalidEdgeScorerUse & ex) {
321  exceptionWarning(goal, ex);
322  result->error_code = ActionT::Result::INVALID_EDGE_SCORER_USE;
323  result->error_msg = ex.what();
324  action_server->terminate_current(result);
325  } catch (nav2_core::OperationFailed & ex) {
326  // A special case since Operation Failed is only in Compute & Track
327  // actions, specifying it to allow otherwise fully shared code
328  exceptionWarning(goal, ex);
329  result->error_code = ComputeAndTrackRoute::Result::OPERATION_FAILED;
330  result->error_msg = ex.what();
331  action_server->terminate_current(result);
332  } catch (nav2_core::RouteException & ex) {
333  exceptionWarning(goal, ex);
334  result->error_code = ActionT::Result::UNKNOWN;
335  result->error_msg = ex.what();
336  action_server->terminate_current(result);
337  } catch (std::exception & ex) {
338  exceptionWarning(goal, ex);
339  result->error_code = ActionT::Result::UNKNOWN;
340  result->error_msg = ex.what();
341  action_server->terminate_current(result);
342  }
343 }
344 
345 void
347 {
348  RCLCPP_INFO(get_logger(), "Computing route to goal.");
349  processRouteRequest<ComputeRoute>(compute_route_server_);
350 }
351 
352 void
353 RouteServer::computeAndTrackRoute()
354 {
355  RCLCPP_INFO(get_logger(), "Computing and tracking route to goal.");
356  processRouteRequest<ComputeAndTrackRoute>(compute_and_track_route_server_);
357 }
358 
360  const std::shared_ptr<rmw_request_id_t>/*request_header*/,
361  const std::shared_ptr<nav2_msgs::srv::SetRouteGraph::Request> request,
362  std::shared_ptr<nav2_msgs::srv::SetRouteGraph::Response> response)
363 {
364  RCLCPP_INFO(get_logger(), "Setting new route graph: %s.", request->graph_filepath.c_str());
365  graph_.clear();
366  id_to_graph_map_.clear();
367  try {
368  if (graph_loader_->loadGraphFromFile(graph_, id_to_graph_map_, request->graph_filepath)) {
369  goal_intent_extractor_->setGraph(graph_, &id_to_graph_map_);
370  graph_vis_publisher_->publish(utils::toMsg(graph_, route_frame_, this->now()));
371  response->success = true;
372  return;
373  }
374  } catch (std::exception & ex) {
375  }
376 
377  RCLCPP_WARN(
378  get_logger(),
379  "Failed to set new route graph: %s!", request->graph_filepath.c_str());
380  response->success = false;
381 }
382 
383 void
385 {
386  if (route_publisher_->is_activated() && route_publisher_->get_subscription_count() > 0) {
387  auto msg = std::make_unique<nav2_msgs::msg::Route>(
388  utils::toMsg(route, route_frame_, this->now()));
389  route_publisher_->publish(std::move(msg));
390  }
391 }
392 
393 template<typename GoalT>
395  const std::shared_ptr<const GoalT> goal,
396  const std::exception & ex)
397 {
398  RCLCPP_WARN(
399  get_logger(),
400  "Route server failed on request: Start: [(%0.2f, %0.2f) / %i] Goal: [(%0.2f, %0.2f) / %i]:"
401  " \"%s\"", goal->start.pose.position.x, goal->start.pose.position.y, goal->start_id,
402  goal->goal.pose.position.x, goal->goal.pose.position.y, goal->goal_id, ex.what());
403 }
404 
405 } // namespace nav2_route
406 
407 #include "rclcpp_components/register_node_macro.hpp"
408 
409 // Register the component with class_loader.
410 // This acts as a sort of entry point, allowing the component to be discoverable when its library
411 // is being loaded into a running process.
412 RCLCPP_COMPONENTS_REGISTER_NODE(nav2_route::RouteServer)
void destroyBond()
Destroy bond connection to lifecycle manager.
nav2::LifecycleNode::SharedPtr shared_from_this()
Get a shared pointer of this.
ParameterT declare_or_get_parameter(const std::string &parameter_name, const ParameterDescriptor &parameter_descriptor=ParameterDescriptor())
Declares or gets a parameter with specified type (not value). If the parameter is already declared,...
void createBond()
Create bond connection to lifecycle manager.
void terminate_current(typename std::shared_ptr< typename ActionT::Result > result=std::make_shared< typename ActionT::Result >())
Terminate the active action.
const std::shared_ptr< const typename ActionT::Goal > get_current_goal() const
Get the current goal object.
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.
void succeeded_current(typename std::shared_ptr< typename ActionT::Result > result=std::make_shared< typename ActionT::Result >())
Return success of the active action.
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.
A QoS profile for latched, reliable topics with a history of 1 messages.
An action server implements a Navigation Route-Graph planner to compliment free-space planning in the...
void computeRoute()
Main route action server callbacks for computing and tracking a route.
void setRouteGraph(const std::shared_ptr< rmw_request_id_t >, const std::shared_ptr< nav2_msgs::srv::SetRouteGraph::Request > request, std::shared_ptr< nav2_msgs::srv::SetRouteGraph::Response > response)
The service callback to set a new route graph.
rclcpp::Duration findPlanningDuration(const rclcpp::Time &start_time)
Find the planning duration of the request and log warnings.
void publishRoute(const Route &route)
Publish the route msg.
Route findRoute(const std::shared_ptr< const GoalT > goal, ReroutingState &rerouting_info)
Compute a route to the goal, incorporating rerouting information.
nav2::CallbackReturn on_configure(const rclcpp_lifecycle::State &state) override
Configure member variables and initializes planner.
nav2::CallbackReturn on_activate(const rclcpp_lifecycle::State &state) override
Activate member variables.
bool isRequestValid(typename nav2::SimpleActionServer< ActionT >::SharedPtr &action_server)
Find the routing request is valid (action server OK and not cancelled)
RouteServer(const rclcpp::NodeOptions &options=rclcpp::NodeOptions())
A constructor for nav2_route::RouteServer.
void exceptionWarning(const std::shared_ptr< const GoalT > goal, const std::exception &ex)
Log exception warnings, templated by action message type.
nav2::CallbackReturn on_cleanup(const rclcpp_lifecycle::State &state) override
Reset member variables.
nav2::CallbackReturn on_deactivate(const rclcpp_lifecycle::State &state) override
Deactivate member variables.
nav2::CallbackReturn on_shutdown(const rclcpp_lifecycle::State &state) override
Called when in shutdown state.
void populateActionResult(std::shared_ptr< ComputeRoute::Result > result, const Route &route, const nav_msgs::msg::Path &path, const rclcpp::Duration &planning_duration)
Populate result for compute route action.
void processRouteRequest(typename nav2::SimpleActionServer< ActionT >::SharedPtr &action_server)
Main processing called by both action server callbacks to centralize the great deal of shared code be...
State shared to objects to communicate important rerouting data to avoid rerouting over blocked edges...
Definition: types.hpp:264
An object to store salient features of the route request including its start and goal node ids,...
Definition: types.hpp:224
An ordered set of nodes and edges corresponding to the planned route.
Definition: types.hpp:211