Skip to content

Commit 7468843

Browse files
authored
Fix/prevent warning when loading bt (#5494)
* Added BT ID finder and used createTree to resolve btcpp warning Signed-off-by: Jad El Hajj <[email protected]> * Support using either bt file path or bt id Signed-off-by: Jad El Hajj <[email protected]> * Not necessarily a file name Signed-off-by: Jad El Hajj <[email protected]> * fix logic Signed-off-by: Jad El Hajj <[email protected]> * fix function definition Signed-off-by: Jad El Hajj <[email protected]> * fixed unit test Signed-off-by: Jad El Hajj <[email protected]> * fixed BB variable Signed-off-by: Jad El Hajj <[email protected]> * fixed error msg Signed-off-by: Jad El Hajj <[email protected]> * fixed variable and its getter naming Signed-off-by: Jad El Hajj <[email protected]> * fixed definition Signed-off-by: Jad El Hajj <[email protected]> * fix xml check Signed-off-by: Jad El Hajj <[email protected]> * fix bt unit test Signed-off-by: Jad El Hajj <[email protected]> * test Signed-off-by: Jad El Hajj <[email protected]> * added back test Signed-off-by: Jad El Hajj <[email protected]> * check bt id using the root or bt id Signed-off-by: Jad El Hajj <[email protected]> * using tinyxml2 instead of regex Signed-off-by: Jad El Hajj <[email protected]> * fixed var name Signed-off-by: Jad El Hajj <[email protected]> * fixed var name Signed-off-by: Jad El Hajj <[email protected]> * check if arg is already a BT ID Signed-off-by: Jad El Hajj <[email protected]> * check if arg is already a BT ID Signed-off-by: Jad El Hajj <[email protected]> * Test was failing due to same BT ID MainTree among all registred trees Signed-off-by: Jad El Hajj <[email protected]> * Fixed error msg to be compliant Signed-off-by: Jad El Hajj <[email protected]> * python linting Signed-off-by: Jad El Hajj <[email protected]> * Removed unused createTreeFromFile since its replaced with createTree Signed-off-by: Jad El Hajj <[email protected]> * PR fixes Signed-off-by: Jad El Hajj <[email protected]> * Added new line at the end of BT xml Signed-off-by: Jad El Hajj <[email protected]> * Should cover most of the cases Signed-off-by: Jad El Hajj <[email protected]> * format Signed-off-by: Jad El Hajj <[email protected]> * Fixed BT format Signed-off-by: Jad El Hajj <[email protected]> * Removed redundant check Signed-off-by: Jad El Hajj <[email protected]> * Allow usage of file paths with a warning, while keeping BT ID usage as the recommended option Signed-off-by: Jad El Hajj <[email protected]> * Additional test Signed-off-by: Jad El Hajj <[email protected]> * Test coverage Signed-off-by: Jad El Hajj <[email protected]> --------- Signed-off-by: Jad El Hajj <[email protected]>
1 parent 4d05d23 commit 7468843

22 files changed

+354
-104
lines changed

nav2_behavior_tree/include/nav2_behavior_tree/behavior_tree_engine.hpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,23 @@ class BehaviorTreeEngine
8686
const std::string & file_path,
8787
BT::Blackboard::Ptr blackboard);
8888

89+
/**
90+
* @brief Extract BehaviorTree ID from BT file path or BT ID
91+
* @param file_or_id
92+
* @return std::string
93+
*/
94+
std::string extractBehaviorTreeID(const std::string & file_or_id);
95+
96+
/**
97+
* @brief Function to create a BT from a BehaviorTree ID
98+
* @param tree_id BehaviorTree ID
99+
* @param blackboard Blackboard for BT
100+
* @return BT::Tree Created behavior tree
101+
*/
102+
BT::Tree createTree(
103+
const std::string & tree_id,
104+
BT::Blackboard::Ptr blackboard);
105+
89106
/**
90107
* @brief Add Groot2 monitor to publish BT status changes
91108
* @param tree BT to monitor

nav2_behavior_tree/include/nav2_behavior_tree/bt_action_server.hpp

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,18 @@ class BtActionServer
9999

100100
/**
101101
* @brief Replace current BT with another one
102-
* @param bt_xml_filename The file containing the new BT, uses default filename if empty
103-
* @return bool true if the resulting BT correspond to the one in bt_xml_filename. false
102+
* @param bt_xml_filename_or_id The file containing the new BT, uses default filename if empty or BT ID
103+
* @return bool true if the resulting BT correspond to the one in bt_xml_filename_or_id. false
104104
* if something went wrong, and previous BT is maintained
105105
*/
106106
bool loadBehaviorTree(
107-
const std::string & bt_xml_filename = "");
107+
const std::string & bt_xml_filename_or_id = "");
108+
109+
/** @brief Extract BehaviorTree ID from XML file
110+
* @param filename The file containing the BT
111+
* @return std::string BehaviorTree ID if found, empty string otherwise
112+
*/
113+
std::string extractBehaviorTreeID(const std::string & file_or_id);
108114

109115
/**
110116
* @brief Getter function for BT Blackboard
@@ -119,18 +125,18 @@ class BtActionServer
119125
* @brief Getter function for current BT XML filename
120126
* @return string Containing current BT XML filename
121127
*/
122-
std::string getCurrentBTFilename() const
128+
std::string getCurrentBTFilenameOrID() const
123129
{
124-
return current_bt_xml_filename_;
130+
return current_bt_file_or_id_;
125131
}
126132

127133
/**
128-
* @brief Getter function for default BT XML filename
129-
* @return string Containing default BT XML filename
134+
* @brief Getter function for default BT XML filename or ID
135+
* @return string Containing default BT XML filename or ID
130136
*/
131-
std::string getDefaultBTFilename() const
137+
std::string getDefaultBTFilenameOrID() const
132138
{
133-
return default_bt_xml_filename_;
139+
return default_bt_xml_filename_or_id_;
134140
}
135141

136142
/**
@@ -245,8 +251,8 @@ class BtActionServer
245251
BT::Blackboard::Ptr blackboard_;
246252

247253
// The XML file that contains the Behavior Tree to create
248-
std::string current_bt_xml_filename_;
249-
std::string default_bt_xml_filename_;
254+
std::string current_bt_file_or_id_;
255+
std::string default_bt_xml_filename_or_id_;
250256
std::vector<std::string> search_directories_;
251257

252258
// The wrapper class for the BT functionality
@@ -283,7 +289,7 @@ class BtActionServer
283289
std::chrono::milliseconds wait_for_service_timeout_;
284290

285291
// should the BT be reloaded even if the same xml filename is requested?
286-
bool always_reload_bt_xml_ = false;
292+
bool always_reload_bt_ = false;
287293

288294
// Parameters for Groot2 monitoring
289295
bool enable_groot_monitoring_ = false;

nav2_behavior_tree/include/nav2_behavior_tree/bt_action_server_impl.hpp

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <limits>
2222
#include <memory>
2323
#include <set>
24+
#include <unordered_set>
2425
#include <string>
2526
#include <vector>
2627

@@ -45,7 +46,7 @@ BtActionServer<ActionT, NodeT>::BtActionServer(
4546
OnCompletionCallback on_completion_callback,
4647
const std::vector<std::string> & search_directories)
4748
: action_name_(action_name),
48-
default_bt_xml_filename_(default_bt_xml_filename),
49+
default_bt_xml_filename_or_id_(default_bt_xml_filename),
4950
search_directories_(search_directories),
5051
plugin_lib_names_(plugin_lib_names),
5152
node_(parent),
@@ -178,7 +179,7 @@ bool BtActionServer<ActionT, NodeT>::on_configure()
178179
int wait_for_service_timeout;
179180
node->get_parameter("wait_for_service_timeout", wait_for_service_timeout);
180181
wait_for_service_timeout_ = std::chrono::milliseconds(wait_for_service_timeout);
181-
node->get_parameter("always_reload_bt_xml", always_reload_bt_xml_);
182+
node->get_parameter("always_reload_bt_xml", always_reload_bt_);
182183

183184
// Get error code id names to grab off of the blackboard
184185
error_code_name_prefixes_ = node->get_parameter("error_code_name_prefixes").as_string_array();
@@ -204,8 +205,8 @@ template<class ActionT, class NodeT>
204205
bool BtActionServer<ActionT, NodeT>::on_activate()
205206
{
206207
resetInternalError();
207-
if (!loadBehaviorTree(default_bt_xml_filename_)) {
208-
RCLCPP_ERROR(logger_, "Error loading XML file: %s", default_bt_xml_filename_.c_str());
208+
if (!loadBehaviorTree(default_bt_xml_filename_or_id_)) {
209+
RCLCPP_ERROR(logger_, "Error loading BT: %s", default_bt_xml_filename_or_id_.c_str());
209210
return false;
210211
}
211212
action_server_->activate();
@@ -228,7 +229,7 @@ bool BtActionServer<ActionT, NodeT>::on_cleanup()
228229
action_server_.reset();
229230
topic_logger_.reset();
230231
plugin_lib_names_.clear();
231-
current_bt_xml_filename_.clear();
232+
current_bt_file_or_id_.clear();
232233
blackboard_.reset();
233234
bt_->haltAllActions(tree_);
234235
bt_->resetGrootMonitor();
@@ -246,40 +247,49 @@ void BtActionServer<ActionT, NodeT>::setGrootMonitoring(
246247
}
247248

248249
template<class ActionT, class NodeT>
249-
bool BtActionServer<ActionT, NodeT>::loadBehaviorTree(const std::string & bt_xml_filename)
250+
bool BtActionServer<ActionT, NodeT>::loadBehaviorTree(const std::string & bt_xml_filename_or_id)
250251
{
251252
namespace fs = std::filesystem;
252253

253-
// Empty filename is default for backward compatibility
254-
auto filename = bt_xml_filename.empty() ? default_bt_xml_filename_ : bt_xml_filename;
254+
// Empty argument is default for backward compatibility
255+
auto file_or_id =
256+
bt_xml_filename_or_id.empty() ? default_bt_xml_filename_or_id_ : bt_xml_filename_or_id;
255257

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

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

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

272-
const auto canonical_main_bt = fs::canonical(filename);
273-
274-
// Register all XML behavior Subtrees found in the given directories
274+
std::unordered_set<std::string> used_bt_id;
275275
for (const auto & directory : search_directories_) {
276276
try {
277277
for (const auto & entry : fs::directory_iterator(directory)) {
278278
if (entry.path().extension() == ".xml") {
279-
// Skip registering the main tree file
280-
if (fs::equivalent(fs::canonical(entry.path()), canonical_main_bt)) {
279+
auto current_bt_id = bt_->extractBehaviorTreeID(entry.path().string());
280+
if (current_bt_id.empty()) {
281+
RCLCPP_ERROR(logger_, "Skipping BT file %s (missing ID)",
282+
entry.path().string().c_str());
281283
continue;
282284
}
285+
auto [it, inserted] = used_bt_id.insert(current_bt_id);
286+
if (!inserted) {
287+
RCLCPP_WARN(
288+
logger_,
289+
"Warning: Duplicate BT IDs found. Make sure to have all BT IDs unique! "
290+
"ID: %s File: %s",
291+
current_bt_id.c_str(), entry.path().string().c_str());
292+
}
283293
bt_->registerTreeFromFile(entry.path().string());
284294
}
285295
}
@@ -289,18 +299,21 @@ bool BtActionServer<ActionT, NodeT>::loadBehaviorTree(const std::string & bt_xml
289299
return false;
290300
}
291301
}
292-
293-
// Try to load the main BT tree
302+
// Try to load the main BT tree (by ID)
294303
try {
295-
tree_ = bt_->createTreeFromFile(filename, blackboard_);
304+
if(!is_bt_id) {
305+
tree_ = bt_->createTreeFromFile(file_or_id, blackboard_);
306+
} else {
307+
tree_ = bt_->createTree(file_or_id, blackboard_);
308+
}
309+
296310
for (auto & subtree : tree_.subtrees) {
297311
auto & blackboard = subtree->blackboard;
298312
blackboard->set("node", client_node_);
299313
blackboard->set<std::chrono::milliseconds>("server_timeout", default_server_timeout_);
300314
blackboard->set<std::chrono::milliseconds>("bt_loop_duration", bt_loop_duration_);
301315
blackboard->set<std::chrono::milliseconds>(
302-
"wait_for_service_timeout",
303-
wait_for_service_timeout_);
316+
"wait_for_service_timeout", wait_for_service_timeout_);
304317
}
305318
} catch (const std::exception & e) {
306319
setInternalError(ActionT::Result::FAILED_TO_LOAD_BEHAVIOR_TREE,
@@ -310,8 +323,7 @@ bool BtActionServer<ActionT, NodeT>::loadBehaviorTree(const std::string & bt_xml
310323

311324
// Optional logging and monitoring
312325
topic_logger_ = std::make_unique<RosTopicLogger>(client_node_, tree_);
313-
314-
current_bt_xml_filename_ = filename;
326+
current_bt_file_or_id_ = file_or_id;
315327

316328
if (enable_groot_monitoring_) {
317329
bt_->addGrootMonitoring(&tree_, groot_server_port_);

nav2_behavior_tree/src/behavior_tree_engine.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <memory>
1919
#include <string>
2020
#include <vector>
21+
#include "tinyxml2.h" //NOLINT
2122

2223
#include "rclcpp/rclcpp.hpp"
2324
#include "behaviortree_cpp/json_export.h"
@@ -102,6 +103,51 @@ BehaviorTreeEngine::createTreeFromFile(
102103
return factory_.createTreeFromFile(file_path, blackboard);
103104
}
104105

106+
std::string BehaviorTreeEngine::extractBehaviorTreeID(
107+
const std::string & bt_file)
108+
{
109+
if(bt_file.empty()) {
110+
RCLCPP_ERROR(rclcpp::get_logger("BehaviorTreeEngine"),
111+
"Error: Empty BT file passed to extractBehaviorTreeID");
112+
return "";
113+
}
114+
tinyxml2::XMLDocument doc;
115+
if (doc.LoadFile(bt_file.c_str()) != tinyxml2::XML_SUCCESS) {
116+
RCLCPP_ERROR(rclcpp::get_logger("BehaviorTreeEngine"), "Error: Could not open or parse file %s",
117+
bt_file.c_str());
118+
return "";
119+
}
120+
tinyxml2::XMLElement * rootElement = doc.RootElement();
121+
if (!rootElement) {
122+
RCLCPP_ERROR(rclcpp::get_logger("BehaviorTreeEngine"), "Error: Root element not found in %s",
123+
bt_file.c_str());
124+
return "";
125+
}
126+
tinyxml2::XMLElement * btElement = rootElement->FirstChildElement("BehaviorTree");
127+
if (!btElement) {
128+
RCLCPP_ERROR(rclcpp::get_logger("BehaviorTreeEngine"),
129+
"Error: <BehaviorTree> element not found in %s", bt_file.c_str());
130+
return "";
131+
}
132+
const char * idValue = btElement->Attribute("ID");
133+
if (idValue) {
134+
return std::string(idValue);
135+
} else {
136+
RCLCPP_ERROR(rclcpp::get_logger("BehaviorTreeEngine"),
137+
"Error: ID attribute not found on <BehaviorTree> element in %s",
138+
bt_file.c_str());
139+
return "";
140+
}
141+
}
142+
143+
BT::Tree
144+
BehaviorTreeEngine::createTree(
145+
const std::string & tree_id,
146+
BT::Blackboard::Ptr blackboard)
147+
{
148+
return factory_.createTree(tree_id, blackboard);
149+
}
150+
105151
/// @brief Register a tree from an XML file and return the tree
106152
void BehaviorTreeEngine::registerTreeFromFile(
107153
const std::string & file_path)

nav2_bt_navigator/behavior_trees/follow_point.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
This Behavior Tree follows a dynamic pose to a certain distance
33
-->
44

5-
<root BTCPP_format="4" main_tree_to_execute="MainTree">
6-
<BehaviorTree ID="MainTree">
5+
<root BTCPP_format="4" main_tree_to_execute="FollowPoint">
6+
<BehaviorTree ID="FollowPoint">
77
<PipelineSequence name="NavigateWithReplanning">
88
<ControllerSelector selected_controller="{selected_controller}" default_controller="FollowPath" topic_name="controller_selector"/>
99
<PlannerSelector selected_planner="{selected_planner}" default_planner="GridBased" topic_name="planner_selector"/>

nav2_bt_navigator/behavior_trees/nav_to_pose_with_consistent_replanning_and_if_path_becomes_invalid.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
recovery actions specific to planning / control as well as general system issues.
44
This will be continuous if a kinematically valid planner is selected.
55
-->
6-
<root BTCPP_format="4" main_tree_to_execute="MainTree">
7-
<BehaviorTree ID="MainTree">
6+
<root BTCPP_format="4" main_tree_to_execute="NavToPoseWithConsistentReplanningAndIfPathBecomesInvalid">
7+
<BehaviorTree ID="NavToPoseWithConsistentReplanningAndIfPathBecomesInvalid">
88
<RecoveryNode number_of_retries="6" name="NavigateRecovery">
99
<PipelineSequence name="NavigateWithReplanning">
1010
<ControllerSelector selected_controller="{selected_controller}" default_controller="FollowPath" topic_name="controller_selector"/>

nav2_bt_navigator/behavior_trees/navigate_on_route_graph_w_recovery.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
It also has recovery actions specific to planning / control as well as general system issues.
1111
-->
1212

13-
<root BTCPP_format="4" main_tree_to_execute="MainTree">
14-
<BehaviorTree ID="MainTree">
13+
<root BTCPP_format="4" main_tree_to_execute="NavigateOnRouteGraphWRecovery">
14+
<BehaviorTree ID="NavigateOnRouteGraphWRecovery">
1515
<RecoveryNode number_of_retries="6" name="NavigateRecovery">
1616
<PipelineSequence name="NavigateWithReplanning">
1717
<ControllerSelector selected_controller="{selected_controller}" default_controller="FollowPath" topic_name="controller_selector"/>

nav2_bt_navigator/behavior_trees/navigate_through_poses_w_replanning_and_recovery.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
This Behavior Tree replans the global path periodically at 1 Hz through an array of poses continuously
44
and it also has recovery actions specific to planning / control as well as general system issues.
55
-->
6-
<root BTCPP_format="4" main_tree_to_execute="MainTree">
7-
<BehaviorTree ID="MainTree">
6+
<root BTCPP_format="4" main_tree_to_execute="NavigateThroughPosesWReplanningAndRecovery">
7+
<BehaviorTree ID="NavigateThroughPosesWReplanningAndRecovery">
88
<RecoveryNode number_of_retries="6" name="NavigateRecovery">
99
<PipelineSequence name="NavigateWithReplanning">
1010
<ProgressCheckerSelector selected_progress_checker="{selected_progress_checker}" default_progress_checker="progress_checker" topic_name="progress_checker_selector"/>

nav2_bt_navigator/behavior_trees/navigate_to_pose_w_replanning_and_recovery.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
recovery actions specific to planning / control as well as general system issues.
55
This will be continuous if a kinematically valid planner is selected.
66
-->
7-
<root BTCPP_format="4" main_tree_to_execute="MainTree">
8-
<BehaviorTree ID="MainTree">
7+
<root BTCPP_format="4" main_tree_to_execute="NavigateToPoseWReplanningAndRecovery">
8+
<BehaviorTree ID="NavigateToPoseWReplanningAndRecovery">
99
<RecoveryNode number_of_retries="6" name="NavigateRecovery">
1010
<PipelineSequence name="NavigateWithReplanning">
1111
<ControllerSelector selected_controller="{selected_controller}" default_controller="FollowPath" topic_name="controller_selector"/>

nav2_bt_navigator/behavior_trees/navigate_to_pose_w_replanning_goal_patience_and_recovery.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
make the robot wait for a specific time, to see if the obstacle clears out before
66
navigating along a significantly longer path to reach the goal location.
77
-->
8-
<root BTCPP_format="4" main_tree_to_execute="MainTree">
9-
<BehaviorTree ID="MainTree">
8+
<root BTCPP_format="4" main_tree_to_execute="NavigateToPoseWReplanningGoalPatienceAndRecovery">
9+
<BehaviorTree ID="NavigateToPoseWReplanningGoalPatienceAndRecovery">
1010
<RecoveryNode number_of_retries="6" name="NavigateRecovery">
1111
<PipelineSequence name="NavigateWithReplanning">
1212
<ControllerSelector selected_controller="{selected_controller}" default_controller="FollowPath" topic_name="controller_selector"/>

0 commit comments

Comments
 (0)