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  fallback_radial_goal_tolerance_(0.25), path_length_tolerance_(1.0),
35  direction_estimation_distance_(0.15), is_overshoot_valid_(false)
36 {
37 }
38 
40 {
41  auto node = node_.lock();
42  if (post_set_params_handler_ && node) {
43  node->remove_post_set_parameters_callback(post_set_params_handler_.get());
44  }
45  post_set_params_handler_.reset();
46  if (on_set_params_handler_ && node) {
47  node->remove_on_set_parameters_callback(on_set_params_handler_.get());
48  }
49  on_set_params_handler_.reset();
50 }
51 
53  const nav2::LifecycleNode::WeakPtr & parent,
54  const std::string & plugin_name,
55  const std::shared_ptr<nav2_costmap_2d::Costmap2DROS>/*costmap_ros*/)
56 {
57  plugin_name_ = plugin_name;
58  node_ = parent;
59  auto node = node_.lock();
60  logger_ = node->get_logger();
61 
62  along_path_tolerance_ = node->declare_or_get_parameter(
63  plugin_name + ".along_path_tolerance", 0.25);
64  cross_track_tolerance_ = node->declare_or_get_parameter(
65  plugin_name + ".cross_track_tolerance", 0.25);
66  fallback_radial_goal_tolerance_ = node->declare_or_get_parameter(
67  plugin_name + ".fallback_radial_goal_tolerance",
68  std::min(along_path_tolerance_, cross_track_tolerance_));
69  path_length_tolerance_ = node->declare_or_get_parameter(
70  plugin_name + ".path_length_tolerance", 1.0);
71  direction_estimation_distance_ = node->declare_or_get_parameter(
72  plugin_name + ".direction_estimation_distance", 0.15);
73  is_overshoot_valid_ = node->declare_or_get_parameter(
74  plugin_name + ".is_overshoot_valid", false);
75 
76  // Add callback for dynamic parameters
77  post_set_params_handler_ = node->add_post_set_parameters_callback(
78  std::bind(
80  this, std::placeholders::_1));
81  on_set_params_handler_ = node->add_on_set_parameters_callback(
82  std::bind(
84  this, std::placeholders::_1));
85 }
86 
88 {
89  std::lock_guard<std::mutex> lock_reinit(mutex_);
90  cached_end_of_path_yaw_.reset();
91 }
92 
94  const geometry_msgs::msg::Pose & query_pose, const geometry_msgs::msg::Pose & goal_pose,
95  const geometry_msgs::msg::Twist & velocity,
96  const nav_msgs::msg::Path & transformed_global_plan)
97 {
98  // Since we do not consider orientation in this goal checker
99  // we can directly check if the XY position is reached
100  return isGoalXYReached(query_pose, goal_pose, velocity, transformed_global_plan);
101 }
102 
104  const geometry_msgs::msg::Pose & query_pose, const geometry_msgs::msg::Pose & goal_pose,
105  const geometry_msgs::msg::Twist &,
106  const nav_msgs::msg::Path & transformed_global_plan)
107 {
108  std::lock_guard<std::mutex> lock_reinit(mutex_);
109 
110  double robot_to_goal_dx = goal_pose.position.x - query_pose.position.x;
111  double robot_to_goal_dy = goal_pose.position.y - query_pose.position.y;
112  double distance_to_goal = std::hypot(robot_to_goal_dx, robot_to_goal_dy);
113 
114  // Already on the goal; skip the direction math and the atan2(0,0) below.
115  if (distance_to_goal < 1e-6) {
116  return true;
117  }
118 
119  // Cache the direction
120  for (int i = static_cast<int>(transformed_global_plan.poses.size()) - 2; i >= 0; --i) {
121  const auto & candidate_pose = transformed_global_plan.poses[i].pose;
122  double dx = goal_pose.position.x - candidate_pose.position.x;
123  double dy = goal_pose.position.y - candidate_pose.position.y;
124 
125  if (std::hypot(dx, dy) >= direction_estimation_distance_) {
126  cached_end_of_path_yaw_ = atan2(dy, dx);
127  break;
128  }
129  }
130 
131  // If the local plan length is longer than the tolerance, we skip the check
132  if (nav2_util::geometry_utils::calculate_path_length(transformed_global_plan) >
133  path_length_tolerance_)
134  {
135  return false;
136  }
137 
138  // If no direction was ever estimated, fall back to simple distance check
139  if (!cached_end_of_path_yaw_.has_value()) {
140  RCLCPP_DEBUG(
141  logger_,
142  "No path direction available, falling back to simple distance check");
143  return distance_to_goal < fallback_radial_goal_tolerance_;
144  }
145 
146  double robot_to_goal_yaw = atan2(robot_to_goal_dy, robot_to_goal_dx);
147  double projection_angle = angles::shortest_angular_distance(
148  robot_to_goal_yaw, cached_end_of_path_yaw_.value());
149  double along_path_distance = distance_to_goal * cos(projection_angle);
150  double cross_track_distance = distance_to_goal * sin(projection_angle);
151 
152  if (is_overshoot_valid_) {
153  return along_path_distance < along_path_tolerance_ &&
154  fabs(cross_track_distance) < cross_track_tolerance_;
155  } else {
156  return fabs(along_path_distance) < along_path_tolerance_ &&
157  fabs(cross_track_distance) < cross_track_tolerance_;
158  }
159 }
160 
162  geometry_msgs::msg::Pose & pose_tolerance,
163  geometry_msgs::msg::Twist & vel_tolerance,
164  double & path_length_tolerance)
165 {
166  std::lock_guard<std::mutex> lock_reinit(mutex_);
167  double invalid_field = std::numeric_limits<double>::lowest();
168 
169  pose_tolerance.position.x = std::min(along_path_tolerance_, cross_track_tolerance_);
170  pose_tolerance.position.y = std::min(along_path_tolerance_, cross_track_tolerance_);
171  pose_tolerance.position.z = invalid_field;
172  pose_tolerance.orientation =
173  nav2_util::geometry_utils::orientationAroundZAxis(M_PI_2);
174 
175  vel_tolerance.linear.x = invalid_field;
176  vel_tolerance.linear.y = invalid_field;
177  vel_tolerance.linear.z = invalid_field;
178 
179  vel_tolerance.angular.x = invalid_field;
180  vel_tolerance.angular.y = invalid_field;
181  vel_tolerance.angular.z = invalid_field;
182 
183  path_length_tolerance = path_length_tolerance_;
184 
185  return true;
186 }
187 
188 rcl_interfaces::msg::SetParametersResult
190  const std::vector<rclcpp::Parameter> & parameters)
191 {
192  rcl_interfaces::msg::SetParametersResult result;
193  result.successful = true;
194  for (auto parameter : parameters) {
195  const auto & param_type = parameter.get_type();
196  const auto & param_name = parameter.get_name();
197  if (param_name.find(plugin_name_ + ".") != 0) {
198  continue;
199  }
200  if (param_type == ParameterType::PARAMETER_DOUBLE) {
201  if (parameter.as_double() < 0.0) {
202  RCLCPP_WARN(
203  logger_, "The value of parameter '%s' is incorrectly set to %f, "
204  "it should be >=0. Ignoring parameter update.",
205  param_name.c_str(), parameter.as_double());
206  result.successful = false;
207  }
208  if (param_name == plugin_name_ + ".direction_estimation_distance") {
209  const double value = parameter.as_double();
210  if (value <= 0.0 || value >= path_length_tolerance_) {
211  RCLCPP_WARN(
212  logger_, "The value of parameter '%s' is set to %f, it should be >0 and "
213  "<path_length_tolerance (%f). Ignoring parameter update.",
214  param_name.c_str(), value, path_length_tolerance_);
215  result.successful = false;
216  }
217  }
218  }
219  }
220  return result;
221 }
222 
223 void
225  const std::vector<rclcpp::Parameter> & parameters)
226 {
227  std::lock_guard<std::mutex> lock_reinit(mutex_);
228  for (const auto & parameter : parameters) {
229  const auto & type = parameter.get_type();
230  const auto & name = parameter.get_name();
231  if (name.find(plugin_name_ + ".") != 0) {
232  continue;
233  }
234  if (type == ParameterType::PARAMETER_DOUBLE) {
235  if (name == plugin_name_ + ".along_path_tolerance") {
236  along_path_tolerance_ = parameter.as_double();
237  } else if (name == plugin_name_ + ".cross_track_tolerance") {
238  cross_track_tolerance_ = parameter.as_double();
239  } else if (name == plugin_name_ + ".fallback_radial_goal_tolerance") {
240  fallback_radial_goal_tolerance_ = parameter.as_double();
241  } else if (name == plugin_name_ + ".path_length_tolerance") {
242  path_length_tolerance_ = parameter.as_double();
243  } else if (name == plugin_name_ + ".direction_estimation_distance") {
244  direction_estimation_distance_ = parameter.as_double();
245  }
246  } else if (type == ParameterType::PARAMETER_BOOL) {
247  if (name == plugin_name_ + ".is_overshoot_valid") {
248  is_overshoot_valid_ = parameter.as_bool();
249  }
250  }
251  }
252 }
253 
254 } // namespace nav2_controller
255 
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.