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