-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New Goal checker: AxisGoalChecker #5746
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ed714bf
1e8fd8f
4929d27
db40de8
2d232ba
fd6bd95
ae77ed4
b264a0b
e4b4352
16dc722
413e6ba
1947d86
69fb133
777c7bd
b69bc4f
a7d0d34
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| // Copyright (c) 2025 Dexory | ||
| // | ||
| // 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. | ||
|
|
||
| #ifndef NAV2_CONTROLLER__PLUGINS__AXIS_GOAL_CHECKER_HPP_ | ||
| #define NAV2_CONTROLLER__PLUGINS__AXIS_GOAL_CHECKER_HPP_ | ||
|
|
||
| #include <memory> | ||
| #include <string> | ||
| #include <vector> | ||
|
|
||
| #include "rclcpp/rclcpp.hpp" | ||
| #include "nav2_ros_common/lifecycle_node.hpp" | ||
| #include "nav2_core/goal_checker.hpp" | ||
| #include "rcl_interfaces/msg/set_parameters_result.hpp" | ||
|
|
||
| namespace nav2_controller | ||
| { | ||
|
|
||
| /** | ||
| * @class AxisGoalChecker | ||
| * @brief Goal Checker plugin that checks progress along the axis defined by the last segment | ||
| * of the path to the goal. | ||
| * | ||
| * This class can be configured to allow overshoot past the goal if the is_overshoot_valid | ||
| * parameter is set to true (which is false by default). | ||
tonynajjar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| */ | ||
| class AxisGoalChecker : public nav2_core::GoalChecker | ||
| { | ||
| public: | ||
| /** | ||
| * @brief Construct a new Axis Goal Checker object | ||
| */ | ||
| AxisGoalChecker(); | ||
tonynajjar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Standard GoalChecker Interface | ||
| /** | ||
| * @brief Initialize the goal checker | ||
| * @param parent Weak pointer to the lifecycle node | ||
| * @param plugin_name Name of the plugin | ||
| * @param costmap_ros Shared pointer to the costmap | ||
| */ | ||
| void initialize( | ||
| const nav2::LifecycleNode::WeakPtr & parent, | ||
| const std::string & plugin_name, | ||
| const std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros) override; | ||
|
|
||
| /** | ||
| * @brief Reset the goal checker state | ||
| */ | ||
| void reset() override; | ||
|
|
||
| /** | ||
| * @brief Check if the goal is reached | ||
| * @param query_pose Current pose of the robot | ||
| * @param goal_pose Target goal pose | ||
| * @param velocity Current velocity of the robot | ||
| * @param transformed_global_plan The transformed global plan | ||
| * @return true if goal is reached, false otherwise | ||
| */ | ||
| 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; | ||
tonynajjar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * @brief Get the position and velocity tolerances | ||
| * @param pose_tolerance Output parameter for pose tolerance | ||
| * @param vel_tolerance Output parameter for velocity tolerance | ||
| * @return true if tolerances are available, false otherwise | ||
| */ | ||
| bool getTolerances( | ||
| geometry_msgs::msg::Pose & pose_tolerance, | ||
| geometry_msgs::msg::Twist & vel_tolerance) override; | ||
|
|
||
| protected: | ||
| double along_path_tolerance_; | ||
| double cross_track_tolerance_; | ||
| double path_length_tolerance_; | ||
| bool is_overshoot_valid_; | ||
| // Dynamic parameters handler | ||
| rclcpp::node_interfaces::OnSetParametersCallbackHandle::SharedPtr dyn_params_handler_; | ||
| std::string plugin_name_; | ||
| rclcpp::Logger logger_{rclcpp::get_logger("AxisGoalChecker")}; | ||
|
|
||
| /** | ||
| * @brief Callback executed when a parameter change is detected | ||
| * @param parameters list of changed parameters | ||
| */ | ||
| rcl_interfaces::msg::SetParametersResult | ||
| dynamicParametersCallback(std::vector<rclcpp::Parameter> parameters); | ||
| }; | ||
|
|
||
| } // namespace nav2_controller | ||
|
|
||
| #endif // NAV2_CONTROLLER__PLUGINS__AXIS_GOAL_CHECKER_HPP_ | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,200 @@ | ||||||||||||||||
| // Copyright (c) 2025 Dexory | ||||||||||||||||
| // | ||||||||||||||||
| // 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 <memory> | ||||||||||||||||
| #include <string> | ||||||||||||||||
| #include <limits> | ||||||||||||||||
| #include <vector> | ||||||||||||||||
|
|
||||||||||||||||
| #include "angles/angles.h" | ||||||||||||||||
| #include "nav2_controller/plugins/axis_goal_checker.hpp" | ||||||||||||||||
| #include "pluginlib/class_list_macros.hpp" | ||||||||||||||||
| #include "nav2_ros_common/node_utils.hpp" | ||||||||||||||||
| #include "nav2_util/geometry_utils.hpp" | ||||||||||||||||
|
|
||||||||||||||||
| using rcl_interfaces::msg::ParameterType; | ||||||||||||||||
| using std::placeholders::_1; | ||||||||||||||||
|
|
||||||||||||||||
| namespace nav2_controller | ||||||||||||||||
| { | ||||||||||||||||
|
|
||||||||||||||||
| AxisGoalChecker::AxisGoalChecker() | ||||||||||||||||
| : along_path_tolerance_(0.25), cross_track_tolerance_(0.25), | ||||||||||||||||
| path_length_tolerance_(1.0), is_overshoot_valid_(false) | ||||||||||||||||
| { | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| void AxisGoalChecker::initialize( | ||||||||||||||||
| const nav2::LifecycleNode::WeakPtr & parent, | ||||||||||||||||
| const std::string & plugin_name, | ||||||||||||||||
| const std::shared_ptr<nav2_costmap_2d::Costmap2DROS>/*costmap_ros*/) | ||||||||||||||||
| { | ||||||||||||||||
| plugin_name_ = plugin_name; | ||||||||||||||||
| auto node = parent.lock(); | ||||||||||||||||
| logger_ = node->get_logger(); | ||||||||||||||||
|
|
||||||||||||||||
| along_path_tolerance_ = node->declare_or_get_parameter( | ||||||||||||||||
| plugin_name + ".along_path_tolerance", 0.25); | ||||||||||||||||
| cross_track_tolerance_ = node->declare_or_get_parameter( | ||||||||||||||||
| plugin_name + ".cross_track_tolerance", 0.25); | ||||||||||||||||
tonynajjar marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
| path_length_tolerance_ = node->declare_or_get_parameter( | ||||||||||||||||
| plugin_name + ".path_length_tolerance", 1.0); | ||||||||||||||||
| is_overshoot_valid_ = node->declare_or_get_parameter( | ||||||||||||||||
| plugin_name + ".is_overshoot_valid", false); | ||||||||||||||||
|
|
||||||||||||||||
tonynajjar marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
| // Add callback for dynamic parameters | ||||||||||||||||
| dyn_params_handler_ = node->add_on_set_parameters_callback( | ||||||||||||||||
| std::bind(&AxisGoalChecker::dynamicParametersCallback, this, _1)); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| void AxisGoalChecker::reset() | ||||||||||||||||
| { | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| bool AxisGoalChecker::isGoalReached( | ||||||||||||||||
| const geometry_msgs::msg::Pose & query_pose, const geometry_msgs::msg::Pose & goal_pose, | ||||||||||||||||
| const geometry_msgs::msg::Twist &, | ||||||||||||||||
| const nav_msgs::msg::Path & transformed_global_plan) | ||||||||||||||||
| { | ||||||||||||||||
| // If the local plan length is longer than the tolerance, we skip the check | ||||||||||||||||
| if (nav2_util::geometry_utils::calculate_path_length(transformed_global_plan) > | ||||||||||||||||
| path_length_tolerance_) | ||||||||||||||||
| { | ||||||||||||||||
| return false; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // Check if we have at least 2 poses to determine path direction | ||||||||||||||||
| if (transformed_global_plan.poses.size() >= 2) { | ||||||||||||||||
| // Use axis-aligned goal checking with path direction | ||||||||||||||||
| // Find a pose before goal that is sufficiently far from goal | ||||||||||||||||
| const geometry_msgs::msg::Pose * before_goal_pose_ptr = nullptr; | ||||||||||||||||
| double dx = 0.0; | ||||||||||||||||
| double dy = 0.0; | ||||||||||||||||
|
|
||||||||||||||||
| for (int i = transformed_global_plan.poses.size() - 2; i >= 0; --i) { | ||||||||||||||||
| const auto & candidate_pose = transformed_global_plan.poses[i].pose; | ||||||||||||||||
| dx = goal_pose.position.x - candidate_pose.position.x; | ||||||||||||||||
| dy = goal_pose.position.y - candidate_pose.position.y; | ||||||||||||||||
| double pose_distance = std::hypot(dx, dy); | ||||||||||||||||
|
|
||||||||||||||||
| if (pose_distance >= 1e-6) { | ||||||||||||||||
tonynajjar marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
| before_goal_pose_ptr = &candidate_pose; | ||||||||||||||||
| break; | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // If all poses are too close to goal, fall back to simple distance check | ||||||||||||||||
| if (!before_goal_pose_ptr) { | ||||||||||||||||
| RCLCPP_DEBUG( | ||||||||||||||||
| logger_, | ||||||||||||||||
| "All poses in path are too close to goal, falling back to simple distance check"); | ||||||||||||||||
| double distance_to_goal = std::hypot( | ||||||||||||||||
| goal_pose.position.x - query_pose.position.x, | ||||||||||||||||
| goal_pose.position.y - query_pose.position.y); | ||||||||||||||||
| double tolerance = std::hypot(along_path_tolerance_, cross_track_tolerance_); | ||||||||||||||||
| return distance_to_goal < tolerance; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // end of path direction | ||||||||||||||||
| double end_of_path_yaw = atan2(dy, dx); | ||||||||||||||||
|
|
||||||||||||||||
| // Check if robot is already at goal (would cause atan2(0,0)) | ||||||||||||||||
| double robot_to_goal_dx = goal_pose.position.x - query_pose.position.x; | ||||||||||||||||
| double robot_to_goal_dy = goal_pose.position.y - query_pose.position.y; | ||||||||||||||||
| double distance_to_goal = std::hypot(robot_to_goal_dx, robot_to_goal_dy); | ||||||||||||||||
|
|
||||||||||||||||
| if (distance_to_goal < 1e-6) { | ||||||||||||||||
tonynajjar marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
| return true; // Robot is at goal | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| double robot_to_goal_yaw = atan2(robot_to_goal_dy, robot_to_goal_dx); | ||||||||||||||||
| double projection_angle = angles::shortest_angular_distance( | ||||||||||||||||
| robot_to_goal_yaw, end_of_path_yaw); | ||||||||||||||||
| double along_path_distance = distance_to_goal * cos(projection_angle); | ||||||||||||||||
| double cross_track_distance = distance_to_goal * sin(projection_angle); | ||||||||||||||||
|
|
||||||||||||||||
| if (is_overshoot_valid_) { | ||||||||||||||||
| return along_path_distance < along_path_tolerance_ && | ||||||||||||||||
tonynajjar marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
| return along_path_distance < along_path_tolerance_ && | |
| return along_path_distance < along_path_tolerance_ && | |
| along_path_distance > -along_path_tolerance_ && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
intended behavior but FYI @doisyg
tonynajjar marked this conversation as resolved.
Show resolved
Hide resolved
tonynajjar marked this conversation as resolved.
Show resolved
Hide resolved
tonynajjar marked this conversation as resolved.
Show resolved
Hide resolved
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The orientation tolerance is set to M_PI_2 (90 degrees), which doesn't align with the goal checker's design that ignores orientation entirely. For consistency with PositionGoalChecker (which also doesn't check orientation), this should be set to an identity quaternion (x=0, y=0, z=0, w=1) to indicate that orientation is not checked, rather than using an arbitrary angle value.
| pose_tolerance.orientation = | |
| nav2_util::geometry_utils::orientationAroundZAxis(M_PI_2); | |
| // Identity quaternion indicates that orientation is not checked | |
| pose_tolerance.orientation.x = 0.0; | |
| pose_tolerance.orientation.y = 0.0; | |
| pose_tolerance.orientation.z = 0.0; | |
| pose_tolerance.orientation.w = 1.0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pose_tolerance.orientation isn't actually used anywhere but conceptually shouldn't it be nav2_util::geometry_utils::orientationAroundZAxis(M_PI) to indicate that orientation is completely unconstrained. @doisyg
Uh oh!
There was an error while loading. Please reload this page.