AutoAPMS
Streamlining behaviors in ROS 2
Loading...
Searching...
No Matches
action_based_executor_node.hpp
1// Copyright 2026 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#pragma once
16
17#include <map>
18
19#include "auto_apms_behavior_tree/executor/generic_executor_node.hpp"
20#include "auto_apms_util/action_context.hpp"
21#include "rclcpp_action/rclcpp_action.hpp"
22
24{
25
45template <typename ActionT>
47{
48public:
49 using TriggerActionContext = auto_apms_util::ActionContext<ActionT>;
50 using TriggerGoal = typename ActionT::Goal;
51 using TriggerFeedback = typename ActionT::Feedback;
52 using TriggerResult = typename ActionT::Result;
53 using TriggerGoalHandle = rclcpp_action::ServerGoalHandle<ActionT>;
54
61 ActionBasedTreeExecutorNode(const std::string & name, const std::string & action_name, Options options);
62
70 const std::string & name, const std::string & action_name, rclcpp::NodeOptions ros_options = rclcpp::NodeOptions());
71
72 virtual ~ActionBasedTreeExecutorNode() override = default;
73
74protected:
75 /* Virtual methods to implement/override */
76
86 virtual TreeConstructor getTreeConstructorFromGoal(std::shared_ptr<const TriggerGoal> goal_ptr) = 0;
87
96 virtual bool shouldAcceptGoal(const rclcpp_action::GoalUUID & uuid, std::shared_ptr<const TriggerGoal> goal_ptr);
97
102 virtual void onAcceptedGoal(std::shared_ptr<TriggerGoalHandle> goal_handle_ptr);
103
111 virtual void onExecutionStarted(std::shared_ptr<TriggerGoalHandle> goal_handle_ptr);
112
121 virtual void onGoalExecutionTermination(const ExecutionResult & result, TriggerActionContext & context);
122
123 TriggerActionContext trigger_action_context_;
124 std::map<rclcpp_action::GoalUUID, TreeConstructor> pending_tree_constructors_;
125
126private:
127 void onTermination(const ExecutionResult & result) override final;
128
129 /* Internal action callbacks */
130
131 rclcpp_action::GoalResponse handle_trigger_goal_(
132 const rclcpp_action::GoalUUID & uuid, std::shared_ptr<const TriggerGoal> goal_ptr);
133 rclcpp_action::CancelResponse handle_trigger_cancel_(std::shared_ptr<TriggerGoalHandle> goal_handle_ptr);
134 void handle_trigger_accept_(std::shared_ptr<TriggerGoalHandle> goal_handle_ptr);
135
136 typename rclcpp_action::Server<ActionT>::SharedPtr trigger_action_ptr_;
137};
138
139// #####################################################################################################################
140// ################################ DEFINITIONS ##############################################
141// #####################################################################################################################
142
143template <typename ActionT>
145 const std::string & name, const std::string & action_name, Options options)
146: GenericTreeExecutorNode(name, options), trigger_action_context_(logger_)
147{
148 using namespace std::placeholders;
149 trigger_action_ptr_ = rclcpp_action::create_server<ActionT>(
150 node_ptr_, action_name, std::bind(&ActionBasedTreeExecutorNode::handle_trigger_goal_, this, _1, _2),
151 std::bind(&ActionBasedTreeExecutorNode::handle_trigger_cancel_, this, _1),
152 std::bind(&ActionBasedTreeExecutorNode::handle_trigger_accept_, this, _1));
153}
154
155template <typename ActionT>
157 const std::string & name, const std::string & action_name, rclcpp::NodeOptions ros_options)
158: ActionBasedTreeExecutorNode(name, action_name, Options(ros_options))
159{
160}
161
162template <typename ActionT>
164 const rclcpp_action::GoalUUID & uuid, std::shared_ptr<const TriggerGoal> /*goal_ptr*/)
165{
166 if (isBusy()) {
167 RCLCPP_WARN(
168 logger_, "Goal %s was REJECTED: Tree '%s' is currently executing.", rclcpp_action::to_string(uuid).c_str(),
169 getTreeName().c_str());
170 return false;
171 }
172 return true;
173}
174
175template <typename ActionT>
176void ActionBasedTreeExecutorNode<ActionT>::onAcceptedGoal(std::shared_ptr<TriggerGoalHandle> /*goal_handle_ptr*/)
177{
178}
179
180template <typename ActionT>
181void ActionBasedTreeExecutorNode<ActionT>::onExecutionStarted(std::shared_ptr<TriggerGoalHandle> goal_handle_ptr)
182{
183 trigger_action_context_.setUp(goal_handle_ptr);
184 RCLCPP_INFO(logger_, "Successfully started execution of tree '%s' via action trigger.", getTreeName().c_str());
185}
186
187template <typename ActionT>
189 const ExecutionResult & result, TriggerActionContext & context)
190{
191 switch (result) {
193 context.succeed();
194 break;
196 context.abort();
197 break;
199 if (context.getGoalHandlePtr()->is_canceling()) {
200 context.cancel();
201 } else {
202 context.abort();
203 }
204 break;
206 default:
207 context.abort();
208 break;
209 }
210}
211
212template <typename ActionT>
213void ActionBasedTreeExecutorNode<ActionT>::onTermination(const ExecutionResult & result)
214{
215 if (trigger_action_context_.isValid()) {
216 onGoalExecutionTermination(result, trigger_action_context_);
217 trigger_action_context_.invalidate();
218 }
219}
220
221template <typename ActionT>
222rclcpp_action::GoalResponse ActionBasedTreeExecutorNode<ActionT>::handle_trigger_goal_(
223 const rclcpp_action::GoalUUID & uuid, std::shared_ptr<const TriggerGoal> goal_ptr)
224{
225 if (!shouldAcceptGoal(uuid, goal_ptr)) {
226 return rclcpp_action::GoalResponse::REJECT;
227 }
228
229 try {
230 pending_tree_constructors_[uuid] = getTreeConstructorFromGoal(goal_ptr);
231 } catch (const std::exception & e) {
232 RCLCPP_WARN(
233 logger_, "Goal %s was REJECTED: Exception in getTreeConstructorFromGoal(): %s",
234 rclcpp_action::to_string(uuid).c_str(), e.what());
235 return rclcpp_action::GoalResponse::REJECT;
236 }
237 return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
238}
239
240template <typename ActionT>
241rclcpp_action::CancelResponse ActionBasedTreeExecutorNode<ActionT>::handle_trigger_cancel_(
242 std::shared_ptr<TriggerGoalHandle> /*goal_handle_ptr*/)
243{
244 setControlCommand(ControlCommand::TERMINATE);
245 return rclcpp_action::CancelResponse::ACCEPT;
246}
247
248template <typename ActionT>
249void ActionBasedTreeExecutorNode<ActionT>::handle_trigger_accept_(std::shared_ptr<TriggerGoalHandle> goal_handle_ptr)
250{
251 onAcceptedGoal(goal_handle_ptr);
252
253 const rclcpp_action::GoalUUID uuid = goal_handle_ptr->get_goal_id();
254 auto node = pending_tree_constructors_.extract(uuid);
255 if (node.empty()) {
256 RCLCPP_ERROR(logger_, "No pending tree constructor found for goal %s.", rclcpp_action::to_string(uuid).c_str());
257 auto result_ptr = std::make_shared<TriggerResult>();
258 goal_handle_ptr->abort(result_ptr);
259 return;
260 }
261 TreeConstructor tree_constructor = std::move(node.mapped());
262
263 const ExecutorParameters params = executor_param_listener_.get_params();
264 try {
265 startExecution(tree_constructor, params.tick_rate, params.groot2_port);
266 } catch (const std::exception & e) {
267 auto result_ptr = std::make_shared<TriggerResult>();
268 goal_handle_ptr->abort(result_ptr);
269 RCLCPP_ERROR(logger_, "An error occurred trying to start execution: %s", e.what());
270 return;
271 }
272
273 onExecutionStarted(goal_handle_ptr);
274}
275
276} // namespace auto_apms_behavior_tree
virtual void onExecutionStarted(std::shared_ptr< TriggerGoalHandle > goal_handle_ptr)
Hook called after execution has been started successfully.
virtual void onAcceptedGoal(std::shared_ptr< TriggerGoalHandle > goal_handle_ptr)
Hook called after a trigger action goal has been accepted and before execution begins.
virtual void onGoalExecutionTermination(const ExecutionResult &result, TriggerActionContext &context)
Handle the execution result for the action client.
ActionBasedTreeExecutorNode(const std::string &name, const std::string &action_name, rclcpp::NodeOptions ros_options=rclcpp::NodeOptions())
Constructor with default options.
ActionBasedTreeExecutorNode(const std::string &name, const std::string &action_name, Options options)
Constructor.
virtual TreeConstructor getTreeConstructorFromGoal(std::shared_ptr< const TriggerGoal > goal_ptr)=0
Create a TreeConstructor from the received action goal.
virtual bool shouldAcceptGoal(const rclcpp_action::GoalUUID &uuid, std::shared_ptr< const TriggerGoal > goal_ptr)
Determine whether an incoming trigger action goal should be accepted.
GenericTreeExecutorNode(const std::string &name, Options options)
Constructor.
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.
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.
Helper class that stores contextual information related to a ROS 2 action.
void cancel()
Terminate the current goal and mark it as canceled.
std::shared_ptr< GoalHandle > getGoalHandlePtr()
Get the goal handle managed by this ActionContext instance.
void succeed()
Terminate the current goal and mark it as succeeded.
void abort()
Terminate the current goal and mark it as aborted.
Powerful tooling for incorporating behavior trees for task development.
Definition behavior.hpp:32