Nav2 Navigation Stack - rolling  main
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  dynamic_pose_sub_.reset();
137  return nav2::CallbackReturn::SUCCESS;
138 }
139 
140 nav2::CallbackReturn
141 FollowingServer::on_shutdown(const rclcpp_lifecycle::State &)
142 {
143  RCLCPP_INFO(get_logger(), "Shutting down %s", get_name());
144  return nav2::CallbackReturn::SUCCESS;
145 }
146 
147 template<typename ActionT>
149  typename std::shared_ptr<const typename ActionT::Goal> goal,
150  const typename nav2::SimpleActionServer<ActionT>::SharedPtr & action_server)
151 {
152  if (action_server->is_preempt_requested()) {
153  goal = action_server->accept_pending_goal();
154  }
155 }
156 
157 template<typename ActionT>
159  typename nav2::SimpleActionServer<ActionT>::SharedPtr & action_server,
160  const std::string & name)
161 {
162  if (action_server->is_cancel_requested()) {
163  RCLCPP_WARN(get_logger(), "Goal was cancelled. Cancelling %s action", name.c_str());
164  return true;
165  }
166  return false;
167 }
168 
169 template<typename ActionT>
171  typename nav2::SimpleActionServer<ActionT>::SharedPtr & action_server,
172  const std::string & name)
173 {
174  if (action_server->is_preempt_requested()) {
175  RCLCPP_WARN(get_logger(), "Goal was preempted. Cancelling %s action", name.c_str());
176  return true;
177  }
178  return false;
179 }
180 
182 {
183  std::unique_lock<std::mutex> lock_reinit(param_handler_->getMutex());
184  action_start_time_ = this->now();
185  nav2::Rate loop_rate(this, params_->controller_frequency);
186 
187  auto goal = following_action_server_->get_current_goal();
188  auto result = std::make_shared<FollowObject::Result>();
189 
190  if (!following_action_server_ || !following_action_server_->is_server_active()) {
191  RCLCPP_DEBUG(get_logger(), "Action server unavailable or inactive. Stopping.");
192  return;
193  }
194 
195  if (checkAndWarnIfCancelled<FollowObject>(following_action_server_, "follow_object")) {
196  following_action_server_->terminate_all();
197  return;
198  }
199 
200  getPreemptedGoalIfRequested<FollowObject>(goal, following_action_server_);
201  num_retries_ = 0;
202  static_timer_initialized_ = false;
203 
204  // Reset the last detected dynamic pose timestamp so we start fresh for this action
205  detected_dynamic_pose_.header.stamp = rclcpp::Time(0);
206 
207  try {
208  auto pose_topic = goal->pose_topic;
209  auto target_frame = goal->tracked_frame;
210  if (target_frame.empty()) {
211  if (pose_topic.empty()) {
212  RCLCPP_ERROR(
213  get_logger(),
214  "Both pose topic and target frame are empty. Cannot follow object.");
215  result->error_code = FollowObject::Result::FAILED_TO_DETECT_OBJECT;
216  result->error_msg = "No pose topic or target frame provided.";
217  following_action_server_->terminate_all(result);
218  return;
219  } else {
220  lock_reinit.unlock();
221  RCLCPP_INFO(get_logger(), "Subscribing to pose topic: %s", pose_topic.c_str());
222  dynamic_pose_sub_ = create_subscription<geometry_msgs::msg::PoseStamped>(
223  pose_topic,
224  [this](const geometry_msgs::msg::PoseStamped::ConstSharedPtr & pose) {
225  detected_dynamic_pose_ = *pose;
226  },
227  nav2::qos::StandardTopicQoS(1)); // Only want the most recent pose
228  lock_reinit.lock();
229  }
230  } else {
231  RCLCPP_INFO(get_logger(), "Following frame: %s instead of pose", target_frame.c_str());
232  }
233 
234  // Following control loop: while not timeout, run controller
235  geometry_msgs::msg::PoseStamped object_pose;
236  rclcpp::Duration max_duration = goal->max_duration;
237  while (rclcpp::ok()) {
238  try {
239  // Check if we have run out of time
240  if (this->now() - action_start_time_ > max_duration && max_duration.seconds() > 0.0) {
241  RCLCPP_INFO(get_logger(), "Exceeded max duration. Stopping.");
242  result->total_elapsed_time = this->now() - action_start_time_;
243  result->num_retries = num_retries_;
245  following_action_server_->succeeded_current(result);
246  dynamic_pose_sub_.reset();
247  return;
248  }
249 
250  // Approach the object using control law
251  if (approachObject(object_pose, target_frame)) {
252  // Initialize static timer on first entry
253  if (!static_timer_initialized_) {
254  static_object_start_time_ = this->now();
255  static_timer_initialized_ = true;
256  }
257 
258  // We have reached the object, maintain position
259  RCLCPP_INFO_THROTTLE(
260  get_logger(), *get_clock(), 1000,
261  "Reached object. Stopping until goal is moved again.");
262  publishFollowingFeedback(FollowObject::Feedback::STOPPING);
264 
265  // Stop if the object has been static for some time
266  if (params_->static_object_timeout > 0.0) {
267  auto static_duration = this->now() - static_object_start_time_;
268  if (static_duration.seconds() > params_->static_object_timeout) {
269  RCLCPP_INFO(
270  get_logger(),
271  "Object has been static for %.2f seconds (timeout: %.2f), stopping.",
272  static_duration.seconds(), params_->static_object_timeout);
273  result->total_elapsed_time = this->now() - action_start_time_;
274  result->num_retries = num_retries_;
276  following_action_server_->succeeded_current(result);
277  dynamic_pose_sub_.reset();
278  return;
279  }
280  }
281  } else {
282  // Cancelled, preempted, or shutting down (recoverable errors throw DockingException)
283  static_timer_initialized_ = false;
284  result->total_elapsed_time = this->now() - action_start_time_;
286  following_action_server_->terminate_all(result);
287  dynamic_pose_sub_.reset();
288  return;
289  }
291  if (++num_retries_ > params_->max_retries) {
292  RCLCPP_ERROR(get_logger(), "Failed to follow, all retries have been used");
293  throw;
294  }
295  RCLCPP_WARN(get_logger(), "Following failed, will retry: %s", e.what());
296 
297  // Perform an in-place rotation to find the object again
298  if (params_->search_by_rotating) {
299  RCLCPP_INFO(get_logger(), "Rotating to find object again");
300  if (!rotateToObject(object_pose, target_frame)) {
301  // Cancelled, preempted, or shutting down
303  following_action_server_->terminate_all(result);
304  return;
305  }
306  } else {
307  RCLCPP_INFO(get_logger(), "Using last known heading to find object again");
308  }
309  }
310  loop_rate.sleep();
311  }
312  } catch (const tf2::TransformException & e) {
313  result->error_msg = std::string("Transform error: ") + e.what();
314  RCLCPP_ERROR(get_logger(), "%s", result->error_msg.c_str());
315  result->error_code = FollowObject::Result::TF_ERROR;
317  result->error_msg = e.what();
318  RCLCPP_ERROR(get_logger(), "%s", result->error_msg.c_str());
319  result->error_code = FollowObject::Result::FAILED_TO_DETECT_OBJECT;
321  result->error_msg = e.what();
322  RCLCPP_ERROR(get_logger(), "%s", result->error_msg.c_str());
323  result->error_code = FollowObject::Result::FAILED_TO_CONTROL;
325  result->error_msg = e.what();
326  RCLCPP_ERROR(get_logger(), "%s", result->error_msg.c_str());
327  result->error_code = FollowObject::Result::UNKNOWN;
328  } catch (std::exception & e) {
329  result->error_msg = e.what();
330  RCLCPP_ERROR(get_logger(), "%s", result->error_msg.c_str());
331  result->error_code = FollowObject::Result::UNKNOWN;
332  }
333 
334  // Stop the robot and report
335  result->total_elapsed_time = this->now() - action_start_time_;
336  result->num_retries = num_retries_;
338  following_action_server_->terminate_current(result);
339  dynamic_pose_sub_.reset();
340 }
341 
343  geometry_msgs::msg::PoseStamped & object_pose, const std::string & target_frame)
344 {
345  rclcpp::Rate loop_rate(params_->controller_frequency);
346  while (rclcpp::ok()) {
347  // Update the iteration start time, used for get robot position, transformation and control
348  iteration_start_time_ = this->now();
349 
350  publishFollowingFeedback(FollowObject::Feedback::CONTROLLING);
351 
352  // Stop if cancelled/preempted
353  if (checkAndWarnIfCancelled<FollowObject>(following_action_server_, "follow_object") ||
354  checkAndWarnIfPreempted<FollowObject>(following_action_server_, "follow_object"))
355  {
356  return false;
357  }
358 
359  // Get the tracking pose from topic or frame
360  getTrackingPose(object_pose, target_frame);
361 
362  // Get the pose at the distance we want to maintain from the object
363  // Stop and report success if goal is reached
364  auto target_pose = getPoseAtDistance(object_pose, params_->desired_distance);
365  if (isGoalReached(target_pose)) {
366  return true;
367  }
368 
369  // The control law can get jittery when close to the end when atan2's can explode.
370  // Thus, we reduce the desired distance by a small amount so that the robot never
371  // gets to the end of the spiral before its at the desired distance to stop the
372  // following procedure.
373  const double backward_projection = 0.25;
374  const double effective_distance = params_->desired_distance - backward_projection;
375  target_pose = getPoseAtDistance(object_pose, effective_distance);
376 
377  // ... and transform the target_pose into base_frame
378  try {
379  tf2_buffer_->transform(
380  target_pose, target_pose, params_->base_frame,
381  tf2::durationFromSec(params_->transform_tolerance));
382  } catch (const tf2::TransformException & ex) {
383  RCLCPP_WARN(get_logger(), "Failed to transform target pose: %s", ex.what());
384  return false;
385  }
386 
387  // Compute and publish controls
388  auto command = std::make_unique<geometry_msgs::msg::TwistStamped>();
389  command->header.stamp = now();
390  if (!controller_->computeVelocityCommand(target_pose.pose, command->twist, true, false)) {
391  throw opennav_docking_core::FailedToControl("Failed to get control");
392  }
393  vel_publisher_->publish(std::move(command));
394 
395  loop_rate.sleep();
396  }
397  return false;
398 }
399 
401  geometry_msgs::msg::PoseStamped & object_pose, const std::string & target_frame)
402 {
403  const double dt = 1.0 / params_->controller_frequency;
404 
405  // object_pose is still default-constructed (empty frame_id) if no detection has
406  // ever arrived for this goal, fall back to the fixed frame and let the robot search.
407  const std::string reference_frame =
408  object_pose.header.frame_id.empty() ? params_->fixed_frame : object_pose.header.frame_id;
409 
410  // Refresh start time before transforming.
411  iteration_start_time_ = this->now();
412 
413  // Compute initial robot heading
414  geometry_msgs::msg::PoseStamped robot_pose;
415  if (!nav2_util::getCurrentPose(
416  robot_pose, *tf2_buffer_, reference_frame, params_->base_frame,
417  params_->transform_tolerance,
418  iteration_start_time_))
419  {
420  RCLCPP_WARN(get_logger(), "Failed to get current robot pose");
421  return false;
422  }
423  double initial_yaw = tf2::getYaw(robot_pose.pose.orientation);
424 
425  // Search angles: left offset, then right offset from initial heading
426  std::vector<double> angles = {initial_yaw + params_->search_angle,
427  initial_yaw - params_->search_angle};
428 
429  rclcpp::Rate loop_rate(params_->controller_frequency);
430  auto start = this->now();
431  auto timeout = rclcpp::Duration::from_seconds(params_->rotate_to_object_timeout);
432 
433  // Iterate over target angles
434  for (const double & target_angle : angles) {
435  // Create a target pose oriented at target_angle
436  auto target_pose = object_pose;
437  target_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(target_angle);
438 
439  // Rotate towards target_angle while checking for detection
440  while (rclcpp::ok()) {
441  // Update the iteration start time, used for get robot position, transformation and control
442  iteration_start_time_ = this->now();
443 
444  publishFollowingFeedback(FollowObject::Feedback::RETRY);
445 
446  // Stop if cancelled/preempted
447  if (checkAndWarnIfCancelled<FollowObject>(following_action_server_, "follow_object") ||
448  checkAndWarnIfPreempted<FollowObject>(following_action_server_, "follow_object"))
449  {
450  return false;
451  }
452 
453  // Get current robot pose
454  if (!nav2_util::getCurrentPose(
455  robot_pose, *tf2_buffer_, reference_frame, params_->base_frame,
456  params_->transform_tolerance,
457  iteration_start_time_))
458  {
459  RCLCPP_WARN(get_logger(), "Failed to get current robot pose");
460  return false;
461  }
462 
463  double angular_distance_to_heading = angles::shortest_angular_distance(
464  tf2::getYaw(robot_pose.pose.orientation), target_angle);
465 
466  // If we are close enough to the target orientation, break and try next angle
467  if (fabs(angular_distance_to_heading) < params_->angular_tolerance) {
468  break;
469  }
470 
471  // While rotating, check if we can get the tracking pose (object detected)
472  try {
473  if (getTrackingPose(object_pose, target_frame)) {
474  return true;
475  }
477  // No detection yet, continue rotating
478  }
479 
480  geometry_msgs::msg::Twist current_vel;
481  current_vel.angular.z = odom_sub_->getRawTwist().angular.z;
482 
483  auto command = std::make_unique<geometry_msgs::msg::TwistStamped>();
484  command->header = robot_pose.header;
485  command->twist = controller_->computeRotateToHeadingCommand(
486  angular_distance_to_heading, current_vel, dt);
487 
488  vel_publisher_->publish(std::move(command));
489 
490  if (this->now() - start > timeout) {
491  throw opennav_docking_core::FailedToControl("Timed out rotating to object");
492  }
493 
494  loop_rate.sleep();
495  }
496  }
497 
498  // If we exhausted all search angles and did not detect the object, fail
499  throw opennav_docking_core::FailedToControl("Failed to rotate to object");
500 }
501 
503 {
504  auto cmd_vel = std::make_unique<geometry_msgs::msg::TwistStamped>();
505  cmd_vel->header.frame_id = params_->base_frame;
506  cmd_vel->header.stamp = now();
507  vel_publisher_->publish(std::move(cmd_vel));
508 }
509 
511 {
512  auto feedback = std::make_shared<FollowObject::Feedback>();
513  feedback->state = state;
514  feedback->following_time = iteration_start_time_ - action_start_time_;
515  feedback->num_retries = num_retries_;
516  following_action_server_->publish_feedback(feedback);
517 }
518 
519 bool FollowingServer::getRefinedPose(geometry_msgs::msg::PoseStamped & pose)
520 {
521  // Get current detections and transform to frame
522  geometry_msgs::msg::PoseStamped detected = detected_dynamic_pose_;
523 
524  // If we haven't received any detection yet, wait up to detection_timeout_ for one to arrive.
525  if (detected.header.stamp == builtin_interfaces::msg::Time{}) {
526  auto start = this->now();
527  auto timeout = rclcpp::Duration::from_seconds(params_->detection_timeout);
528  nav2::Rate wait_rate(this, params_->controller_frequency);
529  while (this->now() - start < timeout) {
530  // Check if a new detection arrived
531  if (detected_dynamic_pose_.header.stamp != builtin_interfaces::msg::Time{}) {
532  detected = detected_dynamic_pose_;
533  break;
534  }
535  wait_rate.sleep();
536  }
537  if (detected.header.stamp == builtin_interfaces::msg::Time{}) {
538  RCLCPP_WARN(this->get_logger(), "No detection received within timeout period");
539  return false;
540  }
541  }
542 
543  // Validate that external pose is new enough
544  auto timeout = rclcpp::Duration::from_seconds(params_->detection_timeout);
545  if (this->now() - detected.header.stamp > timeout) {
546  RCLCPP_WARN(this->get_logger(), "Lost detection or did not detect: timeout exceeded");
547  return false;
548  }
549 
550  // Transform detected pose into fixed frame
551  if (detected.header.frame_id != params_->fixed_frame) {
552  try {
553  tf2_buffer_->transform(
554  detected, detected, params_->fixed_frame,
555  tf2::durationFromSec(params_->transform_tolerance));
556  } catch (const tf2::TransformException & ex) {
557  RCLCPP_WARN(this->get_logger(), "Failed to transform detected object pose");
558  return false;
559  }
560  }
561 
562  // The control law can oscillate if the orientation in the perception
563  // is not set correctly or has a lot of noise.
564  // Then, we skip the target orientation by pointing it
565  // in the same orientation than the vector from the robot to the object.
566  if (params_->skip_orientation) {
567  geometry_msgs::msg::PoseStamped robot_pose;
568  if (!nav2_util::getCurrentPose(
569  robot_pose, *tf2_buffer_, detected.header.frame_id, params_->base_frame,
570  params_->transform_tolerance,
571  iteration_start_time_))
572  {
573  RCLCPP_WARN(get_logger(), "Failed to get current robot pose");
574  return false;
575  }
576  double dx = detected.pose.position.x - robot_pose.pose.position.x;
577  double dy = detected.pose.position.y - robot_pose.pose.position.y;
578  double angle_to_target = std::atan2(dy, dx);
579  detected.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(angle_to_target);
580  }
581 
582  // Filter the detected pose
583  auto pose_filtered = filter_->update(detected);
584  filtered_dynamic_pose_pub_->publish(pose_filtered);
585 
586  pose = pose_filtered;
587  return true;
588 }
589 
591  geometry_msgs::msg::PoseStamped & pose, const std::string & frame_id)
592 {
593  try {
594  // Get the transform from the target frame to the fixed frame
595  auto transform = tf2_buffer_->lookupTransform(
596  params_->fixed_frame, frame_id, iteration_start_time_,
597  tf2::durationFromSec(params_->transform_tolerance));
598 
599  // Convert transform to pose
600  pose.header.frame_id = params_->fixed_frame;
601  pose.header.stamp = transform.header.stamp;
602  pose.pose.position.x = transform.transform.translation.x;
603  pose.pose.position.y = transform.transform.translation.y;
604  pose.pose.position.z = transform.transform.translation.z;
605  pose.pose.orientation = transform.transform.rotation;
606  } catch (const tf2::TransformException & ex) {
607  RCLCPP_WARN(
608  get_logger(),
609  "Failed to get transform for frame %s: %s", frame_id.c_str(), ex.what());
610  return false;
611  }
612 
613  // Filter the detected pose
614  auto filtered_pose = filter_->update(pose);
615  filtered_dynamic_pose_pub_->publish(filtered_pose);
616 
617  pose = filtered_pose;
618  return true;
619 }
620 
622  geometry_msgs::msg::PoseStamped & pose, const std::string & frame_id)
623 {
624  // Use frame tracking if we have a target frame, otherwise use topic tracking
625  if (!frame_id.empty()) {
626  if (!getFramePose(pose, frame_id)) {
628  "Failed to get pose in target frame: " + frame_id);
629  }
630  } else {
631  // Use the traditional pose detection from topic
632  if (!getRefinedPose(pose)) {
633  throw opennav_docking_core::FailedToDetectDock("Failed object detection");
634  }
635  }
636  return true;
637 }
638 
639 geometry_msgs::msg::PoseStamped FollowingServer::getPoseAtDistance(
640  const geometry_msgs::msg::PoseStamped & pose, double distance)
641 {
642  geometry_msgs::msg::PoseStamped robot_pose;
643  if (!nav2_util::getCurrentPose(
644  robot_pose, *tf2_buffer_, pose.header.frame_id, params_->base_frame,
645  params_->transform_tolerance,
646  iteration_start_time_))
647  {
648  RCLCPP_WARN(get_logger(), "Failed to get current robot pose");
649  // Return original pose as fallback
650  return pose;
651  }
652  double dx = pose.pose.position.x - robot_pose.pose.position.x;
653  double dy = pose.pose.position.y - robot_pose.pose.position.y;
654  const double dist = std::hypot(dx, dy);
655  if (dist < 1e-6) {
656  return pose;
657  }
658  geometry_msgs::msg::PoseStamped forward_pose = pose;
659  forward_pose.pose.position.x -= distance * (dx / dist);
660  forward_pose.pose.position.y -= distance * (dy / dist);
661  return forward_pose;
662 }
663 
664 bool FollowingServer::isGoalReached(const geometry_msgs::msg::PoseStamped & goal_pose)
665 {
666  geometry_msgs::msg::PoseStamped robot_pose;
667  if (!nav2_util::getCurrentPose(
668  robot_pose, *tf2_buffer_, goal_pose.header.frame_id, params_->base_frame,
669  params_->transform_tolerance,
670  iteration_start_time_))
671  {
672  RCLCPP_WARN(get_logger(), "Failed to get current robot pose");
673  return false;
674  }
675  const double dist = std::hypot(
676  robot_pose.pose.position.x - goal_pose.pose.position.x,
677  robot_pose.pose.position.y - goal_pose.pose.position.y);
678  const double yaw = angles::shortest_angular_distance(
679  tf2::getYaw(robot_pose.pose.orientation), tf2::getYaw(goal_pose.pose.orientation));
680  return dist < params_->linear_tolerance && abs(yaw) < params_->angular_tolerance;
681 }
682 
683 } // namespace opennav_following
684 
685 #include "rclcpp_components/register_node_macro.hpp"
686 
687 // Register the component with class_loader.
688 // This acts as a sort of entry point, allowing the component to be discoverable when its library
689 // is being loaded into a running process.
690 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.