15#include "auto_apms_behavior_tree/executor/executor_base.hpp"
19#include "auto_apms_util/container.hpp"
20#include "auto_apms_util/logging.hpp"
26 rclcpp::Node::SharedPtr node_ptr, rclcpp::CallbackGroup::SharedPtr tree_node_callback_group_ptr)
29 tree_node_waitables_callback_group_ptr_(tree_node_callback_group_ptr),
30 tree_node_waitables_executor_ptr_(rclcpp::executors::SingleThreadedExecutor::make_shared()),
31 global_blackboard_ptr_(TreeBlackboard::create()),
33 execution_stopped_(true)
38 if (!tree_node_waitables_callback_group_ptr_) {
39 tree_node_waitables_callback_group_ptr_ =
40 node_ptr_->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive,
false);
44 tree_node_waitables_executor_ptr_->add_callback_group(
49 TreeConstructor make_tree,
double tick_rate_sec,
int groot2_port)
52 throw exceptions::TreeExecutorError(
53 "Cannot start execution with tree '" +
getTreeName() +
"' currently executing.");
56 std::unique_ptr<Tree> tree_ptr;
59 TreeBlackboardSharedPtr main_tree_bb_ptr = TreeBlackboard::create(global_blackboard_ptr_);
61 tree_ptr = std::make_unique<Tree>(make_tree(main_tree_bb_ptr));
62 }
catch (
const std::exception & e) {
63 throw exceptions::TreeBuildError(
64 "Cannot start execution because creating the tree failed: " + std::string(e.what()));
68 return startExecution(std::move(tree_ptr), tick_rate_sec, groot2_port);
72 std::unique_ptr<Tree> tree,
double tick_rate_sec,
int groot2_port)
75 throw exceptions::TreeExecutorError(
76 "Cannot start execution with tree '" +
getTreeName() +
"' currently executing.");
79 throw exceptions::TreeExecutorError(
"Cannot start execution because the provided tree is nullptr.");
83 tree_ptr_ = std::move(tree);
86 groot2_publisher_ptr_.reset();
87 if (groot2_port != -1) {
89 groot2_publisher_ptr_ = std::make_unique<BT::Groot2Publisher>(*tree_ptr_, groot2_port);
90 }
catch (
const std::exception & e) {
91 throw exceptions::TreeExecutorError(
92 "Failed to initialize Groot2 publisher with port " + std::to_string(groot2_port) +
": " + e.what());
97 state_observer_ptr_.reset();
98 state_observer_ptr_ = std::make_unique<TreeStateObserver>(*tree_ptr_,
logger_);
99 state_observer_ptr_->enableTransitionToIdle(
false);
106 termination_reason_ =
"";
107 execution_stopped_ =
true;
110 auto promise_ptr = std::make_shared<std::promise<ExecutionResult>>();
111 TerminationCallback termination_callback = [
this, promise_ptr](
ExecutionResult result,
const std::string & msg) {
115 RCLCPP_ERROR(
logger_,
"Termination reason: %s", msg.c_str());
117 RCLCPP_INFO(
logger_,
"Termination reason: %s", msg.c_str());
121 promise_ptr->set_value(result);
122 execution_timer_ptr_->cancel();
127 const std::chrono::nanoseconds period =
128 std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::duration<double>(tick_rate_sec));
129 execution_timer_ptr_ =
node_ptr_->create_wall_timer(period, [
this, period, termination_callback]() {
131 tree_node_waitables_executor_ptr_->spin_all(period);
134 tick_callback_(termination_callback);
136 return promise_ptr->get_future();
139void TreeExecutorBase::tick_callback_(TerminationCallback termination_callback)
142 if (prev_execution_state_ != this_execution_state) {
144 logger_,
"Executor for tree '%s' changed state from '%s' to '%s'.",
getTreeName().c_str(),
145 toStr(prev_execution_state_).c_str(),
toStr(this_execution_state).c_str());
146 prev_execution_state_ = this_execution_state;
151 execution_stopped_ =
false;
152 bool do_on_tick =
true;
153 switch (control_command_) {
155 execution_stopped_ =
true;
162 termination_reason_ =
"onInitialTick() returned false.";
171 termination_reason_ =
"onTick() returned false.";
179 termination_callback(
181 termination_reason_.empty() ?
"Control command was set to TERMINATE." : termination_reason_);
190#ifdef AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_THROW_ON_TICK_ERROR
191 tree_ptr_->haltTree();
194 tree_ptr_->haltTree();
195 }
catch (
const std::exception & e) {
196 termination_callback(
198 "Error during haltTree() on command " +
toStr(control_command_) +
": " + std::string(e.what()));
204 throw std::logic_error(
205 "Handling control command " + std::to_string(
static_cast<int>(control_command_)) +
" '" +
206 toStr(control_command_) +
"' is not implemented.");
211 BT::NodeStatus bt_status = BT::NodeStatus::IDLE;
212#ifdef AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_THROW_ON_TICK_ERROR
213 bt_status = tree_ptr_->tickExactlyOnce();
217 bt_status = tree_ptr_->tickExactlyOnce();
218 }
catch (
const std::exception & e) {
219 std::string msg =
"Ran into an exception during tick: " + std::string(e.what());
221 tree_ptr_->haltTree();
222 }
catch (
const std::exception & e) {
223 msg +=
"\nDuring haltTree(), another exception occurred: " + std::string(e.what());
235 if (bt_status == BT::NodeStatus::RUNNING)
return;
239 if (!(bt_status == BT::NodeStatus::SUCCESS || bt_status == BT::NodeStatus::FAILURE)) {
240 throw std::logic_error(
241 "bt_status is " + BT::toStr(bt_status) +
". Must be one of SUCCESS or FAILURE at this point.");
243 const bool success = bt_status == BT::NodeStatus::SUCCESS;
246 termination_callback(
248 "Terminated on tree result " + BT::toStr(bt_status) +
".");
255 throw std::logic_error(
"Execution routine is not intended to proceed to this statement.");
275 global_blackboard_ptr_ = TreeBlackboard::create();
279 logger_,
"clearGlobalBlackboard() was called when executor is %s, but this is only allowed when IDLE. Ignoring...",
280 toStr(curr_state).c_str());
291 if (!tree_ptr_)
throw std::logic_error(
"tree_ptr_ cannot be nullptr when execution is started.");
292 if (tree_ptr_->rootNode()->status() == BT::NodeStatus::IDLE) {
305 if (tree_ptr_)
return tree_ptr_->subtrees[0]->tree_ID;
306 return "NO_TREE_NAME";
313 if (!state_observer_ptr_) {
314 throw exceptions::TreeExecutorError(
"Cannot get state observer because executor is not busy.");
316 return *state_observer_ptr_;
323 return node_ptr_->get_node_base_interface();
328 return tree_node_waitables_callback_group_ptr_;
333 return tree_node_waitables_executor_ptr_;
383 return "TREE_SUCCEEDED";
385 return "TREE_FAILED";
387 return "TERMINATED_PREMATURELY";
ExecutionState
Enum representing possible behavior tree execution states.
@ STARTING
Initializing execution.
@ RUNNING
Executor is busy and tree has been ticked at least once.
@ PAUSED
Execution routine is active, but tree is not being ticked.
@ HALTED
Execution routine is active, but tree is not being ticked and has been halted before.
virtual void onTermination(const ExecutionResult &result)
Callback invoked when the execution routine terminates.
std::shared_future< ExecutionResult > startExecution(TreeConstructor make_tree, double tick_rate_sec=0.1, int groot2_port=-1)
Start a behavior tree that is built using a callback.
ExecutionState getExecutionState()
Get a status code indicating the current state of execution.
bool isBusy()
Determine whether this executor is currently executing a behavior tree.
rclcpp::Node::SharedPtr node_ptr_
Shared pointer to the parent ROS 2 node.
virtual TreeExitBehavior onTreeExit(bool success)
Callback invoked last thing when the execution routine completes because the behavior tree is finishe...
virtual bool onInitialTick()
Callback invoked once before the behavior tree is ticked for the very first time.
rclcpp::executors::SingleThreadedExecutor::SharedPtr getTreeNodeWaitablesExecutorPtr()
Get the ROS 2 executor instance used for spinning waitables registered by behavior tree nodes.
rclcpp::CallbackGroup::SharedPtr getTreeNodeWaitablesCallbackGroupPtr()
Get the callback group used for all waitables registered by behavior tree nodes.
void setControlCommand(ControlCommand cmd)
Set the command that handles the control flow of the execution routine.
virtual bool onTick()
Callback invoked every time before the behavior tree is ticked.
virtual bool afterTick()
Callback invoked every time after the behavior tree is ticked.
TreeExitBehavior
Enum representing possible options for what to do when a behavior tree is completed.
@ RESTART
Restart execution and keep on running.
@ TERMINATE
Stop execution and reset the timer.
virtual bool clearGlobalBlackboard()
Reset the global blackboard and clear all entries.
ControlCommand
Enum representing possible commands for controlling the behavior tree execution routine.
@ TERMINATE
Halt the currently executing tree and terminate the execution routine.
@ PAUSE
Pause the execution routine.
@ RUN
Start/Resume the execution routine.
@ HALT
Halt the currently executing tree and pause the execution routine.
TreeStateObserver & getStateObserver()
Get a reference to the current behavior tree state observer.
TreeExecutorBase(rclcpp::Node::SharedPtr node_ptr, rclcpp::CallbackGroup::SharedPtr tree_node_callback_group_ptr=nullptr)
Constructor.
rclcpp::Node::SharedPtr getNodePtr()
Get a shared pointer to the parent ROS 2 node.
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr get_node_base_interface()
Get the node's base interface. Is required to be able to register derived classes as ROS2 components.
TreeBlackboardSharedPtr getGlobalBlackboardPtr()
Get a shared pointer to the global blackboard instance.
std::string getTreeName()
Get the name of the tree that is currently executing.
ExecutionResult
Enum representing possible behavior tree execution results.
@ TREE_SUCCEEDED
Tree completed with BT::NodeStatus::SUCCESS.
@ TERMINATED_PREMATURELY
Execution terminated before the tree was able to propagate the tick to all its nodes.
@ TREE_FAILED
Tree completed with BT::NodeStatus::FAILURE.
@ ERROR
An unexpected error occurred.
const rclcpp::Logger logger_
Logger associated with the parent ROS 2 node.
State observer for a particular behavior tree object that writes introspection and debugging informat...
const char * toStr(const ActionNodeErrorCode &err)
Convert the action error code to string.
Powerful tooling for incorporating behavior trees for task development.