21 #include "angles/angles.h"
22 #include "nav2_util/geometry_utils.hpp"
23 #include "nav2_ros_common/node_utils.hpp"
24 #include "nav2_util/robot_utils.hpp"
26 #include "nav2_rotation_shim_controller/nav2_rotation_shim_controller.hpp"
27 #include "nav2_ros_common/tf2_factories.hpp"
29 using rcl_interfaces::msg::ParameterType;
31 namespace nav2_rotation_shim_controller
35 : lp_loader_(
"nav2_core",
"nav2_core::Controller"),
36 primary_controller_(nullptr),
43 const nav2::LifecycleNode::WeakPtr & parent,
44 std::string name, nav2::TransformBuffer::SharedPtr tf,
45 std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
49 auto node = parent.lock();
52 costmap_ros_ = costmap_ros;
53 logger_ = node->get_logger();
54 clock_ = node->get_clock();
58 param_handler_ = std::make_unique<ParameterHandler>(
59 node, plugin_name_, logger_);
60 params_ = param_handler_->getParams();
63 primary_controller_ = lp_loader_.createUniqueInstance(params_->primary_controller);
65 logger_,
"Created internal controller for rotation shimming: %s of type %s",
66 plugin_name_.c_str(), params_->primary_controller.c_str());
67 }
catch (
const pluginlib::PluginlibException & ex) {
70 "Failed to create internal controller for rotation shimming. Exception: %s", ex.what());
74 primary_controller_->configure(parent, name +
".primary_controller", tf, costmap_ros);
77 collision_checker_ = std::make_unique<nav2_costmap_2d::
78 FootprintCollisionChecker<nav2_costmap_2d::Costmap2D *>>(costmap_ros->getCostmap());
85 "Activating controller: %s of type "
86 "nav2_rotation_shim_controller::RotationShimController",
87 plugin_name_.c_str());
89 primary_controller_->activate();
91 last_angular_vel_ = std::numeric_limits<double>::max();
92 param_handler_->activate();
99 "Deactivating controller: %s of type "
100 "nav2_rotation_shim_controller::RotationShimController",
101 plugin_name_.c_str());
103 primary_controller_->deactivate();
104 param_handler_->deactivate();
111 "Cleaning up controller: %s of type "
112 "nav2_rotation_shim_controller::RotationShimController",
113 plugin_name_.c_str());
115 primary_controller_->cleanup();
116 primary_controller_.reset();
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 current_path_ = transformed_global_plan;
127 path_updated_ = params_->rotate_to_heading_once ?
isGoalChanged(global_goal) :
true;
128 current_goal_ = global_goal;
130 if (params_->rotate_to_goal_heading) {
131 std::lock_guard<std::mutex> lock_reinit(param_handler_->getMutex());
134 if (goal_checker->
isGoalXYReached(pose.pose, global_goal.pose, velocity,
135 transformed_global_plan))
137 double pose_yaw = tf2::getYaw(pose.pose.orientation);
138 double goal_yaw = tf2::getYaw(global_goal.pose.orientation);
140 double angular_distance_to_heading = angles::shortest_angular_distance(pose_yaw, goal_yaw);
143 last_angular_vel_ = cmd_vel.twist.angular.z;
146 }
catch (
const std::runtime_error & e) {
149 "Rotation Shim Controller was unable to find a goal point,"
150 " a rotational collision was detected, or TF failed to transform"
151 " into base frame! what(): %s", e.what());
157 std::unique_lock<nav2_costmap_2d::Costmap2D::mutex_t> lock(*(costmap->getMutex()));
159 std::lock_guard<std::mutex> lock_reinit(param_handler_->getMutex());
162 double angular_distance_to_heading;
163 if (params_->use_path_orientations) {
164 angular_distance_to_heading = angles::shortest_angular_distance(
165 tf2::getYaw(pose.pose.orientation),
166 tf2::getYaw(sampled_pt.pose.orientation));
169 angular_distance_to_heading = std::atan2(
170 sampled_pt_base.position.y,
171 sampled_pt_base.position.x);
174 double angular_thresh =
175 in_rotation_ ? params_->angular_disengage_threshold : params_->angular_dist_threshold;
176 if (abs(angular_distance_to_heading) > angular_thresh) {
179 "Robot is not within the new path's rough heading, rotating to heading...");
182 last_angular_vel_ = cmd_vel.twist.angular.z;
187 "Robot is at the new path's rough heading, passing to controller");
188 path_updated_ =
false;
190 }
catch (
const std::runtime_error & e) {
193 "Rotation Shim Controller was unable to find a sampling point,"
194 " a rotational collision was detected, or TF failed to transform"
195 " into base frame! what(): %s", e.what());
196 path_updated_ =
false;
201 in_rotation_ =
false;
202 auto cmd_vel = primary_controller_->computeVelocityCommands(pose, velocity, goal_checker,
203 transformed_global_plan, global_goal);
204 last_angular_vel_ = cmd_vel.twist.angular.z;
209 const geometry_msgs::msg::PoseStamped & global_goal)
211 if (current_path_.poses.size() < 2) {
213 "Path is too short to find a valid sampled path point for rotation.");
216 geometry_msgs::msg::Pose start = current_path_.poses.front().pose;
220 for (
unsigned int i = 1; i != current_path_.poses.size(); i++) {
221 dx = current_path_.poses[i].pose.position.x - start.position.x;
222 dy = current_path_.poses[i].pose.position.y - start.position.y;
223 if (hypot(dx, dy) >= params_->forward_sampling_distance) {
224 current_path_.poses[i].header.frame_id = current_path_.header.frame_id;
226 current_path_.poses[i].header.stamp = clock_->now();
227 return current_path_.poses[i];
231 auto goal = current_path_.poses.back();
232 goal.header.frame_id = current_path_.header.frame_id;
233 goal.header.stamp = clock_->now();
234 double gx = global_goal.pose.position.x - goal.pose.position.x;
235 double gy = global_goal.pose.position.y - goal.pose.position.y;
236 double distance = hypot(gx, gy);
237 if (distance > 1e-4) {
239 "The last pose of the local plan is %.2f m away from the global goal", hypot(gx, gy));
244 geometry_msgs::msg::Pose
247 geometry_msgs::msg::PoseStamped pt_base;
248 if (!nav2_util::transformPoseInTargetFrame(pt, pt_base, *tf_, costmap_ros_->getBaseFrameID(),
249 costmap_ros_->getTransformTolerance()))
256 geometry_msgs::msg::TwistStamped
258 const double & angular_distance_to_heading,
259 const geometry_msgs::msg::PoseStamped & pose,
260 const geometry_msgs::msg::Twist & velocity)
262 auto current = params_->closed_loop ? velocity.angular.z : last_angular_vel_;
263 if (current == std::numeric_limits<double>::max()) {
267 geometry_msgs::msg::TwistStamped cmd_vel;
268 cmd_vel.header = pose.header;
269 const double sign = angular_distance_to_heading > 0.0 ? 1.0 : -1.0;
270 const double angular_vel = sign * params_->rotate_to_heading_angular_vel;
271 const double & dt = params_->control_duration;
272 const double min_feasible_angular_speed = current - params_->max_angular_accel * dt;
273 const double max_feasible_angular_speed = current + params_->max_angular_accel * dt;
274 cmd_vel.twist.angular.z =
275 std::clamp(angular_vel, min_feasible_angular_speed, max_feasible_angular_speed);
278 double max_vel_to_stop = std::sqrt(2 * params_->max_angular_accel *
279 fabs(angular_distance_to_heading));
280 if (fabs(cmd_vel.twist.angular.z) > max_vel_to_stop) {
281 cmd_vel.twist.angular.z = sign * max_vel_to_stop;
289 const geometry_msgs::msg::TwistStamped & cmd_vel,
290 const double & angular_distance_to_heading,
291 const geometry_msgs::msg::PoseStamped & pose)
294 double simulated_time = 0.0;
295 double initial_yaw = tf2::getYaw(pose.pose.orientation);
297 double footprint_cost = 0.0;
298 double remaining_rotation_before_thresh =
299 fabs(angular_distance_to_heading) - params_->angular_dist_threshold;
301 while (simulated_time < params_->simulate_ahead_time) {
302 simulated_time += params_->control_duration;
303 yaw = initial_yaw + cmd_vel.twist.angular.z * simulated_time;
306 if (angles::shortest_angular_distance(yaw, initial_yaw) >= remaining_rotation_before_thresh) {
311 footprint_cost = collision_checker_->footprintCostAtPose(
312 pose.pose.position.x, pose.pose.position.y,
313 yaw, costmap_ros_->getRobotFootprint());
315 if (footprint_cost ==
static_cast<double>(NO_INFORMATION) &&
316 costmap_ros_->getLayeredCostmap()->isTrackingUnknown())
319 "RotationShimController detected a potential collision ahead!");
322 if (footprint_cost >= params_->max_cost_threshold) {
331 if (in_rotation_ || current_goal_ == geometry_msgs::msg::PoseStamped()) {
336 return current_goal_ != goal;
341 primary_controller_->newPathReceived(raw_global_path);
346 primary_controller_->setSpeedLimit(speed_limit, percentage);
351 last_angular_vel_ = std::numeric_limits<double>::max();
352 primary_controller_->reset();
358 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".
Rotate to rough path heading controller shim plugin.
void deactivate() override
Deactivate controller state machine.
geometry_msgs::msg::Pose transformPoseToBaseFrame(const geometry_msgs::msg::PoseStamped &pt)
Uses TF to find the location of the sampled path point in base frame.
geometry_msgs::msg::PoseStamped getSampledPathPt(const geometry_msgs::msg::PoseStamped &global_goal)
Finds the point on the path that is roughly the sampling point distance away from the robot for use....
void cleanup() override
Cleanup controller state machine.
geometry_msgs::msg::TwistStamped computeRotateToHeadingCommand(const double &angular_distance, const geometry_msgs::msg::PoseStamped &pose, const geometry_msgs::msg::Twist &velocity)
Rotates the robot to the rough heading.
void activate() override
Activate controller state machine.
void setSpeedLimit(const double &speed_limit, const bool &percentage) override
Limits the maximum linear speed of the robot.
RotationShimController()
Constructor for nav2_rotation_shim_controller::RotationShimController.
bool isGoalChanged(const geometry_msgs::msg::PoseStamped &goal)
Checks if the goal has changed based on the given path.
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 isCollisionFree(const geometry_msgs::msg::TwistStamped &cmd_vel, const double &angular_distance_to_heading, const geometry_msgs::msg::PoseStamped &pose)
Checks if rotation is safe.
geometry_msgs::msg::TwistStamped computeVelocityCommands(const geometry_msgs::msg::PoseStamped &pose, const geometry_msgs::msg::Twist &velocity, nav2_core::GoalChecker *, 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 newPathReceived(const nav_msgs::msg::Path &raw_global_path) override
nav2_core newPathReceived - Receives a new plan from the Planner Server
void reset() override
Reset the state of the controller.