QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
RequestMessageCoordinator.cc
Go to the documentation of this file.
2
3#include "MAVLinkLib.h"
4#include "MavCommandQueue.h"
5#include "AppMessages.h"
7#include "Vehicle.h"
8
9QGC_LOGGING_CATEGORY(RequestMessageCoordinatorLog, "Vehicle.RequestMessageCoordinator")
10
12 : QObject(vehicle)
13 , _vehicle(vehicle)
14 , _commandQueue(commandQueue)
15{
16}
17
22
24{
25 for (auto& compIdEntry : _infoMap) {
26 qDeleteAll(compIdEntry);
27 }
28 _infoMap.clear();
29
30 for (auto& requestQueue : _queueMap) {
31 qDeleteAll(requestQueue);
32 }
33 _queueMap.clear();
34}
35
36void RequestMessageCoordinator::_noOpResultHandler(void*, MAV_RESULT, RequestMessageResultHandlerFailureCode_t, const mavlink_message_t&)
37{
38}
39
40void RequestMessageCoordinator::cancelRequests(void* resultHandlerData)
41{
42 // Repoint outstanding requests (active or queued) owned by resultHandlerData to the no-op
43 // handler rather than deleting them: an active request may still have a pending command in
44 // MavCommandQueue that references its RequestMessageInfo_t, so the info must stay alive and
45 // be cleaned up through the normal ack/timeout path — just without calling back into the
46 // now-dead context.
47 const auto repoint = [resultHandlerData](RequestMessageInfo_t* info) {
48 if (info->resultHandlerData == resultHandlerData) {
49 info->resultHandler = &_noOpResultHandler;
50 info->resultHandlerData = nullptr;
51 }
52 };
53 for (auto& msgMap : _infoMap) {
54 for (RequestMessageInfo_t* info : msgMap) {
55 repoint(info);
56 }
57 }
58 for (auto& requestQueue : _queueMap) {
59 for (RequestMessageInfo_t* info : requestQueue) {
60 repoint(info);
61 }
62 }
63}
64
65bool RequestMessageCoordinator::_duplicate(int compId, int msgId) const
66{
67 const mavlink_message_info_t* info = mavlink_get_message_info_by_id(msgId);
68 const QString msgName = info ? QString(info->name) : QString::number(msgId);
69
70 if (_infoMap.contains(compId) && _infoMap[compId].contains(msgId)) {
71 qCDebug(RequestMessageCoordinatorLog) << "duplicate in active map - compId:msgId" << compId << msgName;
72 return true;
73 }
74
75 if (_queueMap.contains(compId)) {
76 for (const RequestMessageInfo_t* requestMessageInfo : _queueMap[compId]) {
77 if (requestMessageInfo->msgId == msgId) {
78 qCDebug(RequestMessageCoordinatorLog) << "duplicate in queue - compId:msgId" << compId << msgName;
79 return true;
80 }
81 }
82 }
83
84 return false;
85}
86
87void RequestMessageCoordinator::_removeInfo(int compId, int msgId)
88{
89 if (_infoMap.contains(compId) && _infoMap[compId].contains(msgId)) {
90 const mavlink_message_info_t* info = mavlink_get_message_info_by_id(msgId);
91 const QString msgName = info ? QString(info->name) : QString::number(msgId);
92
93 delete _infoMap[compId][msgId];
94 _infoMap[compId].remove(msgId);
95 qCDebug(RequestMessageCoordinatorLog)
96 << "removed active request compId:msgId"
97 << compId
98 << msgName
99 << "remainingActive"
100 << _infoMap[compId].count();
101 if (_infoMap[compId].isEmpty()) {
102 _infoMap.remove(compId);
103 }
104 _sendNextFromQueue(compId);
105 } else {
106 qWarning() << "compId:msgId not found" << compId << msgId;
107 }
108}
109
110void RequestMessageCoordinator::_sendNow(RequestMessageInfo_t* info)
111{
112 const mavlink_message_info_t* msgInfo = mavlink_get_message_info_by_id(info->msgId);
113 const QString msgName = msgInfo ? QString(msgInfo->name) : QString::number(info->msgId);
114
115 _infoMap[info->compId][info->msgId] = info;
116
117 const int queueDepth = _queueMap.contains(info->compId) ? _queueMap[info->compId].count() : 0;
118 qCDebug(RequestMessageCoordinatorLog)
119 << "sending now compId:msgId"
120 << info->compId
121 << msgName
122 << "queueDepth"
123 << queueDepth;
124
125 MavCmdAckHandlerInfo_t handlerInfo {};
126 handlerInfo.resultHandler = _cmdResultHandler;
127 handlerInfo.resultHandlerData = info;
128
129 _commandQueue->sendWorker(false, // commandInt
130 false, // showError
131 &handlerInfo,
132 info->compId,
133 MAV_CMD_REQUEST_MESSAGE,
134 MAV_FRAME_GLOBAL,
135 info->msgId,
136 info->param1,
137 info->param2,
138 info->param3,
139 info->param4,
140 info->param5,
141 0);
142}
143
144void RequestMessageCoordinator::_sendNextFromQueue(int compId)
145{
146 if (_infoMap.contains(compId) && !_infoMap[compId].isEmpty()) {
147 qCDebug(RequestMessageCoordinatorLog)
148 << "active request still in progress for compId"
149 << compId
150 << "activeCount"
151 << _infoMap[compId].count();
152 return;
153 }
154
155 if (!_queueMap.contains(compId) || _queueMap[compId].isEmpty()) {
156 qCDebug(RequestMessageCoordinatorLog) << "no queued request for compId" << compId;
157 _queueMap.remove(compId);
158 return;
159 }
160
161 RequestMessageInfo_t* info = _queueMap[compId].takeFirst();
162 const mavlink_message_info_t* msgInfo = mavlink_get_message_info_by_id(info->msgId);
163 const QString msgName = msgInfo ? QString(msgInfo->name) : QString::number(info->msgId);
164 const int remainingQueue = _queueMap[compId].count();
165 qCDebug(RequestMessageCoordinatorLog)
166 << "dequeued next request compId:msgId"
167 << compId
168 << msgName
169 << "remainingQueue"
170 << remainingQueue;
171
172 if (_queueMap[compId].isEmpty()) {
173 _queueMap.remove(compId);
174 }
175
176 _sendNow(info);
177}
178
180{
181 if (_infoMap.contains(message.compid) && _infoMap[message.compid].contains(message.msgid)) {
182 auto pInfo = _infoMap[message.compid][message.msgid];
183 auto resultHandler = pInfo->resultHandler;
184 auto resultHandlerData = pInfo->resultHandlerData;
185
186 // A duplicate of the previous mode (e.g. from a second link) must not satisfy this request.
187 // pInfo->param1 is REQUEST_MESSAGE param2 (mode index); 0 requests all modes, so anything matches.
188 if (message.msgid == MAVLINK_MSG_ID_AVAILABLE_MODES) {
189 const uint8_t requestedModeIndex = static_cast<uint8_t>(pInfo->param1);
190 const uint8_t receivedModeIndex = mavlink_msg_available_modes_get_mode_index(&message);
191 if (requestedModeIndex != 0 && receivedModeIndex != requestedModeIndex) {
192 qCDebug(RequestMessageCoordinatorLog)
193 << "ignoring AVAILABLE_MODES for other mode - received:" << receivedModeIndex
194 << "requested:" << requestedModeIndex;
195 return;
196 }
197 }
198
199 pInfo->messageReceived = true;
200 pInfo->message = message;
201
202 const mavlink_message_info_t* info = mavlink_get_message_info_by_id(message.msgid);
203 QString msgName = info ? QString(info->name) : QString::number(message.msgid);
204 const int activeCount = _infoMap.contains(message.compid) ? _infoMap[message.compid].count() : 0;
205 const int queueDepth = _queueMap.contains(message.compid) ? _queueMap[message.compid].count() : 0;
206 qCDebug(RequestMessageCoordinatorLog)
207 << "message received - compId:msgId"
208 << message.compid
209 << msgName
210 << "activeCount"
211 << activeCount
212 << "queueDepth"
213 << queueDepth;
214
215 if (pInfo->commandAckReceived) {
216 _removeInfo(message.compid, message.msgid);
217 (*resultHandler)(resultHandlerData, MAV_RESULT_ACCEPTED, RequestMessageNoFailure, message);
218 }
219 return;
220 }
221
222 // Every inbound message doubles as a timeout tick. _removeInfo mutates _infoMap,
223 // so snapshot the first timed-out entry and handle it after the loops exit.
224 int timedOutCompId = -1;
225 int timedOutMsgId = -1;
226 RequestMessageResultHandler timedOutHandler = nullptr;
227 void* timedOutHandlerData = nullptr;
228 for (auto& compIdEntry : _infoMap) {
229 for (auto info : compIdEntry) {
230 // Shorter timeout during unit tests keeps failure-path tests fast.
231 const int messageWaitTimeoutMs = QGC::runningUnitTests() ? 500 : 1000;
232 if (info->messageWaitElapsedTimer.isValid() && info->messageWaitElapsedTimer.elapsed() > messageWaitTimeoutMs) {
233 const mavlink_message_info_t* msgInfo = mavlink_get_message_info_by_id(info->msgId);
234 QString msgName = msgInfo ? msgInfo->name : QString::number(info->msgId);
235 const int queueDepth = _queueMap.contains(info->compId) ? _queueMap[info->compId].count() : 0;
236 qCDebug(RequestMessageCoordinatorLog)
237 << "request message timed out - compId:msgId"
238 << info->compId
239 << msgName
240 << "queueDepth"
241 << queueDepth;
242
243 timedOutCompId = info->compId;
244 timedOutMsgId = info->msgId;
245 timedOutHandler = info->resultHandler;
246 timedOutHandlerData = info->resultHandlerData;
247 break;
248 }
249 }
250 if (timedOutHandler) {
251 break;
252 }
253 }
254
255 if (timedOutHandler) {
256 _removeInfo(timedOutCompId, timedOutMsgId);
257 mavlink_message_t timeoutMessage = {};
258 (*timedOutHandler)(timedOutHandlerData, MAV_RESULT_FAILED, RequestMessageFailureMessageNotReceived, timeoutMessage);
259 }
260}
261
262void RequestMessageCoordinator::_cmdResultHandler(void* resultHandlerData_, [[maybe_unused]] int compId, const mavlink_command_ack_t& ack, MavCmdResultFailureCode_t failureCode)
263{
264 auto info = static_cast<RequestMessageInfo_t*>(resultHandlerData_);
265 auto resultHandler = info->resultHandler;
266 auto resultHandlerData = info->resultHandlerData;
267 Vehicle* vehicle = info->vehicle; // QPointer converts to raw pointer, null if Vehicle destroyed
268
269 // Vehicle was destroyed before callback fired - clean up and return without accessing vehicle
270 if (!vehicle) {
271 qCDebug(RequestMessageCoordinatorLog) << "Vehicle destroyed before callback - skipping";
272 delete info;
273 return;
274 }
275
276 auto* coordinator = info->coordinator;
277
278 info->commandAckReceived = true;
279 const mavlink_message_info_t* msgInfo = mavlink_get_message_info_by_id(info->msgId);
280 const QString msgName = msgInfo ? QString(msgInfo->name) : QString::number(info->msgId);
281 qCDebug(RequestMessageCoordinatorLog)
282 << "ack for requestMessage compId:msgId"
283 << info->compId
284 << msgName
285 << "ack"
286 << QGCMAVLink::mavResultToString(static_cast<MAV_RESULT>(ack.result))
287 << "failureCode"
289
290 if (ack.result != MAV_RESULT_ACCEPTED) {
291 mavlink_message_t ackMessage = {};
293
294 switch (failureCode) {
296 requestMessageFailureCode = RequestMessageFailureCommandError;
297 break;
299 requestMessageFailureCode = RequestMessageFailureCommandNotAcked;
300 break;
302 requestMessageFailureCode = RequestMessageFailureDuplicate;
303 break;
304 }
305
306 coordinator->_removeInfo(info->compId, info->msgId);
307 (*resultHandler)(resultHandlerData, static_cast<MAV_RESULT>(ack.result), requestMessageFailureCode, ackMessage);
308 return;
309 }
310
311 if (info->messageReceived) {
312 auto message = info->message;
313 coordinator->_removeInfo(info->compId, info->msgId);
314 (*resultHandler)(resultHandlerData, MAV_RESULT_ACCEPTED, RequestMessageNoFailure, message);
315 return;
316 }
317
318 // We have the ack, but we are still waiting for the message. Start the timer to wait for the message
319 info->messageWaitElapsedTimer.start();
320}
321
322void RequestMessageCoordinator::requestMessage(RequestMessageResultHandler resultHandler, void* resultHandlerData, int compId, int messageId, float param1, float param2, float param3, float param4, float param5)
323{
324 const mavlink_message_info_t* msgInfo = mavlink_get_message_info_by_id(messageId);
325 const QString msgName = msgInfo ? QString(msgInfo->name) : QString::number(messageId);
326 const int activeCount = _infoMap.contains(compId) ? _infoMap[compId].count() : 0;
327 const int queueDepth = _queueMap.contains(compId) ? _queueMap[compId].count() : 0;
328 qCDebug(RequestMessageCoordinatorLog)
329 << "incoming request compId:msgId"
330 << compId
331 << msgName
332 << "activeCount"
333 << activeCount
334 << "queueDepth"
335 << queueDepth;
336
337 auto info = new RequestMessageInfo_t;
338 info->vehicle = _vehicle;
339 info->coordinator = this;
340 info->compId = compId;
341 info->msgId = messageId;
342 info->param1 = param1;
343 info->param2 = param2;
344 info->param3 = param3;
345 info->param4 = param4;
346 info->param5 = param5;
347 info->resultHandler = resultHandler;
348 info->resultHandlerData = resultHandlerData;
349
350 if (_duplicate(compId, messageId)) {
351 mavlink_message_t ackMessage = {};
352 qCWarning(RequestMessageCoordinatorLog) << "failing exact duplicate compId:msgId" << compId << msgName;
353 (*resultHandler)(resultHandlerData,
354 MAV_RESULT_FAILED,
356 ackMessage);
357 delete info;
358 return;
359 }
360
361 if (_infoMap.contains(compId) && !_infoMap[compId].isEmpty()) {
362 _queueMap[compId].append(info);
363 qCDebug(RequestMessageCoordinatorLog)
364 << "queued request compId:msgId"
365 << compId
366 << msgName
367 << "newQueueDepth"
368 << _queueMap[compId].count();
369 return;
370 }
371
372 _sendNow(info);
373}
374
376{
377 switch (failureCode) {
379 return QStringLiteral("No Failure");
381 return QStringLiteral("Command Error");
383 return QStringLiteral("Command Not Acked");
385 return QStringLiteral("Message Not Received");
387 return QStringLiteral("Duplicate Request");
388 default:
389 return QStringLiteral("Unknown (%1)").arg(failureCode);
390 }
391}
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
struct __mavlink_command_ack_t mavlink_command_ack_t
Owns the COMMAND_LONG / COMMAND_INT send/retry/ack pipeline for a single Vehicle.
static QString failureCodeToString(MavCmdResultFailureCode_t failureCode)
void sendWorker(bool commandInt, bool showError, const MavCmdAckHandlerInfo_t *ackHandlerInfo, int compId, MAV_CMD command, MAV_FRAME frame, float param1, float param2, float param3, float param4, double param5, double param6, float param7)
Coordinates MAV_CMD_REQUEST_MESSAGE workflows: per-component queueing, ack/message correlation,...
void handleReceivedMessage(const mavlink_message_t &message)
void cancelRequests(void *resultHandlerData)
static QString failureCodeToString(RequestMessageResultHandlerFailureCode_t failureCode)
void requestMessage(RequestMessageResultHandler resultHandler, void *resultHandlerData, int compId, int messageId, float param1=0.0f, float param2=0.0f, float param3=0.0f, float param4=0.0f, float param5=0.0f)
void stop()
Clear pending state without firing callbacks (used during vehicle shutdown).
int compId() const
Definition Vehicle.h:444
std::optional< T > number(std::string_view field, int base=DECIMAL_BASE)
bool runningUnitTests()
MavCmdResultHandler resultHandler
nullptr for no handler
void(* RequestMessageResultHandler)(void *resultHandlerData, MAV_RESULT commandResult, RequestMessageResultHandlerFailureCode_t failureCode, const mavlink_message_t &message)
Callback for requestMessage — delivered when the ack/message pair resolves or a failure occurs.
struct VehicleTypes::MavCmdAckHandlerInfo_s MavCmdAckHandlerInfo_t
Callback info bundle for sendMavCommandWithHandler.
@ MavCmdResultFailureDuplicateCommand
Unable to send command since duplicate is already being waited on for response.
@ MavCmdResultCommandResultOnly
commandResult specifies full success/fail info
@ MavCmdResultFailureNoResponseToCommand
No response from vehicle to command.
RequestMessageResultHandlerFailureCode_t
@ RequestMessageFailureMessageNotReceived
@ RequestMessageFailureCommandNotAcked
@ RequestMessageFailureDuplicate
Exact duplicate request already active or queued for this component/message id.
@ RequestMessageFailureCommandError
@ RequestMessageNoFailure