23 #include "angles/angles.h"
24 #include "nav2_regulated_pure_pursuit_controller/regulated_pure_pursuit_controller.hpp"
25 #include "nav2_core/controller_exceptions.hpp"
26 #include "nav2_util/node_utils.hpp"
27 #include "nav2_util/geometry_utils.hpp"
28 #include "nav2_costmap_2d/costmap_filters/filter_values.hpp"
36 namespace nav2_regulated_pure_pursuit_controller
39 void RegulatedPurePursuitController::configure(
40 const rclcpp_lifecycle::LifecycleNode::WeakPtr & parent,
41 std::string name, std::shared_ptr<tf2_ros::Buffer> tf,
42 std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
44 auto node = parent.lock();
50 costmap_ros_ = costmap_ros;
51 costmap_ = costmap_ros_->getCostmap();
54 logger_ = node->get_logger();
58 param_handler_ = std::make_unique<ParameterHandler>(
59 node, plugin_name_, logger_, costmap_->getSizeInMetersX());
60 params_ = param_handler_->getParams();
63 path_handler_ = std::make_unique<PathHandler>(
64 tf2::durationFromSec(params_->transform_tolerance), tf_, costmap_ros_);
67 collision_checker_ = std::make_unique<CollisionChecker>(node, costmap_ros_, params_);
69 double control_frequency = 20.0;
70 goal_dist_tol_ = 0.25;
72 node->get_parameter(
"controller_frequency", control_frequency);
73 control_duration_ = 1.0 / control_frequency;
75 global_path_pub_ = node->create_publisher<nav_msgs::msg::Path>(
"received_global_plan", 1);
76 carrot_pub_ = node->create_publisher<geometry_msgs::msg::PointStamped>(
"lookahead_point", 1);
77 curvature_carrot_pub_ = node->create_publisher<geometry_msgs::msg::PointStamped>(
78 "curvature_lookahead_point", 1);
79 is_rotating_to_heading_pub_ = node->create_publisher<std_msgs::msg::Bool>(
80 "is_rotating_to_heading", 1);
83 void RegulatedPurePursuitController::cleanup()
87 "Cleaning up controller: %s of type"
88 " regulated_pure_pursuit_controller::RegulatedPurePursuitController",
89 plugin_name_.c_str());
90 global_path_pub_.reset();
92 curvature_carrot_pub_.reset();
93 is_rotating_to_heading_pub_.reset();
96 void RegulatedPurePursuitController::activate()
100 "Activating controller: %s of type "
101 "regulated_pure_pursuit_controller::RegulatedPurePursuitController",
102 plugin_name_.c_str());
103 global_path_pub_->on_activate();
104 carrot_pub_->on_activate();
105 curvature_carrot_pub_->on_activate();
106 is_rotating_to_heading_pub_->on_activate();
109 void RegulatedPurePursuitController::deactivate()
113 "Deactivating controller: %s of type "
114 "regulated_pure_pursuit_controller::RegulatedPurePursuitController",
115 plugin_name_.c_str());
116 global_path_pub_->on_deactivate();
117 carrot_pub_->on_deactivate();
118 curvature_carrot_pub_->on_deactivate();
119 is_rotating_to_heading_pub_->on_deactivate();
122 std::unique_ptr<geometry_msgs::msg::PointStamped> RegulatedPurePursuitController::createCarrotMsg(
123 const geometry_msgs::msg::PoseStamped & carrot_pose)
125 auto carrot_msg = std::make_unique<geometry_msgs::msg::PointStamped>();
126 carrot_msg->header = carrot_pose.header;
127 carrot_msg->point.x = carrot_pose.pose.position.x;
128 carrot_msg->point.y = carrot_pose.pose.position.y;
129 carrot_msg->point.z = 0.01;
133 double RegulatedPurePursuitController::getLookAheadDistance(
134 const geometry_msgs::msg::Twist & speed)
138 double lookahead_dist = params_->lookahead_dist;
139 if (params_->use_velocity_scaled_lookahead_dist) {
140 lookahead_dist = fabs(speed.linear.x) * params_->lookahead_time;
141 lookahead_dist = std::clamp(
142 lookahead_dist, params_->min_lookahead_dist, params_->max_lookahead_dist);
145 return lookahead_dist;
148 double calculateCurvature(geometry_msgs::msg::Point lookahead_point)
152 const double carrot_dist2 =
153 (lookahead_point.x * lookahead_point.x) +
154 (lookahead_point.y * lookahead_point.y);
157 if (carrot_dist2 > 0.001) {
158 return 2.0 * lookahead_point.y / carrot_dist2;
164 geometry_msgs::msg::TwistStamped RegulatedPurePursuitController::computeVelocityCommands(
165 const geometry_msgs::msg::PoseStamped & pose,
166 const geometry_msgs::msg::Twist & speed,
169 std::lock_guard<std::mutex> lock_reinit(param_handler_->getMutex());
172 std::unique_lock<nav2_costmap_2d::Costmap2D::mutex_t> lock(*(costmap->getMutex()));
175 geometry_msgs::msg::Pose pose_tolerance;
176 geometry_msgs::msg::Twist vel_tolerance;
177 if (!goal_checker->
getTolerances(pose_tolerance, vel_tolerance)) {
178 RCLCPP_WARN(logger_,
"Unable to retrieve goal checker's tolerances!");
180 goal_dist_tol_ = pose_tolerance.position.x;
184 auto transformed_plan = path_handler_->transformGlobalPlan(
185 pose, params_->max_robot_pose_search_dist, params_->interpolate_curvature_after_goal);
186 global_path_pub_->publish(transformed_plan);
189 double lookahead_dist = getLookAheadDistance(speed);
190 double curv_lookahead_dist = params_->curvature_lookahead_dist;
193 if (params_->allow_reversing) {
195 const double dist_to_cusp = findVelocitySignChange(transformed_plan);
198 if (dist_to_cusp < lookahead_dist) {
199 lookahead_dist = dist_to_cusp;
201 if (dist_to_cusp < curv_lookahead_dist) {
202 curv_lookahead_dist = dist_to_cusp;
207 auto carrot_pose = getLookAheadPoint(lookahead_dist, transformed_plan);
208 auto rotate_to_path_carrot_pose = carrot_pose;
209 carrot_pub_->publish(createCarrotMsg(carrot_pose));
211 double linear_vel, angular_vel;
213 double lookahead_curvature = calculateCurvature(carrot_pose.pose.position);
215 double regulation_curvature = lookahead_curvature;
216 if (params_->use_fixed_curvature_lookahead) {
217 auto curvature_lookahead_pose = getLookAheadPoint(
219 transformed_plan, params_->interpolate_curvature_after_goal);
220 rotate_to_path_carrot_pose = curvature_lookahead_pose;
221 regulation_curvature = calculateCurvature(curvature_lookahead_pose.pose.position);
222 curvature_carrot_pub_->publish(createCarrotMsg(curvature_lookahead_pose));
226 double x_vel_sign = 1.0;
227 if (params_->allow_reversing) {
228 x_vel_sign = carrot_pose.pose.position.x >= 0.0 ? 1.0 : -1.0;
231 linear_vel = params_->desired_linear_vel;
238 double angle_to_heading;
239 if (shouldRotateToGoalHeading(carrot_pose)) {
240 is_rotating_to_heading_ =
true;
241 double angle_to_goal = tf2::getYaw(transformed_plan.poses.back().pose.orientation);
242 rotateToHeading(linear_vel, angular_vel, angle_to_goal, speed);
243 }
else if (shouldRotateToPath(rotate_to_path_carrot_pose, angle_to_heading, x_vel_sign)) {
244 is_rotating_to_heading_ =
true;
245 rotateToHeading(linear_vel, angular_vel, angle_to_heading, speed);
247 is_rotating_to_heading_ =
false;
249 regulation_curvature, speed,
250 collision_checker_->costAtPose(pose.pose.position.x, pose.pose.position.y), transformed_plan,
251 linear_vel, x_vel_sign);
254 const double & dt = control_duration_;
255 linear_vel = speed.linear.x - x_vel_sign * dt * params_->cancel_deceleration;
257 if (x_vel_sign > 0) {
258 if (linear_vel <= 0) {
260 finished_cancelling_ =
true;
263 if (linear_vel >= 0) {
265 finished_cancelling_ =
true;
271 angular_vel = linear_vel * regulation_curvature;
275 const double & carrot_dist = hypot(carrot_pose.pose.position.x, carrot_pose.pose.position.y);
276 if (params_->use_collision_detection &&
277 collision_checker_->isCollisionImminent(pose, linear_vel, angular_vel, carrot_dist))
283 std_msgs::msg::Bool is_rotating_to_heading_msg;
284 is_rotating_to_heading_msg.data = is_rotating_to_heading_;
285 is_rotating_to_heading_pub_->publish(is_rotating_to_heading_msg);
288 geometry_msgs::msg::TwistStamped cmd_vel;
289 cmd_vel.header = pose.header;
290 cmd_vel.twist.linear.x = linear_vel;
291 cmd_vel.twist.angular.z = angular_vel;
295 bool RegulatedPurePursuitController::cancel()
298 if (!params_->use_cancel_deceleration) {
302 return finished_cancelling_;
305 bool RegulatedPurePursuitController::shouldRotateToPath(
306 const geometry_msgs::msg::PoseStamped & carrot_pose,
double & angle_to_path,
310 angle_to_path = atan2(carrot_pose.pose.position.y, carrot_pose.pose.position.x);
312 if (x_vel_sign < 0.0) {
313 angle_to_path = angles::normalize_angle(angle_to_path + M_PI);
315 return params_->use_rotate_to_heading &&
316 fabs(angle_to_path) > params_->rotate_to_heading_min_angle;
319 bool RegulatedPurePursuitController::shouldRotateToGoalHeading(
320 const geometry_msgs::msg::PoseStamped & carrot_pose)
323 if (!params_->use_rotate_to_heading) {
327 double dist_to_goal = std::hypot(
328 carrot_pose.pose.position.x, carrot_pose.pose.position.y);
330 if (params_->stateful) {
331 if (!has_reached_xy_tolerance_ && dist_to_goal < goal_dist_tol_) {
332 has_reached_xy_tolerance_ =
true;
334 return has_reached_xy_tolerance_;
337 return dist_to_goal < goal_dist_tol_;
340 void RegulatedPurePursuitController::rotateToHeading(
341 double & linear_vel,
double & angular_vel,
342 const double & angle_to_path,
const geometry_msgs::msg::Twist & curr_speed)
346 const double sign = angle_to_path > 0.0 ? 1.0 : -1.0;
347 angular_vel = sign * params_->rotate_to_heading_angular_vel;
349 const double & dt = control_duration_;
350 const double min_feasible_angular_speed = curr_speed.angular.z - params_->max_angular_accel * dt;
351 const double max_feasible_angular_speed = curr_speed.angular.z + params_->max_angular_accel * dt;
352 angular_vel = std::clamp(angular_vel, min_feasible_angular_speed, max_feasible_angular_speed);
355 geometry_msgs::msg::Point RegulatedPurePursuitController::circleSegmentIntersection(
356 const geometry_msgs::msg::Point & p1,
357 const geometry_msgs::msg::Point & p2,
375 double dr2 = dx * dx + dy * dy;
376 double D = x1 * y2 - x2 * y1;
379 double d1 = x1 * x1 + y1 * y1;
380 double d2 = x2 * x2 + y2 * y2;
383 geometry_msgs::msg::Point p;
384 double sqrt_term = std::sqrt(r * r * dr2 - D * D);
385 p.x = (D * dy + std::copysign(1.0, dd) * dx * sqrt_term) / dr2;
386 p.y = (-D * dx + std::copysign(1.0, dd) * dy * sqrt_term) / dr2;
390 geometry_msgs::msg::PoseStamped RegulatedPurePursuitController::getLookAheadPoint(
391 const double & lookahead_dist,
392 const nav_msgs::msg::Path & transformed_plan,
393 bool interpolate_after_goal)
396 auto goal_pose_it = std::find_if(
397 transformed_plan.poses.begin(), transformed_plan.poses.end(), [&](
const auto & ps) {
398 return hypot(ps.pose.position.x, ps.pose.position.y) >= lookahead_dist;
402 if (goal_pose_it == transformed_plan.poses.end()) {
403 if (interpolate_after_goal) {
404 auto last_pose_it = std::prev(transformed_plan.poses.end());
405 auto prev_last_pose_it = std::prev(last_pose_it);
407 double end_path_orientation = atan2(
408 last_pose_it->pose.position.y - prev_last_pose_it->pose.position.y,
409 last_pose_it->pose.position.x - prev_last_pose_it->pose.position.x);
413 auto projected_position = last_pose_it->pose.position;
414 projected_position.x += cos(end_path_orientation) * lookahead_dist;
415 projected_position.y += sin(end_path_orientation) * lookahead_dist;
419 const auto interpolated_position = circleSegmentIntersection(
420 last_pose_it->pose.position, projected_position, lookahead_dist);
422 geometry_msgs::msg::PoseStamped interpolated_pose;
423 interpolated_pose.header = last_pose_it->header;
424 interpolated_pose.pose.position = interpolated_position;
425 return interpolated_pose;
427 goal_pose_it = std::prev(transformed_plan.poses.end());
429 }
else if (goal_pose_it != transformed_plan.poses.begin()) {
435 auto prev_pose_it = std::prev(goal_pose_it);
436 auto point = circleSegmentIntersection(
437 prev_pose_it->pose.position,
438 goal_pose_it->pose.position, lookahead_dist);
439 geometry_msgs::msg::PoseStamped pose;
440 pose.header.frame_id = prev_pose_it->header.frame_id;
441 pose.header.stamp = goal_pose_it->header.stamp;
442 pose.pose.position = point;
446 return *goal_pose_it;
449 void RegulatedPurePursuitController::applyConstraints(
450 const double & curvature,
const geometry_msgs::msg::Twist & ,
451 const double & pose_cost,
const nav_msgs::msg::Path & path,
double & linear_vel,
double & sign)
453 double curvature_vel = linear_vel, cost_vel = linear_vel;
456 if (params_->use_regulated_linear_velocity_scaling) {
457 curvature_vel = heuristics::curvatureConstraint(
458 linear_vel, curvature, params_->regulated_linear_scaling_min_radius);
462 if (params_->use_cost_regulated_linear_velocity_scaling) {
463 cost_vel = heuristics::costConstraint(linear_vel, pose_cost, costmap_ros_, params_);
467 linear_vel = std::min(cost_vel, curvature_vel);
468 linear_vel = std::max(linear_vel, params_->regulated_linear_scaling_min_speed);
471 linear_vel = heuristics::approachVelocityConstraint(
472 linear_vel, path, params_->min_approach_linear_velocity,
473 params_->approach_velocity_scaling_dist);
476 linear_vel = std::clamp(fabs(linear_vel), 0.0, params_->desired_linear_vel);
477 linear_vel = sign * linear_vel;
480 void RegulatedPurePursuitController::setPlan(
const nav_msgs::msg::Path & path)
482 has_reached_xy_tolerance_ =
false;
483 path_handler_->setPlan(path);
486 void RegulatedPurePursuitController::setSpeedLimit(
487 const double & speed_limit,
488 const bool & percentage)
490 std::lock_guard<std::mutex> lock_reinit(param_handler_->getMutex());
492 if (speed_limit == nav2_costmap_2d::NO_SPEED_LIMIT) {
494 params_->desired_linear_vel = params_->base_desired_linear_vel;
498 params_->desired_linear_vel = params_->base_desired_linear_vel * speed_limit / 100.0;
501 params_->desired_linear_vel = speed_limit;
506 void RegulatedPurePursuitController::reset()
509 finished_cancelling_ =
false;
510 has_reached_xy_tolerance_ =
false;
513 double RegulatedPurePursuitController::findVelocitySignChange(
514 const nav_msgs::msg::Path & transformed_plan)
517 for (
unsigned int pose_id = 1; pose_id < transformed_plan.poses.size() - 1; ++pose_id) {
519 double oa_x = transformed_plan.poses[pose_id].pose.position.x -
520 transformed_plan.poses[pose_id - 1].pose.position.x;
521 double oa_y = transformed_plan.poses[pose_id].pose.position.y -
522 transformed_plan.poses[pose_id - 1].pose.position.y;
523 double ab_x = transformed_plan.poses[pose_id + 1].pose.position.x -
524 transformed_plan.poses[pose_id].pose.position.x;
525 double ab_y = transformed_plan.poses[pose_id + 1].pose.position.y -
526 transformed_plan.poses[pose_id].pose.position.y;
531 const double dot_prod = (oa_x * ab_x) + (oa_y * ab_y);
532 if (dot_prod < 0.0) {
536 transformed_plan.poses[pose_id].pose.position.x,
537 transformed_plan.poses[pose_id].pose.position.y);
541 (hypot(oa_x, oa_y) == 0.0 &&
542 transformed_plan.poses[pose_id - 1].pose.orientation !=
543 transformed_plan.poses[pose_id].pose.orientation)
545 (hypot(ab_x, ab_y) == 0.0 &&
546 transformed_plan.poses[pose_id].pose.orientation !=
547 transformed_plan.poses[pose_id + 1].pose.orientation))
552 transformed_plan.poses[pose_id].pose.position.x,
553 transformed_plan.poses[pose_id].pose.position.y);
557 return std::numeric_limits<double>::max();
562 PLUGINLIB_EXPORT_CLASS(
controller interface that acts as a virtual base class for all controller plugins
Function-object for checking whether a goal has been reached.
virtual bool getTolerances(geometry_msgs::msg::Pose &pose_tolerance, geometry_msgs::msg::Twist &vel_tolerance)=0
Get the maximum possible tolerances used for goal checking in the major types. Any field without a va...
A 2D costmap provides a mapping between points in the world and their associated "costs".
Regulated pure pursuit controller plugin.