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"
24 using namespace std::chrono_literals;
25 using rcl_interfaces::msg::ParameterType;
26 using std::placeholders::_1;
28 namespace opennav_following
31 FollowingServer::FollowingServer(
const rclcpp::NodeOptions & options)
32 : nav2_util::LifecycleNode(
"following_server",
"", options)
34 RCLCPP_INFO(get_logger(),
"Creating %s", get_name());
37 nav2_util::CallbackReturn
40 RCLCPP_INFO(get_logger(),
"Configuring %s", get_name());
42 param_handler_ = std::make_unique<ParameterHandler>(
44 params_ = param_handler_->getParams();
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());
50 odom_sub_ = std::make_unique<nav2_util::OdomSmoother>(node, params_->odom_duration,
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);
61 following_action_server_ = std::make_unique<FollowingActionServer>(
62 node,
"follow_object",
64 nullptr, std::chrono::milliseconds(500),
65 true, server_options);
70 std::make_unique<opennav_docking::Controller>(node, tf2_buffer_, params_->fixed_frame,
73 if (params_->use_collision_detection) {
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;
82 filter_ = std::make_unique<opennav_docking::PoseFilter>(params_->filter_coef,
83 params_->detection_timeout);
86 filtered_dynamic_pose_pub_ =
87 create_publisher<geometry_msgs::msg::PoseStamped>(
"filtered_dynamic_pose", 1);
90 static_timer_initialized_ =
false;
91 static_object_start_time_ = rclcpp::Time(0);
93 return nav2_util::CallbackReturn::SUCCESS;
96 nav2_util::CallbackReturn
99 RCLCPP_INFO(get_logger(),
"Activating %s", get_name());
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();
110 return nav2_util::CallbackReturn::SUCCESS;
113 nav2_util::CallbackReturn
116 RCLCPP_INFO(get_logger(),
"Deactivating %s", get_name());
118 following_action_server_->deactivate();
119 vel_publisher_->on_deactivate();
120 filtered_dynamic_pose_pub_->on_deactivate();
121 param_handler_->deactivate();
123 tf2_listener_.reset();
128 return nav2_util::CallbackReturn::SUCCESS;
131 nav2_util::CallbackReturn
134 RCLCPP_INFO(get_logger(),
"Cleaning up %s", get_name());
136 following_action_server_.reset();
138 vel_publisher_.reset();
139 filtered_dynamic_pose_pub_.reset();
141 return nav2_util::CallbackReturn::SUCCESS;
144 nav2_util::CallbackReturn
147 RCLCPP_INFO(get_logger(),
"Shutting down %s", get_name());
148 return nav2_util::CallbackReturn::SUCCESS;
151 template<
typename ActionT>
153 typename std::shared_ptr<const typename ActionT::Goal> goal,
156 if (action_server->is_preempt_requested()) {
157 goal = action_server->accept_pending_goal();
161 template<
typename ActionT>
164 const std::string & name)
166 if (action_server->is_cancel_requested()) {
167 RCLCPP_WARN(get_logger(),
"Goal was cancelled. Cancelling %s action", name.c_str());
173 template<
typename ActionT>
176 const std::string & name)
178 if (action_server->is_preempt_requested()) {
179 RCLCPP_WARN(get_logger(),
"Goal was preempted. Cancelling %s action", name.c_str());
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);
191 auto goal = following_action_server_->get_current_goal();
192 auto result = std::make_shared<FollowObject::Result>();
194 if (!following_action_server_ || !following_action_server_->is_server_active()) {
195 RCLCPP_DEBUG(get_logger(),
"Action server unavailable or inactive. Stopping.");
199 if (checkAndWarnIfCancelled<FollowObject>(following_action_server_,
"follow_object")) {
200 following_action_server_->terminate_all();
204 getPreemptedGoalIfRequested<FollowObject>(goal, following_action_server_);
206 static_timer_initialized_ =
false;
209 detected_dynamic_pose_.header.stamp = rclcpp::Time(0);
212 auto pose_topic = goal->pose_topic;
213 auto target_frame = goal->tracked_frame;
214 if (target_frame.empty()) {
215 if (pose_topic.empty()) {
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);
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;
231 param_handler_->getMutex().lock();
234 RCLCPP_INFO(get_logger(),
"Following frame: %s instead of pose", target_frame.c_str());
238 geometry_msgs::msg::PoseStamped object_pose;
239 rclcpp::Duration max_duration = goal->max_duration;
240 while (rclcpp::ok()) {
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();
256 if (!static_timer_initialized_) {
257 static_object_start_time_ = this->now();
258 static_timer_initialized_ =
true;
262 RCLCPP_INFO_THROTTLE(
263 get_logger(), *get_clock(), 1000,
264 "Reached object. Stopping until goal is moved again.");
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) {
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);
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();
293 if (++num_retries_ > params_->max_retries) {
294 RCLCPP_ERROR(get_logger(),
"Failed to follow, all retries have been used");
297 RCLCPP_WARN(get_logger(),
"Following failed, will retry: %s", e.what());
300 if (params_->search_by_rotating) {
301 RCLCPP_INFO(get_logger(),
"Rotating to find object again");
305 following_action_server_->terminate_all(result);
309 RCLCPP_INFO(get_logger(),
"Using last known heading to find object again");
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;
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();
345 geometry_msgs::msg::PoseStamped & object_pose,
const std::string & target_frame)
347 rclcpp::Rate loop_rate(params_->controller_frequency);
348 while (rclcpp::ok()) {
350 iteration_start_time_ = this->now();
355 if (checkAndWarnIfCancelled<FollowObject>(following_action_server_,
"follow_object") ||
356 checkAndWarnIfPreempted<FollowObject>(following_action_server_,
"follow_object"))
372 const double backward_projection = 0.25;
373 const double effective_distance = params_->desired_distance - backward_projection;
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());
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)) {
392 vel_publisher_->publish(std::move(command));
400 geometry_msgs::msg::PoseStamped & object_pose,
const std::string & target_frame)
402 const double dt = 1.0 / params_->controller_frequency;
406 const std::string reference_frame =
407 object_pose.header.frame_id.empty() ? params_->fixed_frame : object_pose.header.frame_id;
410 iteration_start_time_ = this->now();
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_))
419 RCLCPP_WARN(get_logger(),
"Failed to get current robot pose");
422 double initial_yaw = tf2::getYaw(robot_pose.pose.orientation);
425 std::vector<double> angles = {initial_yaw + params_->search_angle,
426 initial_yaw - params_->search_angle};
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);
433 for (
const double & target_angle : angles) {
435 auto target_pose = object_pose;
436 target_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(target_angle);
439 while (rclcpp::ok()) {
441 iteration_start_time_ = this->now();
446 if (checkAndWarnIfCancelled<FollowObject>(following_action_server_,
"follow_object") ||
447 checkAndWarnIfPreempted<FollowObject>(following_action_server_,
"follow_object"))
453 if (!nav2_util::getCurrentPose(
454 robot_pose, *tf2_buffer_, reference_frame, params_->base_frame,
455 params_->transform_tolerance,
456 iteration_start_time_))
458 RCLCPP_WARN(get_logger(),
"Failed to get current robot pose");
462 double angular_distance_to_heading = angles::shortest_angular_distance(
463 tf2::getYaw(robot_pose.pose.orientation), target_angle);
466 if (fabs(angular_distance_to_heading) < params_->angular_tolerance) {
479 geometry_msgs::msg::Twist current_vel;
480 current_vel.angular.z = odom_sub_->getTwist().angular.z;
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);
487 vel_publisher_->publish(std::move(command));
489 if (this->now() - start > timeout) {
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));
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);
521 geometry_msgs::msg::PoseStamped detected = detected_dynamic_pose_;
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) {
530 if (detected_dynamic_pose_.header.stamp != builtin_interfaces::msg::Time{}) {
531 detected = detected_dynamic_pose_;
536 if (detected.header.stamp == builtin_interfaces::msg::Time{}) {
537 RCLCPP_WARN(this->get_logger(),
"No detection received within timeout period");
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");
550 if (detected.header.frame_id != params_->fixed_frame) {
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");
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_))
569 RCLCPP_WARN(get_logger(),
"Failed to get current robot pose");
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);
579 auto pose_filtered = filter_->update(detected);
580 filtered_dynamic_pose_pub_->publish(pose_filtered);
582 pose = pose_filtered;
587 geometry_msgs::msg::PoseStamped & pose,
const std::string & frame_id)
591 auto transform = tf2_buffer_->lookupTransform(
592 params_->fixed_frame, frame_id, iteration_start_time_,
593 tf2::durationFromSec(params_->transform_tolerance));
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) {
605 "Failed to get transform for frame %s: %s", frame_id.c_str(), ex.what());
610 auto filtered_pose = filter_->update(pose);
611 filtered_dynamic_pose_pub_->publish(filtered_pose);
613 pose = filtered_pose;
618 geometry_msgs::msg::PoseStamped & pose,
const std::string & frame_id)
621 if (!frame_id.empty()) {
624 "Failed to get pose in target frame: " + frame_id);
636 const geometry_msgs::msg::PoseStamped & pose,
double distance)
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_))
644 RCLCPP_WARN(get_logger(),
"Failed to get current robot pose");
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);
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_))
665 RCLCPP_WARN(get_logger(),
"Failed to get current robot pose");
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;
678 #include "rclcpp_components/register_node_macro.hpp"
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.
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.
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.