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(rclcpp::Node::SharedPtr node_ptr, Options options)
32: TreeExecutorBase(node_ptr), executor_options_(options), executor_param_listener_(node_ptr_)
33{
34 if (executor_options_.strict_unkown_parameter_removal_) {
35 // Remove all parameters from overrides that are not supported.
36 rcl_interfaces::msg::ListParametersResult res = node_ptr_->list_parameters({}, 0);
37 std::vector<std::string> unknown_param_names;
38 for (const std::string & param_name : res.names) {
39 if (!stripPrefixFromParameterName(SCRIPTING_ENUM_PARAM_PREFIX, param_name).empty()) continue;
40 if (!stripPrefixFromParameterName(BLACKBOARD_PARAM_PREFIX, param_name).empty()) continue;
41 if (auto_apms_util::contains(TREE_EXECUTOR_EXPLICITLY_ALLOWED_PARAMETERS, param_name)) continue;
42 try {
43 node_ptr_->undeclare_parameter(param_name);
44 } catch (const rclcpp::exceptions::ParameterImmutableException & e) {
45 // Allow all builtin read only parameters.
46 continue;
47 } catch (const rclcpp::exceptions::InvalidParameterTypeException & e) {
48 // Allow all builtin statically typed parameters.
49 continue;
50 }
51 unknown_param_names.push_back(param_name);
52 }
53 if (!unknown_param_names.empty()) {
54 RCLCPP_WARN(
55 logger_, "The following initial parameters are not supported and have been removed: [ %s ].",
56 auto_apms_util::join(unknown_param_names, ", ").c_str());
57 }
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
172GenericTreeExecutorNode::GenericTreeExecutorNode(const std::string & name, Options options)
173: GenericTreeExecutorNode(std::make_shared<rclcpp::Node>(name, options.getROSNodeOptions()), options)
174{
175}
176
178
180 core::TreeBuilder & /*builder*/, const std::string & /*build_request*/, const std::string & /*entry_point*/,
181 const core::NodeManifest & /*node_manifest*/, TreeBlackboard & /*bb*/)
182{
183}
184
186
187std::shared_future<GenericTreeExecutorNode::ExecutionResult> GenericTreeExecutorNode::startExecution(
188 const std::string & build_request, const std::string & entry_point, const core::NodeManifest & node_manifest)
189{
190 const ExecutorParameters params = executor_param_listener_.get_params();
191 return startExecution(
192 makeTreeConstructor(build_request, entry_point, node_manifest), params.tick_rate, params.groot2_port);
193}
194
195bool GenericTreeExecutorNode::onTick()
196{
197 const ExecutorParameters params = executor_param_listener_.get_params();
198 getStateObserver().setLogging(params.state_change_logger);
199 return true;
200}
201
202bool GenericTreeExecutorNode::afterTick()
203{
204 const ExecutorParameters params = executor_param_listener_.get_params();
205
206 // Synchronize parameters with new blackboard entries if enabled
207 if (executor_options_.blackboard_parameters_dynamic_ && params.allow_dynamic_blackboard) {
208 TreeBlackboardSharedPtr bb_ptr = getGlobalBlackboardPtr();
209 std::vector<rclcpp::Parameter> new_parameters;
210 for (const BT::StringView & str : bb_ptr->getKeys()) {
211 const std::string key = std::string(str);
212 const BT::TypeInfo * type_info = bb_ptr->entryInfo(key);
213 const BT::Any * any = bb_ptr->getAnyLocked(key).get();
214
215 if (any->empty()) continue;
216
217 if (translated_global_blackboard_entries_.find(key) == translated_global_blackboard_entries_.end()) {
218 const BT::Expected<rclcpp::ParameterValue> expected =
219 createParameterValueFromAny(*any, rclcpp::PARAMETER_NOT_SET);
220 if (expected) {
221 new_parameters.push_back(rclcpp::Parameter(BLACKBOARD_PARAM_PREFIX + "." + key, expected.value()));
222 translated_global_blackboard_entries_[key] = expected.value();
223 } else {
224 RCLCPP_WARN(
225 logger_, "Failed to translate new blackboard entry '%s' (Type: %s) to parameters: %s", key.c_str(),
226 type_info->typeName().c_str(), expected.error().c_str());
227 }
228 } else {
229 const BT::Expected<rclcpp::ParameterValue> expected =
230 createParameterValueFromAny(*any, translated_global_blackboard_entries_[key].get_type());
231 if (expected) {
232 if (expected.value() != translated_global_blackboard_entries_[key]) {
233 new_parameters.push_back(rclcpp::Parameter(BLACKBOARD_PARAM_PREFIX + "." + key, expected.value()));
234 }
235 } else {
236 RCLCPP_WARN(
237 logger_, "Failed to translate blackboard entry '%s' (Type: %s) to parameters: %s", key.c_str(),
238 type_info->typeName().c_str(), expected.error().c_str());
239 }
240 }
241 }
242 if (!new_parameters.empty()) {
243 const rcl_interfaces::msg::SetParametersResult result = node_ptr_->set_parameters_atomically(new_parameters);
244 if (!result.successful) {
245 throw exceptions::TreeExecutorError(
246 "Unexpectedly failed to set parameters inferred from global blackboard. Reason: " + result.reason);
247 }
248 }
249 }
250
251 return true;
252}
253
254GenericTreeExecutorNode::ExecutorParameters GenericTreeExecutorNode::getExecutorParameters() const
255{
256 return executor_param_listener_.get_params();
257}
258
259std::map<std::string, rclcpp::ParameterValue> GenericTreeExecutorNode::getParameterValuesWithPrefix(
260 const std::string & prefix)
261{
262 const auto res = node_ptr_->list_parameters({prefix}, 2);
263 std::map<std::string, rclcpp::ParameterValue> value_map;
264 for (const std::string & name_with_prefix : res.names) {
265 if (const std::string suffix = stripPrefixFromParameterName(prefix, name_with_prefix); !suffix.empty()) {
266 value_map[suffix] = node_ptr_->get_parameter(name_with_prefix).get_parameter_value();
267 }
268 }
269 return value_map;
270}
271
273 const std::string & prefix, const std::string & param_name)
274{
275 const std::regex reg("^" + prefix + "\\.(\\S+)");
276 if (std::smatch match; std::regex_match(param_name, match, reg)) return match[1].str();
277 return "";
278}
279
281 const std::map<std::string, rclcpp::ParameterValue> & value_map, bool simulate)
282{
283 std::map<std::string, std::string> set_successfully_map;
284 for (const auto & [enum_key, pval] : value_map) {
285 try {
286 switch (pval.get_type()) {
287 case rclcpp::ParameterType::PARAMETER_BOOL:
288 if (simulate) continue;
289 scripting_enums_[enum_key] = static_cast<int>(pval.get<bool>());
290 break;
291 case rclcpp::ParameterType::PARAMETER_INTEGER:
292 if (simulate) continue;
293 scripting_enums_[enum_key] = static_cast<int>(pval.get<int>());
294 break;
295 default:
296 if (simulate) return false;
297 throw exceptions::ParameterConversionError("Parameter to scripting enum conversion is not allowed.");
298 }
299 set_successfully_map[enum_key] = rclcpp::to_string(pval);
300 } catch (const std::exception & e) {
301 RCLCPP_ERROR(
302 logger_, "Error setting scripting enum from parameter %s=%s (Type: %s): %s", enum_key.c_str(),
303 rclcpp::to_string(pval).c_str(), rclcpp::to_string(pval.get_type()).c_str(), e.what());
304 return false;
305 }
306 }
307 if (!set_successfully_map.empty()) {
308 RCLCPP_DEBUG(
309 logger_, "Updated scripting enums from parameters: { %s }",
310 auto_apms_util::printMap(set_successfully_map).c_str());
311 }
312 return true;
313}
314
316 const std::map<std::string, rclcpp::ParameterValue> & value_map, bool simulate)
317{
318 TreeBlackboard & bb = *getGlobalBlackboardPtr();
319 std::map<std::string, std::string> set_successfully_map;
320 for (const auto & [entry_key, pval] : value_map) {
321 try {
322 if (const BT::Expected<BT::Any> expected = createAnyFromParameterValue(pval)) {
323 BT::Any any(expected.value());
324 if (simulate) {
325 if (const BT::TypeInfo * entry_info = bb.entryInfo(entry_key)) {
326 if (entry_info->isStronglyTyped() && entry_info->type() != any.type()) return false;
327 }
328 continue;
329 } else {
330 bb.set(entry_key, any);
331 }
332 } else {
333 throw exceptions::ParameterConversionError(expected.error());
334 }
335 translated_global_blackboard_entries_[entry_key] = pval;
336 set_successfully_map[entry_key] = rclcpp::to_string(pval);
337 } catch (const std::exception & e) {
338 RCLCPP_ERROR(
339 logger_, "Error updating blackboard from parameter %s=%s (Type: %s): %s", entry_key.c_str(),
340 rclcpp::to_string(pval).c_str(), rclcpp::to_string(pval.get_type()).c_str(), e.what());
341 return false;
342 }
343 }
344 if (!set_successfully_map.empty()) {
345 RCLCPP_DEBUG(
346 logger_, "Updated blackboard from parameters: { %s }", auto_apms_util::printMap(set_successfully_map).c_str());
347 }
348 return true;
349}
350
351void GenericTreeExecutorNode::loadBuildHandler(const std::string & name)
352{
353 if (build_handler_ptr_ && !executor_param_listener_.get_params().allow_other_build_handlers) {
354 throw std::logic_error(
355 "Executor option 'Allow other build handlers' is disabled, but loadBuildHandler() was called again after "
356 "instantiating '" +
357 current_build_handler_name_ + "'.");
358 }
359 if (current_build_handler_name_ == name) return;
360 if (name == PARAM_VALUE_NO_BUILD_HANDLER) {
361 build_handler_ptr_.reset();
362 } else {
363 try {
364 build_handler_ptr_ =
365 build_handler_loader_ptr_->createUniqueInstance(name)->makeUnique(node_ptr_, tree_node_loader_ptr_);
366 } catch (const pluginlib::CreateClassException & e) {
367 throw exceptions::TreeExecutorError(
368 "An error occurred when trying to create an instance of tree build handler class '" + name +
369 "'. This might be because you forgot to call the AUTO_APMS_BEHAVIOR_TREE_REGISTER_BUILD_HANDLER macro "
370 "in the source file: " +
371 e.what());
372 } catch (const std::exception & e) {
373 throw exceptions::TreeExecutorError(
374 "An error occurred when trying to create an instance of tree build handler class '" + name + "': " + e.what());
375 }
376 }
377 current_build_handler_name_ = name;
378}
379
381 const std::string & build_request, const std::string & entry_point, const core::NodeManifest & node_manifest)
382{
383 // Request the tree identity
384 if (build_handler_ptr_ && !build_handler_ptr_->setBuildRequest(build_request, entry_point, node_manifest)) {
385 throw exceptions::TreeBuildError(
386 "Build request '" + build_request + "' was denied by '" + current_build_handler_name_ +
387 "' (setBuildRequest() returned false).");
388 }
389
390 return [this, build_request, entry_point, node_manifest](TreeBlackboardSharedPtr bb_ptr) {
391 // Currently, BehaviorTree.CPP requires the memory allocated by the factory to persist even after the tree has
392 // been created, so we make the builder a unique pointer that is only reset when a new tree is to be created. See
393 // https://github.com/BehaviorTree/BehaviorTree.CPP/issues/890
394 this->builder_ptr_.reset(new core::TreeBuilder(
396 this->tree_node_loader_ptr_));
397
398 // Allow executor to make modifications prior to building the tree
399 this->preBuild(*this->builder_ptr_, build_request, entry_point, node_manifest, *bb_ptr);
400
401 // Make scripting enums available to tree instance
402 for (const auto & [enum_key, val] : this->scripting_enums_) this->builder_ptr_->setScriptingEnum(enum_key, val);
403
404 // If a build handler is specified, let it configure the builder and determine which tree is to be instantiated
405 std::string instantiate_name = "";
406 if (this->build_handler_ptr_) {
407 instantiate_name = this->build_handler_ptr_->buildTree(*this->builder_ptr_, *bb_ptr).getName();
408 }
409
410 // Finally, instantiate the tree
411 Tree tree = instantiate_name.empty() ? this->builder_ptr_->instantiate(bb_ptr)
412 : this->builder_ptr_->instantiate(instantiate_name, bb_ptr);
413
414 // Allow executor to make modifications after building the tree, but before execution starts
415 this->postBuild(tree);
416 return tree;
417 };
418}
419
420core::TreeBuilder::SharedPtr GenericTreeExecutorNode::createTreeBuilder()
421{
422 return core::TreeBuilder::make_shared(
424 this->tree_node_loader_ptr_);
425}
426
428{
430 if (executor_options_.blackboard_parameters_from_overrides_ || executor_options_.blackboard_parameters_dynamic_) {
431 const auto res = node_ptr_->list_parameters({BLACKBOARD_PARAM_PREFIX}, 2);
432 for (const std::string & name : res.names) {
433 node_ptr_->undeclare_parameter(name);
434 }
435 }
436 return true;
437 }
438 return false;
439}
440
441rcl_interfaces::msg::SetParametersResult GenericTreeExecutorNode::on_set_parameters_callback_(
442 const std::vector<rclcpp::Parameter> & parameters)
443{
444 const ExecutorParameters params = executor_param_listener_.get_params();
445
446 for (const rclcpp::Parameter & p : parameters) {
447 auto create_rejected = [&p](const std::string msg) {
448 rcl_interfaces::msg::SetParametersResult result;
449 result.successful = false;
450 result.reason = "Rejected to set " + p.get_name() + " = " + p.value_to_string() + " (Type: " + p.get_type_name() +
451 "): " + msg + ".";
452 return result;
453 };
454 const std::string param_name = p.get_name();
455
456 // Check if parameter is a scripting enum
457 if (const std::string enum_key = stripPrefixFromParameterName(SCRIPTING_ENUM_PARAM_PREFIX, param_name);
458 !enum_key.empty()) {
459 if (isBusy()) {
460 return create_rejected("Scripting enums cannot change while tree executor is running");
461 }
462 if (!executor_options_.scripting_enum_parameters_dynamic_ || !params.allow_dynamic_scripting_enums) {
463 return create_rejected(
464 "Cannot set scripting enum '" + enum_key + "', because the 'Dynamic scripting enums' option is disabled");
465 }
466 if (!updateScriptingEnumsWithParameterValues({{enum_key, p.get_parameter_value()}}, true)) {
467 return create_rejected(
468 "Type of scripting enum must be bool or int. Tried to set enum '" + enum_key + "' with value '" +
469 p.value_to_string() + "' (Type: " + p.get_type_name() + ")");
470 }
471 continue;
472 }
473
474 // Check if parameter is a blackboard parameter
475 if (const std::string entry_key = stripPrefixFromParameterName(BLACKBOARD_PARAM_PREFIX, param_name);
476 !entry_key.empty()) {
477 if (!executor_options_.blackboard_parameters_dynamic_ || !params.allow_dynamic_blackboard) {
478 return create_rejected(
479 "Cannot set blackboard entry '" + entry_key + "', because the 'Dynamic blackboard' option is disabled");
480 }
481 if (!updateGlobalBlackboardWithParameterValues({{entry_key, p.get_parameter_value()}}, true)) {
482 return create_rejected(
483 "Type of blackboard entries must not change. Tried to set entry '" + entry_key +
484 "' (Type: " + getGlobalBlackboardPtr()->getEntry(entry_key)->info.typeName() + ") with value '" +
485 p.value_to_string() + "' (Type: " + p.get_type_name() + ")");
486 }
487 continue;
488 }
489
490 // Check if parameter is known
491 if (!auto_apms_util::contains(TREE_EXECUTOR_EXPLICITLY_ALLOWED_PARAMETERS, param_name)) {
492 // Not a parameter managed by the executor. When strict removal is disabled, the executor is embedded in a node
493 // that intentionally declares its own additional parameters (see
494 // TreeExecutorNodeOptions::enableStrictUnkownParameterRemoval), so leave those foreign parameters untouched
495 // instead of rejecting them. Otherwise the executor owns the full parameter set and an unknown name is an error.
496 if (!executor_options_.strict_unkown_parameter_removal_) continue;
497 return create_rejected("Parameter is unknown");
498 }
499
500 // Check if the parameter is allowed to change during execution
501 if (isBusy() && !auto_apms_util::contains(TREE_EXECUTOR_EXPLICITLY_ALLOWED_PARAMETERS_WHILE_BUSY, param_name)) {
502 return create_rejected("Parameter is not allowed to change while tree executor is running");
503 }
504
505 // Check if build handler is allowed to change and valid
506 if (param_name == _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_PARAM_BUILD_HANDLER) {
507 if (!params.allow_other_build_handlers) {
508 return create_rejected(
509 "This executor operates with tree build handler '" + executor_param_listener_.get_params().build_handler +
510 "' and doesn't allow other build handlers to be loaded since the 'Allow other build handlers' option is "
511 "disabled");
512 }
513 const std::string class_name = p.as_string();
514 if (class_name != PARAM_VALUE_NO_BUILD_HANDLER && !build_handler_loader_ptr_->isClassAvailable(class_name)) {
515 return create_rejected(
516 "Cannot load build handler '" + class_name +
517 "' because no corresponding ament_index resource was found. Make sure that you spelled the build handler's "
518 "name correctly "
519 "and registered it by calling auto_apms_behavior_tree_register_build_handlers() in the CMakeLists.txt of "
520 "the "
521 "corresponding package");
522 }
523 }
524
525 // At this point, if the parameter hasn't been declared, we do not support it.
526 if (!node_ptr_->has_parameter(param_name)) {
527 return create_rejected("Parameter '" + param_name + "' is not supported");
528 }
529 }
530
531 rcl_interfaces::msg::SetParametersResult result;
532 result.successful = true;
533 return result;
534}
535
536void GenericTreeExecutorNode::parameter_event_callback_(const rcl_interfaces::msg::ParameterEvent & event)
537{
538 std::regex re(node_ptr_->get_fully_qualified_name());
539 if (std::regex_match(event.node, re)) {
540 for (const rclcpp::Parameter & p : rclcpp::ParameterEventHandler::get_parameters_from_event(event)) {
541 const std::string param_name = p.get_name();
542
543 if (const std::string enum_key = stripPrefixFromParameterName(SCRIPTING_ENUM_PARAM_PREFIX, param_name);
544 !enum_key.empty()) {
545 updateScriptingEnumsWithParameterValues({{enum_key, p.get_parameter_value()}});
546 }
547
548 if (const std::string entry_key = stripPrefixFromParameterName(BLACKBOARD_PARAM_PREFIX, param_name);
549 !entry_key.empty()) {
550 updateGlobalBlackboardWithParameterValues({{entry_key, p.get_parameter_value()}}, false);
551 }
552
553 if (param_name == _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_PARAM_BUILD_HANDLER) {
554 loadBuildHandler(p.as_string());
555 }
556 }
557 }
558}
559
560rclcpp_action::GoalResponse GenericTreeExecutorNode::handle_command_goal_(
561 const rclcpp_action::GoalUUID & /*uuid*/, std::shared_ptr<const CommandActionContext::Goal> goal_ptr)
562{
563 if (command_timer_ptr_ && !command_timer_ptr_->is_canceled()) {
564 RCLCPP_WARN(logger_, "Request for setting tree executor command rejected, because previous one is still busy.");
565 return rclcpp_action::GoalResponse::REJECT;
566 }
567
568 const auto execution_state = getExecutionState();
569 switch (goal_ptr->command) {
570 case CommandActionContext::Goal::COMMAND_RESUME:
571 if (execution_state == ExecutionState::PAUSED || execution_state == ExecutionState::HALTED) {
572 RCLCPP_INFO(logger_, "Tree with ID '%s' will RESUME.", getTreeName().c_str());
573 } else {
574 RCLCPP_WARN(
575 logger_, "Requested to RESUME with executor being in state %s. Rejecting request.",
576 toStr(execution_state).c_str());
577 return rclcpp_action::GoalResponse::REJECT;
578 }
579 break;
580 case CommandActionContext::Goal::COMMAND_PAUSE:
581 if (execution_state == ExecutionState::STARTING || execution_state == ExecutionState::RUNNING) {
582 RCLCPP_INFO(logger_, "Tree with ID '%s' will PAUSE", getTreeName().c_str());
583 } else {
584 RCLCPP_INFO(
585 logger_, "Requested to PAUSE with executor already being inactive (State: %s).",
586 toStr(execution_state).c_str());
587 }
588 break;
589 case CommandActionContext::Goal::COMMAND_HALT:
590 if (
591 execution_state == ExecutionState::STARTING || execution_state == ExecutionState::RUNNING ||
592 execution_state == ExecutionState::PAUSED) {
593 RCLCPP_INFO(logger_, "Tree with ID '%s' will HALT.", getTreeName().c_str());
594 } else {
595 RCLCPP_INFO(
596 logger_, "Requested to HALT with executor already being inactive (State: %s).",
597 toStr(execution_state).c_str());
598 }
599 break;
600 case CommandActionContext::Goal::COMMAND_TERMINATE:
601 if (isBusy()) {
602 RCLCPP_INFO(logger_, "Executor will TERMINATE tree '%s'.", getTreeName().c_str());
603 } else {
604 RCLCPP_INFO(
605 logger_, "Requested to TERMINATE with executor already being inactive (State: %s).",
606 toStr(execution_state).c_str());
607 }
608 break;
609 default:
610 RCLCPP_WARN(logger_, "Executor command %i is undefined. Rejecting request.", goal_ptr->command);
611 return rclcpp_action::GoalResponse::REJECT;
612 }
613 return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
614}
615
616rclcpp_action::CancelResponse GenericTreeExecutorNode::handle_command_cancel_(
617 std::shared_ptr<CommandActionContext::GoalHandle> /*goal_handle_ptr*/)
618{
619 return rclcpp_action::CancelResponse::ACCEPT;
620}
621
622void GenericTreeExecutorNode::handle_command_accept_(std::shared_ptr<CommandActionContext::GoalHandle> goal_handle_ptr)
623{
624 const auto command_request = goal_handle_ptr->get_goal()->command;
625 ExecutionState requested_state;
626 switch (command_request) {
627 case CommandActionContext::Goal::COMMAND_RESUME:
629 requested_state = ExecutionState::RUNNING;
630 break;
631 case CommandActionContext::Goal::COMMAND_PAUSE:
633 requested_state = ExecutionState::PAUSED;
634 break;
635 case CommandActionContext::Goal::COMMAND_HALT:
637 requested_state = ExecutionState::HALTED;
638 break;
639 case CommandActionContext::Goal::COMMAND_TERMINATE:
641 requested_state = ExecutionState::IDLE;
642 break;
643 default:
644 throw std::logic_error("command_request is unknown");
645 }
646
647 command_timer_ptr_ = node_ptr_->create_wall_timer(
648 std::chrono::duration<double>(executor_param_listener_.get_params().tick_rate),
649 [this, requested_state, goal_handle_ptr, action_result_ptr = std::make_shared<CommandActionContext::Result>()]() {
650 if (goal_handle_ptr->is_canceling()) {
651 goal_handle_ptr->canceled(action_result_ptr);
652 command_timer_ptr_->cancel();
653 return;
654 }
655
656 const auto current_state = getExecutionState();
657
658 if (requested_state != ExecutionState::IDLE && current_state == ExecutionState::IDLE) {
659 RCLCPP_ERROR(
660 logger_, "Failed to reach requested state %s due to cancellation of execution timer. Aborting.",
661 toStr(requested_state).c_str());
662 goal_handle_ptr->abort(action_result_ptr);
663 command_timer_ptr_->cancel();
664 return;
665 }
666
667 if (current_state != requested_state) return;
668
669 goal_handle_ptr->succeed(action_result_ptr);
670 command_timer_ptr_->cancel();
671 });
672}
673
674} // 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.
static std::string stripPrefixFromParameterName(const std::string &prefix, const std::string &param_name)
Get the name of a parameter without its prefix.
GenericTreeExecutorNode(rclcpp::Node::SharedPtr node_ptr, Options options)
Constructor using an existing ROS 2 node.
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