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 113 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
    };
    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 },
    };

    QJsonArray rgBoardInfo = json[_jsonBoardInfoKey].toArray();
    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
}