Skip to content
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

Added promixity BT node and BT tree #4620

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions nav2_behavior_tree/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ list(APPEND plugin_libs nav2_progress_checker_selector_bt_node)
add_library(nav2_goal_updated_controller_bt_node SHARED plugins/decorator/goal_updated_controller.cpp)
list(APPEND plugin_libs nav2_goal_updated_controller_bt_node)

add_library(nav2_is_goal_nearby_condition_bt_node SHARED plugins/condition/is_goal_nearby_condition.cpp)
list(APPEND plugin_libs nav2_is_goal_nearby_condition_bt_node)

foreach(bt_plugin ${plugin_libs})
target_include_directories(${bt_plugin}
PRIVATE
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright (c) 2024 Jakub Chudziński
//
// 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_BEHAVIOR_TREE__PLUGINS__CONDITION__IS_GOAL_NEARBY_CONDITION_HPP_
#define NAV2_BEHAVIOR_TREE__PLUGINS__CONDITION__IS_GOAL_NEARBY_CONDITION_HPP_

#include <string>
#include "nav_msgs/msg/path.hpp"

#include "rclcpp/rclcpp.hpp"
#include "behaviortree_cpp/condition_node.h"


namespace nav2_behavior_tree
{

/**
* @brief A BT::ConditionNode that returns SUCCESS when the IsGoalNearby
* service returns true and FAILURE otherwise
*/
class IsGoalNearbyCondition : public BT::ConditionNode
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs test coverage

{
public:
/**
* @brief A constructor for nav2_behavior_tree::IsGoalNearbyCondition
* @param condition_name Name for the XML tag for this node
* @param conf BT node configuration
*/
IsGoalNearbyCondition(
const std::string & condition_name,
const BT::NodeConfiguration & conf);

IsGoalNearbyCondition() = delete;

/**
* @brief The main override required by a BT action
* @return BT::NodeStatus Status of tick execution
*/
BT::NodeStatus tick() override;

/**
* @brief Creates list of BT ports
* @return BT::PortsList Containing node-specific ports
*/
static BT::PortsList providedPorts()
{
return {
BT::InputPort<nav_msgs::msg::Path>("path", "Planned Path"),
BT::InputPort<double>(
"proximity_threshold", 3.0,
"Proximity length (m) of the remaining path considered as a nearby"),
};
}

private:

/**
* @brief Checks if the robot is in the goal proximity
* @param goal_path current planned path to the goal
* @param prox_thr proximity length (m) of the remaining path considered as a nearby
* @return whether the robot is in the goal proximity
*/
bool isRobotInGoalProximity(
const nav_msgs::msg::Path& goal_path,
const double& prox_thr);
};

} // namespace nav2_behavior_tree

#endif // NAV2_BEHAVIOR_TREE__PLUGINS__CONDITION__IS_GOAL_NEARBY_CONDITION_HPP_
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EOF line

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What should I correct in this case?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add an extra line at the end of the file so that there's 81 lines instead of 81

42 changes: 42 additions & 0 deletions nav2_behavior_tree/plugins/condition/is_goal_nearby_condition.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include "nav2_behavior_tree/plugins/condition/is_goal_nearby_condition.hpp"
#include "nav2_util/geometry_utils.hpp"

namespace nav2_behavior_tree
{

IsGoalNearbyCondition::IsGoalNearbyCondition(
const std::string & condition_name,
const BT::NodeConfiguration & conf)
: BT::ConditionNode(condition_name, conf)
{
}

bool IsGoalNearbyCondition::isRobotInGoalProximity(
const nav_msgs::msg::Path& goal_path,
const double& prox_thr)
{
return nav2_util::geometry_utils::calculate_path_length(goal_path, 0) < prox_thr;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's checking the last path length, but not the robot's proximity. If you replan every 10 seconds (or only on events) then this wouldn't tell you much about the robot's proximity to the goal based on the last path marker.

I think this either needs to

  • find the path's closest point like we do in the Controller plugins [1] which means we need to track the last path index to know where to search starting from on each call up to some maximum distance
  • just be based on a distance check from the robot's current pose -- which has problems if the path have overlapping segments from Navigate Through Poses.

[1] https://github.com/ros-navigation/navigation2/blob/main/nav2_regulated_pure_pursuit_controller/src/path_handler.cpp#L61-L79

Copy link
Author

@Jakubach Jakubach Aug 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am trying to implement the first idea but I am not sure if I've understood it correctly. It's should return a value to the closest point in the path (in case of straight line it would be a next point on the line, and in case of complex path not necessarily)?

Here is my current fragment of the code with that idea:

BT::NodeStatus IsGoalNearbyCondition::tick()
{
    nav_msgs::msg::Path path;
    double prox_thr;
    getInput("path", path);
    getInput("proximity_threshold", prox_thr);

    geometry_msgs::msg::PoseStamped pose; // robot pose in map frame
    nav2_util::getCurrentPose(
    pose, *tf_buffer_,
    "map", "base_link", 0.05);
    
    geometry_msgs::msg::PoseStamped robot_pose; // robot_pose in path frame
    if (!transformPose(path.header.frame_id, pose, robot_pose)) {
        return BT::NodeStatus::FAILURE;
    }

    auto closest_pose_upper_bound =
    nav2_util::geometry_utils::first_after_integrated_distance(
    path.poses.begin(), path.poses.end(), max_robot_pose_search_dist_);


    // First find the closest pose on the path to the robot
    // bounded by when the path turns around (if it does) so we don't get a pose from a later
    // portion of the path
    auto closest_pose_it =
    nav2_util::geometry_utils::min_by(
    path.poses.begin() + last_closest_index_, closest_pose_upper_bound,
    [&robot_pose](const geometry_msgs::msg::PoseStamped & ps) {
      return nav2_util::geometry_utils::euclidean_distance(robot_pose, ps);
    });

    last_closest_index_ = std::distance(path.poses.begin(), closest_pose_it);

    double distance_to_closest_point = nav2_util::geometry_utils::euclidean_distance(robot_pose, *closest_pose_it);
    RCLCPP_INFO(node_->get_logger(), "Distance to closest point: %f", distance_to_closest_point);

    if(distance_to_closest_point < prox_thr){
         return BT::NodeStatus::SUCCESS;
    }
    return BT::NodeStatus::FAILURE;
}


bool IsGoalNearbyCondition::transformPose(
  const std::string frame,
  const geometry_msgs::msg::PoseStamped & in_pose,
  geometry_msgs::msg::PoseStamped & out_pose) const
{
  if (in_pose.header.frame_id == frame) {
    out_pose = in_pose;
    return true;
  }

  try {
    tf_buffer_->transform(in_pose, out_pose, frame);
    out_pose.header.frame_id = frame;
    return true;
  } catch (tf2::TransformException & ex) {
    RCLCPP_ERROR(node_->get_logger(), "Exception in transformPose: %s", ex.what());
  }
  return false;
}

I though it's even working but after few attempts I've got segmentation fault so still working on that

}


BT::NodeStatus IsGoalNearbyCondition::tick()
{
nav_msgs::msg::Path path;
double prox_thr;
getInput("path", path);
getInput("proximity_threshold", prox_thr);
if (!path.poses.empty()){
if(isRobotInGoalProximity(path,prox_thr)){
return BT::NodeStatus::SUCCESS;
}
}
return BT::NodeStatus::FAILURE;
}

} // namespace nav2_behavior_tree

#include "behaviortree_cpp/bt_factory.h"
BT_REGISTER_NODES(factory)
{
factory.registerNodeType<nav2_behavior_tree::IsGoalNearbyCondition>("IsGoalNearby");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to add this to the nav2 node index

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

<!--
This Behavior Tree replans the global path until desired proximity to the goal is achived or if the path becomes invalid. It also has
recovery actions specific to planning / control as well as general system issues.
-->
<root main_tree_to_execute="MainTree">
<BehaviorTree ID="MainTree">
<RecoveryNode number_of_retries="6" name="NavigateRecovery">
<PipelineSequence>
<ControllerSelector selected_controller="{selected_controller}" default_controller="FollowPath" topic_name="controller_selector"/>
<PlannerSelector selected_planner="{selected_planner}" default_planner="GridBased" topic_name="planner_selector"/>
<RateController hz="1.0" name="RateControllerComputePathToPose">
<RecoveryNode number_of_retries="1" name="RecoveryComputePathToPose">
<Fallback name="FallbackComputePathToPose">
<ReactiveSequence name="CheckIfNewPathNeeded">
<Inverter>
<GlobalUpdatedGoal/>
</Inverter>
<IsGoalNearby path="{path}" proximity_threshold="2.0"/>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you test this?

<IsPathValid path="{path}"/>
</ReactiveSequence>
<ComputePathToPose goal="{goal}" path="{path}" planner_id="{selected_planner}" error_code_id="{compute_path_error_code}"/>
</Fallback>
<ClearEntireCostmap name="ClearGlobalCostmap-Context" service_name="global_costmap/clear_entirely_global_costmap"/>
</RecoveryNode>
</RateController>
<RecoveryNode number_of_retries="1" name="RecoveryFollowPath">
<FollowPath path="{path}" controller_id="{selected_controller}" error_code_id="{follow_path_error_code}"/>
<ClearEntireCostmap name="ClearLocalCostmap-Context" service_name="local_costmap/clear_entirely_local_costmap"/>
</RecoveryNode>
</PipelineSequence>
<ReactiveFallback name="FallbackRecoveries">
<GoalUpdated/>
<RoundRobin name="RecoveryActions">
<Sequence name="ClearingActions">
<ClearEntireCostmap name="ClearLocalCostmap-Subtree" service_name="local_costmap/clear_entirely_local_costmap"/>
<ClearEntireCostmap name="ClearGlobalCostmap-Subtree" service_name="global_costmap/clear_entirely_global_costmap"/>
</Sequence>
<Spin name="SpinRecovery" spin_dist="1.57"/>
<Wait name="WaitRecovery" wait_duration="5"/>
<BackUp name="BackUpRecovery" backup_dist="0.30" backup_speed="0.05"/>
</RoundRobin>
</ReactiveFallback>
</RecoveryNode>
</BehaviorTree>
</root>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EOF line

Loading