You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
453 lines
18 KiB
453 lines
18 KiB
/**************************************************************************** |
|
* |
|
* (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 "MissionCommandUIInfo.h" |
|
#include "JsonHelper.h" |
|
#include "FactMetaData.h" |
|
#include "QGCLoggingCategory.h" |
|
|
|
const char* MissionCommandUIInfo::_categoryJsonKey = "category"; |
|
const char* MissionCommandUIInfo::_decimalPlacesJsonKey = "decimalPlaces"; |
|
const char* MissionCommandUIInfo::_defaultJsonKey = "default"; |
|
const char* MissionCommandUIInfo::_descriptionJsonKey = "description"; |
|
const char* MissionCommandUIInfo::_enumStringsJsonKey = "enumStrings"; |
|
const char* MissionCommandUIInfo::_enumValuesJsonKey = "enumValues"; |
|
const char* MissionCommandUIInfo::_nanUnchangedJsonKey = "nanUnchanged"; |
|
const char* MissionCommandUIInfo::_friendlyEditJsonKey = "friendlyEdit"; |
|
const char* MissionCommandUIInfo::_friendlyNameJsonKey = "friendlyName"; |
|
const char* MissionCommandUIInfo::_idJsonKey = "id"; |
|
const char* MissionCommandUIInfo::_labelJsonKey = "label"; |
|
const char* MissionCommandUIInfo::_mavCmdInfoJsonKey = "mavCmdInfo"; |
|
const char* MissionCommandUIInfo::_param1JsonKey = "param1"; |
|
const char* MissionCommandUIInfo::_param2JsonKey = "param2"; |
|
const char* MissionCommandUIInfo::_param3JsonKey = "param3"; |
|
const char* MissionCommandUIInfo::_param4JsonKey = "param4"; |
|
const char* MissionCommandUIInfo::_param5JsonKey = "param5"; |
|
const char* MissionCommandUIInfo::_param6JsonKey = "param6"; |
|
const char* MissionCommandUIInfo::_param7JsonKey = "param7"; |
|
const char* MissionCommandUIInfo::_paramJsonKeyFormat = "param%1"; |
|
const char* MissionCommandUIInfo::_paramRemoveJsonKey = "paramRemove"; |
|
const char* MissionCommandUIInfo::_rawNameJsonKey = "rawName"; |
|
const char* MissionCommandUIInfo::_standaloneCoordinateJsonKey = "standaloneCoordinate"; |
|
const char* MissionCommandUIInfo::_specifiesCoordinateJsonKey = "specifiesCoordinate"; |
|
const char* MissionCommandUIInfo::_specifiesAltitudeOnlyJsonKey = "specifiesAltitudeOnly"; |
|
const char* MissionCommandUIInfo::_isLandCommandJsonKey = "isLandCommand"; |
|
const char* MissionCommandUIInfo::_isTakeoffCommandJsonKey = "isTakeoffCommand"; |
|
const char* MissionCommandUIInfo::_unitsJsonKey = "units"; |
|
const char* MissionCommandUIInfo::_commentJsonKey = "comment"; |
|
const char* MissionCommandUIInfo::_advancedCategory = "Advanced"; |
|
|
|
MissionCmdParamInfo::MissionCmdParamInfo(QObject* parent) |
|
: QObject(parent) |
|
{ |
|
|
|
} |
|
|
|
MissionCmdParamInfo::MissionCmdParamInfo(const MissionCmdParamInfo& other, QObject* parent) |
|
: QObject(parent) |
|
{ |
|
*this = other; |
|
} |
|
|
|
const MissionCmdParamInfo& MissionCmdParamInfo::operator=(const MissionCmdParamInfo& other) |
|
{ |
|
_decimalPlaces = other._decimalPlaces; |
|
_defaultValue = other._defaultValue; |
|
_enumStrings = other._enumStrings; |
|
_enumValues = other._enumValues; |
|
_label = other._label; |
|
_param = other._param; |
|
_units = other._units; |
|
_nanUnchanged = other._nanUnchanged; |
|
|
|
return *this; |
|
} |
|
|
|
MissionCommandUIInfo::MissionCommandUIInfo(QObject* parent) |
|
: QObject(parent) |
|
{ |
|
|
|
} |
|
|
|
MissionCommandUIInfo::MissionCommandUIInfo(const MissionCommandUIInfo& other, QObject* parent) |
|
: QObject(parent) |
|
{ |
|
*this = other; |
|
} |
|
|
|
const MissionCommandUIInfo& MissionCommandUIInfo::operator=(const MissionCommandUIInfo& other) |
|
{ |
|
_command = other._command; |
|
_infoMap = other._infoMap; |
|
_paramRemoveList = other._paramRemoveList; |
|
|
|
for (int index: other._paramInfoMap.keys()) { |
|
_paramInfoMap[index] = new MissionCmdParamInfo(*other._paramInfoMap[index], this); |
|
} |
|
|
|
return *this; |
|
} |
|
|
|
QString MissionCommandUIInfo::category(void) const |
|
{ |
|
if (_infoMap.contains(_categoryJsonKey)) { |
|
return _infoMap[_categoryJsonKey].toString(); |
|
} else { |
|
return _advancedCategory; |
|
} |
|
} |
|
|
|
QString MissionCommandUIInfo::description(void) const |
|
{ |
|
if (_infoMap.contains(_descriptionJsonKey)) { |
|
return _infoMap[_descriptionJsonKey].toString(); |
|
} else { |
|
return QString(); |
|
} |
|
} |
|
|
|
bool MissionCommandUIInfo::friendlyEdit(void) const |
|
{ |
|
if (_infoMap.contains(_friendlyEditJsonKey)) { |
|
return _infoMap[_friendlyEditJsonKey].toBool(); |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
QString MissionCommandUIInfo::friendlyName(void) const |
|
{ |
|
if (_infoMap.contains(_friendlyNameJsonKey)) { |
|
return _infoMap[_friendlyNameJsonKey].toString(); |
|
} else { |
|
return QString(); |
|
} |
|
} |
|
|
|
QString MissionCommandUIInfo::rawName(void) const |
|
{ |
|
if (_infoMap.contains(_rawNameJsonKey)) { |
|
return _infoMap[_rawNameJsonKey].toString(); |
|
} else { |
|
return QString(); |
|
} |
|
} |
|
|
|
bool MissionCommandUIInfo::isStandaloneCoordinate(void) const |
|
{ |
|
if (_infoMap.contains(_standaloneCoordinateJsonKey)) { |
|
return _infoMap[_standaloneCoordinateJsonKey].toBool(); |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
bool MissionCommandUIInfo::specifiesCoordinate(void) const |
|
{ |
|
if (_infoMap.contains(_specifiesCoordinateJsonKey)) { |
|
return _infoMap[_specifiesCoordinateJsonKey].toBool(); |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
bool MissionCommandUIInfo::specifiesAltitudeOnly(void) const |
|
{ |
|
if (_infoMap.contains(_specifiesAltitudeOnlyJsonKey)) { |
|
return _infoMap[_specifiesAltitudeOnlyJsonKey].toBool(); |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
bool MissionCommandUIInfo::isLandCommand(void) const |
|
{ |
|
if (_infoMap.contains(_isLandCommandJsonKey)) { |
|
return _infoMap[_isLandCommandJsonKey].toBool(); |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
bool MissionCommandUIInfo::isTakeoffCommand(void) const |
|
{ |
|
if (_infoMap.contains(_isTakeoffCommandJsonKey)) { |
|
return _infoMap[_isTakeoffCommandJsonKey].toBool(); |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
void MissionCommandUIInfo::_overrideInfo(MissionCommandUIInfo* uiInfo) |
|
{ |
|
// Override info values |
|
for (const QString& valueKey: uiInfo->_infoMap.keys()) { |
|
_setInfoValue(valueKey, uiInfo->_infoMap[valueKey]); |
|
} |
|
|
|
// Add to the remove params list |
|
for (int removeIndex: uiInfo->_paramRemoveList) { |
|
if (!_paramRemoveList.contains(removeIndex)) { |
|
_paramRemoveList.append(removeIndex); |
|
} |
|
} |
|
|
|
// Override param info |
|
for (const int paramIndex: uiInfo->_paramInfoMap.keys()) { |
|
_paramRemoveList.removeOne(paramIndex); |
|
// MissionCmdParamInfo objects are owned by MissionCommandTree are are in existence for the entire run so |
|
// we can just use the same pointer reference. |
|
_paramInfoMap[paramIndex] = uiInfo->_paramInfoMap[paramIndex]; |
|
} |
|
} |
|
|
|
QString MissionCommandUIInfo::_loadErrorString(const QString& errorString) const |
|
{ |
|
return QString("%1 %2").arg(_infoValue(_rawNameJsonKey).toString()).arg(errorString); |
|
} |
|
|
|
bool MissionCommandUIInfo::loadJsonInfo(const QJsonObject& jsonObject, bool requireFullObject, QString& errorString) |
|
{ |
|
QString internalError; |
|
|
|
QStringList allKeys; |
|
allKeys << _idJsonKey << _rawNameJsonKey << _friendlyNameJsonKey << _descriptionJsonKey << _standaloneCoordinateJsonKey << _specifiesCoordinateJsonKey |
|
<<_friendlyEditJsonKey << _param1JsonKey << _param2JsonKey << _param3JsonKey << _param4JsonKey << _param5JsonKey << _param6JsonKey << _param7JsonKey |
|
<< _paramRemoveJsonKey << _categoryJsonKey << _specifiesAltitudeOnlyJsonKey << _isLandCommandJsonKey << _isTakeoffCommandJsonKey; |
|
|
|
// Look for unknown keys in top level object |
|
for (const QString& key: jsonObject.keys()) { |
|
if (!allKeys.contains(key) && key != _commentJsonKey) { |
|
errorString = _loadErrorString(QString("Unknown key: %1").arg(key)); |
|
return false; |
|
} |
|
} |
|
|
|
// Make sure we have the required keys |
|
QStringList requiredKeys; |
|
requiredKeys << _idJsonKey; |
|
if (requireFullObject) { |
|
requiredKeys << _rawNameJsonKey; |
|
} |
|
if (!JsonHelper::validateRequiredKeys(jsonObject, requiredKeys, internalError)) { |
|
errorString = _loadErrorString(internalError); |
|
return false; |
|
} |
|
|
|
// Only the full object should specify rawName, friendlyName |
|
if (!requireFullObject && (jsonObject.contains(_rawNameJsonKey) || jsonObject.contains(_friendlyNameJsonKey))) { |
|
errorString = _loadErrorString(QStringLiteral("Only the full object should specify rawName or friendlyName")); |
|
return false; |
|
} |
|
|
|
// Validate key types |
|
|
|
QList<QJsonValue::Type> types; |
|
types << QJsonValue::Double << QJsonValue::String << QJsonValue::String<< QJsonValue::String << QJsonValue::Bool << QJsonValue::Bool << QJsonValue::Bool |
|
<< QJsonValue::Object << QJsonValue::Object << QJsonValue::Object << QJsonValue::Object << QJsonValue::Object << QJsonValue::Object << QJsonValue::Object |
|
<< QJsonValue::String << QJsonValue::String << QJsonValue::Bool << QJsonValue::Bool; |
|
if (!JsonHelper::validateKeyTypes(jsonObject, allKeys, types, internalError)) { |
|
errorString = _loadErrorString(internalError); |
|
return false; |
|
} |
|
|
|
// Read in top level values |
|
|
|
_command = (MAV_CMD)jsonObject.value(_idJsonKey).toInt(); |
|
|
|
if (jsonObject.contains(_categoryJsonKey)) { |
|
_infoMap[_categoryJsonKey] = jsonObject.value(_categoryJsonKey).toVariant(); |
|
} |
|
if (jsonObject.contains(_rawNameJsonKey)) { |
|
_infoMap[_rawNameJsonKey] = jsonObject.value(_rawNameJsonKey).toVariant(); |
|
} |
|
if (jsonObject.contains(_friendlyNameJsonKey)) { |
|
_infoMap[_friendlyNameJsonKey] = jsonObject.value(_friendlyNameJsonKey).toVariant(); |
|
} |
|
if (jsonObject.contains(_descriptionJsonKey)) { |
|
_infoMap[_descriptionJsonKey] = jsonObject.value(_descriptionJsonKey).toVariant(); |
|
} |
|
if (jsonObject.contains(_standaloneCoordinateJsonKey)) { |
|
_infoMap[_standaloneCoordinateJsonKey] = jsonObject.value(_standaloneCoordinateJsonKey).toVariant(); |
|
} |
|
if (jsonObject.contains(_specifiesCoordinateJsonKey)) { |
|
_infoMap[_specifiesCoordinateJsonKey] = jsonObject.value(_specifiesCoordinateJsonKey).toVariant(); |
|
} |
|
if (jsonObject.contains(_specifiesAltitudeOnlyJsonKey)) { |
|
_infoMap[_specifiesAltitudeOnlyJsonKey] = jsonObject.value(_specifiesAltitudeOnlyJsonKey).toBool(); |
|
} |
|
if (jsonObject.contains(_isLandCommandJsonKey)) { |
|
_infoMap[_isLandCommandJsonKey] = jsonObject.value(_isLandCommandJsonKey).toBool(); |
|
} |
|
if (jsonObject.contains(_isTakeoffCommandJsonKey)) { |
|
_infoMap[_isTakeoffCommandJsonKey] = jsonObject.value(_isTakeoffCommandJsonKey).toBool(); |
|
} |
|
if (jsonObject.contains(_friendlyEditJsonKey)) { |
|
_infoMap[_friendlyEditJsonKey] = jsonObject.value(_friendlyEditJsonKey).toVariant(); |
|
} |
|
if (jsonObject.contains(_paramRemoveJsonKey)) { |
|
QStringList indexList = jsonObject.value(_paramRemoveJsonKey).toString().split(QStringLiteral(",")); |
|
for (const QString& indexString: indexList) { |
|
_paramRemoveList.append(indexString.toInt()); |
|
} |
|
} |
|
|
|
if (requireFullObject) { |
|
// Since this is the base of the hierarchy it must contain valid defaults for all values. |
|
if (!_infoAvailable(_categoryJsonKey)) { |
|
_setInfoValue(_categoryJsonKey, _advancedCategory); |
|
} |
|
if (!_infoAvailable(_friendlyNameJsonKey)) { |
|
_setInfoValue(_friendlyNameJsonKey, _infoValue(_rawNameJsonKey)); |
|
} |
|
if (!_infoAvailable(_descriptionJsonKey)) { |
|
_setInfoValue(_descriptionJsonKey, QStringLiteral("")); |
|
} |
|
if (!_infoAvailable(_standaloneCoordinateJsonKey)) { |
|
_setInfoValue(_standaloneCoordinateJsonKey, false); |
|
} |
|
if (!_infoAvailable(_specifiesCoordinateJsonKey)) { |
|
_setInfoValue(_specifiesCoordinateJsonKey, false); |
|
} |
|
if (!_infoAvailable(_isLandCommandJsonKey)) { |
|
_setInfoValue(_isLandCommandJsonKey, false); |
|
} |
|
if (!_infoAvailable(_isTakeoffCommandJsonKey)) { |
|
_setInfoValue(_isTakeoffCommandJsonKey, false); |
|
} |
|
if (!_infoAvailable(_friendlyEditJsonKey)) { |
|
_setInfoValue(_friendlyEditJsonKey, false); |
|
} |
|
} |
|
|
|
if (requireFullObject) { |
|
if (_infoAvailable(_friendlyEditJsonKey) && _infoValue(_friendlyEditJsonKey).toBool()) { |
|
if (!_infoAvailable(_descriptionJsonKey)) { |
|
errorString = _loadErrorString(QStringLiteral("Missing description for friendly edit")); |
|
return false; |
|
} |
|
if (_infoValue(_rawNameJsonKey).toString() == _infoValue(_friendlyNameJsonKey).toString()) { |
|
errorString = _loadErrorString(QStringLiteral("Missing friendlyName for friendly edit")); |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
QString debugOutput; |
|
for (const QString& infoKey: _infoMap.keys()) { |
|
debugOutput.append(QString("MavCmdInfo %1: %2 ").arg(infoKey).arg(_infoMap[infoKey].toString())); |
|
} |
|
qCDebug(MissionCommandsLog) << debugOutput; |
|
|
|
// Read params |
|
|
|
for (int i=1; i<=7; i++) { |
|
QString paramKey = QString(_paramJsonKeyFormat).arg(i); |
|
|
|
if (jsonObject.contains(paramKey)) { |
|
QJsonObject paramObject = jsonObject.value(paramKey).toObject(); |
|
|
|
QStringList allParamKeys; |
|
allParamKeys << _defaultJsonKey << _decimalPlacesJsonKey << _enumStringsJsonKey << _enumValuesJsonKey << _labelJsonKey << _unitsJsonKey << _nanUnchangedJsonKey; |
|
|
|
// Look for unknown keys in param object |
|
for (const QString& key: paramObject.keys()) { |
|
if (!allParamKeys.contains(key) && key != _commentJsonKey) { |
|
errorString = _loadErrorString(QString("Unknown param key: %1").arg(key)); |
|
return false; |
|
} |
|
} |
|
|
|
// Validate key types |
|
QList<QJsonValue::Type> types; |
|
types << QJsonValue::Null << QJsonValue::Double << QJsonValue::String << QJsonValue::String << QJsonValue::String << QJsonValue::String << QJsonValue::Bool; |
|
if (!JsonHelper::validateKeyTypes(jsonObject, allParamKeys, types, internalError)) { |
|
errorString = _loadErrorString(internalError); |
|
return false; |
|
} |
|
|
|
_setInfoValue(_friendlyEditJsonKey, true); // Assume friendly edit if we have params |
|
|
|
if (!paramObject.contains(_labelJsonKey)) { |
|
internalError = QString("param object missing label key: %1").arg(paramKey); |
|
errorString = _loadErrorString(internalError); |
|
return false; |
|
} |
|
|
|
MissionCmdParamInfo* paramInfo = new MissionCmdParamInfo(this); |
|
|
|
paramInfo->_label = paramObject.value(_labelJsonKey).toString(); |
|
paramInfo->_decimalPlaces = paramObject.value(_decimalPlacesJsonKey).toInt(FactMetaData::kUnknownDecimalPlaces); |
|
paramInfo->_enumStrings = paramObject.value(_enumStringsJsonKey).toString().split(",", QString::SkipEmptyParts); |
|
paramInfo->_param = i; |
|
paramInfo->_units = paramObject.value(_unitsJsonKey).toString(); |
|
paramInfo->_nanUnchanged = paramObject.value(_nanUnchangedJsonKey).toBool(false); |
|
|
|
if (paramObject.contains(_defaultJsonKey)) { |
|
if (paramInfo->_nanUnchanged) { |
|
paramInfo->_defaultValue = JsonHelper::possibleNaNJsonValue(paramObject[_defaultJsonKey]); |
|
} else { |
|
if (paramObject[_defaultJsonKey].type() == QJsonValue::Null) { |
|
errorString = QString("Param %1 default value was null/NaN but NaN is not allowed"); |
|
return false; |
|
} |
|
paramInfo->_defaultValue = paramObject.value(_defaultJsonKey).toDouble(0.0); |
|
} |
|
} else { |
|
paramInfo->_defaultValue = paramInfo->_nanUnchanged ? std::numeric_limits<double>::quiet_NaN() : 0; |
|
} |
|
|
|
QStringList enumValues = paramObject.value(_enumValuesJsonKey).toString().split(",", QString::SkipEmptyParts); |
|
for (const QString &enumValue: enumValues) { |
|
bool convertOk; |
|
double value = enumValue.toDouble(&convertOk); |
|
|
|
if (!convertOk) { |
|
internalError = QString("Bad enumValue: %1").arg(enumValue); |
|
errorString = _loadErrorString(internalError); |
|
return false; |
|
} |
|
|
|
paramInfo->_enumValues << QVariant(value); |
|
} |
|
if (paramInfo->_enumValues.count() != paramInfo->_enumStrings.count()) { |
|
internalError = QString("enum strings/values count mismatch: %1, %2").arg(paramInfo->_enumStrings.count()).arg(paramInfo->_enumValues.count()); |
|
errorString = _loadErrorString(internalError); |
|
return false; |
|
} |
|
|
|
qCDebug(MissionCommandsLog) << "Param" |
|
<< paramInfo->_label |
|
<< paramInfo->_defaultValue |
|
<< paramInfo->_decimalPlaces |
|
<< paramInfo->_param |
|
<< paramInfo->_units |
|
<< paramInfo->_enumStrings |
|
<< paramInfo->_enumValues |
|
<< paramInfo->_nanUnchanged; |
|
|
|
_paramInfoMap[i] = paramInfo; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
const MissionCmdParamInfo* MissionCommandUIInfo::getParamInfo(int index, bool& showUI) const |
|
{ |
|
const MissionCmdParamInfo* paramInfo = nullptr; |
|
|
|
if (_paramInfoMap.contains(index)) { |
|
paramInfo = _paramInfoMap[index]; |
|
} |
|
|
|
showUI = (paramInfo != nullptr) && !_paramRemoveList.contains(index); |
|
|
|
return paramInfo; |
|
}
|
|
|