Newer
Older
/****************************************************************************
*
* (c) 2009-2020 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.
*
****************************************************************************/
#include "JsonHelper.h"
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
QGC_LOGGING_CATEGORY(QGCSerialPortInfoLog, "QGCSerialPortInfoLog")
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";
const QGCSerialPortInfo::BoardClassString2BoardType_t QGCSerialPortInfo::_rgBoardClass2BoardType[] = {
{ "Pixhawk", QGCSerialPortInfo::BoardTypePixhawk },
{ "PX4 Flow", QGCSerialPortInfo::BoardTypePX4Flow },
{ "RTK GPS", QGCSerialPortInfo::BoardTypeRTKGPS },
{ "SiK Radio", QGCSerialPortInfo::BoardTypeSiKRadio },
{ "OpenPilot", QGCSerialPortInfo::BoardTypeOpenPilot },
QList<QGCSerialPortInfo::BoardInfo_t> QGCSerialPortInfo::_boardInfoList;
QList<QGCSerialPortInfo::BoardRegExpFallback_t> QGCSerialPortInfo::_boardDescriptionFallbackList;
QList<QGCSerialPortInfo::BoardRegExpFallback_t> QGCSerialPortInfo::_boardManufacturerFallbackList;
QGCSerialPortInfo::QGCSerialPortInfo(void) :
QSerialPortInfo()
{
}
QGCSerialPortInfo::QGCSerialPortInfo(const QSerialPort & port) :
QSerialPortInfo(port)
{
}
void QGCSerialPortInfo::_loadJsonData(void)
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::validateInternalQGCJsonFile(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 },
{ _jsonBoardDescriptionFallbackKey, QJsonValue::Array, true },
{ _jsonBoardManufacturerFallbackKey, 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);
// Load board fallback info used to detect known boards from description string match
QList<JsonHelper::KeyValidateInfo> fallbackKeyInfoList = {
{ _jsonRegExpKey, QJsonValue::String, true },
{ _jsonBoardClassKey, QJsonValue::String, true },
{ _jsonAndroidOnlyKey, QJsonValue::Bool, false },
};
QJsonArray rgBoardFallback = json[_jsonBoardDescriptionFallbackKey].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;
}
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);
}
}
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;
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 || boardInfo.productId == 0)) {
boardType = boardInfo.boardType;
name = boardInfo.name;
return true;
}
}
// 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];
if (description().contains(QRegExp(boardFallback.regExp, Qt::CaseInsensitive))) {
#ifndef __android
if (boardFallback.androidOnly) {
continue;
}
#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;
}
boardType = boardFallback.boardType;
name = _boardTypeToString(boardType);
return true;
}
QString QGCSerialPortInfo::_boardTypeToString(BoardType_t boardType)
{
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:
QList<QGCSerialPortInfo> QGCSerialPortInfo::availablePorts(void)
{
typedef QPair<quint16, quint16> VidPidPair_t;
QList<QGCSerialPortInfo> list;
QMap<VidPidPair_t, QStringList> seenSerialNumbers;
for (QSerialPortInfo portInfo: QSerialPortInfo::availablePorts()) {
if (!isSystemPort(&portInfo)) {
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());
}
list << *((QGCSerialPortInfo*)&portInfo);
}
}
return list;
}
bool QGCSerialPortInfo::isBootloader(void) const
{
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;
}
bool QGCSerialPortInfo::isSystemPort(QSerialPortInfo* port)
// 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")
// We open these by their cu.usbserial and cu.usbmodem handles
// already. We don't want to open them twice and conflict
// with ourselves.
|| port->systemLocation().contains("tty.usbserial")
|| port->systemLocation().contains("tty.usbmodem")) {
return true;
}
return false;
}
bool QGCSerialPortInfo::canFlash(void) const
BoardType_t boardType;
QString name;
if (getBoardInfo(boardType, name)) {
switch(boardType){
case QGCSerialPortInfo::BoardTypePixhawk:
case QGCSerialPortInfo::BoardTypePX4Flow:
case QGCSerialPortInfo::BoardTypeSiKRadio:
return true;
default:
return false;
}
} else {
return false;