AutoAPMS
Resilient Robot Mission Management
Loading...
Searching...
No Matches
executor_node.cpp
1// Copyright 2024 Robin Müller
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include "auto_apms_behavior_tree/executor/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#include "rclcpp/utilities.hpp"
28
30{
31
32const std::vector<std::string> EXPLICITLY_ALLOWED_PARAMETERS{
33 _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_PARAM_ALLOW_OTHER_BUILD_HANDLERS,
34 _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_PARAM_ALLOW_DYNAMIC_BLACKBOARD,
35 _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_PARAM_ALLOW_DYNAMIC_SCRIPTING_ENUMS,
36 _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_PARAM_EXCLUDE_PACKAGES_NODE,
37 _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_PARAM_EXCLUDE_PACKAGES_BUILD_HANDLER,
38 _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_PARAM_BUILD_HANDLER,
39 _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_PARAM_TICK_RATE,
40 _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_PARAM_GROOT2_PORT,
41 _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_PARAM_STATE_CHANGE_LOGGER};
42
43const std::vector<std::string> EXPLICITLY_ALLOWED_PARAMETERS_WHILE_BUSY{
44 _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_PARAM_ALLOW_DYNAMIC_BLACKBOARD,
45 _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_PARAM_STATE_CHANGE_LOGGER};
46
47TreeExecutorNodeOptions::TreeExecutorNodeOptions(const rclcpp::NodeOptions & ros_node_options)
48: ros_node_options_(ros_node_options)
49{
50}
51
53{
54 scripting_enum_parameters_from_overrides_ = from_overrides;
55 scripting_enum_parameters_dynamic_ = dynamic;
56 return *this;
57}
58
60{
61 blackboard_parameters_from_overrides_ = from_overrides;
62 blackboard_parameters_dynamic_ = dynamic;
63 return *this;
64}
65
67{
68 custom_default_parameters_[_AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_PARAM_BUILD_HANDLER] = rclcpp::ParameterValue(name);
69 return *this;
70}
71
73{
74 rclcpp::NodeOptions opt(ros_node_options_);
75 opt.automatically_declare_parameters_from_overrides(
76 scripting_enum_parameters_from_overrides_ || blackboard_parameters_from_overrides_);
77 opt.allow_undeclared_parameters(scripting_enum_parameters_dynamic_ || blackboard_parameters_dynamic_);
78 return opt;
79}
80
81TreeExecutorNode::TreeExecutorNode(const std::string & name, TreeExecutorNodeOptions executor_options)
82: TreeExecutorBase(std::make_shared<rclcpp::Node>(name, executor_options.getROSNodeOptions())),
83 executor_options_(executor_options),
84 executor_param_listener_(node_ptr_),
85 start_action_context_(logger_)
86{
87 // Set custom parameter default values.
88 // NOTE: We cannot do this before the node is created, because we also need the global parameter overrides here, not
89 // just rclcpp::NodeOptions::parameter_overrides (This only contains parameter overrides explicitly provided to the
90 // options object. So generally speaking, this variable doesn't represent all parameter overrides).
91 std::vector<rclcpp::Parameter> new_default_parameters;
92 std::map<std::string, rclcpp::ParameterValue> effective_param_overrides =
93 node_ptr_->get_node_parameters_interface()->get_parameter_overrides();
94 for (const auto & [name, value] : executor_options_.custom_default_parameters_) {
95 // Only set custom default parameters if not present in overrides
96 if (effective_param_overrides.find(name) == effective_param_overrides.end()) {
97 new_default_parameters.push_back(rclcpp::Parameter(name, value));
98 }
99 }
100 if (!new_default_parameters.empty()) node_ptr_->set_parameters_atomically(new_default_parameters);
101
102 const ExecutorParameters initial_params = executor_param_listener_.get_params();
103
104 // Remove all parameters from overrides that are not supported
105 rcl_interfaces::msg::ListParametersResult res = node_ptr_->list_parameters({}, 0);
106 std::vector<std::string> unkown_param_names;
107 for (const std::string & param_name : res.names) {
108 if (!stripPrefixFromParameterName(SCRIPTING_ENUM_PARAM_PREFIX, param_name).empty()) continue;
109 if (!stripPrefixFromParameterName(BLACKBOARD_PARAM_PREFIX, param_name).empty()) continue;
110 if (auto_apms_util::contains(EXPLICITLY_ALLOWED_PARAMETERS, param_name)) continue;
111 try {
112 node_ptr_->undeclare_parameter(param_name);
113 } catch (const rclcpp::exceptions::ParameterImmutableException & e) {
114 // Allow all builtin read only parameters
115 continue;
116 } catch (const rclcpp::exceptions::InvalidParameterTypeException & e) {
117 // Allow all builtin statically typed parameters
118 continue;
119 }
120 unkown_param_names.push_back(param_name);
121 }
122 if (!unkown_param_names.empty()) {
123 RCLCPP_WARN(
124 logger_, "The following initial parameters are not supported and have been removed: [ %s ].",
125 auto_apms_util::join(unkown_param_names, ", ").c_str());
126 }
127
128 // Create behavior tree node loader
129 tree_node_loader_ptr_ = core::NodeRegistrationLoader::make_shared(
130 std::set<std::string>(initial_params.node_exclude_packages.begin(), initial_params.node_exclude_packages.end()));
131
132 // Create behavior tree build handler loader
133 build_handler_loader_ptr_ = TreeBuildHandlerLoader::make_unique(std::set<std::string>(
134 initial_params.build_handler_exclude_packages.begin(), initial_params.build_handler_exclude_packages.end()));
135
136 // Instantiate behavior tree build handler
137 if (
138 initial_params.build_handler != PARAM_VALUE_NO_BUILD_HANDLER &&
139 !build_handler_loader_ptr_->isClassAvailable(initial_params.build_handler)) {
140 throw exceptions::TreeExecutorError(
141 "Cannot load build handler '" + initial_params.build_handler +
142 "' because no corresponding ament_index resource was found. Make sure that you spelled the build handler's "
143 "name correctly "
144 "and registered it by calling auto_apms_behavior_tree_declare_build_handlers() in the CMakeLists.txt of the "
145 "corresponding package.");
146 }
147 loadBuildHandler(initial_params.build_handler);
148
149 // Collect scripting enum and blackboard parameters from initial parameters
150 const auto initial_scripting_enums = getParameterValuesWithPrefix(SCRIPTING_ENUM_PARAM_PREFIX);
151 if (!initial_scripting_enums.empty()) {
152 if (executor_options_.scripting_enum_parameters_from_overrides_) {
153 updateScriptingEnumsWithParameterValues(initial_scripting_enums);
154 } else {
155 RCLCPP_WARN(
156 logger_,
157 "Initial scripting enums have been provided, but the 'Scripting enums from overrides' option is disabled. "
158 "Ignoring.");
159 }
160 }
161 const auto initial_blackboard = getParameterValuesWithPrefix(BLACKBOARD_PARAM_PREFIX);
162 if (!initial_blackboard.empty()) {
163 if (executor_options_.blackboard_parameters_from_overrides_) {
164 updateGlobalBlackboardWithParameterValues(initial_blackboard);
165 } else {
166 RCLCPP_WARN(
167 logger_,
168 "Initial blackboard entries have been provided, but the 'Blackboard from overrides' option is disabled. "
169 "Ignoring.");
170 }
171 }
172
173 using namespace std::placeholders;
174 start_action_ptr_ = rclcpp_action::create_server<StartActionContext::Type>(
175 node_ptr_, std::string(node_ptr_->get_name()) + _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_START_ACTION_NAME_SUFFIX,
176 std::bind(&TreeExecutorNode::handle_start_goal_, this, _1, _2),
177 std::bind(&TreeExecutorNode::handle_start_cancel_, this, _1),
178 std::bind(&TreeExecutorNode::handle_start_accept_, this, _1));
179
180 command_action_ptr_ = rclcpp_action::create_server<CommandActionContext::Type>(
181 node_ptr_, std::string(node_ptr_->get_name()) + _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_COMMAND_ACTION_NAME_SUFFIX,
182 std::bind(&TreeExecutorNode::handle_command_goal_, this, _1, _2),
183 std::bind(&TreeExecutorNode::handle_command_cancel_, this, _1),
184 std::bind(&TreeExecutorNode::handle_command_accept_, this, _1));
185
186 // Adding the local on_set_parameters_callback after the parameter listeners from generate_parameters_library
187 // are created makes sure that this callback will be evaluated before the listener callbacks.
188 // This is desired to keep the internal parameter struct in sync, because the callbacks of the listeners implicitly
189 // set them if the change is accepted. Otherwise, they would be set even if the local callback rejects the change.
190 // We DO NOT set any variables in this callback, but only check if the request to change certain parameters is valid.
191 // The actual change is performed in the callback registered with rclcpp::ParameterEventListener
192 on_set_parameters_callback_handle_ptr_ =
193 node_ptr_->add_on_set_parameters_callback([this](const std::vector<rclcpp::Parameter> & parameters) {
194 return this->on_set_parameters_callback_(parameters);
195 });
196
197 // Add an event handler that applies the actual parameter change
198 parameter_event_handler_ptr_ = std::make_shared<rclcpp::ParameterEventHandler>(node_ptr_);
199 parameter_event_callback_handle_ptr_ = parameter_event_handler_ptr_->add_parameter_event_callback(
200 [this](const rcl_interfaces::msg::ParameterEvent & event) { this->parameter_event_callback_(event); });
201
202 // Make sure ROS arguments are removed. When using rclcpp_components, this is typically not the case.
203 std::vector<std::string> args_with_ros_arguments = node_ptr_->get_node_options().arguments();
204 int argc = args_with_ros_arguments.size();
205 char ** argv = new char *[argc + 1]; // +1 for the null terminator
206 for (int i = 0; i < argc; ++i) {
207 argv[i] = const_cast<char *>(args_with_ros_arguments[i].c_str());
208 }
209 argv[argc] = nullptr; // Null-terminate the array as required for argv[]
210
211 // Evaluate possible cli argument dictating to start executing with a specific build request immediately.
212 // Note: First argument is always path of executable.
213 if (const std::vector<std::string> args = rclcpp::remove_ros_arguments(argc, argv); args.size() > 1) {
214 // Log relevant arguments. First argument is executable name (argv[0]) and won't be considered.
215 std::vector<std::string> relevant_args{args.begin() + 1, args.end()};
216 RCLCPP_DEBUG(
217 logger_, "Additional cli arguments in rclcpp::NodeOptions: [ %s ]",
218 auto_apms_util::join(relevant_args, ", ").c_str());
219
220 // Start tree execution with the build handler request being the first relevant argument
221 startExecution(makeTreeConstructor(relevant_args[0], ""), initial_params.tick_rate, initial_params.groot2_port);
222 }
223}
224
225TreeExecutorNode::TreeExecutorNode(rclcpp::NodeOptions options)
226: TreeExecutorNode(_AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_DEFAULT_NAME, TreeExecutorNodeOptions(options))
227{
228}
229
230void TreeExecutorNode::setUpBuilder(core::TreeBuilder & /*builder*/, const core::NodeManifest & /*node_manifest*/) {}
231
232std::shared_future<TreeExecutorNode::ExecutionResult> TreeExecutorNode::startExecution(
233 const std::string & tree_build_request)
234{
235 const ExecutorParameters params = executor_param_listener_.get_params();
236 return startExecution(makeTreeConstructor(tree_build_request, ""), params.tick_rate, params.groot2_port);
237}
238
239std::map<std::string, rclcpp::ParameterValue> TreeExecutorNode::getParameterValuesWithPrefix(const std::string & prefix)
240{
241 const auto res = node_ptr_->list_parameters({prefix}, 2);
242 std::map<std::string, rclcpp::ParameterValue> value_map;
243 for (const std::string & name_with_prefix : res.names) {
244 if (const std::string suffix = stripPrefixFromParameterName(prefix, name_with_prefix); !suffix.empty()) {
245 value_map[suffix] = node_ptr_->get_parameter(name_with_prefix).get_parameter_value();
246 }
247 }
248 return value_map;
249}
250
251std::string TreeExecutorNode::stripPrefixFromParameterName(const std::string & prefix, const std::string & param_name)
252{
253 const std::regex reg("^" + prefix + "\\.(\\S+)");
254 if (std::smatch match; std::regex_match(param_name, match, reg)) return match[1].str();
255 return "";
256}
257
259 const std::map<std::string, rclcpp::ParameterValue> & value_map, bool simulate)
260{
261 std::map<std::string, std::string> set_successfully_map;
262 for (const auto & [enum_key, pval] : value_map) {
263 try {
264 switch (pval.get_type()) {
265 case rclcpp::ParameterType::PARAMETER_BOOL:
266 if (simulate) continue;
267 scripting_enums_[enum_key] = static_cast<int>(pval.get<bool>());
268 break;
269 case rclcpp::ParameterType::PARAMETER_INTEGER:
270 if (simulate) continue;
271 scripting_enums_[enum_key] = static_cast<int>(pval.get<int64_t>());
272 break;
273 default:
274 if (simulate) return false;
275 throw exceptions::ParameterConversionError("Parameter to scripting enum conversion is not allowed.");
276 }
277 set_successfully_map[enum_key] = rclcpp::to_string(pval);
278 } catch (const std::exception & e) {
279 RCLCPP_ERROR(
280 logger_, "Error setting scripting enum from parameter %s=%s (Type: %s): %s", enum_key.c_str(),
281 rclcpp::to_string(pval).c_str(), rclcpp::to_string(pval.get_type()).c_str(), e.what());
282 return false;
283 }
284 }
285 if (!set_successfully_map.empty()) {
286 RCLCPP_DEBUG(
287 logger_, "Updated scripting enums from parameters: { %s }",
288 auto_apms_util::printMap(set_successfully_map).c_str());
289 }
290 return true;
291}
292
294 const std::map<std::string, rclcpp::ParameterValue> & value_map, bool simulate)
295{
296 TreeBlackboard & bb = *getGlobalBlackboardPtr();
297 std::map<std::string, std::string> set_successfully_map;
298 for (const auto & [entry_key, pval] : value_map) {
299 try {
300 if (const BT::Expected<BT::Any> expected = createAnyFromParameterValue(pval)) {
301 BT::Any any(expected.value());
302 if (simulate) {
303 // Verify that any has the same type as the entry (if it exists)
304 if (const BT::TypeInfo * entry_info = bb.entryInfo(entry_key)) {
305 if (entry_info->isStronglyTyped() && entry_info->type() != any.type()) return false;
306 }
307 continue;
308 } else {
309 bb.set(entry_key, any);
310 }
311 } else {
312 throw exceptions::ParameterConversionError(expected.error());
313 }
314 translated_global_blackboard_entries_[entry_key] = pval;
315 set_successfully_map[entry_key] = rclcpp::to_string(pval);
316 } catch (const std::exception & e) {
317 RCLCPP_ERROR(
318 logger_, "Error updating blackboard from parameter %s=%s (Type: %s): %s", entry_key.c_str(),
319 rclcpp::to_string(pval).c_str(), rclcpp::to_string(pval.get_type()).c_str(), e.what());
320 return false;
321 }
322 }
323 if (!set_successfully_map.empty()) {
324 RCLCPP_DEBUG(
325 logger_, "Updated blackboard from parameters: { %s }", auto_apms_util::printMap(set_successfully_map).c_str());
326 }
327 return true;
328}
329
330void TreeExecutorNode::loadBuildHandler(const std::string & name)
331{
332 if (build_handler_ptr_ && !executor_param_listener_.get_params().allow_other_build_handlers) {
333 throw std::logic_error(
334 "Executor option 'Allow other build handlers' is disabled, but loadBuildHandler() was called again after "
335 "instantiating '" +
336 current_build_handler_name_ + "'.");
337 }
338 if (current_build_handler_name_ == name) return;
339 if (name == PARAM_VALUE_NO_BUILD_HANDLER) {
340 build_handler_ptr_.reset();
341 } else {
342 try {
343 build_handler_ptr_ =
344 build_handler_loader_ptr_->createUniqueInstance(name)->makeUnique(node_ptr_, tree_node_loader_ptr_);
345 } catch (const pluginlib::CreateClassException & e) {
346 throw exceptions::TreeExecutorError(
347 "An error occurred when trying to create an instance of tree build handler class '" + name +
348 "'. This might be because you forgot to call the AUTO_APMS_BEHAVIOR_TREE_DECLARE_BUILD_HANDLER macro "
349 "in the source file: " +
350 e.what());
351 } catch (const std::exception & e) {
352 throw exceptions::TreeExecutorError(
353 "An error occurred when trying to create an instance of tree build handler class '" + name + "': " + e.what());
354 }
355 }
356 current_build_handler_name_ = name;
357}
358
360 const std::string & build_handler_request, const std::string & root_tree_name,
361 const core::NodeManifest & node_manifest, const core::NodeManifest & node_overrides)
362{
363 // Request the tree identity
364 if (
365 build_handler_ptr_ && !build_handler_ptr_->setBuildRequest(build_handler_request, node_manifest, root_tree_name)) {
366 throw exceptions::TreeBuildError(
367 "Build request '" + build_handler_request + "' was denied by '" +
368 executor_param_listener_.get_params().build_handler + "' (setBuildRequest() returned false).");
369 }
370
371 // By passing the the local variables to the callback's captures by value they live on and can be used for creating
372 // the tree later. Otherwise a segmentation fault might occur since memory allocated for the arguments might be
373 // released at the time the method returns.
374 return [this, root_tree_name, node_manifest, node_overrides](TreeBlackboardSharedPtr bb_ptr) {
375 // Currently, BehaviorTree.CPP requires the memory allocated by the factory to persist even after the tree has been
376 // created, so we make the builder a unique pointer that is only reset when a new tree is to be created.
377 // See https://github.com/BehaviorTree/BehaviorTree.CPP/issues/890
378 builder_ptr_.reset(new TreeBuilder(
380
381 // Allow executor to set up the builder independently from the build handler
382 setUpBuilder(*builder_ptr_, node_manifest);
383
384 // Make scripting enums available to tree instance
385 for (const auto & [enum_key, val] : scripting_enums_) builder_ptr_->setScriptingEnum(enum_key, val);
386
387 // If a build handler is specified, let it configure the builder and determine which tree is to be instantiated
388 std::string instantiate_name = root_tree_name;
389 if (build_handler_ptr_) {
390 instantiate_name = build_handler_ptr_->buildTree(*builder_ptr_, *bb_ptr).getName();
391 }
392
393 // Allow for overriding selected node instances
394 builder_ptr_->registerNodes(node_overrides, true);
395
396 // Finally, instantiate the tree
397 return builder_ptr_->instantiate(instantiate_name, bb_ptr);
398 };
399}
400
401rcl_interfaces::msg::SetParametersResult TreeExecutorNode::on_set_parameters_callback_(
402 const std::vector<rclcpp::Parameter> & parameters)
403{
404 const ExecutorParameters params = executor_param_listener_.get_params();
405
406 // Iterate through parameters and individually decide wether to reject the change
407 for (const rclcpp::Parameter & p : parameters) {
408 auto create_rejected = [&p](const std::string msg) {
409 rcl_interfaces::msg::SetParametersResult result;
410 result.successful = false;
411 result.reason = "Rejected to set " + p.get_name() + " = " + p.value_to_string() + " (Type: " + p.get_type_name() +
412 "): " + msg + ".";
413 return result;
414 };
415 const std::string param_name = p.get_name();
416
417 // Check if parameter is a scripting enum
418 if (const std::string enum_key = stripPrefixFromParameterName(SCRIPTING_ENUM_PARAM_PREFIX, param_name);
419 !enum_key.empty()) {
420 if (isBusy()) {
421 return create_rejected("Scripting enums cannot change while tree executor is running");
422 }
423 if (!executor_options_.scripting_enum_parameters_dynamic_ || !params.allow_dynamic_scripting_enums) {
424 return create_rejected(
425 "Cannot set scripting enum '" + enum_key + "', because the 'Dynamic scripting enums' option is disabled");
426 }
427 // Validate type of scripting enum parameters
428 if (!updateScriptingEnumsWithParameterValues({{enum_key, p.get_parameter_value()}}, true)) {
429 return create_rejected(
430 "Type of scripting enum must be bool or int. Tried to set enum '" + enum_key + "' with value '" +
431 p.value_to_string() + "' (Type: " + p.get_type_name() + ")");
432 };
433 // If scripting enum is allowed to change, continue with next parameter.
434 continue;
435 }
436
437 // Check if parameter is a blackboard parameter
438 if (const std::string entry_key = stripPrefixFromParameterName(BLACKBOARD_PARAM_PREFIX, param_name);
439 !entry_key.empty()) {
440 if (!executor_options_.blackboard_parameters_dynamic_ || !params.allow_dynamic_blackboard) {
441 return create_rejected(
442 "Cannot set blackboard entry '" + entry_key + "', because the 'Dynamic blackboard' option is disabled");
443 }
444 // Validate type of blackboard parameters won't change
445 if (!updateGlobalBlackboardWithParameterValues({{entry_key, p.get_parameter_value()}}, true)) {
446 return create_rejected(
447 "Type of blackboard entries must not change. Tried to set entry '" + entry_key +
448 "' (Type: " + getGlobalBlackboardPtr()->getEntry(entry_key)->info.typeName() + ") with value '" +
449 p.value_to_string() + "' (Type: " + p.get_type_name() + ")");
450 };
451 // If blackboard entry is allowed to change, continue with next parameter.
452 continue;
453 }
454
455 // Check if parameter is known
456 if (!auto_apms_util::contains(EXPLICITLY_ALLOWED_PARAMETERS, param_name)) {
457 return create_rejected("Parameter is unkown");
458 }
459
460 // Check if the parameter is allowed to change during execution
461 if (isBusy() && !auto_apms_util::contains(EXPLICITLY_ALLOWED_PARAMETERS_WHILE_BUSY, param_name)) {
462 return create_rejected("Parameter is not allowed to change while tree executor is running");
463 }
464
465 // Check if build handler is allowed to change and valid
466 if (param_name == _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_PARAM_BUILD_HANDLER) {
467 if (!params.allow_other_build_handlers) {
468 return create_rejected(
469 "This executor operates with tree build handler '" + executor_param_listener_.get_params().build_handler +
470 "' and doesn't allow other build handlers to be loaded since the 'Allow other build handlers' option is "
471 "disabled");
472 }
473 const std::string class_name = p.as_string();
474 if (class_name != PARAM_VALUE_NO_BUILD_HANDLER && !build_handler_loader_ptr_->isClassAvailable(class_name)) {
475 return create_rejected(
476 "Cannot load build handler '" + class_name +
477 "' because no corresponding ament_index resource was found. Make sure that you spelled the build handler's "
478 "name correctly "
479 "and registered it by calling auto_apms_behavior_tree_declare_build_handlers() in the CMakeLists.txt of the "
480 "corresponding package");
481 }
482 }
483
484 // At this point, if the parameter hasn't been declared, we do not support it.
485 if (!node_ptr_->has_parameter(param_name)) {
486 return create_rejected("Parameter '" + param_name + "' is not supported");
487 }
488 }
489
490 // If not returned yet, accept to set the parameter
491 rcl_interfaces::msg::SetParametersResult result;
492 result.successful = true;
493 return result;
494}
495
496void TreeExecutorNode::parameter_event_callback_(const rcl_interfaces::msg::ParameterEvent & event)
497{
498 // Look for any updates to parameters of this node
499 std::regex re(node_ptr_->get_fully_qualified_name());
500 if (std::regex_match(event.node, re)) {
501 // Enumerate all changes that came in on this event
502 for (const rclcpp::Parameter & p : rclcpp::ParameterEventHandler::get_parameters_from_event(event)) {
503 const std::string param_name = p.get_name();
504
505 // Change scripting enums
506 if (const std::string enum_key = stripPrefixFromParameterName(SCRIPTING_ENUM_PARAM_PREFIX, param_name);
507 !enum_key.empty()) {
508 updateScriptingEnumsWithParameterValues({{enum_key, p.get_parameter_value()}});
509 }
510
511 // Change blackboard parameters
512 if (const std::string entry_key = stripPrefixFromParameterName(BLACKBOARD_PARAM_PREFIX, param_name);
513 !entry_key.empty()) {
514 updateGlobalBlackboardWithParameterValues({{entry_key, p.get_parameter_value()}}, false);
515 }
516
517 // Change tree build handler instance
518 if (param_name == _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_PARAM_BUILD_HANDLER) {
519 loadBuildHandler(p.as_string());
520 }
521 }
522 }
523}
524
525rclcpp_action::GoalResponse TreeExecutorNode::handle_start_goal_(
526 const rclcpp_action::GoalUUID & uuid, std::shared_ptr<const StartActionContext::Goal> goal_ptr)
527{
528 // Reject if a tree is already executing
529 if (isBusy()) {
530 RCLCPP_WARN(
531 logger_, "Goal %s was REJECTED: Tree '%s' is currently executing.", rclcpp_action::to_string(uuid).c_str(),
532 getTreeName().c_str());
533 return rclcpp_action::GoalResponse::REJECT;
534 }
535
536 if (!goal_ptr->build_handler.empty()) {
537 if (executor_param_listener_.get_params().allow_other_build_handlers) {
538 try {
539 loadBuildHandler(goal_ptr->build_handler);
540 } catch (const std::exception & e) {
541 RCLCPP_WARN(
542 logger_, "Goal %s was REJECTED: Loading tree build handler '%s' failed: %s",
543 rclcpp_action::to_string(uuid).c_str(), goal_ptr->build_handler.c_str(), e.what());
544 return rclcpp_action::GoalResponse::REJECT;
545 }
546 } else if (goal_ptr->build_handler != current_build_handler_name_) {
547 RCLCPP_WARN(
548 logger_,
549 "Goal %s was REJECTED: Current tree build handler '%s' must not change since the 'Allow other build handlers' "
550 "option is disabled.",
551 rclcpp_action::to_string(uuid).c_str(), current_build_handler_name_.c_str());
552 return rclcpp_action::GoalResponse::REJECT;
553 }
554 }
555
556 core::NodeManifest node_manifest;
557 try {
558 node_manifest = core::NodeManifest::decode(goal_ptr->node_manifest);
559 } catch (const std::exception & e) {
560 RCLCPP_WARN(
561 logger_, "Goal %s was REJECTED: Parsing the initial node manifest failed: %s",
562 rclcpp_action::to_string(uuid).c_str(), e.what());
563 return rclcpp_action::GoalResponse::REJECT;
564 }
565
566 core::NodeManifest node_overrides;
567 try {
568 node_overrides = core::NodeManifest::decode(goal_ptr->node_overrides);
569 } catch (const std::exception & e) {
570 RCLCPP_WARN(
571 logger_, "Goal %s was REJECTED: Parsing the node override manifest failed: %s",
572 rclcpp_action::to_string(uuid).c_str(), e.what());
573 return rclcpp_action::GoalResponse::REJECT;
574 }
575
576 try {
577 tree_constructor_ =
578 makeTreeConstructor(goal_ptr->build_request, goal_ptr->root_tree, node_manifest, node_overrides);
579 } catch (const std::exception & e) {
580 RCLCPP_WARN(logger_, "Goal %s was REJECTED: %s", rclcpp_action::to_string(uuid).c_str(), e.what());
581 return rclcpp_action::GoalResponse::REJECT;
582 }
583 return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
584}
585
586rclcpp_action::CancelResponse TreeExecutorNode::handle_start_cancel_(
587 std::shared_ptr<StartActionContext::GoalHandle> /*goal_handle_ptr*/)
588{
590 return rclcpp_action::CancelResponse::ACCEPT;
591}
592
593void TreeExecutorNode::handle_start_accept_(std::shared_ptr<StartActionContext::GoalHandle> goal_handle_ptr)
594{
595 // Clear blackboard parameters if desired
596 if (goal_handle_ptr->get_goal()->clear_blackboard) {
597 const auto res = node_ptr_->list_parameters({BLACKBOARD_PARAM_PREFIX}, 2);
598 for (const std::string & name : res.names) {
599 node_ptr_->undeclare_parameter(name);
600 };
602 }
603
604 const ExecutorParameters params = executor_param_listener_.get_params();
605 try {
606 startExecution(tree_constructor_, params.tick_rate, params.groot2_port);
607 } catch (const std::exception & e) {
608 auto result_ptr = std::make_shared<StartActionContext::Result>();
609 result_ptr->message = "An error occurred trying to start execution: " + std::string(e.what());
610 result_ptr->tree_result = StartActionContext::Result::TREE_RESULT_NOT_SET;
611 goal_handle_ptr->abort(result_ptr);
612 RCLCPP_ERROR_STREAM(logger_, result_ptr->message);
613 return;
614 }
615 const std::string started_tree_name = getTreeName();
616
617 // If attach is true, the goal's life time is synchronized with the execution. Otherwise we succeed immediately and
618 // leave the executor running (Detached mode).
619 if (goal_handle_ptr->get_goal()->attach) {
620 start_action_context_.setUp(goal_handle_ptr);
621 RCLCPP_INFO(logger_, "Successfully started execution of tree '%s' (Mode: Attached).", started_tree_name.c_str());
622 } else {
623 auto result_ptr = std::make_shared<StartActionContext::Result>();
624 result_ptr->message = "Successfully started execution of tree '" + started_tree_name + "' (Mode: Detached).";
625 result_ptr->tree_result = StartActionContext::Result::TREE_RESULT_NOT_SET;
626 result_ptr->terminated_tree_identity = started_tree_name;
627 goal_handle_ptr->succeed(result_ptr);
628 RCLCPP_INFO_STREAM(logger_, result_ptr->message);
629 }
630}
631
632rclcpp_action::GoalResponse TreeExecutorNode::handle_command_goal_(
633 const rclcpp_action::GoalUUID & /*uuid*/, std::shared_ptr<const CommandActionContext::Goal> goal_ptr)
634{
635 if (command_timer_ptr_ && !command_timer_ptr_->is_canceled()) {
636 RCLCPP_WARN(logger_, "Request for setting tree executor command rejected, because previous one is still busy.");
637 return rclcpp_action::GoalResponse::REJECT;
638 }
639
640 if (isBusy() && start_action_context_.isValid() && start_action_context_.getGoalHandlePtr()->is_canceling()) {
641 RCLCPP_WARN(logger_, "Request for setting tree executor command rejected, because tree executor is canceling.");
642 return rclcpp_action::GoalResponse::REJECT;
643 }
644
645 const auto execution_state = getExecutionState();
646 switch (goal_ptr->command) {
647 case CommandActionContext::Goal::COMMAND_RESUME:
648 if (execution_state == ExecutionState::PAUSED || execution_state == ExecutionState::HALTED) {
649 RCLCPP_INFO(logger_, "Tree with ID '%s' will RESUME.", getTreeName().c_str());
650 } else {
651 RCLCPP_WARN(
652 logger_, "Requested to RESUME with executor being in state %s. Rejecting request.",
653 toStr(execution_state).c_str());
654 return rclcpp_action::GoalResponse::REJECT;
655 }
656 break;
657 case CommandActionContext::Goal::COMMAND_PAUSE:
658 if (execution_state == ExecutionState::STARTING || execution_state == ExecutionState::RUNNING) {
659 RCLCPP_INFO(logger_, "Tree with ID '%s' will PAUSE", getTreeName().c_str());
660 } else {
661 RCLCPP_INFO(
662 logger_, "Requested to PAUSE with executor already being inactive (State: %s).",
663 toStr(execution_state).c_str());
664 }
665 break;
666 case CommandActionContext::Goal::COMMAND_HALT:
667 if (
668 execution_state == ExecutionState::STARTING || execution_state == ExecutionState::RUNNING ||
669 execution_state == ExecutionState::PAUSED) {
670 RCLCPP_INFO(logger_, "Tree with ID '%s' will HALT.", getTreeName().c_str());
671 } else {
672 RCLCPP_INFO(
673 logger_, "Requested to HALT with executor already being inactive (State: %s).",
674 toStr(execution_state).c_str());
675 }
676 break;
677 case CommandActionContext::Goal::COMMAND_TERMINATE:
678 if (isBusy()) {
679 RCLCPP_INFO(logger_, "Executor will TERMINATE tree '%s'.", getTreeName().c_str());
680 } else {
681 RCLCPP_INFO(
682 logger_, "Requested to TERMINATE with executor already being inactive (State: %s).",
683 toStr(execution_state).c_str());
684 }
685 break;
686 default:
687 RCLCPP_WARN(logger_, "Executor command %i is undefined. Rejecting request.", goal_ptr->command);
688 return rclcpp_action::GoalResponse::REJECT;
689 }
690 return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
691}
692
693rclcpp_action::CancelResponse TreeExecutorNode::handle_command_cancel_(
694 std::shared_ptr<CommandActionContext::GoalHandle> /*goal_handle_ptr*/)
695{
696 return rclcpp_action::CancelResponse::ACCEPT;
697}
698
699void TreeExecutorNode::handle_command_accept_(std::shared_ptr<CommandActionContext::GoalHandle> goal_handle_ptr)
700{
701 const auto command_request = goal_handle_ptr->get_goal()->command;
702 ExecutionState requested_state;
703 switch (command_request) {
704 case CommandActionContext::Goal::COMMAND_RESUME:
706 requested_state = ExecutionState::RUNNING;
707 break;
708 case CommandActionContext::Goal::COMMAND_PAUSE:
710 requested_state = ExecutionState::PAUSED;
711 break;
712 case CommandActionContext::Goal::COMMAND_HALT:
714 requested_state = ExecutionState::HALTED;
715 break;
716 case CommandActionContext::Goal::COMMAND_TERMINATE:
718 requested_state = ExecutionState::IDLE;
719 break;
720 default:
721 throw std::logic_error("command_request is unkown");
722 }
723
724 command_timer_ptr_ = node_ptr_->create_wall_timer(
725 std::chrono::duration<double>(executor_param_listener_.get_params().tick_rate),
726 [this, requested_state, goal_handle_ptr, action_result_ptr = std::make_shared<CommandActionContext::Result>()]() {
727 // Check if canceling
728 if (goal_handle_ptr->is_canceling()) {
729 // Will abandon any progress
730 goal_handle_ptr->canceled(action_result_ptr);
731 command_timer_ptr_->cancel();
732 return;
733 }
734
735 const auto current_state = getExecutionState();
736
737 // If the execution state has become IDLE in the mean time, request failed if termination was not desired
738 if (requested_state != ExecutionState::IDLE && current_state == ExecutionState::IDLE) {
739 RCLCPP_ERROR(
740 logger_, "Failed to reach requested state %s due to cancellation of execution timer. Aborting.",
741 toStr(requested_state).c_str());
742 goal_handle_ptr->abort(action_result_ptr);
743 command_timer_ptr_->cancel();
744 return;
745 }
746
747 // Wait for the requested state to be reached
748 if (current_state != requested_state) return;
749
750 goal_handle_ptr->succeed(action_result_ptr);
751 command_timer_ptr_->cancel();
752 });
753}
754
756{
757 const ExecutorParameters params = executor_param_listener_.get_params();
758
759 // Set state change logging flag
760 getStateObserver().setLogging(params.state_change_logger);
761 return true;
762}
763
765{
766 const ExecutorParameters params = executor_param_listener_.get_params();
767
771
772 if (executor_options_.blackboard_parameters_dynamic_ && params.allow_dynamic_blackboard) {
773 TreeBlackboardSharedPtr bb_ptr = getGlobalBlackboardPtr();
774 std::vector<rclcpp::Parameter> new_parameters;
775 for (const BT::StringView & str : bb_ptr->getKeys()) {
776 const std::string key = std::string(str);
777 const BT::TypeInfo * type_info = bb_ptr->entryInfo(key);
778 const BT::Any * any = bb_ptr->getAnyLocked(key).get();
779
780 // BehaviorTree.CPP does some type validation logic for all data ports when instantiating the tree. It parses
781 // the XML file, finds the ports of all nodes and initializes blackboard entries wherever used with the
782 // corresponding type. This is done to to detect type mismatches at construction time. This means, that the
783 // blackboard will be initialized with empty instances of BT::Any by the time the tree is created. Therefore, we
784 // must additionally check whether the blackboard entry is empty or not using BT::Any::empty().
785 if (any->empty()) continue;
786
787 // If the entry has actually been set with any value during runtime, we update this node's parameters
788
789 if (translated_global_blackboard_entries_.find(key) == translated_global_blackboard_entries_.end()) {
790 // The key is new, so we must try to infer the parameter's type from BT::Any
791 const BT::Expected<rclcpp::ParameterValue> expected =
792 createParameterValueFromAny(*any, rclcpp::PARAMETER_NOT_SET);
793 if (expected) {
794 new_parameters.push_back(rclcpp::Parameter(BLACKBOARD_PARAM_PREFIX + "." + key, expected.value()));
795 translated_global_blackboard_entries_[key] = expected.value();
796 } else {
797 RCLCPP_WARN(
798 logger_, "Failed to translate new blackboard entry '%s' (Type: %s) to parameters: %s", key.c_str(),
799 type_info->typeName().c_str(), expected.error().c_str());
800 }
801 } else {
802 // The key is not new, so we can look up the parameter's type and update its value (if it changed)
803 const BT::Expected<rclcpp::ParameterValue> expected =
804 createParameterValueFromAny(*any, translated_global_blackboard_entries_[key].get_type());
805 if (expected) {
806 if (expected.value() != translated_global_blackboard_entries_[key]) {
807 new_parameters.push_back(rclcpp::Parameter(BLACKBOARD_PARAM_PREFIX + "." + key, expected.value()));
808 }
809 } else {
810 RCLCPP_WARN(
811 logger_, "Failed to translate blackboard entry '%s' (Type: %s) to parameters: %s", key.c_str(),
812 type_info->typeName().c_str(), expected.error().c_str());
813 }
814 }
815 }
816 const rcl_interfaces::msg::SetParametersResult result = node_ptr_->set_parameters_atomically(new_parameters);
817 if (!result.successful) {
818 throw exceptions::TreeExecutorError(
819 "Unexpectedly failed to set parameters inferred from global blackboard. Reason: " + result.reason);
820 }
821 }
822
826
827 // Only send feedback if started in attached mode
828 if (start_action_context_.isValid()) {
829 TreeStateObserver & state_observer = getStateObserver();
830 auto feedback_ptr = start_action_context_.getFeedbackPtr(); // feedback from previous tick persists
831 feedback_ptr->execution_state_str = toStr(getExecutionState());
832 feedback_ptr->running_tree_identity = getTreeName();
833 auto running_action_history = state_observer.getRunningActionHistory();
834 if (!running_action_history.empty()) {
835 // If there are multiple nodes running (ParallelNode), join the IDs to a single string
836 feedback_ptr->running_action_name = auto_apms_util::join(running_action_history, " + ");
837 feedback_ptr->running_action_timestamp =
838 std::chrono::duration<double>{std::chrono::high_resolution_clock::now().time_since_epoch()}.count();
839
840 // Reset the history cache
841 state_observer.flush();
842 }
843 start_action_context_.publishFeedback();
844 }
845
846 return true;
847}
848
850{
851 if (!start_action_context_.isValid()) // Do nothing if started in detached mode
852 return;
853
854 auto result_ptr = start_action_context_.getResultPtr();
855 result_ptr->terminated_tree_identity = getTreeName();
856 switch (result) {
858 result_ptr->tree_result = StartActionContext::Result::TREE_RESULT_SUCCESS;
859 result_ptr->message = "Tree execution finished with status SUCCESS";
860 start_action_context_.succeed();
861 break;
863 result_ptr->tree_result = StartActionContext::Result::TREE_RESULT_FAILURE;
864 result_ptr->message = "Tree execution finished with status FAILURE";
865 start_action_context_.abort();
866 break;
868 result_ptr->tree_result = StartActionContext::Result::TREE_RESULT_NOT_SET;
869 if (start_action_context_.getGoalHandlePtr()->is_canceling()) {
870 result_ptr->message = "Tree execution canceled successfully";
871 start_action_context_.cancel();
872 } else {
873 result_ptr->message = "Tree execution terminated prematurely";
874 start_action_context_.abort();
875 }
876 break;
878 result_ptr->tree_result = StartActionContext::Result::TREE_RESULT_NOT_SET;
879 result_ptr->message = "An unexpected error occurred during tree execution";
880 start_action_context_.abort();
881 break;
882 default:
883 result_ptr->tree_result = StartActionContext::Result::TREE_RESULT_NOT_SET;
884 result_ptr->message = "Execution result unkown";
885 start_action_context_.abort();
886 break;
887 }
888
889 // Reset action context
890 start_action_context_.invalidate();
891}
892
893} // namespace auto_apms_behavior_tree
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.
void 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.
ExecutionResult
Enum representing possible behavior tree execution results.
@ TREE_SUCCEEDED
Tree completed with BT::NodeStatus::SUCCESS.
@ TERMINATED_PREMATURELY
Execution terminated before the tree was able to propagate the tick to all its nodes.
@ TREE_FAILED
Tree completed with BT::NodeStatus::FAILURE.
const rclcpp::Logger logger_
Logger associated with the parent ROS 2 node.
Configuration options for TreeExecutorNode.
TreeExecutorNodeOptions & enableScriptingEnumParameters(bool from_overrides, bool dynamic)
Configure whether the executor node accepts scripting enum parameters.
rclcpp::NodeOptions getROSNodeOptions() const
Get the ROS 2 node options that comply with the given options.
TreeExecutorNodeOptions(const rclcpp::NodeOptions &ros_node_options)
Constructor.
TreeExecutorNodeOptions & enableGlobalBlackboardParameters(bool from_overrides, bool dynamic)
Configure whether the executor node accepts global blackboard parameters.
TreeExecutorNodeOptions & setDefaultBuildHandler(const std::string &name)
Specify a default behavior tree build handler that will be used initially.
bool updateGlobalBlackboardWithParameterValues(const std::map< std::string, rclcpp::ParameterValue > &value_map, bool simulate=false)
Update the global blackboard using parameter values.
void onTermination(const ExecutionResult &result) override final
Callback invoked when the execution routine terminates.
static std::string stripPrefixFromParameterName(const std::string &prefix, const std::string &param_name)
Get the name of a parameter without its prefix.
bool afterTick() override final
Callback invoked every time after the behavior tree is ticked.
virtual void setUpBuilder(TreeBuilder &builder, const core::NodeManifest &node_manifest)
Callback invoked every time before any behavior trees are built.
std::map< std::string, rclcpp::ParameterValue > getParameterValuesWithPrefix(const std::string &prefix)
Assemble all parameters of this node that have a specific prefix.
std::shared_future< ExecutionResult > startExecution(const std::string &tree_build_request)
Start the behavior tree that is specified by a particular build request.
bool onTick() override final
Callback invoked every time before the behavior tree is ticked.
bool updateScriptingEnumsWithParameterValues(const std::map< std::string, rclcpp::ParameterValue > &value_map, bool simulate=false)
Update the internal buffer of scripting enums used when a behavior tree is created.
TreeConstructor makeTreeConstructor(const std::string &build_handler_request, const std::string &root_tree_name, const core::NodeManifest &node_manifest={}, const core::NodeManifest &node_overrides={})
Create a callback that builds a behavior tree according to a specific request.
void loadBuildHandler(const std::string &name)
Load a particular behavior tree build handler plugin.
TreeExecutorNode(const std::string &name, TreeExecutorNodeOptions executor_options)
Constructor allowing to specify a custom node name and executor options.
State observer for a particular behavior tree object that writes introspection and debugging informat...
virtual void flush() override
Reset the internal state variables.
const std::vector< std::string > & getRunningActionHistory() const
Get all names of action nodes that returned BT::NodeStatus::RUNNING since the last time TreeStateObse...
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.
Useful tooling for incorporating behavior trees for task development.
Definition builder.hpp:30
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