add humble-navigation2

This commit is contained in:
X-lanni
2025-05-27 19:03:40 +08:00
parent 974abb5e1e
commit e74ec539c2
1280 changed files with 204114 additions and 0 deletions
@@ -0,0 +1,365 @@
// Copyright (c) 2023 Alberto J. Tudela Roldán
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "nav2_util/geometry_utils.hpp"
#include "nav2_graceful_controller/graceful_controller.hpp"
#include "nav2_costmap_2d/costmap_filters/filter_values.hpp"
#include "nav2_core/exceptions.hpp"
namespace nav2_graceful_controller
{
void GracefulController::configure(
const rclcpp_lifecycle::LifecycleNode::WeakPtr & parent,
std::string name, const std::shared_ptr<tf2_ros::Buffer> tf,
const std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
{
auto node = parent.lock();
if (!node) {
throw std::runtime_error("Unable to lock node!");
}
costmap_ros_ = costmap_ros;
tf_buffer_ = tf;
plugin_name_ = name;
logger_ = node->get_logger();
// Handles storage and dynamic configuration of parameters.
// Returns pointer to data current param settings.
param_handler_ = std::make_unique<ParameterHandler>(
node, plugin_name_, logger_,
costmap_ros_->getCostmap()->getSizeInMetersX());
params_ = param_handler_->getParams();
// Handles global path transformations
path_handler_ = std::make_unique<PathHandler>(
tf2::durationFromSec(params_->transform_tolerance), tf_buffer_, costmap_ros_);
// Handles the control law to generate the velocity commands
control_law_ = std::make_unique<SmoothControlLaw>(
params_->k_phi, params_->k_delta, params_->beta, params_->lambda, params_->slowdown_radius,
params_->v_linear_min, params_->v_linear_max, params_->v_angular_max);
// Initialize footprint collision checker
collision_checker_ = std::make_unique<nav2_costmap_2d::
FootprintCollisionChecker<nav2_costmap_2d::Costmap2D *>>(costmap_ros_->getCostmap());
// Publishers
transformed_plan_pub_ = node->create_publisher<nav_msgs::msg::Path>("transformed_global_plan", 1);
local_plan_pub_ = node->create_publisher<nav_msgs::msg::Path>("local_plan", 1);
motion_target_pub_ = node->create_publisher<geometry_msgs::msg::PointStamped>("motion_target", 1);
slowdown_pub_ = node->create_publisher<visualization_msgs::msg::Marker>("slowdown", 1);
RCLCPP_INFO(logger_, "Configured Graceful Motion Controller: %s", plugin_name_.c_str());
}
void GracefulController::cleanup()
{
RCLCPP_INFO(
logger_,
"Cleaning up controller: %s of type graceful_controller::GracefulController",
plugin_name_.c_str());
transformed_plan_pub_.reset();
local_plan_pub_.reset();
motion_target_pub_.reset();
slowdown_pub_.reset();
collision_checker_.reset();
path_handler_.reset();
param_handler_.reset();
control_law_.reset();
}
void GracefulController::activate()
{
RCLCPP_INFO(
logger_,
"Activating controller: %s of type nav2_graceful_controller::GracefulController",
plugin_name_.c_str());
transformed_plan_pub_->on_activate();
local_plan_pub_->on_activate();
motion_target_pub_->on_activate();
slowdown_pub_->on_activate();
}
void GracefulController::deactivate()
{
RCLCPP_INFO(
logger_,
"Deactivating controller: %s of type nav2_graceful_controller::GracefulController",
plugin_name_.c_str());
transformed_plan_pub_->on_deactivate();
local_plan_pub_->on_deactivate();
motion_target_pub_->on_deactivate();
slowdown_pub_->on_deactivate();
}
geometry_msgs::msg::TwistStamped GracefulController::computeVelocityCommands(
const geometry_msgs::msg::PoseStamped & pose,
const geometry_msgs::msg::Twist & /*velocity*/,
nav2_core::GoalChecker * goal_checker)
{
std::lock_guard<std::mutex> param_lock(param_handler_->getMutex());
// Update for the current goal checker's state
geometry_msgs::msg::Pose pose_tolerance;
geometry_msgs::msg::Twist velocity_tolerance;
if (!goal_checker->getTolerances(pose_tolerance, velocity_tolerance)) {
RCLCPP_WARN(logger_, "Unable to retrieve goal checker's tolerances!");
} else {
goal_dist_tolerance_ = pose_tolerance.position.x;
}
// Update the smooth control law with the new params
control_law_->setCurvatureConstants(
params_->k_phi, params_->k_delta, params_->beta, params_->lambda);
control_law_->setSlowdownRadius(params_->slowdown_radius);
control_law_->setSpeedLimit(params_->v_linear_min, params_->v_linear_max, params_->v_angular_max);
// Transform path to robot base frame and publish it
auto transformed_plan = path_handler_->transformGlobalPlan(
pose, params_->max_robot_pose_search_dist);
transformed_plan_pub_->publish(transformed_plan);
// Get the particular point on the path at the motion target distance and publish it
auto motion_target = getMotionTarget(params_->motion_target_dist, transformed_plan);
auto motion_target_point = nav2_graceful_controller::createMotionTargetMsg(motion_target);
motion_target_pub_->publish(motion_target_point);
// Publish marker for slowdown radius around motion target for debugging / visualization
auto slowdown_marker = nav2_graceful_controller::createSlowdownMarker(
motion_target,
params_->slowdown_radius);
slowdown_pub_->publish(slowdown_marker);
// Compute distance to goal as the path's integrated distance to account for path curvatures
double dist_to_goal = nav2_util::geometry_utils::calculate_path_length(transformed_plan);
// If the distance to the goal is less than the motion target distance, i.e.
// the 'motion target' is the goal, then we skip the motion target orientation by pointing
// it in the same orientation that the last segment of the path
double angle_to_target = atan2(motion_target.pose.position.y, motion_target.pose.position.x);
if (params_->final_rotation && dist_to_goal < params_->motion_target_dist) {
geometry_msgs::msg::PoseStamped stl_pose =
transformed_plan.poses[transformed_plan.poses.size() - 2];
geometry_msgs::msg::PoseStamped goal_pose = transformed_plan.poses.back();
double dx = goal_pose.pose.position.x - stl_pose.pose.position.x;
double dy = goal_pose.pose.position.y - stl_pose.pose.position.y;
double yaw = std::atan2(dy, dx);
motion_target.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(yaw);
}
// Flip the orientation of the motion target if the robot is moving backwards
bool reversing = false;
if (params_->allow_backward && motion_target.pose.position.x < 0.0) {
reversing = true;
motion_target.pose.orientation = nav2_util::geometry_utils::orientationAroundZAxis(
tf2::getYaw(motion_target.pose.orientation) + M_PI);
}
// Compute velocity command:
// 1. Check if we are close enough to the goal to do a final rotation in place
// 2. Check if we must do a rotation in place before moving
// 3. Calculate the new velocity command using the smooth control law
geometry_msgs::msg::TwistStamped cmd_vel;
cmd_vel.header = pose.header;
if (params_->final_rotation && (dist_to_goal < goal_dist_tolerance_ || goal_reached_)) {
goal_reached_ = true;
double angle_to_goal = tf2::getYaw(transformed_plan.poses.back().pose.orientation);
cmd_vel.twist = rotateToTarget(angle_to_goal);
} else if (params_->initial_rotation && // NOLINT
fabs(angle_to_target) > params_->initial_rotation_min_angle)
{
cmd_vel.twist = rotateToTarget(angle_to_target);
} else {
cmd_vel.twist = control_law_->calculateRegularVelocity(motion_target.pose, reversing);
}
// Transform local frame to global frame to use in collision checking
geometry_msgs::msg::TransformStamped costmap_transform;
try {
costmap_transform = tf_buffer_->lookupTransform(
costmap_ros_->getGlobalFrameID(), costmap_ros_->getBaseFrameID(),
tf2::TimePointZero);
} catch (tf2::TransformException & ex) {
RCLCPP_ERROR(
logger_, "Could not transform %s to %s: %s",
costmap_ros_->getBaseFrameID().c_str(), costmap_ros_->getGlobalFrameID().c_str(),
ex.what());
return cmd_vel;
}
// Generate and publish local plan for debugging / visualization
nav_msgs::msg::Path local_plan;
if (!simulateTrajectory(pose, motion_target, costmap_transform, local_plan, reversing)) {
throw nav2_core::PlannerException("Collision detected in the trajectory");
}
local_plan.header = transformed_plan.header;
local_plan_pub_->publish(local_plan);
return cmd_vel;
}
void GracefulController::setPlan(const nav_msgs::msg::Path & path)
{
path_handler_->setPlan(path);
goal_reached_ = false;
}
void GracefulController::setSpeedLimit(
const double & speed_limit, const bool & percentage)
{
std::lock_guard<std::mutex> param_lock(param_handler_->getMutex());
if (speed_limit == nav2_costmap_2d::NO_SPEED_LIMIT) {
params_->v_linear_max = params_->v_linear_max_initial;
params_->v_angular_max = params_->v_angular_max_initial;
} else {
if (percentage) {
// Speed limit is expressed in % from maximum speed of robot
params_->v_linear_max = std::max(
params_->v_linear_max_initial * speed_limit / 100.0, params_->v_linear_min);
params_->v_angular_max = params_->v_angular_max_initial * speed_limit / 100.0;
} else {
// Speed limit is expressed in m/s
params_->v_linear_max = std::max(speed_limit, params_->v_linear_min);
// Limit the angular velocity to be proportional to the linear velocity
params_->v_angular_max = params_->v_angular_max_initial *
speed_limit / params_->v_linear_max_initial;
}
}
}
geometry_msgs::msg::PoseStamped GracefulController::getMotionTarget(
const double & motion_target_dist,
const nav_msgs::msg::Path & transformed_plan)
{
// Find the first pose which is at a distance greater than the motion target distance
auto goal_pose_it = std::find_if(
transformed_plan.poses.begin(), transformed_plan.poses.end(), [&](const auto & ps) {
return std::hypot(ps.pose.position.x, ps.pose.position.y) >= motion_target_dist;
});
// If the pose is not far enough, take the last pose
if (goal_pose_it == transformed_plan.poses.end()) {
goal_pose_it = std::prev(transformed_plan.poses.end());
}
return *goal_pose_it;
}
bool GracefulController::simulateTrajectory(
const geometry_msgs::msg::PoseStamped & robot_pose,
const geometry_msgs::msg::PoseStamped & motion_target,
const geometry_msgs::msg::TransformStamped & costmap_transform,
nav_msgs::msg::Path & trajectory, const bool & backward)
{
// Check for collision before moving
if (inCollision(
robot_pose.pose.position.x, robot_pose.pose.position.y,
tf2::getYaw(robot_pose.pose.orientation)))
{
return false;
}
// First pose
geometry_msgs::msg::PoseStamped next_pose;
next_pose.header.frame_id = costmap_ros_->getBaseFrameID();
next_pose.pose.orientation.w = 1.0;
trajectory.poses.push_back(next_pose);
double distance = std::numeric_limits<double>::max();
double resolution_ = costmap_ros_->getCostmap()->getResolution();
double dt = (params_->v_linear_max > 0.0) ? resolution_ / params_->v_linear_max : 0.0;
// Set max iter to avoid infinite loop
unsigned int max_iter = 2 * sqrt(
motion_target.pose.position.x * motion_target.pose.position.x +
motion_target.pose.position.y * motion_target.pose.position.y) / resolution_;
// Generate path
do{
// Apply velocities to calculate next pose
next_pose.pose = control_law_->calculateNextPose(
dt, motion_target.pose, next_pose.pose, backward);
// Add the pose to the trajectory for visualization
trajectory.poses.push_back(next_pose);
// Check for collision
geometry_msgs::msg::PoseStamped global_pose;
tf2::doTransform(next_pose, global_pose, costmap_transform);
if (inCollision(
global_pose.pose.position.x, global_pose.pose.position.y,
tf2::getYaw(global_pose.pose.orientation)))
{
return false;
}
// Check if we reach the goal
distance = nav2_util::geometry_utils::euclidean_distance(motion_target.pose, next_pose.pose);
}while(distance > resolution_ && trajectory.poses.size() < max_iter);
return true;
}
geometry_msgs::msg::Twist GracefulController::rotateToTarget(const double & angle_to_target)
{
geometry_msgs::msg::Twist vel;
vel.linear.x = 0.0;
vel.angular.z = params_->rotation_scaling_factor * angle_to_target * params_->v_angular_max;
return vel;
}
bool GracefulController::inCollision(const double & x, const double & y, const double & theta)
{
unsigned int mx, my;
if (!costmap_ros_->getCostmap()->worldToMap(x, y, mx, my)) {
RCLCPP_WARN(
logger_, "The path is not in the costmap. Cannot check for collisions. "
"Proceed at your own risk, slow the robot, or increase your costmap size.");
return false;
}
// Calculate the cost of the footprint at the robot's current position depending
// on the shape of the footprint
bool is_tracking_unknown =
costmap_ros_->getLayeredCostmap()->isTrackingUnknown();
bool consider_footprint = !costmap_ros_->getUseRadius();
double footprint_cost;
if (consider_footprint) {
footprint_cost = collision_checker_->footprintCostAtPose(
x, y, theta, costmap_ros_->getRobotFootprint());
} else {
footprint_cost = collision_checker_->pointCost(mx, my);
}
switch (static_cast<unsigned char>(footprint_cost)) {
case (nav2_costmap_2d::LETHAL_OBSTACLE):
return true;
case (nav2_costmap_2d::INSCRIBED_INFLATED_OBSTACLE):
return consider_footprint ? false : true;
case (nav2_costmap_2d::NO_INFORMATION):
return is_tracking_unknown ? false : true;
}
return false;
}
} // namespace nav2_graceful_controller
// Register this controller as a nav2_core plugin
PLUGINLIB_EXPORT_CLASS(
nav2_graceful_controller::GracefulController,
nav2_core::Controller)
@@ -0,0 +1,171 @@
// Copyright (c) 2023 Alberto J. Tudela Roldán
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <algorithm>
#include <string>
#include <limits>
#include <memory>
#include <vector>
#include <utility>
#include "nav2_graceful_controller/parameter_handler.hpp"
namespace nav2_graceful_controller
{
using nav2_util::declare_parameter_if_not_declared;
using rcl_interfaces::msg::ParameterType;
ParameterHandler::ParameterHandler(
rclcpp_lifecycle::LifecycleNode::SharedPtr node, std::string & plugin_name,
rclcpp::Logger & logger, const double costmap_size_x)
{
plugin_name_ = plugin_name;
logger_ = logger;
declare_parameter_if_not_declared(
node, plugin_name_ + ".transform_tolerance", rclcpp::ParameterValue(0.1));
declare_parameter_if_not_declared(
node, plugin_name_ + ".motion_target_dist", rclcpp::ParameterValue(0.6));
declare_parameter_if_not_declared(
node, plugin_name_ + ".max_robot_pose_search_dist",
rclcpp::ParameterValue(costmap_size_x / 2.0));
declare_parameter_if_not_declared(node, plugin_name_ + ".k_phi", rclcpp::ParameterValue(3.0));
declare_parameter_if_not_declared(node, plugin_name_ + ".k_delta", rclcpp::ParameterValue(2.0));
declare_parameter_if_not_declared(node, plugin_name_ + ".beta", rclcpp::ParameterValue(0.2));
declare_parameter_if_not_declared(node, plugin_name_ + ".lambda", rclcpp::ParameterValue(2.0));
declare_parameter_if_not_declared(
node, plugin_name_ + ".v_linear_min", rclcpp::ParameterValue(0.1));
declare_parameter_if_not_declared(
node, plugin_name_ + ".v_linear_max", rclcpp::ParameterValue(0.5));
declare_parameter_if_not_declared(
node, plugin_name_ + ".v_angular_max", rclcpp::ParameterValue(1.0));
declare_parameter_if_not_declared(
node, plugin_name_ + ".slowdown_radius", rclcpp::ParameterValue(1.5));
declare_parameter_if_not_declared(
node, plugin_name_ + ".initial_rotation", rclcpp::ParameterValue(true));
declare_parameter_if_not_declared(
node, plugin_name_ + ".initial_rotation_min_angle", rclcpp::ParameterValue(0.75));
declare_parameter_if_not_declared(
node, plugin_name_ + ".final_rotation", rclcpp::ParameterValue(true));
declare_parameter_if_not_declared(
node, plugin_name_ + ".rotation_scaling_factor", rclcpp::ParameterValue(0.5));
declare_parameter_if_not_declared(
node, plugin_name_ + ".allow_backward", rclcpp::ParameterValue(false));
node->get_parameter(plugin_name_ + ".transform_tolerance", params_.transform_tolerance);
node->get_parameter(plugin_name_ + ".motion_target_dist", params_.motion_target_dist);
node->get_parameter(
plugin_name_ + ".max_robot_pose_search_dist", params_.max_robot_pose_search_dist);
if (params_.max_robot_pose_search_dist < 0.0) {
RCLCPP_WARN(
logger_, "Max robot search distance is negative, setting to max to search"
" every point on path for the closest value.");
params_.max_robot_pose_search_dist = std::numeric_limits<double>::max();
}
node->get_parameter(plugin_name_ + ".k_phi", params_.k_phi);
node->get_parameter(plugin_name_ + ".k_delta", params_.k_delta);
node->get_parameter(plugin_name_ + ".beta", params_.beta);
node->get_parameter(plugin_name_ + ".lambda", params_.lambda);
node->get_parameter(plugin_name_ + ".v_linear_min", params_.v_linear_min);
node->get_parameter(plugin_name_ + ".v_linear_max", params_.v_linear_max);
params_.v_linear_max_initial = params_.v_linear_max;
node->get_parameter(plugin_name_ + ".v_angular_max", params_.v_angular_max);
params_.v_angular_max_initial = params_.v_angular_max;
node->get_parameter(plugin_name_ + ".slowdown_radius", params_.slowdown_radius);
node->get_parameter(plugin_name_ + ".initial_rotation", params_.initial_rotation);
node->get_parameter(
plugin_name_ + ".initial_rotation_min_angle", params_.initial_rotation_min_angle);
node->get_parameter(plugin_name_ + ".final_rotation", params_.final_rotation);
node->get_parameter(plugin_name_ + ".rotation_scaling_factor", params_.rotation_scaling_factor);
node->get_parameter(plugin_name_ + ".allow_backward", params_.allow_backward);
if (params_.initial_rotation && params_.allow_backward) {
RCLCPP_WARN(
logger_, "Initial rotation and allow backward parameters are both true, "
"setting allow backward to false.");
params_.allow_backward = false;
}
dyn_params_handler_ = node->add_on_set_parameters_callback(
std::bind(&ParameterHandler::dynamicParametersCallback, this, std::placeholders::_1));
}
rcl_interfaces::msg::SetParametersResult
ParameterHandler::dynamicParametersCallback(std::vector<rclcpp::Parameter> parameters)
{
rcl_interfaces::msg::SetParametersResult result;
std::lock_guard<std::mutex> lock_reinit(mutex_);
for (auto parameter : parameters) {
const auto & type = parameter.get_type();
const auto & name = parameter.get_name();
if (type == ParameterType::PARAMETER_DOUBLE) {
if (name == plugin_name_ + ".transform_tolerance") {
params_.transform_tolerance = parameter.as_double();
} else if (name == plugin_name_ + ".motion_target_dist") {
params_.motion_target_dist = parameter.as_double();
} else if (name == plugin_name_ + ".k_phi") {
params_.k_phi = parameter.as_double();
} else if (name == plugin_name_ + ".k_delta") {
params_.k_delta = parameter.as_double();
} else if (name == plugin_name_ + ".beta") {
params_.beta = parameter.as_double();
} else if (name == plugin_name_ + ".lambda") {
params_.lambda = parameter.as_double();
} else if (name == plugin_name_ + ".v_linear_min") {
params_.v_linear_min = parameter.as_double();
} else if (name == plugin_name_ + ".v_linear_max") {
params_.v_linear_max = parameter.as_double();
params_.v_linear_max_initial = params_.v_linear_max;
} else if (name == plugin_name_ + ".v_angular_max") {
params_.v_angular_max = parameter.as_double();
params_.v_angular_max_initial = params_.v_angular_max;
} else if (name == plugin_name_ + ".slowdown_radius") {
params_.slowdown_radius = parameter.as_double();
} else if (name == plugin_name_ + ".initial_rotation_min_angle") {
params_.initial_rotation_min_angle = parameter.as_double();
} else if (name == plugin_name_ + ".rotation_scaling_factor") {
params_.rotation_scaling_factor = parameter.as_double();
}
} else if (type == ParameterType::PARAMETER_BOOL) {
if (name == plugin_name_ + ".initial_rotation") {
if (parameter.as_bool() && params_.allow_backward) {
RCLCPP_WARN(
logger_, "Initial rotation and allow backward parameters are both true, "
"rejecting parameter change.");
continue;
}
params_.initial_rotation = parameter.as_bool();
} else if (name == plugin_name_ + ".final_rotation") {
params_.final_rotation = parameter.as_bool();
} else if (name == plugin_name_ + ".allow_backward") {
if (params_.initial_rotation && parameter.as_bool()) {
RCLCPP_WARN(
logger_, "Initial rotation and allow backward parameters are both true, "
"rejecting parameter change.");
continue;
}
params_.allow_backward = parameter.as_bool();
}
}
}
result.successful = true;
return result;
}
} // namespace nav2_graceful_controller
@@ -0,0 +1,126 @@
// Copyright (c) 2022 Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <algorithm>
#include <string>
#include <limits>
#include <memory>
#include <vector>
#include <utility>
#include "nav2_util/node_utils.hpp"
#include "nav2_util/geometry_utils.hpp"
#include "nav_2d_utils/tf_help.hpp"
#include "nav2_graceful_controller/path_handler.hpp"
#include "nav2_core/exceptions.hpp"
namespace nav2_graceful_controller
{
using nav2_util::geometry_utils::euclidean_distance;
PathHandler::PathHandler(
tf2::Duration transform_tolerance,
std::shared_ptr<tf2_ros::Buffer> tf,
std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
: transform_tolerance_(transform_tolerance), tf_buffer_(tf), costmap_ros_(costmap_ros)
{
}
nav_msgs::msg::Path PathHandler::transformGlobalPlan(
const geometry_msgs::msg::PoseStamped & pose,
double max_robot_pose_search_dist)
{
// Check first if the plan is empty
if (global_plan_.poses.empty()) {
throw nav2_core::PlannerException("Received plan with zero length");
}
// Let's get the pose of the robot in the frame of the plan
geometry_msgs::msg::PoseStamped robot_pose;
if (!nav_2d_utils::transformPose(
tf_buffer_, global_plan_.header.frame_id, pose, robot_pose,
transform_tolerance_))
{
throw nav2_core::PlannerException("Unable to transform robot pose into global plan's frame");
}
// Find the first pose in the global plan that's further than max_robot_pose_search_dist
// from the robot using integrated distance
auto closest_pose_upper_bound =
nav2_util::geometry_utils::first_after_integrated_distance(
global_plan_.poses.begin(), global_plan_.poses.end(), max_robot_pose_search_dist);
// First find the closest pose on the path to the robot
// bounded by when the path turns around (if it does) so we don't get a pose from a later
// portion of the path
auto transformation_begin =
nav2_util::geometry_utils::min_by(
global_plan_.poses.begin(), closest_pose_upper_bound,
[&robot_pose](const geometry_msgs::msg::PoseStamped & ps) {
return euclidean_distance(robot_pose, ps);
});
// We'll discard points on the plan that are outside the local costmap
double dist_threshold = std::max(
costmap_ros_->getCostmap()->getSizeInMetersX(),
costmap_ros_->getCostmap()->getSizeInMetersY()) / 2.0;
auto transformation_end = std::find_if(
transformation_begin, global_plan_.poses.end(),
[&](const auto & global_plan_pose) {
return euclidean_distance(global_plan_pose, robot_pose) > dist_threshold;
});
// Lambda to transform a PoseStamped from global frame to local
auto transformGlobalPoseToLocal = [&](const auto & global_plan_pose) {
geometry_msgs::msg::PoseStamped stamped_pose, transformed_pose;
stamped_pose.header.frame_id = global_plan_.header.frame_id;
stamped_pose.header.stamp = robot_pose.header.stamp;
stamped_pose.pose = global_plan_pose.pose;
if (!nav_2d_utils::transformPose(
tf_buffer_, costmap_ros_->getBaseFrameID(), stamped_pose,
transformed_pose, transform_tolerance_))
{
throw nav2_core::PlannerException("Unable to transform plan pose into local frame");
}
transformed_pose.pose.position.z = 0.0;
return transformed_pose;
};
// Transform the near part of the global plan into the robot's frame of reference.
nav_msgs::msg::Path transformed_plan;
transformed_plan.header.frame_id = costmap_ros_->getBaseFrameID();
transformed_plan.header.stamp = robot_pose.header.stamp;
std::transform(
transformation_begin, transformation_end,
std::back_inserter(transformed_plan.poses),
transformGlobalPoseToLocal);
// Remove the portion of the global plan that we've already passed so we don't
// process it on the next iteration (this is called path pruning)
global_plan_.poses.erase(begin(global_plan_.poses), transformation_begin);
if (transformed_plan.poses.empty()) {
throw nav2_core::PlannerException("Resulting plan has 0 poses in it.");
}
return transformed_plan;
}
void PathHandler::setPlan(const nav_msgs::msg::Path & path)
{
global_plan_ = path;
}
} // namespace nav2_graceful_controller
@@ -0,0 +1,125 @@
// Copyright (c) 2023 Alberto J. Tudela Roldán
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "geometry_msgs/msg/pose_stamped.hpp"
#include "nav2_util/geometry_utils.hpp"
#include "nav2_graceful_controller/ego_polar_coords.hpp"
#include "nav2_graceful_controller/smooth_control_law.hpp"
namespace nav2_graceful_controller
{
SmoothControlLaw::SmoothControlLaw(
double k_phi, double k_delta, double beta, double lambda, double slowdown_radius,
double v_linear_min, double v_linear_max, double v_angular_max)
: k_phi_(k_phi), k_delta_(k_delta), beta_(beta), lambda_(lambda), slowdown_radius_(slowdown_radius),
v_linear_min_(v_linear_min), v_linear_max_(v_linear_max), v_angular_max_(v_angular_max)
{
}
void SmoothControlLaw::setCurvatureConstants(
double k_phi, double k_delta, double beta, double lambda)
{
k_phi_ = k_phi;
k_delta_ = k_delta;
beta_ = beta;
lambda_ = lambda;
}
void SmoothControlLaw::setSlowdownRadius(double slowdown_radius)
{
slowdown_radius_ = slowdown_radius;
}
void SmoothControlLaw::setSpeedLimit(
const double v_linear_min, const double v_linear_max, const double v_angular_max)
{
v_linear_min_ = v_linear_min;
v_linear_max_ = v_linear_max;
v_angular_max_ = v_angular_max;
}
geometry_msgs::msg::Twist SmoothControlLaw::calculateRegularVelocity(
const geometry_msgs::msg::Pose & target, const geometry_msgs::msg::Pose & current,
const bool & backward)
{
// Convert the target to polar coordinates
auto ego_coords = EgocentricPolarCoordinates(target, current, backward);
// Calculate the curvature
double curvature = calculateCurvature(ego_coords.r, ego_coords.phi, ego_coords.delta);
// Invert the curvature if the robot is moving backwards
curvature = backward ? -curvature : curvature;
// Adjust the linear velocity as a function of the path curvature to
// slowdown the controller as it approaches its target
double v = v_linear_max_ / (1.0 + beta_ * std::pow(fabs(curvature), lambda_));
// Slowdown when the robot is near the target to remove singularity
v = std::min(v_linear_max_ * (ego_coords.r / slowdown_radius_), v);
// Set some small v_min when far away from origin to promote faster
// turning motion when the curvature is very high
v = std::clamp(v, v_linear_min_, v_linear_max_);
// Set the velocity to negative if the robot is moving backwards
v = backward ? -v : v;
// Compute the angular velocity
double w = curvature * v;
// Bound angular velocity between [-max_angular_vel, max_angular_vel]
double w_bound = std::clamp(w, -v_angular_max_, v_angular_max_);
// And linear velocity to follow the curvature
v = (curvature != 0.0) ? (w_bound / curvature) : v;
// Return the velocity command
geometry_msgs::msg::Twist cmd_vel;
cmd_vel.linear.x = v;
cmd_vel.angular.z = w_bound;
return cmd_vel;
}
geometry_msgs::msg::Twist SmoothControlLaw::calculateRegularVelocity(
const geometry_msgs::msg::Pose & target, const bool & backward)
{
return calculateRegularVelocity(target, geometry_msgs::msg::Pose(), backward);
}
geometry_msgs::msg::Pose SmoothControlLaw::calculateNextPose(
const double dt,
const geometry_msgs::msg::Pose & target,
const geometry_msgs::msg::Pose & current,
const bool & backward)
{
geometry_msgs::msg::Twist vel = calculateRegularVelocity(target, current, backward);
geometry_msgs::msg::Pose next;
double yaw = tf2::getYaw(current.orientation);
next.position.x = current.position.x + vel.linear.x * dt * cos(yaw);
next.position.y = current.position.y + vel.linear.x * dt * sin(yaw);
yaw += vel.angular.z * dt;
next.orientation = nav2_util::geometry_utils::orientationAroundZAxis(yaw);
return next;
}
double SmoothControlLaw::calculateCurvature(double r, double phi, double delta)
{
// Calculate the proportional term of the control law as the product of the gain and the error:
// difference between the actual steering angle and the virtual control for the slow subsystem
double prop_term = k_delta_ * (delta - std::atan(-k_phi_ * phi));
// Calculate the feedback control law for the steering
double feedback_term = (1.0 + (k_phi_ / (1.0 + std::pow(k_phi_ * phi, 2)))) * sin(delta);
// Calculate the path curvature
return -1.0 / r * (prop_term + feedback_term);
}
} // namespace nav2_graceful_controller
@@ -0,0 +1,51 @@
// Copyright (c) 2023 Alberto J. Tudela Roldán
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "nav2_graceful_controller/utils.hpp"
namespace nav2_graceful_controller
{
geometry_msgs::msg::PointStamped createMotionTargetMsg(
const geometry_msgs::msg::PoseStamped & motion_target)
{
geometry_msgs::msg::PointStamped motion_target_point;
motion_target_point.header = motion_target.header;
motion_target_point.point = motion_target.pose.position;
motion_target_point.point.z = 0.01;
return motion_target_point;
}
visualization_msgs::msg::Marker createSlowdownMarker(
const geometry_msgs::msg::PoseStamped & motion_target, const double & slowdown_radius)
{
visualization_msgs::msg::Marker slowdown_marker;
slowdown_marker.header = motion_target.header;
slowdown_marker.ns = "slowdown";
slowdown_marker.id = 0;
slowdown_marker.type = visualization_msgs::msg::Marker::SPHERE;
slowdown_marker.action = visualization_msgs::msg::Marker::ADD;
slowdown_marker.pose = motion_target.pose;
slowdown_marker.pose.position.z = 0.01;
slowdown_marker.scale.x = slowdown_radius * 2.0;
slowdown_marker.scale.y = slowdown_radius * 2.0;
slowdown_marker.scale.z = 0.02;
slowdown_marker.color.a = 0.2;
slowdown_marker.color.r = 0.0;
slowdown_marker.color.g = 1.0;
slowdown_marker.color.b = 0.0;
return slowdown_marker;
}
} // namespace nav2_graceful_controller