QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
VehicleLinkManager.cc
Go to the documentation of this file.
2#include "MAVLinkLib.h"
3#include "Vehicle.h"
4#include "LinkManager.h"
5#include "AppMessages.h"
6#include "AudioOutput.h"
7#ifndef QGC_NO_SERIAL_LINK
8 #include "SerialLink.h"
9#endif
10#include "QGCLoggingCategory.h"
11
12#include <algorithm>
13
14QGC_LOGGING_CATEGORY(VehicleLinkManagerLog, "Vehicle.VehicleLinkManager")
15
17 : QObject(vehicle)
18 , _vehicle(vehicle)
19 , _commLostCheckTimer(new QTimer(this))
20{
21 // qCDebug(VehicleLinkManagerLog) << Q_FUNC_INFO << this;
22
24 (void) connect(_commLostCheckTimer, &QTimer::timeout, this, &VehicleLinkManager::_commLostCheck);
25
26 _commLostCheckTimer->setSingleShot(false);
27 _commLostCheckTimer->setInterval(QGC::runningUnitTests() ? kTestCommLostCheckTimeoutMs : _commLostCheckTimeoutMSecs);
28}
29
31{
32 // qCDebug(VehicleLinkManagerLog) << Q_FUNC_INFO << this;
33}
34
36{
37 // Radio status messages come from Sik Radios directly. It doesn't indicate there is any life on the other end.
38 if (message.msgid == MAVLINK_MSG_ID_RADIO_STATUS) {
39 return;
40 }
41
42 const int linkIndex = _containsLinkIndex(link);
43 if (linkIndex == -1) {
44 _addLink(link);
45 return;
46 }
47
48 LinkInfo_t &linkInfo = _rgLinkInfo[linkIndex];
49 linkInfo.heartbeatElapsedTimer.restart();
50 if (_rgLinkInfo[linkIndex].commLost) {
51 _commRegainedOnLink(link);
52 }
53}
54
55void VehicleLinkManager::_commRegainedOnLink(LinkInterface *link)
56{
57
58 const int linkIndex = _containsLinkIndex(link);
59 if (linkIndex == -1) {
60 return;
61 }
62
63 _rgLinkInfo[linkIndex].commLost = false;
64
65 // Notify the user of communication regained
66 QString commRegainedMessage;
67 const bool isPrimaryLink = link == _primaryLink.lock().get();
68 if (_rgLinkInfo.count() > 1) {
69 commRegainedMessage = tr("%1Communication regained on %2 link").arg(_vehicle->_vehicleIdSpeech()).arg(isPrimaryLink ? tr("primary") : tr("secondary"));
70 } else {
71 commRegainedMessage = tr("%1Communication regained").arg(_vehicle->_vehicleIdSpeech());
72 }
73
74 // Try to switch to another link
75 QString primarySwitchMessage;
76 if (_updatePrimaryLink()) {
77 primarySwitchMessage = tr("%1Switching communication to new primary link").arg(_vehicle->_vehicleIdSpeech());
78 }
79
80 if (!commRegainedMessage.isEmpty()) {
81 AudioOutput::instance()->say(commRegainedMessage.toLower());
82 }
83
84 if (!primarySwitchMessage.isEmpty()) {
85 AudioOutput::instance()->say(primarySwitchMessage.toLower());
86 QGC::showAppMessage(primarySwitchMessage);
87 }
88
90
91 // Any single regained link ends total communication loss
92 if (_communicationLost &&
93 std::any_of(_rgLinkInfo.cbegin(), _rgLinkInfo.cend(),
94 [](const LinkInfo_t &info) { return !info.commLost; })) {
95 _communicationLost = false;
96 emit communicationLostChanged(_communicationLost);
97 }
98}
99
100void VehicleLinkManager::_commLostCheck()
101{
102 if (!_communicationLostEnabled) {
103 return;
104 }
105
106 // Use much shorter heartbeat timeout in unit tests since MockLink sends heartbeats instantly
107 const int heartbeatTimeout = QGC::runningUnitTests() ? kTestHeartbeatTimeoutMs : _heartbeatMaxElpasedMSecs;
108
109 bool linkStatusChange = false;
110 for (LinkInfo_t &linkInfo: _rgLinkInfo) {
111 if (!linkInfo.commLost && !linkInfo.link->linkConfiguration()->isHighLatency() && (linkInfo.heartbeatElapsedTimer.elapsed() > heartbeatTimeout)) {
112 linkInfo.commLost = true;
113 linkStatusChange = true;
114
115 // Notify the user of individual link communication loss
116 const bool isPrimaryLink = linkInfo.link.get() == _primaryLink.lock().get();
117 if (_rgLinkInfo.count() > 1) {
118 const QString msg = tr("%1Communication lost on %2 link.").arg(_vehicle->_vehicleIdSpeech()).arg(isPrimaryLink ? tr("primary") : tr("secondary"));
119 AudioOutput::instance()->say(msg.toLower());
120 }
121 }
122 }
123
124 if (linkStatusChange) {
125 emit linkStatusesChanged();
126 }
127
128 if (_updatePrimaryLink()) {
129 QString msg = tr("%1Switching communication to secondary link.").arg(_vehicle->_vehicleIdSpeech());
130 AudioOutput::instance()->say(msg.toLower());
132 }
133
134 if (_communicationLost) {
135 return;
136 }
137
138 bool totalCommunicationLoss = true;
139 for (const LinkInfo_t &linkInfo: _rgLinkInfo) {
140 if (!linkInfo.commLost) {
141 totalCommunicationLoss = false;
142 break;
143 }
144 }
145
146 if (totalCommunicationLoss) {
147 if (_autoDisconnect) {
148 // There is only one link to the vehicle and we want to auto disconnect from it
149 closeVehicle();
150 return;
151 }
152
153 AudioOutput::instance()->say(tr("%1Communication lost").arg(_vehicle->_vehicleIdSpeech()).toLower());
154
155 _communicationLost = true;
156 emit communicationLostChanged(_communicationLost);
157 }
158}
159
160int VehicleLinkManager::_containsLinkIndex(const LinkInterface *link)
161{
162 for (int i = 0; i < _rgLinkInfo.count(); i++) {
163 if (_rgLinkInfo[i].link.get() == link) {
164 return i;
165 }
166 }
167
168 return -1;
169}
170
171void VehicleLinkManager::_addLink(LinkInterface *link)
172{
173 if (_containsLinkIndex(link) != -1) {
174 qCWarning(VehicleLinkManagerLog) << "_addLink call with link which is already in the list";
175 return;
176 }
177
179 if (!sharedLink) {
180 qCDebug(VehicleLinkManagerLog) << "_addLink stale link" << (void*)link;
181 return;
182 }
183
184 qCDebug(VehicleLinkManagerLog) << "_addLink:" << link->linkConfiguration()->name() << QString("%1").arg((qulonglong)link, 0, 16);
185
186 link->addVehicleReference();
187
188 LinkInfo_t linkInfo;
189 linkInfo.link = sharedLink;
190 if (!link->linkConfiguration()->isHighLatency()) {
191 linkInfo.heartbeatElapsedTimer.start();
192 }
193 _rgLinkInfo.append(linkInfo);
194
195 _updatePrimaryLink();
196
197 (void) connect(link, &LinkInterface::disconnected, this, &VehicleLinkManager::_linkDisconnected);
198
199 emit linkNamesChanged();
200
201 if (_rgLinkInfo.count() == 1) {
202 _commLostCheckTimer->start();
203 }
204}
205
206void VehicleLinkManager::_removeLink(LinkInterface *link)
207{
208 const int linkIndex = _containsLinkIndex(link);
209 if (linkIndex == -1) {
210 qCWarning(VehicleLinkManagerLog) << "_removeLink call with link which is already in the list";
211 return;
212 }
213
214 qCDebug(VehicleLinkManagerLog) << "_removeLink:" << QString("%1").arg((qulonglong)link, 0, 16);
215
216 if (link == _primaryLink.lock().get()) {
217 _primaryLink.reset();
218 emit primaryLinkChanged();
219 }
220
221 disconnect(link, &LinkInterface::disconnected, this, &VehicleLinkManager::_linkDisconnected);
222 link->removeVehicleReference();
223 emit linkNamesChanged();
224 _rgLinkInfo.removeAt(linkIndex); // Remove the link last since it may cause the link itself to be deleted
225
226 if (_rgLinkInfo.isEmpty()) {
227 _commLostCheckTimer->stop();
228 }
229}
230
231void VehicleLinkManager::_linkDisconnected()
232{
233 qCDebug(VehicleLinkManagerLog) << Q_FUNC_INFO << "linkCount" << _rgLinkInfo.count();
234
235 LinkInterface *link = qobject_cast<LinkInterface*>(sender());
236 if (!link) {
237 return;
238 }
239
240 _removeLink(link);
241 _updatePrimaryLink();
242 if (_rgLinkInfo.isEmpty() && !_allLinksRemovedSignalledByCloseVehicle) {
243 qCDebug(VehicleLinkManagerLog) << "signalling allLinksRemoved";
244 // Stop command processing timers immediately to prevent callbacks during the
245 // asynchronous vehicle destruction sequence
246 _vehicle->_stopCommandProcessing();
247 emit allLinksRemoved(_vehicle);
248 }
249}
250
251SharedLinkInterfacePtr VehicleLinkManager::_bestActivePrimaryLink()
252{
253#ifndef QGC_NO_SERIAL_LINK
254 // Best choice is a USB connection
255 for (const LinkInfo_t &linkInfo: _rgLinkInfo) {
256 if (linkInfo.commLost) {
257 continue;
258 }
259
260 SharedLinkInterfacePtr candidateLink = linkInfo.link;
261 auto linkInterface = candidateLink.get();
262 if (linkInterface && LinkManager::isLinkUSBDirect(linkInterface)) {
263 return candidateLink;
264 }
265 }
266#endif
267
268 // Next best is normal latency link
269 for (const LinkInfo_t &linkInfo: _rgLinkInfo) {
270 if (linkInfo.commLost) {
271 continue;
272 }
273
274 SharedLinkInterfacePtr candidateLink = linkInfo.link;
275 const SharedLinkConfigurationPtr config = candidateLink->linkConfiguration();
276 if (config && !config->isHighLatency()) {
277 return candidateLink;
278 }
279 }
280
281 // Last possible choice is a high latency link
282 SharedLinkInterfacePtr primaryLink = _primaryLink.lock();
283 if (primaryLink && primaryLink->linkConfiguration()->isHighLatency()) {
284 // Best choice continues to be the current high latency link
285 return primaryLink;
286 }
287
288 // Pick any high latency link if one exists
289 for (const LinkInfo_t &linkInfo: _rgLinkInfo) {
290 if (linkInfo.commLost) {
291 continue;
292 }
293
294 SharedLinkInterfacePtr candidateLink = linkInfo.link;
295 const SharedLinkConfigurationPtr config = candidateLink->linkConfiguration();
296 if (config && config->isHighLatency()) {
297 return candidateLink;
298 }
299 }
300
301 return {};
302}
303
304bool VehicleLinkManager::_updatePrimaryLink()
305{
306 SharedLinkInterfacePtr primaryLink = _primaryLink.lock();
307 const int linkIndex = _containsLinkIndex(primaryLink.get());
308
309 if ((linkIndex != -1) && !_rgLinkInfo[linkIndex].commLost && !primaryLink->linkConfiguration()->isHighLatency()) {
310 // Current priority link is still valid
311 return false;
312 }
313
314 SharedLinkInterfacePtr bestActivePrimaryLink = _bestActivePrimaryLink();
315 if ((linkIndex != -1) && !bestActivePrimaryLink) {
316 // Nothing better available, leave things set to current primary link
317 return false;
318 }
319
320 if (bestActivePrimaryLink == primaryLink) {
321 return false;
322 }
323
324 if (primaryLink && primaryLink->linkConfiguration()->isHighLatency()) {
325 _vehicle->sendMavCommand(
326 MAV_COMP_ID_AUTOPILOT1,
327 MAV_CMD_CONTROL_HIGH_LATENCY,
328 true,
329 0 // Stop transmission on this link
330 );
331 }
332
333 _primaryLink = bestActivePrimaryLink;
334 emit primaryLinkChanged();
335
336 if (bestActivePrimaryLink && bestActivePrimaryLink->linkConfiguration()->isHighLatency()) {
337 _vehicle->sendMavCommand(MAV_COMP_ID_AUTOPILOT1,
338 MAV_CMD_CONTROL_HIGH_LATENCY,
339 true,
340 1); // Start transmission on this link
341 }
342
343 return true;
344}
345
347{
348 // Vehicle is no longer communicating with us. Remove all link references
349
350 const QList<LinkInfo_t> rgLinkInfoCopy = _rgLinkInfo;
351 for (const LinkInfo_t &linkInfo: rgLinkInfoCopy) {
352 _removeLink(linkInfo.link.get());
353 }
354
355 _rgLinkInfo.clear();
356
357 _allLinksRemovedSignalledByCloseVehicle = true; // Prevent double signal of allLinksRemoved
358 // Stop command processing timers immediately to prevent callbacks during the
359 // asynchronous vehicle destruction sequence
360 _vehicle->_stopCommandProcessing();
361 emit allLinksRemoved(_vehicle);
362}
363
364void VehicleLinkManager::setCommunicationLostEnabled(bool communicationLostEnabled)
365{
366 if (_communicationLostEnabled != communicationLostEnabled) {
367 _communicationLostEnabled = communicationLostEnabled;
369 }
370}
371
373{
374 return (_containsLinkIndex(link) != -1);
375}
376
378{
379 if (!_primaryLink.expired()) {
380 return _primaryLink.lock()->linkConfiguration()->name();
381 }
382
383 return QString();
384}
385
387{
388 for (const LinkInfo_t& linkInfo: _rgLinkInfo) {
389 if (linkInfo.link->linkConfiguration()->name() == name) {
390 _primaryLink = linkInfo.link;
391 emit primaryLinkChanged();
392 }
393 }
394}
395
397{
398 QStringList rgNames;
399
400 for (const LinkInfo_t &linkInfo: _rgLinkInfo) {
401 rgNames.append(linkInfo.link->linkConfiguration()->name());
402 }
403
404 return rgNames;
405}
406
408{
409 QStringList rgStatuses;
410
411 for (const LinkInfo_t &linkInfo: _rgLinkInfo) {
412 rgStatuses.append(linkInfo.commLost ? tr("Comm Lost") : "");
413 }
414
415 return rgStatuses;
416}
Config config
std::shared_ptr< LinkConfiguration > SharedLinkConfigurationPtr
std::shared_ptr< LinkInterface > SharedLinkInterfacePtr
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
void say(const QString &text, TextMods textMods=TextMod::None)
static AudioOutput * instance()
The link interface defines the interface for all links used to communicate with the ground station ap...
void disconnected()
SharedLinkConfigurationPtr linkConfiguration()
SharedLinkInterfacePtr sharedLinkInterfacePointerForLink(const LinkInterface *link)
static bool isLinkUSBDirect(const LinkInterface *link)
static LinkManager * instance()
void communicationLostEnabledChanged(bool communicationLostEnabled)
QStringList linkNames() const
WeakLinkInterfacePtr primaryLink() const
void allLinksRemoved(Vehicle *vehicle)
QStringList linkStatuses() const
void setPrimaryLinkByName(const QString &name)
void mavlinkMessageReceived(LinkInterface *link, const mavlink_message_t &message)
void setCommunicationLostEnabled(bool communicationLostEnabled)
bool containsLink(LinkInterface *link)
void communicationLostChanged(bool communicationLost)
static constexpr int kTestHeartbeatTimeoutMs
void linkStatusesChanged()
bool communicationLostEnabled() const
QString primaryLinkName() const
void sendMavCommand(int compId, MAV_CMD command, bool showError, float param1=0.0f, float param2=0.0f, float param3=0.0f, float param4=0.0f, float param5=0.0f, float param6=0.0f, float param7=0.0f)
Definition Vehicle.cc:2140
bool runningUnitTests()
void showAppMessage(const QString &message, const QString &title)
Modal application message. Queued if the UI isn't ready yet.
Definition AppMessages.cc:9