Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
0e56130
Added BT ID finder and used createTree to resolve btcpp warning
Jad-ELHAJJ Aug 25, 2025
5bdac96
Support using either bt file path or bt id
Jad-ELHAJJ Aug 25, 2025
70cd1b7
Not necessarily a file name
Jad-ELHAJJ Aug 25, 2025
f2ac463
fix logic
Jad-ELHAJJ Aug 25, 2025
daf4271
fix function definition
Jad-ELHAJJ Aug 25, 2025
b87c64a
fixed unit test
Jad-ELHAJJ Aug 26, 2025
24928d9
fixed BB variable
Jad-ELHAJJ Aug 27, 2025
1823f8e
fixed error msg
Jad-ELHAJJ Aug 27, 2025
0046752
fixed variable and its getter naming
Jad-ELHAJJ Aug 28, 2025
b7afc39
fixed definition
Jad-ELHAJJ Aug 28, 2025
7e40323
fix xml check
Jad-ELHAJJ Aug 28, 2025
8760146
fix bt unit test
Jad-ELHAJJ Aug 28, 2025
5b4a75b
test
Jad-ELHAJJ Aug 28, 2025
2e83f9e
added back test
Jad-ELHAJJ Aug 29, 2025
d7a59a0
check bt id using the root or bt id
Jad-ELHAJJ Aug 29, 2025
4684439
using tinyxml2 instead of regex
Jad-ELHAJJ Aug 29, 2025
7e56b46
fixed var name
Jad-ELHAJJ Aug 29, 2025
6910540
fixed var name
Jad-ELHAJJ Aug 29, 2025
5e7b1f6
check if arg is already a BT ID
Jad-ELHAJJ Aug 29, 2025
7dd0d2e
check if arg is already a BT ID
Jad-ELHAJJ Aug 29, 2025
8dfb41b
Test was failing due to same BT ID MainTree among all registred trees
Jad-ELHAJJ Aug 29, 2025
95a7213
Fixed error msg to be compliant
Jad-ELHAJJ Sep 1, 2025
210a8cd
python linting
Jad-ELHAJJ Sep 1, 2025
c80770c
Removed unused createTreeFromFile since its replaced with createTree
Jad-ELHAJJ Sep 1, 2025
4f30812
PR fixes
Jad-ELHAJJ Sep 5, 2025
7982498
Added new line at the end of BT xml
Jad-ELHAJJ Sep 5, 2025
e4865e7
Should cover most of the cases
Jad-ELHAJJ Sep 5, 2025
0e7bf9c
format
Jad-ELHAJJ Sep 5, 2025
8843105
Fixed BT format
Jad-ELHAJJ Sep 8, 2025
4524a28
Removed redundant check
Jad-ELHAJJ Sep 8, 2025
f4108b4
Allow usage of file paths with a warning, while keeping BT ID usage a…
Jad-ELHAJJ Sep 12, 2025
3da4fba
Additional test
Jad-ELHAJJ Sep 12, 2025
05147ee
Test coverage
Jad-ELHAJJ Sep 17, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,23 @@ class BehaviorTreeEngine
const std::string & file_path,
BT::Blackboard::Ptr blackboard);

/**
* @brief Extract BehaviorTree ID from BT file path or BT ID
* @param file_or_id
* @return std::string
*/
std::string extractBehaviorTreeID(const std::string & file_or_id);

/**
* @brief Function to create a BT from a BehaviorTree ID
* @param tree_id BehaviorTree ID
* @param blackboard Blackboard for BT
* @return BT::Tree Created behavior tree
*/
BT::Tree createTree(
const std::string & tree_id,
BT::Blackboard::Ptr blackboard);

/**
* @brief Add Groot2 monitor to publish BT status changes
* @param tree BT to monitor
Expand Down
30 changes: 18 additions & 12 deletions nav2_behavior_tree/include/nav2_behavior_tree/bt_action_server.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,18 @@ class BtActionServer

/**
* @brief Replace current BT with another one
* @param bt_xml_filename The file containing the new BT, uses default filename if empty
* @return bool true if the resulting BT correspond to the one in bt_xml_filename. false
* @param bt_xml_filename_or_id The file containing the new BT, uses default filename if empty or BT ID
* @return bool true if the resulting BT correspond to the one in bt_xml_filename_or_id. false
* if something went wrong, and previous BT is maintained
*/
bool loadBehaviorTree(
const std::string & bt_xml_filename = "");
const std::string & bt_xml_filename_or_id = "");

/** @brief Extract BehaviorTree ID from XML file
* @param filename The file containing the BT
* @return std::string BehaviorTree ID if found, empty string otherwise
*/
std::string extractBehaviorTreeID(const std::string & file_or_id);

/**
* @brief Getter function for BT Blackboard
Expand All @@ -119,18 +125,18 @@ class BtActionServer
* @brief Getter function for current BT XML filename
* @return string Containing current BT XML filename
*/
std::string getCurrentBTFilename() const
std::string getCurrentBTFilenameOrID() const
{
return current_bt_xml_filename_;
return current_bt_file_or_id_;
}

/**
* @brief Getter function for default BT XML filename
* @return string Containing default BT XML filename
* @brief Getter function for default BT XML filename or ID
* @return string Containing default BT XML filename or ID
*/
std::string getDefaultBTFilename() const
std::string getDefaultBTFilenameOrID() const
{
return default_bt_xml_filename_;
return default_bt_xml_filename_or_id_;
}

/**
Expand Down Expand Up @@ -245,8 +251,8 @@ class BtActionServer
BT::Blackboard::Ptr blackboard_;

// The XML file that contains the Behavior Tree to create
std::string current_bt_xml_filename_;
std::string default_bt_xml_filename_;
std::string current_bt_file_or_id_;
std::string default_bt_xml_filename_or_id_;
std::vector<std::string> search_directories_;

// The wrapper class for the BT functionality
Expand Down Expand Up @@ -283,7 +289,7 @@ class BtActionServer
std::chrono::milliseconds wait_for_service_timeout_;

// should the BT be reloaded even if the same xml filename is requested?
bool always_reload_bt_xml_ = false;
bool always_reload_bt_ = false;

// Parameters for Groot2 monitoring
bool enable_groot_monitoring_ = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <limits>
#include <memory>
#include <set>
#include <unordered_set>
#include <string>
#include <vector>

Expand All @@ -45,7 +46,7 @@ BtActionServer<ActionT, NodeT>::BtActionServer(
OnPreemptCallback on_preempt_callback,
OnCompletionCallback on_completion_callback)
: action_name_(action_name),
default_bt_xml_filename_(default_bt_xml_filename),
default_bt_xml_filename_or_id_(default_bt_xml_filename),
search_directories_(search_directories),
plugin_lib_names_(plugin_lib_names),
node_(parent),
Expand Down Expand Up @@ -178,7 +179,7 @@ bool BtActionServer<ActionT, NodeT>::on_configure()
int wait_for_service_timeout;
node->get_parameter("wait_for_service_timeout", wait_for_service_timeout);
wait_for_service_timeout_ = std::chrono::milliseconds(wait_for_service_timeout);
node->get_parameter("always_reload_bt_xml", always_reload_bt_xml_);
node->get_parameter("always_reload_bt_xml", always_reload_bt_);

// Get error code id names to grab off of the blackboard
error_code_name_prefixes_ = node->get_parameter("error_code_name_prefixes").as_string_array();
Expand All @@ -204,8 +205,8 @@ template<class ActionT, class NodeT>
bool BtActionServer<ActionT, NodeT>::on_activate()
{
resetInternalError();
if (!loadBehaviorTree(default_bt_xml_filename_)) {
RCLCPP_ERROR(logger_, "Error loading XML file: %s", default_bt_xml_filename_.c_str());
if (!loadBehaviorTree(default_bt_xml_filename_or_id_)) {
RCLCPP_ERROR(logger_, "Error loading BT: %s", default_bt_xml_filename_or_id_.c_str());
return false;
}
action_server_->activate();
Expand All @@ -228,7 +229,7 @@ bool BtActionServer<ActionT, NodeT>::on_cleanup()
action_server_.reset();
topic_logger_.reset();
plugin_lib_names_.clear();
current_bt_xml_filename_.clear();
current_bt_file_or_id_.clear();
blackboard_.reset();
bt_->haltAllActions(tree_);
bt_->resetGrootMonitor();
Expand All @@ -246,40 +247,49 @@ void BtActionServer<ActionT, NodeT>::setGrootMonitoring(
}

template<class ActionT, class NodeT>
bool BtActionServer<ActionT, NodeT>::loadBehaviorTree(const std::string & bt_xml_filename)
bool BtActionServer<ActionT, NodeT>::loadBehaviorTree(const std::string & bt_xml_filename_or_id)
{
namespace fs = std::filesystem;

// Empty filename is default for backward compatibility
auto filename = bt_xml_filename.empty() ? default_bt_xml_filename_ : bt_xml_filename;
// Empty argument is default for backward compatibility
auto file_or_id =
bt_xml_filename_or_id.empty() ? default_bt_xml_filename_or_id_ : bt_xml_filename_or_id;

// Use previous BT if it is the existing one and always reload flag is not set to true
if (!always_reload_bt_xml_ && current_bt_xml_filename_ == filename) {
RCLCPP_DEBUG(logger_, "BT will not be reloaded as the given xml is already loaded");
if (!always_reload_bt_ && current_bt_file_or_id_ == file_or_id) {
RCLCPP_DEBUG(logger_, "BT will not be reloaded as the given xml or ID is already loaded");
return true;
}

// Reset any existing Groot2 monitoring
bt_->resetGrootMonitor();

std::ifstream xml_file(filename);
if (!xml_file.good()) {
setInternalError(ActionT::Result::FAILED_TO_LOAD_BEHAVIOR_TREE,
"Couldn't open BT XML file: " + filename);
return false;
bool is_bt_id = false;
if ((file_or_id.length() < 4) ||
file_or_id.substr(file_or_id.length() - 4) != ".xml")
{
is_bt_id = true;
}

const auto canonical_main_bt = fs::canonical(filename);

// Register all XML behavior Subtrees found in the given directories
std::unordered_set<std::string> used_bt_id;
for (const auto & directory : search_directories_) {
try {
for (const auto & entry : fs::directory_iterator(directory)) {
if (entry.path().extension() == ".xml") {
// Skip registering the main tree file
if (fs::equivalent(fs::canonical(entry.path()), canonical_main_bt)) {
auto current_bt_id = bt_->extractBehaviorTreeID(entry.path().string());
if (current_bt_id.empty()) {
RCLCPP_ERROR(logger_, "Skipping BT file %s (missing ID)",
entry.path().string().c_str());
continue;
}
auto [it, inserted] = used_bt_id.insert(current_bt_id);
if (!inserted) {
RCLCPP_WARN(
logger_,
"Warning: Duplicate BT IDs found. Make sure to have all BT IDs unique! "
"ID: %s File: %s",
current_bt_id.c_str(), entry.path().string().c_str());
}
bt_->registerTreeFromFile(entry.path().string());
}
}
Expand All @@ -289,18 +299,21 @@ bool BtActionServer<ActionT, NodeT>::loadBehaviorTree(const std::string & bt_xml
return false;
}
}

// Try to load the main BT tree
// Try to load the main BT tree (by ID)
try {
tree_ = bt_->createTreeFromFile(filename, blackboard_);
if(!is_bt_id) {
tree_ = bt_->createTreeFromFile(file_or_id, blackboard_);
} else {
tree_ = bt_->createTree(file_or_id, blackboard_);
}

for (auto & subtree : tree_.subtrees) {
auto & blackboard = subtree->blackboard;
blackboard->set("node", client_node_);
blackboard->set<std::chrono::milliseconds>("server_timeout", default_server_timeout_);
blackboard->set<std::chrono::milliseconds>("bt_loop_duration", bt_loop_duration_);
blackboard->set<std::chrono::milliseconds>(
"wait_for_service_timeout",
wait_for_service_timeout_);
"wait_for_service_timeout", wait_for_service_timeout_);
}
} catch (const std::exception & e) {
setInternalError(ActionT::Result::FAILED_TO_LOAD_BEHAVIOR_TREE,
Expand All @@ -310,8 +323,7 @@ bool BtActionServer<ActionT, NodeT>::loadBehaviorTree(const std::string & bt_xml

// Optional logging and monitoring
topic_logger_ = std::make_unique<RosTopicLogger>(client_node_, tree_);

current_bt_xml_filename_ = filename;
current_bt_file_or_id_ = file_or_id;

if (enable_groot_monitoring_) {
bt_->addGrootMonitoring(&tree_, groot_server_port_);
Expand Down
46 changes: 46 additions & 0 deletions nav2_behavior_tree/src/behavior_tree_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <memory>
#include <string>
#include <vector>
#include "tinyxml2.h" //NOLINT

#include "rclcpp/rclcpp.hpp"
#include "behaviortree_cpp/json_export.h"
Expand Down Expand Up @@ -102,6 +103,51 @@ BehaviorTreeEngine::createTreeFromFile(
return factory_.createTreeFromFile(file_path, blackboard);
}

std::string BehaviorTreeEngine::extractBehaviorTreeID(
const std::string & bt_file)
{
if(bt_file.empty()) {
RCLCPP_ERROR(rclcpp::get_logger("BehaviorTreeEngine"),
"Error: Empty BT file passed to extractBehaviorTreeID");
return "";
}
tinyxml2::XMLDocument doc;
if (doc.LoadFile(bt_file.c_str()) != tinyxml2::XML_SUCCESS) {
RCLCPP_ERROR(rclcpp::get_logger("BehaviorTreeEngine"), "Error: Could not open or parse file %s",
bt_file.c_str());
return "";
}
tinyxml2::XMLElement * rootElement = doc.RootElement();
if (!rootElement) {
RCLCPP_ERROR(rclcpp::get_logger("BehaviorTreeEngine"), "Error: Root element not found in %s",
bt_file.c_str());
return "";
}
tinyxml2::XMLElement * btElement = rootElement->FirstChildElement("BehaviorTree");
if (!btElement) {
RCLCPP_ERROR(rclcpp::get_logger("BehaviorTreeEngine"),
"Error: <BehaviorTree> element not found in %s", bt_file.c_str());
return "";
}
const char * idValue = btElement->Attribute("ID");
if (idValue) {
return std::string(idValue);
} else {
RCLCPP_ERROR(rclcpp::get_logger("BehaviorTreeEngine"),
"Error: ID attribute not found on <BehaviorTree> element in %s",
bt_file.c_str());
return "";
}
}

BT::Tree
BehaviorTreeEngine::createTree(
const std::string & tree_id,
BT::Blackboard::Ptr blackboard)
{
return factory_.createTree(tree_id, blackboard);
}

/// @brief Register a tree from an XML file and return the tree
void BehaviorTreeEngine::registerTreeFromFile(
const std::string & file_path)
Expand Down
4 changes: 2 additions & 2 deletions nav2_bt_navigator/behavior_trees/follow_point.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
This Behavior Tree follows a dynamic pose to a certain distance
-->

<root BTCPP_format="4" main_tree_to_execute="MainTree">
<BehaviorTree ID="MainTree">
<root BTCPP_format="4" main_tree_to_execute="FollowPoint">
<BehaviorTree ID="FollowPoint">
<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"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
recovery actions specific to planning / control as well as general system issues.
This will be continuous if a kinematically valid planner is selected.
-->
<root BTCPP_format="4" main_tree_to_execute="MainTree">
<BehaviorTree ID="MainTree">
<root BTCPP_format="4" main_tree_to_execute="NavToPoseWithConsistentReplanningAndIfPathBecomesInvalid">
<BehaviorTree ID="NavToPoseWithConsistentReplanningAndIfPathBecomesInvalid">
<RecoveryNode number_of_retries="6" name="NavigateRecovery">
<PipelineSequence name="NavigateWithReplanning">
<ControllerSelector selected_controller="{selected_controller}" default_controller="FollowPath" topic_name="controller_selector"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
It also has recovery actions specific to planning / control as well as general system issues.
-->

<root BTCPP_format="4" main_tree_to_execute="MainTree">
<BehaviorTree ID="MainTree">
<root BTCPP_format="4" main_tree_to_execute="NavigateOnRouteGraphWRecovery">
<BehaviorTree ID="NavigateOnRouteGraphWRecovery">
<RecoveryNode number_of_retries="6" name="NavigateRecovery">
<PipelineSequence name="NavigateWithReplanning">
<ControllerSelector selected_controller="{selected_controller}" default_controller="FollowPath" topic_name="controller_selector"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
This Behavior Tree replans the global path periodically at 1 Hz through an array of poses continuously
and it also has recovery actions specific to planning / control as well as general system issues.
-->
<root BTCPP_format="4" main_tree_to_execute="MainTree">
<BehaviorTree ID="MainTree">
<root BTCPP_format="4" main_tree_to_execute="NavigateThroughPosesWReplanningAndRecovery">
<BehaviorTree ID="NavigateThroughPosesWReplanningAndRecovery">
<RecoveryNode number_of_retries="6" name="NavigateRecovery">
<PipelineSequence name="NavigateWithReplanning">
<ProgressCheckerSelector selected_progress_checker="{selected_progress_checker}" default_progress_checker="progress_checker" topic_name="progress_checker_selector"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
recovery actions specific to planning / control as well as general system issues.
This will be continuous if a kinematically valid planner is selected.
-->
<root BTCPP_format="4" main_tree_to_execute="MainTree">
<BehaviorTree ID="MainTree">
<root BTCPP_format="4" main_tree_to_execute="NavigateToPoseWReplanningAndRecovery">
<BehaviorTree ID="NavigateToPoseWReplanningAndRecovery">
<RecoveryNode number_of_retries="6" name="NavigateRecovery">
<PipelineSequence name="NavigateWithReplanning">
<ControllerSelector selected_controller="{selected_controller}" default_controller="FollowPath" topic_name="controller_selector"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
make the robot wait for a specific time, to see if the obstacle clears out before
navigating along a significantly longer path to reach the goal location.
-->
<root BTCPP_format="4" main_tree_to_execute="MainTree">
<BehaviorTree ID="MainTree">
<root BTCPP_format="4" main_tree_to_execute="NavigateToPoseWReplanningGoalPatienceAndRecovery">
<BehaviorTree ID="NavigateToPoseWReplanningGoalPatienceAndRecovery">
<RecoveryNode number_of_retries="6" name="NavigateRecovery">
<PipelineSequence name="NavigateWithReplanning">
<ControllerSelector selected_controller="{selected_controller}" default_controller="FollowPath" topic_name="controller_selector"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
recovery actions specific to planning / control as well as general system issues.
This will be continuous if a kinematically valid planner is selected.
-->
<root BTCPP_format="4" main_tree_to_execute="MainTree">
<BehaviorTree ID="MainTree">
<root BTCPP_format="4" main_tree_to_execute="NavigateWRecoveryAndReplanningOnlyIfPathBecomesInvalid">
<BehaviorTree ID="NavigateWRecoveryAndReplanningOnlyIfPathBecomesInvalid">
<RecoveryNode number_of_retries="6" name="NavigateRecovery">
<PipelineSequence>
<ControllerSelector selected_controller="{selected_controller}" default_controller="FollowPath" topic_name="controller_selector"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
This Behavior Tree replans the global path after every 1m.
-->

<root BTCPP_format="4" main_tree_to_execute="MainTree">
<BehaviorTree ID="MainTree">
<root BTCPP_format="4" main_tree_to_execute="NavigateWithReplanningDistance">
<BehaviorTree ID="NavigateWithReplanningDistance">
<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"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
This Behavior Tree replans the global path only when the goal is updated.
-->

<root BTCPP_format="4" main_tree_to_execute="MainTree">
<BehaviorTree ID="MainTree">
<root BTCPP_format="4" main_tree_to_execute="NavigateWReplanningOnlyIfGoalIsUpdated">
<BehaviorTree ID="NavigateWReplanningOnlyIfGoalIsUpdated">
<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"/>
Expand Down
Loading
Loading