15#include "auto_apms_behavior_tree/executor/generic_executor_node.hpp"
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"
35 rcl_interfaces::msg::ListParametersResult res =
node_ptr_->list_parameters({}, 0);
36 std::vector<std::string> unknown_param_names;
37 for (
const std::string & param_name : res.names) {
38 if (!stripPrefixFromParameterName(SCRIPTING_ENUM_PARAM_PREFIX, param_name).empty()) continue;
39 if (!stripPrefixFromParameterName(BLACKBOARD_PARAM_PREFIX, param_name).empty()) continue;
40 if (auto_apms_util::contains(TREE_EXECUTOR_EXPLICITLY_ALLOWED_PARAMETERS, param_name)) continue;
42 node_ptr_->undeclare_parameter(param_name);
43 } catch (
const rclcpp::exceptions::ParameterImmutableException & e) {
46 }
catch (
const rclcpp::exceptions::InvalidParameterTypeException & e) {
50 unknown_param_names.push_back(param_name);
52 if (!unknown_param_names.empty()) {
54 logger_,
"The following initial parameters are not supported and have been removed: [ %s ].",
55 auto_apms_util::join(unknown_param_names,
", ").c_str());
59 std::vector<rclcpp::Parameter> new_default_parameters;
60 std::map<std::string, rclcpp::ParameterValue> effective_param_overrides =
61 node_ptr_->get_node_parameters_interface()->get_parameter_overrides();
62 for (
const auto & [name, value] : executor_options_.custom_default_parameters_) {
63 if (effective_param_overrides.find(name) == effective_param_overrides.end()) {
64 new_default_parameters.push_back(rclcpp::Parameter(name, value));
67 if (!new_default_parameters.empty()) node_ptr_->set_parameters_atomically(new_default_parameters);
69 const ExecutorParameters initial_params = executor_param_listener_.get_params();
72 tree_node_loader_ptr_ = core::NodeRegistrationLoader::make_shared(
73 std::set<std::string>(initial_params.node_exclude_packages.begin(), initial_params.node_exclude_packages.end()));
76 build_handler_loader_ptr_ = TreeBuildHandlerLoader::make_unique(
77 std::set<std::string>(
78 initial_params.build_handler_exclude_packages.begin(), initial_params.build_handler_exclude_packages.end()));
82 initial_params.build_handler != PARAM_VALUE_NO_BUILD_HANDLER &&
83 !build_handler_loader_ptr_->isClassAvailable(initial_params.build_handler)) {
84 throw exceptions::TreeExecutorError(
85 "Cannot load build handler '" + initial_params.build_handler +
86 "' because no corresponding ament_index resource was found. Make sure that you spelled the build handler's "
88 "and registered it by calling auto_apms_behavior_tree_register_build_handlers() in the CMakeLists.txt of the "
89 "corresponding package.");
91 loadBuildHandler(initial_params.build_handler);
94 const auto initial_scripting_enums = getParameterValuesWithPrefix(SCRIPTING_ENUM_PARAM_PREFIX);
95 if (!initial_scripting_enums.empty()) {
96 if (executor_options_.scripting_enum_parameters_from_overrides_) {
97 updateScriptingEnumsWithParameterValues(initial_scripting_enums);
101 "Initial scripting enums have been provided, but the 'Scripting enums from overrides' option is disabled. "
105 const auto initial_blackboard = getParameterValuesWithPrefix(BLACKBOARD_PARAM_PREFIX);
106 if (!initial_blackboard.empty()) {
107 if (executor_options_.blackboard_parameters_from_overrides_) {
108 updateGlobalBlackboardWithParameterValues(initial_blackboard);
112 "Initial blackboard entries have been provided, but the 'Blackboard from overrides' option is disabled. "
117 using namespace std::placeholders;
120 const std::string command_action_name =
121 executor_options_.command_action_name_.empty()
122 ? std::string(node_ptr_->get_name()) + _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_COMMAND_ACTION_NAME_SUFFIX
123 : executor_options_.command_action_name_;
124 const std::string clear_bb_service_name =
125 executor_options_.clear_blackboard_service_name_.empty()
126 ? std::string(node_ptr_->get_name()) + _AUTO_APMS_BEHAVIOR_TREE__CLEAR_BLACKBOARD_SERVICE_NAME_SUFFIX
127 : executor_options_.clear_blackboard_service_name_;
130 if (executor_options_.enable_command_action_) {
131 command_action_ptr_ = rclcpp_action::create_server<CommandActionContext::Type>(
132 node_ptr_, command_action_name, std::bind(&GenericTreeExecutorNode::handle_command_goal_,
this, _1, _2),
133 std::bind(&GenericTreeExecutorNode::handle_command_cancel_,
this, _1),
134 std::bind(&GenericTreeExecutorNode::handle_command_accept_,
this, _1));
138 if (executor_options_.enable_clear_blackboard_service_) {
139 clear_blackboard_service_ptr_ = node_ptr_->create_service<std_srvs::srv::Trigger>(
140 clear_bb_service_name, [
this](
141 const std::shared_ptr<std_srvs::srv::Trigger::Request> ,
142 std::shared_ptr<std_srvs::srv::Trigger::Response> response) {
143 response->success = this->clearGlobalBlackboard();
144 if (response->success) {
145 response->message =
"Blackboard was cleared successfully";
147 response->message =
"Blackboard cannot be cleared, because executor is in state " +
148 toStr(this->getExecutionState()) +
" but must be idling";
150 RCLCPP_DEBUG_STREAM(this->logger_, response->message);
156 executor_options_.scripting_enum_parameters_from_overrides_ ||
157 executor_options_.scripting_enum_parameters_dynamic_ || executor_options_.blackboard_parameters_from_overrides_ ||
158 executor_options_.blackboard_parameters_dynamic_) {
159 on_set_parameters_callback_handle_ptr_ =
160 node_ptr_->add_on_set_parameters_callback([
this](
const std::vector<rclcpp::Parameter> & parameters) {
161 return this->on_set_parameters_callback_(parameters);
164 parameter_event_handler_ptr_ = std::make_shared<rclcpp::ParameterEventHandler>(node_ptr_);
165 parameter_event_callback_handle_ptr_ = parameter_event_handler_ptr_->add_parameter_event_callback(
166 [
this](
const rcl_interfaces::msg::ParameterEvent & event) { this->parameter_event_callback_(event); });
186 const std::string & build_request,
const std::string & entry_point,
const core::NodeManifest & node_manifest)
188 const ExecutorParameters params = executor_param_listener_.get_params();
190 makeTreeConstructor(build_request, entry_point, node_manifest), params.tick_rate, params.groot2_port);
193bool GenericTreeExecutorNode::onTick()
195 const ExecutorParameters params = executor_param_listener_.get_params();
200bool GenericTreeExecutorNode::afterTick()
202 const ExecutorParameters params = executor_param_listener_.get_params();
205 if (executor_options_.blackboard_parameters_dynamic_ && params.allow_dynamic_blackboard) {
207 std::vector<rclcpp::Parameter> new_parameters;
208 for (
const BT::StringView & str : bb_ptr->getKeys()) {
209 const std::string key = std::string(str);
210 const BT::TypeInfo * type_info = bb_ptr->entryInfo(key);
211 const BT::Any * any = bb_ptr->getAnyLocked(key).get();
213 if (any->empty())
continue;
215 if (translated_global_blackboard_entries_.find(key) == translated_global_blackboard_entries_.end()) {
216 const BT::Expected<rclcpp::ParameterValue> expected =
219 new_parameters.push_back(rclcpp::Parameter(BLACKBOARD_PARAM_PREFIX +
"." + key, expected.value()));
220 translated_global_blackboard_entries_[key] = expected.value();
223 logger_,
"Failed to translate new blackboard entry '%s' (Type: %s) to parameters: %s", key.c_str(),
224 type_info->typeName().c_str(), expected.error().c_str());
227 const BT::Expected<rclcpp::ParameterValue> expected =
230 if (expected.value() != translated_global_blackboard_entries_[key]) {
231 new_parameters.push_back(rclcpp::Parameter(BLACKBOARD_PARAM_PREFIX +
"." + key, expected.value()));
235 logger_,
"Failed to translate blackboard entry '%s' (Type: %s) to parameters: %s", key.c_str(),
236 type_info->typeName().c_str(), expected.error().c_str());
240 if (!new_parameters.empty()) {
241 const rcl_interfaces::msg::SetParametersResult result =
node_ptr_->set_parameters_atomically(new_parameters);
242 if (!result.successful) {
243 throw exceptions::TreeExecutorError(
244 "Unexpectedly failed to set parameters inferred from global blackboard. Reason: " + result.reason);
254 return executor_param_listener_.get_params();
258 const std::string & prefix)
260 const auto res =
node_ptr_->list_parameters({prefix}, 2);
261 std::map<std::string, rclcpp::ParameterValue> value_map;
262 for (
const std::string & name_with_prefix : res.names) {
264 value_map[suffix] =
node_ptr_->get_parameter(name_with_prefix).get_parameter_value();
271 const std::string & prefix,
const std::string & param_name)
273 const std::regex reg(
"^" + prefix +
"\\.(\\S+)");
274 if (std::smatch match; std::regex_match(param_name, match, reg))
return match[1].str();
279 const std::map<std::string, rclcpp::ParameterValue> & value_map,
bool simulate)
281 std::map<std::string, std::string> set_successfully_map;
282 for (
const auto & [enum_key, pval] : value_map) {
284 switch (pval.get_type()) {
285 case rclcpp::ParameterType::PARAMETER_BOOL:
286 if (simulate)
continue;
287 scripting_enums_[enum_key] =
static_cast<int>(pval.get<
bool>());
289 case rclcpp::ParameterType::PARAMETER_INTEGER:
290 if (simulate)
continue;
291 scripting_enums_[enum_key] =
static_cast<int>(pval.get<
int>());
294 if (simulate)
return false;
295 throw exceptions::ParameterConversionError(
"Parameter to scripting enum conversion is not allowed.");
297 set_successfully_map[enum_key] = rclcpp::to_string(pval);
298 }
catch (
const std::exception & e) {
300 logger_,
"Error setting scripting enum from parameter %s=%s (Type: %s): %s", enum_key.c_str(),
301 rclcpp::to_string(pval).c_str(), rclcpp::to_string(pval.get_type()).c_str(), e.what());
305 if (!set_successfully_map.empty()) {
307 logger_,
"Updated scripting enums from parameters: { %s }",
314 const std::map<std::string, rclcpp::ParameterValue> & value_map,
bool simulate)
317 std::map<std::string, std::string> set_successfully_map;
318 for (
const auto & [entry_key, pval] : value_map) {
321 BT::Any any(expected.value());
323 if (
const BT::TypeInfo * entry_info = bb.entryInfo(entry_key)) {
324 if (entry_info->isStronglyTyped() && entry_info->type() != any.type())
return false;
328 bb.set(entry_key, any);
331 throw exceptions::ParameterConversionError(expected.error());
333 translated_global_blackboard_entries_[entry_key] = pval;
334 set_successfully_map[entry_key] = rclcpp::to_string(pval);
335 }
catch (
const std::exception & e) {
337 logger_,
"Error updating blackboard from parameter %s=%s (Type: %s): %s", entry_key.c_str(),
338 rclcpp::to_string(pval).c_str(), rclcpp::to_string(pval.get_type()).c_str(), e.what());
342 if (!set_successfully_map.empty()) {
351 if (build_handler_ptr_ && !executor_param_listener_.get_params().allow_other_build_handlers) {
352 throw std::logic_error(
353 "Executor option 'Allow other build handlers' is disabled, but loadBuildHandler() was called again after "
355 current_build_handler_name_ +
"'.");
357 if (current_build_handler_name_ == name)
return;
359 build_handler_ptr_.reset();
363 build_handler_loader_ptr_->createUniqueInstance(name)->makeUnique(
node_ptr_, tree_node_loader_ptr_);
364 }
catch (
const pluginlib::CreateClassException & e) {
365 throw exceptions::TreeExecutorError(
366 "An error occurred when trying to create an instance of tree build handler class '" + name +
367 "'. This might be because you forgot to call the AUTO_APMS_BEHAVIOR_TREE_REGISTER_BUILD_HANDLER macro "
368 "in the source file: " +
370 }
catch (
const std::exception & e) {
371 throw exceptions::TreeExecutorError(
372 "An error occurred when trying to create an instance of tree build handler class '" + name +
"': " + e.what());
375 current_build_handler_name_ = name;
379 const std::string & build_request,
const std::string & entry_point,
const core::NodeManifest & node_manifest)
382 if (build_handler_ptr_ && !build_handler_ptr_->setBuildRequest(build_request, entry_point, node_manifest)) {
383 throw exceptions::TreeBuildError(
384 "Build request '" + build_request +
"' was denied by '" + current_build_handler_name_ +
385 "' (setBuildRequest() returned false).");
388 return [
this, build_request, entry_point, node_manifest](TreeBlackboardSharedPtr bb_ptr) {
394 this->tree_node_loader_ptr_));
397 this->
preBuild(*this->builder_ptr_, build_request, entry_point, node_manifest, *bb_ptr);
400 for (
const auto & [enum_key, val] : this->scripting_enums_) this->builder_ptr_->setScriptingEnum(enum_key, val);
403 std::string instantiate_name =
"";
404 if (this->build_handler_ptr_) {
405 instantiate_name = this->build_handler_ptr_->buildTree(*this->builder_ptr_, *bb_ptr).getName();
409 Tree tree = instantiate_name.empty() ? this->builder_ptr_->instantiate(bb_ptr)
410 : this->builder_ptr_->instantiate(instantiate_name, bb_ptr);
420 return core::TreeBuilder::make_shared(
422 this->tree_node_loader_ptr_);
428 if (executor_options_.blackboard_parameters_from_overrides_ || executor_options_.blackboard_parameters_dynamic_) {
429 const auto res =
node_ptr_->list_parameters({BLACKBOARD_PARAM_PREFIX}, 2);
430 for (
const std::string & name : res.names) {
439rcl_interfaces::msg::SetParametersResult GenericTreeExecutorNode::on_set_parameters_callback_(
440 const std::vector<rclcpp::Parameter> & parameters)
442 const ExecutorParameters params = executor_param_listener_.get_params();
444 for (
const rclcpp::Parameter & p : parameters) {
445 auto create_rejected = [&p](
const std::string msg) {
446 rcl_interfaces::msg::SetParametersResult result;
447 result.successful =
false;
448 result.reason =
"Rejected to set " + p.get_name() +
" = " + p.value_to_string() +
" (Type: " + p.get_type_name() +
452 const std::string param_name = p.get_name();
458 return create_rejected(
"Scripting enums cannot change while tree executor is running");
460 if (!executor_options_.scripting_enum_parameters_dynamic_ || !params.allow_dynamic_scripting_enums) {
461 return create_rejected(
462 "Cannot set scripting enum '" + enum_key +
"', because the 'Dynamic scripting enums' option is disabled");
465 return create_rejected(
466 "Type of scripting enum must be bool or int. Tried to set enum '" + enum_key +
"' with value '" +
467 p.value_to_string() +
"' (Type: " + p.get_type_name() +
")");
474 !entry_key.empty()) {
475 if (!executor_options_.blackboard_parameters_dynamic_ || !params.allow_dynamic_blackboard) {
476 return create_rejected(
477 "Cannot set blackboard entry '" + entry_key +
"', because the 'Dynamic blackboard' option is disabled");
480 return create_rejected(
481 "Type of blackboard entries must not change. Tried to set entry '" + entry_key +
483 p.value_to_string() +
"' (Type: " + p.get_type_name() +
")");
490 return create_rejected(
"Parameter is unknown");
495 return create_rejected(
"Parameter is not allowed to change while tree executor is running");
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 "
506 const std::string class_name = p.as_string();
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 "
512 "and registered it by calling auto_apms_behavior_tree_register_build_handlers() in the CMakeLists.txt of "
514 "corresponding package");
519 if (!
node_ptr_->has_parameter(param_name)) {
520 return create_rejected(
"Parameter '" + param_name +
"' is not supported");
524 rcl_interfaces::msg::SetParametersResult result;
525 result.successful =
true;
529void GenericTreeExecutorNode::parameter_event_callback_(
const rcl_interfaces::msg::ParameterEvent & event)
531 std::regex re(
node_ptr_->get_fully_qualified_name());
532 if (std::regex_match(event.node, re)) {
533 for (
const rclcpp::Parameter & p : rclcpp::ParameterEventHandler::get_parameters_from_event(event)) {
534 const std::string param_name = p.get_name();
542 !entry_key.empty()) {
546 if (param_name == _AUTO_APMS_BEHAVIOR_TREE__EXECUTOR_PARAM_BUILD_HANDLER) {
553rclcpp_action::GoalResponse GenericTreeExecutorNode::handle_command_goal_(
554 const rclcpp_action::GoalUUID & , std::shared_ptr<const CommandActionContext::Goal> goal_ptr)
556 if (command_timer_ptr_ && !command_timer_ptr_->is_canceled()) {
557 RCLCPP_WARN(
logger_,
"Request for setting tree executor command rejected, because previous one is still busy.");
558 return rclcpp_action::GoalResponse::REJECT;
562 switch (goal_ptr->command) {
563 case CommandActionContext::Goal::COMMAND_RESUME:
568 logger_,
"Requested to RESUME with executor being in state %s. Rejecting request.",
569 toStr(execution_state).c_str());
570 return rclcpp_action::GoalResponse::REJECT;
573 case CommandActionContext::Goal::COMMAND_PAUSE:
578 logger_,
"Requested to PAUSE with executor already being inactive (State: %s).",
579 toStr(execution_state).c_str());
582 case CommandActionContext::Goal::COMMAND_HALT:
589 logger_,
"Requested to HALT with executor already being inactive (State: %s).",
590 toStr(execution_state).c_str());
593 case CommandActionContext::Goal::COMMAND_TERMINATE:
598 logger_,
"Requested to TERMINATE with executor already being inactive (State: %s).",
599 toStr(execution_state).c_str());
603 RCLCPP_WARN(
logger_,
"Executor command %i is undefined. Rejecting request.", goal_ptr->command);
604 return rclcpp_action::GoalResponse::REJECT;
606 return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
609rclcpp_action::CancelResponse GenericTreeExecutorNode::handle_command_cancel_(
610 std::shared_ptr<CommandActionContext::GoalHandle> )
612 return rclcpp_action::CancelResponse::ACCEPT;
615void GenericTreeExecutorNode::handle_command_accept_(std::shared_ptr<CommandActionContext::GoalHandle> goal_handle_ptr)
617 const auto command_request = goal_handle_ptr->get_goal()->command;
619 switch (command_request) {
620 case CommandActionContext::Goal::COMMAND_RESUME:
624 case CommandActionContext::Goal::COMMAND_PAUSE:
628 case CommandActionContext::Goal::COMMAND_HALT:
632 case CommandActionContext::Goal::COMMAND_TERMINATE:
637 throw std::logic_error(
"command_request is unknown");
640 command_timer_ptr_ =
node_ptr_->create_wall_timer(
641 std::chrono::duration<double>(executor_param_listener_.get_params().tick_rate),
642 [
this, requested_state, goal_handle_ptr, action_result_ptr = std::make_shared<CommandActionContext::Result>()]() {
643 if (goal_handle_ptr->is_canceling()) {
644 goal_handle_ptr->canceled(action_result_ptr);
645 command_timer_ptr_->cancel();
653 logger_,
"Failed to reach requested state %s due to cancellation of execution timer. Aborting.",
654 toStr(requested_state).c_str());
655 goal_handle_ptr->abort(action_result_ptr);
656 command_timer_ptr_->cancel();
660 if (current_state != requested_state)
return;
662 goal_handle_ptr->succeed(action_result_ptr);
663 command_timer_ptr_->cancel();
bool updateGlobalBlackboardWithParameterValues(const std::map< std::string, rclcpp::ParameterValue > &value_map, bool simulate=false)
Update the global blackboard using parameter values.
static const std::string PARAM_VALUE_NO_BUILD_HANDLER
Value indicating that no build handler is loaded.
static std::string stripPrefixFromParameterName(const std::string &prefix, const std::string ¶m_name)
Get the name of a parameter without its prefix.
GenericTreeExecutorNode(rclcpp::Node::SharedPtr node_ptr, Options options)
Constructor using an existing ROS 2 node.
virtual bool clearGlobalBlackboard() override
Reset the global blackboard and clear all entries.
core::TreeBuilder::SharedPtr createTreeBuilder()
Create a tree builder for building the behavior tree.
std::map< std::string, rclcpp::ParameterValue > getParameterValuesWithPrefix(const std::string &prefix)
Assemble all parameters of this node that have a specific prefix.
bool updateScriptingEnumsWithParameterValues(const std::map< std::string, rclcpp::ParameterValue > &value_map, bool simulate=false)
Update the internal buffer of scripting enums.
ExecutorParameters getExecutorParameters() const
Get a copy of the current executor parameters.
std::shared_future< ExecutionResult > startExecution(const std::string &build_request, const std::string &entry_point="", const core::NodeManifest &node_manifest={})
Start the behavior tree specified by a particular build request.
virtual void postBuild(Tree &tree)
Callback invoked after the behavior tree has been instantiated.
virtual void preBuild(core::TreeBuilder &builder, const std::string &build_request, const std::string &entry_point, const core::NodeManifest &node_manifest, TreeBlackboard &bb)
Callback invoked before building the behavior tree.
void loadBuildHandler(const std::string &name)
Load a particular behavior tree build handler plugin.
TreeConstructor makeTreeConstructor(const std::string &build_request, const std::string &entry_point="", const core::NodeManifest &node_manifest={})
Create a callback that builds a behavior tree according to a specific request.
ExecutionState
Enum representing possible behavior tree execution states.
@ STARTING
Initializing execution.
@ 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.
@ PAUSE
Pause the execution routine.
@ RUN
Start/Resume the execution routine.
@ HALT
Halt the currently executing tree and pause the execution routine.
TreeStateObserver & getStateObserver()
Get a reference to the current behavior tree state observer.
TreeExecutorBase(rclcpp::Node::SharedPtr node_ptr, rclcpp::CallbackGroup::SharedPtr tree_node_callback_group_ptr=nullptr)
Constructor.
TreeBlackboardSharedPtr getGlobalBlackboardPtr()
Get a shared pointer to the global blackboard instance.
std::string getTreeName()
Get the name of the tree that is currently executing.
const rclcpp::Logger logger_
Logger associated with the parent ROS 2 node.
void setLogging(bool active)
Configure whether the observer should write to the logger.
Data structure for information about which behavior tree node plugin to load and how to configure the...
Class for configuring and instantiating behavior trees.
bool contains(const ContainerT< ValueT, AllocatorT > &c, const ValueT &val)
Check whether a particular container structure contains a value.
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.
const char * toStr(const ActionNodeErrorCode &err)
Convert the action error code to string.
Powerful tooling for incorporating behavior trees for task development.
BT::Expected< BT::Any > createAnyFromParameterValue(const rclcpp::ParameterValue &val)
Convert a ROS 2 parameter value to a BT::Any object.
BT::Expected< rclcpp::ParameterValue > createParameterValueFromAny(const BT::Any &any, rclcpp::ParameterType type)
Convert a BT::Any object to a ROS 2 parameter value.