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