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 const QString rawName = _infoValue(_rawNameJsonKey).toString();
196 if (!rawName.isEmpty()) {
197 return QStringLiteral("%1 %2").arg(rawName, errorString);
198 }
199 // Override entries have no rawName, so fall back to the command id
200 if (_commandIdLoaded) {
201 return QStringLiteral("MAV_CMD(%1) %2").arg(QString::number(static_cast<int>(_command)), errorString);
202 }
203 return errorString;
204}
205
206bool MissionCommandUIInfo::loadJsonInfo(const QJsonObject& jsonObject, bool requireFullObject, QString& errorString)
207{
208 QString internalError;
209
210 const QJsonValue idValue = jsonObject.value(_idJsonKey);
211 _command = static_cast<MAV_CMD>(idValue.toInt());
212 _commandIdLoaded = idValue.toInt(-1) >= 0;
213
214 QStringList allKeys;
215 allKeys << _idJsonKey << _rawNameJsonKey << _friendlyNameJsonKey << _descriptionJsonKey << _standaloneCoordinateJsonKey << _specifiesCoordinateJsonKey
216 <<_friendlyEditJsonKey << _param1JsonKey << _param2JsonKey << _param3JsonKey << _param4JsonKey << _param5JsonKey << _param6JsonKey << _param7JsonKey
217 << _paramRemoveJsonKey << _categoryJsonKey << _specifiesAltitudeOnlyJsonKey << _isLandCommandJsonKey << _isTakeoffCommandJsonKey << _isLoiterCommandJsonKey;
218
219 // Look for unknown keys in top level object
220 for (const QString& key: jsonObject.keys()) {
221 if (!allKeys.contains(key) && key != _commentJsonKey) {
222 errorString = _loadErrorString(QString("Unknown key: %1").arg(key));
223 return false;
224 }
225 }
226
227 // Make sure we have the required keys
228 QStringList requiredKeys;
229 requiredKeys << _idJsonKey;
230 if (requireFullObject) {
231 requiredKeys << _rawNameJsonKey;
232 }
233 if (!JsonParsing::validateRequiredKeys(jsonObject, requiredKeys, internalError)) {
234 errorString = _loadErrorString(internalError);
235 return false;
236 }
237
238 // Only the full object should specify rawName, friendlyName
239 if (!requireFullObject && (jsonObject.contains(_rawNameJsonKey) || jsonObject.contains(_friendlyNameJsonKey))) {
240 errorString = _loadErrorString(QStringLiteral("Only the full object should specify rawName or friendlyName"));
241 return false;
242 }
243
244 // Validate key types
245
246 QList<QJsonValue::Type> types;
247 types << QJsonValue::Double << QJsonValue::String << QJsonValue::String<< QJsonValue::String << QJsonValue::Bool << QJsonValue::Bool << QJsonValue::Bool
248 << QJsonValue::Object << QJsonValue::Object << QJsonValue::Object << QJsonValue::Object << QJsonValue::Object << QJsonValue::Object << QJsonValue::Object
249 << QJsonValue::String << QJsonValue::String << QJsonValue::Bool << QJsonValue::Bool << QJsonValue::Bool << QJsonValue::Bool;
250 if (!JsonParsing::validateKeyTypes(jsonObject, allKeys, types, internalError)) {
251 errorString = _loadErrorString(internalError);
252 return false;
253 }
254
255 // Read in top level values
256
257 if (jsonObject.contains(_categoryJsonKey)) {
258 _infoMap[_categoryJsonKey] = jsonObject.value(_categoryJsonKey).toVariant();
259 }
260 if (jsonObject.contains(_rawNameJsonKey)) {
261 _infoMap[_rawNameJsonKey] = jsonObject.value(_rawNameJsonKey).toVariant();
262 }
263 if (jsonObject.contains(_friendlyNameJsonKey)) {
264 _infoMap[_friendlyNameJsonKey] = jsonObject.value(_friendlyNameJsonKey).toVariant();
265 }
266 if (jsonObject.contains(_descriptionJsonKey)) {
267 _infoMap[_descriptionJsonKey] = jsonObject.value(_descriptionJsonKey).toVariant();
268 }
269 if (jsonObject.contains(_standaloneCoordinateJsonKey)) {
270 _infoMap[_standaloneCoordinateJsonKey] = jsonObject.value(_standaloneCoordinateJsonKey).toVariant();
271 }
272 if (jsonObject.contains(_specifiesCoordinateJsonKey)) {
273 _infoMap[_specifiesCoordinateJsonKey] = jsonObject.value(_specifiesCoordinateJsonKey).toVariant();
274 }
275 if (jsonObject.contains(_specifiesAltitudeOnlyJsonKey)) {
276 _infoMap[_specifiesAltitudeOnlyJsonKey] = jsonObject.value(_specifiesAltitudeOnlyJsonKey).toBool();
277 }
278 if (jsonObject.contains(_isLandCommandJsonKey)) {
279 _infoMap[_isLandCommandJsonKey] = jsonObject.value(_isLandCommandJsonKey).toBool();
280 }
281 if (jsonObject.contains(_isTakeoffCommandJsonKey)) {
282 _infoMap[_isTakeoffCommandJsonKey] = jsonObject.value(_isTakeoffCommandJsonKey).toBool();
283 }
284 if (jsonObject.contains(_isLoiterCommandJsonKey)) {
285 _infoMap[_isLoiterCommandJsonKey] = jsonObject.value(_isLoiterCommandJsonKey).toBool();
286 }
287 if (jsonObject.contains(_friendlyEditJsonKey)) {
288 _infoMap[_friendlyEditJsonKey] = jsonObject.value(_friendlyEditJsonKey).toVariant();
289 }
290 if (jsonObject.contains(_paramRemoveJsonKey)) {
291 QStringList indexList = jsonObject.value(_paramRemoveJsonKey).toString().split(QStringLiteral(","));
292 for (const QString& indexString: indexList) {
293 _paramRemoveList.append(indexString.toInt());
294 }
295 }
296
297 if (requireFullObject) {
298 // Since this is the base of the hierarchy it must contain valid defaults for all values.
299 if (!_infoAvailable(_categoryJsonKey)) {
300 _setInfoValue(_categoryJsonKey, _advancedCategory);
301 }
302 if (!_infoAvailable(_friendlyNameJsonKey)) {
303 _setInfoValue(_friendlyNameJsonKey, _infoValue(_rawNameJsonKey));
304 }
305 if (!_infoAvailable(_descriptionJsonKey)) {
306 _setInfoValue(_descriptionJsonKey, QStringLiteral(""));
307 }
308 if (!_infoAvailable(_standaloneCoordinateJsonKey)) {
309 _setInfoValue(_standaloneCoordinateJsonKey, false);
310 }
311 if (!_infoAvailable(_specifiesCoordinateJsonKey)) {
312 _setInfoValue(_specifiesCoordinateJsonKey, false);
313 }
314 if (!_infoAvailable(_isLandCommandJsonKey)) {
315 _setInfoValue(_isLandCommandJsonKey, false);
316 }
317 if (!_infoAvailable(_isTakeoffCommandJsonKey)) {
318 _setInfoValue(_isTakeoffCommandJsonKey, false);
319 }
320 if (!_infoAvailable(_isLoiterCommandJsonKey)) {
321 _setInfoValue(_isLoiterCommandJsonKey, false);
322 }
323 if (!_infoAvailable(_friendlyEditJsonKey)) {
324 _setInfoValue(_friendlyEditJsonKey, false);
325 }
326 }
327
328 if (requireFullObject) {
329 if (_infoAvailable(_friendlyEditJsonKey) && _infoValue(_friendlyEditJsonKey).toBool()) {
330 if (!_infoAvailable(_descriptionJsonKey)) {
331 errorString = _loadErrorString(QStringLiteral("Missing description for friendly edit"));
332 return false;
333 }
334 if (_infoValue(_rawNameJsonKey).toString() == _infoValue(_friendlyNameJsonKey).toString()) {
335 errorString = _loadErrorString(QStringLiteral("Missing friendlyName for friendly edit"));
336 return false;
337 }
338 }
339 }
340
341 QString debugOutput;
342 for (const QString& infoKey: _infoMap.keys()) {
343 debugOutput.append(QString("MavCmdInfo %1: %2 ").arg(infoKey).arg(_infoMap[infoKey].toString()));
344 }
345 qCDebug(MissionCommandsLog) << debugOutput;
346
347 // Read params
348
349 for (int i=1; i<=7; i++) {
350 QString paramKey = QString(_paramJsonKeyFormat).arg(i);
351
352 if (jsonObject.contains(paramKey)) {
353 QJsonObject paramObject = jsonObject.value(paramKey).toObject();
354
355 QStringList allParamKeys;
356 allParamKeys << _defaultJsonKey << _decimalPlacesJsonKey << _enumStringsJsonKey << _enumValuesJsonKey
357 << _labelJsonKey << _unitsJsonKey << _nanUnchangedJsonKey
358 << _advancedJsonKey
359 << _minJsonKey << _maxJsonKey << _userMinJsonKey << _userMaxJsonKey;
360
361 // Look for unknown keys in param object
362 for (const QString& key: paramObject.keys()) {
363 if (!allParamKeys.contains(key) && key != _commentJsonKey) {
364 errorString = _loadErrorString(QString("Unknown param key: %1").arg(key));
365 return false;
366 }
367 }
368
369 // Validate key types
370 QList<QJsonValue::Type> paramTypes;
371 paramTypes << QJsonValue::Null << QJsonValue::Double << QJsonValue::String << QJsonValue::String
372 << QJsonValue::String << QJsonValue::String << QJsonValue::Bool
373 << QJsonValue::Bool
374 << QJsonValue::Double << QJsonValue::Double << QJsonValue::Double << QJsonValue::Double;
375 if (!JsonParsing::validateKeyTypes(paramObject, allParamKeys, paramTypes, internalError)) {
376 errorString = _loadErrorString(internalError);
377 return false;
378 }
379
380 _setInfoValue(_friendlyEditJsonKey, true); // Assume friendly edit if we have params
381
382 if (!paramObject.contains(_labelJsonKey)) {
383 internalError = QString("param object missing label key: %1").arg(paramKey);
384 errorString = _loadErrorString(internalError);
385 return false;
386 }
387
388 MissionCmdParamInfo* paramInfo = new MissionCmdParamInfo(this);
389
390 paramInfo->_label = paramObject.value(_labelJsonKey).toString();
391 paramInfo->_decimalPlaces = paramObject.value(_decimalPlacesJsonKey).toInt(FactMetaData::kUnknownDecimalPlaces);
392 paramInfo->_param = i;
393 paramInfo->_units = paramObject.value(_unitsJsonKey).toString();
394 paramInfo->_nanUnchanged = paramObject.value(_nanUnchangedJsonKey).toBool(false);
395 paramInfo->_advanced = paramObject.value(_advancedJsonKey).toBool(false);
396 paramInfo->_enumStrings = FactMetaData::splitTranslatedList(paramObject.value(_enumStringsJsonKey).toString());
397
398 // The min and max values are defaulted correctly already, so only set them if a value is present in the JSON.
399 if (paramObject.value(_minJsonKey).isDouble()) {
400 paramInfo->_min = paramObject.value(_minJsonKey).toDouble();
401 }
402 if (paramObject.value(_maxJsonKey).isDouble()) {
403 paramInfo->_max = paramObject.value(_maxJsonKey).toDouble();
404 }
405 if (paramObject.value(_userMinJsonKey).isDouble()) {
406 paramInfo->_userMin = paramObject.value(_userMinJsonKey).toDouble();
407 }
408 if (paramObject.value(_userMaxJsonKey).isDouble()) {
409 paramInfo->_userMax = paramObject.value(_userMaxJsonKey).toDouble();
410 }
411
412 if (paramObject.contains(_defaultJsonKey)) {
413 if (paramInfo->_nanUnchanged) {
414 paramInfo->_defaultValue = JsonParsing::possibleNaNJsonValue(paramObject[_defaultJsonKey]);
415 } else {
416 if (paramObject[_defaultJsonKey].type() == QJsonValue::Null) {
417 errorString = QString("Param %1 default value was null/NaN but NaN is not allowed");
418 return false;
419 }
420 paramInfo->_defaultValue = paramObject.value(_defaultJsonKey).toDouble(0.0);
421 }
422 } else {
423 paramInfo->_defaultValue = paramInfo->_nanUnchanged ? std::numeric_limits<double>::quiet_NaN() : 0;
424 }
425
426 QStringList enumValues = FactMetaData::splitTranslatedList(paramObject.value(_enumValuesJsonKey).toString()); //Never translated but still useful to use common string splitting code
427 for (const QString &enumValue: enumValues) {
428 bool convertOk;
429 double value = enumValue.toDouble(&convertOk);
430
431 if (!convertOk) {
432 internalError = QString("Bad enumValue: %1").arg(enumValue);
433 errorString = _loadErrorString(internalError);
434 return false;
435 }
436
437 paramInfo->_enumValues << QVariant(value);
438 }
439 if (paramInfo->_enumValues.count() != paramInfo->_enumStrings.count()) {
440 internalError = QStringLiteral("Enum strings/values count mismatch - label: '%1' strings: '%2'[%3] values: '%4'[%5]")
441 .arg(paramInfo->_label).arg(paramObject.value(_enumStringsJsonKey).toString()).arg(paramInfo->_enumStrings.count())
442 .arg(paramObject.value(_enumValuesJsonKey).toString()).arg(paramInfo->_enumValues.count());
443 errorString = _loadErrorString(internalError);
444 return false;
445 }
446
447 qCDebug(MissionCommandsLog) << "Param"
448 << paramInfo->_label
449 << paramInfo->_defaultValue
450 << paramInfo->_decimalPlaces
451 << paramInfo->_param
452 << paramInfo->_units
453 << paramInfo->_enumStrings
454 << paramInfo->_enumValues
455 << paramInfo->_nanUnchanged
456 << paramInfo->_advanced
457 << paramInfo->_min
458 << paramInfo->_max
459 << paramInfo->_userMin
460 << paramInfo->_userMax;
461
462 _paramInfoMap[i] = paramInfo;
463 }
464 }
465
466 return true;
467}
468
469const MissionCmdParamInfo* MissionCommandUIInfo::getParamInfo(int index, bool& showUI) const
470{
471 const MissionCmdParamInfo* paramInfo = nullptr;
472
473 if (_paramInfoMap.contains(index)) {
474 paramInfo = _paramInfoMap[index];
475 }
476
477 showUI = (paramInfo != nullptr) && !_paramRemoveList.contains(index);
478
479 return paramInfo;
480}
QString errorString
#define QGC_LOGGING_CATEGORY(name, categoryStr)
std::array< MsgOutKey, 4 > keys
Definition UBXPlan.cc:13
Holds the meta data associated with a Fact.
static QStringList splitTranslatedList(const QString &translatedList)
static constexpr int kUnknownDecimalPlaces
Number of decimal places to specify is not known.
UI Information associated with a mission command (MAV_CMD) parameter.
const MissionCmdParamInfo & operator=(const MissionCmdParamInfo &other)
MissionCmdParamInfo(QObject *parent=nullptr)
UI Information associated with a mission command (MAV_CMD)
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.