15#include "auto_apms_px4/behavior_mode_executor.hpp"
21#include "auto_apms_behavior_tree/executor/options.hpp"
22#include "auto_apms_behavior_tree_core/node/node_manifest.hpp"
23#include "auto_apms_px4/behavior_mode_executor_params.hpp"
24#include "px4_msgs/msg/vehicle_command.hpp"
33BehaviorOwnedMode::BehaviorOwnedMode(rclcpp::Node & node,
const px4_ros2::ModeBase::Settings & settings)
34: px4_ros2::ModeBase(node, settings)
36 actuator_setpoint_ptr_ = std::make_shared<px4_ros2::DirectActuatorsSetpointType>(*
this);
39void BehaviorOwnedMode::updateSetpoint(
float )
51 BehaviorOwnedMode & owned_mode,
const px4_ros2::ModeExecutorBase::Settings & settings,
52 std::function<Config()> config_provider,
BehaviorSpec initial_spec,
54: px4_ros2::ModeExecutorBase(settings, owned_mode),
55 node_(owned_mode.node()),
56 owned_mode_(owned_mode),
57 behavior_executor_(engine),
58 config_provider_(std::move(config_provider)),
59 config_(config_provider_()),
60 current_spec_(std::move(initial_spec)),
61 vehicle_command_client_(node_)
67 RCLCPP_INFO(node_.get_logger(),
"Behavior execution result: %s.", auto_apms_behavior_tree::toStr(result).c_str());
73 result == ExecutionResult::TREE_SUCCEEDED ? config_.on_completion : config_.on_failure;
74 performReaction(reaction, result);
76 RCLCPP_INFO(node_.get_logger(),
"No longer in charge. Skipping completion reaction");
84 }
catch (
const std::exception & e) {
85 RCLCPP_ERROR(node_.get_logger(),
"Failed to prepare behavior tree for the next activation: %s", e.what());
97 throw std::invalid_argument(
98 "Invalid completion reaction '" + str +
"' (expected one of: hold, rtl, land, disarm, complete, none)");
101void BehaviorModeExecutor::onActivate()
106 config_ = config_provider_();
108 if (!prepared_tree_ptr_) {
109 RCLCPP_ERROR(node_.get_logger(),
"Cannot start behavior: no pre-built behavior tree is available");
115 node_.get_logger(),
"Behavior executor put in charge. Starting behavior '%s'", current_spec_.
build_request.c_str());
117 if (config_.defer_failsafes) {
118 if (deferFailsafesSync(
true)) {
119 RCLCPP_INFO(node_.get_logger(),
"Failsafes are now being deferred while the behavior is running");
121 RCLCPP_WARN(node_.get_logger(),
"Failed to enable failsafe deferral");
130 const int source_component =
static_cast<int>(px4_msgs::msg::VehicleCommand::COMPONENT_MODE_EXECUTOR_START) +
id();
131 behavior_executor_.getGlobalBlackboardPtr()->set(AUTO_APMS_PX4_SOURCE_COMPONENT_GLOBAL_KEY, source_component);
135 const auto executor_params = behavior_executor_.getExecutorParameters();
137 behavior_executor_.startExecution(
138 std::move(prepared_tree_ptr_), executor_params.tick_rate, executor_params.groot2_port);
139 }
catch (
const std::exception & e) {
140 RCLCPP_ERROR(node_.get_logger(),
"Failed to start behavior: %s", e.what());
145void BehaviorModeExecutor::onDeactivate(DeactivateReason reason)
147 const char * reason_str = reason == DeactivateReason::FailsafeActivated ?
"failsafe activated" :
"other";
148 RCLCPP_INFO(node_.get_logger(),
"Behavior executor deactivating (reason: %s)", reason_str);
151 if (behavior_executor_.isBusy()) {
152 RCLCPP_INFO(node_.get_logger(),
"Behavior is still running. Terminating it now...");
156 if (config_.defer_failsafes) {
157 deferFailsafesSync(
false);
161std::unique_ptr<auto_apms_behavior_tree::Tree> BehaviorModeExecutor::buildTree(
const BehaviorSpec & spec)
167 behavior_executor_.getGlobalBlackboardPtr()->set(AUTO_APMS_PX4_MODE_EXECUTOR_ACTIVE_GLOBAL_KEY,
true);
169 auto_apms_behavior_tree::core::NodeManifest node_manifest;
170 if (!spec.node_manifest.empty()) {
171 node_manifest = auto_apms_behavior_tree::core::NodeManifest::decode(spec.node_manifest);
178 const auto_apms_behavior_tree::TreeConstructor make_tree =
179 behavior_executor_.makeTreeConstructor(spec.build_request, spec.entry_point, node_manifest);
180 const auto_apms_behavior_tree::TreeBlackboardSharedPtr main_tree_bb_ptr =
181 auto_apms_behavior_tree::TreeBlackboard::create(behavior_executor_.getGlobalBlackboardPtr());
182 return std::make_unique<auto_apms_behavior_tree::Tree>(make_tree(main_tree_bb_ptr));
187 prepared_tree_ptr_ = buildTree(current_spec_);
189 node_.get_logger(),
"Behavior tree '%s' built and ready for activation", current_spec_.build_request.c_str());
196 std::unique_ptr<auto_apms_behavior_tree::Tree> candidate_tree;
198 candidate_tree = buildTree(candidate);
199 }
catch (
const std::exception & e) {
201 RCLCPP_WARN(node_.get_logger(),
"Rejecting behavior '%s': %s", candidate.
build_request.c_str(), message.c_str());
206 prepared_tree_ptr_ = std::move(candidate_tree);
207 current_spec_ = candidate;
208 message =
"Behavior '" + candidate.
build_request +
"' verified and set";
209 RCLCPP_INFO(node_.get_logger(),
"%s", message.c_str());
215 RCLCPP_INFO(node_.get_logger(),
"Requesting activation of owned mode (nav_state %d)", owned_mode_.id());
216 return vehicle_command_client_.syncActivateFlightModeAndWait(&owned_mode_, timeout);
219void BehaviorModeExecutor::performReaction(CompletionReaction reaction, ExecutionResult result)
221 px4_ros2::Result px4_result = px4_ros2::Result::Success;
223 case ExecutionResult::TREE_SUCCEEDED:
224 px4_result = px4_ros2::Result::Success;
226 case ExecutionResult::TERMINATED_PREMATURELY:
227 px4_result = px4_ros2::Result::Deactivated;
229 case ExecutionResult::TREE_FAILED:
230 case ExecutionResult::ERROR:
232 px4_result = px4_ros2::Result::ModeFailureOther;
236 const auto log_done = [
this](px4_ros2::Result r) {
237 RCLCPP_INFO(node_.get_logger(),
"Completion reaction finished (%s)", px4_ros2::resultToString(r));
242 RCLCPP_INFO(node_.get_logger(),
"Completion reaction: HOLD");
246 RCLCPP_INFO(node_.get_logger(),
"Completion reaction: RTL");
250 RCLCPP_INFO(node_.get_logger(),
"Completion reaction: LAND");
254 RCLCPP_INFO(node_.get_logger(),
"Completion reaction: DISARM");
258 RCLCPP_INFO(node_.get_logger(),
"Completion reaction: COMPLETE (reporting owned mode completion)");
259 owned_mode_.finish(px4_result);
262 RCLCPP_INFO(node_.get_logger(),
"Completion reaction: NONE");
267void BehaviorModeExecutor::scheduleLoiter()
269 scheduleMode(px4_ros2::ModeBase::kModeIDLoiter, [](px4_ros2::Result) {
278px4_ros2::ModeExecutorBase::Settings::Activation BehaviorModeExecutorNode::activationFromString(
const std::string & str)
280 using Activation = px4_ros2::ModeExecutorBase::Settings::Activation;
281 if (str ==
"armed")
return Activation::ActivateOnlyWhenArmed;
282 if (str ==
"always")
return Activation::ActivateAlways;
283 if (str ==
"immediately")
return Activation::ActivateImmediately;
284 throw std::invalid_argument(
"Invalid activation '" + str +
"' (expected one of: armed, always, immediately)");
287BehaviorModeExecutorNode::BehaviorModeExecutorNode(
const rclcpp::NodeOptions & options)
288: GenericTreeExecutorNode(
289 "behavior_mode_executor",
296 auto_apms_behavior_tree::TreeExecutorNodeOptions(options)
297 .enableStrictUnkownParameterRemoval(false)
298 .enableScriptingEnumParameters(false, true)
299 .enableGlobalBlackboardParameters(false, true)),
300 registration_handler_(getNodePtr()),
301 start_action_context_(logger_)
309 const auto param_listener_ptr = std::make_shared<behavior_mode_executor_params::ParamListener>(getNodePtr());
310 const behavior_mode_executor_params::Params params = param_listener_ptr->get_params();
315 auto config_provider = [param_listener_ptr]() -> BehaviorModeExecutor::Config {
316 const behavior_mode_executor_params::Params p = param_listener_ptr->get_params();
317 BehaviorModeExecutor::Config config;
320 config.defer_failsafes = p.defer_failsafes;
324 const px4_ros2::ModeExecutorBase::Settings settings{activationFromString(params.activation)};
326 owned_mode_ptr_ = std::make_unique<BehaviorOwnedMode>(*getNodePtr(), px4_ros2::ModeBase::Settings{params.mode_name});
328 BehaviorSpec initial_spec;
329 initial_spec.build_request = params.behavior.build_request;
330 initial_spec.build_handler =
331 current_build_handler_name_;
332 initial_spec.entry_point = params.behavior.entry_point;
333 initial_spec.node_manifest = params.behavior.node_manifest;
335 mode_executor_ptr_ = std::make_unique<BehaviorModeExecutor>(
336 *owned_mode_ptr_, settings, std::move(config_provider), std::move(initial_spec), *
this);
341 mode_executor_ptr_->prepareTree();
344 registration_handler_.registerMode(*mode_executor_ptr_, params.mode_name);
348 set_behavior_service_ptr_ = getNodePtr()->create_service<SetBehaviorSrv>(
350 std::bind(&BehaviorModeExecutorNode::handleSetBehavior,
this, std::placeholders::_1, std::placeholders::_2));
352 start_action_ptr_ = rclcpp_action::create_server<StartAction>(
353 getNodePtr(),
"~/start",
354 std::bind(&BehaviorModeExecutorNode::handleStartGoal,
this, std::placeholders::_1, std::placeholders::_2),
355 std::bind(&BehaviorModeExecutorNode::handleStartCancel,
this, std::placeholders::_1),
356 std::bind(&BehaviorModeExecutorNode::handleStartAccept,
this, std::placeholders::_1));
359bool BehaviorModeExecutorNode::applyBehavior(
BehaviorSpec candidate, std::string & message)
363 const std::string previous_handler = current_build_handler_name_;
364 bool switched =
false;
365 if (!candidate.build_handler.empty() && candidate.build_handler != current_build_handler_name_) {
367 message =
"Build handler '" + candidate.build_handler +
368 "' rejected: the 'Allow other build handlers' option is "
369 "disabled (current: '" +
370 current_build_handler_name_ +
"')";
371 RCLCPP_WARN(
logger_,
"%s", message.c_str());
377 }
catch (
const std::exception & e) {
378 message = std::string(
"Failed to load build handler '") + candidate.build_handler +
"': " + e.what();
379 RCLCPP_WARN(
logger_,
"%s", message.c_str());
385 candidate.build_handler = current_build_handler_name_;
387 if (mode_executor_ptr_->setBehavior(candidate, message))
return true;
393 }
catch (
const std::exception & e) {
394 RCLCPP_ERROR(
logger_,
"Failed to restore previous build handler '%s': %s", previous_handler.c_str(), e.what());
400void BehaviorModeExecutorNode::handleSetBehavior(
401 const std::shared_ptr<SetBehaviorSrv::Request> request, std::shared_ptr<SetBehaviorSrv::Response> response)
405 if (
isBusy() || mode_executor_ptr_->isInCharge()) {
406 response->success =
false;
407 response->message =
"Cannot change behavior while a behavior is running or the executor is in charge";
408 RCLCPP_WARN(
logger_,
"%s", response->message.c_str());
412 BehaviorSpec candidate;
413 candidate.build_request = request->build_request;
414 candidate.build_handler = request->build_handler;
415 candidate.entry_point = request->entry_point;
416 candidate.node_manifest = request->node_manifest;
419 response->success = applyBehavior(candidate, message);
420 response->message = message;
423rclcpp_action::GoalResponse BehaviorModeExecutorNode::handleStartGoal(
424 const rclcpp_action::GoalUUID & uuid, std::shared_ptr<const StartAction::Goal> goal_ptr)
426 if (
isBusy() || mode_executor_ptr_->isInCharge()) {
428 logger_,
"Goal %s REJECTED: a behavior is already running or the executor is in charge",
429 rclcpp_action::to_string(uuid).c_str());
430 return rclcpp_action::GoalResponse::REJECT;
434 BehaviorSpec candidate;
435 candidate.build_request = goal_ptr->build_request;
436 candidate.build_handler = goal_ptr->build_handler;
437 candidate.entry_point = goal_ptr->entry_point;
438 candidate.node_manifest = goal_ptr->node_manifest;
441 if (!applyBehavior(candidate, message)) {
442 RCLCPP_WARN(
logger_,
"Goal %s REJECTED: %s", rclcpp_action::to_string(uuid).c_str(), message.c_str());
443 return rclcpp_action::GoalResponse::REJECT;
446 pending_attach_[uuid] = goal_ptr->attach;
447 pending_clear_blackboard_[uuid] = goal_ptr->clear_blackboard;
448 return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
451rclcpp_action::CancelResponse BehaviorModeExecutorNode::handleStartCancel(
452 std::shared_ptr<StartGoalHandle> )
455 return rclcpp_action::CancelResponse::ACCEPT;
458void BehaviorModeExecutorNode::handleStartAccept(std::shared_ptr<StartGoalHandle> goal_handle_ptr)
460 const rclcpp_action::GoalUUID uuid = goal_handle_ptr->get_goal_id();
461 const bool attach = pending_attach_.extract(uuid).mapped();
462 const bool clear_blackboard = pending_clear_blackboard_.extract(uuid).mapped();
469 auto result_ptr = std::make_shared<StartAction::Result>();
470 if (!mode_executor_ptr_->requestAndVerifyActivation()) {
471 result_ptr->tree_result = StartAction::Result::TREE_RESULT_NOT_SET;
472 result_ptr->message =
"Failed to activate the owned mode (not put in charge within the timeout)";
473 RCLCPP_ERROR(
logger_,
"%s", result_ptr->message.c_str());
474 goal_handle_ptr->abort(result_ptr);
480 result_ptr->tree_result = StartAction::Result::TREE_RESULT_NOT_SET;
481 result_ptr->message =
"Owned mode activated; behavior running detached";
482 goal_handle_ptr->succeed(result_ptr);
487 start_action_context_.setUp(goal_handle_ptr);
488 RCLCPP_INFO(
logger_,
"Owned mode activated; tracking behavior execution for attached goal.");
491void BehaviorModeExecutorNode::onTermination(
const ExecutionResult & result)
493 mode_executor_ptr_->onExecutionResult(result);
497 if (!start_action_context_.isValid())
return;
499 const std::shared_ptr<StartAction::Result> result_ptr = start_action_context_.getResultPtr();
500 result_ptr->terminated_tree_identity =
getTreeName();
503 result_ptr->tree_result = StartAction::Result::TREE_RESULT_SUCCESS;
504 result_ptr->message =
"Tree execution finished with status SUCCESS";
505 start_action_context_.succeed();
508 result_ptr->tree_result = StartAction::Result::TREE_RESULT_FAILURE;
509 result_ptr->message =
"Tree execution finished with status FAILURE";
510 start_action_context_.abort();
513 result_ptr->tree_result = StartAction::Result::TREE_RESULT_NOT_SET;
514 if (start_action_context_.getGoalHandlePtr()->is_canceling()) {
515 result_ptr->message =
"Tree execution canceled successfully";
516 start_action_context_.cancel();
518 result_ptr->message =
"Tree execution terminated prematurely";
519 start_action_context_.abort();
524 result_ptr->tree_result = StartAction::Result::TREE_RESULT_NOT_SET;
525 result_ptr->message =
"An unexpected error occurred during tree execution";
526 start_action_context_.abort();
529 start_action_context_.invalidate();
534#include "rclcpp_components/register_node_macro.hpp"
Flexible and configurable ROS 2 behavior tree executor node.
virtual bool clearGlobalBlackboard() override
Reset the global blackboard and clear all entries.
ExecutorParameters getExecutorParameters() const
Get a copy of the current executor parameters.
void loadBuildHandler(const std::string &name)
Load a particular behavior tree build handler plugin.
bool isBusy()
Determine whether this executor is currently executing a behavior tree.
void setControlCommand(ControlCommand cmd)
Set the command that handles the control flow of the execution routine.
@ TERMINATE
Halt the currently executing tree and terminate the execution routine.
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.
@ ERROR
An unexpected error occurred.
const rclcpp::Logger logger_
Logger associated with the parent ROS 2 node.
ROS 2 component that hosts a BehaviorModeExecutor and its owned mode on an in-process behavior tree e...
static CompletionReaction reactionFromString(const std::string &str)
Parse a string into a CompletionReaction.
CompletionReaction
Reaction performed when a behavior terminates or fails, using the in-charge executor API.
@ HOLD
Schedule the owned mode (hold position) and stay in charge.
@ DISARM
Disarm the vehicle.
@ COMPLETE
Report the owned mode as completed to the FMU (relinquish charge).
@ LAND
Land at the current position.
@ NONE
Do nothing and stay in charge.
void onExecutionResult(ExecutionResult result)
Handle the termination of the behavior running on the in-process executor.
bool requestAndVerifyActivation(std::chrono::milliseconds timeout=DEFAULT_ACTIVATION_TIMEOUT)
Request activation of the owned mode from ROS 2 and wait until it is confirmed in charge.
bool setBehavior(const BehaviorSpec &candidate, std::string &message)
Verify a candidate behavior and, if valid, make it the current behavior.
void prepareTree()
Build the current behavior tree and keep it detached, ready to be executed on the next activation.
BehaviorModeExecutor(BehaviorOwnedMode &owned_mode, const px4_ros2::ModeExecutorBase::Settings &settings, std::function< Config()> config_provider, BehaviorSpec initial_spec, auto_apms_behavior_tree::GenericTreeExecutorNode &engine)
Constructor.
Registration placeholder PX4 mode owned by a BehaviorModeExecutor.
Implementation of PX4 mode peers offered by px4_ros2_cpp enabling integration with AutoAPMS.
Which behavior to run and how to build it.
std::string build_request
Behavior build request (e.g. a registered behavior resource identity or XML).