Detailed Behavior Tree Walkthrough
Overview
This document serves as a reference guide to the main behavior tree (BT) used in Nav2.
There are many example behavior trees provided in nav2_bt_navigator/behavior_trees,
but these sometimes have to be re-configured based on the application of the robot.
The following document will walk through the current main default BT navigate_to_pose_w_replanning_and_recovery.xml
in great detail.
Navigate To Pose With Replanning and Recovery
The following section will describe in detail the concept of the main and default BT currently used in Nav2, navigate_to_pose_w_replanning_and_recovery.xml.
This behavior tree replans the global path periodically at 1 Hz and it also has recovery actions.
BTs are primarily defined in XML. The tree shown above is represented in XML as follows.
<root BTCPP_format="4" main_tree_to_execute="MainTree">
<BehaviorTree ID="MainTree">
<RecoveryNode number_of_retries="6" name="NavigateRecovery">
<PipelineSequence name="NavigateWithReplanning">
<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">
<RecoveryNode number_of_retries="1" name="ComputePathToPose">
<ComputePathToPose goal="{goal}" path="{path}" planner_id="{selected_planner}" error_code_id="{compute_path_error_code}" error_msg="{compute_path_error_msg}"/>
<Sequence>
<WouldAPlannerRecoveryHelp error_code="{compute_path_error_code}"/>
<ClearEntireCostmap name="ClearGlobalCostmap-Context" service_name="global_costmap/clear_entirely_global_costmap"/>
</Sequence>
</RecoveryNode>
</RateController>
<RecoveryNode number_of_retries="1" name="FollowPath">
<FollowPath path="{path}" controller_id="{selected_controller}" error_code_id="{follow_path_error_code}" error_msg="{follow_path_error_msg}"/>
<Sequence>
<WouldAControllerRecoveryHelp error_code="{follow_path_error_code}"/>
<ClearEntireCostmap name="ClearLocalCostmap-Context" service_name="local_costmap/clear_entirely_local_costmap"/>
</Sequence>
</RecoveryNode>
</PipelineSequence>
<Sequence>
<Fallback>
<WouldAControllerRecoveryHelp error_code="{follow_path_error_code}"/>
<WouldAPlannerRecoveryHelp error_code="{compute_path_error_code}"/>
</Fallback>
<ReactiveFallback name="RecoveryFallback">
<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 spin_dist="1.57" error_code_id="{spin_error_code}" error_msg="{spin_error_msg}"/>
<Wait wait_duration="5.0" error_code_id="{wait_error_code}" error_msg="{wait_error_msg}"/>
<BackUp backup_dist="0.30" backup_speed="0.15" error_code_id="{backup_error_code}" error_msg="{backup_error_msg}"/>
</RoundRobin>
</ReactiveFallback>
</Sequence>
</RecoveryNode>
</BehaviorTree>
</root>
This is likely still a bit overwhelming, but this tree can be broken into two smaller subtrees that we can focus on one at a time.
These smaller subtrees are the children of the top-most RecoveryNode. From this point forward the NavigateWithReplanning subtree will be referred to as the Navigation subtree, and the RecoveryFallback subtree will be known as the Recovery subtree.
This can be represented in the following way:
The Navigation subtree mainly involves actual navigation behavior:
The Recovery subtree includes behaviors for system level failures or items that were not easily dealt with internally.
The overall BT will (hopefully) spend most of its time in the Navigation subtree. If either of the two main behaviors in the Navigation subtree fail
(path calculation or path following), contextual recoveries will be attempted.
If the contextual recoveries were still not enough, the Navigation subtree will return FAILURE.
The system will move on to the Recovery subtree to attempt to clear any system level navigation failures.
This happens until the number_of_retries for the parent RecoveryNode is exceeded (which by default is 6).
<RecoveryNode number_of_retries="6" name="NavigateRecovery">
Navigation Subtree
Now that we have gone over the control flow between the Navigation subtree and the Recovery subtree, let’s focus on the Navigation subtree.
The XML of this subtree is as follows:
<PipelineSequence name="NavigateWithReplanning">
<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">
<RecoveryNode number_of_retries="1" name="ComputePathToPose">
<ComputePathToPose goal="{goal}" path="{path}" planner_id="{selected_planner}" error_code_id="{compute_path_error_code}" error_msg="{compute_path_error_msg}"/>
<Sequence>
<WouldAPlannerRecoveryHelp error_code="{compute_path_error_code}"/>
<ClearEntireCostmap name="ClearGlobalCostmap-Context" service_name="global_costmap/clear_entirely_global_costmap"/>
</Sequence>
</RecoveryNode>
</RateController>
<RecoveryNode number_of_retries="1" name="FollowPath">
<FollowPath path="{path}" controller_id="{selected_controller}" error_code_id="{follow_path_error_code}" error_msg="{follow_path_error_msg}"/>
<Sequence>
<WouldAControllerRecoveryHelp error_code="{follow_path_error_code}"/>
<ClearEntireCostmap name="ClearLocalCostmap-Context" service_name="local_costmap/clear_entirely_local_costmap"/>
</Sequence>
</RecoveryNode>
</PipelineSequence>
This subtree has two primary actions ComputePathToPose and FollowPath.
If either of these two actions fail, they will attempt to clear the failure contextually.
The crux of the tree can be represented with only one parent and two children nodes like this:
The parent PipelineSequence node allows the ComputePathToPose to be ticked, and once that succeeds, FollowPath to be ticked.
While the FollowPath subtree is being ticked, the ComputePathToPose subtree will be ticked as well. This allows for the path to be recomputed as the robot moves around.
Both the ComputePathToPose and the FollowPath follow the same general structure.
The below is the ComputePathToPose subtree:
The parent RecoveryNode controls the flow between the action, and the contextual recovery subtree.
The contextual recoveries for both ComputePathToPose and FollowPath involve checking if the recovery could help clear the error code and clearing the relevant costmap.
Consider changing the number_of_retries parameter in the parent RecoveryNode control node if your application can tolerate more attempts at contextual recoveries before moving on to system-level recoveries.
The only differences in the BT subtree of ComputePathToPose and FollowPath are outlined below:
- The action node in the subtree:
-
- The
RateController that decorates the ComputePathToPose subtree The RateController decorates the ComputePathToPose subtree to keep planning at the specified frequency. The default frequency for this BT is 1 hz.
This is done to prevent the BT from flooding the planning server with too many useless requests at the tree update rate (100Hz). Consider changing this frequency to something higher or lower depending on the application and the computational cost of
calculating the path. There are other decorators that can be used instead of the RateController. Consider using the SpeedController or DistanceController decorators if appropriate.
- The costmap that is being cleared within the contextual recovery:
The ComputePathToPose subtree clears the global costmap. The global costmap is the relevant costmap in the context of the planner
The FollowPath subtree clears the local costmap. The local costmap is the relevant costmap in the context of the controller
This subtree also utilizes the PlannerSelector and ControllerSelector nodes. These nodes ffer flexibility for applications that need to adjust navigation behavior on the fly.
Recovery Subtree
The Recovery subtree is the second big “half” of the Nav2 default navigate_to_pose_w_replanning_and_recovery.xml tree.
In short, this subtree is triggered when the Navigation subtree returns FAILURE and controls the recoveries at the system level (in the case the contextual recoveries in the Navigation subtree were not sufficient).
And the XML snippet:
<Sequence>
<Fallback>
<WouldAControllerRecoveryHelp error_code="{follow_path_error_code}"/>
<WouldAPlannerRecoveryHelp error_code="{compute_path_error_code}"/>
</Fallback>
<ReactiveFallback name="RecoveryFallback">
<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 spin_dist="1.57" error_code_id="{spin_error_code}" error_msg="{spin_error_msg}"/>
<Wait wait_duration="5.0" error_code_id="{wait_error_code}" error_msg="{wait_error_msg}"/>
<BackUp backup_dist="0.30" backup_speed="0.15" error_code_id="{backup_error_code}" error_msg="{backup_error_msg}"/>
</RoundRobin>
</ReactiveFallback>
</Sequence>
At the top level, a Sequence ensures the following steps are executed in order:
A Fallback node first checks whether planner or controller recoveries might help resolve the issue. If either returns SUCCESS, the fallback succeeds and the sequence proceeds to the next step.
A ReactiveFallback that controls the flow between the rest of the system wide recoveries, and asynchronously checks if a new goal has been received.
If at any point the goal gets updated, this subtree will halt all children and return SUCCESS. This allows for quick reactions to new goals and preempt currently executing recoveries.
This should look familiar to the contextual recovery portions of the Navigation subtree. This is a common BT pattern to handle the situation “Unless ‘this condition’ happens, Do action A”.
These condition nodes can be extremely powerful and are typically paired with ReactiveFallback. It can be easy to imagine wrapping this whole navigate_to_pose_w_replanning_and_recovery tree
in a ReactiveFallback with a isBatteryLow condition – meaning the navigate_to_pose_w_replanning_and_recovery tree will execute unless the battery becomes low (and then enter a different subtree for docking to recharge).
If the goal is never updated, the behavior tree will go on to the RoundRobin node. These are the default four system-level recoveries in the BT are:
Upon SUCCESS of any of the four children of the parent RoundRobin, the robot will attempt to renavigate in the Navigation subtree.
If this renavigation was not successful, the next child of the RoundRobin will be ticked.
For example, let’s say the robot is stuck and the Navigation subtree returns FAILURE:
(for the sake of this example, let’s assume that the goal is never updated).
The Costmap clearing sequence in the Recovery subtree is attempted, and returns SUCCESS. The robot now moves to Navigation subtree again
Let’s assume that clearing both costmaps was not sufficient, and the Navigation subtree returns FAILURE once again. The robot now ticks the Recovery subtree
In the Recovery subtree, the Spin action will be ticked. If this returns SUCCESS, then the robot will return to the main Navigation subtree BUT let’s assume that the Spin action returns FAILURE. In this case, the tree will remain in the Recovery subtree
Let’s say the next action, Wait returns SUCCESS. The robot will then move on to the Navigation subtree
Assume the Navigation subtree returns FAILURE (clearing the costmaps, attempting a spin, and waiting were still not sufficient to recover the system). The robot will move onto the Recovery subtree and attempt the BackUp action. Let’s say that the robot attempts the BackUp action and was able to successfully complete the action. The BackUp action node returns SUCCESS and so now we move on to the Navigation subtree again.
In this hypothetical scenario, let’s assume that the BackUp action allowed the robot to successfully navigate in the Navigation subtree, and the robot reaches the goal. In this case, the overall BT will still return SUCCESS.
If the BackUp action was not sufficient enough to allow the robot to become un-stuck, the above logic will go on indefinitely until the number_of_retries in the parent of the Navigate subtree and Recovery subtree is exceeded, or if all the system-wide recoveries in the Recovery subtree return FAILURE (this is unlikely, and likely points to some other system failure).