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

10 11

#include "QGCSerialPortInfo.h"
12 13 14 15 16 17
#include "JsonHelper.h"

#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
18 19 20

QGC_LOGGING_CATEGORY(QGCSerialPortInfoLog, "QGCSerialPortInfoLog")

21 22 23 24 25 26 27 28 29 30 31
bool         QGCSerialPortInfo::_jsonLoaded =                       false;
const char*  QGCSerialPortInfo::_jsonFileTypeValue =                "USBBoardInfo";
const char*  QGCSerialPortInfo::_jsonBoardInfoKey =                 "boardInfo";
const char*  QGCSerialPortInfo::_jsonBoardDescriptionFallbackKey =  "boardDescriptionFallback";
const char*  QGCSerialPortInfo::_jsonBoardManufacturerFallbackKey = "boardManufacturerFallback";
const char*  QGCSerialPortInfo::_jsonVendorIDKey =                  "vendorID";
const char*  QGCSerialPortInfo::_jsonProductIDKey =                 "productID";
const char*  QGCSerialPortInfo::_jsonBoardClassKey =                "boardClass";
const char*  QGCSerialPortInfo::_jsonNameKey =                      "name";
const char*  QGCSerialPortInfo::_jsonRegExpKey =                    "regExp";
const char*  QGCSerialPortInfo::_jsonAndroidOnlyKey =               "androidOnly";
32 33 34 35 36 37 38

const QGCSerialPortInfo::BoardClassString2BoardType_t QGCSerialPortInfo::_rgBoardClass2BoardType[] = {
    { "Pixhawk",    QGCSerialPortInfo::BoardTypePixhawk },
    { "PX4 Flow",   QGCSerialPortInfo::BoardTypePX4Flow },
    { "RTK GPS",    QGCSerialPortInfo::BoardTypeRTKGPS },
    { "SiK Radio",  QGCSerialPortInfo::BoardTypeSiKRadio },
    { "OpenPilot",  QGCSerialPortInfo::BoardTypeOpenPilot },
Don Gagne's avatar
Don Gagne committed
39 40
};

41 42 43
QList<QGCSerialPortInfo::BoardInfo_t>           QGCSerialPortInfo::_boardInfoList;
QList<QGCSerialPortInfo::BoardRegExpFallback_t> QGCSerialPortInfo::_boardDescriptionFallbackList;
QList<QGCSerialPortInfo::BoardRegExpFallback_t> QGCSerialPortInfo::_boardManufacturerFallbackList;
44

Don Gagne's avatar
Don Gagne committed
45 46 47 48 49 50
QGCSerialPortInfo::QGCSerialPortInfo(void) :
    QSerialPortInfo()
{

}

51 52 53 54 55 56
QGCSerialPortInfo::QGCSerialPortInfo(const QSerialPort & port) :
    QSerialPortInfo(port)
{

}

57
void QGCSerialPortInfo::_loadJsonData(void)
58
{
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
    if (_jsonLoaded) {
        return;
    }
    _jsonLoaded = true;

    QFile file(QStringLiteral(":/json/USBBoardInfo.json"));

    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qWarning() << "Unable to open board info json:" << file.errorString();
        return;
    }

    QByteArray  bytes = file.readAll();

    QJsonParseError jsonParseError;
    QJsonDocument   jsonDoc(QJsonDocument::fromJson(bytes, &jsonParseError));
    if (jsonParseError.error != QJsonParseError::NoError) {
        qWarning() << "Unable to parse board info json:" << jsonParseError.errorString();
        return;
    }
    QJsonObject json = jsonDoc.object();

    int fileVersion;
    QString errorString;
83 84 85 86 87 88
    if (!JsonHelper::validateInternalQGCJsonFile(json,
                                                 _jsonFileTypeValue,    // expected file type
                                                 1,                     // minimum supported version
                                                 1,                     // maximum supported version
                                                 fileVersion,
                                                 errorString)) {
89 90 91 92 93 94
        qWarning() << errorString;
        return;
    }

    // Validate root object keys
    QList<JsonHelper::KeyValidateInfo> rootKeyInfoList = {
95 96 97
        { _jsonBoardInfoKey,                    QJsonValue::Array, true },
        { _jsonBoardDescriptionFallbackKey,     QJsonValue::Array, true },
        { _jsonBoardManufacturerFallbackKey,    QJsonValue::Array, true },
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
    };
    if (!JsonHelper::validateKeys(json, rootKeyInfoList, errorString)) {
        qWarning() << errorString;
        return;
    }

    // Load board info used to detect known board from vendor/product id

    QList<JsonHelper::KeyValidateInfo> boardKeyInfoList = {
        { _jsonVendorIDKey,     QJsonValue::Double, true },
        { _jsonProductIDKey,    QJsonValue::Double, true },
        { _jsonBoardClassKey,   QJsonValue::String, true },
        { _jsonNameKey,         QJsonValue::String, true },
    };

113
    QJsonArray rgBoardInfo = json[_jsonBoardInfoKey].toArray();
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
    for (int i=0; i<rgBoardInfo.count(); i++) {
        const QJsonValue& jsonValue = rgBoardInfo[i];
        if (!jsonValue.isObject()) {
            qWarning() << "Entry in boardInfo array is not object";
            return;
        }

        QJsonObject  boardObject = jsonValue.toObject();
        if (!JsonHelper::validateKeys(boardObject, boardKeyInfoList, errorString)) {
            qWarning() << errorString;
            return;
        }

        BoardInfo_t boardInfo;
        boardInfo.vendorId = boardObject[_jsonVendorIDKey].toInt();
        boardInfo.productId = boardObject[_jsonProductIDKey].toInt();
        boardInfo.name = boardObject[_jsonNameKey].toString();
        boardInfo.boardType = _boardClassStringToType(boardObject[_jsonBoardClassKey].toString());

        if (boardInfo.boardType == BoardTypeUnknown) {
            qWarning() << "Bad board class" << boardObject[_jsonBoardClassKey].toString();
            return;
        }

        _boardInfoList.append(boardInfo);
139 140
    }

141
    // Load board fallback info used to detect known boards from description string match
142

143 144 145 146 147
    QList<JsonHelper::KeyValidateInfo> fallbackKeyInfoList = {
        { _jsonRegExpKey,       QJsonValue::String, true },
        { _jsonBoardClassKey,   QJsonValue::String, true },
        { _jsonAndroidOnlyKey,  QJsonValue::Bool,   false },
    };
Don Gagne's avatar
Don Gagne committed
148

149
    QJsonArray rgBoardFallback = json[_jsonBoardDescriptionFallbackKey].toArray();
150 151 152 153 154 155 156 157 158 159 160 161 162
    for (int i=0; i<rgBoardFallback.count(); i++) {
        const QJsonValue& jsonValue = rgBoardFallback[i];
        if (!jsonValue.isObject()) {
            qWarning() << "Entry in boardFallback array is not object";
            return;
        }

        QJsonObject  fallbackObject = jsonValue.toObject();
        if (!JsonHelper::validateKeys(fallbackObject, fallbackKeyInfoList, errorString)) {
            qWarning() << errorString;
            return;
        }

163 164
        BoardRegExpFallback_t boardFallback;
        boardFallback.regExp =      fallbackObject[_jsonRegExpKey].toString();
165
        boardFallback.androidOnly = fallbackObject[_jsonAndroidOnlyKey].toBool(false);
166
        boardFallback.boardType =   _boardClassStringToType(fallbackObject[_jsonBoardClassKey].toString());
167 168 169 170 171 172

        if (boardFallback.boardType == BoardTypeUnknown) {
            qWarning() << "Bad board class" << fallbackObject[_jsonBoardClassKey].toString();
            return;
        }

173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
        _boardDescriptionFallbackList.append(boardFallback);
    }

    rgBoardFallback = json[_jsonBoardManufacturerFallbackKey].toArray();
    for (int i=0; i<rgBoardFallback.count(); i++) {
        const QJsonValue& jsonValue = rgBoardFallback[i];
        if (!jsonValue.isObject()) {
            qWarning() << "Entry in boardFallback array is not object";
            return;
        }

        QJsonObject  fallbackObject = jsonValue.toObject();
        if (!JsonHelper::validateKeys(fallbackObject, fallbackKeyInfoList, errorString)) {
            qWarning() << errorString;
            return;
        }

        BoardRegExpFallback_t boardFallback;
        boardFallback.regExp =      fallbackObject[_jsonRegExpKey].toString();
        boardFallback.androidOnly = fallbackObject[_jsonAndroidOnlyKey].toBool(false);
        boardFallback.boardType =   _boardClassStringToType(fallbackObject[_jsonBoardClassKey].toString());

        if (boardFallback.boardType == BoardTypeUnknown) {
            qWarning() << "Bad board class" << fallbackObject[_jsonBoardClassKey].toString();
            return;
        }

        _boardManufacturerFallbackList.append(boardFallback);
201 202 203 204 205 206 207 208
    }
}

QGCSerialPortInfo::BoardType_t QGCSerialPortInfo::_boardClassStringToType(const QString& boardClass)
{
    for (size_t j=0; j<sizeof(_rgBoardClass2BoardType)/sizeof(_rgBoardClass2BoardType[0]); j++) {
        if (boardClass == _rgBoardClass2BoardType[j].classString) {
            return _rgBoardClass2BoardType[j].boardType;
Don Gagne's avatar
Don Gagne committed
209
        }
210 211
    }

212 213 214 215 216
    return BoardTypeUnknown;
}

bool QGCSerialPortInfo::getBoardInfo(QGCSerialPortInfo::BoardType_t& boardType, QString& name) const
{
Don Gagne's avatar
Don Gagne committed
217 218
    boardType = BoardTypeUnknown;

219 220 221 222 223 224 225 226 227
    _loadJsonData();

    if (isNull()) {
        return false;
    }

    for (int i=0; i<_boardInfoList.count(); i++) {
        const BoardInfo_t& boardInfo = _boardInfoList[i];

228
        if (vendorIdentifier() == boardInfo.vendorId && (productIdentifier() == boardInfo.productId || boardInfo.productId == 0)) {
229 230 231 232 233 234
            boardType = boardInfo.boardType;
            name = boardInfo.name;
            return true;
        }
    }

235
    if (boardType == BoardTypeUnknown) {
236 237 238 239
        // Fall back to port description matching and then manufactrure name matching

        for (int i=0; i<_boardDescriptionFallbackList.count(); i++) {
            const BoardRegExpFallback_t& boardFallback = _boardDescriptionFallbackList[i];
240 241 242 243 244 245

            if (description().contains(QRegExp(boardFallback.regExp, Qt::CaseInsensitive))) {
#ifndef __android
                if (boardFallback.androidOnly) {
                    continue;
                }
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
#endif
                boardType = boardFallback.boardType;
                name = _boardTypeToString(boardType);
                return true;
            }
        }

        for (int i=0; i<_boardManufacturerFallbackList.count(); i++) {
            const BoardRegExpFallback_t& boardFallback = _boardManufacturerFallbackList[i];

            if (manufacturer().contains(QRegExp(boardFallback.regExp, Qt::CaseInsensitive))) {
#ifndef __android
                if (boardFallback.androidOnly) {
                    continue;
                }
261
#endif
262 263 264 265
                boardType = boardFallback.boardType;
                name = _boardTypeToString(boardType);
                return true;
            }
266 267 268
        }
    }

269
    return false;
270 271
}

272 273
QString QGCSerialPortInfo::_boardTypeToString(BoardType_t boardType)
{
Don Gagne's avatar
Don Gagne committed
274 275
    QString unknown = QObject::tr("Unknown");

276 277 278 279 280 281 282 283 284 285 286 287
    switch (boardType) {
    case BoardTypePixhawk:
        return QObject::tr("Pixhawk");
    case BoardTypeSiKRadio:
        return QObject::tr("SiK Radio");
    case BoardTypePX4Flow:
        return QObject::tr("PX4 Flow");
    case BoardTypeOpenPilot:
        return QObject::tr("OpenPilot");
    case BoardTypeRTKGPS:
        return QObject::tr("RTK GPS");
    case BoardTypeUnknown:
Don Gagne's avatar
Don Gagne committed
288
        return unknown;
289
    }
Don Gagne's avatar
Don Gagne committed
290 291

    return unknown;
292 293 294
}


295 296
QList<QGCSerialPortInfo> QGCSerialPortInfo::availablePorts(void)
{
297
    typedef QPair<quint16, quint16> VidPidPair_t;
298

299 300 301 302
    QList<QGCSerialPortInfo>        list;
    QMap<VidPidPair_t, QStringList> seenSerialNumbers;

    for (QSerialPortInfo portInfo: QSerialPortInfo::availablePorts()) {
303
        if (!isSystemPort(&portInfo)) {
304 305 306 307 308 309 310 311 312
            if (portInfo.hasVendorIdentifier() && portInfo.hasProductIdentifier() && !portInfo.serialNumber().isEmpty() && portInfo.serialNumber() != "0") {
                VidPidPair_t vidPid(portInfo.vendorIdentifier(), portInfo.productIdentifier());
                if (seenSerialNumbers.contains(vidPid) && seenSerialNumbers[vidPid].contains(portInfo.serialNumber())) {
                    // Some boards are a composite USB device, with the first port being mavlink and the second something else. We only expose to first mavlink port.
                    qCDebug(QGCSerialPortInfoLog) << "Skipping secondary port on same device" << portInfo.portName() << portInfo.vendorIdentifier() << portInfo.productIdentifier() << portInfo.serialNumber();
                    continue;
                }
                seenSerialNumbers[vidPid].append(portInfo.serialNumber());
            }
313 314
            list << *((QGCSerialPortInfo*)&portInfo);
        }
315 316 317 318 319 320 321
    }

    return list;
}

bool QGCSerialPortInfo::isBootloader(void) const
{
322 323 324 325 326 327 328 329 330
    BoardType_t boardType;
    QString     name;

    if (getBoardInfo(boardType, name)) {
        // FIXME: Check SerialLink bootloade detect code which is different
        return boardType == BoardTypePixhawk && description().contains("BL");
    } else {
        return false;
    }
331
}
332

333
bool QGCSerialPortInfo::isSystemPort(QSerialPortInfo* port)
334
{
335 336 337 338 339 340 341 342 343
    // Known operating system peripherals that are NEVER a peripheral
    // that we should connect to.

    // XXX Add Linux (LTE modems, etc) and Windows as needed

    // MAC OS
    if (port->systemLocation().contains("tty.MALS")
        || port->systemLocation().contains("tty.SOC")
        || port->systemLocation().contains("tty.Bluetooth-Incoming-Port")
344 345 346
        // We open these by their cu.usbserial and cu.usbmodem handles
        // already. We don't want to open them twice and conflict
        // with ourselves.
347 348
        || port->systemLocation().contains("tty.usbserial")
        || port->systemLocation().contains("tty.usbmodem")) {
349 350 351 352 353 354

        return true;
    }
    return false;
}

355
bool QGCSerialPortInfo::canFlash(void) const
356
{
357 358
    BoardType_t boardType;
    QString     name;
359

360 361 362 363 364 365 366 367 368 369 370
    if (getBoardInfo(boardType, name)) {
        switch(boardType){
        case QGCSerialPortInfo::BoardTypePixhawk:
        case QGCSerialPortInfo::BoardTypePX4Flow:
        case QGCSerialPortInfo::BoardTypeSiKRadio:
            return true;
        default:
            return false;
        }
    } else {
        return false;
371
    }
372
}