AutoAPMS
Resilient Robot Mission Management
Loading...
Searching...
No Matches
executor_server.cpp
Go to the documentation of this file.
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// https://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
16
17#include <functional>
18
22
24{
25
26// clang-format off
27const std::string TreeExecutorServer::PARAM_NAME_TREE_BUILDER = _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_PARAM_TREE_BUILDER;
28const std::string TreeExecutorServer::PARAM_NAME_TICK_RATE = _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_PARAM_TICK_RATE;
29const std::string TreeExecutorServer::PARAM_NAME_GROOT2_PORT = _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_PARAM_GROOT2_PORT;
30const std::string TreeExecutorServer::PARAM_NAME_STATE_CHANGE_LOGGER = _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_PARAM_STATE_CHANGE_LOGGER;
31// clang-format on
32
33TreeExecutorServer::TreeExecutorServer(const std::string& name, const rclcpp::NodeOptions& options)
34 : TreeExecutor(std::make_shared<rclcpp::Node>(name, options))
35 , logger_(getNodePtr()->get_logger())
36 , executor_param_listener_(getNodePtr())
37 , start_action_context_(logger_)
38 , command_action_context_(logger_)
39{
40 using namespace std::placeholders;
41 start_action_ptr_ = rclcpp_action::create_server<StartActionContext::Type>(
43 std::bind(&TreeExecutorServer::handle_start_goal_, this, _1, _2),
44 std::bind(&TreeExecutorServer::handle_start_cancel_, this, _1),
45 std::bind(&TreeExecutorServer::handle_start_accept_, this, _1));
46
47 command_action_ptr_ = rclcpp_action::create_server<CommandActionContext::Type>(
49 std::bind(&TreeExecutorServer::handle_command_goal_, this, _1, _2),
50 std::bind(&TreeExecutorServer::handle_command_cancel_, this, _1),
51 std::bind(&TreeExecutorServer::handle_command_accept_, this, _1));
52
53 // Adding the local on_set_parameters_callback after the parameter listeners from generate_parameters_library
54 // are created makes sure that this callback will be evaluated before the listener callbacks.
55 // This is desired to keep the internal parameter struct in sync, because the callbacks of the listeners implicitly
56 // set them if the change is accepted. Otherwise, they would be set even if the local callback rejects the change.
57 on_set_parameters_callback_handle_ =
58 getNodePtr()->add_on_set_parameters_callback([this](const std::vector<rclcpp::Parameter>& parameters) {
59 return this->on_set_parameters_callback_(parameters);
60 });
61
62 // Evaluate possible cli argument dictating to start executing a specific tree immediately
63 auto& args = options.arguments();
64 if (args.size() > 1) // First argument is always path of executable
65 {
66 std::vector<std::string> additional_args{ args.begin() + 1, args.end() };
67 RCLCPP_DEBUG(logger_, "Additional cli arguments in rclcpp::NodeOptions: %s",
68 rcpputils::join(additional_args, ", ").c_str());
69 startExecution(makeCreateTreeCallback(executor_param_listener_.get_params().tree_builder_name, args[1]));
70 }
71}
72
73TreeExecutorServer::TreeExecutorServer(const rclcpp::NodeOptions& options)
74 : TreeExecutorServer(DEFAULT_NODE_NAME, options)
75{
76}
77
79 const std::string& tree_build_request,
80 const NodeManifest& node_overrides)
81{
82 // Make sure builder is available
83 if (!tree_build_director_loader_.isClassAvailable(tree_builder_name))
84 {
85 throw exceptions::TreeBuildError("There is no tree builder class named '" + tree_builder_name +
86 "'. Make sure that it's spelled correctly and registered by calling "
87 "auto_apms_behavior_tree_register_builders() in the CMakeLists.txt of the "
88 "corresponding package.");
89 }
90
91 // Try to load and create the tree builder
92 std::shared_ptr<TreeBuilderBase> build_director_ptr;
93 try
94 {
95 build_director_ptr =
96 tree_build_director_loader_.createUniqueInstance(tree_builder_name)->instantiateBuilder(getNodePtr());
97 }
98 catch (const std::exception& e)
99 {
100 throw exceptions::TreeBuildError("An error occured when trying to create an instance of tree builder '" +
101 tree_builder_name +
102 "'. Remember that the AUTO_APMS_BEHAVIOR_TREE_REGISTER_BUILDER macro must be "
103 "called in the source file for the builder class to be discoverable. Error "
104 "message: " +
105 e.what());
106 }
107
108 // Request the tree identity
109 if (!build_director_ptr->setRequest(tree_build_request))
110 {
111 throw exceptions::TreeBuildError("Request to build tree '" + tree_build_request + "' was denied by tree builder '" +
112 tree_builder_name + "'(setRequest() returned false).");
113 }
114
115 // By passing the builder's shared pointer to the callback, it lives on and the tree can be built later.
116 return [this, node_overrides, build_director_ptr](TreeBlackboardSharedPtr bb) {
117 TreeBuilder builder;
118 build_director_ptr->configureBuilder(builder);
119 builder.registerNodePlugins(getNodePtr(), node_overrides, true);
120 return builder.buildTree(bb);
121 };
122}
123
124rcl_interfaces::msg::SetParametersResult
125TreeExecutorServer::on_set_parameters_callback_(const std::vector<rclcpp::Parameter>& parameters)
126{
127 // Parameters allowed to be set while busy
128 const std::set<std::string> allowed_while_busy{ PARAM_NAME_STATE_CHANGE_LOGGER };
129
130 // Iterate through parameters and individually decide wether to reject the change
131 for (const auto& p : parameters)
132 {
133 const std::string param_name = p.get_name();
134 auto not_in_list = [&param_name](const std::set<std::string>& list) { return list.find(param_name) == list.end(); };
135 auto create_rejected = [&param_name](const std::string msg) {
136 rcl_interfaces::msg::SetParametersResult result;
137 result.successful = false;
138 result.reason = "Rejected to set parameter '" + param_name + "': " + msg;
139 return result;
140 };
141
142 // Check for allowed while busy
143 if (isBusy() && not_in_list(allowed_while_busy))
144 {
145 return create_rejected("Parameter cannot change when executor is busy");
146 }
147
148 // Check if builder plugin name is valid
149 if (param_name == PARAM_NAME_TREE_BUILDER)
150 {
151 const std::string class_name = p.as_string();
152 if (!tree_build_director_loader_.isClassAvailable(class_name))
153 {
154 return create_rejected("There is no tree builder class named '" + class_name +
155 "'. Make sure it is registered using the CMake macro "
156 "auto_apms_behavior_tree_register_builders().");
157 }
158 }
159 }
160
161 // If not returned yet, accept to set the parameter
162 rcl_interfaces::msg::SetParametersResult result;
163 result.successful = true;
164 return result;
165}
166
167rclcpp_action::GoalResponse TreeExecutorServer::handle_start_goal_(
168 const rclcpp_action::GoalUUID& uuid, std::shared_ptr<const StartActionContext::Goal> goal_ptr)
169{
170 // Reject if a tree is already executing
171 if (isBusy())
172 {
173 RCLCPP_WARN(logger_, "Goal %s was REJECTED: Tree '%s' is currently executing.",
174 rclcpp_action::to_string(uuid).c_str(), getTreeName().c_str());
175 return rclcpp_action::GoalResponse::REJECT;
176 }
177
178 NodeManifest node_overrides;
179 try
180 {
181 node_overrides = NodeManifest::fromString(goal_ptr->node_overrides);
182 }
183 catch (const std::exception& e)
184 {
185 RCLCPP_WARN(logger_, "Goal %s was REJECTED: Parsing the node override manifest failed: %s",
186 rclcpp_action::to_string(uuid).c_str(), e.what());
187 return rclcpp_action::GoalResponse::REJECT;
188 }
189
190 try
191 {
192 // Use the goal's information for determining the builder plugin to load if the string is non-empty.
193 // Otherwise use the current value of the parameter.
194 const std::string builder_name = goal_ptr->tree_builder_name.empty() ?
195 executor_param_listener_.get_params().tree_builder_name :
196 goal_ptr->tree_builder_name;
197 create_tree_callback_ = makeCreateTreeCallback(builder_name, goal_ptr->tree_build_request, node_overrides);
198 }
199 catch (const std::exception& e)
200 {
201 RCLCPP_WARN(logger_, "Goal %s was REJECTED: Error during configuring the tree builder: %s",
202 rclcpp_action::to_string(uuid).c_str(), e.what());
203 return rclcpp_action::GoalResponse::REJECT;
204 }
205 return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
206}
207
208rclcpp_action::CancelResponse
209TreeExecutorServer::handle_start_cancel_(std::shared_ptr<StartActionContext::GoalHandle> /*goal_handle_ptr*/)
210{
212 return rclcpp_action::CancelResponse::ACCEPT;
213}
214
215void TreeExecutorServer::handle_start_accept_(std::shared_ptr<StartActionContext::GoalHandle> goal_handle_ptr)
216{
217 try
218 {
219 startExecution(create_tree_callback_);
220 }
221 catch (const std::exception& e)
222 {
223 auto result_ptr = std::make_shared<StartActionContext::Result>();
224 result_ptr->message = "An error occured trying to start execution: " + std::string(e.what());
225 result_ptr->tree_result = StartActionContext::Result::TREE_RESULT_NOT_SET;
226 goal_handle_ptr->abort(result_ptr);
227 RCLCPP_ERROR_STREAM(logger_, result_ptr->message);
228 return;
229 }
230 const std::string started_tree_name = getTreeName();
231
232 // If attach is true, the goal's life time is synchronized with the execution. Otherwise we succeed immediately and
233 // leave the executor running (Detached mode).
234 if (goal_handle_ptr->get_goal()->attach)
235 {
236 start_action_context_.setUp(goal_handle_ptr);
237 RCLCPP_INFO(logger_, "Successfully started execution of tree '%s' (Mode: Attached).", started_tree_name.c_str());
238 }
239 else
240 {
241 auto result_ptr = std::make_shared<StartActionContext::Result>();
242 result_ptr->message = "Successfully started execution of tree '" + started_tree_name + "' (Mode: Detached).";
243 result_ptr->tree_result = StartActionContext::Result::TREE_RESULT_NOT_SET;
244 result_ptr->terminated_tree_identity = started_tree_name;
245 goal_handle_ptr->succeed(result_ptr);
246 RCLCPP_INFO_STREAM(logger_, result_ptr->message);
247 }
248}
249
250rclcpp_action::GoalResponse TreeExecutorServer::handle_command_goal_(
251 const rclcpp_action::GoalUUID& /*uuid*/, std::shared_ptr<const CommandActionContext::Goal> goal_ptr)
252{
253 if (command_timer_ptr_ && !command_timer_ptr_->is_canceled())
254 {
255 RCLCPP_WARN(logger_, "Request for setting tree executor command rejected, because previous one is still busy.");
256 return rclcpp_action::GoalResponse::REJECT;
257 }
258
259 if (isBusy() && start_action_context_.isValid() && start_action_context_.getGoalHandlePtr()->is_canceling())
260 {
261 RCLCPP_WARN(logger_, "Request for setting tree executor command rejected, because tree executor is canceling.");
262 return rclcpp_action::GoalResponse::REJECT;
263 }
264
265 const auto execution_state = getExecutionState();
266 switch (goal_ptr->command)
267 {
268 case CommandActionContext::Goal::COMMAND_RESUME:
269 if (execution_state == ExecutionState::PAUSED || execution_state == ExecutionState::HALTED)
270 {
271 RCLCPP_INFO(logger_, "Tree with ID '%s' will RESUME.", getTreeName().c_str());
272 }
273 else
274 {
275 RCLCPP_WARN(logger_, "Requested to RESUME with executor being in state %s. Rejecting request.",
276 toStr(execution_state).c_str());
277 return rclcpp_action::GoalResponse::REJECT;
278 }
279 break;
280 case CommandActionContext::Goal::COMMAND_PAUSE:
281 if (execution_state == ExecutionState::STARTING || execution_state == ExecutionState::RUNNING)
282 {
283 RCLCPP_INFO(logger_, "Tree with ID '%s' will PAUSE", getTreeName().c_str());
284 }
285 else
286 {
287 RCLCPP_INFO(logger_, "Requested to PAUSE with executor already being inactive (State: %s).",
288 toStr(execution_state).c_str());
289 }
290 break;
291 case CommandActionContext::Goal::COMMAND_HALT:
292 if (execution_state == ExecutionState::STARTING || execution_state == ExecutionState::RUNNING ||
293 execution_state == ExecutionState::PAUSED)
294 {
295 RCLCPP_INFO(logger_, "Tree with ID '%s' will HALT.", getTreeName().c_str());
296 }
297 else
298 {
299 RCLCPP_INFO(logger_, "Requested to HALT with executor already being inactive (State: %s).",
300 toStr(execution_state).c_str());
301 }
302 break;
303 case CommandActionContext::Goal::COMMAND_TERMINATE:
304 if (isBusy())
305 {
306 RCLCPP_INFO(logger_, "Executor will TERMINATE tree '%s'.", getTreeName().c_str());
307 }
308 else
309 {
310 RCLCPP_INFO(logger_, "Requested to TERMINATE with executor already being inactive (State: %s).",
311 toStr(execution_state).c_str());
312 }
313 break;
314 default:
315 RCLCPP_WARN(logger_, "Executor command %i is undefined. Rejecting request.", goal_ptr->command);
316 return rclcpp_action::GoalResponse::REJECT;
317 }
318 return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
319}
320
321rclcpp_action::CancelResponse
322TreeExecutorServer::handle_command_cancel_(std::shared_ptr<CommandActionContext::GoalHandle> /*goal_handle_ptr*/)
323{
324 return rclcpp_action::CancelResponse::ACCEPT;
325}
326
327void TreeExecutorServer::handle_command_accept_(std::shared_ptr<CommandActionContext::GoalHandle> goal_handle_ptr)
328{
329 const auto command_request = goal_handle_ptr->get_goal()->command;
330 switch (command_request)
331 {
332 case CommandActionContext::Goal::COMMAND_RESUME:
334 break;
335 case CommandActionContext::Goal::COMMAND_PAUSE:
337 break;
338 case CommandActionContext::Goal::COMMAND_HALT:
340 break;
341 case CommandActionContext::Goal::COMMAND_TERMINATE:
343 break;
344 }
345 ExecutionState requested_state;
346 switch (command_request)
347 {
348 case CommandActionContext::Goal::COMMAND_RESUME:
349 requested_state = ExecutionState::RUNNING;
350 break;
351 case CommandActionContext::Goal::COMMAND_PAUSE:
352 requested_state = ExecutionState::PAUSED;
353 break;
354 case CommandActionContext::Goal::COMMAND_HALT:
355 requested_state = ExecutionState::HALTED;
356 break;
357 case CommandActionContext::Goal::COMMAND_TERMINATE:
358 requested_state = ExecutionState::IDLE;
359 break;
360 default:
361 throw std::logic_error("command_request is unkown");
362 }
363
364 command_timer_ptr_ = getNodePtr()->create_wall_timer(
365 std::chrono::duration<double>(executor_param_listener_.get_params().tick_rate),
366 [this, requested_state, goal_handle_ptr, action_result_ptr = std::make_shared<CommandActionContext::Result>()]() {
367 // Check if canceling
368 if (goal_handle_ptr->is_canceling())
369 {
370 // Will abandon any progress
371 goal_handle_ptr->canceled(action_result_ptr);
372 command_timer_ptr_->cancel();
373 return;
374 }
375
376 const auto current_state = getExecutionState();
377
378 // If the execution state has become IDLE in the mean time, request failed if termination was not desired
379 if (requested_state != ExecutionState::IDLE && current_state == ExecutionState::IDLE)
380 {
381 RCLCPP_ERROR(logger_, "Failed to reach requested state %s due to cancelation of executon timer. Aborting.",
382 toStr(requested_state).c_str());
383 goal_handle_ptr->abort(action_result_ptr);
384 command_timer_ptr_->cancel();
385 return;
386 }
387
388 // Wait for the requested state to be reached
389 if (current_state != requested_state)
390 return;
391
392 goal_handle_ptr->succeed(action_result_ptr);
393 command_timer_ptr_->cancel();
394 });
395}
396
397bool TreeExecutorServer::onTick()
398{
402 setExecutorParameters(executor_param_listener_.get_params());
403
404 if (!start_action_context_.isValid()) // Don't send feedback if started in detached mode
405 return true;
406
410 auto feedback_ptr = start_action_context_.getFeedbackPtr(); // feedback from previous tick persists
411 feedback_ptr->execution_state_str = toStr(getExecutionState());
412 feedback_ptr->running_tree_identity = getTreeName();
413 auto running_action_history = getStateObserver().getRunningActionHistory();
414 if (!running_action_history.empty())
415 {
416 // If there are multiple nodes running (ParallelNode), join the IDs to a single string
417 feedback_ptr->running_action_name = rcpputils::join(running_action_history, " + ");
418 feedback_ptr->running_action_timestamp =
419 std::chrono::duration<double>{ std::chrono::high_resolution_clock::now().time_since_epoch() }.count();
420
421 // Reset the history cache
422 getStateObserver().flush();
423 }
424 start_action_context_.publishFeedback();
425
426 return true;
427}
428
429void TreeExecutorServer::onTermination(const ExecutionResult& result)
430{
431 if (!start_action_context_.isValid()) // Do nothing if started in detached mode
432 return;
433
434 auto result_ptr = start_action_context_.getResultPtr();
435 result_ptr->terminated_tree_identity = getTreeName();
436 switch (result)
437 {
438 case ExecutionResult::TREE_SUCCEEDED:
439 result_ptr->tree_result = StartActionContext::Result::TREE_RESULT_SUCCESS;
440 result_ptr->message = "Tree execution finished with status SUCCESS";
441 start_action_context_.succeed();
442 break;
443 case ExecutionResult::TREE_FAILED:
444 result_ptr->tree_result = StartActionContext::Result::TREE_RESULT_FAILURE;
445 result_ptr->message = "Tree execution finished with status FAILURE";
446 start_action_context_.abort();
447 break;
448 case ExecutionResult::TERMINATED_PREMATURELY:
449 result_ptr->tree_result = StartActionContext::Result::TREE_RESULT_NOT_SET;
450 if (start_action_context_.getGoalHandlePtr()->is_canceling())
451 {
452 result_ptr->message = "Tree execution canceled successfully";
453 start_action_context_.cancel();
454 }
455 else
456 {
457 result_ptr->message = "Tree execution terminated prematurely";
458 start_action_context_.abort();
459 }
460 break;
461 case ExecutionResult::ERROR:
462 result_ptr->tree_result = StartActionContext::Result::TREE_RESULT_NOT_SET;
463 result_ptr->message = "An unexpected error occured during tree execution";
464 start_action_context_.abort();
465 break;
466 default:
467 result_ptr->tree_result = StartActionContext::Result::TREE_RESULT_NOT_SET;
468 result_ptr->message = "Execution result unkown";
469 start_action_context_.abort();
470 break;
471 }
472
473 // Reset action context
474 start_action_context_.invalidate();
475}
476
477} // namespace auto_apms_behavior_tree
478
479#include "rclcpp_components/register_node_macro.hpp"
480RCLCPP_COMPONENTS_REGISTER_NODE(auto_apms_behavior_tree::TreeExecutorServer)
Data structure for resource lookup data and configuration parameters required for loading and registe...
static NodeManifest fromString(const std::string &manifest_str)
Class for creating behavior trees according to the builder design pattern.
TreeBuilder & registerNodePlugins(rclcpp::Node::SharedPtr node_ptr, const NodeManifest &node_manifest, NodePluginClassLoader &tree_node_loader, bool override=false)
Load behavior tree node plugins and register with behavior tree factory.
Tree buildTree(const std::string main_tree_name, TreeBlackboardSharedPtr root_bb_ptr=TreeBlackboard::create())
static const std::string PARAM_NAME_STATE_CHANGE_LOGGER
CreateTreeCallback makeCreateTreeCallback(const std::string &tree_builder_name, const std::string &tree_build_request, const NodeManifest &node_overrides={})
TreeExecutorServer(const std::string &name, const rclcpp::NodeOptions &options)
Constructor for TreeExecutorServer with custom name.
std::function< Tree(TreeBlackboardSharedPtr)> CreateTreeCallback
Definition executor.hpp:65
void setControlCommand(ControlCommand cmd)
Definition executor.cpp:255
rclcpp::Node::SharedPtr getNodePtr()
Get a pointer to the internal ROS2 node instance.
Definition executor.cpp:265
std::shared_future< ExecutionResult > startExecution(CreateTreeCallback create_tree_cb)
Definition executor.cpp:34
std::shared_ptr< GoalHandle > getGoalHandlePtr()
void setUp(std::shared_ptr< GoalHandle > goal_handle_ptr)
std::string toStr(TreeExecutor::ExecutionState state)
Definition executor.cpp:290
const char BT_EXECUTOR_RUN_ACTION_NAME_SUFFIX[]
const char BT_EXECUTOR_COMMAND_ACTION_NAME_SUFFIX[]
std::shared_ptr< TreeBlackboard > TreeBlackboardSharedPtr