QGCSerialPortInfo.cc 10.4 KB
Newer Older
1 2 3 4 5 6 7 8 9
/****************************************************************************
 *
 *   (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.
 *
 ****************************************************************************/

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 32 33 34 35 36 37
bool         QGCSerialPortInfo::_jsonLoaded =           false;
const char*  QGCSerialPortInfo::_jsonFileTypeValue =    "USBBoardInfo";
const char*  QGCSerialPortInfo::_jsonBoardInfoKey =     "boardInfo";
const char*  QGCSerialPortInfo::_jsonBoardFallbackKey = "boardFallback";
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";

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
38 39
};

40 41 42
QList<QGCSerialPortInfo::BoardInfo_t>       QGCSerialPortInfo::_boardInfoList;
QList<QGCSerialPortInfo::BoardFallback_t>   QGCSerialPortInfo::_boardFallbackList;

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

}

49 50 51 52 53 54
QGCSerialPortInfo::QGCSerialPortInfo(const QSerialPort & port) :
    QSerialPortInfo(port)
{

}

55
void QGCSerialPortInfo::_loadJsonData(void)
56
{
57 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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 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
    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;
    if (!JsonHelper::validateQGCJsonFile(json,
                                         _jsonFileTypeValue,    // expected file type
                                         1,                     // minimum supported version
                                         1,                     // maximum supported version
                                         fileVersion,
                                         errorString)) {
        qWarning() << errorString;
        return;
    }

    // Validate root object keys
    QList<JsonHelper::KeyValidateInfo> rootKeyInfoList = {
        { _jsonBoardInfoKey,        QJsonValue::Array, true },
        { _jsonBoardFallbackKey,    QJsonValue::Array, true },
    };
    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);
136 137
    }

138
    // Load board fallback info used to detect known boards from description string match
139

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

146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
    QJsonArray rgBoardFallback = json[_jsonBoardFallbackKey].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;
        }

        BoardFallback_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;
        }

        _boardFallbackList.append(boardFallback);
    }
}

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;
179
            break;
Don Gagne's avatar
Don Gagne committed
180
        }
181 182
    }

183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
    return BoardTypeUnknown;
}

bool QGCSerialPortInfo::getBoardInfo(QGCSerialPortInfo::BoardType_t& boardType, QString& name) const
{
    _loadJsonData();

    if (isNull()) {
        return false;
    }

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

        if (vendorIdentifier() == boardInfo.vendorId && productIdentifier() == boardInfo.productId) {
            boardType = boardInfo.boardType;
            name = boardInfo.name;
            return true;
        }
    }

204 205 206
    if (boardType == BoardTypeUnknown) {
        // Fall back to port name matching which could lead to incorrect board mapping. But in some cases the
        // vendor and product id do not come through correctly so this is used as a last chance detection method.
207 208 209 210 211 212 213 214
        for (int i=0; i<_boardFallbackList.count(); i++) {
            const BoardFallback_t& boardFallback = _boardFallbackList[i];

            if (description().contains(QRegExp(boardFallback.regExp, Qt::CaseInsensitive))) {
#ifndef __android
                if (boardFallback.androidOnly) {
                    continue;
                }
215
#endif
216 217 218 219
                boardType = boardFallback.boardType;
                name = _boardTypeToString(boardType);
                return true;
            }
220 221 222
        }
    }

223 224
    boardType = BoardTypeUnknown;
    return false;
225 226
}

227 228
QString QGCSerialPortInfo::_boardTypeToString(BoardType_t boardType)
{
Don Gagne's avatar
Don Gagne committed
229 230
    QString unknown = QObject::tr("Unknown");

231 232 233 234 235 236 237 238 239 240 241 242
    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
243
        return unknown;
244
    }
Don Gagne's avatar
Don Gagne committed
245 246

    return unknown;
247 248 249
}


250 251 252 253 254
QList<QGCSerialPortInfo> QGCSerialPortInfo::availablePorts(void)
{
    QList<QGCSerialPortInfo>    list;

    foreach(QSerialPortInfo portInfo, QSerialPortInfo::availablePorts()) {
255 256 257
        if (!isSystemPort(&portInfo)) {
            list << *((QGCSerialPortInfo*)&portInfo);
        }
258 259 260 261 262 263 264
    }

    return list;
}

bool QGCSerialPortInfo::isBootloader(void) const
{
265 266 267 268 269 270 271 272 273
    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;
    }
274
}
275

276
bool QGCSerialPortInfo::isSystemPort(QSerialPortInfo* port)
277
{
278 279 280 281 282 283 284 285 286
    // 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")
287 288 289
        // We open these by their cu.usbserial and cu.usbmodem handles
        // already. We don't want to open them twice and conflict
        // with ourselves.
290 291
        || port->systemLocation().contains("tty.usbserial")
        || port->systemLocation().contains("tty.usbmodem")) {
292 293 294 295 296 297

        return true;
    }
    return false;
}

298 299
bool QGCSerialPortInfo::canFlash(void)
{
300 301
    BoardType_t boardType;
    QString     name;
302

303 304 305 306 307 308 309 310 311 312 313
    if (getBoardInfo(boardType, name)) {
        switch(boardType){
        case QGCSerialPortInfo::BoardTypePixhawk:
        case QGCSerialPortInfo::BoardTypePX4Flow:
        case QGCSerialPortInfo::BoardTypeSiKRadio:
            return true;
        default:
            return false;
        }
    } else {
        return false;
314
    }
315
}