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_util/path_utils.hpp"
30 #include "nav2_costmap_2d/costmap_filters/filter_values.hpp"
31 #include "nav2_ros_common/tf2_factories.hpp"
39 namespace nav2_regulated_pure_pursuit_controller
42 void RegulatedPurePursuitController::configure(
43 const nav2::LifecycleNode::WeakPtr & parent,
44 std::string name, nav2::TransformBuffer::SharedPtr tf,
45 std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
47 auto node = parent.lock();
53 costmap_ros_ = costmap_ros;
54 costmap_ = costmap_ros_->getCostmap();
57 logger_ = node->get_logger();
61 param_handler_ = std::make_unique<ParameterHandler>(
62 node, plugin_name_, logger_, costmap_->getSizeInMetersX());
63 params_ = param_handler_->getParams();
66 collision_checker_ = std::make_unique<CollisionChecker>(node, costmap_ros_, params_);
68 double control_frequency = 20.0;
70 node->get_parameter(
"controller_frequency", control_frequency);
71 control_duration_ = 1.0 / control_frequency;
73 carrot_pub_ = node->create_publisher<geometry_msgs::msg::PointStamped>(
"lookahead_point");
74 curvature_carrot_pub_ = node->create_publisher<geometry_msgs::msg::PointStamped>(
75 "curvature_lookahead_point");
76 is_rotating_to_heading_pub_ = node->create_publisher<std_msgs::msg::Bool>(
77 "is_rotating_to_heading");
80 void RegulatedPurePursuitController::cleanup()
84 "Cleaning up controller: %s of type"
85 " regulated_pure_pursuit_controller::RegulatedPurePursuitController",
86 plugin_name_.c_str());
88 curvature_carrot_pub_.reset();
89 is_rotating_to_heading_pub_.reset();
92 void RegulatedPurePursuitController::activate()
96 "Activating controller: %s of type "
97 "regulated_pure_pursuit_controller::RegulatedPurePursuitController",
98 plugin_name_.c_str());
99 carrot_pub_->on_activate();
100 curvature_carrot_pub_->on_activate();
101 is_rotating_to_heading_pub_->on_activate();
102 param_handler_->activate();
105 void RegulatedPurePursuitController::deactivate()
109 "Deactivating controller: %s of type "
110 "regulated_pure_pursuit_controller::RegulatedPurePursuitController",
111 plugin_name_.c_str());
112 carrot_pub_->on_deactivate();
113 curvature_carrot_pub_->on_deactivate();
114 is_rotating_to_heading_pub_->on_deactivate();
115 param_handler_->deactivate();
116 last_command_velocity_ = geometry_msgs::msg::Twist();
119 std::unique_ptr<geometry_msgs::msg::PointStamped> RegulatedPurePursuitController::createCarrotMsg(
120 const geometry_msgs::msg::PoseStamped & carrot_pose)
122 auto carrot_msg = std::make_unique<geometry_msgs::msg::PointStamped>();
123 carrot_msg->header = carrot_pose.header;
124 carrot_msg->point.x = carrot_pose.pose.position.x;
125 carrot_msg->point.y = carrot_pose.pose.position.y;
126 carrot_msg->point.z = 0.01;
130 double RegulatedPurePursuitController::getLookAheadDistance(
131 const geometry_msgs::msg::Twist & speed)
135 double lookahead_dist = params_->lookahead_dist;
136 if (params_->use_velocity_scaled_lookahead_dist) {
137 lookahead_dist = fabs(speed.linear.x) * params_->lookahead_time;
138 lookahead_dist = std::clamp(
139 lookahead_dist, params_->min_lookahead_dist, params_->max_lookahead_dist);
142 return lookahead_dist;
145 double calculateCurvature(geometry_msgs::msg::Point lookahead_point)
149 const double carrot_dist2 =
150 (lookahead_point.x * lookahead_point.x) +
151 (lookahead_point.y * lookahead_point.y);
154 if (carrot_dist2 > 0.001) {
155 return 2.0 * lookahead_point.y / carrot_dist2;
161 geometry_msgs::msg::TwistStamped RegulatedPurePursuitController::computeVelocityCommands(
162 const geometry_msgs::msg::PoseStamped & pose,
163 const geometry_msgs::msg::Twist & speed,
165 const nav_msgs::msg::Path & transformed_global_plan,
166 const geometry_msgs::msg::PoseStamped & global_goal)
168 std::lock_guard<std::mutex> lock_reinit(param_handler_->getMutex());
171 std::unique_lock<nav2_costmap_2d::Costmap2D::mutex_t> lock(*(costmap->getMutex()));
174 nav_msgs::msg::Path transformed_plan;
175 if (!nav2_util::transformPathInTargetFrame(
176 transformed_global_plan, transformed_plan, *tf_,
177 costmap_ros_->getBaseFrameID(), costmap_ros_->getTransformTolerance()))
180 "Unable to transform plan pose into local frame");
184 double lookahead_dist = getLookAheadDistance(speed);
185 double curv_lookahead_dist = params_->curvature_lookahead_dist;
188 auto carrot_pose = nav2_util::getLookAheadPoint(lookahead_dist, transformed_plan);
189 auto rotate_to_path_carrot_pose = carrot_pose;
190 carrot_pub_->publish(createCarrotMsg(carrot_pose));
192 double linear_vel, angular_vel;
194 double lookahead_curvature = calculateCurvature(carrot_pose.pose.position);
196 double regulation_curvature = lookahead_curvature;
197 if (params_->use_fixed_curvature_lookahead) {
198 auto curvature_lookahead_pose = nav2_util::getLookAheadPoint(
200 transformed_plan, params_->interpolate_curvature_after_goal);
201 rotate_to_path_carrot_pose = curvature_lookahead_pose;
202 regulation_curvature = calculateCurvature(curvature_lookahead_pose.pose.position);
203 curvature_carrot_pub_->publish(createCarrotMsg(curvature_lookahead_pose));
207 double x_vel_sign = 1.0;
208 if (params_->allow_reversing) {
209 x_vel_sign = carrot_pose.pose.position.x >= 0.0 ? 1.0 : -1.0;
212 linear_vel = params_->max_linear_vel;
219 double angle_to_heading;
225 if (shouldRotateToGoalHeading(goal_checker, pose, global_goal, speed, transformed_global_plan)) {
226 is_rotating_to_heading_ =
true;
227 double angle_to_goal = tf2::getYaw(transformed_plan.poses.back().pose.orientation);
228 rotateToHeading(linear_vel, angular_vel, angle_to_goal, speed);
229 }
else if (shouldRotateToPath(rotate_to_path_carrot_pose, angle_to_heading, x_vel_sign)) {
230 is_rotating_to_heading_ =
true;
231 rotateToHeading(linear_vel, angular_vel, angle_to_heading, speed);
233 is_rotating_to_heading_ =
false;
235 regulation_curvature, speed,
236 collision_checker_->costAtPose(pose.pose.position.x, pose.pose.position.y), transformed_plan,
237 linear_vel, x_vel_sign);
240 const double & dt = control_duration_;
241 linear_vel = speed.linear.x - x_vel_sign * dt * params_->cancel_deceleration;
243 if (x_vel_sign > 0) {
244 if (linear_vel <= 0) {
246 finished_cancelling_ =
true;
249 if (linear_vel >= 0) {
251 finished_cancelling_ =
true;
257 if (!params_->use_dynamic_window) {
258 angular_vel = linear_vel * regulation_curvature;
262 const double regulated_linear_vel = linear_vel;
264 const geometry_msgs::msg::Twist current_speed = last_command_velocity_;
266 std::tie(linear_vel, angular_vel) =
267 dynamic_window_pure_pursuit::computeDynamicWindowVelocities(
269 params_->max_linear_vel,
270 params_->min_linear_vel,
271 params_->max_angular_vel,
272 params_->min_angular_vel,
273 params_->max_linear_accel,
274 params_->max_linear_decel,
275 params_->max_angular_accel,
276 params_->max_angular_decel,
277 regulated_linear_vel,
278 regulation_curvature,
285 const double dist_to_path_end =
286 nav2_util::geometry_utils::calculate_path_length(transformed_plan);
287 const double & carrot_dist = hypot(carrot_pose.pose.position.x, carrot_pose.pose.position.y);
288 if (params_->use_collision_detection &&
289 collision_checker_->isCollisionImminent(pose, linear_vel, angular_vel, carrot_dist,
296 auto is_rotating_to_heading_msg = std::make_unique<std_msgs::msg::Bool>();
297 is_rotating_to_heading_msg->data = is_rotating_to_heading_;
298 is_rotating_to_heading_pub_->publish(std::move(is_rotating_to_heading_msg));
301 geometry_msgs::msg::TwistStamped cmd_vel;
302 cmd_vel.header = pose.header;
303 cmd_vel.twist.linear.x = linear_vel;
304 cmd_vel.twist.angular.z = angular_vel;
307 last_command_velocity_ = cmd_vel.twist;
312 bool RegulatedPurePursuitController::cancel()
315 if (!params_->use_cancel_deceleration) {
319 return finished_cancelling_;
322 bool RegulatedPurePursuitController::shouldRotateToPath(
323 const geometry_msgs::msg::PoseStamped & carrot_pose,
double & angle_to_path,
327 angle_to_path = atan2(carrot_pose.pose.position.y, carrot_pose.pose.position.x);
329 if (x_vel_sign < 0.0) {
330 angle_to_path = angles::normalize_angle(angle_to_path + M_PI);
332 return params_->use_rotate_to_heading &&
333 fabs(angle_to_path) > params_->rotate_to_heading_min_angle;
336 bool RegulatedPurePursuitController::shouldRotateToGoalHeading(
338 const geometry_msgs::msg::PoseStamped & robot_pose,
339 const geometry_msgs::msg::PoseStamped & goal_pose,
340 const geometry_msgs::msg::Twist & speed,
341 const nav_msgs::msg::Path & transformed_plan)
344 if (!params_->use_rotate_to_heading) {
347 return goal_checker->
isGoalXYReached(robot_pose.pose, goal_pose.pose, speed,
351 void RegulatedPurePursuitController::rotateToHeading(
352 double & linear_vel,
double & angular_vel,
353 const double & angle_to_path,
const geometry_msgs::msg::Twist & curr_speed)
357 const double sign = angle_to_path > 0.0 ? 1.0 : -1.0;
358 angular_vel = sign * params_->rotate_to_heading_angular_vel;
360 const double & dt = control_duration_;
361 const double min_feasible_angular_speed = curr_speed.angular.z - params_->max_angular_accel * dt;
362 const double max_feasible_angular_speed = curr_speed.angular.z + params_->max_angular_accel * dt;
363 angular_vel = std::clamp(angular_vel, min_feasible_angular_speed, max_feasible_angular_speed);
366 double max_vel_to_stop = std::sqrt(2 * params_->max_angular_accel * fabs(angle_to_path));
367 if (fabs(angular_vel) > max_vel_to_stop) {
368 angular_vel = sign * max_vel_to_stop;
372 void RegulatedPurePursuitController::applyConstraints(
373 const double & curvature,
const geometry_msgs::msg::Twist & ,
374 const double & pose_cost,
const nav_msgs::msg::Path & path,
double & linear_vel,
double & sign)
376 double curvature_vel = linear_vel, cost_vel = linear_vel;
379 if (params_->use_regulated_linear_velocity_scaling) {
380 curvature_vel = heuristics::curvatureConstraint(
381 linear_vel, curvature, params_->regulated_linear_scaling_min_radius);
385 if (params_->use_cost_regulated_linear_velocity_scaling) {
386 cost_vel = heuristics::costConstraint(linear_vel, pose_cost, costmap_ros_, params_);
390 linear_vel = std::min(cost_vel, curvature_vel);
391 linear_vel = std::max(linear_vel, params_->regulated_linear_scaling_min_speed);
394 linear_vel = heuristics::approachVelocityConstraint(
395 linear_vel, path, params_->min_approach_linear_velocity,
396 params_->approach_velocity_scaling_dist);
399 linear_vel = std::clamp(fabs(linear_vel), 0.0, params_->max_linear_vel);
400 linear_vel = sign * linear_vel;
403 void RegulatedPurePursuitController::newPathReceived(
404 const nav_msgs::msg::Path & )
408 void RegulatedPurePursuitController::setSpeedLimit(
409 const double & speed_limit,
410 const bool & percentage)
412 std::lock_guard<std::mutex> lock_reinit(param_handler_->getMutex());
414 if (speed_limit == nav2_costmap_2d::NO_SPEED_LIMIT) {
416 params_->max_linear_vel = params_->base_max_linear_vel;
420 params_->max_linear_vel = params_->base_max_linear_vel * speed_limit / 100.0;
423 params_->max_linear_vel = speed_limit;
428 void RegulatedPurePursuitController::reset()
431 finished_cancelling_ =
false;
432 last_command_velocity_ = geometry_msgs::msg::Twist();
437 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 isGoalXYReached(const geometry_msgs::msg::Pose &query_pose, const geometry_msgs::msg::Pose &goal_pose, const geometry_msgs::msg::Twist &velocity, const nav_msgs::msg::Path &transformed_global_plan)=0
Check if XY goal position has been reached (without considering yaw) This is useful for controllers t...
A 2D costmap provides a mapping between points in the world and their associated "costs".
Regulated pure pursuit controller plugin.