19#include "auto_apms_px4/mode.hpp"
20#include "auto_apms_px4/mode_registration.hpp"
21#include "auto_apms_px4/vehicle_command_client.hpp"
22#include "auto_apms_util/action_wrapper.hpp"
23#include "px4_msgs/msg/mode_completed.hpp"
24#include "px4_msgs/msg/vehicle_status.hpp"
25#include "px4_ros2/components/wait_for_fmu.hpp"
26#include "px4_ros2/utils/message_version.hpp"
97template <
class ActionT>
100 enum class State : uint8_t
104 WAIT_FOR_COMPLETION_SIGNAL,
110 using FlightMode = VehicleCommandClient::FlightMode;
111 using typename auto_apms_util::ActionWrapper<ActionT>::ActionContextType;
112 using typename auto_apms_util::ActionWrapper<ActionT>::Goal;
113 using typename auto_apms_util::ActionWrapper<ActionT>::Feedback;
114 using typename auto_apms_util::ActionWrapper<ActionT>::Result;
117 explicit ModeProxyAction(
118 const std::string & action_name, rclcpp::Node::SharedPtr node_ptr,
119 std::shared_ptr<ActionContextType> action_context_ptr, uint8_t mode_id,
120 FlightMode deactivation_flight_mode = FlightMode::Hold,
bool disarm_after_completion =
false);
121 explicit ModeProxyAction(
122 const std::string & action_name,
const rclcpp::NodeOptions & options, uint8_t mode_id,
123 FlightMode deactivation_flight_mode = FlightMode::Hold,
bool disarm_after_completion =
false);
124 explicit ModeProxyAction(
125 const std::string & action_name,
const rclcpp::NodeOptions & options, FlightMode flight_mode,
126 FlightMode deactivation_flight_mode = FlightMode::Hold,
bool disarm_after_completion =
false);
128 static bool isExternalMode(uint8_t mode_id);
133 bool onGoalRequest(std::shared_ptr<const Goal> goal_ptr)
override final;
134 bool onCancelRequest(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<Result> result_ptr)
override final;
138 std::shared_ptr<const Goal> goal_ptr, std::shared_ptr<Feedback> feedback_ptr,
139 std::shared_ptr<Result> result_ptr)
override final;
142 bool isCurrentNavState(uint8_t nav_state);
143 virtual bool sendActivationCommand(
const VehicleCommandClient & client, std::shared_ptr<const Goal> goal_ptr);
144 virtual bool isCompleted(std::shared_ptr<const Goal> goal_ptr,
const px4_msgs::msg::VehicleStatus & vehicle_status);
145 virtual void setFeedback(std::shared_ptr<Feedback> feedback_ptr,
const px4_msgs::msg::VehicleStatus & vehicle_status);
147 uint8_t getModeID()
const;
150 const VehicleCommandClient vehicle_command_client_;
151 const uint8_t mode_id_;
152 bool is_custom_mode_{
false};
153 FlightMode deactivation_flight_mode_;
154 bool disarm_after_completion_;
155 rclcpp::Subscription<px4_msgs::msg::VehicleStatus>::SharedPtr vehicle_status_sub_ptr_;
156 rclcpp::Subscription<px4_msgs::msg::ModeCompleted>::SharedPtr mode_completed_sub_ptr_;
157 px4_msgs::msg::VehicleStatus::SharedPtr last_vehicle_status_ptr_;
158 std::optional<uint8_t> mode_completed_result_;
159 bool deactivation_command_sent_{
false};
160 State state_{State::REQUEST_ACTIVATION};
161 rclcpp::Time activation_command_sent_time_;
162 rclcpp::Duration activation_timeout_{0, 0};
176template <
class ActionT,
class ModeT>
177class ModeProxyActionFactory
180 ModeProxyActionFactory(
181 const std::string & action_name,
const rclcpp::NodeOptions & options,
182 VehicleCommandClient::FlightMode deactivation_flight_mode = VehicleCommandClient::FlightMode::Hold,
183 bool disarm_after_completion =
false);
185 rclcpp::node_interfaces::NodeBaseInterface::SharedPtr get_node_base_interface();
188 rclcpp::Node::SharedPtr node_ptr_;
189 std::unique_ptr<ActionDrivenMode<ActionT>> mode_ptr_;
191 std::shared_ptr<ModeProxyAction<ActionT>> mode_proxy_action_ptr_;
198template <
class ActionT>
199ModeProxyAction<ActionT>::ModeProxyAction(
200 const std::string & action_name, rclcpp::Node::SharedPtr node_ptr,
201 std::shared_ptr<ActionContextType> action_context_ptr, uint8_t mode_id, FlightMode deactivation_flight_mode,
202 bool disarm_after_completion)
203:
auto_apms_util::ActionWrapper<ActionT>(action_name, node_ptr, action_context_ptr),
204 vehicle_command_client_(*node_ptr),
206 deactivation_flight_mode_(deactivation_flight_mode),
207 disarm_after_completion_(disarm_after_completion)
212template <
class ActionT>
213ModeProxyAction<ActionT>::ModeProxyAction(
214 const std::string & action_name,
const rclcpp::NodeOptions & options, uint8_t mode_id,
215 FlightMode deactivation_flight_mode,
bool disarm_after_completion)
217 vehicle_command_client_(*this->node_ptr_),
219 is_custom_mode_(isExternalMode(mode_id_)),
220 deactivation_flight_mode_(deactivation_flight_mode),
221 disarm_after_completion_(disarm_after_completion)
226template <
class ActionT>
227ModeProxyAction<ActionT>::ModeProxyAction(
228 const std::string & action_name,
const rclcpp::NodeOptions & options, FlightMode flight_mode,
229 FlightMode deactivation_flight_mode,
bool disarm_after_completion)
231 action_name, options, static_cast<uint8_t>(flight_mode), deactivation_flight_mode, disarm_after_completion)
235template <
class ActionT>
236inline bool ModeProxyAction<ActionT>::isExternalMode(uint8_t mode_id)
238 const uint8_t first_external_mode_nav_state = px4_msgs::msg::VehicleStatus::NAVIGATION_STATE_EXTERNAL1;
239 const uint8_t max_external_mode_nav_state = px4_msgs::msg::VehicleStatus::NAVIGATION_STATE_EXTERNAL8 + 1;
240 return mode_id >= first_external_mode_nav_state && mode_id < max_external_mode_nav_state;
243template <
class ActionT>
244void ModeProxyAction<ActionT>::setUp()
246 vehicle_status_sub_ptr_ = this->node_ptr_->template create_subscription<px4_msgs::msg::VehicleStatus>(
247 "fmu/out/vehicle_status" + px4_ros2::getMessageNameVersion<px4_msgs::msg::VehicleStatus>(),
248 rclcpp::QoS(1).best_effort(),
249 [
this](px4_msgs::msg::VehicleStatus::UniquePtr msg) { last_vehicle_status_ptr_ = std::move(msg); });
251 mode_completed_sub_ptr_ = this->node_ptr_->template create_subscription<px4_msgs::msg::ModeCompleted>(
252 "fmu/out/mode_completed" + px4_ros2::getMessageNameVersion<px4_msgs::msg::ModeCompleted>(),
253 rclcpp::QoS(1).best_effort(), [
this](px4_msgs::msg::ModeCompleted::UniquePtr msg) {
254 bool is_relevant = msg->nav_state == this->mode_id_;
256 this->node_ptr_->get_logger(),
257 "Flight mode completion signal received by mode %i. Signal was: timestamp=%li, mode_id=%i, result=%i (is "
258 "relevant for this mode: %i)",
259 this->mode_id_, msg->timestamp, msg->nav_state, msg->result, is_relevant);
261 this->mode_completed_result_ = msg->result;
266template <
class ActionT>
272 bool is_deactivation_mode_active = isCurrentNavState(
static_cast<uint8_t
>(deactivation_flight_mode_));
273 if (state_ == State::WAIT_FOR_ACTIVATION) {
274 if (is_deactivation_mode_active) {
275 auto & clock = *this->node_ptr_->get_clock();
276 RCLCPP_DEBUG_THROTTLE(
277 this->node_ptr_->get_logger(), clock, 250,
"Waiting for flight mode %i to become active before deactivating...",
279 return ActionStatus::RUNNING;
281 state_ = State::COMPLETE;
285 if (is_deactivation_mode_active) {
287 this->node_ptr_->get_logger(),
"Deactivated flight mode successfully (deactivation mode %i is active)",
288 static_cast<int>(deactivation_flight_mode_));
289 return ActionStatus::SUCCESS;
292 if (!deactivation_command_sent_) {
293 if (!vehicle_command_client_.syncActivateFlightMode(deactivation_flight_mode_)) {
295 this->node_ptr_->get_logger(),
"Failed to send command to activate deactivation flight mode %i",
296 static_cast<int>(deactivation_flight_mode_));
297 return ActionStatus::FAILURE;
300 last_vehicle_status_ptr_ =
nullptr;
301 deactivation_command_sent_ =
true;
305 return ActionStatus::RUNNING;
308template <
class ActionT>
309bool ModeProxyAction<ActionT>::onGoalRequest(
const std::shared_ptr<const Goal> )
311 state_ = State::REQUEST_ACTIVATION;
312 deactivation_command_sent_ =
false;
313 mode_completed_result_ = std::nullopt;
314 activation_timeout_ = rclcpp::Duration::from_seconds(fmin(this->param_listener_.get_params().loop_rate * 15, 1.5));
318template <
class ActionT>
319bool ModeProxyAction<ActionT>::onCancelRequest(
320 std::shared_ptr<const Goal> , std::shared_ptr<Result> )
322 RCLCPP_INFO(this->node_ptr_->get_logger(),
"Cancellation requested!");
326template <
class ActionT>
328 std::shared_ptr<const Goal> , std::shared_ptr<Result> )
334 if (is_custom_mode_ && state_ != State::COMPLETE) {
337 if (mode_completed_result_.has_value()) {
338 state_ = State::COMPLETE;
340 if (!isCurrentNavState(mode_id_)) {
342 this->node_ptr_->get_logger(),
"Flight mode %i was deactivated externally during cancellation", mode_id_);
344 return ActionStatus::SUCCESS;
346 return ActionStatus::RUNNING;
351 if (deactivation_flight_mode_ != FlightMode::Unset) {
352 ret = asyncDeactivateFlightMode();
355 if (ret != ActionStatus::RUNNING) {
356 if (disarm_after_completion_ && !vehicle_command_client_.disarm()) {
357 RCLCPP_WARN(this->node_ptr_->get_logger(),
"Failed to disarm after flight mode %i cancellation", mode_id_);
359 RCLCPP_INFO(this->node_ptr_->get_logger(),
"Flight mode %i cancellation complete", mode_id_);
364template <
class ActionT>
365bool ModeProxyAction<ActionT>::isCurrentNavState(uint8_t nav_state)
367 if (last_vehicle_status_ptr_ && last_vehicle_status_ptr_->nav_state == nav_state) {
373template <
class ActionT>
375 std::shared_ptr<const Goal> goal_ptr, std::shared_ptr<Feedback> feedback_ptr, std::shared_ptr<Result> )
378 case State::REQUEST_ACTIVATION:
379 if (!sendActivationCommand(vehicle_command_client_, goal_ptr)) {
381 this->node_ptr_->get_logger(),
"Failed to send activation command for flight mode %i. Aborting...", mode_id_);
382 return ActionStatus::FAILURE;
385 last_vehicle_status_ptr_ =
nullptr;
386 state_ = State::WAIT_FOR_ACTIVATION;
387 activation_command_sent_time_ = this->node_ptr_->now();
389 this->node_ptr_->get_logger(),
"Activation command for flight mode %i was sent successfully", mode_id_);
390 return ActionStatus::RUNNING;
391 case State::WAIT_FOR_ACTIVATION:
392 if (isCurrentNavState(mode_id_)) {
393 RCLCPP_DEBUG(this->node_ptr_->get_logger(),
"Flight mode %i is active", mode_id_);
394 state_ = State::WAIT_FOR_COMPLETION_SIGNAL;
395 }
else if (this->node_ptr_->now() - activation_command_sent_time_ > activation_timeout_) {
396 RCLCPP_ERROR(this->node_ptr_->get_logger(),
"Timeout activating flight mode %i. Aborting...", mode_id_);
397 return ActionStatus::FAILURE;
399 return ActionStatus::RUNNING;
400 case State::WAIT_FOR_COMPLETION_SIGNAL:
402 setFeedback(feedback_ptr, *last_vehicle_status_ptr_);
405 if (isCompleted(goal_ptr, *last_vehicle_status_ptr_)) {
406 state_ = State::COMPLETE;
407 if (deactivation_flight_mode_ != FlightMode::Unset) {
409 this->node_ptr_->get_logger(),
410 "Flight mode %i complete! Will deactivate before termination (switching to flight mode %i)...", mode_id_,
411 static_cast<int>(deactivation_flight_mode_));
414 this->node_ptr_->get_logger(),
415 "Flight mode %i complete! Will leave current navigation state as is. User is "
416 "responsible for initiating the next flight mode...",
424 if (!isCurrentNavState(mode_id_)) {
425 RCLCPP_WARN(this->node_ptr_->get_logger(),
"Flight mode %i was deactivated externally. Aborting...", mode_id_);
426 return ActionStatus::FAILURE;
428 return ActionStatus::RUNNING;
429 case State::COMPLETE:
433 if (deactivation_flight_mode_ != FlightMode::Unset) {
434 const auto deactivation_state = asyncDeactivateFlightMode();
435 if (deactivation_state != ActionStatus::SUCCESS) {
436 return deactivation_state;
441 RCLCPP_INFO(this->node_ptr_->get_logger(),
"Flight mode %i execution complete", mode_id_);
442 if (disarm_after_completion_ && !vehicle_command_client_.disarm()) {
443 RCLCPP_WARN(this->node_ptr_->get_logger(),
"Failed to disarm after flight mode %i completion", mode_id_);
445 return ActionStatus::SUCCESS;
448template <
class ActionT>
449bool ModeProxyAction<ActionT>::sendActivationCommand(
452 return client.syncActivateFlightMode(mode_id_);
455template <
class ActionT>
456bool ModeProxyAction<ActionT>::isCompleted(
457 std::shared_ptr<const Goal> ,
const px4_msgs::msg::VehicleStatus & )
459 if (!mode_completed_result_.has_value()) {
462 if (*mode_completed_result_ == px4_msgs::msg::ModeCompleted::RESULT_SUCCESS) {
466 this->node_ptr_->get_logger(),
"Flight mode %i completed unsuccessfully (result: %i). Aborting...", mode_id_,
467 static_cast<int>(*mode_completed_result_));
468 this->action_context_ptr_->abort();
473template <
class ActionT>
474void ModeProxyAction<ActionT>::setFeedback(
475 std::shared_ptr<Feedback> ,
const px4_msgs::msg::VehicleStatus & )
480template <
class ActionT>
481uint8_t ModeProxyAction<ActionT>::getModeID()
const
486template <
class ActionT,
class ModeT>
487ModeProxyActionFactory<ActionT, ModeT>::ModeProxyActionFactory(
488 const std::string & action_name,
const rclcpp::NodeOptions & options,
489 VehicleCommandClient::FlightMode deactivation_flight_mode,
bool disarm_after_completion)
490: node_ptr_(std::make_shared<rclcpp::Node>(action_name +
"_node", options)), registration_handler_(node_ptr_)
493 std::is_base_of<ActionDrivenMode<ActionT>, ModeT>::value,
494 "Template argument ModeT must inherit auto_apms_px4::ActionDrivenMode<ActionT> as public and with same type "
495 "ActionT as auto_apms_util::ActionWrapper<ActionT>");
497 const auto action_context_ptr = std::make_shared<auto_apms_util::ActionContext<ActionT>>(node_ptr_->get_logger());
499 mode_ptr_ = std::make_unique<ModeT>(*node_ptr_, px4_ros2::ModeBase::Settings(action_name), action_context_ptr);
502 registration_handler_.registerMode(*mode_ptr_, action_name);
505 mode_proxy_action_ptr_ = std::make_shared<ModeProxyAction<ActionT>>(
506 action_name, node_ptr_, action_context_ptr, mode_ptr_->id(), deactivation_flight_mode, disarm_after_completion);
509template <
class ActionT,
class ModeT>
510rclcpp::node_interfaces::NodeBaseInterface::SharedPtr ModeProxyActionFactory<ActionT, ModeT>::get_node_base_interface()
512 return node_ptr_->get_node_base_interface();
Generic template class for executing a PX4 mode implementing the interface of a standard ROS 2 action...
Handles the registration of a PX4 mode with the FMU and announces its dynamically assigned nav_state.
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.