AutoAPMS
Streamlining behaviors in ROS 2
Loading...
Searching...
No Matches
generic_executor_node.cpp
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#include "auto_apms_behavior_tree/executor/generic_executor_node.hpp"
16
17#include <algorithm>
18#include <functional>
19#include <regex>
20
21#include "auto_apms_behavior_tree/exceptions.hpp"
22#include "auto_apms_behavior_tree/util/parameter.hpp"
23#include "auto_apms_behavior_tree_core/definitions.hpp"
24#include "auto_apms_util/container.hpp"
25#include "auto_apms_util/string.hpp"
26#include "pluginlib/exceptions.hpp"
27
29{
30
31GenericTreeExecutorNode::GenericTreeExecutorNode(const std::string & name, Options options)
32: TreeExecutorBase(std::make_shared<rclcpp::Node>(name, options.getROSNodeOptions())),
33 executor_options_(options),
34 executor_param_listener_(node_ptr_)
35{
36 // Remove all parameters from overrides that are not supported
37 rcl_interfaces::msg::ListParametersResult res = node_ptr_->list_parameters({}, 0);
38 std::vector<std::string> unknown_param_names;
39 for (const std::string & param_name : res.names) {
40 if (!stripPrefixFromParameterName(SCRIPTING_ENUM_PARAM_PREFIX, param_name).empty()) continue;
41 if (!stripPrefixFromParameterName(BLACKBOARD_PARAM_PREFIX, param_name).empty()) continue;
42 if (auto_apms_util::contains(TREE_EXECUTOR_EXPLICITLY_ALLOWED_PARAMETERS, param_name)) continue;
43 try {
44 node_ptr_->undeclare_parameter(param_name);
45 } catch (const rclcpp::exceptions::ParameterImmutableException & e) {
46 // Allow all builtin read only parameters
47 continue;
48 } catch (const rclcpp::exceptions::InvalidParameterTypeException & e) {
49 // Allow all builtin statically typed parameters
50 continue;
51 }
52 unknown_param_names.push_back(param_name);
53 }
54 if (!unknown_param_names.empty()) {
55 RCLCPP_WARN(
56 logger_, "The following initial parameters are not supported and have been removed: [ %s ].",
57 auto_apms_util::join(unknown_param_names, ", ").c_str());
58 }
59
60 // Set custom parameter default values.
61 std::vector<rclcpp::Parameter> new_default_parameters;
62 std::map<std::string, rclcpp::ParameterValue> effective_param_overrides =
63 node_ptr_->get_node_parameters_interface()->get_parameter_overrides();
64 for (const auto & [name, value] : executor_options_.custom_default_parameters_) {
65 if (effective_param_overrides.find(name) == effective_param_overrides.end()) {
66 new_default_parameters.push_back(rclcpp::Parameter(name, value));
67 }
68 }
69 if (!new_default_parameters.empty()) node_ptr_->set_parameters_atomically(new_default_parameters);
70
71 const ExecutorParameters initial_params = executor_param_listener_.get_params();
72
73 // Create behavior tree node loader
74 tree_node_loader_ptr_ = core::NodeRegistrationLoader::make_shared(
75 std::set<std::string>(initial_params.node_exclude_packages.begin(), initial_params.node_exclude_packages.end()));
76
77 // Create behavior tree build handler loader
78 build_handler_loader_ptr_ = TreeBuildHandlerLoader::make_unique(
79 std::set<std::string>(
80 initial_params.build_handler_exclude_packages.begin(), initial_params.build_handler_exclude_packages.end()));
81
82 // Instantiate behavior tree build handler
83 if (
84 initial_params.build_handler != PARAM_VALUE_NO_BUILD_HANDLER &&
85 !build_handler_loader_ptr_->isClassAvailable(initial_params.build_handler)) {
86 throw exceptions::TreeExecutorError(
87 "Cannot load build handler '" + initial_params.build_handler +
88 "' because no corresponding ament_index resource was found. Make sure that you spelled the build handler's "
89 "name correctly "
90 "and registered it by calling auto_apms_behavior_tree_register_build_handlers() in the CMakeLists.txt of the "
91 "corresponding package.");
92 }
93 loadBuildHandler(initial_params.build_handler);
94
95 // Collect scripting enum and blackboard parameters from initial parameters
96 const auto initial_scripting_enums = getParameterValuesWithPrefix(SCRIPTING_ENUM_PARAM_PREFIX);
97 if (!initial_scripting_enums.empty()) {
98 if (executor_options_.scripting_enum_parameters_from_overrides_) {
99 updateScriptingEnumsWithParameterValues(initial_scripting_enums);
100 } else {
101 RCLCPP_WARN(
102 logger_,
103 "Initial scripting enums have been provided, but the 'Scripting enums from overrides' option is disabled. "
104 "Ignoring.");
105 }
106 }
107 const auto initial_blackboard = getParameterValuesWithPrefix(BLACKBOARD_PARAM_PREFIX);
108 if (!initial_blackboard.empty()) {
109 if (executor_options_.blackboard_parameters_from_overrides_) {
110 updateGlobalBlackboardWithParameterValues(initial_blackboard);
111 } else {
112 RCLCPP_WARN(
113 logger_,
114 "Initial blackboard entries have been provided, but the 'Blackboard from overrides' option is disabled. "
115 "Ignoring.");
116 }
117 }
118
119 using namespace std::placeholders;
120
121 // Determine action/service names
122 const std::string command_action_name =
123 executor_options_.command_action_name_.empty()
124 ? std::string(node_ptr_->get_name()) + _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_COMMAND_ACTION_NAME_SUFFIX
125 : executor_options_.command_action_name_;
126 const std::string clear_bb_service_name =
127 executor_options_.clear_blackboard_service_name_.empty()
128 ? std::string(node_ptr_->get_name()) + _AUTO_APMS_BEHAVIOR_TREE__CLEAR_BLACKBOARD_SERVICE_NAME_SUFFIX
129 : executor_options_.clear_blackboard_service_name_;
130
131 // Command action server (optional)
132 if (executor_options_.enable_command_action_) {
133 command_action_ptr_ = rclcpp_action::create_server<CommandActionContext::Type>(
134 node_ptr_, command_action_name, std::bind(&GenericTreeExecutorNode::handle_command_goal_, this, _1, _2),
135 std::bind(&GenericTreeExecutorNode::handle_command_cancel_, this, _1),
136 std::bind(&GenericTreeExecutorNode::handle_command_accept_, this, _1));
137 }
138
139 // Clear blackboard service (optional)
140 if (executor_options_.enable_clear_blackboard_service_) {
141 clear_blackboard_service_ptr_ = node_ptr_->create_service<std_srvs::srv::Trigger>(
142 clear_bb_service_name, [this](
143 const std::shared_ptr<std_srvs::srv::Trigger::Request> /*request*/,
144 std::shared_ptr<std_srvs::srv::Trigger::Response> response) {
145 response->success = this->clearGlobalBlackboard();
146 if (response->success) {
147 response->message = "Blackboard was cleared successfully";
148 } else {
149 response->message = "Blackboard cannot be cleared, because executor is in state " +
150 toStr(this->getExecutionState()) + " but must be idling";
151 }
152 RCLCPP_DEBUG_STREAM(this->logger_, response->message);
153 });
154 }
155
156 // Parameter callbacks (only if parameter sync is enabled)
157 if (
158 executor_options_.scripting_enum_parameters_from_overrides_ ||
159 executor_options_.scripting_enum_parameters_dynamic_ || executor_options_.blackboard_parameters_from_overrides_ ||
160 executor_options_.blackboard_parameters_dynamic_) {
161 on_set_parameters_callback_handle_ptr_ =
162 node_ptr_->add_on_set_parameters_callback([this](const std::vector<rclcpp::Parameter> & parameters) {
163 return this->on_set_parameters_callback_(parameters);
164 });
165
166 parameter_event_handler_ptr_ = std::make_shared<rclcpp::ParameterEventHandler>(node_ptr_);
167 parameter_event_callback_handle_ptr_ = parameter_event_handler_ptr_->add_parameter_event_callback(
168 [this](const rcl_interfaces::msg::ParameterEvent & event) { this->parameter_event_callback_(event); });
169 }
170}
171
173: GenericTreeExecutorNode("event_based_tree_executor", Options(options))
174{
175}
176
178 core::TreeBuilder & /*builder*/, const std::string & /*build_request*/, const std::string & /*entry_point*/,
179 const core::NodeManifest & /*node_manifest*/, TreeBlackboard & /*bb*/)
180{
181}
182
184
185std::shared_future<GenericTreeExecutorNode::ExecutionResult> GenericTreeExecutorNode::startExecution(
186 const std::string & build_request, const std::string & entry_point, const core::NodeManifest & node_manifest)
187{
188 const ExecutorParameters params = executor_param_listener_.get_params();
189 return startExecution(
190 makeTreeConstructor(build_request, entry_point, node_manifest), params.tick_rate, params.groot2_port);
191}
192
193GenericTreeExecutorNode::ExecutorParameters GenericTreeExecutorNode::getExecutorParameters() const
194{
195 return executor_param_listener_.get_params();
196}
197
198std::map<std::string, rclcpp::ParameterValue> GenericTreeExecutorNode::getParameterValuesWithPrefix(
199 const std::string & prefix)
200{
201 const auto res = node_ptr_->list_parameters({prefix}, 2);
202 std::map<std::string, rclcpp::ParameterValue> value_map;
203 for (const std::string & name_with_prefix : res.names) {
204 if (const std::string suffix = stripPrefixFromParameterName(prefix, name_with_prefix); !suffix.empty()) {
205 value_map[suffix] = node_ptr_->get_parameter(name_with_prefix).get_parameter_value();
206 }
207 }
208 return value_map;
209}
210
212 const std::string & prefix, const std::string & param_name)
213{
214 const std::regex reg("^" + prefix + "\\.(\\S+)");
215 if (std::smatch match; std::regex_match(param_name, match, reg)) return match[1].str();
216 return "";
217}
218
220 const std::map<std::string, rclcpp::ParameterValue> & value_map, bool simulate)
221{
222 std::map<std::string, std::string> set_successfully_map;
223 for (const auto & [enum_key, pval] : value_map) {
224 try {
225 switch (pval.get_type()) {
226 case rclcpp::ParameterType::PARAMETER_BOOL:
227 if (simulate) continue;
228 scripting_enums_[enum_key] = static_cast<int>(pval.get<bool>());
229 break;
230 case rclcpp::ParameterType::PARAMETER_INTEGER:
231 if (simulate) continue;
232 scripting_enums_[enum_key] = static_cast<int>(pval.get<int>());
233 break;
234 default:
235 if (simulate) return false;
236 throw exceptions::ParameterConversionError("Parameter to scripting enum conversion is not allowed.");
237 }
238 set_successfully_map[enum_key] = rclcpp::to_string(pval);
239 } catch (const std::exception & e) {
240 RCLCPP_ERROR(
241 logger_, "Error setting scripting enum from parameter %s=%s (Type: %s): %s", enum_key.c_str(),
242 rclcpp::to_string(pval).c_str(), rclcpp::to_string(pval.get_type()).c_str(), e.what());
243 return false;
244 }
245 }
246 if (!set_successfully_map.empty()) {
247 RCLCPP_DEBUG(
248 logger_, "Updated scripting enums from parameters: { %s }",
249 auto_apms_util::printMap(set_successfully_map).c_str());
250 }
251 return true;
252}
253
255 const std::map<std::string, rclcpp::ParameterValue> & value_map, bool simulate)
256{
257 TreeBlackboard & bb = *getGlobalBlackboardPtr();
258 std::map<std::string, std::string> set_successfully_map;
259 for (const auto & [entry_key, pval] : value_map) {
260 try {
261 if (const BT::Expected<BT::Any> expected = createAnyFromParameterValue(pval)) {
262 BT::Any any(expected.value());
263 if (simulate) {
264 if (const BT::TypeInfo * entry_info = bb.entryInfo(entry_key)) {
265 if (entry_info->isStronglyTyped() && entry_info->type() != any.type()) return false;
266 }
267 continue;
268 } else {
269 bb.set(entry_key, any);
270 }
271 } else {
272 throw exceptions::ParameterConversionError(expected.error());
273 }
274 translated_global_blackboard_entries_[entry_key] = pval;
275 set_successfully_map[entry_key] = rclcpp::to_string(pval);
276 } catch (const std::exception & e) {
277 RCLCPP_ERROR(
278 logger_, "Error updating blackboard from parameter %s=%s (Type: %s): %s", entry_key.c_str(),
279 rclcpp::to_string(pval).c_str(), rclcpp::to_string(pval.get_type()).c_str(), e.what());
280 return false;
281 }
282 }
283 if (!set_successfully_map.empty()) {
284 RCLCPP_DEBUG(
285 logger_, "Updated blackboard from parameters: { %s }", auto_apms_util::printMap(set_successfully_map).c_str());
286 }
287 return true;
288}
289
290void GenericTreeExecutorNode::loadBuildHandler(const std::string & name)
291{
292 if (build_handler_ptr_ && !executor_param_listener_.get_params().allow_other_build_handlers) {
293 throw std::logic_error(
294 "Executor option 'Allow other build handlers' is disabled, but loadBuildHandler() was called again after "
295 "instantiating '" +
296 current_build_handler_name_ + "'.");
297 }
298 if (current_build_handler_name_ == name) return;
299 if (name == PARAM_VALUE_NO_BUILD_HANDLER) {
300 build_handler_ptr_.reset();
301 } else {
302 try {
303 build_handler_ptr_ =
304 build_handler_loader_ptr_->createUniqueInstance(name)->makeUnique(node_ptr_, tree_node_loader_ptr_);
305 } catch (const pluginlib::CreateClassException & e) {
306 throw exceptions::TreeExecutorError(
307 "An error occurred when trying to create an instance of tree build handler class '" + name +
308 "'. This might be because you forgot to call the AUTO_APMS_BEHAVIOR_TREE_REGISTER_BUILD_HANDLER macro "
309 "in the source file: " +
310 e.what());
311 } catch (const std::exception & e) {
312 throw exceptions::TreeExecutorError(
313 "An error occurred when trying to create an instance of tree build handler class '" + name + "': " + e.what());
314 }
315 }
316 current_build_handler_name_ = name;
317}
318
320 const std::string & build_request, const std::string & entry_point, const core::NodeManifest & node_manifest)
321{
322 // Request the tree identity
323 if (build_handler_ptr_ && !build_handler_ptr_->setBuildRequest(build_request, entry_point, node_manifest)) {
324 throw exceptions::TreeBuildError(
325 "Build request '" + build_request + "' was denied by '" + current_build_handler_name_ +
326 "' (setBuildRequest() returned false).");
327 }
328
329 return [this, build_request, entry_point, node_manifest](TreeBlackboardSharedPtr bb_ptr) {
330 // Currently, BehaviorTree.CPP requires the memory allocated by the factory to persist even after the tree has
331 // been created, so we make the builder a unique pointer that is only reset when a new tree is to be created. See
332 // https://github.com/BehaviorTree/BehaviorTree.CPP/issues/890
333 this->builder_ptr_.reset(new core::TreeBuilder(
335 this->tree_node_loader_ptr_));
336
337 // Allow executor to make modifications prior to building the tree
338 this->preBuild(*this->builder_ptr_, build_request, entry_point, node_manifest, *bb_ptr);
339
340 // Make scripting enums available to tree instance
341 for (const auto & [enum_key, val] : this->scripting_enums_) this->builder_ptr_->setScriptingEnum(enum_key, val);
342
343 // If a build handler is specified, let it configure the builder and determine which tree is to be instantiated
344 std::string instantiate_name = "";
345 if (this->build_handler_ptr_) {
346 instantiate_name = this->build_handler_ptr_->buildTree(*this->builder_ptr_, *bb_ptr).getName();
347 }
348
349 // Finally, instantiate the tree
350 Tree tree = instantiate_name.empty() ? this->builder_ptr_->instantiate(bb_ptr)
351 : this->builder_ptr_->instantiate(instantiate_name, bb_ptr);
352
353 // Allow executor to make modifications after building the tree, but before execution starts
354 this->postBuild(tree);
355 return tree;
356 };
357}
358
359core::TreeBuilder::SharedPtr GenericTreeExecutorNode::createTreeBuilder()
360{
361 return core::TreeBuilder::make_shared(
363 this->tree_node_loader_ptr_);
364}
365
367{
369 if (executor_options_.blackboard_parameters_from_overrides_ || executor_options_.blackboard_parameters_dynamic_) {
370 const auto res = node_ptr_->list_parameters({BLACKBOARD_PARAM_PREFIX}, 2);
371 for (const std::string & name : res.names) {
372 node_ptr_->undeclare_parameter(name);
373 }
374 }
375 return true;
376 }
377 return false;
378}
379
380rcl_interfaces::msg::SetParametersResult GenericTreeExecutorNode::on_set_parameters_callback_(
381 const std::vector<rclcpp::Parameter> & parameters)
382{
383 const ExecutorParameters params = executor_param_listener_.get_params();
384
385 for (const rclcpp::Parameter & p : parameters) {
386 auto create_rejected = [&p](const std::string msg) {
387 rcl_interfaces::msg::SetParametersResult result;
388 result.successful = false;
389 result.reason = "Rejected to set " + p.get_name() + " = " + p.value_to_string() + " (Type: " + p.get_type_name() +
390 "): " + msg + ".";
391 return result;
392 };
393 const std::string param_name = p.get_name();
394
395 // Check if parameter is a scripting enum
396 if (const std::string enum_key = stripPrefixFromParameterName(SCRIPTING_ENUM_PARAM_PREFIX, param_name);
397 !enum_key.empty()) {
398 if (isBusy()) {
399 return create_rejected("Scripting enums cannot change while tree executor is running");
400 }
401 if (!executor_options_.scripting_enum_parameters_dynamic_ || !params.allow_dynamic_scripting_enums) {
402 return create_rejected(
403 "Cannot set scripting enum '" + enum_key + "', because the 'Dynamic scripting enums' option is disabled");
404 }
405 if (!updateScriptingEnumsWithParameterValues({{enum_key, p.get_parameter_value()}}, true)) {
406 return create_rejected(
407 "Type of scripting enum must be bool or int. Tried to set enum '" + enum_key + "' with value '" +
408 p.value_to_string() + "' (Type: " + p.get_type_name() + ")");
409 }
410 continue;
411 }
412
413 // Check if parameter is a blackboard parameter
414 if (const std::string entry_key = stripPrefixFromParameterName(BLACKBOARD_PARAM_PREFIX, param_name);
415 !entry_key.empty()) {
416 if (!executor_options_.blackboard_parameters_dynamic_ || !params.allow_dynamic_blackboard) {
417 return create_rejected(
418 "Cannot set blackboard entry '" + entry_key + "', because the 'Dynamic blackboard' option is disabled");
419 }
420 if (!updateGlobalBlackboardWithParameterValues({{entry_key, p.get_parameter_value()}}, true)) {
421 return create_rejected(
422 "Type of blackboard entries must not change. Tried to set entry '" + entry_key +
423 "' (Type: " + getGlobalBlackboardPtr()->getEntry(entry_key)->info.typeName() + ") with value '" +
424 p.value_to_string() + "' (Type: " + p.get_type_name() + ")");
425 }
426 continue;
427 }
428
429 // Check if parameter is known
430 if (!auto_apms_util::contains(TREE_EXECUTOR_EXPLICITLY_ALLOWED_PARAMETERS, param_name)) {
431 return create_rejected("Parameter is unknown");
432 }
433
434 // Check if the parameter is allowed to change during execution
435 if (isBusy() && !auto_apms_util::contains(TREE_EXECUTOR_EXPLICITLY_ALLOWED_PARAMETERS_WHILE_BUSY, param_name)) {
436 return create_rejected("Parameter is not allowed to change while tree executor is running");
437 }
438
439 // Check if build handler is allowed to change and valid
440 if (param_name == _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_PARAM_BUILD_HANDLER) {
441 if (!params.allow_other_build_handlers) {
442 return create_rejected(
443 "This executor operates with tree build handler '" + executor_param_listener_.get_params().build_handler +
444 "' and doesn't allow other build handlers to be loaded since the 'Allow other build handlers' option is "
445 "disabled");
446 }
447 const std::string class_name = p.as_string();
448 if (class_name != PARAM_VALUE_NO_BUILD_HANDLER && !build_handler_loader_ptr_->isClassAvailable(class_name)) {
449 return create_rejected(
450 "Cannot load build handler '" + class_name +
451 "' because no corresponding ament_index resource was found. Make sure that you spelled the build handler's "
452 "name correctly "
453 "and registered it by calling auto_apms_behavior_tree_register_build_handlers() in the CMakeLists.txt of "
454 "the "
455 "corresponding package");
456 }
457 }
458
459 // At this point, if the parameter hasn't been declared, we do not support it.
460 if (!node_ptr_->has_parameter(param_name)) {
461 return create_rejected("Parameter '" + param_name + "' is not supported");
462 }
463 }
464
465 rcl_interfaces::msg::SetParametersResult result;
466 result.successful = true;
467 return result;
468}
469
470void GenericTreeExecutorNode::parameter_event_callback_(const rcl_interfaces::msg::ParameterEvent & event)
471{
472 std::regex re(node_ptr_->get_fully_qualified_name());
473 if (std::regex_match(event.node, re)) {
474 for (const rclcpp::Parameter & p : rclcpp::ParameterEventHandler::get_parameters_from_event(event)) {
475 const std::string param_name = p.get_name();
476
477 if (const std::string enum_key = stripPrefixFromParameterName(SCRIPTING_ENUM_PARAM_PREFIX, param_name);
478 !enum_key.empty()) {
479 updateScriptingEnumsWithParameterValues({{enum_key, p.get_parameter_value()}});
480 }
481
482 if (const std::string entry_key = stripPrefixFromParameterName(BLACKBOARD_PARAM_PREFIX, param_name);
483 !entry_key.empty()) {
484 updateGlobalBlackboardWithParameterValues({{entry_key, p.get_parameter_value()}}, false);
485 }
486
487 if (param_name == _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_PARAM_BUILD_HANDLER) {
488 loadBuildHandler(p.as_string());
489 }
490 }
491 }
492}
493
494rclcpp_action::GoalResponse GenericTreeExecutorNode::handle_command_goal_(
495 const rclcpp_action::GoalUUID & /*uuid*/, std::shared_ptr<const CommandActionContext::Goal> goal_ptr)
496{
497 if (command_timer_ptr_ && !command_timer_ptr_->is_canceled()) {
498 RCLCPP_WARN(logger_, "Request for setting tree executor command rejected, because previous one is still busy.");
499 return rclcpp_action::GoalResponse::REJECT;
500 }
501
502 const auto execution_state = getExecutionState();
503 switch (goal_ptr->command) {
504 case CommandActionContext::Goal::COMMAND_RESUME:
505 if (execution_state == ExecutionState::PAUSED || execution_state == ExecutionState::HALTED) {
506 RCLCPP_INFO(logger_, "Tree with ID '%s' will RESUME.", getTreeName().c_str());
507 } else {
508 RCLCPP_WARN(
509 logger_, "Requested to RESUME with executor being in state %s. Rejecting request.",
510 toStr(execution_state).c_str());
511 return rclcpp_action::GoalResponse::REJECT;
512 }
513 break;
514 case CommandActionContext::Goal::COMMAND_PAUSE:
515 if (execution_state == ExecutionState::STARTING || execution_state == ExecutionState::RUNNING) {
516 RCLCPP_INFO(logger_, "Tree with ID '%s' will PAUSE", getTreeName().c_str());
517 } else {
518 RCLCPP_INFO(
519 logger_, "Requested to PAUSE with executor already being inactive (State: %s).",
520 toStr(execution_state).c_str());
521 }
522 break;
523 case CommandActionContext::Goal::COMMAND_HALT:
524 if (
525 execution_state == ExecutionState::STARTING || execution_state == ExecutionState::RUNNING ||
526 execution_state == ExecutionState::PAUSED) {
527 RCLCPP_INFO(logger_, "Tree with ID '%s' will HALT.", getTreeName().c_str());
528 } else {
529 RCLCPP_INFO(
530 logger_, "Requested to HALT with executor already being inactive (State: %s).",
531 toStr(execution_state).c_str());
532 }
533 break;
534 case CommandActionContext::Goal::COMMAND_TERMINATE:
535 if (isBusy()) {
536 RCLCPP_INFO(logger_, "Executor will TERMINATE tree '%s'.", getTreeName().c_str());
537 } else {
538 RCLCPP_INFO(
539 logger_, "Requested to TERMINATE with executor already being inactive (State: %s).",
540 toStr(execution_state).c_str());
541 }
542 break;
543 default:
544 RCLCPP_WARN(logger_, "Executor command %i is undefined. Rejecting request.", goal_ptr->command);
545 return rclcpp_action::GoalResponse::REJECT;
546 }
547 return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
548}
549
550rclcpp_action::CancelResponse GenericTreeExecutorNode::handle_command_cancel_(
551 std::shared_ptr<CommandActionContext::GoalHandle> /*goal_handle_ptr*/)
552{
553 return rclcpp_action::CancelResponse::ACCEPT;
554}
555
556void GenericTreeExecutorNode::handle_command_accept_(std::shared_ptr<CommandActionContext::GoalHandle> goal_handle_ptr)
557{
558 const auto command_request = goal_handle_ptr->get_goal()->command;
559 ExecutionState requested_state;
560 switch (command_request) {
561 case CommandActionContext::Goal::COMMAND_RESUME:
563 requested_state = ExecutionState::RUNNING;
564 break;
565 case CommandActionContext::Goal::COMMAND_PAUSE:
567 requested_state = ExecutionState::PAUSED;
568 break;
569 case CommandActionContext::Goal::COMMAND_HALT:
571 requested_state = ExecutionState::HALTED;
572 break;
573 case CommandActionContext::Goal::COMMAND_TERMINATE:
575 requested_state = ExecutionState::IDLE;
576 break;
577 default:
578 throw std::logic_error("command_request is unknown");
579 }
580
581 command_timer_ptr_ = node_ptr_->create_wall_timer(
582 std::chrono::duration<double>(executor_param_listener_.get_params().tick_rate),
583 [this, requested_state, goal_handle_ptr, action_result_ptr = std::make_shared<CommandActionContext::Result>()]() {
584 if (goal_handle_ptr->is_canceling()) {
585 goal_handle_ptr->canceled(action_result_ptr);
586 command_timer_ptr_->cancel();
587 return;
588 }
589
590 const auto current_state = getExecutionState();
591
592 if (requested_state != ExecutionState::IDLE && current_state == ExecutionState::IDLE) {
593 RCLCPP_ERROR(
594 logger_, "Failed to reach requested state %s due to cancellation of execution timer. Aborting.",
595 toStr(requested_state).c_str());
596 goal_handle_ptr->abort(action_result_ptr);
597 command_timer_ptr_->cancel();
598 return;
599 }
600
601 if (current_state != requested_state) return;
602
603 goal_handle_ptr->succeed(action_result_ptr);
604 command_timer_ptr_->cancel();
605 });
606}
607
608bool GenericTreeExecutorNode::onTick()
609{
610 const ExecutorParameters params = executor_param_listener_.get_params();
611 getStateObserver().setLogging(params.state_change_logger);
612 return true;
613}
614
615bool GenericTreeExecutorNode::afterTick()
616{
617 const ExecutorParameters params = executor_param_listener_.get_params();
618
619 // Synchronize parameters with new blackboard entries if enabled
620 if (executor_options_.blackboard_parameters_dynamic_ && params.allow_dynamic_blackboard) {
621 TreeBlackboardSharedPtr bb_ptr = getGlobalBlackboardPtr();
622 std::vector<rclcpp::Parameter> new_parameters;
623 for (const BT::StringView & str : bb_ptr->getKeys()) {
624 const std::string key = std::string(str);
625 const BT::TypeInfo * type_info = bb_ptr->entryInfo(key);
626 const BT::Any * any = bb_ptr->getAnyLocked(key).get();
627
628 if (any->empty()) continue;
629
630 if (translated_global_blackboard_entries_.find(key) == translated_global_blackboard_entries_.end()) {
631 const BT::Expected<rclcpp::ParameterValue> expected =
632 createParameterValueFromAny(*any, rclcpp::PARAMETER_NOT_SET);
633 if (expected) {
634 new_parameters.push_back(rclcpp::Parameter(BLACKBOARD_PARAM_PREFIX + "." + key, expected.value()));
635 translated_global_blackboard_entries_[key] = expected.value();
636 } else {
637 RCLCPP_WARN(
638 logger_, "Failed to translate new blackboard entry '%s' (Type: %s) to parameters: %s", key.c_str(),
639 type_info->typeName().c_str(), expected.error().c_str());
640 }
641 } else {
642 const BT::Expected<rclcpp::ParameterValue> expected =
643 createParameterValueFromAny(*any, translated_global_blackboard_entries_[key].get_type());
644 if (expected) {
645 if (expected.value() != translated_global_blackboard_entries_[key]) {
646 new_parameters.push_back(rclcpp::Parameter(BLACKBOARD_PARAM_PREFIX + "." + key, expected.value()));
647 }
648 } else {
649 RCLCPP_WARN(
650 logger_, "Failed to translate blackboard entry '%s' (Type: %s) to parameters: %s", key.c_str(),
651 type_info->typeName().c_str(), expected.error().c_str());
652 }
653 }
654 }
655 if (!new_parameters.empty()) {
656 const rcl_interfaces::msg::SetParametersResult result = node_ptr_->set_parameters_atomically(new_parameters);
657 if (!result.successful) {
658 throw exceptions::TreeExecutorError(
659 "Unexpectedly failed to set parameters inferred from global blackboard. Reason: " + result.reason);
660 }
661 }
662 }
663
664 return true;
665}
666
667} // namespace auto_apms_behavior_tree
bool updateGlobalBlackboardWithParameterValues(const std::map< std::string, rclcpp::ParameterValue > &value_map, bool simulate=false)
Update the global blackboard using parameter values.
static const std::string PARAM_VALUE_NO_BUILD_HANDLER
Value indicating that no build handler is loaded.
GenericTreeExecutorNode(const std::string &name, Options options)
Constructor.
static std::string stripPrefixFromParameterName(const std::string &prefix, const std::string &param_name)
Get the name of a parameter without its prefix.
virtual bool clearGlobalBlackboard() override
Reset the global blackboard and clear all entries.
core::TreeBuilder::SharedPtr createTreeBuilder()
Create a tree builder for building the behavior tree.
std::map< std::string, rclcpp::ParameterValue > getParameterValuesWithPrefix(const std::string &prefix)
Assemble all parameters of this node that have a specific prefix.
bool updateScriptingEnumsWithParameterValues(const std::map< std::string, rclcpp::ParameterValue > &value_map, bool simulate=false)
Update the internal buffer of scripting enums.
ExecutorParameters getExecutorParameters() const
Get a copy of the current executor parameters.
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.
virtual void postBuild(Tree &tree)
Callback invoked after the behavior tree has been instantiated.
virtual void preBuild(core::TreeBuilder &builder, const std::string &build_request, const std::string &entry_point, const core::NodeManifest &node_manifest, TreeBlackboard &bb)
Callback invoked before building the behavior tree.
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
Enum representing possible behavior tree execution states.
@ 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.
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.
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 clearGlobalBlackboard()
Reset the global blackboard and clear all entries.
@ TERMINATE
Halt the currently executing tree and terminate 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.
TreeBlackboardSharedPtr getGlobalBlackboardPtr()
Get a shared pointer to the global blackboard instance.
std::string getTreeName()
Get the name of the tree that is currently executing.
const rclcpp::Logger logger_
Logger associated with the parent ROS 2 node.
void setLogging(bool active)
Configure whether the observer should write to the logger.
Data structure for information about which behavior tree node plugin to load and how to configure the...
Class for configuring and instantiating behavior trees.
Definition builder.hpp:55
bool contains(const ContainerT< ValueT, AllocatorT > &c, const ValueT &val)
Check whether a particular container structure contains a value.
Definition container.hpp:36
std::string printMap(const std::map< std::string, std::string > &map, const std::string &key_val_sep="=", const std::string &entry_sep=", ")
Converts a map to a string representation that is suited for printing to console.
Definition string.cpp:50
const char * toStr(const ActionNodeErrorCode &err)
Convert the action error code to string.
Powerful tooling for incorporating behavior trees for task development.
Definition behavior.hpp:32
BT::Expected< BT::Any > createAnyFromParameterValue(const rclcpp::ParameterValue &val)
Convert a ROS 2 parameter value to a BT::Any object.
Definition parameter.cpp:20
BT::Expected< rclcpp::ParameterValue > createParameterValueFromAny(const BT::Any &any, rclcpp::ParameterType type)
Convert a BT::Any object to a ROS 2 parameter value.
Definition parameter.cpp:51