18 #include "angles/angles.h"
19 #include "nav2_core/controller_exceptions.hpp"
20 #include "nav2_util/geometry_utils.hpp"
21 #include "nav2_util/controller_utils.hpp"
22 #include "nav2_util/path_utils.hpp"
23 #include "nav2_graceful_controller/graceful_controller.hpp"
24 #include "nav2_costmap_2d/costmap_filters/filter_values.hpp"
25 #include "nav2_ros_common/tf2_factories.hpp"
27 namespace nav2_graceful_controller
31 const nav2::LifecycleNode::WeakPtr & parent,
32 std::string name,
const nav2::TransformBuffer::SharedPtr tf,
33 const std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
35 nav2::LifecycleNode::SharedPtr node = parent.lock();
40 costmap_ros_ = costmap_ros;
43 logger_ = node->get_logger();
47 param_handler_ = std::make_unique<ParameterHandler>(
48 node, plugin_name_, logger_);
49 params_ = param_handler_->getParams();
52 control_law_ = std::make_unique<SmoothControlLaw>(
53 params_->k_phi, params_->k_delta, params_->beta, params_->lambda,
54 params_->slowdown_radius, params_->deceleration_max,
55 params_->v_linear_min, params_->v_linear_max, params_->v_angular_max);
58 if (params_->use_collision_detection) {
59 collision_checker_ = std::make_unique<nav2_costmap_2d::
60 FootprintCollisionChecker<nav2_costmap_2d::Costmap2D *>>(costmap_ros_->getCostmap());
63 double max_valid_cost = costmap_ros_->getUseRadius() ?
64 static_cast<double>(nav2_costmap_2d::MAX_NON_OBSTACLE) :
65 static_cast<double>(nav2_costmap_2d::INSCRIBED_INFLATED_OBSTACLE);
66 if (max_valid_cost -
static_cast<double>(params_->obstacle_cost_margin) < 0.0) {
68 logger_,
"obstacle_cost_margin (%d) is higher than max cost (%d).",
69 params_->obstacle_cost_margin, nav2_costmap_2d::MAX_NON_OBSTACLE);
74 local_plan_pub_ = node->create_publisher<nav_msgs::msg::Path>(
"local_plan");
75 motion_target_pub_ = node->create_publisher<geometry_msgs::msg::PoseStamped>(
"motion_target");
76 slowdown_pub_ = node->create_publisher<visualization_msgs::msg::Marker>(
"slowdown");
78 RCLCPP_INFO(logger_,
"Configured Graceful Motion Controller: %s", plugin_name_.c_str());
85 "Cleaning up controller: %s of type graceful_controller::GracefulController",
86 plugin_name_.c_str());
87 local_plan_pub_.reset();
88 motion_target_pub_.reset();
89 slowdown_pub_.reset();
90 collision_checker_.reset();
91 param_handler_.reset();
99 "Activating controller: %s of type nav2_graceful_controller::GracefulController",
100 plugin_name_.c_str());
101 local_plan_pub_->on_activate();
102 motion_target_pub_->on_activate();
103 slowdown_pub_->on_activate();
104 param_handler_->activate();
111 "Deactivating controller: %s of type nav2_graceful_controller::GracefulController",
112 plugin_name_.c_str());
113 local_plan_pub_->on_deactivate();
114 motion_target_pub_->on_deactivate();
115 slowdown_pub_->on_deactivate();
116 param_handler_->deactivate();
120 const geometry_msgs::msg::PoseStamped & pose,
121 const geometry_msgs::msg::Twist & velocity,
123 const nav_msgs::msg::Path & transformed_global_plan,
124 const geometry_msgs::msg::PoseStamped & global_goal)
126 std::lock_guard<std::mutex> param_lock(param_handler_->getMutex());
128 geometry_msgs::msg::TwistStamped cmd_vel;
129 cmd_vel.header = pose.header;
132 nav_msgs::msg::Path transformed_plan;
133 if (!nav2_util::transformPathInTargetFrame(
134 transformed_global_plan, transformed_plan, *tf_buffer_,
135 costmap_ros_->getBaseFrameID(), costmap_ros_->getTransformTolerance()))
138 "Unable to transform plan pose into local frame");
142 control_law_->setCurvatureConstants(
143 params_->k_phi, params_->k_delta, params_->beta, params_->lambda);
144 control_law_->setSlowdownRadius(params_->slowdown_radius);
145 control_law_->setMaxDeceleration(params_->deceleration_max);
146 control_law_->setSpeedLimit(params_->v_linear_min, params_->v_linear_max, params_->v_angular_max);
151 geometry_msgs::msg::TransformStamped costmap_transform;
153 costmap_transform = tf_buffer_->lookupTransform(
154 costmap_ros_->getGlobalFrameID(), costmap_ros_->getBaseFrameID(),
156 }
catch (tf2::TransformException & ex) {
158 logger_,
"Could not transform %s to %s: %s",
159 costmap_ros_->getBaseFrameID().c_str(), costmap_ros_->getGlobalFrameID().c_str(),
165 double dist_to_goal = nav2_util::geometry_utils::calculate_path_length(transformed_plan);
170 if (goal_checker->
isGoalXYReached(pose.pose, global_goal.pose, velocity,
171 transformed_global_plan))
173 double angle_to_goal = tf2::getYaw(transformed_plan.poses.back().pose.orientation);
175 size_t num_steps = fabs(angle_to_goal) / params_->in_place_collision_resolution;
177 num_steps = std::max(
static_cast<size_t>(1), num_steps);
178 bool collision_free =
true;
179 for (
size_t i = 1; i <= num_steps; ++i) {
180 double step =
static_cast<double>(i) /
static_cast<double>(num_steps);
181 double yaw = step * angle_to_goal;
182 geometry_msgs::msg::PoseStamped next_pose;
183 next_pose.header.frame_id = costmap_ros_->getBaseFrameID();
184 next_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(yaw);
185 geometry_msgs::msg::PoseStamped costmap_pose;
186 tf2::doTransform(next_pose, costmap_pose, costmap_transform);
187 if (params_->use_collision_detection &&
inCollision(
188 costmap_pose.pose.position.x, costmap_pose.pose.position.y,
189 tf2::getYaw(costmap_pose.pose.orientation)))
191 collision_free =
false;
196 if (collision_free) {
204 nav_msgs::msg::Path local_plan;
205 geometry_msgs::msg::PoseStamped target_pose;
207 double dist_to_target;
208 std::vector<double> target_distances;
211 bool is_first_iteration =
true;
212 for (
int i = transformed_plan.poses.size() - 1; i >= 0; --i) {
213 if (is_first_iteration) {
216 dist_to_target = params_->max_lookahead;
220 target_pose = nav2_util::getLookAheadPoint(dist_to_target, transformed_plan,
false);
221 is_first_iteration =
false;
227 dist_to_target = target_distances[i];
228 target_pose = transformed_plan.poses[i];
234 costmap_transform, cmd_vel) ||
235 validateTargetPose(target_pose, dist_to_target, local_plan, costmap_transform, cmd_vel))
238 motion_target_pub_->publish(std::make_unique<geometry_msgs::msg::PoseStamped>(target_pose));
240 auto slowdown_marker = nav2_graceful_controller::createSlowdownMarker(
241 target_pose, params_->slowdown_radius);
242 slowdown_pub_->publish(std::make_unique<visualization_msgs::msg::Marker>(slowdown_marker));
244 local_plan.header = transformed_plan.header;
245 local_plan_pub_->publish(std::make_unique<nav_msgs::msg::Path>(local_plan));
256 do_initial_rotation_ =
true;
257 safe_approach_angle_.reset();
261 const double & speed_limit,
const bool & percentage)
263 std::lock_guard<std::mutex> param_lock(param_handler_->getMutex());
265 if (speed_limit == nav2_costmap_2d::NO_SPEED_LIMIT) {
266 params_->v_linear_max = params_->v_linear_max_initial;
267 params_->v_angular_max = params_->v_angular_max_initial;
271 params_->v_linear_max = std::max(
272 params_->v_linear_max_initial * speed_limit / 100.0, params_->v_linear_min);
273 params_->v_angular_max = params_->v_angular_max_initial * speed_limit / 100.0;
276 params_->v_linear_max = std::max(speed_limit, params_->v_linear_min);
278 params_->v_angular_max = params_->v_angular_max_initial *
279 speed_limit / params_->v_linear_max_initial;
285 geometry_msgs::msg::PoseStamped & target_pose,
double dist_to_target,
286 nav_msgs::msg::Path & trajectory, geometry_msgs::msg::TransformStamped & costmap_transform,
287 geometry_msgs::msg::TwistStamped & cmd_vel)
290 if (dist_to_target > params_->max_lookahead) {
295 bool reversing =
false;
296 if (params_->allow_backward && target_pose.pose.position.x < 0.0) {
298 target_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(
299 tf2::getYaw(target_pose.pose.orientation) + M_PI);
303 double sim_linear_velocity = params_->v_linear_max;
305 control_law_->setSpeedLimit(params_->v_linear_min, sim_linear_velocity, params_->v_angular_max);
306 if (
simulateTrajectory(target_pose, costmap_transform, trajectory, cmd_vel, reversing)) {
311 sim_linear_velocity -= params_->footprint_scaling_step;
312 }
while (sim_linear_velocity >= params_->footprint_scaling_linear_vel);
319 geometry_msgs::msg::PoseStamped & target_pose,
double dist_to_target,
double dist_to_goal,
320 nav_msgs::msg::Path & trajectory, geometry_msgs::msg::TransformStamped & costmap_transform,
321 geometry_msgs::msg::TwistStamped & cmd_vel)
325 if (dist_to_goal >= params_->max_lookahead || !params_->prefer_final_rotation) {
330 double yaw = std::atan2(target_pose.pose.position.y, target_pose.pose.position.x);
331 target_pose.pose.orientation =
332 nav2_util::geometry_utils::orientationAroundZAxis(yaw);
334 if (
validateTargetPose(target_pose, dist_to_target, trajectory, costmap_transform, cmd_vel)) {
336 double max_valid_cost =
337 costmap_ros_->getUseRadius() ?
static_cast<double>(nav2_costmap_2d::MAX_NON_OBSTACLE) :
338 static_cast<double>(nav2_costmap_2d::INSCRIBED_INFLATED_OBSTACLE);
341 double safety_threshold = max_valid_cost -
static_cast<double>(params_->obstacle_cost_margin);
342 if (
getMaxCost(trajectory, costmap_transform) >= safety_threshold) {
345 target_pose, dist_to_target, costmap_transform, max_valid_cost, trajectory, cmd_vel);
353 const geometry_msgs::msg::PoseStamped & motion_target,
354 const geometry_msgs::msg::TransformStamped & costmap_transform,
355 nav_msgs::msg::Path & trajectory,
356 geometry_msgs::msg::TwistStamped & cmd_vel,
359 trajectory.poses.clear();
362 geometry_msgs::msg::PoseStamped next_pose;
363 next_pose.header.frame_id = costmap_ros_->getBaseFrameID();
364 next_pose.pose.orientation.w = 1.0;
367 bool sim_initial_rotation = do_initial_rotation_ && params_->initial_rotation;
368 double angle_to_target =
369 std::atan2(motion_target.pose.position.y, motion_target.pose.position.x);
370 if (fabs(angle_to_target) < params_->initial_rotation_tolerance) {
371 sim_initial_rotation =
false;
372 do_initial_rotation_ =
false;
375 double distance = std::numeric_limits<double>::max();
376 double resolution = costmap_ros_->getCostmap()->getResolution();
377 double dt = (params_->v_linear_max > 0.0) ? resolution / params_->v_linear_max : 0.0;
380 unsigned int max_iter = 3 *
381 std::hypot(motion_target.pose.position.x, motion_target.pose.position.y) / resolution;
385 if (sim_initial_rotation) {
387 double next_pose_yaw = tf2::getYaw(next_pose.pose.orientation);
391 if (trajectory.poses.empty()) {cmd_vel.twist = cmd;}
394 if (fabs(angle_to_target - next_pose_yaw) < params_->initial_rotation_tolerance) {
395 sim_initial_rotation =
false;
399 next_pose_yaw += cmd_vel.twist.angular.z * dt;
400 next_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(next_pose_yaw);
403 if (trajectory.poses.empty()) {
404 cmd_vel.twist = control_law_->calculateRegularVelocity(
405 motion_target.pose, next_pose.pose, backward);
409 next_pose.pose = control_law_->calculateNextPose(
410 dt, motion_target.pose, next_pose.pose, backward);
414 trajectory.poses.push_back(next_pose);
417 double footprint_scaling = 1.0;
418 if (cmd_vel.twist.linear.x > params_->footprint_scaling_linear_vel) {
420 double ratio = params_->v_linear_max - params_->footprint_scaling_linear_vel;
423 ratio = (cmd_vel.twist.linear.x - params_->footprint_scaling_linear_vel) / ratio;
424 footprint_scaling += ratio * params_->footprint_scaling_factor;
429 geometry_msgs::msg::PoseStamped global_pose;
430 tf2::doTransform(next_pose, global_pose, costmap_transform);
431 if (params_->use_collision_detection &&
inCollision(
432 global_pose.pose.position.x, global_pose.pose.position.y,
433 tf2::getYaw(global_pose.pose.orientation), footprint_scaling))
439 distance = nav2_util::geometry_utils::euclidean_distance(motion_target.pose, next_pose.pose);
440 }
while(distance > resolution && trajectory.poses.size() < max_iter);
447 geometry_msgs::msg::Twist vel;
449 vel.angular.z = params_->rotation_scaling_factor * angle_to_target * params_->v_angular_max;
450 vel.angular.z = std::copysign(1.0, vel.angular.z) * std::max(
452 params_->v_angular_min_in_place);
457 const nav_msgs::msg::Path & path, geometry_msgs::msg::TransformStamped & costmap_transform)
459 double max_cost = 0.0;
461 for (
const auto & pose : path.poses) {
462 geometry_msgs::msg::PoseStamped costmap_pose;
463 tf2::doTransform(pose, costmap_pose, costmap_transform);
465 if (costmap_ros_->getCostmap()->worldToMap(costmap_pose.pose.position.x,
466 costmap_pose.pose.position.y, mx, my))
468 max_cost = std::max(max_cost, collision_checker_->pointCost(mx, my));
476 const double & x,
const double & y,
const double & theta,
477 double inflation_scale)
480 if (!costmap_ros_->getCostmap()->worldToMap(x, y, mx, my)) {
482 logger_,
"The path is not in the costmap. Cannot check for collisions. "
483 "Proceed at your own risk, slow the robot, or increase your costmap size.");
487 if (inflation_scale < 1.0) {
488 RCLCPP_WARN(logger_,
"Inflation ratio cannot be less than 1.0");
494 bool is_tracking_unknown =
495 costmap_ros_->getLayeredCostmap()->isTrackingUnknown();
496 bool consider_footprint = !costmap_ros_->getUseRadius();
498 double footprint_cost;
499 if (consider_footprint) {
500 std::vector<geometry_msgs::msg::Point> spec = costmap_ros_->getRobotFootprint();
501 if (spec.size() > 3) {
502 for (
auto & point : spec) {
503 point.x *= inflation_scale;
504 point.y *= inflation_scale;
507 footprint_cost = collision_checker_->footprintCostAtPose(x, y, theta, spec);
509 footprint_cost = collision_checker_->pointCost(mx, my);
512 switch (
static_cast<unsigned char>(footprint_cost)) {
513 case (nav2_costmap_2d::LETHAL_OBSTACLE):
515 case (nav2_costmap_2d::INSCRIBED_INFLATED_OBSTACLE):
516 return consider_footprint ? false :
true;
517 case (nav2_costmap_2d::NO_INFORMATION):
518 return is_tracking_unknown ? false :
true;
525 const std::vector<geometry_msgs::msg::PoseStamped> & poses,
526 std::vector<double> & distances)
528 distances.resize(poses.size());
530 double d = std::hypot(poses[0].pose.position.x, poses[0].pose.position.y);
533 for (
size_t i = 1; i < poses.size(); ++i) {
534 d += nav2_util::geometry_utils::euclidean_distance(poses[i - 1].pose, poses[i].pose);
540 std::vector<geometry_msgs::msg::PoseStamped> & path)
544 if (path.size() < 3) {
return;}
547 double initial_yaw = tf2::getYaw(path[1].pose.orientation);
548 for (
size_t i = 2; i < path.size() - 1; ++i) {
549 double this_yaw = tf2::getYaw(path[i].pose.orientation);
550 if (angles::shortest_angular_distance(this_yaw, initial_yaw) > 1e-6) {
return;}
555 for (
size_t i = 0; i < path.size() - 1; ++i) {
557 double dx = path[i + 1].pose.position.x - path[i].pose.position.x;
558 double dy = path[i + 1].pose.position.y - path[i].pose.position.y;
559 double yaw = std::atan2(dy, dx);
560 path[i].pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(yaw);
565 geometry_msgs::msg::PoseStamped & target_pose,
double dist_to_target,
566 geometry_msgs::msg::TransformStamped & costmap_transform,
double safety_cost,
567 nav_msgs::msg::Path & best_trajectory, geometry_msgs::msg::TwistStamped & best_cmd_vel)
569 bool found_valid =
false;
570 double best_eta = std::numeric_limits<double>::max();
572 for (
int i = 0; i < 2 * M_PI / params_->final_rotation_search_step; ++i) {
573 double angle =
static_cast<double>(i) * params_->final_rotation_search_step;
575 if (safe_approach_angle_.has_value()) {
576 angle += safe_approach_angle_.value();
580 auto candidate_pose = target_pose;
581 candidate_pose.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(angle);
583 nav_msgs::msg::Path candidate_path = best_trajectory;
584 geometry_msgs::msg::TwistStamped candidate_cmd_vel = best_cmd_vel;
588 candidate_pose, dist_to_target, candidate_path, costmap_transform,
591 double candidate_cost =
getMaxCost(candidate_path, costmap_transform);
593 bool reversing =
false;
594 if (params_->allow_backward && target_pose.pose.position.x < 0.0) {
599 for (
size_t j = 1; j < candidate_path.poses.size(); ++j) {
600 auto current_pose = candidate_path.poses[j - 1];
601 auto next_pose = candidate_path.poses[j];
602 auto cmd = control_law_->calculateRegularVelocity(candidate_pose.pose, current_pose.pose,
604 double speed = std::abs(cmd.linear.x);
606 speed = std::max(speed, 1e-3);
607 double step_dist = nav2_util::geometry_utils::euclidean_distance(
608 current_pose.pose, next_pose.pose);
609 double step_time = step_dist / speed;
614 if (eta < best_eta) {
616 if (candidate_cost < safety_cost) {
617 best_trajectory = candidate_path;
618 best_cmd_vel = candidate_cmd_vel;
619 target_pose = candidate_pose;
622 if (safe_approach_angle_.value_or(1e3 ) == angle) {
625 safe_approach_angle_ = angle;
637 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...
Graceful controller plugin.
void activate() override
Activate controller state machine.
void computeDistanceAlongPath(const std::vector< geometry_msgs::msg::PoseStamped > &poses, std::vector< double > &distances)
Compute the distance to each pose in a path.
bool validateTargetPose(geometry_msgs::msg::PoseStamped &target_pose, double dist_to_target, nav_msgs::msg::Path &trajectory, geometry_msgs::msg::TransformStamped &costmap_transform, geometry_msgs::msg::TwistStamped &cmd_vel)
Validate a given target pose for calculating command velocity.
bool validateTargetPoseOnApproach(geometry_msgs::msg::PoseStamped &target_pose, double dist_to_target, double dist_to_goal, nav_msgs::msg::Path &trajectory, geometry_msgs::msg::TransformStamped &costmap_transform, geometry_msgs::msg::TwistStamped &cmd_vel)
Validate a given target pose for calculating command velocity on approach to goal.
void deactivate() override
Deactivate controller state machine.
bool inCollision(const double &x, const double &y, const double &theta, double inflation_scale=1.0)
Checks if the robot is in collision.
bool findBestApproachTrajectory(geometry_msgs::msg::PoseStamped &target_pose, double dist_to_target, geometry_msgs::msg::TransformStamped &costmap_transform, double safety_cost, nav_msgs::msg::Path &best_trajectory, geometry_msgs::msg::TwistStamped &best_cmd_vel)
Find the best approach trajectory by searching multiple orientations.
bool simulateTrajectory(const geometry_msgs::msg::PoseStamped &motion_target, const geometry_msgs::msg::TransformStamped &costmap_transform, nav_msgs::msg::Path &trajectory, geometry_msgs::msg::TwistStamped &cmd_vel, bool backward)
Simulate trajectory calculating in every step the new velocity command based on a new curvature value...
geometry_msgs::msg::TwistStamped computeVelocityCommands(const geometry_msgs::msg::PoseStamped &pose, const geometry_msgs::msg::Twist &velocity, nav2_core::GoalChecker *goal_checker, const nav_msgs::msg::Path &transformed_global_plan, const geometry_msgs::msg::PoseStamped &global_goal) override
Compute the best command given the current pose and velocity.
void configure(const nav2::LifecycleNode::WeakPtr &parent, std::string name, nav2::TransformBuffer::SharedPtr tf, std::shared_ptr< nav2_costmap_2d::Costmap2DROS > costmap_ros) override
Configure controller state machine.
void cleanup() override
Cleanup controller state machine.
geometry_msgs::msg::Twist rotateToTarget(double angle_to_target)
Rotate the robot to face the motion target with maximum angular velocity.
void newPathReceived(const nav_msgs::msg::Path &raw_global_path) override
nav2_core newPathReceived - Receives a new plan from the Planner Server
void setSpeedLimit(const double &speed_limit, const bool &percentage) override
Limits the maximum linear speed of the robot.
void validateOrientations(std::vector< geometry_msgs::msg::PoseStamped > &path)
Control law requires proper orientations, not all planners provide them.
double getMaxCost(const nav_msgs::msg::Path &path, geometry_msgs::msg::TransformStamped &costmap_transform)
Get the maximum cost of a path.