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