21 #include "nav2_rotation_shim_controller/nav2_rotation_shim_controller.hpp"
23 using rcl_interfaces::msg::ParameterType;
25 namespace nav2_rotation_shim_controller
29 : lp_loader_(
"nav2_core",
"nav2_core::Controller"),
30 primary_controller_(nullptr),
37 const rclcpp_lifecycle::LifecycleNode::WeakPtr & parent,
38 std::string name, std::shared_ptr<tf2_ros::Buffer> tf,
39 std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
41 position_goal_checker_ = std::make_unique<nav2_controller::PositionGoalChecker>();
42 position_goal_checker_->initialize(parent, plugin_name_ +
".position_checker", costmap_ros);
45 auto node = parent.lock();
48 costmap_ros_ = costmap_ros;
49 logger_ = node->get_logger();
50 clock_ = node->get_clock();
52 std::string primary_controller;
53 double control_frequency;
54 nav2_util::declare_parameter_if_not_declared(
55 node, plugin_name_ +
".angular_dist_threshold", rclcpp::ParameterValue(0.785));
56 nav2_util::declare_parameter_if_not_declared(
57 node, plugin_name_ +
".angular_disengage_threshold", rclcpp::ParameterValue(0.785 / 2.0));
58 nav2_util::declare_parameter_if_not_declared(
59 node, plugin_name_ +
".forward_sampling_distance", rclcpp::ParameterValue(0.5));
60 nav2_util::declare_parameter_if_not_declared(
61 node, plugin_name_ +
".rotate_to_heading_angular_vel", rclcpp::ParameterValue(1.8));
62 nav2_util::declare_parameter_if_not_declared(
63 node, plugin_name_ +
".max_angular_accel", rclcpp::ParameterValue(3.2));
64 nav2_util::declare_parameter_if_not_declared(
65 node, plugin_name_ +
".max_cost_threshold",
66 rclcpp::ParameterValue(
static_cast<double>(nav2_costmap_2d::LETHAL_OBSTACLE)));
67 nav2_util::declare_parameter_if_not_declared(
68 node, plugin_name_ +
".simulate_ahead_time", rclcpp::ParameterValue(1.0));
69 nav2_util::declare_parameter_if_not_declared(
70 node, plugin_name_ +
".primary_controller", rclcpp::PARAMETER_STRING);
71 nav2_util::declare_parameter_if_not_declared(
72 node, plugin_name_ +
".rotate_to_goal_heading", rclcpp::ParameterValue(
false));
73 nav2_util::declare_parameter_if_not_declared(
74 node, plugin_name_ +
".rotate_to_heading_once", rclcpp::ParameterValue(
false));
75 nav2_util::declare_parameter_if_not_declared(
76 node, plugin_name_ +
".closed_loop", rclcpp::ParameterValue(
true));
77 nav2_util::declare_parameter_if_not_declared(
78 node, plugin_name_ +
".use_path_orientations", rclcpp::ParameterValue(
false));
80 node->get_parameter(plugin_name_ +
".angular_dist_threshold", angular_dist_threshold_);
81 node->get_parameter(plugin_name_ +
".angular_disengage_threshold", angular_disengage_threshold_);
82 node->get_parameter(plugin_name_ +
".forward_sampling_distance", forward_sampling_distance_);
84 plugin_name_ +
".rotate_to_heading_angular_vel",
85 rotate_to_heading_angular_vel_);
86 node->get_parameter(plugin_name_ +
".max_angular_accel", max_angular_accel_);
87 node->get_parameter(plugin_name_ +
".max_cost_threshold", max_cost_threshold_);
88 node->get_parameter(plugin_name_ +
".simulate_ahead_time", simulate_ahead_time_);
90 primary_controller = node->get_parameter(plugin_name_ +
".primary_controller").as_string();
91 node->get_parameter(
"controller_frequency", control_frequency);
92 control_duration_ = 1.0 / control_frequency;
94 node->get_parameter(plugin_name_ +
".rotate_to_goal_heading", rotate_to_goal_heading_);
95 node->get_parameter(plugin_name_ +
".rotate_to_heading_once", rotate_to_heading_once_);
96 node->get_parameter(plugin_name_ +
".closed_loop", closed_loop_);
97 node->get_parameter(plugin_name_ +
".use_path_orientations", use_path_orientations_);
100 primary_controller_ = lp_loader_.createUniqueInstance(primary_controller);
102 logger_,
"Created internal controller for rotation shimming: %s of type %s",
103 plugin_name_.c_str(), primary_controller.c_str());
104 }
catch (
const pluginlib::PluginlibException & ex) {
107 "Failed to create internal controller for rotation shimming. Exception: %s", ex.what());
111 primary_controller_->configure(parent, name, tf, costmap_ros);
114 collision_checker_ = std::make_unique<nav2_costmap_2d::
115 FootprintCollisionChecker<nav2_costmap_2d::Costmap2D *>>(costmap_ros->getCostmap());
122 "Activating controller: %s of type "
123 "nav2_rotation_shim_controller::RotationShimController",
124 plugin_name_.c_str());
126 primary_controller_->activate();
127 in_rotation_ =
false;
128 last_angular_vel_ = std::numeric_limits<double>::max();
130 auto node = node_.lock();
131 dyn_params_handler_ = node->add_on_set_parameters_callback(
134 this, std::placeholders::_1));
135 position_goal_checker_->reset();
142 "Deactivating controller: %s of type "
143 "nav2_rotation_shim_controller::RotationShimController",
144 plugin_name_.c_str());
146 primary_controller_->deactivate();
148 if (
auto node = node_.lock()) {
149 node->remove_on_set_parameters_callback(dyn_params_handler_.get());
151 dyn_params_handler_.reset();
158 "Cleaning up controller: %s of type "
159 "nav2_rotation_shim_controller::RotationShimController",
160 plugin_name_.c_str());
162 primary_controller_->cleanup();
163 primary_controller_.reset();
164 position_goal_checker_.reset();
168 const geometry_msgs::msg::PoseStamped & pose,
169 const geometry_msgs::msg::Twist & velocity,
173 if (rotate_to_goal_heading_) {
174 std::lock_guard<std::mutex> lock_reinit(mutex_);
179 if (!nav2_util::transformPoseInTargetFrame(
180 sampled_pt_goal, sampled_pt_goal, *tf_,
181 pose.header.frame_id))
186 geometry_msgs::msg::Pose pose_tolerance;
187 geometry_msgs::msg::Twist vel_tolerance;
189 position_goal_checker_->setXYGoalTolerance(pose_tolerance.position.x);
191 if (position_goal_checker_->isGoalReached(pose.pose, sampled_pt_goal.pose, velocity)) {
192 double pose_yaw = tf2::getYaw(pose.pose.orientation);
193 double goal_yaw = tf2::getYaw(sampled_pt_goal.pose.orientation);
195 double angular_distance_to_heading = angles::shortest_angular_distance(pose_yaw, goal_yaw);
198 last_angular_vel_ = cmd_vel.twist.angular.z;
201 }
catch (
const std::runtime_error & e) {
204 "Rotation Shim Controller was unable to find a goal point,"
205 " a rotational collision was detected, or TF failed to transform"
206 " into base frame! what(): %s", e.what());
212 std::unique_lock<nav2_costmap_2d::Costmap2D::mutex_t> lock(*(costmap->getMutex()));
214 std::lock_guard<std::mutex> lock_reinit(mutex_);
217 double angular_distance_to_heading;
218 if (use_path_orientations_) {
219 angular_distance_to_heading = angles::shortest_angular_distance(
220 tf2::getYaw(pose.pose.orientation),
221 tf2::getYaw(sampled_pt.pose.orientation));
224 angular_distance_to_heading = std::atan2(
225 sampled_pt_base.position.y,
226 sampled_pt_base.position.x);
229 double angular_thresh =
230 in_rotation_ ? angular_disengage_threshold_ : angular_dist_threshold_;
231 if (abs(angular_distance_to_heading) > angular_thresh) {
234 "Robot is not within the new path's rough heading, rotating to heading...");
237 last_angular_vel_ = cmd_vel.twist.angular.z;
242 "Robot is at the new path's rough heading, passing to controller");
243 path_updated_ =
false;
245 }
catch (
const std::runtime_error & e) {
248 "Rotation Shim Controller was unable to find a sampling point,"
249 " a rotational collision was detected, or TF failed to transform"
250 " into base frame! what(): %s", e.what());
251 path_updated_ =
false;
256 in_rotation_ =
false;
257 auto cmd_vel = primary_controller_->computeVelocityCommands(pose, velocity, goal_checker);
258 last_angular_vel_ = cmd_vel.twist.angular.z;
264 if (current_path_.poses.size() < 2) {
266 "Path is too short to find a valid sampled path point for rotation.");
269 geometry_msgs::msg::Pose start = current_path_.poses.front().pose;
273 for (
unsigned int i = 1; i != current_path_.poses.size(); i++) {
274 dx = current_path_.poses[i].pose.position.x - start.position.x;
275 dy = current_path_.poses[i].pose.position.y - start.position.y;
276 if (hypot(dx, dy) >= forward_sampling_distance_) {
277 current_path_.poses[i].header.frame_id = current_path_.header.frame_id;
278 current_path_.poses[i].header.stamp = clock_->now();
279 return current_path_.poses[i];
283 auto goal = current_path_.poses.back();
284 goal.header.frame_id = current_path_.header.frame_id;
285 goal.header.stamp = clock_->now();
291 if (current_path_.poses.empty()) {
295 auto goal = current_path_.poses.back();
296 goal.header.frame_id = current_path_.header.frame_id;
297 goal.header.stamp = clock_->now();
301 geometry_msgs::msg::Pose
304 geometry_msgs::msg::PoseStamped pt_base;
305 if (!nav2_util::transformPoseInTargetFrame(pt, pt_base, *tf_, costmap_ros_->getBaseFrameID())) {
311 geometry_msgs::msg::TwistStamped
313 const double & angular_distance_to_heading,
314 const geometry_msgs::msg::PoseStamped & pose,
315 const geometry_msgs::msg::Twist & velocity)
317 auto current = closed_loop_ ? velocity.angular.z : last_angular_vel_;
318 if (current == std::numeric_limits<double>::max()) {
322 geometry_msgs::msg::TwistStamped cmd_vel;
323 cmd_vel.header = pose.header;
324 const double sign = angular_distance_to_heading > 0.0 ? 1.0 : -1.0;
325 const double angular_vel = sign * rotate_to_heading_angular_vel_;
326 const double & dt = control_duration_;
327 const double min_feasible_angular_speed = current - max_angular_accel_ * dt;
328 const double max_feasible_angular_speed = current + max_angular_accel_ * dt;
329 cmd_vel.twist.angular.z =
330 std::clamp(angular_vel, min_feasible_angular_speed, max_feasible_angular_speed);
333 double max_vel_to_stop = std::sqrt(2 * max_angular_accel_ * fabs(angular_distance_to_heading));
334 if (fabs(cmd_vel.twist.angular.z) > max_vel_to_stop) {
335 cmd_vel.twist.angular.z = sign * max_vel_to_stop;
343 const geometry_msgs::msg::TwistStamped & cmd_vel,
344 const double & angular_distance_to_heading,
345 const geometry_msgs::msg::PoseStamped & pose)
348 double simulated_time = 0.0;
349 double initial_yaw = tf2::getYaw(pose.pose.orientation);
351 double footprint_cost = 0.0;
352 double remaining_rotation_before_thresh =
353 fabs(angular_distance_to_heading) - angular_dist_threshold_;
355 while (simulated_time < simulate_ahead_time_) {
356 simulated_time += control_duration_;
357 yaw = initial_yaw + cmd_vel.twist.angular.z * simulated_time;
360 if (angles::shortest_angular_distance(yaw, initial_yaw) >= remaining_rotation_before_thresh) {
365 footprint_cost = collision_checker_->footprintCostAtPose(
366 pose.pose.position.x, pose.pose.position.y,
367 yaw, costmap_ros_->getRobotFootprint());
369 if (footprint_cost ==
static_cast<double>(NO_INFORMATION) &&
370 costmap_ros_->getLayeredCostmap()->isTrackingUnknown())
373 "RotationShimController detected a potential collision ahead!");
376 if (footprint_cost >= max_cost_threshold_) {
385 if (in_rotation_ || current_path_.poses.empty()) {
390 return current_path_.poses.back().pose != path.poses.back().pose;
395 path_updated_ = rotate_to_heading_once_ ?
isGoalChanged(path) :
true;
396 current_path_ = path;
397 primary_controller_->setPlan(path);
398 position_goal_checker_->reset();
403 primary_controller_->setSpeedLimit(speed_limit, percentage);
408 last_angular_vel_ = std::numeric_limits<double>::max();
409 primary_controller_->reset();
410 position_goal_checker_->reset();
413 rcl_interfaces::msg::SetParametersResult
416 rcl_interfaces::msg::SetParametersResult result;
417 std::lock_guard<std::mutex> lock_reinit(mutex_);
419 for (
auto parameter : parameters) {
420 const auto & type = parameter.get_type();
421 const auto & name = parameter.get_name();
423 if (type == ParameterType::PARAMETER_DOUBLE) {
424 if (name == plugin_name_ +
".angular_dist_threshold") {
425 angular_dist_threshold_ = parameter.as_double();
426 }
else if (name == plugin_name_ +
".forward_sampling_distance") {
427 forward_sampling_distance_ = parameter.as_double();
428 }
else if (name == plugin_name_ +
".rotate_to_heading_angular_vel") {
429 rotate_to_heading_angular_vel_ = parameter.as_double();
430 }
else if (name == plugin_name_ +
".max_angular_accel") {
431 max_angular_accel_ = parameter.as_double();
432 }
else if (name == plugin_name_ +
".simulate_ahead_time") {
433 simulate_ahead_time_ = parameter.as_double();
434 }
else if (name == plugin_name_ +
".max_cost_threshold") {
435 max_cost_threshold_ = parameter.as_double();
437 }
else if (type == ParameterType::PARAMETER_BOOL) {
438 if (name == plugin_name_ +
".rotate_to_goal_heading") {
439 rotate_to_goal_heading_ = parameter.as_bool();
440 }
else if (name == plugin_name_ +
".rotate_to_heading_once") {
441 rotate_to_heading_once_ = parameter.as_bool();
442 }
else if (name == plugin_name_ +
".closed_loop") {
443 closed_loop_ = parameter.as_bool();
444 }
else if (name == plugin_name_ +
".use_path_orientations") {
445 use_path_orientations_ = parameter.as_bool();
450 result.successful =
true;
457 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 getTolerances(geometry_msgs::msg::Pose &pose_tolerance, geometry_msgs::msg::Twist &vel_tolerance)=0
Get the maximum possible tolerances used for goal checking in the major types. Any field without a va...
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.
void 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) override
Configure controller state machine.
geometry_msgs::msg::PoseStamped getSampledPathPt()
Finds the point on the path that is roughly the sampling point distance away from the robot for use....
geometry_msgs::msg::TwistStamped computeVelocityCommands(const geometry_msgs::msg::PoseStamped &pose, const geometry_msgs::msg::Twist &velocity, nav2_core::GoalChecker *) override
Compute the best command given the current pose and velocity.
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.
void cleanup() override
Cleanup controller state machine.
geometry_msgs::msg::PoseStamped getSampledPathGoal()
Find the goal point in path May throw exception if the path is empty.
bool isGoalChanged(const nav_msgs::msg::Path &path)
Checks if the goal has changed based on the given path.
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 setPlan(const nav_msgs::msg::Path &path) override
nav2_core setPlan - Sets the global plan
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.
rcl_interfaces::msg::SetParametersResult dynamicParametersCallback(std::vector< rclcpp::Parameter > parameters)
Callback executed when a parameter change is detected.
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.
void reset() override
Reset the state of the controller.