Behavior Trees in ROS2
1. Behavior Tree Fundamentals
Leaf Nodes (Executable Behaviors)
- Definition:
- Leaf nodes are the executable behaviors where the BT connects with the lowest-level code in the application
- They represent concrete actions or conditions
-
Each leaf node is a class that returns one of three states:
SUCCESS
FAILURE
RUNNING
(for asynchronous operations)
-
Key Characteristics:
- Framework-independent (can be used without ROS2)
- Implement the actual functionality of the behavior tree
- Typically inherit from
BT::ActionNodeBase
or similar base classes
XML Tree Definition
Behavior trees are typically defined in XML files that specify: - The logical structure (sequence, fallback, parallel, etc.) - Node connections - Parameters
<root main_tree_to_execute="MainTree">
<BehaviorTree ID="MainTree">
<Sequence name="main_sequence">
<Action1/>
<Action2/>
</Sequence>
</BehaviorTree>
</root>
2. Control Flow Nodes
Reactive Sequence
- Behavior:
- All children must return SUCCESS for the sequence to succeed
- Children are ticked asynchronously
-
If a running child returns FAILURE, it's halted immediately
-
Use Case:
- When you need to monitor conditions while executing actions
- For reactive behaviors that might need to be interrupted
Other Common Control Nodes
Node Type | Description |
---|---|
Sequence | Ticks children sequentially until one fails |
Fallback | Ticks children sequentially until one succeeds |
Decorator | Modifies the behavior of a single child node |
3. ROS2 Integration Architecture
System Design Recommendations
- Task Planner:
- Centralized coordinator node
- Responsible for BT execution
-
Implemented using BT.CPP
-
Service-Oriented Components:
- Delegate decision-making to Task Planner
- Implement specific capabilities as services
- Should not contain complex business logic
4. Asynchronous Action Nodes
Using rclcpp Action Clients
BT.CPP provides asynchronous action nodes that:
- Don't require manual thread management
- Support abortion (implements TreeNode::halt()
)
- Are reactive to state changes
Fibonacci Action Example
#include <behaviortree_ros2/bt_action_node.hpp>
using namespace BT;
class FibonacciAction: public RosActionNode<Fibonacci>
{
public:
FibonacciAction(const std::string& name,
const NodeConfig& conf,
const RosNodeParams& params)
: RosActionNode<Fibonacci>(name, conf, params)
{}
// The specific ports of this Derived class
// should be merged with the ports of the base class,
// using RosActionNode::providedBasicPorts()
static PortsList providedPorts()
{
return providedBasicPorts({InputPort<unsigned>("order")});
}
// This is called when the TreeNode is ticked and it should
// send the request to the action server
bool setGoal(RosActionNode::Goal& goal) override
{
// get "order" from the Input port
getInput("order", goal.order);
// return true, if we were able to set the goal correctly.
return true;
}
// Callback executed when the reply is received.
// Based on the reply you may decide to return SUCCESS or FAILURE.
NodeStatus onResultReceived(const WrappedResult& wr) override
{
std::stringstream ss;
ss << "Result received: ";
for (auto number : wr.result->sequence) {
ss << number << " ";
}
RCLCPP_INFO(node_->get_logger(), ss.str().c_str());
return NodeStatus::SUCCESS;
}
// Callback invoked when there was an error at the level
// of the communication between client and server.
// This will set the status of the TreeNode to either SUCCESS or FAILURE,
// based on the return value.
// If not overridden, it will return FAILURE by default.
virtual NodeStatus onFailure(ActionNodeErrorCode error) override
{
RCLCPP_ERROR(node_->get_logger(), "Error: %d", error);
return NodeStatus::FAILURE;
}
// we also support a callback for the feedback, as in
// the original tutorial.
// Usually, this callback should return RUNNING, but you
// might decide, based on the value of the feedback, to abort
// the action, and consider the TreeNode completed.
// In that case, return SUCCESS or FAILURE.
// The Cancel request will be send automatically to the server.
NodeStatus onFeedback(const std::shared_ptr<const Feedback> feedback)
{
std::stringstream ss;
ss << "Next number in sequence received: ";
for (auto number : feedback->partial_sequence) {
ss << number << " ";
}
RCLCPP_INFO(node_->get_logger(), ss.str().c_str());
return NodeStatus::RUNNING;
}
};
Registration in Main
BehaviorTreeFactory factory;
auto node = std::make_shared<rclcpp::Node>("fibonacci_action_client");
// provide the ROS node and the name of the action service
RosNodeParams params;
params.nh = node;
params.default_port_value = "fibonacci";
factory.registerNodeType<FibonacciAction>("Fibonacci", params);
5. Service Client Nodes
Using rclcpp Service Clients
Similar to action nodes but for ROS services:
AddTwoInts Service Example
#include <behaviortree_ros2/bt_service_node.hpp>
using AddTwoInts = example_interfaces::srv::AddTwoInts;
using namespace BT;
class AddTwoIntsNode: public RosServiceNode<AddTwoInts>
{
public:
AddTwoIntsNode(const std::string& name,
const NodeConfig& conf,
const RosNodeParams& params)
: RosServiceNode<AddTwoInts>(name, conf, params)
{}
// The specific ports of this Derived class
// should be merged with the ports of the base class,
// using RosServiceNode::providedBasicPorts()
static PortsList providedPorts()
{
return providedBasicPorts({
InputPort<unsigned>("A"),
InputPort<unsigned>("B")});
}
// This is called when the TreeNode is ticked and it should
// send the request to the service provider
bool setRequest(Request::SharedPtr& request) override
{
// use input ports to set A and B
getInput("A", request->a);
getInput("B", request->b);
// must return true if we are ready to send the request
return true;
}
// Callback invoked when the answer is received.
// It must return SUCCESS or FAILURE
NodeStatus onResponseReceived(const Response::SharedPtr& response) override
{
RCLCPP_INFO(node_->get_logger(), "Sum: %ld", response->sum);
return NodeStatus::SUCCESS;
}
// Callback invoked when there was an error at the level
// of the communication between client and server.
// This will set the status of the TreeNode to either SUCCESS or FAILURE,
// based on the return value.
// If not overridden, it will return FAILURE by default.
virtual NodeStatus onFailure(ServiceNodeErrorCode error) override
{
RCLCPP_ERROR(node_->get_logger(), "Error: %d", error);
return NodeStatus::FAILURE;
}
};
6. Practical Example: Simple Navigator
Behavior Tree XML
<root main_tree_to_execute="MainTree">
<BehaviorTree ID="MainTree">
<PipelineSequence name="NavigateWithReplanning">
<DistanceController distance="1.0">
<ComputePathToPose goal="{goal}" path="{path}"/>
</DistanceController>
<FollowPath path="{path}"/>
</PipelineSequence>
</BehaviorTree>
</root>
Explanation
- PipelineSequence: Special sequence node where children are executed in parallel but with ordered conditions
- DistanceController: Controls when path replanning should occur
- ComputePathToPose: Generates a path to the goal pose
- FollowPath: Executes the path following behavior
7. Best Practices
Node Implementation
- Keep leaf nodes focused on single responsibilities
- Use proper error handling in all callbacks
- Make nodes configurable through input ports
System Design
- Keep business logic in the behavior tree
- Make components service-oriented
- Use asynchronous patterns for long-running actions
Performance Considerations
- Minimize blocking operations in leaf nodes
- Use reactive sequences for time-critical behaviors
- Consider using dedicated executors for complex trees
Advanced Nodes and Patterns
1. Action Nodes
Common Navigation Action Nodes
Node | Type | Description |
---|---|---|
ComputePathToPose |
Action Server Client | Planner interface for path computation |
FollowPath |
Action Server Client | Controller interface for path following |
Spin , Wait , Backup |
Behavior Actions | Basic navigation behaviors |
ClearCostmapService |
Service Client | Clears costmaps when called |
2. Condition Nodes
Essential Condition Nodes
Node | Description | ROS Interface |
---|---|---|
GoalUpdated |
Checks if goal on topic has been updated | Subscribes to goal topic |
GoalReached |
Verifies if goal pose is reached | Checks robot state |
InitialPoseReceived |
Checks for initial pose | Subscribes to initial_pose topic |
isBatteryLow |
Monitors battery level | Subscribes to battery topic |
Key Usage Pattern
- Purpose: Implements "If goal updated, then replan" logic - Behavior: Asynchronously checks GoalUpdated while ComputePathToPose runs3. Decorator Nodes
Common Decorators
Decorator | Functionality | Parameters |
---|---|---|
DistanceController |
Ticks child after robot travels specified distance | distance (meters) |
RateController |
Controls tick frequency | hz (frequency) |
GoalUpdater |
Updates child node's goal via ports | Input/output ports |
SingleTrigger |
Only ticks child once | - |
SpeedController |
Tick rate proportional to robot speed | - |
4. PipelineSequence Control Node
Behavior Comparison
Scenario | Traditional Sequence | PipelineSequence |
---|---|---|
Child returns RUNNING | Only ticks running child | Re-ticks all previous successful children |
Child returns FAILURE | Fails entire sequence | Fails entire sequence |
Last child SUCCESS | Sequence succeeds | Sequence succeeds |
Example Usage
<root main_tree_to_execute="MainTree">
<BehaviorTree ID="MainTree">
<PipelineSequence>
<Action_A/>
<Action_B/>
<Action_C/>
</PipelineSequence>
</BehaviorTree>
</root>
5. Recovery Control Node
Behavior Logic
- Ticks first child (main behavior)
- If first child FAILS:
- Ticks second child (recovery behavior)
- Repeats until:
- First child SUCCEEDS (returns SUCCESS)
- Second child FAILS (returns FAILURE)
number_of_retries
exceeded
XML Example
6. RoundRobin Control Node
Behavior Characteristics
- Ticks children in circular order
- Returns SUCCESS if any child succeeds
- Returns FAILURE if all children fail
- Maintains internal pointer to last ticked child
Comparison with Sequence
Feature | Sequence | RoundRobin |
---|---|---|
Start point | Always first child | Last ticked child |
Success condition | All succeed | Any succeeds |
Failure condition | Any fails | All fail |
7. Algorithm Selectors
Dynamic Algorithm Selection
Selector | Purpose | Default Algorithm | ROS Topic |
---|---|---|---|
PlannerSelector |
Switch planners | GridBased | /planner_selector/set_planner |
ControllerSelector |
Switch controllers | FollowPath | /controller_selector/set_controller |
GoalCheckerSelector |
Switch goal checkers | - | /goal_checker_selector/set_checker |
Example Configuration
<ControllerSelector selected_controller="{selected_controller}" default_controller="FollowPath" topic_name="controller_selector"/>
<PlannerSelector selected_planner="{selected_planner}" default_planner="GridBased" topic_name="planner_selector"/>
8. Advanced Navigation Patterns
ComputePathThroughPoses
- Accepts multiple intermediate waypoints
- Replans every 3 seconds
- Uses
RemovePassedGoals
(discards waypoints within 0.5m)
MonitorAndFollowPath (Obstacle Handling)
A specialized monitoring system that runs alongside normal navigation to handle obstacles near the destination. It uses a reactive approach to dynamically adjust the robot's behavior when blocked paths are detected.
1. Obstacle Detection Phase
- Path Length Check: Constantly compares the current path length to the expected distance
- Trigger Condition: Activates when the path becomes significantly longer (typically >20% longer)
- Normal Operation: If path length is normal, navigation continues uninterrupted
2. Recovery Activation
When obstacles are detected: 1. Immediate Stop: - Halts all robot movement as a safety precaution - Prevents collision with nearby obstacles
- Waiting Period:
- Pauses for a configurable duration (typically 5-10 seconds)
- Allows temporary obstacles (like people) to move away
-
Continues monitoring during the entire wait
-
Path Reevaluation:
- If clear: Resumes navigation using the original optimal path
- If still blocked:
- Switches to the longer alternative route
- May repeat the recovery sequence if configured
Typical Use Cases
- Handling crowded delivery destinations
- Navigating through dynamic doorways
- Recovering from mapping errors near goals
- Managing temporary obstructions in final approach
Advantages
- Safety: Prevents forced navigation through blocked areas
- Efficiency: Minimizes unnecessary path recomputations
- Flexibility: Adapts to both permanent and temporary obstacles
- Predictability: Provides consistent recovery behavior
<root 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}"/>
<ClearEntireCostmap name="ClearGlobalCostmap-Context" service_name="global_costmap/clear_entirely_global_costmap"/>
</RecoveryNode>
</RateController>
<ReactiveSequence name="MonitorAndFollowPath">
<PathLongerOnApproach path="{path}" prox_len="3.0" length_factor="2.0">
<RetryUntilSuccessful num_attempts="1">
<SequenceWithMemory name="CancelingControlAndWait">
<CancelControl name="ControlCancel"/>
<Wait wait_duration="5.0"/>
</SequenceWithMemory>
</RetryUntilSuccessful>
</PathLongerOnApproach>
<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}"/>
<ClearEntireCostmap name="ClearLocalCostmap-Context" service_name="local_costmap/clear_entirely_local_costmap"/>
</RecoveryNode>
</ReactiveSequence>
</PipelineSequence>
<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"/>
<BackUp backup_dist="0.30" backup_speed="0.05" error_code_id="{backup_error_code}" error_msg="{backup_error_msg}"/>
</RoundRobin>
</ReactiveFallback>
</RecoveryNode>
</BehaviorTree>
</root>
9. Follow Dynamic Point Pattern
Node Structure
Node | Function | Parameters |
---|---|---|
GoalUpdater |
Updates dynamic goal | ROS topic input |
ComputePathToPose |
Path calculation | 1Hz (RateController) |
TruncatePath |
Shortens path | distance=1.0m |
FollowPath |
Path execution | Continuous |
Complete Example
<root main_tree_to_execute="MainTree">
<BehaviorTree ID="MainTree">
<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">
<Sequence>
<GoalUpdater input_goal="{goal}" output_goal="{updated_goal}">
<ComputePathToPose goal="{updated_goal}" path="{path}" planner_id="{selected_planner}" error_code_id="{compute_path_error_code}" error_msg="{compute_path_error_msg}"/>
</GoalUpdater>
<TruncatePath distance="1.0" input_path="{path}" output_path="{truncated_path}"/>
</Sequence>
</RateController>
<KeepRunningUntilFailure>
<FollowPath path="{truncated_path}" controller_id="{selected_controller}" error_code_id="{follow_path_error_code}" error_msg="{follow_path_error_msg}"/>
</KeepRunningUntilFailure>
</PipelineSequence>
</BehaviorTree>
</root>
10. Odometry Calibration
Square Test Pattern
- 3 counter-clockwise squares (12 segments)
- Low speed (0.2 m/s) for precision
- Measures closure error (final vs initial pose)
Implementation
<root main_tree_to_execute="MainTree">
<BehaviorTree ID="MainTree">
<Repeat num_cycles="3">
<Sequence name="Drive in a square">
<DriveOnHeading dist_to_travel="2.0" speed="0.2" time_allowance="12"/>
<Spin spin_dist="1.570796" is_recovery="false"/>
<DriveOnHeading dist_to_travel="2.0" speed="0.2" time_allowance="12"/>
<Spin spin_dist="1.570796" is_recovery="false"/>
<DriveOnHeading dist_to_travel="2.0" speed="0.2" time_allowance="12"/>
<Spin spin_dist="1.570796" is_recovery="false"/>
<DriveOnHeading dist_to_travel="2.0" speed="0.2" time_allowance="12"/>
<Spin spin_dist="1.570796" is_recovery="false"/>
</Sequence>
</Repeat>
</BehaviorTree>
</root>
11. Node Base Classes
Node Type Selection Guide
Node Type | Base Class | When to Use |
---|---|---|
ROS Action | BtActionNode |
Needs ROS action server |
Decorator | BT::DecoratorNode |
Modifies child behavior |
Control | BT::ControlNode |
Manages flow (Sequence, etc.) |
Condition | BT::ConditionNode |
Boolean checks |
Simple Action | BT::ActionNodeBase |
Non-ROS actions |
BehaviorTree.CPP Node Methods Overview
Method | Required | Description | Typical Use Cases |
---|---|---|---|
Constructor | Yes | Initializes the node with: - XML tag name for plugin matching - ROS 2 action server name - BT.CPP configurations |
Registering the node in the factory Setting default values |
providedPorts() | Yes | Defines input/output ports for: - Parameter configuration - Data flow between nodes |
Declaring node parameters Connecting to blackboard variables |
on_tick() | No | Called when node is executed by the tree: - First method called during tick - Runs before action is sent |
Updating dynamic parameters Resetting node state Input validation |
on_wait_for_result() | No | Called while waiting for action server response | Progress monitoring Timeout checks Preemption conditions |
on_success() | No | Handles successful action completion | Processing result data Updating blackboard Cleanup operations |
on_aborted() | No | Handles action server abortion | Error recovery Failure logging Status reporting |
on_cancelled() | No | Handles action cancellation | Resource cleanup Interruption handling |
Key Notes:
- Required Methods must be implemented for basic functionality
- Optional Methods provide advanced control when implemented
12. Groot: Behavior Tree Visualization and Editing Tool
Overview
Groot is the official graphical editor for Behavior Trees. It provides:
- Visual tree creation and modification
- Real-time monitoring of tree execution (with ROS2 Humble logger is recommended)
- Integration with ROS2 through BehaviorTree.CPP
Key Features
Feature | Description |
---|---|
Drag-and-Drop Interface | Visually construct trees using node palettes |
XML Import/Export | Seamless integration with ROS2 BT implementations |
Node Customization | Edit ports, parameters, and metadata |
Plugin System | Extend with custom node types |
Installation
https://github.com/BehaviorTree/Groot
- Create Tree:
- Drag nodes from palette
- Connect with edges
- Configure node parameters
Custom Nodes Integration
https://docs.nav2.org/plugin_tutorials/docs/writing_new_bt_plugin.html#writing-new-nbt-plugin