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_->base_frame);
66 
67  if (params_->use_collision_detection) {
68  RCLCPP_ERROR(
69  get_logger(),
70  "Collision detection is not supported in the following server. Please disable "
71  "the controller.use_collision_detection parameter.");
72  return nav2::CallbackReturn::FAILURE;
73  }
74 
75  // Setup filter
76  filter_ = std::make_unique<opennav_docking::PoseFilter>(params_->filter_coef,
77  params_->detection_timeout);
78 
79  // And publish the filtered pose
80  filtered_dynamic_pose_pub_ =
81  create_publisher<geometry_msgs::msg::PoseStamped>("filtered_dynamic_pose");
82 
83  // Initialize static object detection variables
84  static_timer_initialized_ = false;
85  static_object_start_time_ = rclcpp::Time(0);
86 
87  return nav2::CallbackReturn::SUCCESS;
88 }
89 
90 nav2::CallbackReturn
91 FollowingServer::on_activate(const rclcpp_lifecycle::State & /*state*/)
92 {
93  RCLCPP_INFO(get_logger(), "Activating %s", get_name());
94 
95  tf2_listener_ = nav2::create_transform_listener(*tf2_buffer_, this, true);
96  vel_publisher_->on_activate();
97  filtered_dynamic_pose_pub_->on_activate();
98  following_action_server_->activate();
99  param_handler_->activate();
100 
101  // Create bond connection
102  createBond();
103 
104  return nav2::CallbackReturn::SUCCESS;
105 }
106 
107 nav2::CallbackReturn
108 FollowingServer::on_deactivate(const rclcpp_lifecycle::State & /*state*/)
109 {
110  RCLCPP_INFO(get_logger(), "Deactivating %s", get_name());
111 
112  following_action_server_->deactivate();
113  vel_publisher_->on_deactivate();
114  filtered_dynamic_pose_pub_->on_deactivate();
115  param_handler_->deactivate();
116 
117  tf2_listener_.reset();
118 
119  // Destroy bond connection
120  destroyBond();
121 
122  return nav2::CallbackReturn::SUCCESS;
123 }
124 
125 nav2::CallbackReturn
126 FollowingServer::on_cleanup(const rclcpp_lifecycle::State & /*state*/)
127 {
128  RCLCPP_INFO(get_logger(), "Cleaning up %s", get_name());
129  tf2_buffer_.reset();
130  following_action_server_.reset();
131  controller_.reset();
132  vel_publisher_.reset();
133  filtered_dynamic_pose_pub_.reset();
134  odom_sub_.reset();
135  dynamic_pose_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::unique_lock<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  lock_reinit.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  lock_reinit.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  dynamic_pose_sub_.reset();
277  return;
278  }
279  }
280  } else {
281  // Cancelled, preempted, or shutting down (recoverable errors throw DockingException)
282  static_timer_initialized_ = false;
283  result->total_elapsed_time = this->now() - action_start_time_;
285  following_action_server_->terminate_all(result);
286  dynamic_pose_sub_.reset();
287  return;
288  }
290  if (++num_retries_ > params_->max_retries) {
291  RCLCPP_ERROR(get_logger(), "Failed to follow, all retries have been used");
292  throw;
293  }
294  RCLCPP_WARN(get_logger(), "Following failed, will retry: %s", e.what());
295 
296  // Perform an in-place rotation to find the object again
297  if (params_->search_by_rotating) {
298  RCLCPP_INFO(get_logger(), "Rotating to find object again");
299  if (!rotateToObject(object_pose, target_frame)) {
300  // Cancelled, preempted, or shutting down
302  following_action_server_->terminate_all(result);
303  return;
304  }
305  } else {
306  RCLCPP_INFO(get_logger(), "Using last known heading to find object again");
307  }
308  }
309  loop_rate.sleep();
310  }
311  } catch (const opennav_docking_core::DockingTFError & e) {
312  result->error_msg = std::string("Transform error: ") + e.what();
313  RCLCPP_ERROR(get_logger(), "%s", result->error_msg.c_str());
314  result->error_code = FollowObject::Result::TF_ERROR;
315  } catch (const tf2::TransformException & e) {
316  result->error_msg = std::string("Transform error: ") + e.what();
317  RCLCPP_ERROR(get_logger(), "%s", result->error_msg.c_str());
318  result->error_code = FollowObject::Result::TF_ERROR;
320  result->error_msg = e.what();
321  RCLCPP_ERROR(get_logger(), "%s", result->error_msg.c_str());
322  result->error_code = FollowObject::Result::FAILED_TO_DETECT_OBJECT;
324  result->error_msg = e.what();
325  RCLCPP_ERROR(get_logger(), "%s", result->error_msg.c_str());
326  result->error_code = FollowObject::Result::FAILED_TO_CONTROL;
328  result->error_msg = e.what();
329  RCLCPP_ERROR(get_logger(), "%s", result->error_msg.c_str());
330  result->error_code = FollowObject::Result::UNKNOWN;
331  } catch (std::exception & e) {
332  result->error_msg = e.what();
333  RCLCPP_ERROR(get_logger(), "%s", result->error_msg.c_str());
334  result->error_code = FollowObject::Result::UNKNOWN;
335  }
336 
337  // Stop the robot and report
338  result->total_elapsed_time = this->now() - action_start_time_;
339  result->num_retries = num_retries_;
341  following_action_server_->terminate_current(result);
342  dynamic_pose_sub_.reset();
343 }
344 
346  geometry_msgs::msg::PoseStamped & object_pose, const std::string & target_frame)
347 {
348  rclcpp::Rate loop_rate(params_->controller_frequency);
349  while (rclcpp::ok()) {
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  // Use one robot pose and timestamp for all geometry and control in this iteration.
358  const auto robot_pose = getRobotPose();
359  iteration_start_time_ = robot_pose.header.stamp;
360  const auto base_to_fixed_transform = nav2_util::poseToTransformStamped(
361  robot_pose, params_->base_frame);
362 
363  publishFollowingFeedback(FollowObject::Feedback::CONTROLLING);
364 
365  // Get the tracking pose from topic or frame
366  getTrackingPose(object_pose, target_frame, robot_pose);
367 
368  // Get the pose at the distance we want to maintain from the object
369  // Stop and report success if goal is reached
370  auto target_pose = getPoseAtDistance(robot_pose, object_pose, params_->desired_distance);
371  if (isGoalReached(robot_pose, target_pose)) {
372  return true;
373  }
374 
375  // The control law can get jittery when close to the end when atan2's can explode.
376  // Thus, we reduce the desired distance by a small amount so that the robot never
377  // gets to the end of the spiral before its at the desired distance to stop the
378  // following procedure.
379  const double backward_projection = 0.25;
380  const double effective_distance = params_->desired_distance - backward_projection;
381  target_pose = getPoseAtDistance(robot_pose, object_pose, effective_distance);
382 
383  // ... and transform the target_pose into base_frame using the same transform
384  tf2::doTransform(
385  target_pose, target_pose, nav2_util::invertTransform(base_to_fixed_transform));
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(
391  target_pose.pose, command->twist, base_to_fixed_transform, true, false))
392  {
393  throw opennav_docking_core::FailedToControl("Failed to get control");
394  }
395  vel_publisher_->publish(std::move(command));
396 
397  loop_rate.sleep();
398  }
399  return false;
400 }
401 
403  geometry_msgs::msg::PoseStamped & object_pose, const std::string & target_frame)
404 {
405  const double dt = 1.0 / params_->controller_frequency;
406 
407  // Compute initial robot heading
408  auto robot_pose = getRobotPose();
409  double initial_yaw = tf2::getYaw(robot_pose.pose.orientation);
410 
411  // Search angles: left offset, then right offset from initial heading
412  std::vector<double> angles = {initial_yaw + params_->search_angle,
413  initial_yaw - params_->search_angle};
414 
415  rclcpp::Rate loop_rate(params_->controller_frequency);
416  auto start = this->now();
417  auto timeout = rclcpp::Duration::from_seconds(params_->rotate_to_object_timeout);
418 
419  // Iterate over target angles
420  for (const double & target_angle : angles) {
421  // Create a target pose oriented at target_angle
422  auto target_pose = object_pose;
423  target_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(target_angle);
424 
425  // Rotate towards target_angle while checking for detection
426  while (rclcpp::ok()) {
427  // Obtain one pose for all geometry and control in this rotation iteration.
428  robot_pose = getRobotPose();
429  iteration_start_time_ = robot_pose.header.stamp;
430 
431  publishFollowingFeedback(FollowObject::Feedback::RETRY);
432 
433  // Stop if cancelled/preempted
434  if (checkAndWarnIfCancelled<FollowObject>(following_action_server_, "follow_object") ||
435  checkAndWarnIfPreempted<FollowObject>(following_action_server_, "follow_object"))
436  {
437  return false;
438  }
439 
440  double angular_distance_to_heading = angles::shortest_angular_distance(
441  tf2::getYaw(robot_pose.pose.orientation), target_angle);
442 
443  // If we are close enough to the target orientation, break and try next angle
444  if (fabs(angular_distance_to_heading) < params_->angular_tolerance) {
445  break;
446  }
447 
448  // While rotating, check if we can get the tracking pose (object detected)
449  try {
450  if (getTrackingPose(object_pose, target_frame, robot_pose)) {
451  return true;
452  }
454  // No detection yet, continue rotating
455  }
456 
457  geometry_msgs::msg::Twist current_vel;
458  current_vel.angular.z = odom_sub_->getRawTwist().angular.z;
459 
460  auto command = std::make_unique<geometry_msgs::msg::TwistStamped>();
461  command->header = robot_pose.header;
462  command->twist = controller_->computeRotateToHeadingCommand(
463  angular_distance_to_heading, current_vel, dt);
464 
465  vel_publisher_->publish(std::move(command));
466 
467  if (this->now() - start > timeout) {
468  throw opennav_docking_core::FailedToControl("Timed out rotating to object");
469  }
470 
471  loop_rate.sleep();
472  }
473  }
474 
475  // If we exhausted all search angles and did not detect the object, fail
476  throw opennav_docking_core::FailedToControl("Failed to rotate to object");
477 }
478 
480 {
481  auto cmd_vel = std::make_unique<geometry_msgs::msg::TwistStamped>();
482  cmd_vel->header.frame_id = params_->base_frame;
483  cmd_vel->header.stamp = now();
484  vel_publisher_->publish(std::move(cmd_vel));
485 }
486 
488 {
489  auto feedback = std::make_shared<FollowObject::Feedback>();
490  feedback->state = state;
491  feedback->following_time = this->now() - action_start_time_;
492  feedback->num_retries = num_retries_;
493  following_action_server_->publish_feedback(feedback);
494 }
495 
497  geometry_msgs::msg::PoseStamped & pose,
498  const geometry_msgs::msg::PoseStamped & robot_pose)
499 {
500  // Get current detections and transform to frame
501  geometry_msgs::msg::PoseStamped detected = detected_dynamic_pose_;
502 
503  // If we haven't received any detection yet, wait up to detection_timeout_ for one to arrive.
504  if (detected.header.stamp == builtin_interfaces::msg::Time{}) {
505  auto start = this->now();
506  auto timeout = rclcpp::Duration::from_seconds(params_->detection_timeout);
507  nav2::Rate wait_rate(this, params_->controller_frequency);
508  while (this->now() - start < timeout) {
509  // Check if a new detection arrived
510  if (detected_dynamic_pose_.header.stamp != builtin_interfaces::msg::Time{}) {
511  detected = detected_dynamic_pose_;
512  break;
513  }
514  wait_rate.sleep();
515  }
516  if (detected.header.stamp == builtin_interfaces::msg::Time{}) {
517  RCLCPP_WARN(this->get_logger(), "No detection received within timeout period");
518  return false;
519  }
520  }
521 
522  // Validate that external pose is new enough
523  auto timeout = rclcpp::Duration::from_seconds(params_->detection_timeout);
524  if (this->now() - detected.header.stamp > timeout) {
525  RCLCPP_WARN(this->get_logger(), "Lost detection or did not detect: timeout exceeded");
526  return false;
527  }
528 
529  // Transform detected pose into fixed frame
530  if (detected.header.frame_id != params_->fixed_frame) {
531  try {
532  tf2_buffer_->transform(
533  detected, detected, params_->fixed_frame,
534  tf2::durationFromSec(params_->transform_tolerance));
535  } catch (const tf2::TransformException & ex) {
536  RCLCPP_WARN(this->get_logger(), "Failed to transform detected object pose");
537  return false;
538  }
539  }
540 
541  // The control law can oscillate if the orientation in the perception
542  // is not set correctly or has a lot of noise.
543  // Then, we skip the target orientation by pointing it
544  // in the same orientation than the vector from the robot to the object.
545  if (params_->skip_orientation) {
546  double dx = detected.pose.position.x - robot_pose.pose.position.x;
547  double dy = detected.pose.position.y - robot_pose.pose.position.y;
548  double angle_to_target = std::atan2(dy, dx);
549  detected.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(angle_to_target);
550  }
551 
552  // Filter the detected pose
553  auto pose_filtered = filter_->update(detected);
554  filtered_dynamic_pose_pub_->publish(pose_filtered);
555 
556  pose = pose_filtered;
557  return true;
558 }
559 
561  geometry_msgs::msg::PoseStamped & pose, const std::string & frame_id)
562 {
563  try {
564  // Matching the target to the staleness-checked robot snapshot used by this control
565  // iteration.
566  const auto transform = tf2_buffer_->lookupTransform(
567  params_->fixed_frame, frame_id, iteration_start_time_,
568  tf2::durationFromSec(params_->transform_tolerance));
569  pose = nav2_util::transformToPoseStamped(transform);
570  } catch (const tf2::TransformException & ex) {
571  RCLCPP_WARN(
572  get_logger(),
573  "Failed to get transform for frame %s: %s", frame_id.c_str(), ex.what());
574  return false;
575  }
576 
577  // Filter the detected pose
578  auto filtered_pose = filter_->update(pose);
579  filtered_dynamic_pose_pub_->publish(filtered_pose);
580 
581  pose = filtered_pose;
582  return true;
583 }
584 
586  geometry_msgs::msg::PoseStamped & pose, const std::string & frame_id,
587  const geometry_msgs::msg::PoseStamped & robot_pose)
588 {
589  // Use frame tracking if we have a target frame, otherwise use topic tracking
590  if (!frame_id.empty()) {
591  if (!getFramePose(pose, frame_id)) {
593  "Failed to get pose in target frame: " + frame_id);
594  }
595  } else {
596  // Use the traditional pose detection from topic
597  if (!getRefinedPose(pose, robot_pose)) {
598  throw opennav_docking_core::FailedToDetectDock("Failed object detection");
599  }
600  }
601  return true;
602 }
603 
604 geometry_msgs::msg::PoseStamped FollowingServer::getRobotPose()
605 {
606  geometry_msgs::msg::PoseStamped robot_pose;
607  if (!nav2_util::getFreshPose(
608  *tf2_buffer_, params_->fixed_frame, params_->base_frame, now(),
609  params_->transform_staleness_threshold, robot_pose))
610  {
611  throw opennav_docking_core::DockingTFError("Failed to get a fresh robot pose");
612  }
613  return robot_pose;
614 }
615 
616 geometry_msgs::msg::PoseStamped FollowingServer::getPoseAtDistance(
617  const geometry_msgs::msg::PoseStamped & robot_pose,
618  const geometry_msgs::msg::PoseStamped & pose, double distance)
619 {
620  double dx = pose.pose.position.x - robot_pose.pose.position.x;
621  double dy = pose.pose.position.y - robot_pose.pose.position.y;
622  const double dist = std::hypot(dx, dy);
623  if (dist < 1e-6) {
624  return pose;
625  }
626  geometry_msgs::msg::PoseStamped forward_pose = pose;
627  forward_pose.pose.position.x -= distance * (dx / dist);
628  forward_pose.pose.position.y -= distance * (dy / dist);
629  return forward_pose;
630 }
631 
633  const geometry_msgs::msg::PoseStamped & robot_pose,
634  const geometry_msgs::msg::PoseStamped & goal_pose)
635 {
636  const double dist = std::hypot(
637  robot_pose.pose.position.x - goal_pose.pose.position.x,
638  robot_pose.pose.position.y - goal_pose.pose.position.y);
639  const double yaw = angles::shortest_angular_distance(
640  tf2::getYaw(robot_pose.pose.orientation), tf2::getYaw(goal_pose.pose.orientation));
641  return dist < params_->linear_tolerance && abs(yaw) < params_->angular_tolerance;
642 }
643 
644 } // namespace opennav_following
645 
646 #include "rclcpp_components/register_node_macro.hpp"
647 
648 // Register the component with class_loader.
649 // This acts as a sort of entry point, allowing the component to be discoverable when its library
650 // is being loaded into a running process.
651 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.
A required transform was unavailable or stale.
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 getRefinedPose(geometry_msgs::msg::PoseStamped &pose, const geometry_msgs::msg::PoseStamped &robot_pose)
Method to obtain the refined dynamic pose.
geometry_msgs::msg::PoseStamped getRobotPose()
Get the latest robot pose in the fixed frame and reject stale transforms.
void publishFollowingFeedback(uint16_t state)
Publish feedback from a following action.
virtual bool getTrackingPose(geometry_msgs::msg::PoseStamped &pose, const std::string &frame_id, const geometry_msgs::msg::PoseStamped &robot_pose)
Get the tracking pose based on the current tracking mode.
bool isGoalReached(const geometry_msgs::msg::PoseStamped &robot_pose, const geometry_msgs::msg::PoseStamped &goal_pose)
Check if the goal has been reached.
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.
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.
geometry_msgs::msg::PoseStamped getPoseAtDistance(const geometry_msgs::msg::PoseStamped &robot_pose, const geometry_msgs::msg::PoseStamped &pose, double distance)
Get the pose at a distance in front of the input pose.
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.