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_ros_common/node_utils.hpp"
27 #include "nav2_util/geometry_utils.hpp"
28 #include "nav2_util/controller_utils.hpp"
29 #include "nav2_costmap_2d/costmap_filters/filter_values.hpp"
37 namespace nav2_regulated_pure_pursuit_controller
40 void RegulatedPurePursuitController::configure(
41 const nav2::LifecycleNode::WeakPtr & parent,
42 std::string name, std::shared_ptr<tf2_ros::Buffer> tf,
43 std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
45 auto node = parent.lock();
51 costmap_ros_ = costmap_ros;
52 costmap_ = costmap_ros_->getCostmap();
55 logger_ = node->get_logger();
59 param_handler_ = std::make_unique<ParameterHandler>(
60 node, plugin_name_, logger_, costmap_->getSizeInMetersX());
61 params_ = param_handler_->getParams();
64 path_handler_ = std::make_unique<PathHandler>(
65 params_->transform_tolerance, tf_, costmap_ros_);
68 collision_checker_ = std::make_unique<CollisionChecker>(node, costmap_ros_, params_);
70 double control_frequency = 20.0;
71 goal_dist_tol_ = 0.25;
73 node->get_parameter(
"controller_frequency", control_frequency);
74 control_duration_ = 1.0 / control_frequency;
76 global_path_pub_ = node->create_publisher<nav_msgs::msg::Path>(
"received_global_plan");
77 carrot_pub_ = node->create_publisher<geometry_msgs::msg::PointStamped>(
"lookahead_point");
78 curvature_carrot_pub_ = node->create_publisher<geometry_msgs::msg::PointStamped>(
79 "curvature_lookahead_point");
80 is_rotating_to_heading_pub_ = node->create_publisher<std_msgs::msg::Bool>(
81 "is_rotating_to_heading");
84 void RegulatedPurePursuitController::cleanup()
88 "Cleaning up controller: %s of type"
89 " regulated_pure_pursuit_controller::RegulatedPurePursuitController",
90 plugin_name_.c_str());
91 global_path_pub_.reset();
93 curvature_carrot_pub_.reset();
94 is_rotating_to_heading_pub_.reset();
97 void RegulatedPurePursuitController::activate()
101 "Activating controller: %s of type "
102 "regulated_pure_pursuit_controller::RegulatedPurePursuitController",
103 plugin_name_.c_str());
104 global_path_pub_->on_activate();
105 carrot_pub_->on_activate();
106 curvature_carrot_pub_->on_activate();
107 is_rotating_to_heading_pub_->on_activate();
108 param_handler_->activate();
111 void RegulatedPurePursuitController::deactivate()
115 "Deactivating controller: %s of type "
116 "regulated_pure_pursuit_controller::RegulatedPurePursuitController",
117 plugin_name_.c_str());
118 global_path_pub_->on_deactivate();
119 carrot_pub_->on_deactivate();
120 curvature_carrot_pub_->on_deactivate();
121 is_rotating_to_heading_pub_->on_deactivate();
122 param_handler_->deactivate();
125 std::unique_ptr<geometry_msgs::msg::PointStamped> RegulatedPurePursuitController::createCarrotMsg(
126 const geometry_msgs::msg::PoseStamped & carrot_pose)
128 auto carrot_msg = std::make_unique<geometry_msgs::msg::PointStamped>();
129 carrot_msg->header = carrot_pose.header;
130 carrot_msg->point.x = carrot_pose.pose.position.x;
131 carrot_msg->point.y = carrot_pose.pose.position.y;
132 carrot_msg->point.z = 0.01;
136 double RegulatedPurePursuitController::getLookAheadDistance(
137 const geometry_msgs::msg::Twist & speed)
141 double lookahead_dist = params_->lookahead_dist;
142 if (params_->use_velocity_scaled_lookahead_dist) {
143 lookahead_dist = fabs(speed.linear.x) * params_->lookahead_time;
144 lookahead_dist = std::clamp(
145 lookahead_dist, params_->min_lookahead_dist, params_->max_lookahead_dist);
148 return lookahead_dist;
151 double calculateCurvature(geometry_msgs::msg::Point lookahead_point)
155 const double carrot_dist2 =
156 (lookahead_point.x * lookahead_point.x) +
157 (lookahead_point.y * lookahead_point.y);
160 if (carrot_dist2 > 0.001) {
161 return 2.0 * lookahead_point.y / carrot_dist2;
167 geometry_msgs::msg::TwistStamped RegulatedPurePursuitController::computeVelocityCommands(
168 const geometry_msgs::msg::PoseStamped & pose,
169 const geometry_msgs::msg::Twist & speed,
172 std::lock_guard<std::mutex> lock_reinit(param_handler_->getMutex());
175 std::unique_lock<nav2_costmap_2d::Costmap2D::mutex_t> lock(*(costmap->getMutex()));
178 geometry_msgs::msg::Pose pose_tolerance;
179 geometry_msgs::msg::Twist vel_tolerance;
180 if (!goal_checker->
getTolerances(pose_tolerance, vel_tolerance)) {
181 RCLCPP_WARN(logger_,
"Unable to retrieve goal checker's tolerances!");
183 goal_dist_tol_ = pose_tolerance.position.x;
187 auto transformed_plan = path_handler_->transformGlobalPlan(
188 pose, params_->max_robot_pose_search_dist, params_->interpolate_curvature_after_goal);
189 global_path_pub_->publish(transformed_plan);
192 double lookahead_dist = getLookAheadDistance(speed);
193 double curv_lookahead_dist = params_->curvature_lookahead_dist;
196 if (params_->allow_reversing) {
198 const double dist_to_cusp = findVelocitySignChange(transformed_plan);
201 if (dist_to_cusp < lookahead_dist) {
202 lookahead_dist = dist_to_cusp;
204 if (dist_to_cusp < curv_lookahead_dist) {
205 curv_lookahead_dist = dist_to_cusp;
210 auto carrot_pose = nav2_util::getLookAheadPoint(lookahead_dist, transformed_plan);
211 auto rotate_to_path_carrot_pose = carrot_pose;
212 carrot_pub_->publish(createCarrotMsg(carrot_pose));
214 double linear_vel, angular_vel;
216 double lookahead_curvature = calculateCurvature(carrot_pose.pose.position);
218 double regulation_curvature = lookahead_curvature;
219 if (params_->use_fixed_curvature_lookahead) {
220 auto curvature_lookahead_pose = nav2_util::getLookAheadPoint(
222 transformed_plan, params_->interpolate_curvature_after_goal);
223 rotate_to_path_carrot_pose = curvature_lookahead_pose;
224 regulation_curvature = calculateCurvature(curvature_lookahead_pose.pose.position);
225 curvature_carrot_pub_->publish(createCarrotMsg(curvature_lookahead_pose));
229 double x_vel_sign = 1.0;
230 if (params_->allow_reversing) {
231 x_vel_sign = carrot_pose.pose.position.x >= 0.0 ? 1.0 : -1.0;
234 linear_vel = params_->desired_linear_vel;
241 double angle_to_heading;
242 if (shouldRotateToGoalHeading(carrot_pose)) {
243 is_rotating_to_heading_ =
true;
244 double angle_to_goal = tf2::getYaw(transformed_plan.poses.back().pose.orientation);
245 rotateToHeading(linear_vel, angular_vel, angle_to_goal, speed);
246 }
else if (shouldRotateToPath(rotate_to_path_carrot_pose, angle_to_heading, x_vel_sign)) {
247 is_rotating_to_heading_ =
true;
248 rotateToHeading(linear_vel, angular_vel, angle_to_heading, speed);
250 is_rotating_to_heading_ =
false;
252 regulation_curvature, speed,
253 collision_checker_->costAtPose(pose.pose.position.x, pose.pose.position.y), transformed_plan,
254 linear_vel, x_vel_sign);
257 const double & dt = control_duration_;
258 linear_vel = speed.linear.x - x_vel_sign * dt * params_->cancel_deceleration;
260 if (x_vel_sign > 0) {
261 if (linear_vel <= 0) {
263 finished_cancelling_ =
true;
266 if (linear_vel >= 0) {
268 finished_cancelling_ =
true;
274 angular_vel = linear_vel * regulation_curvature;
278 const double & carrot_dist = hypot(carrot_pose.pose.position.x, carrot_pose.pose.position.y);
279 if (params_->use_collision_detection &&
280 collision_checker_->isCollisionImminent(pose, linear_vel, angular_vel, carrot_dist))
286 std_msgs::msg::Bool is_rotating_to_heading_msg;
287 is_rotating_to_heading_msg.data = is_rotating_to_heading_;
288 is_rotating_to_heading_pub_->publish(is_rotating_to_heading_msg);
291 geometry_msgs::msg::TwistStamped cmd_vel;
292 cmd_vel.header = pose.header;
293 cmd_vel.twist.linear.x = linear_vel;
294 cmd_vel.twist.angular.z = angular_vel;
298 bool RegulatedPurePursuitController::cancel()
301 if (!params_->use_cancel_deceleration) {
305 return finished_cancelling_;
308 bool RegulatedPurePursuitController::shouldRotateToPath(
309 const geometry_msgs::msg::PoseStamped & carrot_pose,
double & angle_to_path,
313 angle_to_path = atan2(carrot_pose.pose.position.y, carrot_pose.pose.position.x);
315 if (x_vel_sign < 0.0) {
316 angle_to_path = angles::normalize_angle(angle_to_path + M_PI);
318 return params_->use_rotate_to_heading &&
319 fabs(angle_to_path) > params_->rotate_to_heading_min_angle;
322 bool RegulatedPurePursuitController::shouldRotateToGoalHeading(
323 const geometry_msgs::msg::PoseStamped & carrot_pose)
326 if (!params_->use_rotate_to_heading) {
330 double dist_to_goal = std::hypot(
331 carrot_pose.pose.position.x, carrot_pose.pose.position.y);
333 if (params_->stateful) {
334 if (!has_reached_xy_tolerance_ && dist_to_goal < goal_dist_tol_) {
335 has_reached_xy_tolerance_ =
true;
337 return has_reached_xy_tolerance_;
340 return dist_to_goal < goal_dist_tol_;
343 void RegulatedPurePursuitController::rotateToHeading(
344 double & linear_vel,
double & angular_vel,
345 const double & angle_to_path,
const geometry_msgs::msg::Twist & curr_speed)
349 const double sign = angle_to_path > 0.0 ? 1.0 : -1.0;
350 angular_vel = sign * params_->rotate_to_heading_angular_vel;
352 const double & dt = control_duration_;
353 const double min_feasible_angular_speed = curr_speed.angular.z - params_->max_angular_accel * dt;
354 const double max_feasible_angular_speed = curr_speed.angular.z + params_->max_angular_accel * dt;
355 angular_vel = std::clamp(angular_vel, min_feasible_angular_speed, max_feasible_angular_speed);
358 double max_vel_to_stop = std::sqrt(2 * params_->max_angular_accel * fabs(angle_to_path));
359 if (fabs(angular_vel) > max_vel_to_stop) {
360 angular_vel = sign * max_vel_to_stop;
364 void RegulatedPurePursuitController::applyConstraints(
365 const double & curvature,
const geometry_msgs::msg::Twist & ,
366 const double & pose_cost,
const nav_msgs::msg::Path & path,
double & linear_vel,
double & sign)
368 double curvature_vel = linear_vel, cost_vel = linear_vel;
371 if (params_->use_regulated_linear_velocity_scaling) {
372 curvature_vel = heuristics::curvatureConstraint(
373 linear_vel, curvature, params_->regulated_linear_scaling_min_radius);
377 if (params_->use_cost_regulated_linear_velocity_scaling) {
378 cost_vel = heuristics::costConstraint(linear_vel, pose_cost, costmap_ros_, params_);
382 linear_vel = std::min(cost_vel, curvature_vel);
383 linear_vel = std::max(linear_vel, params_->regulated_linear_scaling_min_speed);
386 linear_vel = heuristics::approachVelocityConstraint(
387 linear_vel, path, params_->min_approach_linear_velocity,
388 params_->approach_velocity_scaling_dist);
391 linear_vel = std::clamp(fabs(linear_vel), 0.0, params_->desired_linear_vel);
392 linear_vel = sign * linear_vel;
395 void RegulatedPurePursuitController::setPlan(
const nav_msgs::msg::Path & path)
397 has_reached_xy_tolerance_ =
false;
398 path_handler_->setPlan(path);
401 void RegulatedPurePursuitController::setSpeedLimit(
402 const double & speed_limit,
403 const bool & percentage)
405 std::lock_guard<std::mutex> lock_reinit(param_handler_->getMutex());
407 if (speed_limit == nav2_costmap_2d::NO_SPEED_LIMIT) {
409 params_->desired_linear_vel = params_->base_desired_linear_vel;
413 params_->desired_linear_vel = params_->base_desired_linear_vel * speed_limit / 100.0;
416 params_->desired_linear_vel = speed_limit;
421 void RegulatedPurePursuitController::reset()
424 finished_cancelling_ =
false;
425 has_reached_xy_tolerance_ =
false;
428 double RegulatedPurePursuitController::findVelocitySignChange(
429 const nav_msgs::msg::Path & transformed_plan)
432 for (
unsigned int pose_id = 1; pose_id < transformed_plan.poses.size() - 1; ++pose_id) {
434 double oa_x = transformed_plan.poses[pose_id].pose.position.x -
435 transformed_plan.poses[pose_id - 1].pose.position.x;
436 double oa_y = transformed_plan.poses[pose_id].pose.position.y -
437 transformed_plan.poses[pose_id - 1].pose.position.y;
438 double ab_x = transformed_plan.poses[pose_id + 1].pose.position.x -
439 transformed_plan.poses[pose_id].pose.position.x;
440 double ab_y = transformed_plan.poses[pose_id + 1].pose.position.y -
441 transformed_plan.poses[pose_id].pose.position.y;
446 const double dot_prod = (oa_x * ab_x) + (oa_y * ab_y);
447 if (dot_prod < 0.0) {
451 transformed_plan.poses[pose_id].pose.position.x,
452 transformed_plan.poses[pose_id].pose.position.y);
456 (hypot(oa_x, oa_y) == 0.0 &&
457 transformed_plan.poses[pose_id - 1].pose.orientation !=
458 transformed_plan.poses[pose_id].pose.orientation)
460 (hypot(ab_x, ab_y) == 0.0 &&
461 transformed_plan.poses[pose_id].pose.orientation !=
462 transformed_plan.poses[pose_id + 1].pose.orientation))
467 transformed_plan.poses[pose_id].pose.position.x,
468 transformed_plan.poses[pose_id].pose.position.y);
472 return std::numeric_limits<double>::max();
477 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.