AutoAPMS
Streamlining behaviors in ROS 2
Loading...
Searching...
No Matches
behavior_mode_executor.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// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include "auto_apms_px4/behavior_mode_executor.hpp"
16
17#include <cstdint>
18#include <stdexcept>
19#include <utility>
20
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"
25
26namespace auto_apms_px4
27{
28
29// #####################################################################################################################
30// ################################## BehaviorOwnedMode ######################################
31// #####################################################################################################################
32
33BehaviorOwnedMode::BehaviorOwnedMode(rclcpp::Node & node, const px4_ros2::ModeBase::Settings & settings)
34: px4_ros2::ModeBase(node, settings)
35{
36 actuator_setpoint_ptr_ = std::make_shared<px4_ros2::DirectActuatorsSetpointType>(*this);
37}
38
39void BehaviorOwnedMode::updateSetpoint(float /*dt_s*/)
40{
41 // Intentionally a no-op. This mode is a registration placeholder: px4_ros2 requires every mode to declare at least
42 // one setpoint type (the DirectActuatorsSetpointType constructed above), but this mode is never meant to actively
43 // fly.
44}
45
46// #####################################################################################################################
47// ################################# BehaviorModeExecutor ####################################
48// #####################################################################################################################
49
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_)
62{
63}
64
65void BehaviorModeExecutor::onExecutionResult(ExecutionResult result)
66{
67 RCLCPP_INFO(node_.get_logger(), "Behavior execution result: %s.", auto_apms_behavior_tree::toStr(result).c_str());
68
69 // If we lost charge in the meantime (pilot/failsafe), don't fight the FMU: skip the completion reaction. We still
70 // prepare a fresh tree below so the next activation stays lightweight.
71 if (isInCharge()) {
72 const CompletionReaction reaction =
73 result == ExecutionResult::TREE_SUCCEEDED ? config_.on_completion : config_.on_failure;
74 performReaction(reaction, result);
75 } else {
76 RCLCPP_INFO(node_.get_logger(), "No longer in charge. Skipping completion reaction");
77 }
78
79 // Rebuild the detached tree so the next time this executor is put in charge, activation does not pay the tree
80 // construction cost. A build failure here (e.g. an invalid build request set at runtime) leaves no prepared tree;
81 // the next activation then reports an error instead of running a stale one.
82 try {
84 } catch (const std::exception & e) {
85 RCLCPP_ERROR(node_.get_logger(), "Failed to prepare behavior tree for the next activation: %s", e.what());
86 }
87}
88
90{
91 if (str == "hold") return CompletionReaction::HOLD;
92 if (str == "rtl") return CompletionReaction::RTL;
93 if (str == "land") return CompletionReaction::LAND;
94 if (str == "disarm") return CompletionReaction::DISARM;
95 if (str == "complete") return CompletionReaction::COMPLETE;
96 if (str == "none") return CompletionReaction::NONE;
97 throw std::invalid_argument(
98 "Invalid completion reaction '" + str + "' (expected one of: hold, rtl, land, disarm, complete, none)");
99}
100
101void BehaviorModeExecutor::onActivate()
102{
103 // Refresh the configuration from the current parameter values so runtime changes (e.g. via `ros2 param set`) to the
104 // completion reactions and failsafe deferral take effect on this activation. The behavior itself was already built
105 // (see prepareTree); its build request is latched at build time.
106 config_ = config_provider_();
107
108 if (!prepared_tree_ptr_) {
109 RCLCPP_ERROR(node_.get_logger(), "Cannot start behavior: no pre-built behavior tree is available");
110 onExecutionResult(ExecutionResult::ERROR);
111 return;
112 }
113
114 RCLCPP_INFO(
115 node_.get_logger(), "Behavior executor put in charge. Starting behavior '%s'", current_spec_.build_request.c_str());
116
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");
120 } else {
121 RCLCPP_WARN(node_.get_logger(), "Failed to enable failsafe deferral");
122 }
123 }
124
125 // Publish the executor's VehicleCommand source component on the global blackboard so that ownership-aware behavior
126 // tree nodes (e.g. SendCmdSetNavState) can attribute their commands to this executor. Those nodes read it at tick
127 // time, so setting it here (id() is valid once registered) is sufficient. Stored as int to match the type read by
128 // the SendCmdSetNavState node. The global key already carries the '@' prefix; on the global blackboard (which is its
129 // own root) this resolves to the same entry the tree nodes read transitively.
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);
132
133 // Hand the pre-built tree to the executor. This only (re)creates the lightweight execution timer and starts ticking;
134 // the expensive tree construction already happened in prepareTree.
135 const auto executor_params = behavior_executor_.getExecutorParameters();
136 try {
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());
141 onExecutionResult(ExecutionResult::ERROR);
142 }
143}
144
145void BehaviorModeExecutor::onDeactivate(DeactivateReason reason)
146{
147 const char * reason_str = reason == DeactivateReason::FailsafeActivated ? "failsafe activated" : "other";
148 RCLCPP_INFO(node_.get_logger(), "Behavior executor deactivating (reason: %s)", reason_str);
149
150 // The FMU has already taken over (pilot override or failsafe). Halting the behavior is cleanup, not safety.
151 if (behavior_executor_.isBusy()) {
152 RCLCPP_INFO(node_.get_logger(), "Behavior is still running. Terminating it now...");
154 }
155
156 if (config_.defer_failsafes) {
157 deferFailsafesSync(false);
158 }
159}
160
161std::unique_ptr<auto_apms_behavior_tree::Tree> BehaviorModeExecutor::buildTree(const BehaviorSpec & spec)
162{
163 // Flag that a mode executor is in charge so SendVehicleCommand routes commands through the mode-executor command
164 // topic. This must be set before the tree is built below, because the command nodes latch their command topic from
165 // this flag as they construct. The behavior executor is dedicated to this mode executor, so the flag stays true for
166 // the lifetime of its global blackboard.
167 behavior_executor_.getGlobalBlackboardPtr()->set(AUTO_APMS_PX4_MODE_EXECUTOR_ACTIVE_GLOBAL_KEY, true);
168
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);
172 }
173
174 // Build the tree now: this is the expensive step because it instantiates the ROS 2 waitables of the behavior tree
175 // nodes. The tree is kept detached until the next activation hands it to the executor. Its blackboard is rooted at
176 // the executor's global blackboard so that '@'-prefixed entries resolve at runtime (mirroring what
177 // TreeExecutorBase::startExecution does for the TreeConstructor overloads).
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));
183}
184
186{
187 prepared_tree_ptr_ = buildTree(current_spec_);
188 RCLCPP_INFO(
189 node_.get_logger(), "Behavior tree '%s' built and ready for activation", current_spec_.build_request.c_str());
190}
191
192bool BehaviorModeExecutor::setBehavior(const BehaviorSpec & candidate, std::string & message)
193{
194 // Verify the candidate by building its tree. This validates the tree structure and instantiates its nodes, so a
195 // malformed or unresolvable behavior throws here and the currently prepared tree and spec are left untouched.
196 std::unique_ptr<auto_apms_behavior_tree::Tree> candidate_tree;
197 try {
198 candidate_tree = buildTree(candidate);
199 } catch (const std::exception & e) {
200 message = e.what();
201 RCLCPP_WARN(node_.get_logger(), "Rejecting behavior '%s': %s", candidate.build_request.c_str(), message.c_str());
202 return false;
203 }
204
205 // Latch the verified tree and spec atomically: from here on this is what the next activation runs.
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());
210 return true;
211}
212
213bool BehaviorModeExecutor::requestAndVerifyActivation(std::chrono::milliseconds timeout)
214{
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);
217}
218
219void BehaviorModeExecutor::performReaction(CompletionReaction reaction, ExecutionResult result)
220{
221 px4_ros2::Result px4_result = px4_ros2::Result::Success;
222 switch (result) {
223 case ExecutionResult::TREE_SUCCEEDED:
224 px4_result = px4_ros2::Result::Success;
225 break;
226 case ExecutionResult::TERMINATED_PREMATURELY:
227 px4_result = px4_ros2::Result::Deactivated;
228 break;
229 case ExecutionResult::TREE_FAILED:
230 case ExecutionResult::ERROR:
231 default:
232 px4_result = px4_ros2::Result::ModeFailureOther;
233 break;
234 }
235
236 const auto log_done = [this](px4_ros2::Result r) {
237 RCLCPP_INFO(node_.get_logger(), "Completion reaction finished (%s)", px4_ros2::resultToString(r));
238 };
239
240 switch (reaction) {
242 RCLCPP_INFO(node_.get_logger(), "Completion reaction: HOLD");
243 scheduleLoiter();
244 break;
246 RCLCPP_INFO(node_.get_logger(), "Completion reaction: RTL");
247 rtl(log_done);
248 break;
250 RCLCPP_INFO(node_.get_logger(), "Completion reaction: LAND");
251 land(log_done);
252 break;
254 RCLCPP_INFO(node_.get_logger(), "Completion reaction: DISARM");
255 disarm(log_done);
256 break;
258 RCLCPP_INFO(node_.get_logger(), "Completion reaction: COMPLETE (reporting owned mode completion)");
259 owned_mode_.finish(px4_result);
260 break;
262 RCLCPP_INFO(node_.get_logger(), "Completion reaction: NONE");
263 break;
264 }
265}
266
267void BehaviorModeExecutor::scheduleLoiter()
268{
269 scheduleMode(px4_ros2::ModeBase::kModeIDLoiter, [](px4_ros2::Result) {
270 // Loiter mode has no completion signal, so callback is no-op.
271 });
272}
273
274// #####################################################################################################################
275// ############################### BehaviorModeExecutorNode ###################################
276// #####################################################################################################################
277
278px4_ros2::ModeExecutorBase::Settings::Activation BehaviorModeExecutorNode::activationFromString(const std::string & str)
279{
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)");
285}
286
287BehaviorModeExecutorNode::BehaviorModeExecutorNode(const rclcpp::NodeOptions & options)
288: GenericTreeExecutorNode(
289 "behavior_mode_executor",
290 // Disable declaring parameters from overrides for the scripting-enum and blackboard groups (runtime/dynamic use is
291 // kept). Without this the base would set automatically_declare_parameters_from_overrides(true), which declares the
292 // launch-provided `behavior.*` parameters as writable before generate_parameter_library runs - making it skip its
293 // `read_only` descriptor. With auto-declaration off, generate_parameter_library declares `behavior.*` itself (read
294 // only) while still picking up the launch-provided initial values. All other launch parameters are explicitly
295 // declared, so none rely on override auto-declaration.
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_)
302{
303 // Declares all behavior-specific parameters via generate_parameter_library. The listener is kept alive for the
304 // lifetime of the executor (captured by the config provider below) so its parameter-validation callback stays
305 // registered and get_params() keeps reflecting runtime changes. The behavior parameters (`behavior.*`) are read
306 // only: they only seed the initial behavior below. The behavior is changed at runtime exclusively through the
307 // `set_behavior` service and the `StartTreeExecutor` action, which verify a candidate before latching it. Only the
308 // reaction parameters remain writable and are re-read on each activation via the config provider.
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();
311
312 // Builds a fresh Config (completion reactions and failsafe deferral only) from the current parameter values. Invoked
313 // by the executor on every activation so runtime parameter changes take effect. The reaction strings are constrained
314 // to valid values by the parameter validation (one_of<>), so reactionFromString never sees an invalid value here.
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;
318 config.on_completion = BehaviorModeExecutor::reactionFromString(p.on_completion);
319 config.on_failure = BehaviorModeExecutor::reactionFromString(p.on_failure);
320 config.defer_failsafes = p.defer_failsafes;
321 return config;
322 };
323
324 const px4_ros2::ModeExecutorBase::Settings settings{activationFromString(params.activation)};
325
326 owned_mode_ptr_ = std::make_unique<BehaviorOwnedMode>(*getNodePtr(), px4_ros2::ModeBase::Settings{params.mode_name});
327
328 BehaviorSpec initial_spec;
329 initial_spec.build_request = params.behavior.build_request;
330 initial_spec.build_handler =
331 current_build_handler_name_; // Handler loaded by the base from the 'build_handler' param.
332 initial_spec.entry_point = params.behavior.entry_point;
333 initial_spec.node_manifest = params.behavior.node_manifest;
334
335 mode_executor_ptr_ = std::make_unique<BehaviorModeExecutor>(
336 *owned_mode_ptr_, settings, std::move(config_provider), std::move(initial_spec), *this);
337
338 // Build the initial behavior tree up front so the expensive tree construction (instantiating the behavior tree
339 // nodes' ROS 2 waitables) happens here instead of on the activation path. If building fails, the exception
340 // propagates and the mode is never registered with the FMU.
341 mode_executor_ptr_->prepareTree();
342
343 // Wait for the FMU and register the executor together with its owned mode
344 registration_handler_.registerMode(*mode_executor_ptr_, params.mode_name);
345
346 // Interfaces for changing the behavior at runtime. Both verify a candidate behavior (by building its tree) before
347 // latching it; the action additionally triggers execution by requesting activation of the owned mode.
348 set_behavior_service_ptr_ = getNodePtr()->create_service<SetBehaviorSrv>(
349 "~/set_behavior",
350 std::bind(&BehaviorModeExecutorNode::handleSetBehavior, this, std::placeholders::_1, std::placeholders::_2));
351
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));
357}
358
359bool BehaviorModeExecutorNode::applyBehavior(BehaviorSpec candidate, std::string & message)
360{
361 // Apply the requested build handler (mirroring the standard executor's goal handling), remembering the previous one
362 // so it can be restored if the subsequent build fails.
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_) {
366 if (!getExecutorParameters().allow_other_build_handlers) {
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());
372 return false;
373 }
374 try {
375 loadBuildHandler(candidate.build_handler);
376 switched = true;
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());
380 return false;
381 }
382 }
383
384 // Record the effective handler so the latched spec reflects reality even when the request kept the current one.
385 candidate.build_handler = current_build_handler_name_;
386
387 if (mode_executor_ptr_->setBehavior(candidate, message)) return true;
388
389 // Verification failed: undo the handler switch so executor state is left untouched.
390 if (switched) {
391 try {
392 loadBuildHandler(previous_handler);
393 } catch (const std::exception & e) {
394 RCLCPP_ERROR(logger_, "Failed to restore previous build handler '%s': %s", previous_handler.c_str(), e.what());
395 }
396 }
397 return false;
398}
399
400void BehaviorModeExecutorNode::handleSetBehavior(
401 const std::shared_ptr<SetBehaviorSrv::Request> request, std::shared_ptr<SetBehaviorSrv::Response> response)
402{
403 // Changing the behavior rebuilds the prepared tree, which must not race a running behavior (the prepared tree is
404 // moved into the executor while it runs and rebuilt on termination).
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());
409 return;
410 }
411
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;
417
418 std::string message;
419 response->success = applyBehavior(candidate, message);
420 response->message = message;
421}
422
423rclcpp_action::GoalResponse BehaviorModeExecutorNode::handleStartGoal(
424 const rclcpp_action::GoalUUID & uuid, std::shared_ptr<const StartAction::Goal> goal_ptr)
425{
426 if (isBusy() || mode_executor_ptr_->isInCharge()) {
427 RCLCPP_WARN(
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;
431 }
432
433 // Apply the requested build handler (if any) and verify+latch the behavior; reject the goal if it does not build.
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;
439
440 std::string message;
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;
444 }
445
446 pending_attach_[uuid] = goal_ptr->attach;
447 pending_clear_blackboard_[uuid] = goal_ptr->clear_blackboard;
448 return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
449}
450
451rclcpp_action::CancelResponse BehaviorModeExecutorNode::handleStartCancel(
452 std::shared_ptr<StartGoalHandle> /*goal_handle_ptr*/)
453{
455 return rclcpp_action::CancelResponse::ACCEPT;
456}
457
458void BehaviorModeExecutorNode::handleStartAccept(std::shared_ptr<StartGoalHandle> goal_handle_ptr)
459{
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();
463
464 if (clear_blackboard) clearGlobalBlackboard();
465
466 // Request activation of the owned mode and wait until it is confirmed active (the executor is put in charge and the
467 // prepared tree starts running). This uses the client's own wait set, so it does not depend on this node being spun
468 // by an external executor.
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);
475 return;
476 }
477
478 if (!attach) {
479 // Detached: succeed as soon as activation is verified; the behavior keeps running under the mode executor.
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);
483 return;
484 }
485
486 // Attached: keep the goal open and resolve it in onTermination with the tree result.
487 start_action_context_.setUp(goal_handle_ptr);
488 RCLCPP_INFO(logger_, "Owned mode activated; tracking behavior execution for attached goal.");
489}
490
491void BehaviorModeExecutorNode::onTermination(const ExecutionResult & result)
492{
493 mode_executor_ptr_->onExecutionResult(result);
494
495 // Resolve an attached StartTreeExecutor goal, if one is open. Behaviors started from a GCS/RC (no open goal) leave
496 // the context invalid and are unaffected.
497 if (!start_action_context_.isValid()) return;
498
499 const std::shared_ptr<StartAction::Result> result_ptr = start_action_context_.getResultPtr();
500 result_ptr->terminated_tree_identity = getTreeName();
501 switch (result) {
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();
506 break;
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();
511 break;
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();
517 } else {
518 result_ptr->message = "Tree execution terminated prematurely";
519 start_action_context_.abort();
520 }
521 break;
523 default:
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();
527 break;
528 }
529 start_action_context_.invalidate();
530}
531
532} // namespace auto_apms_px4
533
534#include "rclcpp_components/register_node_macro.hpp"
535RCLCPP_COMPONENTS_REGISTER_NODE(auto_apms_px4::BehaviorModeExecutorNode)
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.
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.
@ COMPLETE
Report the owned mode as completed to the FMU (relinquish 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).