AutoAPMS
Streamlining behaviors in ROS 2
Loading...
Searching...
No Matches
executor_node.cpp
1// Copyright 2024 Robin Müller
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include "auto_apms_behavior_tree/executor/executor_node.hpp"
16
17#include <functional>
18
19#include "auto_apms_behavior_tree/exceptions.hpp"
20#include "auto_apms_behavior_tree_core/definitions.hpp"
21#include "auto_apms_util/container.hpp"
22#include "auto_apms_util/string.hpp"
23#include "rclcpp/utilities.hpp"
24
26{
27
28TreeExecutorNode::TreeExecutorNode(const std::string & name, const std::string & start_action_name, Options options)
29: ActionBasedTreeExecutorNode<auto_apms_interfaces::action::StartTreeExecutor>(
30 name,
31 start_action_name.empty() ? name + _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_START_ACTION_NAME_SUFFIX : start_action_name,
32 options)
33{
34 // Make sure ROS arguments are removed. When using rclcpp_components, this is typically not the case.
35 std::vector<std::string> args_with_ros_arguments = node_ptr_->get_node_options().arguments();
36 int argc = args_with_ros_arguments.size();
37 char ** argv = new char *[argc + 1]; // +1 for the null terminator
38 for (int i = 0; i < argc; ++i) {
39 argv[i] = const_cast<char *>(args_with_ros_arguments[i].c_str());
40 }
41 argv[argc] = nullptr; // Null-terminate the array as required for argv[]
42
43 // Evaluate possible cli argument dictating to start executing with a specific build request immediately.
44 // Note: First argument is always path of executable.
45 if (const std::vector<std::string> args = rclcpp::remove_ros_arguments(argc, argv); args.size() > 1) {
46 // Log relevant arguments. First argument is executable name (argv[0]) and won't be considered.
47 std::vector<std::string> relevant_args{args.begin() + 1, args.end()};
48 RCLCPP_DEBUG(
49 logger_, "Additional cli arguments in rclcpp::NodeOptions: [ %s ]",
50 auto_apms_util::join(relevant_args, ", ").c_str());
51
52 const ExecutorParameters initial_params = executor_param_listener_.get_params();
53 // Start tree execution with the build handler request being the first relevant argument
54 startExecution(makeTreeConstructor(relevant_args[0]), initial_params.tick_rate, initial_params.groot2_port);
55 }
56}
57
58TreeExecutorNode::TreeExecutorNode(const std::string & name, Options options) : TreeExecutorNode(name, "", options) {}
59
60TreeExecutorNode::TreeExecutorNode(rclcpp::NodeOptions ros_options)
61: TreeExecutorNode(_AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_DEFAULT_NAME, Options(ros_options))
62{
63}
64
66 const rclcpp_action::GoalUUID & uuid, std::shared_ptr<const TriggerGoal> /*goal_ptr*/)
67{
68 if (isBusy()) {
69 RCLCPP_WARN(
70 logger_, "Goal %s was REJECTED: Tree '%s' is currently executing.", rclcpp_action::to_string(uuid).c_str(),
71 getTreeName().c_str());
72 return false;
73 }
74 return true;
75}
76
77TreeConstructor TreeExecutorNode::getTreeConstructorFromGoal(std::shared_ptr<const TriggerGoal> goal_ptr)
78{
79 if (!goal_ptr->build_handler.empty()) {
80 if (executor_param_listener_.get_params().allow_other_build_handlers) {
81 loadBuildHandler(goal_ptr->build_handler);
82 } else if (goal_ptr->build_handler != current_build_handler_name_) {
83 throw exceptions::TreeExecutorError(
84 "Current tree build handler '" + current_build_handler_name_ +
85 "' must not change since the 'Allow other build handlers' option is disabled.");
86 }
87 }
88
89 core::NodeManifest node_manifest = core::NodeManifest::decode(goal_ptr->node_manifest);
90 return makeTreeConstructor(goal_ptr->build_request, goal_ptr->entry_point, node_manifest);
91}
92
93void TreeExecutorNode::onAcceptedGoal(std::shared_ptr<TriggerGoalHandle> goal_handle_ptr)
94{
95 // Clear blackboard parameters if desired
96 if (goal_handle_ptr->get_goal()->clear_blackboard) {
98 }
99}
100
101void TreeExecutorNode::onExecutionStarted(std::shared_ptr<TriggerGoalHandle> goal_handle_ptr)
102{
103 const std::string started_tree_name = getTreeName();
104
105 // If attach is true, the goal's life time is synchronized with the execution. Otherwise we succeed immediately and
106 // leave the executor running (Detached mode).
107 if (goal_handle_ptr->get_goal()->attach) {
108 trigger_action_context_.setUp(goal_handle_ptr);
109 RCLCPP_INFO(logger_, "Successfully started execution of tree '%s' (Mode: Attached).", started_tree_name.c_str());
110 } else {
111 auto result_ptr = std::make_shared<TriggerResult>();
112 result_ptr->message = "Successfully started execution of tree '" + started_tree_name + "' (Mode: Detached).";
113 result_ptr->tree_result = TriggerResult::TREE_RESULT_NOT_SET;
114 result_ptr->terminated_tree_identity = started_tree_name;
115 goal_handle_ptr->succeed(result_ptr);
116 RCLCPP_INFO_STREAM(logger_, result_ptr->message);
117 }
118}
119
120bool TreeExecutorNode::afterTick()
121{
122 // Call parent's afterTick for blackboard synchronization etc.
123 if (!GenericTreeExecutorNode::afterTick()) return false;
124
125 // Send feedback (only in attached mode)
126 if (trigger_action_context_.isValid()) {
127 TreeStateObserver & state_observer = getStateObserver();
128 auto feedback_ptr = trigger_action_context_.getFeedbackPtr();
129 feedback_ptr->execution_state_str = toStr(getExecutionState());
130 feedback_ptr->running_tree_identity = getTreeName();
131 auto running_action_history = state_observer.getRunningActionHistory();
132 if (!running_action_history.empty()) {
133 // If there are multiple nodes running (ParallelNode), join the IDs to a single string
134 feedback_ptr->running_action_name = auto_apms_util::join(running_action_history, " + ");
135 feedback_ptr->running_action_timestamp =
136 std::chrono::duration<double>{std::chrono::high_resolution_clock::now().time_since_epoch()}.count();
137
138 // Reset the history cache
139 state_observer.flush();
140 }
141 trigger_action_context_.publishFeedback();
142 }
143
144 return true;
145}
146
147void TreeExecutorNode::onGoalExecutionTermination(const ExecutionResult & result, TriggerActionContext & context)
148{
149 auto result_ptr = context.getResultPtr();
150 result_ptr->terminated_tree_identity = getTreeName();
151 switch (result) {
153 result_ptr->tree_result = TriggerResult::TREE_RESULT_SUCCESS;
154 result_ptr->message = "Tree execution finished with status SUCCESS";
155 context.succeed();
156 break;
158 result_ptr->tree_result = TriggerResult::TREE_RESULT_FAILURE;
159 result_ptr->message = "Tree execution finished with status FAILURE";
160 context.abort();
161 break;
163 result_ptr->tree_result = TriggerResult::TREE_RESULT_NOT_SET;
164 if (context.getGoalHandlePtr()->is_canceling()) {
165 result_ptr->message = "Tree execution canceled successfully";
166 context.cancel();
167 } else {
168 result_ptr->message = "Tree execution terminated prematurely";
169 context.abort();
170 }
171 break;
173 result_ptr->tree_result = TriggerResult::TREE_RESULT_NOT_SET;
174 result_ptr->message = "An unexpected error occurred during tree execution";
175 context.abort();
176 break;
177 default:
178 result_ptr->tree_result = TriggerResult::TREE_RESULT_NOT_SET;
179 result_ptr->message = "Execution result unknown";
180 context.abort();
181 break;
182 }
183}
184
185} // namespace auto_apms_behavior_tree
ActionBasedTreeExecutorNode(const std::string &name, const std::string &action_name, Options options)
virtual bool clearGlobalBlackboard() override
Reset the global blackboard and clear all entries.
std::shared_future< ExecutionResult > startExecution(const std::string &build_request, const std::string &entry_point="", const core::NodeManifest &node_manifest={})
Start the behavior tree specified by a particular build request.
void loadBuildHandler(const std::string &name)
Load a particular behavior tree build handler plugin.
TreeConstructor makeTreeConstructor(const std::string &build_request, const std::string &entry_point="", const core::NodeManifest &node_manifest={})
Create a callback that builds a behavior tree according to a specific request.
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.
TreeStateObserver & getStateObserver()
Get a reference to the current behavior tree state observer.
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.
const rclcpp::Logger logger_
Logger associated with the parent ROS 2 node.
TreeConstructor getTreeConstructorFromGoal(std::shared_ptr< const TriggerGoal > goal_ptr) override
Create a TreeConstructor from a StartTreeExecutor action goal.
void onExecutionStarted(std::shared_ptr< TriggerGoalHandle > goal_handle_ptr) override
Hook called after execution has been started successfully.
TreeExecutorNode(const std::string &name, const std::string &start_action_name, Options options)
Constructor allowing to specify a custom node name and executor options.
void onAcceptedGoal(std::shared_ptr< TriggerGoalHandle > goal_handle_ptr) override
Hook called after a start action goal has been accepted and before execution begins.
void onGoalExecutionTermination(const ExecutionResult &result, TriggerActionContext &context) override
Handle the execution result for the StartTreeExecutor action client.
bool shouldAcceptGoal(const rclcpp_action::GoalUUID &uuid, std::shared_ptr< const TriggerGoal > goal_ptr) override
Determine whether an incoming start action goal should be accepted.
State observer for a particular behavior tree object that writes introspection and debugging informat...
virtual void flush() override
Reset the internal state variables.
const std::vector< std::string > & getRunningActionHistory() const
Get all names of action nodes that returned BT::NodeStatus::RUNNING since the last time TreeStateObse...
Data structure for information about which behavior tree node plugin to load and how to configure the...
std::shared_ptr< Feedback > getFeedbackPtr()
Access the internal action feedback buffer.
bool isValid()
Check if this ActionContext is valid (e.g. is managing a valid action goal handle).
Powerful tooling for incorporating behavior trees for task development.
Definition behavior.hpp:32