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"
25 using namespace std::chrono_literals;
26 using rcl_interfaces::msg::ParameterType;
27 using std::placeholders::_1;
29 namespace opennav_following
32 FollowingServer::FollowingServer(
const rclcpp::NodeOptions & options)
33 : nav2::LifecycleNode(
"following_server",
"", options)
35 RCLCPP_INFO(get_logger(),
"Creating %s", get_name());
41 RCLCPP_INFO(get_logger(),
"Configuring %s", get_name());
43 param_handler_ = std::make_unique<ParameterHandler>(
45 params_ = param_handler_->getParams();
47 vel_publisher_ = std::make_unique<nav2_util::TwistPublisher>(node,
"cmd_vel");
48 tf2_buffer_ = nav2::create_transform_buffer(node);
51 odom_sub_ = std::make_unique<nav2_util::OdomSmoother>(node, params_->odom_duration,
55 following_action_server_ = node->create_action_server<FollowObject>(
58 nullptr,
nullptr, std::chrono::milliseconds(500),
65 std::make_unique<opennav_docking::Controller>(node, tf2_buffer_, params_->fixed_frame,
68 if (params_->use_collision_detection) {
71 "Collision detection is not supported in the following server. Please disable "
72 "the controller.use_collision_detection parameter.");
73 return nav2::CallbackReturn::FAILURE;
77 filter_ = std::make_unique<opennav_docking::PoseFilter>(params_->filter_coef,
78 params_->detection_timeout);
81 filtered_dynamic_pose_pub_ =
82 create_publisher<geometry_msgs::msg::PoseStamped>(
"filtered_dynamic_pose");
85 static_timer_initialized_ =
false;
86 static_object_start_time_ = rclcpp::Time(0);
88 return nav2::CallbackReturn::SUCCESS;
94 RCLCPP_INFO(get_logger(),
"Activating %s", get_name());
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();
105 return nav2::CallbackReturn::SUCCESS;
111 RCLCPP_INFO(get_logger(),
"Deactivating %s", get_name());
113 following_action_server_->deactivate();
114 vel_publisher_->on_deactivate();
115 filtered_dynamic_pose_pub_->on_deactivate();
116 param_handler_->deactivate();
118 tf2_listener_.reset();
123 return nav2::CallbackReturn::SUCCESS;
129 RCLCPP_INFO(get_logger(),
"Cleaning up %s", get_name());
131 following_action_server_.reset();
133 vel_publisher_.reset();
134 filtered_dynamic_pose_pub_.reset();
136 return nav2::CallbackReturn::SUCCESS;
142 RCLCPP_INFO(get_logger(),
"Shutting down %s", get_name());
143 return nav2::CallbackReturn::SUCCESS;
146 template<
typename ActionT>
148 typename std::shared_ptr<const typename ActionT::Goal> goal,
149 const typename nav2::SimpleActionServer<ActionT>::SharedPtr & action_server)
156 template<
typename ActionT>
158 typename nav2::SimpleActionServer<ActionT>::SharedPtr & action_server,
159 const std::string & name)
162 RCLCPP_WARN(get_logger(),
"Goal was cancelled. Cancelling %s action", name.c_str());
168 template<
typename ActionT>
170 typename nav2::SimpleActionServer<ActionT>::SharedPtr & action_server,
171 const std::string & name)
174 RCLCPP_WARN(get_logger(),
"Goal was preempted. Cancelling %s action", name.c_str());
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);
186 auto goal = following_action_server_->get_current_goal();
187 auto result = std::make_shared<FollowObject::Result>();
189 if (!following_action_server_ || !following_action_server_->is_server_active()) {
190 RCLCPP_DEBUG(get_logger(),
"Action server unavailable or inactive. Stopping.");
194 if (checkAndWarnIfCancelled<FollowObject>(following_action_server_,
"follow_object")) {
195 following_action_server_->terminate_all();
199 getPreemptedGoalIfRequested<FollowObject>(goal, following_action_server_);
201 static_timer_initialized_ =
false;
204 detected_dynamic_pose_.header.stamp = rclcpp::Time(0);
207 auto pose_topic = goal->pose_topic;
208 auto target_frame = goal->tracked_frame;
209 if (target_frame.empty()) {
210 if (pose_topic.empty()) {
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);
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>(
223 [
this](
const geometry_msgs::msg::PoseStamped::ConstSharedPtr & pose) {
224 detected_dynamic_pose_ = *pose;
227 param_handler_->getMutex().lock();
230 RCLCPP_INFO(get_logger(),
"Following frame: %s instead of pose", target_frame.c_str());
234 geometry_msgs::msg::PoseStamped object_pose;
235 rclcpp::Duration max_duration = goal->max_duration;
236 while (rclcpp::ok()) {
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();
252 if (!static_timer_initialized_) {
253 static_object_start_time_ = this->now();
254 static_timer_initialized_ =
true;
258 RCLCPP_INFO_THROTTLE(
259 get_logger(), *get_clock(), 1000,
260 "Reached object. Stopping until goal is moved again.");
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) {
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);
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();
289 if (++num_retries_ > params_->max_retries) {
290 RCLCPP_ERROR(get_logger(),
"Failed to follow, all retries have been used");
293 RCLCPP_WARN(get_logger(),
"Following failed, will retry: %s", e.what());
296 if (params_->search_by_rotating) {
297 RCLCPP_INFO(get_logger(),
"Rotating to find object again");
301 following_action_server_->terminate_all(result);
305 RCLCPP_INFO(get_logger(),
"Using last known heading to find object again");
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;
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();
341 geometry_msgs::msg::PoseStamped & object_pose,
const std::string & target_frame)
343 rclcpp::Rate loop_rate(params_->controller_frequency);
344 while (rclcpp::ok()) {
346 iteration_start_time_ = this->now();
351 if (checkAndWarnIfCancelled<FollowObject>(following_action_server_,
"follow_object") ||
352 checkAndWarnIfPreempted<FollowObject>(following_action_server_,
"follow_object"))
371 const double backward_projection = 0.25;
372 const double effective_distance = params_->desired_distance - backward_projection;
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());
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_))
392 RCLCPP_WARN(get_logger(),
"Failed to get current robot pose");
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)) {
402 vel_publisher_->publish(std::move(command));
410 geometry_msgs::msg::PoseStamped & object_pose,
const std::string & target_frame)
412 const double dt = 1.0 / params_->controller_frequency;
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_))
421 RCLCPP_WARN(get_logger(),
"Failed to get current robot pose");
424 double initial_yaw = tf2::getYaw(robot_pose.pose.orientation);
427 std::vector<double> angles = {initial_yaw + params_->search_angle,
428 initial_yaw - params_->search_angle};
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);
435 for (
const double & target_angle : angles) {
437 auto target_pose = object_pose;
438 target_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(target_angle);
441 while (rclcpp::ok()) {
443 iteration_start_time_ = this->now();
448 if (checkAndWarnIfCancelled<FollowObject>(following_action_server_,
"follow_object") ||
449 checkAndWarnIfPreempted<FollowObject>(following_action_server_,
"follow_object"))
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_))
460 RCLCPP_WARN(get_logger(),
"Failed to get current robot pose");
464 double angular_distance_to_heading = angles::shortest_angular_distance(
465 tf2::getYaw(robot_pose.pose.orientation), target_angle);
468 if (fabs(angular_distance_to_heading) < params_->angular_tolerance) {
481 geometry_msgs::msg::Twist current_vel;
482 current_vel.angular.z = odom_sub_->getRawTwist().angular.z;
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);
489 vel_publisher_->publish(std::move(command));
491 if (this->now() - start > timeout) {
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));
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);
523 geometry_msgs::msg::PoseStamped detected = detected_dynamic_pose_;
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) {
532 if (detected_dynamic_pose_.header.stamp != builtin_interfaces::msg::Time{}) {
533 detected = detected_dynamic_pose_;
538 if (detected.header.stamp == builtin_interfaces::msg::Time{}) {
539 RCLCPP_WARN(this->get_logger(),
"No detection received within timeout period");
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");
552 if (detected.header.frame_id != params_->fixed_frame) {
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");
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_))
574 RCLCPP_WARN(get_logger(),
"Failed to get current robot pose");
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);
584 auto pose_filtered = filter_->update(detected);
585 filtered_dynamic_pose_pub_->publish(pose_filtered);
587 pose = pose_filtered;
592 geometry_msgs::msg::PoseStamped & pose,
const std::string & frame_id)
596 auto transform = tf2_buffer_->lookupTransform(
597 params_->fixed_frame, frame_id, iteration_start_time_,
598 tf2::durationFromSec(params_->transform_tolerance));
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) {
610 "Failed to get transform for frame %s: %s", frame_id.c_str(), ex.what());
615 auto filtered_pose = filter_->update(pose);
616 filtered_dynamic_pose_pub_->publish(filtered_pose);
618 pose = filtered_pose;
623 geometry_msgs::msg::PoseStamped & pose,
const std::string & frame_id)
626 if (!frame_id.empty()) {
629 "Failed to get pose in target frame: " + frame_id);
641 const geometry_msgs::msg::PoseStamped & pose,
double distance)
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_))
649 RCLCPP_WARN(get_logger(),
"Failed to get current robot pose");
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);
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_))
670 RCLCPP_WARN(get_logger(),
"Failed to get current robot pose");
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;
683 #include "rclcpp_components/register_node_macro.hpp"
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.
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.
Abstract docking exception.
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.