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,408 @@
// Copyright (c) 2021 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_rotation_shim_controller/nav2_rotation_shim_controller.hpp"
using rcl_interfaces::msg::ParameterType;
namespace nav2_rotation_shim_controller
{
RotationShimController::RotationShimController()
: lp_loader_("nav2_core", "nav2_core::Controller"),
primary_controller_(nullptr),
path_updated_(false),
in_rotation_(false)
{
}
void RotationShimController::configure(
const rclcpp_lifecycle::LifecycleNode::WeakPtr & parent,
std::string name, std::shared_ptr<tf2_ros::Buffer> tf,
std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
{
position_goal_checker_ = std::make_unique<nav2_controller::PositionGoalChecker>();
position_goal_checker_->initialize(parent, plugin_name_ + ".position_checker", costmap_ros);
plugin_name_ = name;
node_ = parent;
auto node = parent.lock();
tf_ = tf;
costmap_ros_ = costmap_ros;
logger_ = node->get_logger();
clock_ = node->get_clock();
std::string primary_controller;
double control_frequency;
nav2_util::declare_parameter_if_not_declared(
node, plugin_name_ + ".angular_dist_threshold", rclcpp::ParameterValue(0.785)); // 45 deg
nav2_util::declare_parameter_if_not_declared(
node, plugin_name_ + ".angular_disengage_threshold", rclcpp::ParameterValue(0.785));
nav2_util::declare_parameter_if_not_declared(
node, plugin_name_ + ".forward_sampling_distance", rclcpp::ParameterValue(0.5));
nav2_util::declare_parameter_if_not_declared(
node, plugin_name_ + ".rotate_to_heading_angular_vel", rclcpp::ParameterValue(1.8));
nav2_util::declare_parameter_if_not_declared(
node, plugin_name_ + ".max_angular_accel", rclcpp::ParameterValue(3.2));
nav2_util::declare_parameter_if_not_declared(
node, plugin_name_ + ".simulate_ahead_time", rclcpp::ParameterValue(1.0));
nav2_util::declare_parameter_if_not_declared(
node, plugin_name_ + ".primary_controller", rclcpp::PARAMETER_STRING);
nav2_util::declare_parameter_if_not_declared(
node, plugin_name_ + ".rotate_to_goal_heading", rclcpp::ParameterValue(false));
nav2_util::declare_parameter_if_not_declared(
node, plugin_name_ + ".closed_loop", rclcpp::ParameterValue(true));
node->get_parameter(plugin_name_ + ".angular_dist_threshold", angular_dist_threshold_);
node->get_parameter(plugin_name_ + ".angular_disengage_threshold", angular_disengage_threshold_);
node->get_parameter(plugin_name_ + ".forward_sampling_distance", forward_sampling_distance_);
node->get_parameter(
plugin_name_ + ".rotate_to_heading_angular_vel",
rotate_to_heading_angular_vel_);
node->get_parameter(plugin_name_ + ".max_angular_accel", max_angular_accel_);
node->get_parameter(plugin_name_ + ".simulate_ahead_time", simulate_ahead_time_);
primary_controller = node->get_parameter(plugin_name_ + ".primary_controller").as_string();
node->get_parameter("controller_frequency", control_frequency);
control_duration_ = 1.0 / control_frequency;
node->get_parameter(plugin_name_ + ".rotate_to_goal_heading", rotate_to_goal_heading_);
node->get_parameter(plugin_name_ + ".closed_loop", closed_loop_);
try {
primary_controller_ = lp_loader_.createUniqueInstance(primary_controller);
RCLCPP_INFO(
logger_, "Created internal controller for rotation shimming: %s of type %s",
plugin_name_.c_str(), primary_controller.c_str());
} catch (const pluginlib::PluginlibException & ex) {
RCLCPP_FATAL(
logger_,
"Failed to create internal controller for rotation shimming. Exception: %s", ex.what());
return;
}
primary_controller_->configure(parent, name, tf, costmap_ros);
// initialize collision checker and set costmap
collision_checker_ = std::make_unique<nav2_costmap_2d::
FootprintCollisionChecker<nav2_costmap_2d::Costmap2D *>>(costmap_ros->getCostmap());
}
void RotationShimController::activate()
{
RCLCPP_INFO(
logger_,
"Activating controller: %s of type "
"nav2_rotation_shim_controller::RotationShimController",
plugin_name_.c_str());
primary_controller_->activate();
in_rotation_ = false;
last_angular_vel_ = std::numeric_limits<double>::max();
auto node = node_.lock();
dyn_params_handler_ = node->add_on_set_parameters_callback(
std::bind(
&RotationShimController::dynamicParametersCallback,
this, std::placeholders::_1));
position_goal_checker_->reset();
}
void RotationShimController::deactivate()
{
RCLCPP_INFO(
logger_,
"Deactivating controller: %s of type "
"nav2_rotation_shim_controller::RotationShimController",
plugin_name_.c_str());
primary_controller_->deactivate();
dyn_params_handler_.reset();
}
void RotationShimController::cleanup()
{
RCLCPP_INFO(
logger_,
"Cleaning up controller: %s of type "
"nav2_rotation_shim_controller::RotationShimController",
plugin_name_.c_str());
primary_controller_->cleanup();
primary_controller_.reset();
position_goal_checker_.reset();
}
geometry_msgs::msg::TwistStamped RotationShimController::computeVelocityCommands(
const geometry_msgs::msg::PoseStamped & pose,
const geometry_msgs::msg::Twist & velocity,
nav2_core::GoalChecker * goal_checker)
{
// Rotate to goal heading when in goal xy tolerance
if (rotate_to_goal_heading_) {
std::lock_guard<std::mutex> lock_reinit(mutex_);
try {
geometry_msgs::msg::PoseStamped sampled_pt_goal = getSampledPathGoal();
if (!nav2_util::transformPoseInTargetFrame(
sampled_pt_goal, sampled_pt_goal, *tf_,
pose.header.frame_id))
{
throw std::runtime_error("Failed to transform pose to base frame!");
}
geometry_msgs::msg::Pose pose_tolerance;
geometry_msgs::msg::Twist vel_tolerance;
goal_checker->getTolerances(pose_tolerance, vel_tolerance);
position_goal_checker_->setXYGoalTolerance(pose_tolerance.position.x);
if (position_goal_checker_->isGoalReached(pose.pose, sampled_pt_goal.pose, velocity)) {
double pose_yaw = tf2::getYaw(pose.pose.orientation);
double goal_yaw = tf2::getYaw(sampled_pt_goal.pose.orientation);
double angular_distance_to_heading = angles::shortest_angular_distance(pose_yaw, goal_yaw);
auto cmd_vel = computeRotateToHeadingCommand(angular_distance_to_heading, pose, velocity);
last_angular_vel_ = cmd_vel.twist.angular.z;
return cmd_vel;
}
} catch (const std::runtime_error & e) {
RCLCPP_INFO(
logger_,
"Rotation Shim Controller was unable to find a goal point,"
" a rotational collision was detected, or TF failed to transform"
" into base frame! what(): %s", e.what());
}
}
if (path_updated_) {
nav2_costmap_2d::Costmap2D * costmap = costmap_ros_->getCostmap();
std::unique_lock<nav2_costmap_2d::Costmap2D::mutex_t> lock(*(costmap->getMutex()));
std::lock_guard<std::mutex> lock_reinit(mutex_);
try {
geometry_msgs::msg::Pose sampled_pt_base = transformPoseToBaseFrame(getSampledPathPt());
double angular_distance_to_heading =
std::atan2(sampled_pt_base.position.y, sampled_pt_base.position.x);
double angular_thresh =
in_rotation_ ? angular_disengage_threshold_ : angular_dist_threshold_;
if (abs(angular_distance_to_heading) > angular_thresh) {
RCLCPP_DEBUG(
logger_,
"Robot is not within the new path's rough heading, rotating to heading...");
in_rotation_ = true;
auto cmd_vel = computeRotateToHeadingCommand(angular_distance_to_heading, pose, velocity);
last_angular_vel_ = cmd_vel.twist.angular.z;
return cmd_vel;
} else {
RCLCPP_DEBUG(
logger_,
"Robot is at the new path's rough heading, passing to controller");
path_updated_ = false;
}
} catch (const std::runtime_error & e) {
RCLCPP_DEBUG(
logger_,
"Rotation Shim Controller was unable to find a sampling point,"
" a rotational collision was detected, or TF failed to transform"
" into base frame! what(): %s", e.what());
path_updated_ = false;
}
}
// If at this point, use the primary controller to path track
in_rotation_ = false;
auto cmd_vel = primary_controller_->computeVelocityCommands(pose, velocity, goal_checker);
last_angular_vel_ = cmd_vel.twist.angular.z;
return cmd_vel;
}
geometry_msgs::msg::PoseStamped RotationShimController::getSampledPathPt()
{
if (current_path_.poses.size() < 2) {
throw nav2_core::PlannerException(
"Path is too short to find a valid sampled path point for rotation.");
}
geometry_msgs::msg::Pose start = current_path_.poses.front().pose;
double dx, dy;
// Find the first point at least sampling distance away
for (unsigned int i = 1; i != current_path_.poses.size(); i++) {
dx = current_path_.poses[i].pose.position.x - start.position.x;
dy = current_path_.poses[i].pose.position.y - start.position.y;
if (hypot(dx, dy) >= forward_sampling_distance_) {
current_path_.poses[i].header.frame_id = current_path_.header.frame_id;
current_path_.poses[i].header.stamp = clock_->now(); // Get current time transformation
return current_path_.poses[i];
}
}
throw nav2_core::PlannerException(
std::string(
"Unable to find a sampling point at least %0.2f from the robot,"
"passing off to primary controller plugin.", forward_sampling_distance_));
}
geometry_msgs::msg::PoseStamped RotationShimController::getSampledPathGoal()
{
if (current_path_.poses.empty()) {
throw std::runtime_error("Path is empty - cannot find a goal point");
}
auto goal = current_path_.poses.back();
goal.header.frame_id = current_path_.header.frame_id;
goal.header.stamp = clock_->now();
return goal;
}
geometry_msgs::msg::Pose
RotationShimController::transformPoseToBaseFrame(const geometry_msgs::msg::PoseStamped & pt)
{
geometry_msgs::msg::PoseStamped pt_base;
if (!nav2_util::transformPoseInTargetFrame(pt, pt_base, *tf_, costmap_ros_->getBaseFrameID())) {
throw nav2_core::PlannerException("Failed to transform pose to base frame!");
}
return pt_base.pose;
}
geometry_msgs::msg::TwistStamped
RotationShimController::computeRotateToHeadingCommand(
const double & angular_distance_to_heading,
const geometry_msgs::msg::PoseStamped & pose,
const geometry_msgs::msg::Twist & velocity)
{
auto current = closed_loop_ ? velocity.angular.z : last_angular_vel_;
if (current == std::numeric_limits<double>::max()) {
current = 0.0;
}
geometry_msgs::msg::TwistStamped cmd_vel;
cmd_vel.header = pose.header;
const double sign = angular_distance_to_heading > 0.0 ? 1.0 : -1.0;
const double angular_vel = sign * rotate_to_heading_angular_vel_;
const double & dt = control_duration_;
const double min_feasible_angular_speed = current - max_angular_accel_ * dt;
const double max_feasible_angular_speed = current + max_angular_accel_ * dt;
cmd_vel.twist.angular.z =
std::clamp(angular_vel, min_feasible_angular_speed, max_feasible_angular_speed);
isCollisionFree(cmd_vel, angular_distance_to_heading, pose);
return cmd_vel;
}
void RotationShimController::isCollisionFree(
const geometry_msgs::msg::TwistStamped & cmd_vel,
const double & angular_distance_to_heading,
const geometry_msgs::msg::PoseStamped & pose)
{
// Simulate rotation ahead by time in control frequency increments
double simulated_time = 0.0;
double initial_yaw = tf2::getYaw(pose.pose.orientation);
double yaw = 0.0;
double footprint_cost = 0.0;
double remaining_rotation_before_thresh =
fabs(angular_distance_to_heading) - angular_dist_threshold_;
while (simulated_time < simulate_ahead_time_) {
simulated_time += control_duration_;
yaw = initial_yaw + cmd_vel.twist.angular.z * simulated_time;
// Stop simulating past the point it would be passed onto the primary controller
if (angles::shortest_angular_distance(yaw, initial_yaw) >= remaining_rotation_before_thresh) {
break;
}
using namespace nav2_costmap_2d; // NOLINT
footprint_cost = collision_checker_->footprintCostAtPose(
pose.pose.position.x, pose.pose.position.y,
yaw, costmap_ros_->getRobotFootprint());
if (footprint_cost == static_cast<double>(NO_INFORMATION) &&
costmap_ros_->getLayeredCostmap()->isTrackingUnknown())
{
throw std::runtime_error("RotationShimController detected a potential collision ahead!");
}
if (footprint_cost >= static_cast<double>(LETHAL_OBSTACLE)) {
throw std::runtime_error("RotationShimController detected collision ahead!");
}
}
}
void RotationShimController::setPlan(const nav_msgs::msg::Path & path)
{
path_updated_ = true;
current_path_ = path;
primary_controller_->setPlan(path);
position_goal_checker_->reset();
}
void RotationShimController::setSpeedLimit(const double & speed_limit, const bool & percentage)
{
primary_controller_->setSpeedLimit(speed_limit, percentage);
}
rcl_interfaces::msg::SetParametersResult
RotationShimController::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_ + ".angular_dist_threshold") {
angular_dist_threshold_ = parameter.as_double();
} else if (name == plugin_name_ + ".forward_sampling_distance") {
forward_sampling_distance_ = parameter.as_double();
} else if (name == plugin_name_ + ".rotate_to_heading_angular_vel") {
rotate_to_heading_angular_vel_ = parameter.as_double();
} else if (name == plugin_name_ + ".max_angular_accel") {
max_angular_accel_ = parameter.as_double();
} else if (name == plugin_name_ + ".simulate_ahead_time") {
simulate_ahead_time_ = parameter.as_double();
}
} else if (type == ParameterType::PARAMETER_BOOL) {
if (name == plugin_name_ + ".rotate_to_goal_heading") {
rotate_to_goal_heading_ = parameter.as_bool();
} else if (name == plugin_name_ + ".closed_loop") {
closed_loop_ = parameter.as_bool();
}
}
}
result.successful = true;
return result;
}
} // namespace nav2_rotation_shim_controller
// Register this controller as a nav2_core plugin
PLUGINLIB_EXPORT_CLASS(
nav2_rotation_shim_controller::RotationShimController,
nav2_core::Controller)