AutoAPMS
Streamlining behaviors in ROS 2
Loading...
Searching...
No Matches
mode_executor.hpp
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#pragma once
16
17#include "auto_apms_px4/mode.hpp"
18#include "auto_apms_px4/vehicle_command_client.hpp"
19#include "auto_apms_util/action_wrapper.hpp"
20#include "px4_msgs/msg/mode_completed.hpp"
21#include "px4_msgs/msg/vehicle_status.hpp"
22#include "px4_ros2/components/wait_for_fmu.hpp"
23#include "px4_ros2/utils/message_version.hpp"
24
38
44namespace auto_apms_px4
45{
46
93template <class ActionT>
94class ModeExecutor : public auto_apms_util::ActionWrapper<ActionT>
95{
96 enum class State : uint8_t
97 {
98 REQUEST_ACTIVATION,
99 WAIT_FOR_ACTIVATION,
100 WAIT_FOR_COMPLETION_SIGNAL,
101 COMPLETE
102 };
103
104public:
105 using VehicleCommandClient = auto_apms_px4::VehicleCommandClient;
106 using FlightMode = VehicleCommandClient::FlightMode;
107 using typename auto_apms_util::ActionWrapper<ActionT>::ActionContextType;
108 using typename auto_apms_util::ActionWrapper<ActionT>::Goal;
109 using typename auto_apms_util::ActionWrapper<ActionT>::Feedback;
110 using typename auto_apms_util::ActionWrapper<ActionT>::Result;
111 using ActionStatus = auto_apms_util::ActionStatus;
112
113 explicit ModeExecutor(
114 const std::string & action_name, rclcpp::Node::SharedPtr node_ptr,
115 std::shared_ptr<ActionContextType> action_context_ptr, uint8_t mode_id, bool deactivate_before_completion = true);
116 explicit ModeExecutor(
117 const std::string & action_name, const rclcpp::NodeOptions & options, uint8_t mode_id,
118 bool deactivate_before_completion = true);
119 explicit ModeExecutor(
120 const std::string & action_name, const rclcpp::NodeOptions & options, FlightMode flight_mode,
121 bool deactivate_before_completion = true);
122
123private:
124 void setUp();
125 auto_apms_util::ActionStatus asyncDeactivateFlightMode();
126 bool onGoalRequest(std::shared_ptr<const Goal> goal_ptr) override final;
127 bool onCancelRequest(std::shared_ptr<const Goal> goal_ptr, std::shared_ptr<Result> result_ptr) override final;
129 std::shared_ptr<const Goal> goal_ptr, std::shared_ptr<Result> result_ptr) override final;
131 std::shared_ptr<const Goal> goal_ptr, std::shared_ptr<Feedback> feedback_ptr,
132 std::shared_ptr<Result> result_ptr) override final;
133
134protected:
135 bool isCurrentNavState(uint8_t nav_state);
136 virtual bool sendActivationCommand(const VehicleCommandClient & client, std::shared_ptr<const Goal> goal_ptr);
137 virtual bool isCompleted(std::shared_ptr<const Goal> goal_ptr, const px4_msgs::msg::VehicleStatus & vehicle_status);
138 virtual void setFeedback(std::shared_ptr<Feedback> feedback_ptr, const px4_msgs::msg::VehicleStatus & vehicle_status);
139
140 uint8_t getModeID() const;
141
142private:
143 const VehicleCommandClient vehicle_command_client_;
144 const uint8_t mode_id_;
145 bool deactivate_before_completion_;
146 rclcpp::Subscription<px4_msgs::msg::VehicleStatus>::SharedPtr vehicle_status_sub_ptr_;
147 rclcpp::Subscription<px4_msgs::msg::ModeCompleted>::SharedPtr mode_completed_sub_ptr_;
148 px4_msgs::msg::VehicleStatus::SharedPtr last_vehicle_status_ptr_;
149 bool mode_completed_{false};
150 bool deactivation_command_sent_{false};
151 State state_{State::REQUEST_ACTIVATION};
152 rclcpp::Time activation_command_sent_time_;
153 rclcpp::Duration activation_timeout_{0, 0};
154};
155
163template <class ActionT, class ModeT>
164class ModeExecutorFactory
165{
166public:
167 ModeExecutorFactory(
168 const std::string & action_name, const rclcpp::NodeOptions & options, bool deactivate_before_completion = true);
169
170 rclcpp::node_interfaces::NodeBaseInterface::SharedPtr get_node_base_interface();
171
172private:
173 rclcpp::Node::SharedPtr node_ptr_; // It's necessary to also store the node pointer here for successful destruction
174 std::unique_ptr<ModeBase<ActionT>> mode_ptr_;
175 std::shared_ptr<ModeExecutor<ActionT>> mode_executor_ptr_;
176};
177
178// #####################################################################################################################
179// ################################ DEFINITIONS ##############################################
180// #####################################################################################################################
181
182template <class ActionT>
183ModeExecutor<ActionT>::ModeExecutor(
184 const std::string & action_name, rclcpp::Node::SharedPtr node_ptr,
185 std::shared_ptr<ActionContextType> action_context_ptr, uint8_t mode_id, bool deactivate_before_completion)
186: auto_apms_util::ActionWrapper<ActionT>(action_name, node_ptr, action_context_ptr),
187 vehicle_command_client_(*node_ptr),
188 mode_id_(mode_id),
189 deactivate_before_completion_(deactivate_before_completion)
190{
191 setUp();
192}
193
194template <class ActionT>
195ModeExecutor<ActionT>::ModeExecutor(
196 const std::string & action_name, const rclcpp::NodeOptions & options, uint8_t mode_id,
197 bool deactivate_before_completion)
198: auto_apms_util::ActionWrapper<ActionT>(action_name, options),
199 vehicle_command_client_(*this->node_ptr_),
200 mode_id_(mode_id),
201 deactivate_before_completion_(deactivate_before_completion)
202{
203 setUp();
204}
205
206template <class ActionT>
207ModeExecutor<ActionT>::ModeExecutor(
208 const std::string & action_name, const rclcpp::NodeOptions & options, FlightMode flight_mode,
209 bool deactivate_before_completion)
210: ModeExecutor<ActionT>(action_name, options, static_cast<uint8_t>(flight_mode), deactivate_before_completion)
211{
212}
213
214template <class ActionT>
215void ModeExecutor<ActionT>::setUp()
216{
217 vehicle_status_sub_ptr_ = this->node_ptr_->template create_subscription<px4_msgs::msg::VehicleStatus>(
218 "fmu/out/vehicle_status" + px4_ros2::getMessageNameVersion<px4_msgs::msg::VehicleStatus>(),
219 rclcpp::QoS(1).best_effort(),
220 [this](px4_msgs::msg::VehicleStatus::UniquePtr msg) { last_vehicle_status_ptr_ = std::move(msg); });
221
222 mode_completed_sub_ptr_ = this->node_ptr_->template create_subscription<px4_msgs::msg::ModeCompleted>(
223 "fmu/out/mode_completed" + px4_ros2::getMessageNameVersion<px4_msgs::msg::ModeCompleted>(),
224 rclcpp::QoS(1).best_effort(), [this](px4_msgs::msg::ModeCompleted::UniquePtr msg) {
225 if (msg->nav_state == mode_id_) {
226 if (msg->result == px4_msgs::msg::ModeCompleted::RESULT_SUCCESS) {
227 this->mode_completed_ = true;
228 } else {
229 RCLCPP_ERROR(this->node_ptr_->get_logger(), "Flight mode %i failed to execute. Aborting...", this->mode_id_);
230 this->action_context_ptr_->abort();
231 }
232 return;
233 }
234 });
235}
236
237template <class ActionT>
238auto_apms_util::ActionStatus ModeExecutor<ActionT>::asyncDeactivateFlightMode()
239{
240 // If currently waiting for flight mode activation and HOLD is active we need to wait for the nav state to change
241 // before starting deactivation. Otherwise, we'll misinterpret the current nav state when in
242 // WAIT_FOR_HOLDING_STATE_REACHED and return success immediately
243 bool is_holding = isCurrentNavState(static_cast<uint8_t>(FlightMode::Hold));
244 if (state_ == State::WAIT_FOR_ACTIVATION) {
245 if (is_holding) {
246 auto & clock = *this->node_ptr_->get_clock();
247 RCLCPP_DEBUG_THROTTLE(
248 this->node_ptr_->get_logger(), clock, 250, "Waiting for flight mode %i to become active before deactivating...",
249 mode_id_);
250 return ActionStatus::RUNNING;
251 } else {
252 state_ = State::COMPLETE; // Change state to indicate that mode has been activated
253 }
254 }
255
256 if (is_holding) {
257 RCLCPP_DEBUG(this->node_ptr_->get_logger(), "Deactivated flight mode successfully (HOLD is active)");
258 return ActionStatus::SUCCESS;
259 } else {
260 // Only send command if not in HOLD already
261 if (!deactivation_command_sent_) {
262 if (!vehicle_command_client_.syncActivateFlightMode(FlightMode::Hold)) {
263 RCLCPP_ERROR(this->node_ptr_->get_logger(), "Failed to send command to activate HOLD");
264 return ActionStatus::FAILURE;
265 }
266 // Force to consider only new status messages after sending new command
267 last_vehicle_status_ptr_ = nullptr;
268 deactivation_command_sent_ = true;
269 }
270 }
271
272 return ActionStatus::RUNNING;
273}
274
275template <class ActionT>
276bool ModeExecutor<ActionT>::onGoalRequest(const std::shared_ptr<const Goal> /*goal_ptr*/)
277{
278 state_ = State::REQUEST_ACTIVATION;
279 deactivation_command_sent_ = false;
280 mode_completed_ = false;
281 activation_timeout_ = rclcpp::Duration::from_seconds(fmin(this->param_listener_.get_params().loop_rate * 15, 1.5));
282 return true;
283}
284
285template <class ActionT>
286bool ModeExecutor<ActionT>::onCancelRequest(
287 std::shared_ptr<const Goal> /*goal_ptr*/, std::shared_ptr<Result> /*result_ptr*/)
288{
289 if (deactivate_before_completion_) {
290 // To deactivate current flight mode, enable HOLD mode.
291 RCLCPP_DEBUG(
292 this->node_ptr_->get_logger(), "Cancellation requested! Will deactivate before termination (Enter HOLD)...");
293 } else {
294 RCLCPP_DEBUG(this->node_ptr_->get_logger(), "Cancellation requested!");
295 }
296 return true;
297}
298
299template <class ActionT>
300auto_apms_util::ActionStatus ModeExecutor<ActionT>::cancelGoal(
301 std::shared_ptr<const Goal> /*goal_ptr*/, std::shared_ptr<Result> /*result_ptr*/)
302{
303 if (deactivate_before_completion_) {
304 return asyncDeactivateFlightMode();
305 }
306 return ActionStatus::SUCCESS;
307}
308
309template <class ActionT>
310bool ModeExecutor<ActionT>::isCurrentNavState(uint8_t nav_state)
311{
312 if (last_vehicle_status_ptr_ && last_vehicle_status_ptr_->nav_state == nav_state) {
313 return true;
314 }
315 return false;
316}
317
318template <class ActionT>
319auto_apms_util::ActionStatus ModeExecutor<ActionT>::executeGoal(
320 std::shared_ptr<const Goal> goal_ptr, std::shared_ptr<Feedback> feedback_ptr, std::shared_ptr<Result> /*result_ptr*/)
321{
322 switch (state_) {
323 case State::REQUEST_ACTIVATION:
324 if (!sendActivationCommand(vehicle_command_client_, goal_ptr)) {
325 RCLCPP_ERROR(
326 this->node_ptr_->get_logger(), "Failed to send activation command for flight mode %i. Aborting...", mode_id_);
327 return ActionStatus::FAILURE;
328 }
329 // Force to consider only new status messages after sending new command
330 last_vehicle_status_ptr_ = nullptr;
331 state_ = State::WAIT_FOR_ACTIVATION;
332 activation_command_sent_time_ = this->node_ptr_->now();
333 RCLCPP_DEBUG(
334 this->node_ptr_->get_logger(), "Activation command for flight mode %i was sent successfully", mode_id_);
335 return ActionStatus::RUNNING;
336 case State::WAIT_FOR_ACTIVATION:
337 if (isCurrentNavState(mode_id_)) {
338 RCLCPP_DEBUG(this->node_ptr_->get_logger(), "Flight mode %i is active", mode_id_);
339 state_ = State::WAIT_FOR_COMPLETION_SIGNAL;
340 } else if (this->node_ptr_->now() - activation_command_sent_time_ > activation_timeout_) {
341 RCLCPP_ERROR(this->node_ptr_->get_logger(), "Timeout activating flight mode %i. Aborting...", mode_id_);
342 return ActionStatus::FAILURE;
343 }
344 return ActionStatus::RUNNING;
345 case State::WAIT_FOR_COMPLETION_SIGNAL:
346 // Populate feedback message
347 setFeedback(feedback_ptr, *last_vehicle_status_ptr_);
348
349 // Check if execution should be terminated
350 if (isCompleted(goal_ptr, *last_vehicle_status_ptr_)) {
351 state_ = State::COMPLETE;
352 if (deactivate_before_completion_) {
353 // To deactivate current flight mode, enable HOLD mode
354 RCLCPP_DEBUG(
355 this->node_ptr_->get_logger(),
356 "Flight mode %i complete! Will deactivate before termination (Enter HOLD)...", mode_id_);
357 } else {
358 RCLCPP_DEBUG(
359 this->node_ptr_->get_logger(),
360 "Flight mode %i complete! Will leave current navigation state as is. User is "
361 "responsible for initiating the next flight mode...",
362 mode_id_);
363 }
364
365 // Don't return to complete in same iteration
366 break;
367 }
368 // Check if nav state changed
369 if (!isCurrentNavState(mode_id_)) {
370 RCLCPP_WARN(this->node_ptr_->get_logger(), "Flight mode %i was deactivated externally. Aborting...", mode_id_);
371 return ActionStatus::FAILURE;
372 }
373 return ActionStatus::RUNNING;
374 case State::COMPLETE:
375 break;
376 }
377
378 if (deactivate_before_completion_) {
379 const auto deactivation_state = asyncDeactivateFlightMode();
380 if (deactivation_state != ActionStatus::SUCCESS) {
381 return deactivation_state;
382 }
383 // Don't return to complete in same iteration
384 }
385
386 RCLCPP_DEBUG(this->node_ptr_->get_logger(), "Flight mode %i execution termination", mode_id_);
387 return ActionStatus::SUCCESS;
388}
389
390template <class ActionT>
391bool ModeExecutor<ActionT>::sendActivationCommand(
392 const VehicleCommandClient & client, std::shared_ptr<const Goal> /*goal_ptr*/)
393{
394 return client.syncActivateFlightMode(mode_id_);
395}
396
397template <class ActionT>
398bool ModeExecutor<ActionT>::isCompleted(
399 std::shared_ptr<const Goal> /*goal_ptr*/, const px4_msgs::msg::VehicleStatus & /*vehicle_status*/)
400{
401 return mode_completed_;
402}
403
404template <class ActionT>
405void ModeExecutor<ActionT>::setFeedback(
406 std::shared_ptr<Feedback> /*feedback_ptr*/, const px4_msgs::msg::VehicleStatus & /*vehicle_status*/)
407{
408 return;
409}
410
411template <class ActionT>
412uint8_t ModeExecutor<ActionT>::getModeID() const
413{
414 return mode_id_;
415}
416
417template <class ActionT, class ModeT>
418ModeExecutorFactory<ActionT, ModeT>::ModeExecutorFactory(
419 const std::string & action_name, const rclcpp::NodeOptions & options, bool deactivate_before_completion)
420: node_ptr_(std::make_shared<rclcpp::Node>(action_name + "_node", options))
421{
422 static_assert(
423 std::is_base_of<ModeBase<ActionT>, ModeT>::value,
424 "Template argument ModeT must inherit auto_apms::ModeBase<ActionT> as public and with same type "
425 "ActionT as auto_apms::ActionWrapper<ActionT>");
426
427 const auto action_context_ptr = std::make_shared<auto_apms_util::ActionContext<ActionT>>(node_ptr_->get_logger());
428
429 mode_ptr_ = std::make_unique<ModeT>(*node_ptr_, px4_ros2::ModeBase::Settings(action_name), action_context_ptr);
430
431 constexpr int max_retries = 5;
432 bool fmu_available = false;
433 for (int attempt = 0; attempt < max_retries; ++attempt) {
434 if (px4_ros2::waitForFMU(*node_ptr_, std::chrono::seconds(3))) {
435 fmu_available = true;
436 RCLCPP_DEBUG(node_ptr_->get_logger(), "FMU availability test successful (attempt %d).", attempt + 1);
437 break;
438 }
439 RCLCPP_WARN(node_ptr_->get_logger(), "No message from FMU (attempt %d/%d). Retrying...", attempt + 1, max_retries);
440 }
441 if (!fmu_available) {
442 throw std::runtime_error("No message from FMU after multiple attempts");
443 }
444
445 if (!mode_ptr_->doRegister()) {
446 RCLCPP_FATAL(node_ptr_->get_logger(), "Registration of mode with action_name: '%s' failed.", action_name.c_str());
447 throw std::runtime_error("Mode registration failed");
448 }
449
450 // AFTER (!) registration, the mode id can be queried to set up the executor
451 mode_executor_ptr_ = std::make_shared<ModeExecutor<ActionT>>(
452 action_name, node_ptr_, action_context_ptr, mode_ptr_->id(), deactivate_before_completion);
453}
454
455template <class ActionT, class ModeT>
456rclcpp::node_interfaces::NodeBaseInterface::SharedPtr ModeExecutorFactory<ActionT, ModeT>::get_node_base_interface()
457{
458 return node_ptr_->get_node_base_interface();
459}
460
461} // namespace auto_apms_px4
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.
Definition mode.hpp:28
Fundamental helper classes and utility functions.