Nav2 Navigation Stack - rolling  main
ROS 2 Navigation Stack
axis_goal_checker.cpp
1 // Copyright (c) 2025 Dexory
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include <memory>
16 #include <string>
17 #include <limits>
18 #include <vector>
19 
20 #include "angles/angles.h"
21 #include "nav2_controller/plugins/axis_goal_checker.hpp"
22 #include "pluginlib/class_list_macros.hpp"
23 #include "nav2_ros_common/node_utils.hpp"
24 #include "nav2_util/geometry_utils.hpp"
25 
26 using rcl_interfaces::msg::ParameterType;
27 using std::placeholders::_1;
28 
29 namespace nav2_controller
30 {
31 
33 : along_path_tolerance_(0.25), cross_track_tolerance_(0.25),
34  path_length_tolerance_(1.0), direction_estimation_distance_(0.15), is_overshoot_valid_(false)
35 {
36 }
37 
39 {
40  auto node = node_.lock();
41  if (post_set_params_handler_ && node) {
42  node->remove_post_set_parameters_callback(post_set_params_handler_.get());
43  }
44  post_set_params_handler_.reset();
45  if (on_set_params_handler_ && node) {
46  node->remove_on_set_parameters_callback(on_set_params_handler_.get());
47  }
48  on_set_params_handler_.reset();
49 }
50 
52  const nav2::LifecycleNode::WeakPtr & parent,
53  const std::string & plugin_name,
54  const std::shared_ptr<nav2_costmap_2d::Costmap2DROS>/*costmap_ros*/)
55 {
56  plugin_name_ = plugin_name;
57  node_ = parent;
58  auto node = node_.lock();
59  logger_ = node->get_logger();
60 
61  along_path_tolerance_ = node->declare_or_get_parameter(
62  plugin_name + ".along_path_tolerance", 0.25);
63  cross_track_tolerance_ = node->declare_or_get_parameter(
64  plugin_name + ".cross_track_tolerance", 0.25);
65  path_length_tolerance_ = node->declare_or_get_parameter(
66  plugin_name + ".path_length_tolerance", 1.0);
67  direction_estimation_distance_ = node->declare_or_get_parameter(
68  plugin_name + ".direction_estimation_distance", 0.15);
69  is_overshoot_valid_ = node->declare_or_get_parameter(
70  plugin_name + ".is_overshoot_valid", false);
71 
72  // Add callback for dynamic parameters
73  post_set_params_handler_ = node->add_post_set_parameters_callback(
74  std::bind(
76  this, std::placeholders::_1));
77  on_set_params_handler_ = node->add_on_set_parameters_callback(
78  std::bind(
80  this, std::placeholders::_1));
81 }
82 
84 {
85  std::lock_guard<std::mutex> lock_reinit(mutex_);
86  cached_end_of_path_yaw_.reset();
87 }
88 
90  const geometry_msgs::msg::Pose & query_pose, const geometry_msgs::msg::Pose & goal_pose,
91  const geometry_msgs::msg::Twist & velocity,
92  const nav_msgs::msg::Path & transformed_global_plan)
93 {
94  // Since we do not consider orientation in this goal checker
95  // we can directly check if the XY position is reached
96  return isGoalXYReached(query_pose, goal_pose, velocity, transformed_global_plan);
97 }
98 
100  const geometry_msgs::msg::Pose & query_pose, const geometry_msgs::msg::Pose & goal_pose,
101  const geometry_msgs::msg::Twist &,
102  const nav_msgs::msg::Path & transformed_global_plan)
103 {
104  std::lock_guard<std::mutex> lock_reinit(mutex_);
105 
106  double robot_to_goal_dx = goal_pose.position.x - query_pose.position.x;
107  double robot_to_goal_dy = goal_pose.position.y - query_pose.position.y;
108  double distance_to_goal = std::hypot(robot_to_goal_dx, robot_to_goal_dy);
109 
110  // Already on the goal; skip the direction math and the atan2(0,0) below.
111  if (distance_to_goal < 1e-6) {
112  return true;
113  }
114 
115  // Cache the direction
116  for (int i = static_cast<int>(transformed_global_plan.poses.size()) - 2; i >= 0; --i) {
117  const auto & candidate_pose = transformed_global_plan.poses[i].pose;
118  double dx = goal_pose.position.x - candidate_pose.position.x;
119  double dy = goal_pose.position.y - candidate_pose.position.y;
120 
121  if (std::hypot(dx, dy) >= direction_estimation_distance_) {
122  cached_end_of_path_yaw_ = atan2(dy, dx);
123  break;
124  }
125  }
126 
127  // If the local plan length is longer than the tolerance, we skip the check
128  if (nav2_util::geometry_utils::calculate_path_length(transformed_global_plan) >
129  path_length_tolerance_)
130  {
131  return false;
132  }
133 
134  // If no direction was ever estimated, fall back to simple distance check
135  if (!cached_end_of_path_yaw_.has_value()) {
136  RCLCPP_DEBUG(
137  logger_,
138  "No path direction available, falling back to simple distance check");
139  return distance_to_goal < std::min(along_path_tolerance_, cross_track_tolerance_);
140  }
141 
142  double robot_to_goal_yaw = atan2(robot_to_goal_dy, robot_to_goal_dx);
143  double projection_angle = angles::shortest_angular_distance(
144  robot_to_goal_yaw, cached_end_of_path_yaw_.value());
145  double along_path_distance = distance_to_goal * cos(projection_angle);
146  double cross_track_distance = distance_to_goal * sin(projection_angle);
147 
148  if (is_overshoot_valid_) {
149  return along_path_distance < along_path_tolerance_ &&
150  fabs(cross_track_distance) < cross_track_tolerance_;
151  } else {
152  return fabs(along_path_distance) < along_path_tolerance_ &&
153  fabs(cross_track_distance) < cross_track_tolerance_;
154  }
155 }
156 
158  geometry_msgs::msg::Pose & pose_tolerance,
159  geometry_msgs::msg::Twist & vel_tolerance,
160  double & path_length_tolerance)
161 {
162  std::lock_guard<std::mutex> lock_reinit(mutex_);
163  double invalid_field = std::numeric_limits<double>::lowest();
164 
165  pose_tolerance.position.x = std::min(along_path_tolerance_, cross_track_tolerance_);
166  pose_tolerance.position.y = std::min(along_path_tolerance_, cross_track_tolerance_);
167  pose_tolerance.position.z = invalid_field;
168  pose_tolerance.orientation =
169  nav2_util::geometry_utils::orientationAroundZAxis(M_PI_2);
170 
171  vel_tolerance.linear.x = invalid_field;
172  vel_tolerance.linear.y = invalid_field;
173  vel_tolerance.linear.z = invalid_field;
174 
175  vel_tolerance.angular.x = invalid_field;
176  vel_tolerance.angular.y = invalid_field;
177  vel_tolerance.angular.z = invalid_field;
178 
179  path_length_tolerance = path_length_tolerance_;
180 
181  return true;
182 }
183 
184 rcl_interfaces::msg::SetParametersResult
186  const std::vector<rclcpp::Parameter> & parameters)
187 {
188  rcl_interfaces::msg::SetParametersResult result;
189  result.successful = true;
190  for (auto parameter : parameters) {
191  const auto & param_type = parameter.get_type();
192  const auto & param_name = parameter.get_name();
193  if (param_name.find(plugin_name_ + ".") != 0) {
194  continue;
195  }
196  if (param_type == ParameterType::PARAMETER_DOUBLE) {
197  if (parameter.as_double() < 0.0) {
198  RCLCPP_WARN(
199  logger_, "The value of parameter '%s' is incorrectly set to %f, "
200  "it should be >=0. Ignoring parameter update.",
201  param_name.c_str(), parameter.as_double());
202  result.successful = false;
203  }
204  if (param_name == plugin_name_ + ".direction_estimation_distance") {
205  const double value = parameter.as_double();
206  if (value <= 0.0 || value >= path_length_tolerance_) {
207  RCLCPP_WARN(
208  logger_, "The value of parameter '%s' is set to %f, it should be >0 and "
209  "<path_length_tolerance (%f). Ignoring parameter update.",
210  param_name.c_str(), value, path_length_tolerance_);
211  result.successful = false;
212  }
213  }
214  }
215  }
216  return result;
217 }
218 
219 void
221  const std::vector<rclcpp::Parameter> & parameters)
222 {
223  std::lock_guard<std::mutex> lock_reinit(mutex_);
224  for (const auto & parameter : parameters) {
225  const auto & type = parameter.get_type();
226  const auto & name = parameter.get_name();
227  if (name.find(plugin_name_ + ".") != 0) {
228  continue;
229  }
230  if (type == ParameterType::PARAMETER_DOUBLE) {
231  if (name == plugin_name_ + ".along_path_tolerance") {
232  along_path_tolerance_ = parameter.as_double();
233  } else if (name == plugin_name_ + ".cross_track_tolerance") {
234  cross_track_tolerance_ = parameter.as_double();
235  } else if (name == plugin_name_ + ".path_length_tolerance") {
236  path_length_tolerance_ = parameter.as_double();
237  } else if (name == plugin_name_ + ".direction_estimation_distance") {
238  direction_estimation_distance_ = parameter.as_double();
239  }
240  } else if (type == ParameterType::PARAMETER_BOOL) {
241  if (name == plugin_name_ + ".is_overshoot_valid") {
242  is_overshoot_valid_ = parameter.as_bool();
243  }
244  }
245  }
246 }
247 
248 } // namespace nav2_controller
249 
Goal Checker plugin that checks progress along the axis defined by the last segment of the path to th...
bool getTolerances(geometry_msgs::msg::Pose &pose_tolerance, geometry_msgs::msg::Twist &vel_tolerance, double &path_length_tolerance) override
Get the position and velocity tolerances.
~AxisGoalChecker()
Destroy the Axis Goal Checker object.
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) override
Check if XY goal position has been reached (without considering yaw)
rcl_interfaces::msg::SetParametersResult validateParameterUpdatesCallback(const std::vector< rclcpp::Parameter > &parameters)
Validate incoming parameter updates before applying them. This callback is triggered when one or more...
void reset() override
Reset the goal checker state.
void initialize(const nav2::LifecycleNode::WeakPtr &parent, const std::string &plugin_name, const std::shared_ptr< nav2_costmap_2d::Costmap2DROS > costmap_ros) override
Initialize the goal checker.
bool isGoalReached(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) override
Check if the goal is reached.
void updateParametersCallback(const std::vector< rclcpp::Parameter > &parameters)
Apply parameter updates after validation This callback is executed when parameters have been successf...
AxisGoalChecker()
Construct a new Axis Goal Checker object.
Function-object for checking whether a goal has been reached.