Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
waypoint_follower.cpp
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 #include "nav2_waypoint_follower/waypoint_follower.hpp"
16 
17 #include <fstream>
18 #include <memory>
19 #include <streambuf>
20 #include <string>
21 #include <utility>
22 #include <vector>
23 
24 #include "nav2_ros_common/rate.hpp"
25 
26 namespace nav2_waypoint_follower
27 {
28 
29 using rcl_interfaces::msg::ParameterType;
30 using std::placeholders::_1;
31 
32 WaypointFollower::WaypointFollower(const rclcpp::NodeOptions & options)
33 : nav2::LifecycleNode("waypoint_follower", "", options),
34  waypoint_task_executor_loader_("nav2_core",
35  "nav2_core::WaypointTaskExecutor")
36 {
37  RCLCPP_INFO(get_logger(), "Creating");
38 }
39 
41 {
42 }
43 
44 nav2::CallbackReturn
45 WaypointFollower::on_configure(const rclcpp_lifecycle::State & state)
46 {
47  RCLCPP_INFO(get_logger(), "Configuring");
48 
49  auto node = shared_from_this();
50 
51  param_handler_ = std::make_unique<ParameterHandler>(
52  node, get_logger());
53  params_ = param_handler_->getParams();
54 
55  callback_group_ = create_callback_group(
56  rclcpp::CallbackGroupType::MutuallyExclusive,
57  false);
58  callback_group_executor_.add_callback_group(callback_group_, get_node_base_interface());
59 
60  nav_to_pose_client_ = create_action_client<ClientT>(
61  "navigate_to_pose", callback_group_);
62 
63  xyz_action_server_ = create_action_server<ActionT>(
64  "follow_waypoints", std::bind(
66  this),
67  std::bind(&WaypointFollower::goalReceived<ActionT>, this, std::placeholders::_1),
68  nullptr, std::chrono::milliseconds(
69  500), false);
70 
71  from_ll_to_map_client_ = node->create_client<robot_localization::srv::FromLL>(
72  "/fromLL",
73  true /*creates and spins an internal executor*/);
74 
75  gps_action_server_ = create_action_server<ActionTGPS>(
76  "follow_gps_waypoints",
77  std::bind(
79  this),
80  std::bind(&WaypointFollower::goalReceived<ActionTGPS>, this, std::placeholders::_1),
81  nullptr, std::chrono::milliseconds(
82  500), false);
83 
84  try {
85  waypoint_task_executor_ = waypoint_task_executor_loader_.createUniqueInstance(
86  params_->waypoint_task_executor_type);
87  RCLCPP_INFO(
88  get_logger(), "Created waypoint_task_executor : %s of type %s",
89  params_->waypoint_task_executor_id.c_str(), params_->waypoint_task_executor_type.c_str());
90  waypoint_task_executor_->initialize(node, params_->waypoint_task_executor_id);
91  } catch (const std::exception & e) {
92  RCLCPP_FATAL(
93  get_logger(),
94  "Failed to create waypoint_task_executor. Exception: %s", e.what());
95  on_cleanup(state);
96  return nav2::CallbackReturn::FAILURE;
97  }
98 
99  return nav2::CallbackReturn::SUCCESS;
100 }
101 
102 nav2::CallbackReturn
103 WaypointFollower::on_activate(const rclcpp_lifecycle::State & /*state*/)
104 {
105  RCLCPP_INFO(get_logger(), "Activating");
106 
107  xyz_action_server_->activate();
108  gps_action_server_->activate();
109 
110  // create bond connection
111  createBond();
112 
113  return nav2::CallbackReturn::SUCCESS;
114 }
115 
116 nav2::CallbackReturn
117 WaypointFollower::on_deactivate(const rclcpp_lifecycle::State & /*state*/)
118 {
119  RCLCPP_INFO(get_logger(), "Deactivating");
120 
121  xyz_action_server_->deactivate();
122  gps_action_server_->deactivate();
123  // destroy bond connection
124  destroyBond();
125 
126  return nav2::CallbackReturn::SUCCESS;
127 }
128 
129 nav2::CallbackReturn
130 WaypointFollower::on_cleanup(const rclcpp_lifecycle::State & /*state*/)
131 {
132  RCLCPP_INFO(get_logger(), "Cleaning up");
133 
134  xyz_action_server_.reset();
135  nav_to_pose_client_.reset();
136  gps_action_server_.reset();
137  from_ll_to_map_client_.reset();
138 
139  return nav2::CallbackReturn::SUCCESS;
140 }
141 
142 nav2::CallbackReturn
143 WaypointFollower::on_shutdown(const rclcpp_lifecycle::State & /*state*/)
144 {
145  RCLCPP_INFO(get_logger(), "Shutting down");
146  return nav2::CallbackReturn::SUCCESS;
147 }
148 
149 template<typename T>
150 bool WaypointFollower::goalReceived(std::shared_ptr<const typename T::Goal> goal)
151 {
152  if constexpr (std::is_same_v<T, ActionTGPS>) {
153  if (goal->gps_poses.empty()) {
154  RCLCPP_ERROR(
155  get_logger(), "Empty vector of GPS waypoints passed to waypoint following action.");
156  return false;
157  }
158  } else {
159  if (goal->poses.empty()) {
160  RCLCPP_ERROR(
161  get_logger(), "Empty vector of waypoints passed to waypoint following action.");
162  return false;
163  }
164  }
165  return true;
166 }
167 
168 template<typename T>
169 std::vector<geometry_msgs::msg::PoseStamped> WaypointFollower::getLatestGoalPoses(
170  const T & action_server)
171 {
172  std::vector<geometry_msgs::msg::PoseStamped> poses;
173  const auto current_goal = action_server->get_current_goal();
174 
175  if (!current_goal) {
176  RCLCPP_ERROR(get_logger(), "No current action goal found!");
177  return poses;
178  }
179 
180  // compile time static check to decide which block of code to be built
181  if constexpr (std::is_same<T, ActionServer::SharedPtr>::value) {
182  // If normal waypoint following callback was called, we build here
183  poses = current_goal->poses;
184  } else {
185  // If GPS waypoint following callback was called, we build here
187  current_goal->gps_poses);
188  }
189  return poses;
190 }
191 
192 template<typename T, typename V, typename Z>
194  const T & action_server,
195  const V & feedback,
196  const Z & result)
197 {
198  auto goal = action_server->get_current_goal();
199 
200  // handling loops
201  unsigned int current_loop_no = 0;
202  auto no_of_loops = goal->number_of_loops;
203 
204  std::vector<geometry_msgs::msg::PoseStamped> poses;
205  poses = getLatestGoalPoses<T>(action_server);
206 
207  if (!action_server || !action_server->is_server_active()) {
208  RCLCPP_DEBUG(get_logger(), "Action server inactive. Stopping.");
209  return;
210  }
211 
212  RCLCPP_INFO(
213  get_logger(), "Received follow waypoint request with %i waypoints.",
214  static_cast<int>(poses.size()));
215 
216  // Check again, GPS waypoint following the poses may still be empty if conversion failed
217  if (poses.empty()) {
218  result->error_code =
219  nav2_msgs::action::FollowWaypoints::Result::NO_VALID_WAYPOINTS;
220  result->error_msg =
221  "Empty vector of waypoints, probably due to conversion failure.";
222  RCLCPP_ERROR(get_logger(), "%s", result->error_msg.c_str());
223  action_server->terminate_current(result);
224  return;
225  }
226 
227  nav2::Rate r(this, params_->loop_rate);
228 
229  // get the goal index, by default, the first in the list of waypoints given.
230  uint32_t goal_index = goal->goal_index;
231  bool new_goal = true;
232 
233  while (rclcpp::ok()) {
234  // Check if asked to stop processing action
235  if (action_server->is_cancel_requested()) {
236  auto cancel_future = nav_to_pose_client_->async_cancel_all_goals();
237  callback_group_executor_.spin_until_future_complete(cancel_future);
238  // for result callback processing
239  callback_group_executor_.spin_some();
240  action_server->terminate_all();
241  return;
242  }
243 
244  // Check if asked to process another action
245  if (action_server->is_preempt_requested()) {
246  RCLCPP_INFO(get_logger(), "Preempting the goal pose.");
247  goal = action_server->accept_pending_goal();
248  poses = getLatestGoalPoses<T>(action_server);
249  if (poses.empty()) {
250  result->error_code =
251  nav2_msgs::action::FollowWaypoints::Result::NO_VALID_WAYPOINTS;
252  result->error_msg =
253  "Empty vector of Waypoints passed to waypoint following logic. "
254  "Nothing to execute, returning with failure!";
255  RCLCPP_ERROR(get_logger(), "%s", result->error_msg.c_str());
256  action_server->terminate_current(result);
257  return;
258  }
259  goal_index = goal->goal_index;
260  no_of_loops = goal->number_of_loops;
261  current_loop_no = 0;
262  result->missed_waypoints.clear();
263  new_goal = true;
264  }
265 
266  // Check if we need to send a new goal
267  if (new_goal) {
268  new_goal = false;
269  ClientT::Goal client_goal;
270  client_goal.pose = poses[goal_index];
271  client_goal.pose.header.stamp = this->now();
272 
273  auto send_goal_options = nav2::ActionClient<ClientT>::SendGoalOptions();
274  send_goal_options.result_callback = std::bind(
276  std::placeholders::_1);
277  send_goal_options.goal_response_callback = std::bind(
279  this, std::placeholders::_1);
280 
281  future_goal_handle_ =
282  nav_to_pose_client_->async_send_goal(client_goal, send_goal_options);
283  current_goal_status_ = {ActionStatus::PROCESSING, 0, ""};
284  }
285 
286  feedback->current_waypoint = goal_index;
287  action_server->publish_feedback(feedback);
288 
289  if (
290  current_goal_status_.status == ActionStatus::FAILED ||
291  current_goal_status_.status == ActionStatus::UNKNOWN)
292  {
293  nav2_msgs::msg::WaypointStatus missedWaypoint;
294  missedWaypoint.waypoint_status = nav2_msgs::msg::WaypointStatus::FAILED;
295  missedWaypoint.waypoint_index = goal_index;
296  missedWaypoint.waypoint_pose = poses[goal_index];
297  missedWaypoint.error_code = current_goal_status_.error_code;
298  missedWaypoint.error_msg = current_goal_status_.error_msg;
299  result->missed_waypoints.push_back(missedWaypoint);
300 
301  if (params_->stop_on_failure) {
302  result->error_code =
303  nav2_msgs::action::FollowWaypoints::Result::STOP_ON_MISSED_WAYPOINT;
304  result->error_msg =
305  "Failed to process waypoint " + std::to_string(goal_index) +
306  " in waypoint list and stop on failure is enabled."
307  " Terminating action.";
308  RCLCPP_WARN(get_logger(), "%s", result->error_msg.c_str());
309  action_server->terminate_current(result);
310  current_goal_status_.error_code = 0;
311  current_goal_status_.error_msg = "";
312  return;
313  } else {
314  RCLCPP_INFO(
315  get_logger(), "Failed to process waypoint %i,"
316  " moving to next.", goal_index);
317  }
318  } else if (current_goal_status_.status == ActionStatus::SUCCEEDED) {
319  RCLCPP_INFO(
320  get_logger(), "Succeeded processing waypoint %i, processing waypoint task execution",
321  goal_index);
322  bool is_task_executed = waypoint_task_executor_->processAtWaypoint(
323  poses[goal_index], goal_index);
324  RCLCPP_INFO(
325  get_logger(), "Task execution at waypoint %i %s", goal_index,
326  is_task_executed ? "succeeded" : "failed!");
327 
328  if (!is_task_executed) {
329  nav2_msgs::msg::WaypointStatus missedWaypoint;
330  missedWaypoint.waypoint_status = nav2_msgs::msg::WaypointStatus::FAILED;
331  missedWaypoint.waypoint_index = goal_index;
332  missedWaypoint.waypoint_pose = poses[goal_index];
333  missedWaypoint.error_code =
334  nav2_msgs::action::FollowWaypoints::Result::TASK_EXECUTOR_FAILED;
335  missedWaypoint.error_msg = "Task execution failed";
336  result->missed_waypoints.push_back(missedWaypoint);
337  }
338  // if task execution was failed and stop_on_failure_ is on , terminate action
339  if (!is_task_executed && params_->stop_on_failure) {
340  result->error_code =
341  nav2_msgs::action::FollowWaypoints::Result::TASK_EXECUTOR_FAILED;
342  result->error_msg =
343  "Failed to execute task at waypoint " + std::to_string(goal_index) +
344  " stop on failure is enabled. Terminating action.";
345  RCLCPP_WARN(get_logger(), "%s", result->error_msg.c_str());
346  action_server->terminate_current(result);
347  current_goal_status_.error_code = 0;
348  current_goal_status_.error_msg = "";
349  return;
350  } else {
351  RCLCPP_INFO(
352  get_logger(), "Handled task execution on waypoint %i,"
353  " moving to next.", goal_index);
354  }
355  }
356 
357  if (current_goal_status_.status != ActionStatus::PROCESSING) {
358  // Update server state
359  goal_index++;
360  new_goal = true;
361  if (goal_index >= poses.size()) {
362  if (current_loop_no == no_of_loops) {
363  RCLCPP_INFO(
364  get_logger(), "Completed all %zu waypoints requested.",
365  poses.size());
366  action_server->succeeded_current(result);
367  current_goal_status_.error_code = 0;
368  current_goal_status_.error_msg = "";
369  return;
370  }
371  RCLCPP_INFO(
372  get_logger(), "Starting a new loop, current loop count is %i",
373  current_loop_no);
374  goal_index = 0;
375  current_loop_no++;
376  }
377  }
378 
379  callback_group_executor_.spin_some();
380  r.sleep();
381  }
382 }
383 
385 {
386  auto feedback = std::make_shared<ActionT::Feedback>();
387  auto result = std::make_shared<ActionT::Result>();
388 
389  followWaypointsHandler<typename ActionServer::SharedPtr,
390  ActionT::Feedback::SharedPtr,
391  ActionT::Result::SharedPtr>(
392  xyz_action_server_,
393  feedback, result);
394 }
395 
397 {
398  auto feedback = std::make_shared<ActionTGPS::Feedback>();
399  auto result = std::make_shared<ActionTGPS::Result>();
400 
401  followWaypointsHandler<typename ActionServerGPS::SharedPtr,
402  ActionTGPS::Feedback::SharedPtr,
403  ActionTGPS::Result::SharedPtr>(
404  gps_action_server_,
405  feedback, result);
406 }
407 
408 void
410  const rclcpp_action::ClientGoalHandle<ClientT>::WrappedResult & result)
411 {
412  if (result.goal_id != future_goal_handle_.get()->get_goal_id()) {
413  RCLCPP_DEBUG(
414  get_logger(),
415  "Goal IDs do not match for the current goal handle and received result."
416  "Ignoring likely due to receiving result for an old goal.");
417  return;
418  }
419 
420  switch (result.code) {
421  case rclcpp_action::ResultCode::SUCCEEDED:
422  current_goal_status_.status = ActionStatus::SUCCEEDED;
423  return;
424  case rclcpp_action::ResultCode::ABORTED:
425  current_goal_status_.status = ActionStatus::FAILED;
426  current_goal_status_.error_code = result.result->error_code;
427  current_goal_status_.error_msg = result.result->error_msg;
428  return;
429  case rclcpp_action::ResultCode::CANCELED:
430  current_goal_status_.status = ActionStatus::FAILED;
431  return;
432  default:
433  current_goal_status_.status = ActionStatus::UNKNOWN;
434  current_goal_status_.error_code = nav2_msgs::action::FollowWaypoints::Result::UNKNOWN;
435  current_goal_status_.error_msg = "Received an UNKNOWN result code from navigation action!";
436  RCLCPP_ERROR(get_logger(), "%s", current_goal_status_.error_msg.c_str());
437  return;
438  }
439 }
440 
441 void
443  const rclcpp_action::ClientGoalHandle<ClientT>::SharedPtr & goal)
444 {
445  if (!goal) {
446  current_goal_status_.status = ActionStatus::FAILED;
447  current_goal_status_.error_code = nav2_msgs::action::FollowWaypoints::Result::UNKNOWN;
448  current_goal_status_.error_msg =
449  "navigate_to_pose action client failed to send goal to server.";
450  RCLCPP_ERROR(get_logger(), "%s", current_goal_status_.error_msg.c_str());
451  }
452 }
453 
454 std::vector<geometry_msgs::msg::PoseStamped>
456  const std::vector<geographic_msgs::msg::GeoPose> & gps_poses)
457 {
458  RCLCPP_INFO(
459  this->get_logger(), "Converting GPS waypoints to %s Frame..",
460  params_->global_frame_id.c_str());
461 
462  std::vector<geometry_msgs::msg::PoseStamped> poses_in_map_frame_vector;
463  int waypoint_index = 0;
464  for (auto && curr_geopose : gps_poses) {
465  auto request = std::make_shared<robot_localization::srv::FromLL::Request>();
466  auto response = std::make_shared<robot_localization::srv::FromLL::Response>();
467  request->ll_point.latitude = curr_geopose.position.latitude;
468  request->ll_point.longitude = curr_geopose.position.longitude;
469  request->ll_point.altitude = curr_geopose.position.altitude;
470 
471  from_ll_to_map_client_->wait_for_service((std::chrono::seconds(1)));
472  if (!from_ll_to_map_client_->invoke(request, response)) {
473  RCLCPP_ERROR(
474  this->get_logger(),
475  "fromLL service of robot_localization could not convert %i th GPS waypoint to"
476  "%s frame, going to skip this point!"
477  "Make sure you have run navsat_transform_node of robot_localization",
478  waypoint_index, params_->global_frame_id.c_str());
479  if (params_->stop_on_failure) {
480  RCLCPP_ERROR(
481  this->get_logger(),
482  "Conversion of %i th GPS waypoint to"
483  "%s frame failed and stop_on_failure is set to true"
484  "Not going to execute any of waypoints, exiting with failure!",
485  waypoint_index, params_->global_frame_id.c_str());
486  return std::vector<geometry_msgs::msg::PoseStamped>();
487  }
488  continue;
489  } else {
490  geometry_msgs::msg::PoseStamped curr_pose_map_frame;
491  curr_pose_map_frame.header.frame_id = params_->global_frame_id;
492  curr_pose_map_frame.header.stamp = this->now();
493  curr_pose_map_frame.pose.position = response->map_point;
494  curr_pose_map_frame.pose.orientation = curr_geopose.orientation;
495  poses_in_map_frame_vector.push_back(curr_pose_map_frame);
496  }
497  waypoint_index++;
498  }
499  RCLCPP_INFO(
500  this->get_logger(),
501  "Converted all %i GPS waypoint to %s frame",
502  static_cast<int>(poses_in_map_frame_vector.size()), params_->global_frame_id.c_str());
503  return poses_in_map_frame_vector;
504 }
505 
506 } // namespace nav2_waypoint_follower
507 
508 #include "rclcpp_components/register_node_macro.hpp"
509 
510 // Register the component with class_loader.
511 // This acts as a sort of entry point, allowing the component to be discoverable when its library
512 // is being loaded into a running process.
513 RCLCPP_COMPONENTS_REGISTER_NODE(nav2_waypoint_follower::WaypointFollower)
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.
A sim-time-aware rate for Nav2 loops.
Definition: rate.hpp:61
ResponseType::SharedPtr invoke(typename RequestType::SharedPtr &request, const std::chrono::nanoseconds timeout=std::chrono::nanoseconds(-1), const std::chrono::nanoseconds wait_for_service_timeout=std::chrono::seconds(10))
Invoke the service and block until completed or timed out.
bool wait_for_service(const std::chrono::nanoseconds timeout=std::chrono::nanoseconds::max())
Block until a service is available or timeout.
An action server that uses behavior tree for navigating a robot to its goal position.
nav2::CallbackReturn on_deactivate(const rclcpp_lifecycle::State &state) override
Deactivates action server.
bool goalReceived(std::shared_ptr< const typename T::Goal > goal)
Goal received callbacks to validate a new goal before acceptance. Rejects goals with empty waypoint l...
nav2::CallbackReturn on_shutdown(const rclcpp_lifecycle::State &state) override
Called when in shutdown state.
void resultCallback(const rclcpp_action::ClientGoalHandle< ClientT >::WrappedResult &result)
Action client result callback.
~WaypointFollower()
A destructor for nav2_waypoint_follower::WaypointFollower class.
WaypointFollower(const rclcpp::NodeOptions &options=rclcpp::NodeOptions())
A constructor for nav2_waypoint_follower::WaypointFollower class.
void goalResponseCallback(const rclcpp_action::ClientGoalHandle< ClientT >::SharedPtr &goal)
Action client goal response callback.
nav2::CallbackReturn on_cleanup(const rclcpp_lifecycle::State &state) override
Resets member variables.
nav2::CallbackReturn on_configure(const rclcpp_lifecycle::State &state) override
Configures member variables.
void followGPSWaypointsCallback()
send robot through each of GPS point , which are converted to map frame first then using a client to ...
std::vector< geometry_msgs::msg::PoseStamped > convertGPSPosesToMapPoses(const std::vector< geographic_msgs::msg::GeoPose > &gps_poses)
given some gps_poses, converts them to map frame using robot_localization's service fromLL....
void followWaypointsHandler(const T &action_server, const V &feedback, const Z &result)
Templated function to perform internal logic behind waypoint following, Both GPS and non GPS waypoint...
void followWaypointsCallback()
Action server callbacks.
std::vector< geometry_msgs::msg::PoseStamped > getLatestGoalPoses(const T &action_server)
get the latest poses on the action server goal. If they are GPS poses, convert them to the global car...
nav2::CallbackReturn on_activate(const rclcpp_lifecycle::State &state) override
Activates action server.