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
29 rclcpp::Node::SharedPtr node_ptr, const std::string & start_action_name, Options options)
30: ActionBasedTreeExecutorNode<auto_apms_interfaces::action::StartTreeExecutor>(
31 node_ptr,
32 start_action_name.empty()
33 ? std::string(node_ptr->get_name()) + _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_START_ACTION_NAME_SUFFIX
34 : start_action_name,
35 options)
36{
37 // Make sure ROS arguments are removed. When using rclcpp_components, this is typically not the case.
38 std::vector<std::string> args_with_ros_arguments = node_ptr_->get_node_options().arguments();
39 int argc = args_with_ros_arguments.size();
40 char ** argv = new char *[argc + 1]; // +1 for the null terminator
41 for (int i = 0; i < argc; ++i) {
42 argv[i] = const_cast<char *>(args_with_ros_arguments[i].c_str());
43 }
44 argv[argc] = nullptr; // Null-terminate the array as required for argv[]
45
46 // Evaluate possible cli argument dictating to start executing with a specific build request immediately.
47 // Note: First argument is always path of executable.
48 if (const std::vector<std::string> args = rclcpp::remove_ros_arguments(argc, argv); args.size() > 1) {
49 // Log relevant arguments. First argument is executable name (argv[0]) and won't be considered.
50 std::vector<std::string> relevant_args{args.begin() + 1, args.end()};
51 RCLCPP_DEBUG(
52 logger_, "Additional cli arguments in rclcpp::NodeOptions: [ %s ]",
53 auto_apms_util::join(relevant_args, ", ").c_str());
54
55 const ExecutorParameters initial_params = executor_param_listener_.get_params();
56 // Start tree execution with the build handler request being the first relevant argument
57 startExecution(makeTreeConstructor(relevant_args[0]), initial_params.tick_rate, initial_params.groot2_port);
58 }
59}
60
61TreeExecutorNode::TreeExecutorNode(const std::string & name, const std::string & start_action_name, Options options)
62: TreeExecutorNode(std::make_shared<rclcpp::Node>(name, options.getROSNodeOptions()), start_action_name, options)
63{
64}
65
66TreeExecutorNode::TreeExecutorNode(const std::string & name, Options options) : TreeExecutorNode(name, "", options) {}
67
68TreeExecutorNode::TreeExecutorNode(rclcpp::NodeOptions ros_options)
69: TreeExecutorNode(_AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_DEFAULT_NAME, Options(ros_options))
70{
71}
72
74 const rclcpp_action::GoalUUID & uuid, std::shared_ptr<const TriggerGoal> /*goal_ptr*/)
75{
76 if (isBusy()) {
77 RCLCPP_WARN(
78 logger_, "Goal %s was REJECTED: Tree '%s' is currently executing.", rclcpp_action::to_string(uuid).c_str(),
79 getTreeName().c_str());
80 return false;
81 }
82 return true;
83}
84
85TreeConstructor TreeExecutorNode::getTreeConstructorFromGoal(std::shared_ptr<const TriggerGoal> goal_ptr)
86{
87 if (!goal_ptr->build_handler.empty()) {
88 if (executor_param_listener_.get_params().allow_other_build_handlers) {
89 loadBuildHandler(goal_ptr->build_handler);
90 } else if (goal_ptr->build_handler != current_build_handler_name_) {
91 throw exceptions::TreeExecutorError(
92 "Current tree build handler '" + current_build_handler_name_ +
93 "' must not change since the 'Allow other build handlers' option is disabled.");
94 }
95 }
96
97 core::NodeManifest node_manifest = core::NodeManifest::decode(goal_ptr->node_manifest);
98 return makeTreeConstructor(goal_ptr->build_request, goal_ptr->entry_point, node_manifest);
99}
100
101void TreeExecutorNode::onAcceptedGoal(std::shared_ptr<TriggerGoalHandle> goal_handle_ptr)
102{
103 // Clear blackboard parameters if desired
104 if (goal_handle_ptr->get_goal()->clear_blackboard) {
106 }
107}
108
109void TreeExecutorNode::onExecutionStarted(std::shared_ptr<TriggerGoalHandle> goal_handle_ptr)
110{
111 const std::string started_tree_name = getTreeName();
112
113 // If attach is true, the goal's life time is synchronized with the execution. Otherwise we succeed immediately and
114 // leave the executor running (Detached mode).
115 if (goal_handle_ptr->get_goal()->attach) {
116 trigger_action_context_.setUp(goal_handle_ptr);
117 RCLCPP_INFO(logger_, "Successfully started execution of tree '%s' (Mode: Attached).", started_tree_name.c_str());
118 } else {
119 auto result_ptr = std::make_shared<TriggerResult>();
120 result_ptr->message = "Successfully started execution of tree '" + started_tree_name + "' (Mode: Detached).";
121 result_ptr->tree_result = TriggerResult::TREE_RESULT_NOT_SET;
122 result_ptr->terminated_tree_identity = started_tree_name;
123 goal_handle_ptr->succeed(result_ptr);
124 RCLCPP_INFO_STREAM(logger_, result_ptr->message);
125 }
126}
127
128bool TreeExecutorNode::afterTick()
129{
130 // Call parent's afterTick for blackboard synchronization etc.
131 if (!GenericTreeExecutorNode::afterTick()) return false;
132
133 // Send feedback (only in attached mode)
134 if (trigger_action_context_.isValid()) {
135 TreeStateObserver & state_observer = getStateObserver();
136 auto feedback_ptr = trigger_action_context_.getFeedbackPtr();
137 feedback_ptr->execution_state_str = toStr(getExecutionState());
138 feedback_ptr->running_tree_identity = getTreeName();
139 auto running_action_history = state_observer.getRunningActionHistory();
140 if (!running_action_history.empty()) {
141 // If there are multiple nodes running (ParallelNode), join the IDs to a single string
142 feedback_ptr->running_action_name = auto_apms_util::join(running_action_history, " + ");
143 feedback_ptr->running_action_timestamp =
144 std::chrono::duration<double>{std::chrono::high_resolution_clock::now().time_since_epoch()}.count();
145
146 // Reset the history cache
147 state_observer.flush();
148 }
149 trigger_action_context_.publishFeedback();
150 }
151
152 return true;
153}
154
155void TreeExecutorNode::onGoalExecutionTermination(const ExecutionResult & result, TriggerActionContext & context)
156{
157 auto result_ptr = context.getResultPtr();
158 result_ptr->terminated_tree_identity = getTreeName();
159 switch (result) {
161 result_ptr->tree_result = TriggerResult::TREE_RESULT_SUCCESS;
162 result_ptr->message = "Tree execution finished with status SUCCESS";
163 context.succeed();
164 break;
166 result_ptr->tree_result = TriggerResult::TREE_RESULT_FAILURE;
167 result_ptr->message = "Tree execution finished with status FAILURE";
168 context.abort();
169 break;
171 result_ptr->tree_result = TriggerResult::TREE_RESULT_NOT_SET;
172 if (context.getGoalHandlePtr()->is_canceling()) {
173 result_ptr->message = "Tree execution canceled successfully";
174 context.cancel();
175 } else {
176 result_ptr->message = "Tree execution terminated prematurely";
177 context.abort();
178 }
179 break;
181 result_ptr->tree_result = TriggerResult::TREE_RESULT_NOT_SET;
182 result_ptr->message = "An unexpected error occurred during tree execution";
183 context.abort();
184 break;
185 default:
186 result_ptr->tree_result = TriggerResult::TREE_RESULT_NOT_SET;
187 result_ptr->message = "Execution result unknown";
188 context.abort();
189 break;
190 }
191}
192
193} // namespace auto_apms_behavior_tree
ActionBasedTreeExecutorNode(rclcpp::Node::SharedPtr node_ptr, 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.
TreeExecutorNode(rclcpp::Node::SharedPtr node_ptr, const std::string &start_action_name, Options options)
Constructor using an existing 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.
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