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