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