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
    return BoardTypeUnknown;
}

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

190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
    _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;
        }
    }

206 207 208
    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.
209 210 211 212 213 214 215 216
        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;
                }
217
#endif
218 219 220 221
                boardType = boardFallback.boardType;
                name = _boardTypeToString(boardType);
                return true;
            }
222 223 224
        }
    }

225
    return false;
226 227
}

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

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

    return unknown;
248 249 250
}


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

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

    return list;
}

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

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

        return true;
    }
    return false;
}

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

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