Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
regulated_pure_pursuit_controller.cpp
1 // Copyright (c) 2020 Shrijit Singh
2 // Copyright (c) 2020 Samsung Research America
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 
16 #include <algorithm>
17 #include <string>
18 #include <limits>
19 #include <memory>
20 #include <vector>
21 #include <utility>
22 
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"
32 
33 using std::hypot;
34 using std::min;
35 using std::max;
36 using std::abs;
37 using namespace nav2_costmap_2d; // NOLINT
38 
39 namespace nav2_regulated_pure_pursuit_controller
40 {
41 
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)
46 {
47  auto node = parent.lock();
48  node_ = parent;
49  if (!node) {
50  throw nav2_core::ControllerException("Unable to lock node!");
51  }
52 
53  costmap_ros_ = costmap_ros;
54  costmap_ = costmap_ros_->getCostmap();
55  tf_ = tf;
56  plugin_name_ = name;
57  logger_ = node->get_logger();
58 
59  // Handles storage and dynamic configuration of parameters.
60  // Returns pointer to data current param settings.
61  param_handler_ = std::make_unique<ParameterHandler>(
62  node, plugin_name_, logger_, costmap_->getSizeInMetersX());
63  params_ = param_handler_->getParams();
64 
65  // Checks for imminent collisions
66  collision_checker_ = std::make_unique<CollisionChecker>(node, costmap_ros_, params_);
67 
68  double control_frequency = 20.0;
69 
70  node->get_parameter("controller_frequency", control_frequency);
71  control_duration_ = 1.0 / control_frequency;
72 
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");
78 }
79 
80 void RegulatedPurePursuitController::cleanup()
81 {
82  RCLCPP_INFO(
83  logger_,
84  "Cleaning up controller: %s of type"
85  " regulated_pure_pursuit_controller::RegulatedPurePursuitController",
86  plugin_name_.c_str());
87  carrot_pub_.reset();
88  curvature_carrot_pub_.reset();
89  is_rotating_to_heading_pub_.reset();
90 }
91 
92 void RegulatedPurePursuitController::activate()
93 {
94  RCLCPP_INFO(
95  logger_,
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();
103 }
104 
105 void RegulatedPurePursuitController::deactivate()
106 {
107  RCLCPP_INFO(
108  logger_,
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();
117 }
118 
119 std::unique_ptr<geometry_msgs::msg::PointStamped> RegulatedPurePursuitController::createCarrotMsg(
120  const geometry_msgs::msg::PoseStamped & carrot_pose)
121 {
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; // publish right over map to stand out
127  return carrot_msg;
128 }
129 
130 double RegulatedPurePursuitController::getLookAheadDistance(
131  const geometry_msgs::msg::Twist & speed)
132 {
133  // If using velocity-scaled look ahead distances, find and clamp the dist
134  // Else, use the static look ahead distance
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);
140  }
141 
142  return lookahead_dist;
143 }
144 
145 double calculateCurvature(geometry_msgs::msg::Point lookahead_point)
146 {
147  // Find distance^2 to look ahead point (carrot) in robot base frame
148  // This is the chord length of the circle
149  const double carrot_dist2 =
150  (lookahead_point.x * lookahead_point.x) +
151  (lookahead_point.y * lookahead_point.y);
152 
153  // Find curvature of circle (k = 1 / R)
154  if (carrot_dist2 > 0.001) {
155  return 2.0 * lookahead_point.y / carrot_dist2;
156  } else {
157  return 0.0;
158  }
159 }
160 
161 geometry_msgs::msg::TwistStamped RegulatedPurePursuitController::computeVelocityCommands(
162  const geometry_msgs::msg::PoseStamped & pose,
163  const geometry_msgs::msg::Twist & speed,
164  nav2_core::GoalChecker * goal_checker,
165  const nav_msgs::msg::Path & transformed_global_plan,
166  const geometry_msgs::msg::PoseStamped & global_goal)
167 {
168  std::lock_guard<std::mutex> lock_reinit(param_handler_->getMutex());
169 
170  nav2_costmap_2d::Costmap2D * costmap = costmap_ros_->getCostmap();
171  std::unique_lock<nav2_costmap_2d::Costmap2D::mutex_t> lock(*(costmap->getMutex()));
172 
173  // Transform the plan from costmap's global frame to robot base frame
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()))
178  {
180  "Unable to transform plan pose into local frame");
181  }
182 
183  // Find look ahead distance and point on path and publish
184  double lookahead_dist = getLookAheadDistance(speed);
185  double curv_lookahead_dist = params_->curvature_lookahead_dist;
186 
187  // Get the particular point on the path at the lookahead distance
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));
191 
192  double linear_vel, angular_vel;
193 
194  double lookahead_curvature = calculateCurvature(carrot_pose.pose.position);
195 
196  double regulation_curvature = lookahead_curvature;
197  if (params_->use_fixed_curvature_lookahead) {
198  auto curvature_lookahead_pose = nav2_util::getLookAheadPoint(
199  curv_lookahead_dist,
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));
204  }
205 
206  // Setting the velocity direction
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;
210  }
211 
212  linear_vel = params_->max_linear_vel;
213 
214  // Make sure we're in compliance with basic constraints
215  // For shouldRotateToPath, using x_vel_sign in order to support allow_reversing
216  // and rotate_to_path_carrot_pose for the direction carrot pose:
217  // - equal to "normal" carrot_pose when curvature_lookahead_pose = false
218  // - otherwise equal to curvature_lookahead_pose (which can be interpolated after goal)
219  double angle_to_heading;
220  // Feed the goal checker the GLOBAL-frame plan (same frame as `pose` / `global_goal`), not the
221  // base_link-frame `transformed_plan` used for lookahead. The custom AxisGoalChecker derives the
222  // path-progress direction from the plan geometry, so a base-frame plan combined with global
223  // query/goal poses yields a wrong end-of-path direction and a spurious goal-reached. This keeps
224  // the check identical to controller_server::isGoalReached(), which uses the global-frame plan.
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);
232  } else {
233  is_rotating_to_heading_ = false;
234  applyConstraints(
235  regulation_curvature, speed,
236  collision_checker_->costAtPose(pose.pose.position.x, pose.pose.position.y), transformed_plan,
237  linear_vel, x_vel_sign);
238 
239  if (cancelling_) {
240  const double & dt = control_duration_;
241  linear_vel = speed.linear.x - x_vel_sign * dt * params_->cancel_deceleration;
242 
243  if (x_vel_sign > 0) {
244  if (linear_vel <= 0) {
245  linear_vel = 0;
246  finished_cancelling_ = true;
247  }
248  } else {
249  if (linear_vel >= 0) {
250  linear_vel = 0;
251  finished_cancelling_ = true;
252  }
253  }
254  }
255 
256  // Apply curvature to angular velocity after constraining linear velocity
257  if (!params_->use_dynamic_window) {
258  angular_vel = linear_vel * regulation_curvature;
259  } else {
260  // compute optimal path tracking velocity commands
261  // considering velocity and acceleration constraints (DWPP)
262  const double regulated_linear_vel = linear_vel;
263  // using last command velocity as a current velocity
264  const geometry_msgs::msg::Twist current_speed = last_command_velocity_;
265 
266  std::tie(linear_vel, angular_vel) =
267  dynamic_window_pure_pursuit::computeDynamicWindowVelocities(
268  current_speed,
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,
279  x_vel_sign,
280  control_duration_);
281  }
282  }
283 
284  // Collision checking on this velocity heading
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,
290  dist_to_path_end))
291  {
292  throw nav2_core::NoValidControl("RegulatedPurePursuitController detected collision ahead!");
293  }
294 
295  // Publish whether we are rotating to goal heading
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));
299 
300  // populate and return message
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;
305 
306  // For dynamic window scaling in open-loop speed control
307  last_command_velocity_ = cmd_vel.twist;
308 
309  return cmd_vel;
310 }
311 
312 bool RegulatedPurePursuitController::cancel()
313 {
314  // if false then publish zero velocity
315  if (!params_->use_cancel_deceleration) {
316  return true;
317  }
318  cancelling_ = true;
319  return finished_cancelling_;
320 }
321 
322 bool RegulatedPurePursuitController::shouldRotateToPath(
323  const geometry_msgs::msg::PoseStamped & carrot_pose, double & angle_to_path,
324  double & x_vel_sign)
325 {
326  // Whether we should rotate robot to rough path heading
327  angle_to_path = atan2(carrot_pose.pose.position.y, carrot_pose.pose.position.x);
328  // In case we are reversing
329  if (x_vel_sign < 0.0) {
330  angle_to_path = angles::normalize_angle(angle_to_path + M_PI);
331  }
332  return params_->use_rotate_to_heading &&
333  fabs(angle_to_path) > params_->rotate_to_heading_min_angle;
334 }
335 
336 bool RegulatedPurePursuitController::shouldRotateToGoalHeading(
337  nav2_core::GoalChecker * goal_checker,
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)
342 {
343  // Whether we should rotate robot to goal heading
344  if (!params_->use_rotate_to_heading) {
345  return false;
346  }
347  return goal_checker->isGoalXYReached(robot_pose.pose, goal_pose.pose, speed,
348  transformed_plan);
349 }
350 
351 void RegulatedPurePursuitController::rotateToHeading(
352  double & linear_vel, double & angular_vel,
353  const double & angle_to_path, const geometry_msgs::msg::Twist & curr_speed)
354 {
355  // Rotate in place using max angular velocity / acceleration possible
356  linear_vel = 0.0;
357  const double sign = angle_to_path > 0.0 ? 1.0 : -1.0;
358  angular_vel = sign * params_->rotate_to_heading_angular_vel;
359 
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);
364 
365  // Check if we need to slow down to avoid overshooting
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;
369  }
370 }
371 
372 void RegulatedPurePursuitController::applyConstraints(
373  const double & curvature, const geometry_msgs::msg::Twist & /*curr_speed*/,
374  const double & pose_cost, const nav_msgs::msg::Path & path, double & linear_vel, double & sign)
375 {
376  double curvature_vel = linear_vel, cost_vel = linear_vel;
377 
378  // limit the linear velocity by curvature
379  if (params_->use_regulated_linear_velocity_scaling) {
380  curvature_vel = heuristics::curvatureConstraint(
381  linear_vel, curvature, params_->regulated_linear_scaling_min_radius);
382  }
383 
384  // limit the linear velocity by proximity to obstacles
385  if (params_->use_cost_regulated_linear_velocity_scaling) {
386  cost_vel = heuristics::costConstraint(linear_vel, pose_cost, costmap_ros_, params_);
387  }
388 
389  // Use the lowest of the 2 constraints, but above the minimum translational speed
390  linear_vel = std::min(cost_vel, curvature_vel);
391  linear_vel = std::max(linear_vel, params_->regulated_linear_scaling_min_speed);
392 
393  // Apply constraint to reduce speed on approach to the final goal pose
394  linear_vel = heuristics::approachVelocityConstraint(
395  linear_vel, path, params_->min_approach_linear_velocity,
396  params_->approach_velocity_scaling_dist);
397 
398  // Limit linear velocities to be valid
399  linear_vel = std::clamp(fabs(linear_vel), 0.0, params_->max_linear_vel);
400  linear_vel = sign * linear_vel;
401 }
402 
403 void RegulatedPurePursuitController::newPathReceived(
404  const nav_msgs::msg::Path & /*raw_global_path*/)
405 {
406 }
407 
408 void RegulatedPurePursuitController::setSpeedLimit(
409  const double & speed_limit,
410  const bool & percentage)
411 {
412  std::lock_guard<std::mutex> lock_reinit(param_handler_->getMutex());
413 
414  if (speed_limit == nav2_costmap_2d::NO_SPEED_LIMIT) {
415  // Restore default value
416  params_->max_linear_vel = params_->base_max_linear_vel;
417  } else {
418  if (percentage) {
419  // Speed limit is expressed in % from maximum speed of robot
420  params_->max_linear_vel = params_->base_max_linear_vel * speed_limit / 100.0;
421  } else {
422  // Speed limit is expressed in absolute value
423  params_->max_linear_vel = speed_limit;
424  }
425  }
426 }
427 
428 void RegulatedPurePursuitController::reset()
429 {
430  cancelling_ = false;
431  finished_cancelling_ = false;
432  last_command_velocity_ = geometry_msgs::msg::Twist();
433 }
434 } // namespace nav2_regulated_pure_pursuit_controller
435 
436 // Register this controller as a nav2_core plugin
437 PLUGINLIB_EXPORT_CLASS(
controller interface that acts as a virtual base class for all controller plugins
Definition: controller.hpp:60
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".
Definition: costmap_2d.hpp:69