19#include "auto_apms_px4/mode.hpp"
20#include "auto_apms_px4/vehicle_command_client.hpp"
21#include "auto_apms_util/action_wrapper.hpp"
22#include "px4_msgs/msg/mode_completed.hpp"
23#include "px4_msgs/msg/vehicle_status.hpp"
24#include "px4_ros2/components/wait_for_fmu.hpp"
25#include "px4_ros2/utils/message_version.hpp"
95template <
class ActionT>
98 enum class State : uint8_t
102 WAIT_FOR_COMPLETION_SIGNAL,
108 using FlightMode = VehicleCommandClient::FlightMode;
109 using typename auto_apms_util::ActionWrapper<ActionT>::ActionContextType;
110 using typename auto_apms_util::ActionWrapper<ActionT>::Goal;
111 using typename auto_apms_util::ActionWrapper<ActionT>::Feedback;
112 using typename auto_apms_util::ActionWrapper<ActionT>::Result;
115 explicit ModeExecutor(
116 const std::string & action_name, rclcpp::Node::SharedPtr node_ptr,
117 std::shared_ptr<ActionContextType> action_context_ptr, uint8_t mode_id,
118 FlightMode deactivation_flight_mode = FlightMode::Hold,
bool disarm_after_completion =
false);
119 explicit ModeExecutor(
120 const std::string & action_name,
const rclcpp::NodeOptions & options, uint8_t mode_id,
121 FlightMode deactivation_flight_mode = FlightMode::Hold,
bool disarm_after_completion =
false);
122 explicit ModeExecutor(
123 const std::string & action_name,
const rclcpp::NodeOptions & options, FlightMode flight_mode,
124 FlightMode deactivation_flight_mode = FlightMode::Hold,
bool disarm_after_completion =
false);
126 static bool isExternalMode(uint8_t mode_id);
131 bool onGoalRequest(std::shared_ptr<const Goal> goal_ptr)
override final;
132 bool onCancelRequest(std::shared_ptr<const Goal> goal_ptr, std::shared_ptr<Result> result_ptr)
override final;
134 std::shared_ptr<const Goal> goal_ptr, std::shared_ptr<Result> result_ptr)
override final;
136 std::shared_ptr<const Goal> goal_ptr, std::shared_ptr<Feedback> feedback_ptr,
137 std::shared_ptr<Result> result_ptr)
override final;
140 bool isCurrentNavState(uint8_t nav_state);
141 virtual bool sendActivationCommand(
const VehicleCommandClient & client, std::shared_ptr<const Goal> goal_ptr);
142 virtual bool isCompleted(std::shared_ptr<const Goal> goal_ptr,
const px4_msgs::msg::VehicleStatus & vehicle_status);
143 virtual void setFeedback(std::shared_ptr<Feedback> feedback_ptr,
const px4_msgs::msg::VehicleStatus & vehicle_status);
145 uint8_t getModeID()
const;
148 const VehicleCommandClient vehicle_command_client_;
149 const uint8_t mode_id_;
150 bool is_custom_mode_{
false};
151 FlightMode deactivation_flight_mode_;
152 bool disarm_after_completion_;
153 rclcpp::Subscription<px4_msgs::msg::VehicleStatus>::SharedPtr vehicle_status_sub_ptr_;
154 rclcpp::Subscription<px4_msgs::msg::ModeCompleted>::SharedPtr mode_completed_sub_ptr_;
155 px4_msgs::msg::VehicleStatus::SharedPtr last_vehicle_status_ptr_;
156 std::optional<uint8_t> mode_completed_result_;
157 bool deactivation_command_sent_{
false};
158 State state_{State::REQUEST_ACTIVATION};
159 rclcpp::Time activation_command_sent_time_;
160 rclcpp::Duration activation_timeout_{0, 0};
170template <
class ActionT,
class ModeT>
171class ModeExecutorFactory
175 const std::string & action_name,
const rclcpp::NodeOptions & options,
176 VehicleCommandClient::FlightMode deactivation_flight_mode = VehicleCommandClient::FlightMode::Hold,
177 bool disarm_after_completion =
false);
179 rclcpp::node_interfaces::NodeBaseInterface::SharedPtr get_node_base_interface();
182 rclcpp::Node::SharedPtr node_ptr_;
183 std::unique_ptr<ModeBase<ActionT>> mode_ptr_;
184 std::shared_ptr<ModeExecutor<ActionT>> mode_executor_ptr_;
191template <
class ActionT>
192ModeExecutor<ActionT>::ModeExecutor(
193 const std::string & action_name, rclcpp::Node::SharedPtr node_ptr,
194 std::shared_ptr<ActionContextType> action_context_ptr, uint8_t mode_id, FlightMode deactivation_flight_mode,
195 bool disarm_after_completion)
196:
auto_apms_util::ActionWrapper<ActionT>(action_name, node_ptr, action_context_ptr),
197 vehicle_command_client_(*node_ptr),
199 deactivation_flight_mode_(deactivation_flight_mode),
200 disarm_after_completion_(disarm_after_completion)
205template <
class ActionT>
206ModeExecutor<ActionT>::ModeExecutor(
207 const std::string & action_name,
const rclcpp::NodeOptions & options, uint8_t mode_id,
208 FlightMode deactivation_flight_mode,
bool disarm_after_completion)
210 vehicle_command_client_(*this->node_ptr_),
212 is_custom_mode_(isExternalMode(mode_id_)),
213 deactivation_flight_mode_(deactivation_flight_mode),
214 disarm_after_completion_(disarm_after_completion)
219template <
class ActionT>
220ModeExecutor<ActionT>::ModeExecutor(
221 const std::string & action_name,
const rclcpp::NodeOptions & options, FlightMode flight_mode,
222 FlightMode deactivation_flight_mode,
bool disarm_after_completion)
224 action_name, options, static_cast<uint8_t>(flight_mode), deactivation_flight_mode, disarm_after_completion)
228template <
class ActionT>
229inline bool ModeExecutor<ActionT>::isExternalMode(uint8_t mode_id)
231 const uint8_t first_external_mode_nav_state = px4_msgs::msg::VehicleStatus::NAVIGATION_STATE_EXTERNAL1;
232 const uint8_t max_external_mode_nav_state = px4_msgs::msg::VehicleStatus::NAVIGATION_STATE_EXTERNAL8 + 1;
233 return mode_id >= first_external_mode_nav_state && mode_id < max_external_mode_nav_state;
236template <
class ActionT>
237void ModeExecutor<ActionT>::setUp()
239 vehicle_status_sub_ptr_ = this->node_ptr_->template create_subscription<px4_msgs::msg::VehicleStatus>(
240 "fmu/out/vehicle_status" + px4_ros2::getMessageNameVersion<px4_msgs::msg::VehicleStatus>(),
241 rclcpp::QoS(1).best_effort(),
242 [
this](px4_msgs::msg::VehicleStatus::UniquePtr msg) { last_vehicle_status_ptr_ = std::move(msg); });
244 mode_completed_sub_ptr_ = this->node_ptr_->template create_subscription<px4_msgs::msg::ModeCompleted>(
245 "fmu/out/mode_completed" + px4_ros2::getMessageNameVersion<px4_msgs::msg::ModeCompleted>(),
246 rclcpp::QoS(1).best_effort(), [
this](px4_msgs::msg::ModeCompleted::UniquePtr msg) {
247 bool is_relevant = msg->nav_state == this->mode_id_;
249 this->node_ptr_->get_logger(),
250 "Flight mode completion signal received by mode %i. Signal was: timestamp=%li, mode_id=%i, result=%i (is "
251 "relevant for this mode: %i)",
252 this->mode_id_, msg->timestamp, msg->nav_state, msg->result, is_relevant);
254 this->mode_completed_result_ = msg->result;
259template <
class ActionT>
265 bool is_deactivation_mode_active = isCurrentNavState(
static_cast<uint8_t
>(deactivation_flight_mode_));
266 if (state_ == State::WAIT_FOR_ACTIVATION) {
267 if (is_deactivation_mode_active) {
268 auto & clock = *this->node_ptr_->get_clock();
269 RCLCPP_DEBUG_THROTTLE(
270 this->node_ptr_->get_logger(), clock, 250,
"Waiting for flight mode %i to become active before deactivating...",
272 return ActionStatus::RUNNING;
274 state_ = State::COMPLETE;
278 if (is_deactivation_mode_active) {
280 this->node_ptr_->get_logger(),
"Deactivated flight mode successfully (deactivation mode %i is active)",
281 static_cast<int>(deactivation_flight_mode_));
282 return ActionStatus::SUCCESS;
285 if (!deactivation_command_sent_) {
286 if (!vehicle_command_client_.syncActivateFlightMode(deactivation_flight_mode_)) {
288 this->node_ptr_->get_logger(),
"Failed to send command to activate deactivation flight mode %i",
289 static_cast<int>(deactivation_flight_mode_));
290 return ActionStatus::FAILURE;
293 last_vehicle_status_ptr_ =
nullptr;
294 deactivation_command_sent_ =
true;
298 return ActionStatus::RUNNING;
301template <
class ActionT>
302bool ModeExecutor<ActionT>::onGoalRequest(
const std::shared_ptr<const Goal> )
304 state_ = State::REQUEST_ACTIVATION;
305 deactivation_command_sent_ =
false;
306 mode_completed_result_ = std::nullopt;
307 activation_timeout_ = rclcpp::Duration::from_seconds(fmin(this->param_listener_.get_params().loop_rate * 15, 1.5));
311template <
class ActionT>
312bool ModeExecutor<ActionT>::onCancelRequest(
313 std::shared_ptr<const Goal> , std::shared_ptr<Result> )
315 RCLCPP_INFO(this->node_ptr_->get_logger(),
"Cancellation requested!");
319template <
class ActionT>
321 std::shared_ptr<const Goal> , std::shared_ptr<Result> )
327 if (is_custom_mode_ && state_ != State::COMPLETE) {
330 if (mode_completed_result_.has_value()) {
331 state_ = State::COMPLETE;
333 if (!isCurrentNavState(mode_id_)) {
335 this->node_ptr_->get_logger(),
"Flight mode %i was deactivated externally during cancellation", mode_id_);
337 return ActionStatus::SUCCESS;
339 return ActionStatus::RUNNING;
344 if (deactivation_flight_mode_ != FlightMode::Unset) {
345 ret = asyncDeactivateFlightMode();
348 if (ret != ActionStatus::RUNNING) {
349 if (disarm_after_completion_ && !vehicle_command_client_.disarm()) {
350 RCLCPP_WARN(this->node_ptr_->get_logger(),
"Failed to disarm after flight mode %i cancellation", mode_id_);
352 RCLCPP_INFO(this->node_ptr_->get_logger(),
"Flight mode %i cancellation complete", mode_id_);
357template <
class ActionT>
358bool ModeExecutor<ActionT>::isCurrentNavState(uint8_t nav_state)
360 if (last_vehicle_status_ptr_ && last_vehicle_status_ptr_->nav_state == nav_state) {
366template <
class ActionT>
368 std::shared_ptr<const Goal> goal_ptr, std::shared_ptr<Feedback> feedback_ptr, std::shared_ptr<Result> )
371 case State::REQUEST_ACTIVATION:
372 if (!sendActivationCommand(vehicle_command_client_, goal_ptr)) {
374 this->node_ptr_->get_logger(),
"Failed to send activation command for flight mode %i. Aborting...", mode_id_);
375 return ActionStatus::FAILURE;
378 last_vehicle_status_ptr_ =
nullptr;
379 state_ = State::WAIT_FOR_ACTIVATION;
380 activation_command_sent_time_ = this->node_ptr_->now();
382 this->node_ptr_->get_logger(),
"Activation command for flight mode %i was sent successfully", mode_id_);
383 return ActionStatus::RUNNING;
384 case State::WAIT_FOR_ACTIVATION:
385 if (isCurrentNavState(mode_id_)) {
386 RCLCPP_DEBUG(this->node_ptr_->get_logger(),
"Flight mode %i is active", mode_id_);
387 state_ = State::WAIT_FOR_COMPLETION_SIGNAL;
388 }
else if (this->node_ptr_->now() - activation_command_sent_time_ > activation_timeout_) {
389 RCLCPP_ERROR(this->node_ptr_->get_logger(),
"Timeout activating flight mode %i. Aborting...", mode_id_);
390 return ActionStatus::FAILURE;
392 return ActionStatus::RUNNING;
393 case State::WAIT_FOR_COMPLETION_SIGNAL:
395 setFeedback(feedback_ptr, *last_vehicle_status_ptr_);
398 if (isCompleted(goal_ptr, *last_vehicle_status_ptr_)) {
399 state_ = State::COMPLETE;
400 if (deactivation_flight_mode_ != FlightMode::Unset) {
402 this->node_ptr_->get_logger(),
403 "Flight mode %i complete! Will deactivate before termination (switching to flight mode %i)...", mode_id_,
404 static_cast<int>(deactivation_flight_mode_));
407 this->node_ptr_->get_logger(),
408 "Flight mode %i complete! Will leave current navigation state as is. User is "
409 "responsible for initiating the next flight mode...",
417 if (!isCurrentNavState(mode_id_)) {
418 RCLCPP_WARN(this->node_ptr_->get_logger(),
"Flight mode %i was deactivated externally. Aborting...", mode_id_);
419 return ActionStatus::FAILURE;
421 return ActionStatus::RUNNING;
422 case State::COMPLETE:
426 if (deactivation_flight_mode_ != FlightMode::Unset) {
427 const auto deactivation_state = asyncDeactivateFlightMode();
428 if (deactivation_state != ActionStatus::SUCCESS) {
429 return deactivation_state;
434 RCLCPP_INFO(this->node_ptr_->get_logger(),
"Flight mode %i execution complete", mode_id_);
435 if (disarm_after_completion_ && !vehicle_command_client_.disarm()) {
436 RCLCPP_WARN(this->node_ptr_->get_logger(),
"Failed to disarm after flight mode %i completion", mode_id_);
438 return ActionStatus::SUCCESS;
441template <
class ActionT>
442bool ModeExecutor<ActionT>::sendActivationCommand(
445 return client.syncActivateFlightMode(mode_id_);
448template <
class ActionT>
449bool ModeExecutor<ActionT>::isCompleted(
450 std::shared_ptr<const Goal> ,
const px4_msgs::msg::VehicleStatus & )
452 if (!mode_completed_result_.has_value()) {
455 if (*mode_completed_result_ == px4_msgs::msg::ModeCompleted::RESULT_SUCCESS) {
459 this->node_ptr_->get_logger(),
"Flight mode %i completed unsuccessfully (result: %i). Aborting...", mode_id_,
460 static_cast<int>(*mode_completed_result_));
461 this->action_context_ptr_->abort();
466template <
class ActionT>
467void ModeExecutor<ActionT>::setFeedback(
468 std::shared_ptr<Feedback> ,
const px4_msgs::msg::VehicleStatus & )
473template <
class ActionT>
474uint8_t ModeExecutor<ActionT>::getModeID()
const
479template <
class ActionT,
class ModeT>
480ModeExecutorFactory<ActionT, ModeT>::ModeExecutorFactory(
481 const std::string & action_name,
const rclcpp::NodeOptions & options,
482 VehicleCommandClient::FlightMode deactivation_flight_mode,
bool disarm_after_completion)
483: node_ptr_(std::make_shared<rclcpp::Node>(action_name +
"_node", options))
486 std::is_base_of<ModeBase<ActionT>, ModeT>::value,
487 "Template argument ModeT must inherit auto_apms::ModeBase<ActionT> as public and with same type "
488 "ActionT as auto_apms::ActionWrapper<ActionT>");
490 const auto action_context_ptr = std::make_shared<auto_apms_util::ActionContext<ActionT>>(node_ptr_->get_logger());
492 mode_ptr_ = std::make_unique<ModeT>(*node_ptr_, px4_ros2::ModeBase::Settings(action_name), action_context_ptr);
494 constexpr int max_retries = 5;
495 bool fmu_available =
false;
496 for (
int attempt = 0; attempt < max_retries; ++attempt) {
497 if (px4_ros2::waitForFMU(*node_ptr_, std::chrono::seconds(3))) {
498 fmu_available =
true;
499 RCLCPP_DEBUG(node_ptr_->get_logger(),
"FMU availability test successful (attempt %d).", attempt + 1);
502 RCLCPP_WARN(node_ptr_->get_logger(),
"No message from FMU (attempt %d/%d). Retrying...", attempt + 1, max_retries);
504 if (!fmu_available) {
505 throw std::runtime_error(
"No message from FMU after multiple attempts");
508 if (!mode_ptr_->doRegister()) {
509 RCLCPP_FATAL(node_ptr_->get_logger(),
"Registration of mode with action_name: '%s' failed.", action_name.c_str());
510 throw std::runtime_error(
"Mode registration failed");
514 mode_executor_ptr_ = std::make_shared<ModeExecutor<ActionT>>(
515 action_name, node_ptr_, action_context_ptr, mode_ptr_->id(), deactivation_flight_mode, disarm_after_completion);
518template <
class ActionT,
class ModeT>
519rclcpp::node_interfaces::NodeBaseInterface::SharedPtr ModeExecutorFactory<ActionT, ModeT>::get_node_base_interface()
521 return node_ptr_->get_node_base_interface();
Generic template class for executing a PX4 mode implementing the interface of a standard ROS 2 action...
Client for sending requests to the PX4 autopilot using the VehicleCommand topic.
Generic base class for implementing robot skills using the ROS 2 action concept.
ActionStatus
Status of the auto_apms_util::ActionWrapper execution process.
Implementation of PX4 mode peers offered by px4_ros2_cpp enabling integration with AutoAPMS.
Fundamental helper classes and utility functions.