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