LinkManager.cc 34.6 KB
Newer Older
1 2 3 4 5 6 7 8
/****************************************************************************
 *
 *   (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/
lm's avatar
lm committed
9

pixhawk's avatar
pixhawk committed
10 11
#include <QList>
#include <QApplication>
12
#include <QDebug>
13
#include <QSignalSpy>
dogmaphobic's avatar
dogmaphobic committed
14

Gus Grubba's avatar
Gus Grubba committed
15
#ifndef NO_SERIAL_LINK
Don Gagne's avatar
Don Gagne committed
16
#include "QGCSerialPortInfo.h"
dogmaphobic's avatar
dogmaphobic committed
17
#endif
18

19
#include "LinkManager.h"
20
#include "QGCApplication.h"
Don Gagne's avatar
Don Gagne committed
21 22
#include "UDPLink.h"
#include "TCPLink.h"
dogmaphobic's avatar
dogmaphobic committed
23
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
24 25
#include "BluetoothLink.h"
#endif
26

Don Gagne's avatar
Don Gagne committed
27 28 29 30
#ifndef __mobile__
    #include "GPSManager.h"
#endif

Don Gagne's avatar
Don Gagne committed
31
QGC_LOGGING_CATEGORY(LinkManagerLog, "LinkManagerLog")
Don Gagne's avatar
Don Gagne committed
32 33
QGC_LOGGING_CATEGORY(LinkManagerVerboseLog, "LinkManagerVerboseLog")

34 35 36 37 38 39 40
const char* LinkManager::_settingsGroup =            "LinkManager";
const char* LinkManager::_autoconnectUDPKey =        "AutoconnectUDP";
const char* LinkManager::_autoconnectPixhawkKey =    "AutoconnectPixhawk";
const char* LinkManager::_autoconnect3DRRadioKey =   "Autoconnect3DRRadio";
const char* LinkManager::_autoconnectPX4FlowKey =    "AutoconnectPX4Flow";
const char* LinkManager::_autoconnectRTKGPSKey =     "AutoconnectRTKGPS";
const char* LinkManager::_autoconnectLibrePilotKey = "AutoconnectLibrePilot";
Don Gagne's avatar
Don Gagne committed
41
const char* LinkManager::_defaultUPDLinkName =       "UDP Link (AutoConnect)";
42

43 44 45 46 47 48 49 50
const int LinkManager::_autoconnectUpdateTimerMSecs =   1000;
#ifdef Q_OS_WIN
// Have to manually let the bootloader go by on Windows to get a working connect
const int LinkManager::_autoconnectConnectDelayMSecs =  6000;
#else
const int LinkManager::_autoconnectConnectDelayMSecs =  1000;
#endif

51 52
LinkManager::LinkManager(QGCApplication* app)
    : QGCTool(app)
53 54 55
    , _configUpdateSuspended(false)
    , _configurationsLoaded(false)
    , _connectionsSuspended(false)
56
    , _mavlinkChannelsUsedBitMask(1)    // We never use channel 0 to avoid sequence numbering problems
57
    , _mavlinkProtocol(NULL)
Don Gagne's avatar
Don Gagne committed
58 59 60 61
    , _autoconnectUDP(true)
    , _autoconnectPixhawk(true)
    , _autoconnect3DRRadio(true)
    , _autoconnectPX4Flow(true)
Don Gagne's avatar
Don Gagne committed
62
    , _autoconnectRTKGPS(true)
63
    , _autoconnectLibrePilot(true)
pixhawk's avatar
pixhawk committed
64
{
Don Gagne's avatar
Don Gagne committed
65 66 67 68 69
    qmlRegisterUncreatableType<LinkManager>         ("QGroundControl", 1, 0, "LinkManager",         "Reference only");
    qmlRegisterUncreatableType<LinkConfiguration>   ("QGroundControl", 1, 0, "LinkConfiguration",   "Reference only");
    qmlRegisterUncreatableType<LinkInterface>       ("QGroundControl", 1, 0, "LinkInterface",       "Reference only");

    QSettings settings;
70

Don Gagne's avatar
Don Gagne committed
71
    settings.beginGroup(_settingsGroup);
72 73 74 75 76 77
    _autoconnectUDP =        settings.value(_autoconnectUDPKey, true).toBool();
    _autoconnectPixhawk =    settings.value(_autoconnectPixhawkKey, true).toBool();
    _autoconnect3DRRadio =   settings.value(_autoconnect3DRRadioKey, true).toBool();
    _autoconnectPX4Flow =    settings.value(_autoconnectPX4FlowKey, true).toBool();
    _autoconnectRTKGPS =     settings.value(_autoconnectRTKGPSKey, true).toBool();
    _autoconnectLibrePilot = settings.value(_autoconnectLibrePilotKey, true).toBool();
Don Gagne's avatar
Don Gagne committed
78

Gus Grubba's avatar
Gus Grubba committed
79
#ifndef NO_SERIAL_LINK
Don Gagne's avatar
Don Gagne committed
80 81 82
    _activeLinkCheckTimer.setInterval(_activeLinkCheckTimeoutMSecs);
    _activeLinkCheckTimer.setSingleShot(false);
    connect(&_activeLinkCheckTimer, &QTimer::timeout, this, &LinkManager::_activeLinkCheck);
Don Gagne's avatar
Don Gagne committed
83
#endif
pixhawk's avatar
pixhawk committed
84 85 86 87
}

LinkManager::~LinkManager()
{
88

pixhawk's avatar
pixhawk committed
89 90
}

91 92 93 94 95 96
void LinkManager::setToolbox(QGCToolbox *toolbox)
{
   QGCTool::setToolbox(toolbox);

   _mavlinkProtocol = _toolbox->mavlinkProtocol();

Don Gagne's avatar
Don Gagne committed
97
    connect(&_portListTimer, &QTimer::timeout, this, &LinkManager::_updateAutoConnectLinks);
98
    _portListTimer.start(_autoconnectUpdateTimerMSecs); // timeout must be long enough to get past bootloader on second pass
99

100 101
}

Don Gagne's avatar
Don Gagne committed
102 103 104 105 106 107 108 109 110 111
// This should only be used by Qml code
void LinkManager::createConnectedLink(LinkConfiguration* config)
{
    for(int i = 0; i < _sharedConfigurations.count(); i++) {
        SharedLinkConfigurationPointer& sharedConf = _sharedConfigurations[i];
        if (sharedConf->name() == config->name())
            createConnectedLink(sharedConf);
    }
}

112
LinkInterface* LinkManager::createConnectedLink(SharedLinkConfigurationPointer& config)
113
{
114 115 116 117 118
    if (!config) {
        qWarning() << "LinkManager::createConnectedLink called with NULL config";
        return NULL;
    }

119 120
    LinkInterface* pLink = NULL;
    switch(config->type()) {
Gus Grubba's avatar
Gus Grubba committed
121
#ifndef NO_SERIAL_LINK
122
        case LinkConfiguration::TypeSerial:
Don Gagne's avatar
Don Gagne committed
123
        {
124
            SerialConfiguration* serialConfig = dynamic_cast<SerialConfiguration*>(config.data());
Don Gagne's avatar
Don Gagne committed
125
            if (serialConfig) {
126
                pLink = new SerialLink(config);
Don Gagne's avatar
Don Gagne committed
127
                if (serialConfig->usbDirect()) {
128
                    _activeLinkCheckList.append((SerialLink*)pLink);
Don Gagne's avatar
Don Gagne committed
129 130 131 132 133 134 135
                    if (!_activeLinkCheckTimer.isActive()) {
                        _activeLinkCheckTimer.start();
                    }
                }
            }
        }
        break;
dogmaphobic's avatar
dogmaphobic committed
136
#endif
137
        case LinkConfiguration::TypeUdp:
138
            pLink = new UDPLink(config);
139
            break;
140
        case LinkConfiguration::TypeTcp:
141
            pLink = new TCPLink(config);
142
            break;
dogmaphobic's avatar
dogmaphobic committed
143
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
144
        case LinkConfiguration::TypeBluetooth:
145
            pLink = new BluetoothLink(config);
dogmaphobic's avatar
dogmaphobic committed
146 147
            break;
#endif
dogmaphobic's avatar
dogmaphobic committed
148
#ifndef __mobile__
Don Gagne's avatar
Don Gagne committed
149
        case LinkConfiguration::TypeLogReplay:
150
            pLink = new LogReplayLink(config);
Don Gagne's avatar
Don Gagne committed
151
            break;
dogmaphobic's avatar
dogmaphobic committed
152
#endif
153
#ifdef QT_DEBUG
154
        case LinkConfiguration::TypeMock:
155
            pLink = new MockLink(config);
156
            break;
157
#endif
158 159 160
        case LinkConfiguration::TypeLast:
        default:
            break;
161
    }
162 163

    if (pLink) {
164 165
        _addLink(pLink);
        connectLink(pLink);
166
    }
167

168 169 170
    return pLink;
}

Don Gagne's avatar
Don Gagne committed
171
LinkInterface* LinkManager::createConnectedLink(const QString& name)
172 173
{
    Q_ASSERT(name.isEmpty() == false);
174 175 176
    for(int i = 0; i < _sharedConfigurations.count(); i++) {
        SharedLinkConfigurationPointer& conf = _sharedConfigurations[i];
        if (conf->name() == name)
Don Gagne's avatar
Don Gagne committed
177
            return createConnectedLink(conf);
178 179 180 181
    }
    return NULL;
}

182
void LinkManager::_addLink(LinkInterface* link)
pixhawk's avatar
pixhawk committed
183
{
Don Gagne's avatar
Don Gagne committed
184 185 186 187
    if (thread() != QThread::currentThread()) {
        qWarning() << "_deleteLink called from incorrect thread";
        return;
    }
188

Don Gagne's avatar
Don Gagne committed
189 190 191
    if (!link) {
        return;
    }
192

193
    if (!containsLink(link)) {
194 195
        bool channelSet = false;

196 197
        // Find a mavlink channel to use for this link, Channel 0 is reserved for internal use.
        for (int i=1; i<32; i++) {
198
            if (!(_mavlinkChannelsUsedBitMask & 1 << i)) {
199
                mavlink_reset_channel_status(i);
200
                link->_setMavlinkChannel(i);
Don Gagne's avatar
Don Gagne committed
201
                // Start the channel on Mav 1 protocol
202 203
                mavlink_status_t* mavlinkStatus = mavlink_get_channel_status(i);
                mavlinkStatus->flags = mavlink_get_channel_status(i)->flags | MAVLINK_STATUS_FLAG_OUT_MAVLINK1;
204
                qDebug() << "LinkManager mavlinkStatus:channel:flags" << mavlinkStatus << i << mavlinkStatus->flags;
Don Gagne's avatar
Don Gagne committed
205
                _mavlinkChannelsUsedBitMask |= 1 << i;
206
                channelSet = true;
207 208 209
                break;
            }
        }
210

211 212 213 214
        if (!channelSet) {
            qWarning() << "Ran out of mavlink channels";
        }

215
        _sharedLinks.append(SharedLinkInterfacePointer(link));
216 217
        emit newLink(link);
    }
218

Don Gagne's avatar
Don Gagne committed
219 220
    connect(link, &LinkInterface::communicationError,   _app,               &QGCApplication::criticalMessageBoxOnMainThread);
    connect(link, &LinkInterface::bytesReceived,        _mavlinkProtocol,   &MAVLinkProtocol::receiveBytes);
221

222
    _mavlinkProtocol->resetMetadataForLink(link);
223

224 225 226 227 228 229
    connect(link, &LinkInterface::connected,            this, &LinkManager::_linkConnected);
    connect(link, &LinkInterface::disconnected,         this, &LinkManager::_linkDisconnected);

    // This connection is queued since it will cloe the link. So we want the link emitter to return otherwise we would
    // close the link our from under itself.
    connect(link, &LinkInterface::connectionRemoved,    this, &LinkManager::_linkConnectionRemoved, Qt::QueuedConnection);
230
}
pixhawk's avatar
pixhawk committed
231

Don Gagne's avatar
Don Gagne committed
232
void LinkManager::disconnectAll(void)
pixhawk's avatar
pixhawk committed
233
{
Don Gagne's avatar
Don Gagne committed
234
    // Walk list in reverse order to preserve indices during delete
235 236
    for (int i=_sharedLinks.count()-1; i>=0; i--) {
        disconnectLink(_sharedLinks[i].data());
237
    }
pixhawk's avatar
pixhawk committed
238 239 240 241
}

bool LinkManager::connectLink(LinkInterface* link)
{
242
    Q_ASSERT(link);
243

244 245 246 247
    if (_connectionsSuspendedMsg()) {
        return false;
    }

248
    return link->_connect();
pixhawk's avatar
pixhawk committed
249 250
}

Don Gagne's avatar
Don Gagne committed
251
void LinkManager::disconnectLink(LinkInterface* link)
pixhawk's avatar
pixhawk committed
252
{
253
    if (!link || !containsLink(link)) {
254 255
        return;
    }
Don Gagne's avatar
Don Gagne committed
256

Don Gagne's avatar
Don Gagne committed
257
    link->_disconnect();
258

Don Gagne's avatar
Don Gagne committed
259
    LinkConfiguration* config = link->getLinkConfiguration();
260 261 262 263 264
    for (int i=0; i<_sharedAutoconnectConfigurations.count(); i++) {
        if (_sharedAutoconnectConfigurations[i].data() == config) {
            qCDebug(LinkManagerLog) << "Removing disconnected autoconnect config" << config->name();
            _sharedAutoconnectConfigurations.removeAt(i);
            break;
265
        }
266
    }
267

Don Gagne's avatar
Don Gagne committed
268
    _deleteLink(link);
pixhawk's avatar
pixhawk committed
269 270
}

271
void LinkManager::_deleteLink(LinkInterface* link)
272
{
Don Gagne's avatar
Don Gagne committed
273 274 275 276 277 278 279 280
    if (thread() != QThread::currentThread()) {
        qWarning() << "_deleteLink called from incorrect thread";
        return;
    }

    if (!link) {
        return;
    }
281

282
    // Free up the mavlink channel associated with this link
283
    _mavlinkChannelsUsedBitMask &= ~(1 << link->mavlinkChannel());
284

285 286 287 288 289 290
    for (int i=0; i<_sharedLinks.count(); i++) {
        if (_sharedLinks[i].data() == link) {
            _sharedLinks.removeAt(i);
            break;
        }
    }
291

Don Gagne's avatar
Don Gagne committed
292
    // Emit removal of link
293
    emit linkDeleted(link);
pixhawk's avatar
pixhawk committed
294 295
}

296 297 298 299 300 301 302 303 304 305 306 307
SharedLinkInterfacePointer LinkManager::sharedLinkInterfacePointerForLink(LinkInterface* link)
{
    for (int i=0; i<_sharedLinks.count(); i++) {
        if (_sharedLinks[i].data() == link) {
            return _sharedLinks[i];
        }
    }

    qWarning() << "LinkManager::sharedLinkInterfaceForLink returning NULL";
    return SharedLinkInterfacePointer(NULL);
}

308 309 310 311 312
/// @brief If all new connections should be suspended a message is displayed to the user and true
///         is returned;
bool LinkManager::_connectionsSuspendedMsg(void)
{
    if (_connectionsSuspended) {
313
        qgcApp()->showMessage(QString("Connect not allowed: %1").arg(_connectionsSuspendedReason));
314 315 316 317 318 319 320 321 322 323 324 325
        return true;
    } else {
        return false;
    }
}

void LinkManager::setConnectionsSuspended(QString reason)
{
    _connectionsSuspended = true;
    _connectionsSuspendedReason = reason;
    Q_ASSERT(!reason.isEmpty());
}
326

327 328 329 330 331 332 333 334 335
void LinkManager::_linkConnected(void)
{
    emit linkConnected((LinkInterface*)sender());
}

void LinkManager::_linkDisconnected(void)
{
    emit linkDisconnected((LinkInterface*)sender());
}
336

337 338 339 340 341 342
void LinkManager::_linkConnectionRemoved(LinkInterface* link)
{
    // Link has been removed from system, disconnect it automatically
    disconnectLink(link);
}

343 344 345 346 347 348 349 350 351
void LinkManager::suspendConfigurationUpdates(bool suspend)
{
    _configUpdateSuspended = suspend;
}

void LinkManager::saveLinkConfigurationList()
{
    QSettings settings;
    settings.remove(LinkConfiguration::settingsRoot());
352
    int trueCount = 0;
353 354
    for (int i = 0; i < _sharedConfigurations.count(); i++) {
        SharedLinkConfigurationPointer linkConfig = _sharedConfigurations[i];
Don Gagne's avatar
Don Gagne committed
355
        if (linkConfig) {
356
            if (!linkConfig->isDynamic()) {
Don Gagne's avatar
Don Gagne committed
357
                QString root = LinkConfiguration::settingsRoot();
358
                root += QString("/Link%1").arg(trueCount++);
Don Gagne's avatar
Don Gagne committed
359 360
                settings.setValue(root + "/name", linkConfig->name());
                settings.setValue(root + "/type", linkConfig->type());
361
                settings.setValue(root + "/auto", linkConfig->isAutoConnect());
Don Gagne's avatar
Don Gagne committed
362 363 364 365
                // Have the instance save its own values
                linkConfig->saveSettings(settings, root);
            }
        } else {
366
            qWarning() << "Internal error for link configuration in LinkManager";
dogmaphobic's avatar
dogmaphobic committed
367
        }
368
    }
dogmaphobic's avatar
dogmaphobic committed
369
    QString root(LinkConfiguration::settingsRoot());
370 371
    settings.setValue(root + "/count", trueCount);
    emit linkConfigurationsChanged();
372 373 374 375
}

void LinkManager::loadLinkConfigurationList()
{
376
    bool linksChanged = false;
377 378 379 380 381 382 383 384 385 386
    QSettings settings;
    // Is the group even there?
    if(settings.contains(LinkConfiguration::settingsRoot() + "/count")) {
        // Find out how many configurations we have
        int count = settings.value(LinkConfiguration::settingsRoot() + "/count").toInt();
        for(int i = 0; i < count; i++) {
            QString root(LinkConfiguration::settingsRoot());
            root += QString("/Link%1").arg(i);
            if(settings.contains(root + "/type")) {
                int type = settings.value(root + "/type").toInt();
387
                if((LinkConfiguration::LinkType)type < LinkConfiguration::TypeLast) {
388 389 390 391
                    if(settings.contains(root + "/name")) {
                        QString name = settings.value(root + "/name").toString();
                        if(!name.isEmpty()) {
                            LinkConfiguration* pLink = NULL;
392 393
                            bool autoConnect = settings.value(root + "/auto").toBool();
                            switch((LinkConfiguration::LinkType)type) {
Gus Grubba's avatar
Gus Grubba committed
394
#ifndef NO_SERIAL_LINK
395 396 397
                                case LinkConfiguration::TypeSerial:
                                    pLink = (LinkConfiguration*)new SerialConfiguration(name);
                                    break;
dogmaphobic's avatar
dogmaphobic committed
398
#endif
399 400 401
                                case LinkConfiguration::TypeUdp:
                                    pLink = (LinkConfiguration*)new UDPConfiguration(name);
                                    break;
402 403 404
                                case LinkConfiguration::TypeTcp:
                                    pLink = (LinkConfiguration*)new TCPConfiguration(name);
                                    break;
dogmaphobic's avatar
dogmaphobic committed
405
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
406 407 408 409
                                case LinkConfiguration::TypeBluetooth:
                                    pLink = (LinkConfiguration*)new BluetoothConfiguration(name);
                                    break;
#endif
dogmaphobic's avatar
dogmaphobic committed
410
#ifndef __mobile__
Don Gagne's avatar
Don Gagne committed
411 412 413
                                case LinkConfiguration::TypeLogReplay:
                                    pLink = (LinkConfiguration*)new LogReplayLinkConfiguration(name);
                                    break;
dogmaphobic's avatar
dogmaphobic committed
414
#endif
415
#ifdef QT_DEBUG
416 417 418
                                case LinkConfiguration::TypeMock:
                                    pLink = (LinkConfiguration*)new MockConfiguration(name);
                                    break;
419
#endif
420 421 422
                                default:
                                case LinkConfiguration::TypeLast:
                                    break;
423 424
                            }
                            if(pLink) {
425 426
                                //-- Have the instance load its own values
                                pLink->setAutoConnect(autoConnect);
427
                                pLink->loadSettings(settings, root);
428
                                addConfiguration(pLink);
429
                                linksChanged = true;
430 431
                            }
                        } else {
432
                            qWarning() << "Link Configuration" << root << "has an empty name." ;
433 434
                        }
                    } else {
435
                        qWarning() << "Link Configuration" << root << "has no name." ;
436 437
                    }
                } else {
438
                    qWarning() << "Link Configuration" << root << "an invalid type: " << type;
439 440
                }
            } else {
441
                qWarning() << "Link Configuration" << root << "has no type." ;
442 443 444
            }
        }
    }
445 446

    if(linksChanged) {
447
        emit linkConfigurationsChanged();
448 449
    }
    // Enable automatic Serial PX4/3DR Radio hunting
450 451 452
    _configurationsLoaded = true;
}

Gus Grubba's avatar
Gus Grubba committed
453
#ifndef NO_SERIAL_LINK
Don Gagne's avatar
Don Gagne committed
454
SerialConfiguration* LinkManager::_autoconnectConfigurationsContainsPort(const QString& portName)
455 456
{
    QString searchPort = portName.trimmed();
Don Gagne's avatar
Don Gagne committed
457

458 459
    for (int i=0; i<_sharedAutoconnectConfigurations.count(); i++) {
        SerialConfiguration* serialConfig = qobject_cast<SerialConfiguration*>(_sharedAutoconnectConfigurations[i].data());
Don Gagne's avatar
Don Gagne committed
460

461 462 463
        if (serialConfig) {
            if (serialConfig->portName() == searchPort) {
                return serialConfig;
464
            }
Don Gagne's avatar
Don Gagne committed
465 466
        } else {
            qWarning() << "Internal error";
467 468 469 470
        }
    }
    return NULL;
}
dogmaphobic's avatar
dogmaphobic committed
471
#endif
472

Don Gagne's avatar
Don Gagne committed
473
void LinkManager::_updateAutoConnectLinks(void)
474
{
Don Gagne's avatar
Don Gagne committed
475
    if (_connectionsSuspended || qgcApp()->runningUnitTests()) {
476 477
        return;
    }
Don Gagne's avatar
Don Gagne committed
478

Don Gagne's avatar
Don Gagne committed
479 480
    // Re-add UDP if we need to
    bool foundUDP = false;
481 482
    for (int i=0; i<_sharedLinks.count(); i++) {
        LinkConfiguration* linkConfig = _sharedLinks[i]->getLinkConfiguration();
Don Gagne's avatar
Don Gagne committed
483 484 485 486 487
        if (linkConfig->type() == LinkConfiguration::TypeUdp && linkConfig->name() == _defaultUPDLinkName) {
            foundUDP = true;
            break;
        }
    }
Don Gagne's avatar
Don Gagne committed
488
    if (!foundUDP && _autoconnectUDP) {
Don Gagne's avatar
Don Gagne committed
489
        qCDebug(LinkManagerLog) << "New auto-connect UDP port added";
Don Gagne's avatar
Don Gagne committed
490 491
        UDPConfiguration* udpConfig = new UDPConfiguration(_defaultUPDLinkName);
        udpConfig->setLocalPort(QGC_UDP_LOCAL_PORT);
492 493 494
        udpConfig->setDynamic(true);        
        SharedLinkConfigurationPointer config = addConfiguration(udpConfig);
        createConnectedLink(config);
495
        emit linkConfigurationsChanged();
Don Gagne's avatar
Don Gagne committed
496 497
    }

Gus Grubba's avatar
Gus Grubba committed
498
#ifndef NO_SERIAL_LINK
dogmaphobic's avatar
dogmaphobic committed
499
    QStringList currentPorts;
500 501 502 503 504 505
    QList<QGCSerialPortInfo> portList;

#ifdef __android__
    // Android builds only support a single serial connection. Repeatedly calling availablePorts after that one serial
    // port is connected leaks file handles due to a bug somewhere in android serial code. In order to work around that
    // bug after we connect the first serial port we stop probing for additional ports.
506
    if (!_sharedAutoconnectConfigurations.count()) {
507 508
        portList = QGCSerialPortInfo::availablePorts();
    }
509 510
#else
    portList = QGCSerialPortInfo::availablePorts();
511
#endif
Don Gagne's avatar
Don Gagne committed
512

513
    // Iterate Comm Ports
Don Gagne's avatar
Don Gagne committed
514
    foreach (QGCSerialPortInfo portInfo, portList) {
Don Gagne's avatar
Don Gagne committed
515 516 517 518 519 520 521 522 523
        qCDebug(LinkManagerVerboseLog) << "-----------------------------------------------------";
        qCDebug(LinkManagerVerboseLog) << "portName:          " << portInfo.portName();
        qCDebug(LinkManagerVerboseLog) << "systemLocation:    " << portInfo.systemLocation();
        qCDebug(LinkManagerVerboseLog) << "description:       " << portInfo.description();
        qCDebug(LinkManagerVerboseLog) << "manufacturer:      " << portInfo.manufacturer();
        qCDebug(LinkManagerVerboseLog) << "serialNumber:      " << portInfo.serialNumber();
        qCDebug(LinkManagerVerboseLog) << "vendorIdentifier:  " << portInfo.vendorIdentifier();
        qCDebug(LinkManagerVerboseLog) << "productIdentifier: " << portInfo.productIdentifier();

dogmaphobic's avatar
dogmaphobic committed
524 525
        // Save port name
        currentPorts << portInfo.systemLocation();
Don Gagne's avatar
Don Gagne committed
526

527 528
        QGCSerialPortInfo::BoardType_t boardType;
        QString boardName;
Don Gagne's avatar
Don Gagne committed
529

530
        if (portInfo.getBoardInfo(boardType, boardName)) {
Don Gagne's avatar
Don Gagne committed
531 532
            if (portInfo.isBootloader()) {
                // Don't connect to bootloader
533
                qCDebug(LinkManagerLog) << "Waiting for bootloader to finish" << portInfo.systemLocation();
Don Gagne's avatar
Don Gagne committed
534 535
                continue;
            }
536

537 538 539 540 541 542 543
            if (_autoconnectConfigurationsContainsPort(portInfo.systemLocation())) {
                qCDebug(LinkManagerVerboseLog) << "Skipping existing autoconnect" << portInfo.systemLocation();
            } else if (!_autoconnectWaitList.contains(portInfo.systemLocation())) {
                // We don't connect to the port the first time we see it. The ability to correctly detect whether we
                // are in the bootloader is flaky from a cross-platform standpoint. So by putting it on a wait list
                // and only connect on the second pass we leave enough time for the board to boot up.
                qCDebug(LinkManagerLog) << "Waiting for next autoconnect pass" << portInfo.systemLocation();
544 545
                _autoconnectWaitList[portInfo.systemLocation()] = 1;
            } else if (++_autoconnectWaitList[portInfo.systemLocation()] * _autoconnectUpdateTimerMSecs > _autoconnectConnectDelayMSecs) {
Don Gagne's avatar
Don Gagne committed
546 547
                SerialConfiguration* pSerialConfig = NULL;

548
                _autoconnectWaitList.remove(portInfo.systemLocation());
549

Don Gagne's avatar
Don Gagne committed
550
                switch (boardType) {
551
                case QGCSerialPortInfo::BoardTypePixhawk:
Don Gagne's avatar
Don Gagne committed
552
                    if (_autoconnectPixhawk) {
553
                        pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName).arg(portInfo.portName().trimmed()));
554 555 556
                        pSerialConfig->setUsbDirect(true);
                    }
                    break;
Don Gagne's avatar
Don Gagne committed
557
                case QGCSerialPortInfo::BoardTypePX4Flow:
Don Gagne's avatar
Don Gagne committed
558
                    if (_autoconnectPX4Flow) {
559
                        pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName).arg(portInfo.portName().trimmed()));
Don Gagne's avatar
Don Gagne committed
560
                    }
Don Gagne's avatar
Don Gagne committed
561
                    break;
562
                case QGCSerialPortInfo::BoardTypeSiKRadio:
Don Gagne's avatar
Don Gagne committed
563
                    if (_autoconnect3DRRadio) {
564
                        pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName).arg(portInfo.portName().trimmed()));
Don Gagne's avatar
Don Gagne committed
565 566
                    }
                    break;
567
                case QGCSerialPortInfo::BoardTypeOpenPilot:
568
                    if (_autoconnectLibrePilot) {
569
                        pSerialConfig = new SerialConfiguration(tr("%1 on %2 (AutoConnect)").arg(boardName).arg(portInfo.portName().trimmed()));
570 571
                    }
                    break;
Don Gagne's avatar
Don Gagne committed
572 573 574 575 576 577 578 579
#ifndef __mobile__
                case QGCSerialPortInfo::BoardTypeRTKGPS:
                    if (_autoconnectRTKGPS && !_toolbox->gpsManager()->connected()) {
                        qCDebug(LinkManagerLog) << "RTK GPS auto-connected";
                        _toolbox->gpsManager()->connectGPS(portInfo.systemLocation());
                    }
                    break;
#endif
Don Gagne's avatar
Don Gagne committed
580 581
                default:
                    qWarning() << "Internal error";
Don Gagne's avatar
Don Gagne committed
582
                    continue;
dogmaphobic's avatar
dogmaphobic committed
583
                }
Don Gagne's avatar
Don Gagne committed
584

Don Gagne's avatar
Don Gagne committed
585 586
                if (pSerialConfig) {
                    qCDebug(LinkManagerLog) << "New auto-connect port added: " << pSerialConfig->name() << portInfo.systemLocation();
587
                    pSerialConfig->setBaud(boardType == QGCSerialPortInfo::BoardTypeSiKRadio ? 57600 : 115200);
Don Gagne's avatar
Don Gagne committed
588 589
                    pSerialConfig->setDynamic(true);
                    pSerialConfig->setPortName(portInfo.systemLocation());
590 591
                    _sharedAutoconnectConfigurations.append(SharedLinkConfigurationPointer(pSerialConfig));
                    createConnectedLink(_sharedAutoconnectConfigurations.last());
Don Gagne's avatar
Don Gagne committed
592
                }
dogmaphobic's avatar
dogmaphobic committed
593 594 595
            }
        }
    }
Don Gagne's avatar
Don Gagne committed
596

597 598 599 600 601 602 603
#ifndef __android__
    // Android builds only support a single serial connection. Repeatedly calling availablePorts after that one serial
    // port is connected leaks file handles due to a bug somewhere in android serial code. In order to work around that
    // bug after we connect the first serial port we stop probing for additional ports. The means we must rely on
    // the port disconnecting itself when the radio is pulled to signal communication list as opposed to automatically
    // closing the Link.

dogmaphobic's avatar
dogmaphobic committed
604 605
    // Now we go through the current configuration list and make sure any dynamic config has gone away
    QList<LinkConfiguration*>  _confToDelete;
606 607 608 609 610 611 612
    for (int i=0; i<_sharedAutoconnectConfigurations.count(); i++) {
        SerialConfiguration* serialConfig = qobject_cast<SerialConfiguration*>(_sharedAutoconnectConfigurations[i].data());
        if (serialConfig) {
            if (!currentPorts.contains(serialConfig->portName())) {
                if (serialConfig->link()) {
                    if (serialConfig->link()->isConnected()) {
                        if (serialConfig->link()->active()) {
613 614 615 616 617 618
                            // We don't remove links which are still connected which have been active with a vehicle on them
                            // even though at this point the cable may have been pulled. Instead we wait for the user to
                            // Disconnect. Once the user disconnects, the link will be removed.
                            continue;
                        }
                    }
Don Gagne's avatar
Don Gagne committed
619
                }
620
                _confToDelete.append(serialConfig);
dogmaphobic's avatar
dogmaphobic committed
621
            }
Don Gagne's avatar
Don Gagne committed
622 623
        } else {
            qWarning() << "Internal error";
dogmaphobic's avatar
dogmaphobic committed
624 625
        }
    }
Don Gagne's avatar
Don Gagne committed
626

Don Gagne's avatar
Don Gagne committed
627
    // Now remove all configs that are gone
Don Gagne's avatar
Don Gagne committed
628
    foreach (LinkConfiguration* pDeleteConfig, _confToDelete) {
Don Gagne's avatar
Don Gagne committed
629
        qCDebug(LinkManagerLog) << "Removing unused autoconnect config" << pDeleteConfig->name();
Don Gagne's avatar
Don Gagne committed
630 631 632
        if (pDeleteConfig->link()) {
            disconnectLink(pDeleteConfig->link());
        }
633 634 635 636 637 638
        for (int i=0; i<_sharedAutoconnectConfigurations.count(); i++) {
            if (_sharedAutoconnectConfigurations[i].data() == pDeleteConfig) {
                _sharedAutoconnectConfigurations.removeAt(i);
                break;
            }
        }
639
    }
640
#endif
Gus Grubba's avatar
Gus Grubba committed
641
#endif // NO_SERIAL_LINK
642 643
}

Don Gagne's avatar
Don Gagne committed
644 645 646
void LinkManager::shutdown(void)
{
    setConnectionsSuspended("Shutdown");
Don Gagne's avatar
Don Gagne committed
647
    disconnectAll();
Don Gagne's avatar
Don Gagne committed
648 649
}

Don Gagne's avatar
Don Gagne committed
650
bool LinkManager::_setAutoconnectWorker(bool& currentAutoconnect, bool newAutoconnect, const char* autoconnectKey)
Don Gagne's avatar
Don Gagne committed
651
{
Don Gagne's avatar
Don Gagne committed
652
    if (currentAutoconnect != newAutoconnect) {
Don Gagne's avatar
Don Gagne committed
653 654 655
        QSettings settings;

        settings.beginGroup(_settingsGroup);
Don Gagne's avatar
Don Gagne committed
656 657 658 659 660 661 662
        settings.setValue(autoconnectKey, newAutoconnect);
        currentAutoconnect = newAutoconnect;
        return true;
    }

    return false;
}
Don Gagne's avatar
Don Gagne committed
663

Don Gagne's avatar
Don Gagne committed
664 665 666
void LinkManager::setAutoconnectUDP(bool autoconnect)
{
    if (_setAutoconnectWorker(_autoconnectUDP, autoconnect, _autoconnectUDPKey)) {
Don Gagne's avatar
Don Gagne committed
667
        emit autoconnectUDPChanged(autoconnect);
668
    }
Don Gagne's avatar
Don Gagne committed
669 670 671 672
}

void LinkManager::setAutoconnectPixhawk(bool autoconnect)
{
Don Gagne's avatar
Don Gagne committed
673
    if (_setAutoconnectWorker(_autoconnectPixhawk, autoconnect, _autoconnectPixhawkKey)) {
Don Gagne's avatar
Don Gagne committed
674 675 676 677 678 679
        emit autoconnectPixhawkChanged(autoconnect);
    }
}

void LinkManager::setAutoconnect3DRRadio(bool autoconnect)
{
Don Gagne's avatar
Don Gagne committed
680
    if (_setAutoconnectWorker(_autoconnect3DRRadio, autoconnect, _autoconnect3DRRadioKey)) {
Don Gagne's avatar
Don Gagne committed
681 682 683 684 685 686
        emit autoconnect3DRRadioChanged(autoconnect);
    }
}

void LinkManager::setAutoconnectPX4Flow(bool autoconnect)
{
Don Gagne's avatar
Don Gagne committed
687
    if (_setAutoconnectWorker(_autoconnectPX4Flow, autoconnect, _autoconnectPX4FlowKey)) {
Don Gagne's avatar
Don Gagne committed
688 689
        emit autoconnectPX4FlowChanged(autoconnect);
    }
Don Gagne's avatar
Don Gagne committed
690
}
Don Gagne's avatar
Don Gagne committed
691

Don Gagne's avatar
Don Gagne committed
692 693 694 695 696
void LinkManager::setAutoconnectRTKGPS(bool autoconnect)
{
    if (_setAutoconnectWorker(_autoconnectRTKGPS, autoconnect, _autoconnectRTKGPSKey)) {
        emit autoconnectRTKGPSChanged(autoconnect);
    }
697
}
698

699 700 701 702 703 704 705
void LinkManager::setAutoconnectLibrePilot(bool autoconnect)
{
    if (_setAutoconnectWorker(_autoconnectLibrePilot, autoconnect, _autoconnectLibrePilotKey)) {
        emit autoconnectLibrePilotChanged(autoconnect);
    }
}

706 707 708 709 710 711
QStringList LinkManager::linkTypeStrings(void) const
{
    //-- Must follow same order as enum LinkType in LinkConfiguration.h
    static QStringList list;
    if(!list.size())
    {
Gus Grubba's avatar
Gus Grubba committed
712
#ifndef NO_SERIAL_LINK
713 714 715 716
        list += "Serial";
#endif
        list += "UDP";
        list += "TCP";
dogmaphobic's avatar
dogmaphobic committed
717
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
718 719
        list += "Bluetooth";
#endif
dogmaphobic's avatar
dogmaphobic committed
720
#ifdef QT_DEBUG
721
        list += "Mock Link";
dogmaphobic's avatar
dogmaphobic committed
722 723
#endif
#ifndef __mobile__
724
        list += "Log Replay";
dogmaphobic's avatar
dogmaphobic committed
725
#endif
dogmaphobic's avatar
dogmaphobic committed
726
        Q_ASSERT(list.size() == (int)LinkConfiguration::TypeLast);
727 728 729 730
    }
    return list;
}

731
void LinkManager::_updateSerialPorts()
732
{
733 734
    _commPortList.clear();
    _commPortDisplayList.clear();
Gus Grubba's avatar
Gus Grubba committed
735
#ifndef NO_SERIAL_LINK
736 737
    QList<QSerialPortInfo> portList = QSerialPortInfo::availablePorts();
    foreach (const QSerialPortInfo &info, portList)
738
    {
739 740 741
        QString port = info.systemLocation().trimmed();
        _commPortList += port;
        _commPortDisplayList += SerialConfiguration::cleanPortDisplayname(port);
742 743
    }
#endif
744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760
}

QStringList LinkManager::serialPortStrings(void)
{
    if(!_commPortDisplayList.size())
    {
        _updateSerialPorts();
    }
    return _commPortDisplayList;
}

QStringList LinkManager::serialPorts(void)
{
    if(!_commPortList.size())
    {
        _updateSerialPorts();
    }
761 762 763 764 765
    return _commPortList;
}

QStringList LinkManager::serialBaudRates(void)
{
Gus Grubba's avatar
Gus Grubba committed
766
#ifdef NO_SERIAL_LINK
767 768 769 770 771 772
    QStringList foo;
    return foo;
#else
    return SerialConfiguration::supportedBaudRates();
#endif
}
773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791

bool LinkManager::endConfigurationEditing(LinkConfiguration* config, LinkConfiguration* editedConfig)
{
    Q_ASSERT(config != NULL);
    Q_ASSERT(editedConfig != NULL);
    _fixUnnamed(editedConfig);
    config->copyFrom(editedConfig);
    saveLinkConfigurationList();
    // Tell link about changes (if any)
    config->updateSettings();
    // Discard temporary duplicate
    delete editedConfig;
    return true;
}

bool LinkManager::endCreateConfiguration(LinkConfiguration* config)
{
    Q_ASSERT(config != NULL);
    _fixUnnamed(config);
792
    addConfiguration(config);
793 794 795 796 797 798
    saveLinkConfigurationList();
    return true;
}

LinkConfiguration* LinkManager::createConfiguration(int type, const QString& name)
{
Gus Grubba's avatar
Gus Grubba committed
799
#ifndef NO_SERIAL_LINK
800 801
    if((LinkConfiguration::LinkType)type == LinkConfiguration::TypeSerial)
        _updateSerialPorts();
dogmaphobic's avatar
dogmaphobic committed
802
#endif
803 804 805 806 807 808
    return LinkConfiguration::createSettings(type, name);
}

LinkConfiguration* LinkManager::startConfigurationEditing(LinkConfiguration* config)
{
    Q_ASSERT(config != NULL);
Gus Grubba's avatar
Gus Grubba committed
809
#ifndef NO_SERIAL_LINK
810 811
    if(config->type() == LinkConfiguration::TypeSerial)
        _updateSerialPorts();
dogmaphobic's avatar
dogmaphobic committed
812
#endif
813 814 815 816 817 818 819 820 821 822
    return LinkConfiguration::duplicateSettings(config);
}


void LinkManager::_fixUnnamed(LinkConfiguration* config)
{
    Q_ASSERT(config != NULL);
    //-- Check for "Unnamed"
    if (config->name() == "Unnamed") {
        switch(config->type()) {
Gus Grubba's avatar
Gus Grubba committed
823
#ifndef NO_SERIAL_LINK
824 825
            case LinkConfiguration::TypeSerial: {
                QString tname = dynamic_cast<SerialConfiguration*>(config)->portName();
826
#ifdef Q_OS_WIN
827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847
                tname.replace("\\\\.\\", "");
#else
                tname.replace("/dev/cu.", "");
                tname.replace("/dev/", "");
#endif
                config->setName(QString("Serial Device on %1").arg(tname));
                break;
                }
#endif
            case LinkConfiguration::TypeUdp:
                config->setName(
                    QString("UDP Link on Port %1").arg(dynamic_cast<UDPConfiguration*>(config)->localPort()));
                break;
            case LinkConfiguration::TypeTcp: {
                    TCPConfiguration* tconfig = dynamic_cast<TCPConfiguration*>(config);
                    if(tconfig) {
                        config->setName(
                            QString("TCP Link %1:%2").arg(tconfig->address().toString()).arg((int)tconfig->port()));
                    }
                }
                break;
dogmaphobic's avatar
dogmaphobic committed
848
#ifdef QGC_ENABLE_BLUETOOTH
dogmaphobic's avatar
dogmaphobic committed
849 850 851
            case LinkConfiguration::TypeBluetooth: {
                    BluetoothConfiguration* tconfig = dynamic_cast<BluetoothConfiguration*>(config);
                    if(tconfig) {
852
                        config->setName(QString("%1 (Bluetooth Device)").arg(tconfig->device().name));
dogmaphobic's avatar
dogmaphobic committed
853 854 855 856
                    }
                }
                break;
#endif
dogmaphobic's avatar
dogmaphobic committed
857
#ifndef __mobile__
858
            case LinkConfiguration::TypeLogReplay: {
dogmaphobic's avatar
dogmaphobic committed
859 860 861 862
                    LogReplayLinkConfiguration* tconfig = dynamic_cast<LogReplayLinkConfiguration*>(config);
                    if(tconfig) {
                        config->setName(QString("Log Replay %1").arg(tconfig->logFilenameShort()));
                    }
863 864
                }
                break;
dogmaphobic's avatar
dogmaphobic committed
865
#endif
866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885
#ifdef QT_DEBUG
            case LinkConfiguration::TypeMock:
                config->setName(
                    QString("Mock Link"));
                break;
#endif
            case LinkConfiguration::TypeLast:
            default:
                break;
        }
    }
}

void LinkManager::removeConfiguration(LinkConfiguration* config)
{
    Q_ASSERT(config != NULL);
    LinkInterface* iface = config->link();
    if(iface) {
        disconnectLink(iface);
    }
886 887

    _removeConfiguration(config);
888 889
    saveLinkConfigurationList();
}
890

891 892
bool LinkManager::isAutoconnectLink(LinkInterface* link)
{
893 894 895 896 897 898
    for (int i=0; i<_sharedAutoconnectConfigurations.count(); i++) {
        if (_sharedAutoconnectConfigurations[i].data() == link->getLinkConfiguration()) {
            return true;
        }
    }
    return false;
899
}
dogmaphobic's avatar
dogmaphobic committed
900 901 902 903 904

bool LinkManager::isBluetoothAvailable(void)
{
    return qgcApp()->isBluetoothAvailable();
}
Don Gagne's avatar
Don Gagne committed
905

Gus Grubba's avatar
Gus Grubba committed
906
#ifndef NO_SERIAL_LINK
Don Gagne's avatar
Don Gagne committed
907 908
void LinkManager::_activeLinkCheck(void)
{
909
    SerialLink* link = NULL;
Don Gagne's avatar
Don Gagne committed
910 911 912
    bool found = false;

    if (_activeLinkCheckList.count() != 0) {
913
        link = _activeLinkCheckList.takeFirst();
914
        if (containsLink(link) && link->isConnected()) {
Don Gagne's avatar
Don Gagne committed
915 916 917 918 919 920 921 922 923
            // Make sure there is a vehicle on the link
            QmlObjectListModel* vehicles = _toolbox->multiVehicleManager()->vehicles();
            for (int i=0; i<vehicles->count(); i++) {
                Vehicle* vehicle = qobject_cast<Vehicle*>(vehicles->get(i));
                if (vehicle->containsLink(link)) {
                    found = true;
                    break;
                }
            }
924 925
        } else {
            link = NULL;
Don Gagne's avatar
Don Gagne committed
926 927 928 929 930 931 932
        }
    }

    if (_activeLinkCheckList.count() == 0) {
        _activeLinkCheckTimer.stop();
    }

933 934 935
    if (!found && link) {
        // See if we can get an NSH prompt on this link
        bool foundNSHPrompt = false;
936
        link->writeBytesSafe("\r", 1);
937 938 939 940 941 942 943 944 945 946 947
        QSignalSpy spy(link, SIGNAL(bytesReceived(LinkInterface*, QByteArray)));
        if (spy.wait(100)) {
            QList<QVariant> arguments = spy.takeFirst();
            if (arguments[1].value<QByteArray>().contains("nsh>")) {
                foundNSHPrompt = true;
            }
        }

        qgcApp()->showMessage(foundNSHPrompt ?
                                  QStringLiteral("Please check to make sure you have an SD Card inserted in your Vehicle and try again.") :
                                  QStringLiteral("Your Vehicle is not responding. If this continues shutdown QGroundControl, restart the Vehicle letting it boot completely, then start QGroundControl."));
Don Gagne's avatar
Don Gagne committed
948 949
    }
}
Don Gagne's avatar
Don Gagne committed
950
#endif
951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992

bool LinkManager::containsLink(LinkInterface* link)
{
    for (int i=0; i<_sharedLinks.count(); i++) {
        if (_sharedLinks[i].data() == link) {
            return true;
        }
    }
    return false;
}

SharedLinkConfigurationPointer LinkManager::addConfiguration(LinkConfiguration* config)
{
    _qmlConfigurations.append(config);
    _sharedConfigurations.append(SharedLinkConfigurationPointer(config));
    return _sharedConfigurations.last();
}

void LinkManager::_removeConfiguration(LinkConfiguration* config)
{
    _qmlConfigurations.removeOne(config);

    for (int i=0; i<_sharedConfigurations.count(); i++) {
        if (_sharedConfigurations[i].data() == config) {
            _sharedConfigurations.removeAt(i);
            return;
        }
    }

    qWarning() << "LinkManager::_removeConfiguration called with unknown config";
}

QList<LinkInterface*> LinkManager::links(void)
{
    QList<LinkInterface*> rawLinks;

    for (int i=0; i<_sharedLinks.count(); i++) {
        rawLinks.append(_sharedLinks[i].data());
    }

    return rawLinks;
}