QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
MissionCommandUIInfo.cc
Go to the documentation of this file.
2#include "JsonParsing.h"
3#include "FactMetaData.h"
5
6#include <limits>
7
8QGC_LOGGING_CATEGORY(MissionCommandsLog, "Plan.MissionCommands")
9
11 : QObject(parent)
12 , _advanced(false)
13 , _min (FactMetaData::minForType(FactMetaData::valueTypeDouble).toDouble())
14 , _max (FactMetaData::maxForType(FactMetaData::valueTypeDouble).toDouble())
15 , _userMin(std::numeric_limits<double>::quiet_NaN())
16 , _userMax(std::numeric_limits<double>::quiet_NaN())
17{
18
19}
20
22 : QObject(parent)
23{
24 *this = other;
25}
26
28{
29 _decimalPlaces = other._decimalPlaces;
30 _defaultValue = other._defaultValue;
31 _enumStrings = other._enumStrings;
32 _enumValues = other._enumValues;
33 _label = other._label;
34 _param = other._param;
35 _units = other._units;
36 _nanUnchanged = other._nanUnchanged;
37 _advanced = other._advanced;
38 _min = other._min;
39 _max = other._max;
40 _userMin = other._userMin;
41 _userMax = other._userMax;
42
43 return *this;
44}
45
47 : QObject(parent)
48{
49
50}
51
53 : QObject(parent)
54{
55 *this = other;
56}
57
59{
60 _command = other._command;
61 _infoMap = other._infoMap;
62 _paramRemoveList = other._paramRemoveList;
63
64 for (int index: other._paramInfoMap.keys()) {
65 _paramInfoMap[index] = new MissionCmdParamInfo(*other._paramInfoMap[index], this);
66 }
67
68 return *this;
69}
70
72{
73 if (_infoMap.contains(_categoryJsonKey)) {
74 return _infoMap[_categoryJsonKey].toString();
75 } else {
76 return _advancedCategory;
77 }
78}
79
81{
82 if (_infoMap.contains(_descriptionJsonKey)) {
83 return _infoMap[_descriptionJsonKey].toString();
84 } else {
85 return QString();
86 }
87}
88
90{
91 if (_infoMap.contains(_friendlyEditJsonKey)) {
92 return _infoMap[_friendlyEditJsonKey].toBool();
93 } else {
94 return false;
95 }
96}
97
99{
100 if (_infoMap.contains(_friendlyNameJsonKey)) {
101 return _infoMap[_friendlyNameJsonKey].toString();
102 } else {
103 return QString();
104 }
105}
106
108{
109 if (_infoMap.contains(_rawNameJsonKey)) {
110 return _infoMap[_rawNameJsonKey].toString();
111 } else {
112 return QString();
113 }
114}
115
117{
118 if (_infoMap.contains(_standaloneCoordinateJsonKey)) {
119 return _infoMap[_standaloneCoordinateJsonKey].toBool();
120 } else {
121 return false;
122 }
123}
124
126{
127 if (_infoMap.contains(_specifiesCoordinateJsonKey)) {
128 return _infoMap[_specifiesCoordinateJsonKey].toBool();
129 } else {
130 return false;
131 }
132}
133
135{
136 if (_infoMap.contains(_specifiesAltitudeOnlyJsonKey)) {
137 return _infoMap[_specifiesAltitudeOnlyJsonKey].toBool();
138 } else {
139 return false;
140 }
141}
142
144{
145 if (_infoMap.contains(_isLandCommandJsonKey)) {
146 return _infoMap[_isLandCommandJsonKey].toBool();
147 } else {
148 return false;
149 }
150}
151
153{
154 if (_infoMap.contains(_isTakeoffCommandJsonKey)) {
155 return _infoMap[_isTakeoffCommandJsonKey].toBool();
156 } else {
157 return false;
158 }
159}
160
162{
163 if (_infoMap.contains(_isLoiterCommandJsonKey)) {
164 return _infoMap[_isLoiterCommandJsonKey].toBool();
165 } else {
166 return false;
167 }
168}
169
170void MissionCommandUIInfo::_overrideInfo(MissionCommandUIInfo* uiInfo)
171{
172 // Override info values
173 for (const QString& valueKey: uiInfo->_infoMap.keys()) {
174 _setInfoValue(valueKey, uiInfo->_infoMap[valueKey]);
175 }
176
177 // Add to the remove params list
178 for (int removeIndex: uiInfo->_paramRemoveList) {
179 if (!_paramRemoveList.contains(removeIndex)) {
180 _paramRemoveList.append(removeIndex);
181 }
182 }
183
184 // Override param info
185 for (const int paramIndex: uiInfo->_paramInfoMap.keys()) {
186 _paramRemoveList.removeOne(paramIndex);
187 // MissionCmdParamInfo objects are owned by MissionCommandTree are are in existence for the entire run so
188 // we can just use the same pointer reference.
189 _paramInfoMap[paramIndex] = uiInfo->_paramInfoMap[paramIndex];
190 }
191}
192
193QString MissionCommandUIInfo::_loadErrorString(const QString& errorString) const
194{
195 return QString("%1 %2").arg(_infoValue(_rawNameJsonKey).toString()).arg(errorString);
196}
197
198bool MissionCommandUIInfo::loadJsonInfo(const QJsonObject& jsonObject, bool requireFullObject, QString& errorString)
199{
200 QString internalError;
201
202 QStringList allKeys;
203 allKeys << _idJsonKey << _rawNameJsonKey << _friendlyNameJsonKey << _descriptionJsonKey << _standaloneCoordinateJsonKey << _specifiesCoordinateJsonKey
204 <<_friendlyEditJsonKey << _param1JsonKey << _param2JsonKey << _param3JsonKey << _param4JsonKey << _param5JsonKey << _param6JsonKey << _param7JsonKey
205 << _paramRemoveJsonKey << _categoryJsonKey << _specifiesAltitudeOnlyJsonKey << _isLandCommandJsonKey << _isTakeoffCommandJsonKey << _isLoiterCommandJsonKey;
206
207 // Look for unknown keys in top level object
208 for (const QString& key: jsonObject.keys()) {
209 if (!allKeys.contains(key) && key != _commentJsonKey) {
210 errorString = _loadErrorString(QString("Unknown key: %1").arg(key));
211 return false;
212 }
213 }
214
215 // Make sure we have the required keys
216 QStringList requiredKeys;
217 requiredKeys << _idJsonKey;
218 if (requireFullObject) {
219 requiredKeys << _rawNameJsonKey;
220 }
221 if (!JsonParsing::validateRequiredKeys(jsonObject, requiredKeys, internalError)) {
222 errorString = _loadErrorString(internalError);
223 return false;
224 }
225
226 // Only the full object should specify rawName, friendlyName
227 if (!requireFullObject && (jsonObject.contains(_rawNameJsonKey) || jsonObject.contains(_friendlyNameJsonKey))) {
228 errorString = _loadErrorString(QStringLiteral("Only the full object should specify rawName or friendlyName"));
229 return false;
230 }
231
232 // Validate key types
233
234 QList<QJsonValue::Type> types;
235 types << QJsonValue::Double << QJsonValue::String << QJsonValue::String<< QJsonValue::String << QJsonValue::Bool << QJsonValue::Bool << QJsonValue::Bool
236 << QJsonValue::Object << QJsonValue::Object << QJsonValue::Object << QJsonValue::Object << QJsonValue::Object << QJsonValue::Object << QJsonValue::Object
237 << QJsonValue::String << QJsonValue::String << QJsonValue::Bool << QJsonValue::Bool << QJsonValue::Bool << QJsonValue::Bool;
238 if (!JsonParsing::validateKeyTypes(jsonObject, allKeys, types, internalError)) {
239 errorString = _loadErrorString(internalError);
240 return false;
241 }
242
243 // Read in top level values
244
245 _command = (MAV_CMD)jsonObject.value(_idJsonKey).toInt();
246
247 if (jsonObject.contains(_categoryJsonKey)) {
248 _infoMap[_categoryJsonKey] = jsonObject.value(_categoryJsonKey).toVariant();
249 }
250 if (jsonObject.contains(_rawNameJsonKey)) {
251 _infoMap[_rawNameJsonKey] = jsonObject.value(_rawNameJsonKey).toVariant();
252 }
253 if (jsonObject.contains(_friendlyNameJsonKey)) {
254 _infoMap[_friendlyNameJsonKey] = jsonObject.value(_friendlyNameJsonKey).toVariant();
255 }
256 if (jsonObject.contains(_descriptionJsonKey)) {
257 _infoMap[_descriptionJsonKey] = jsonObject.value(_descriptionJsonKey).toVariant();
258 }
259 if (jsonObject.contains(_standaloneCoordinateJsonKey)) {
260 _infoMap[_standaloneCoordinateJsonKey] = jsonObject.value(_standaloneCoordinateJsonKey).toVariant();
261 }
262 if (jsonObject.contains(_specifiesCoordinateJsonKey)) {
263 _infoMap[_specifiesCoordinateJsonKey] = jsonObject.value(_specifiesCoordinateJsonKey).toVariant();
264 }
265 if (jsonObject.contains(_specifiesAltitudeOnlyJsonKey)) {
266 _infoMap[_specifiesAltitudeOnlyJsonKey] = jsonObject.value(_specifiesAltitudeOnlyJsonKey).toBool();
267 }
268 if (jsonObject.contains(_isLandCommandJsonKey)) {
269 _infoMap[_isLandCommandJsonKey] = jsonObject.value(_isLandCommandJsonKey).toBool();
270 }
271 if (jsonObject.contains(_isTakeoffCommandJsonKey)) {
272 _infoMap[_isTakeoffCommandJsonKey] = jsonObject.value(_isTakeoffCommandJsonKey).toBool();
273 }
274 if (jsonObject.contains(_isLoiterCommandJsonKey)) {
275 _infoMap[_isLoiterCommandJsonKey] = jsonObject.value(_isLoiterCommandJsonKey).toBool();
276 }
277 if (jsonObject.contains(_friendlyEditJsonKey)) {
278 _infoMap[_friendlyEditJsonKey] = jsonObject.value(_friendlyEditJsonKey).toVariant();
279 }
280 if (jsonObject.contains(_paramRemoveJsonKey)) {
281 QStringList indexList = jsonObject.value(_paramRemoveJsonKey).toString().split(QStringLiteral(","));
282 for (const QString& indexString: indexList) {
283 _paramRemoveList.append(indexString.toInt());
284 }
285 }
286
287 if (requireFullObject) {
288 // Since this is the base of the hierarchy it must contain valid defaults for all values.
289 if (!_infoAvailable(_categoryJsonKey)) {
290 _setInfoValue(_categoryJsonKey, _advancedCategory);
291 }
292 if (!_infoAvailable(_friendlyNameJsonKey)) {
293 _setInfoValue(_friendlyNameJsonKey, _infoValue(_rawNameJsonKey));
294 }
295 if (!_infoAvailable(_descriptionJsonKey)) {
296 _setInfoValue(_descriptionJsonKey, QStringLiteral(""));
297 }
298 if (!_infoAvailable(_standaloneCoordinateJsonKey)) {
299 _setInfoValue(_standaloneCoordinateJsonKey, false);
300 }
301 if (!_infoAvailable(_specifiesCoordinateJsonKey)) {
302 _setInfoValue(_specifiesCoordinateJsonKey, false);
303 }
304 if (!_infoAvailable(_isLandCommandJsonKey)) {
305 _setInfoValue(_isLandCommandJsonKey, false);
306 }
307 if (!_infoAvailable(_isTakeoffCommandJsonKey)) {
308 _setInfoValue(_isTakeoffCommandJsonKey, false);
309 }
310 if (!_infoAvailable(_isLoiterCommandJsonKey)) {
311 _setInfoValue(_isLoiterCommandJsonKey, false);
312 }
313 if (!_infoAvailable(_friendlyEditJsonKey)) {
314 _setInfoValue(_friendlyEditJsonKey, false);
315 }
316 }
317
318 if (requireFullObject) {
319 if (_infoAvailable(_friendlyEditJsonKey) && _infoValue(_friendlyEditJsonKey).toBool()) {
320 if (!_infoAvailable(_descriptionJsonKey)) {
321 errorString = _loadErrorString(QStringLiteral("Missing description for friendly edit"));
322 return false;
323 }
324 if (_infoValue(_rawNameJsonKey).toString() == _infoValue(_friendlyNameJsonKey).toString()) {
325 errorString = _loadErrorString(QStringLiteral("Missing friendlyName for friendly edit"));
326 return false;
327 }
328 }
329 }
330
331 QString debugOutput;
332 for (const QString& infoKey: _infoMap.keys()) {
333 debugOutput.append(QString("MavCmdInfo %1: %2 ").arg(infoKey).arg(_infoMap[infoKey].toString()));
334 }
335 qCDebug(MissionCommandsLog) << debugOutput;
336
337 // Read params
338
339 for (int i=1; i<=7; i++) {
340 QString paramKey = QString(_paramJsonKeyFormat).arg(i);
341
342 if (jsonObject.contains(paramKey)) {
343 QJsonObject paramObject = jsonObject.value(paramKey).toObject();
344
345 QStringList allParamKeys;
346 allParamKeys << _defaultJsonKey << _decimalPlacesJsonKey << _enumStringsJsonKey << _enumValuesJsonKey
347 << _labelJsonKey << _unitsJsonKey << _nanUnchangedJsonKey
348 << _advancedJsonKey
349 << _minJsonKey << _maxJsonKey << _userMinJsonKey << _userMaxJsonKey;
350
351 // Look for unknown keys in param object
352 for (const QString& key: paramObject.keys()) {
353 if (!allParamKeys.contains(key) && key != _commentJsonKey) {
354 errorString = _loadErrorString(QString("Unknown param key: %1").arg(key));
355 return false;
356 }
357 }
358
359 // Validate key types
360 QList<QJsonValue::Type> paramTypes;
361 paramTypes << QJsonValue::Null << QJsonValue::Double << QJsonValue::String << QJsonValue::String
362 << QJsonValue::String << QJsonValue::String << QJsonValue::Bool
363 << QJsonValue::Bool
364 << QJsonValue::Double << QJsonValue::Double << QJsonValue::Double << QJsonValue::Double;
365 if (!JsonParsing::validateKeyTypes(paramObject, allParamKeys, paramTypes, internalError)) {
366 errorString = _loadErrorString(internalError);
367 return false;
368 }
369
370 _setInfoValue(_friendlyEditJsonKey, true); // Assume friendly edit if we have params
371
372 if (!paramObject.contains(_labelJsonKey)) {
373 internalError = QString("param object missing label key: %1").arg(paramKey);
374 errorString = _loadErrorString(internalError);
375 return false;
376 }
377
378 MissionCmdParamInfo* paramInfo = new MissionCmdParamInfo(this);
379
380 paramInfo->_label = paramObject.value(_labelJsonKey).toString();
381 paramInfo->_decimalPlaces = paramObject.value(_decimalPlacesJsonKey).toInt(FactMetaData::kUnknownDecimalPlaces);
382 paramInfo->_param = i;
383 paramInfo->_units = paramObject.value(_unitsJsonKey).toString();
384 paramInfo->_nanUnchanged = paramObject.value(_nanUnchangedJsonKey).toBool(false);
385 paramInfo->_advanced = paramObject.value(_advancedJsonKey).toBool(false);
386 paramInfo->_enumStrings = FactMetaData::splitTranslatedList(paramObject.value(_enumStringsJsonKey).toString());
387
388 // The min and max values are defaulted correctly already, so only set them if a value is present in the JSON.
389 if (paramObject.value(_minJsonKey).isDouble()) {
390 paramInfo->_min = paramObject.value(_minJsonKey).toDouble();
391 }
392 if (paramObject.value(_maxJsonKey).isDouble()) {
393 paramInfo->_max = paramObject.value(_maxJsonKey).toDouble();
394 }
395 if (paramObject.value(_userMinJsonKey).isDouble()) {
396 paramInfo->_userMin = paramObject.value(_userMinJsonKey).toDouble();
397 }
398 if (paramObject.value(_userMaxJsonKey).isDouble()) {
399 paramInfo->_userMax = paramObject.value(_userMaxJsonKey).toDouble();
400 }
401
402 if (paramObject.contains(_defaultJsonKey)) {
403 if (paramInfo->_nanUnchanged) {
404 paramInfo->_defaultValue = JsonParsing::possibleNaNJsonValue(paramObject[_defaultJsonKey]);
405 } else {
406 if (paramObject[_defaultJsonKey].type() == QJsonValue::Null) {
407 errorString = QString("Param %1 default value was null/NaN but NaN is not allowed");
408 return false;
409 }
410 paramInfo->_defaultValue = paramObject.value(_defaultJsonKey).toDouble(0.0);
411 }
412 } else {
413 paramInfo->_defaultValue = paramInfo->_nanUnchanged ? std::numeric_limits<double>::quiet_NaN() : 0;
414 }
415
416 QStringList enumValues = FactMetaData::splitTranslatedList(paramObject.value(_enumValuesJsonKey).toString()); //Never translated but still useful to use common string splitting code
417 for (const QString &enumValue: enumValues) {
418 bool convertOk;
419 double value = enumValue.toDouble(&convertOk);
420
421 if (!convertOk) {
422 internalError = QString("Bad enumValue: %1").arg(enumValue);
423 errorString = _loadErrorString(internalError);
424 return false;
425 }
426
427 paramInfo->_enumValues << QVariant(value);
428 }
429 if (paramInfo->_enumValues.count() != paramInfo->_enumStrings.count()) {
430 internalError = QStringLiteral("Enum strings/values count mismatch - label: '%1' strings: '%2'[%3] values: '%4'[%5]")
431 .arg(paramInfo->_label).arg(paramObject.value(_enumStringsJsonKey).toString()).arg(paramInfo->_enumStrings.count())
432 .arg(paramObject.value(_enumValuesJsonKey).toString()).arg(paramInfo->_enumValues.count());
433 errorString = _loadErrorString(internalError);
434 return false;
435 }
436
437 qCDebug(MissionCommandsLog) << "Param"
438 << paramInfo->_label
439 << paramInfo->_defaultValue
440 << paramInfo->_decimalPlaces
441 << paramInfo->_param
442 << paramInfo->_units
443 << paramInfo->_enumStrings
444 << paramInfo->_enumValues
445 << paramInfo->_nanUnchanged
446 << paramInfo->_advanced
447 << paramInfo->_min
448 << paramInfo->_max
449 << paramInfo->_userMin
450 << paramInfo->_userMax;
451
452 _paramInfoMap[i] = paramInfo;
453 }
454 }
455
456 return true;
457}
458
459const MissionCmdParamInfo* MissionCommandUIInfo::getParamInfo(int index, bool& showUI) const
460{
461 const MissionCmdParamInfo* paramInfo = nullptr;
462
463 if (_paramInfoMap.contains(index)) {
464 paramInfo = _paramInfoMap[index];
465 }
466
467 showUI = (paramInfo != nullptr) && !_paramRemoveList.contains(index);
468
469 return paramInfo;
470}
QString errorString
#define QGC_LOGGING_CATEGORY(name, categoryStr)
static QStringList splitTranslatedList(const QString &translatedList)
static constexpr int kUnknownDecimalPlaces
Number of decimal places to specify is not known.
const MissionCmdParamInfo & operator=(const MissionCmdParamInfo &other)
MissionCmdParamInfo(QObject *parent=nullptr)
bool loadJsonInfo(const QJsonObject &jsonObject, bool requireFullObject, QString &errorString)
bool isLoiterCommand(void) const
QString category(void) const
bool isTakeoffCommand(void) const
QString description(void) const
QString friendlyName(void) const
bool specifiesCoordinate(void) const
const MissionCmdParamInfo * getParamInfo(int index, bool &showUI) const
bool isStandaloneCoordinate(void) const
QString rawName(void) const
const MissionCommandUIInfo & operator=(const MissionCommandUIInfo &other)
bool specifiesAltitudeOnly(void) const
bool friendlyEdit(void) const
MissionCommandUIInfo(QObject *parent=nullptr)
bool validateKeyTypes(const QJsonObject &jsonObject, const QStringList &keys, const QList< QJsonValue::Type > &types, QString &errorString)
double possibleNaNJsonValue(const QJsonValue &value)
Returns NaN if the value is null, or the value converted to double otherwise.
bool validateRequiredKeys(const QJsonObject &jsonObject, const QStringList &keys, QString &errorString)
Validates that all listed keys are present in the object.