Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
following_server.cpp
1 // Copyright (c) 2024 Open Navigation LLC
2 // Copyright (c) 2024 Alberto J. Tudela Roldán
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 "angles/angles.h"
17 #include "nav2_ros_common/rate.hpp"
18 #include "opennav_docking_core/docking_exceptions.hpp"
19 #include "opennav_following/following_server.hpp"
20 #include "nav2_util/geometry_utils.hpp"
21 #include "nav2_util/robot_utils.hpp"
22 #include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"
23 #include "tf2/utils.hpp"
24 
25 using namespace std::chrono_literals;
26 using rcl_interfaces::msg::ParameterType;
27 using std::placeholders::_1;
28 
29 namespace opennav_following
30 {
31 
32 FollowingServer::FollowingServer(const rclcpp::NodeOptions & options)
33 : nav2::LifecycleNode("following_server", "", options)
34 {
35  RCLCPP_INFO(get_logger(), "Creating %s", get_name());
36 }
37 
38 nav2::CallbackReturn
39 FollowingServer::on_configure(const rclcpp_lifecycle::State & /*state*/)
40 {
41  RCLCPP_INFO(get_logger(), "Configuring %s", get_name());
42  auto node = shared_from_this();
43  param_handler_ = std::make_unique<ParameterHandler>(
44  node, get_logger());
45  params_ = param_handler_->getParams();
46 
47  vel_publisher_ = std::make_unique<nav2_util::TwistPublisher>(node, "cmd_vel");
48  tf2_buffer_ = nav2::create_transform_buffer(node);
49 
50  // Create odom subscriber for backward blind docking
51  odom_sub_ = std::make_unique<nav2_util::OdomSmoother>(node, params_->odom_duration,
52  params_->odom_topic);
53 
54  // Create the action server for dynamic following
55  following_action_server_ = node->create_action_server<FollowObject>(
56  "follow_object",
57  std::bind(&FollowingServer::followObject, this),
58  nullptr, nullptr, std::chrono::milliseconds(500),
59  true);
60 
61  // Create the controller
62  // Note: Collision detection is not supported in following server so we force it off
63  // and warn if the user has it enabled (from launch file or parameter file)
64  controller_ =
65  std::make_unique<opennav_docking::Controller>(node, tf2_buffer_, params_->fixed_frame,
66  params_->base_frame);
67 
68  if (params_->use_collision_detection) {
69  RCLCPP_ERROR(
70  get_logger(),
71  "Collision detection is not supported in the following server. Please disable "
72  "the controller.use_collision_detection parameter.");
73  return nav2::CallbackReturn::FAILURE;
74  }
75 
76  // Setup filter
77  filter_ = std::make_unique<opennav_docking::PoseFilter>(params_->filter_coef,
78  params_->detection_timeout);
79 
80  // And publish the filtered pose
81  filtered_dynamic_pose_pub_ =
82  create_publisher<geometry_msgs::msg::PoseStamped>("filtered_dynamic_pose");
83 
84  // Initialize static object detection variables
85  static_timer_initialized_ = false;
86  static_object_start_time_ = rclcpp::Time(0);
87 
88  return nav2::CallbackReturn::SUCCESS;
89 }
90 
91 nav2::CallbackReturn
92 FollowingServer::on_activate(const rclcpp_lifecycle::State & /*state*/)
93 {
94  RCLCPP_INFO(get_logger(), "Activating %s", get_name());
95 
96  tf2_listener_ = nav2::create_transform_listener(*tf2_buffer_, this, true);
97  vel_publisher_->on_activate();
98  filtered_dynamic_pose_pub_->on_activate();
99  following_action_server_->activate();
100  param_handler_->activate();
101 
102  // Create bond connection
103  createBond();
104 
105  return nav2::CallbackReturn::SUCCESS;
106 }
107 
108 nav2::CallbackReturn
109 FollowingServer::on_deactivate(const rclcpp_lifecycle::State & /*state*/)
110 {
111  RCLCPP_INFO(get_logger(), "Deactivating %s", get_name());
112 
113  following_action_server_->deactivate();
114  vel_publisher_->on_deactivate();
115  filtered_dynamic_pose_pub_->on_deactivate();
116  param_handler_->deactivate();
117 
118  tf2_listener_.reset();
119 
120  // Destroy bond connection
121  destroyBond();
122 
123  return nav2::CallbackReturn::SUCCESS;
124 }
125 
126 nav2::CallbackReturn
127 FollowingServer::on_cleanup(const rclcpp_lifecycle::State & /*state*/)
128 {
129  RCLCPP_INFO(get_logger(), "Cleaning up %s", get_name());
130  tf2_buffer_.reset();
131  following_action_server_.reset();
132  controller_.reset();
133  vel_publisher_.reset();
134  filtered_dynamic_pose_pub_.reset();
135  odom_sub_.reset();
136  return nav2::CallbackReturn::SUCCESS;
137 }
138 
139 nav2::CallbackReturn
140 FollowingServer::on_shutdown(const rclcpp_lifecycle::State &)
141 {
142  RCLCPP_INFO(get_logger(), "Shutting down %s", get_name());
143  return nav2::CallbackReturn::SUCCESS;
144 }
145 
146 template<typename ActionT>
148  typename std::shared_ptr<const typename ActionT::Goal> goal,
149  const typename nav2::SimpleActionServer<ActionT>::SharedPtr & action_server)
150 {
151  if (action_server->is_preempt_requested()) {
152  goal = action_server->accept_pending_goal();
153  }
154 }
155 
156 template<typename ActionT>
158  typename nav2::SimpleActionServer<ActionT>::SharedPtr & action_server,
159  const std::string & name)
160 {
161  if (action_server->is_cancel_requested()) {
162  RCLCPP_WARN(get_logger(), "Goal was cancelled. Cancelling %s action", name.c_str());
163  return true;
164  }
165  return false;
166 }
167 
168 template<typename ActionT>
170  typename nav2::SimpleActionServer<ActionT>::SharedPtr & action_server,
171  const std::string & name)
172 {
173  if (action_server->is_preempt_requested()) {
174  RCLCPP_WARN(get_logger(), "Goal was preempted. Cancelling %s action", name.c_str());
175  return true;
176  }
177  return false;
178 }
179 
181 {
182  std::lock_guard<std::mutex> lock_reinit(param_handler_->getMutex());
183  action_start_time_ = this->now();
184  nav2::Rate loop_rate(this, params_->controller_frequency);
185 
186  auto goal = following_action_server_->get_current_goal();
187  auto result = std::make_shared<FollowObject::Result>();
188 
189  if (!following_action_server_ || !following_action_server_->is_server_active()) {
190  RCLCPP_DEBUG(get_logger(), "Action server unavailable or inactive. Stopping.");
191  return;
192  }
193 
194  if (checkAndWarnIfCancelled<FollowObject>(following_action_server_, "follow_object")) {
195  following_action_server_->terminate_all();
196  return;
197  }
198 
199  getPreemptedGoalIfRequested<FollowObject>(goal, following_action_server_);
200  num_retries_ = 0;
201  static_timer_initialized_ = false;
202 
203  // Reset the last detected dynamic pose timestamp so we start fresh for this action
204  detected_dynamic_pose_.header.stamp = rclcpp::Time(0);
205 
206  try {
207  auto pose_topic = goal->pose_topic;
208  auto target_frame = goal->tracked_frame;
209  if (target_frame.empty()) {
210  if (pose_topic.empty()) {
211  RCLCPP_ERROR(
212  get_logger(),
213  "Both pose topic and target frame are empty. Cannot follow object.");
214  result->error_code = FollowObject::Result::FAILED_TO_DETECT_OBJECT;
215  result->error_msg = "No pose topic or target frame provided.";
216  following_action_server_->terminate_all(result);
217  return;
218  } else {
219  param_handler_->getMutex().unlock();
220  RCLCPP_INFO(get_logger(), "Subscribing to pose topic: %s", pose_topic.c_str());
221  dynamic_pose_sub_ = create_subscription<geometry_msgs::msg::PoseStamped>(
222  pose_topic,
223  [this](const geometry_msgs::msg::PoseStamped::ConstSharedPtr & pose) {
224  detected_dynamic_pose_ = *pose;
225  },
226  nav2::qos::StandardTopicQoS(1)); // Only want the most recent pose
227  param_handler_->getMutex().lock();
228  }
229  } else {
230  RCLCPP_INFO(get_logger(), "Following frame: %s instead of pose", target_frame.c_str());
231  }
232 
233  // Following control loop: while not timeout, run controller
234  geometry_msgs::msg::PoseStamped object_pose;
235  rclcpp::Duration max_duration = goal->max_duration;
236  while (rclcpp::ok()) {
237  try {
238  // Check if we have run out of time
239  if (this->now() - action_start_time_ > max_duration && max_duration.seconds() > 0.0) {
240  RCLCPP_INFO(get_logger(), "Exceeded max duration. Stopping.");
241  result->total_elapsed_time = this->now() - action_start_time_;
242  result->num_retries = num_retries_;
244  following_action_server_->succeeded_current(result);
245  dynamic_pose_sub_.reset();
246  return;
247  }
248 
249  // Approach the object using control law
250  if (approachObject(object_pose, target_frame)) {
251  // Initialize static timer on first entry
252  if (!static_timer_initialized_) {
253  static_object_start_time_ = this->now();
254  static_timer_initialized_ = true;
255  }
256 
257  // We have reached the object, maintain position
258  RCLCPP_INFO_THROTTLE(
259  get_logger(), *get_clock(), 1000,
260  "Reached object. Stopping until goal is moved again.");
261  publishFollowingFeedback(FollowObject::Feedback::STOPPING);
263 
264  // Stop if the object has been static for some time
265  if (params_->static_object_timeout > 0.0) {
266  auto static_duration = this->now() - static_object_start_time_;
267  if (static_duration.seconds() > params_->static_object_timeout) {
268  RCLCPP_INFO(
269  get_logger(),
270  "Object has been static for %.2f seconds (timeout: %.2f), stopping.",
271  static_duration.seconds(), params_->static_object_timeout);
272  result->total_elapsed_time = this->now() - action_start_time_;
273  result->num_retries = num_retries_;
275  following_action_server_->succeeded_current(result);
276  return;
277  }
278  }
279  } else {
280  // Cancelled, preempted, or shutting down (recoverable errors throw DockingException)
281  static_timer_initialized_ = false;
282  result->total_elapsed_time = this->now() - action_start_time_;
284  following_action_server_->terminate_all(result);
285  dynamic_pose_sub_.reset();
286  return;
287  }
289  if (++num_retries_ > params_->max_retries) {
290  RCLCPP_ERROR(get_logger(), "Failed to follow, all retries have been used");
291  throw;
292  }
293  RCLCPP_WARN(get_logger(), "Following failed, will retry: %s", e.what());
294 
295  // Perform an in-place rotation to find the object again
296  if (params_->search_by_rotating) {
297  RCLCPP_INFO(get_logger(), "Rotating to find object again");
298  if (!rotateToObject(object_pose, target_frame)) {
299  // Cancelled, preempted, or shutting down
301  following_action_server_->terminate_all(result);
302  return;
303  }
304  } else {
305  RCLCPP_INFO(get_logger(), "Using last known heading to find object again");
306  }
307  }
308  loop_rate.sleep();
309  }
310  } catch (const tf2::TransformException & e) {
311  result->error_msg = std::string("Transform error: ") + e.what();
312  RCLCPP_ERROR(get_logger(), "%s", result->error_msg.c_str());
313  result->error_code = FollowObject::Result::TF_ERROR;
315  result->error_msg = e.what();
316  RCLCPP_ERROR(get_logger(), "%s", result->error_msg.c_str());
317  result->error_code = FollowObject::Result::FAILED_TO_DETECT_OBJECT;
319  result->error_msg = e.what();
320  RCLCPP_ERROR(get_logger(), "%s", result->error_msg.c_str());
321  result->error_code = FollowObject::Result::FAILED_TO_CONTROL;
323  result->error_msg = e.what();
324  RCLCPP_ERROR(get_logger(), "%s", result->error_msg.c_str());
325  result->error_code = FollowObject::Result::UNKNOWN;
326  } catch (std::exception & e) {
327  result->error_msg = e.what();
328  RCLCPP_ERROR(get_logger(), "%s", result->error_msg.c_str());
329  result->error_code = FollowObject::Result::UNKNOWN;
330  }
331 
332  // Stop the robot and report
333  result->total_elapsed_time = this->now() - action_start_time_;
334  result->num_retries = num_retries_;
336  following_action_server_->terminate_current(result);
337  dynamic_pose_sub_.reset();
338 }
339 
341  geometry_msgs::msg::PoseStamped & object_pose, const std::string & target_frame)
342 {
343  rclcpp::Rate loop_rate(params_->controller_frequency);
344  while (rclcpp::ok()) {
345  // Update the iteration start time, used for get robot position, transformation and control
346  iteration_start_time_ = this->now();
347 
348  publishFollowingFeedback(FollowObject::Feedback::CONTROLLING);
349 
350  // Stop if cancelled/preempted
351  if (checkAndWarnIfCancelled<FollowObject>(following_action_server_, "follow_object") ||
352  checkAndWarnIfPreempted<FollowObject>(following_action_server_, "follow_object"))
353  {
354  return false;
355  }
356 
357  // Get the tracking pose from topic or frame
358  getTrackingPose(object_pose, target_frame);
359 
360  // Get the pose at the distance we want to maintain from the object
361  // Stop and report success if goal is reached
362  auto target_pose = getPoseAtDistance(object_pose, params_->desired_distance);
363  if (isGoalReached(target_pose)) {
364  return true;
365  }
366 
367  // The control law can get jittery when close to the end when atan2's can explode.
368  // Thus, we reduce the desired distance by a small amount so that the robot never
369  // gets to the end of the spiral before its at the desired distance to stop the
370  // following procedure.
371  const double backward_projection = 0.25;
372  const double effective_distance = params_->desired_distance - backward_projection;
373  target_pose = getPoseAtDistance(object_pose, effective_distance);
374 
375  // ... and transform the target_pose into base_frame
376  try {
377  tf2_buffer_->transform(
378  target_pose, target_pose, params_->base_frame,
379  tf2::durationFromSec(params_->transform_tolerance));
380  } catch (const tf2::TransformException & ex) {
381  RCLCPP_WARN(get_logger(), "Failed to transform target pose: %s", ex.what());
382  return false;
383  }
384 
385  // If the object is behind the robot, we reverse the control
386  geometry_msgs::msg::PoseStamped robot_pose;
387  if (!nav2_util::getCurrentPose(
388  robot_pose, *tf2_buffer_, target_pose.header.frame_id, params_->base_frame,
389  params_->transform_tolerance,
390  iteration_start_time_))
391  {
392  RCLCPP_WARN(get_logger(), "Failed to get current robot pose");
393  return false;
394  }
395 
396  // Compute and publish controls
397  auto command = std::make_unique<geometry_msgs::msg::TwistStamped>();
398  command->header.stamp = now();
399  if (!controller_->computeVelocityCommand(target_pose.pose, command->twist, true, false)) {
400  throw opennav_docking_core::FailedToControl("Failed to get control");
401  }
402  vel_publisher_->publish(std::move(command));
403 
404  loop_rate.sleep();
405  }
406  return false;
407 }
408 
410  geometry_msgs::msg::PoseStamped & object_pose, const std::string & target_frame)
411 {
412  const double dt = 1.0 / params_->controller_frequency;
413 
414  // Compute initial robot heading
415  geometry_msgs::msg::PoseStamped robot_pose;
416  if (!nav2_util::getCurrentPose(
417  robot_pose, *tf2_buffer_, object_pose.header.frame_id, params_->base_frame,
418  params_->transform_tolerance,
419  iteration_start_time_))
420  {
421  RCLCPP_WARN(get_logger(), "Failed to get current robot pose");
422  return false;
423  }
424  double initial_yaw = tf2::getYaw(robot_pose.pose.orientation);
425 
426  // Search angles: left offset, then right offset from initial heading
427  std::vector<double> angles = {initial_yaw + params_->search_angle,
428  initial_yaw - params_->search_angle};
429 
430  rclcpp::Rate loop_rate(params_->controller_frequency);
431  auto start = this->now();
432  auto timeout = rclcpp::Duration::from_seconds(params_->rotate_to_object_timeout);
433 
434  // Iterate over target angles
435  for (const double & target_angle : angles) {
436  // Create a target pose oriented at target_angle
437  auto target_pose = object_pose;
438  target_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(target_angle);
439 
440  // Rotate towards target_angle while checking for detection
441  while (rclcpp::ok()) {
442  // Update the iteration start time, used for get robot position, transformation and control
443  iteration_start_time_ = this->now();
444 
445  publishFollowingFeedback(FollowObject::Feedback::RETRY);
446 
447  // Stop if cancelled/preempted
448  if (checkAndWarnIfCancelled<FollowObject>(following_action_server_, "follow_object") ||
449  checkAndWarnIfPreempted<FollowObject>(following_action_server_, "follow_object"))
450  {
451  return false;
452  }
453 
454  // Get current robot pose
455  if (!nav2_util::getCurrentPose(
456  robot_pose, *tf2_buffer_, object_pose.header.frame_id, params_->base_frame,
457  params_->transform_tolerance,
458  iteration_start_time_))
459  {
460  RCLCPP_WARN(get_logger(), "Failed to get current robot pose");
461  return false;
462  }
463 
464  double angular_distance_to_heading = angles::shortest_angular_distance(
465  tf2::getYaw(robot_pose.pose.orientation), target_angle);
466 
467  // If we are close enough to the target orientation, break and try next angle
468  if (fabs(angular_distance_to_heading) < params_->angular_tolerance) {
469  break;
470  }
471 
472  // While rotating, check if we can get the tracking pose (object detected)
473  try {
474  if (getTrackingPose(object_pose, target_frame)) {
475  return true;
476  }
478  // No detection yet, continue rotating
479  }
480 
481  geometry_msgs::msg::Twist current_vel;
482  current_vel.angular.z = odom_sub_->getRawTwist().angular.z;
483 
484  auto command = std::make_unique<geometry_msgs::msg::TwistStamped>();
485  command->header = robot_pose.header;
486  command->twist = controller_->computeRotateToHeadingCommand(
487  angular_distance_to_heading, current_vel, dt);
488 
489  vel_publisher_->publish(std::move(command));
490 
491  if (this->now() - start > timeout) {
492  throw opennav_docking_core::FailedToControl("Timed out rotating to object");
493  }
494 
495  loop_rate.sleep();
496  }
497  }
498 
499  // If we exhausted all search angles and did not detect the object, fail
500  throw opennav_docking_core::FailedToControl("Failed to rotate to object");
501 }
502 
504 {
505  auto cmd_vel = std::make_unique<geometry_msgs::msg::TwistStamped>();
506  cmd_vel->header.frame_id = params_->base_frame;
507  cmd_vel->header.stamp = now();
508  vel_publisher_->publish(std::move(cmd_vel));
509 }
510 
512 {
513  auto feedback = std::make_shared<FollowObject::Feedback>();
514  feedback->state = state;
515  feedback->following_time = iteration_start_time_ - action_start_time_;
516  feedback->num_retries = num_retries_;
517  following_action_server_->publish_feedback(feedback);
518 }
519 
520 bool FollowingServer::getRefinedPose(geometry_msgs::msg::PoseStamped & pose)
521 {
522  // Get current detections and transform to frame
523  geometry_msgs::msg::PoseStamped detected = detected_dynamic_pose_;
524 
525  // If we haven't received any detection yet, wait up to detection_timeout_ for one to arrive.
526  if (detected.header.stamp == builtin_interfaces::msg::Time{}) {
527  auto start = this->now();
528  auto timeout = rclcpp::Duration::from_seconds(params_->detection_timeout);
529  nav2::Rate wait_rate(this, params_->controller_frequency);
530  while (this->now() - start < timeout) {
531  // Check if a new detection arrived
532  if (detected_dynamic_pose_.header.stamp != builtin_interfaces::msg::Time{}) {
533  detected = detected_dynamic_pose_;
534  break;
535  }
536  wait_rate.sleep();
537  }
538  if (detected.header.stamp == builtin_interfaces::msg::Time{}) {
539  RCLCPP_WARN(this->get_logger(), "No detection received within timeout period");
540  return false;
541  }
542  }
543 
544  // Validate that external pose is new enough
545  auto timeout = rclcpp::Duration::from_seconds(params_->detection_timeout);
546  if (this->now() - detected.header.stamp > timeout) {
547  RCLCPP_WARN(this->get_logger(), "Lost detection or did not detect: timeout exceeded");
548  return false;
549  }
550 
551  // Transform detected pose into fixed frame
552  if (detected.header.frame_id != params_->fixed_frame) {
553  try {
554  tf2_buffer_->transform(
555  detected, detected, params_->fixed_frame,
556  tf2::durationFromSec(params_->transform_tolerance));
557  } catch (const tf2::TransformException & ex) {
558  RCLCPP_WARN(this->get_logger(), "Failed to transform detected object pose");
559  return false;
560  }
561  }
562 
563  // The control law can oscillate if the orientation in the perception
564  // is not set correctly or has a lot of noise.
565  // Then, we skip the target orientation by pointing it
566  // in the same orientation than the vector from the robot to the object.
567  if (params_->skip_orientation) {
568  geometry_msgs::msg::PoseStamped robot_pose;
569  if (!nav2_util::getCurrentPose(
570  robot_pose, *tf2_buffer_, detected.header.frame_id, params_->base_frame,
571  params_->transform_tolerance,
572  iteration_start_time_))
573  {
574  RCLCPP_WARN(get_logger(), "Failed to get current robot pose");
575  return false;
576  }
577  double dx = detected.pose.position.x - robot_pose.pose.position.x;
578  double dy = detected.pose.position.y - robot_pose.pose.position.y;
579  double angle_to_target = std::atan2(dy, dx);
580  detected.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(angle_to_target);
581  }
582 
583  // Filter the detected pose
584  auto pose_filtered = filter_->update(detected);
585  filtered_dynamic_pose_pub_->publish(pose_filtered);
586 
587  pose = pose_filtered;
588  return true;
589 }
590 
592  geometry_msgs::msg::PoseStamped & pose, const std::string & frame_id)
593 {
594  try {
595  // Get the transform from the target frame to the fixed frame
596  auto transform = tf2_buffer_->lookupTransform(
597  params_->fixed_frame, frame_id, iteration_start_time_,
598  tf2::durationFromSec(params_->transform_tolerance));
599 
600  // Convert transform to pose
601  pose.header.frame_id = params_->fixed_frame;
602  pose.header.stamp = transform.header.stamp;
603  pose.pose.position.x = transform.transform.translation.x;
604  pose.pose.position.y = transform.transform.translation.y;
605  pose.pose.position.z = transform.transform.translation.z;
606  pose.pose.orientation = transform.transform.rotation;
607  } catch (const tf2::TransformException & ex) {
608  RCLCPP_WARN(
609  get_logger(),
610  "Failed to get transform for frame %s: %s", frame_id.c_str(), ex.what());
611  return false;
612  }
613 
614  // Filter the detected pose
615  auto filtered_pose = filter_->update(pose);
616  filtered_dynamic_pose_pub_->publish(filtered_pose);
617 
618  pose = filtered_pose;
619  return true;
620 }
621 
623  geometry_msgs::msg::PoseStamped & pose, const std::string & frame_id)
624 {
625  // Use frame tracking if we have a target frame, otherwise use topic tracking
626  if (!frame_id.empty()) {
627  if (!getFramePose(pose, frame_id)) {
629  "Failed to get pose in target frame: " + frame_id);
630  }
631  } else {
632  // Use the traditional pose detection from topic
633  if (!getRefinedPose(pose)) {
634  throw opennav_docking_core::FailedToDetectDock("Failed object detection");
635  }
636  }
637  return true;
638 }
639 
640 geometry_msgs::msg::PoseStamped FollowingServer::getPoseAtDistance(
641  const geometry_msgs::msg::PoseStamped & pose, double distance)
642 {
643  geometry_msgs::msg::PoseStamped robot_pose;
644  if (!nav2_util::getCurrentPose(
645  robot_pose, *tf2_buffer_, pose.header.frame_id, params_->base_frame,
646  params_->transform_tolerance,
647  iteration_start_time_))
648  {
649  RCLCPP_WARN(get_logger(), "Failed to get current robot pose");
650  // Return original pose as fallback
651  return pose;
652  }
653  double dx = pose.pose.position.x - robot_pose.pose.position.x;
654  double dy = pose.pose.position.y - robot_pose.pose.position.y;
655  const double dist = std::hypot(dx, dy);
656  geometry_msgs::msg::PoseStamped forward_pose = pose;
657  forward_pose.pose.position.x -= distance * (dx / dist);
658  forward_pose.pose.position.y -= distance * (dy / dist);
659  return forward_pose;
660 }
661 
662 bool FollowingServer::isGoalReached(const geometry_msgs::msg::PoseStamped & goal_pose)
663 {
664  geometry_msgs::msg::PoseStamped robot_pose;
665  if (!nav2_util::getCurrentPose(
666  robot_pose, *tf2_buffer_, goal_pose.header.frame_id, params_->base_frame,
667  params_->transform_tolerance,
668  iteration_start_time_))
669  {
670  RCLCPP_WARN(get_logger(), "Failed to get current robot pose");
671  return false;
672  }
673  const double dist = std::hypot(
674  robot_pose.pose.position.x - goal_pose.pose.position.x,
675  robot_pose.pose.position.y - goal_pose.pose.position.y);
676  const double yaw = angles::shortest_angular_distance(
677  tf2::getYaw(robot_pose.pose.orientation), tf2::getYaw(goal_pose.pose.orientation));
678  return dist < params_->linear_tolerance && abs(yaw) < params_->angular_tolerance;
679 }
680 
681 } // namespace opennav_following
682 
683 #include "rclcpp_components/register_node_macro.hpp"
684 
685 // Register the component with class_loader.
686 // This acts as a sort of entry point, allowing the component to be discoverable when its library
687 // is being loaded into a running process.
688 RCLCPP_COMPONENTS_REGISTER_NODE(opennav_following::FollowingServer)
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
bool is_cancel_requested() const
Whether or not a cancel command has come in.
bool is_preempt_requested() const
Whether the action server has been asked to be preempted with a new goal.
const std::shared_ptr< const typename ActionT::Goal > accept_pending_goal()
Accept pending goals.
A QoS profile for standard reliable topics with a history of 10 messages.
Failed to control into or out of the dock.
Failed to detect the charging dock.
An action server which implements a dynamic following behavior.
bool checkAndWarnIfCancelled(typename nav2::SimpleActionServer< ActionT >::SharedPtr &action_server, const std::string &name)
Checks and logs warning if action canceled.
nav2::CallbackReturn on_shutdown(const rclcpp_lifecycle::State &state) override
Called when in shutdown state.
void followObject()
Main action callback method to complete following request.
virtual bool getFramePose(geometry_msgs::msg::PoseStamped &pose, const std::string &frame_id)
Get the pose of a specific frame in the fixed frame.
virtual bool approachObject(geometry_msgs::msg::PoseStamped &object_pose, const std::string &target_frame=std::string(""))
Use control law and perception to approach the object.
virtual bool getTrackingPose(geometry_msgs::msg::PoseStamped &pose, const std::string &frame_id)
Get the tracking pose based on the current tracking mode.
void publishFollowingFeedback(uint16_t state)
Publish feedback from a following action.
geometry_msgs::msg::PoseStamped getPoseAtDistance(const geometry_msgs::msg::PoseStamped &pose, double distance)
Get the pose at a distance in front of the input pose.
virtual bool getRefinedPose(geometry_msgs::msg::PoseStamped &pose)
Method to obtain the refined dynamic pose.
nav2::CallbackReturn on_deactivate(const rclcpp_lifecycle::State &state) override
Deactivate member variables.
void publishZeroVelocity()
Publish zero velocity at terminal condition.
void getPreemptedGoalIfRequested(typename std::shared_ptr< const typename ActionT::Goal > goal, const typename nav2::SimpleActionServer< ActionT >::SharedPtr &action_server)
Gets a preempted goal if immediately requested.
bool isGoalReached(const geometry_msgs::msg::PoseStamped &goal_pose)
Check if the goal has been reached.
virtual bool rotateToObject(geometry_msgs::msg::PoseStamped &object_pose, const std::string &target_frame=std::string(""))
Rotate the robot to find the object again.
bool checkAndWarnIfPreempted(typename nav2::SimpleActionServer< ActionT >::SharedPtr &action_server, const std::string &name)
Checks and logs warning if action preempted.
nav2::CallbackReturn on_activate(const rclcpp_lifecycle::State &state) override
Activate member variables.
nav2::CallbackReturn on_configure(const rclcpp_lifecycle::State &state) override
Configure member variables.
nav2::CallbackReturn on_cleanup(const rclcpp_lifecycle::State &state) override
Reset member variables.