QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
PX4ParameterMetaData.cc
Go to the documentation of this file.
3
4#include <QtCore/QFile>
5#include <QtCore/QDir>
6#include <QtCore/QDebug>
7#include <QtCore/QXmlStreamReader>
8
9QGC_LOGGING_CATEGORY(PX4ParameterMetaDataLog, "FirmwarePlugin.PX4ParameterMetaData")
10
12 : QObject(parent)
13{
14
15}
16
22QVariant PX4ParameterMetaData::_stringToTypedVariant(const QString& string, FactMetaData::ValueType_t type, bool* convertOk)
23{
24 QVariant var(string);
25
26 QMetaType::Type convertTo = QMetaType::Int; // keep compiler warning happy
27 switch (type) {
32 convertTo = QMetaType::UInt;
33 break;
38 convertTo = QMetaType::Int;
39 break;
41 convertTo = QMetaType::Float;
42 break;
45 convertTo = QMetaType::Double;
46 break;
48 qWarning() << kInvalidConverstion;
49 convertTo = QMetaType::QString;
50 break;
52 qWarning() << kInvalidConverstion;
53 convertTo = QMetaType::Bool;
54 break;
56 qWarning() << kInvalidConverstion;
57 convertTo = QMetaType::QByteArray;
58 break;
59 }
60
61 *convertOk = var.convert(QMetaType(convertTo));
62
63 return var;
64}
65
67{
68 qCDebug(PX4ParameterMetaDataLog) << "PX4ParameterMetaData::loadParameterFactMetaDataFile" << metaDataFile;
69
70 if (_parameterMetaDataLoaded) {
71 qWarning() << "Internal error: parameter meta data loaded more than once";
72 return;
73 }
74 _parameterMetaDataLoaded = true;
75
76 qCDebug(PX4ParameterMetaDataLog) << "Loading parameter meta data:" << metaDataFile;
77
78 QFile xmlFile(metaDataFile);
79
80 if (!xmlFile.exists()) {
81 qWarning() << "Internal error: metaDataFile mission" << metaDataFile;
82 return;
83 }
84
85 if (!xmlFile.open(QIODevice::ReadOnly)) {
86 qWarning() << "Internal error: Unable to open parameter file:" << metaDataFile << xmlFile.errorString();
87 return;
88 }
89
90 QXmlStreamReader xml(xmlFile.readAll());
91 xmlFile.close();
92 if (xml.hasError()) {
93 qWarning() << "Badly formed XML" << xml.errorString();
94 return;
95 }
96
97 QString factGroup;
98 QString errorString;
99 FactMetaData* metaData = nullptr;
100 int xmlState = XmlStateNone;
101 bool badMetaData = true;
102
103 while (!xml.atEnd()) {
104 if (xml.isStartElement()) {
105 QString elementName = xml.name().toString();
106
107 if (elementName == "parameters") {
108 if (xmlState != XmlStateNone) {
109 qWarning() << "Badly formed XML";
110 return;
111 }
112 xmlState = XmlStateFoundParameters;
113
114 } else if (elementName == "version") {
115 if (xmlState != XmlStateFoundParameters) {
116 qWarning() << "Badly formed XML";
117 return;
118 }
119 xmlState = XmlStateFoundVersion;
120
121 bool convertOk;
122 QString strVersion = xml.readElementText();
123 int intVersion = strVersion.toInt(&convertOk);
124 if (!convertOk) {
125 qWarning() << "Badly formed XML";
126 return;
127 }
128 if (intVersion <= 2) {
129 // We can't read these old files
130 qDebug() << "Parameter version stamp too old, skipping load. Found:" << intVersion << "Want: 3 File:" << metaDataFile;
131 return;
132 }
133
134 } else if (elementName == "parameter_version_major") {
135 // Just skip over for now
136 } else if (elementName == "parameter_version_minor") {
137 // Just skip over for now
138
139 } else if (elementName == "group") {
140 if (xmlState != XmlStateFoundVersion) {
141 // We didn't get a version stamp, assume older version we can't read
142 qDebug() << "Parameter version stamp not found, skipping load" << metaDataFile;
143 return;
144 }
145 xmlState = XmlStateFoundGroup;
146
147 if (!xml.attributes().hasAttribute("name")) {
148 qWarning() << "Badly formed XML";
149 return;
150 }
151 factGroup = xml.attributes().value("name").toString();
152 qCDebug(PX4ParameterMetaDataLog) << "Found group: " << factGroup;
153
154 } else if (elementName == "parameter") {
155 if (xmlState != XmlStateFoundGroup) {
156 qWarning() << "Badly formed XML";
157 return;
158 }
159 xmlState = XmlStateFoundParameter;
160
161 if (!xml.attributes().hasAttribute("name") || !xml.attributes().hasAttribute("type")) {
162 qWarning() << "Badly formed XML";
163 return;
164 }
165
166 QString name = xml.attributes().value("name").toString();
167 QString type = xml.attributes().value("type").toString();
168 QString strDefault = xml.attributes().value("default").toString();
169
170 QString category = xml.attributes().value("category").toString();
171 if (category.isEmpty()) {
172 category = QStringLiteral("Standard");
173 }
174
175 bool volatileValue = false;
176 bool readOnly = false;
177 QString volatileStr = xml.attributes().value("volatile").toString();
178 if (volatileStr.compare(QStringLiteral("true")) == 0) {
179 volatileValue = true;
180 readOnly = true;
181 }
182 if (!volatileValue) {
183 QString readOnlyStr = xml.attributes().value("readonly").toString();
184 if (readOnlyStr.compare(QStringLiteral("true")) == 0) {
185 readOnly = true;
186 }
187 }
188
189 qCDebug(PX4ParameterMetaDataLog) << "Found parameter name:" << name << " type:" << type << " default:" << strDefault;
190
191 // Convert type from string to FactMetaData::ValueType_t
192 bool unknownType;
193 FactMetaData::ValueType_t foundType = FactMetaData::stringToType(type, unknownType);
194 if (unknownType) {
195 qWarning() << "Parameter meta data with bad type:" << type << " name:" << name;
196 return;
197 }
198
199 // Now that we know type we can create meta data object and add it to the system
200 metaData = new FactMetaData(foundType, this);
201 if (_mapParameterName2FactMetaData.contains(name)) {
202 // We can't trust the meta data since we have dups
203 qCWarning(PX4ParameterMetaDataLog) << "Duplicate parameter found:" << name;
204 badMetaData = true;
205 // Reset to default meta data
206 _mapParameterName2FactMetaData[name] = metaData;
207 } else {
208 _mapParameterName2FactMetaData[name] = metaData;
209 metaData->setName(name);
210 metaData->setCategory(category);
211 metaData->setGroup(factGroup);
212 metaData->setReadOnly(readOnly);
213 metaData->setVolatileValue(volatileValue);
214
215 if (xml.attributes().hasAttribute("default") && !strDefault.isEmpty()) {
216 QVariant varDefault;
217
218 if (metaData->convertAndValidateRaw(strDefault, false, varDefault, errorString)) {
219 metaData->setRawDefaultValue(varDefault);
220 } else {
221 qCWarning(PX4ParameterMetaDataLog) << "Invalid default value, name:" << name << " type:" << type << " default:" << strDefault << " error:" << errorString;
222 }
223 }
224 }
225
226 } else {
227 // We should be getting meta data now
228 if (xmlState != XmlStateFoundParameter) {
229 qWarning() << "Badly formed XML";
230 return;
231 }
232
233 if (!badMetaData) {
234 if (metaData) {
235 if (elementName == "short_desc") {
236 QString text = xml.readElementText();
237 text = text.replace("\n", " ");
238 qCDebug(PX4ParameterMetaDataLog) << "Short description:" << text;
239 metaData->setShortDescription(text);
240
241 } else if (elementName == "long_desc") {
242 QString text = xml.readElementText();
243 text = text.replace("\n", " ");
244 qCDebug(PX4ParameterMetaDataLog) << "Long description:" << text;
245 metaData->setLongDescription(text);
246
247 } else if (elementName == "min") {
248 QString text = xml.readElementText();
249 qCDebug(PX4ParameterMetaDataLog) << "Min:" << text;
250
251 QVariant varMin;
252 if (metaData->convertAndValidateRaw(text, false /* convertOnly */, varMin, errorString)) {
253 metaData->setRawMin(varMin);
254 } else {
255 qCWarning(PX4ParameterMetaDataLog) << "Invalid min value, name:" << metaData->name() << " type:" << metaData->type() << " min:" << text << " error:" << errorString;
256 }
257
258 } else if (elementName == "max") {
259 QString text = xml.readElementText();
260 qCDebug(PX4ParameterMetaDataLog) << "Max:" << text;
261
262 QVariant varMax;
263 if (metaData->convertAndValidateRaw(text, false /* convertOnly */, varMax, errorString)) {
264 metaData->setRawMax(varMax);
265 } else {
266 // PX4 firmware has a metadata generation bug for VTQ_TELEM_IDS_* parameters
267 if (!metaData->name().startsWith("VTQ_TELEM_IDS_")) {
268 qCWarning(PX4ParameterMetaDataLog) << "Invalid max value, name:" << metaData->name() << " type:" << metaData->type() << " max:" << text << " error:" << errorString;
269 }
270 }
271
272 } else if (elementName == "unit") {
273 QString text = xml.readElementText();
274 qCDebug(PX4ParameterMetaDataLog) << "Unit:" << text;
275 metaData->setRawUnits(text);
276
277 } else if (elementName == "decimal") {
278 QString text = xml.readElementText();
279 qCDebug(PX4ParameterMetaDataLog) << "Decimal:" << text;
280
281 bool convertOk;
282 QVariant varDecimals = QVariant(text).toUInt(&convertOk);
283 if (convertOk) {
284 metaData->setDecimalPlaces(varDecimals.toInt());
285 } else {
286 qCWarning(PX4ParameterMetaDataLog) << "Invalid decimals value, name:" << metaData->name() << " type:" << metaData->type() << " decimals:" << text << " error: invalid number";
287 }
288
289 } else if (elementName == "reboot_required") {
290 QString text = xml.readElementText();
291 qCDebug(PX4ParameterMetaDataLog) << "RebootRequired:" << text;
292 if (text.compare("true", Qt::CaseInsensitive) == 0) {
293 metaData->setVehicleRebootRequired(true);
294 }
295
296 } else if (elementName == "values") {
297 // doing nothing individual value will follow anyway. May be used for sanity checking.
298
299 } else if (elementName == "value") {
300 QString enumValueStr = xml.attributes().value("code").toString();
301 QString enumString = xml.readElementText();
302 qCDebug(PX4ParameterMetaDataLog) << "parameter value:"
303 << "value desc:" << enumString << "code:" << enumValueStr;
304
305 QVariant enumValue;
306 QString enumErrorString;
307 if (metaData->convertAndValidateRaw(enumValueStr, false /* validate */, enumValue, enumErrorString)) {
308 metaData->addEnumInfo(enumString, enumValue);
309 } else {
310 qCDebug(PX4ParameterMetaDataLog) << "Invalid enum value, name:" << metaData->name()
311 << " type:" << metaData->type() << " value:" << enumValueStr
312 << " error:" << enumErrorString;
313 }
314 } else if (elementName == "increment") {
315 double increment;
316 bool ok;
317 QString text = xml.readElementText();
318 increment = text.toDouble(&ok);
319 if (ok) {
320 metaData->setRawIncrement(increment);
321 } else {
322 qCWarning(PX4ParameterMetaDataLog) << "Invalid value for increment, name:" << metaData->name() << " increment:" << text;
323 }
324
325 } else if (elementName == "boolean") {
326 QVariant enumValue;
327 metaData->convertAndValidateRaw(1, false /* validate */, enumValue, errorString);
328 metaData->addEnumInfo(tr("Enabled"), enumValue);
329 metaData->convertAndValidateRaw(0, false /* validate */, enumValue, errorString);
330 metaData->addEnumInfo(tr("Disabled"), enumValue);
331
332 } else if (elementName == "bitmask") {
333 // doing nothing individual bits will follow anyway. May be used for sanity checking.
334
335 } else if (elementName == "bit") {
336 bool ok = false;
337 unsigned char bit = xml.attributes().value("index").toString().toUInt(&ok);
338 if (ok) {
339 QString bitDescription = xml.readElementText();
340 qCDebug(PX4ParameterMetaDataLog) << "parameter value:"
341 << "index:" << bit << "description:" << bitDescription;
342
343 if (bit < 32) {
344 QVariant bitmaskRawValue = 1 << bit;
345 QVariant bitmaskValue;
346 QString bitmaskErrorString;
347 if (metaData->convertAndValidateRaw(bitmaskRawValue, true, bitmaskValue, bitmaskErrorString)) {
348 metaData->addBitmaskInfo(bitDescription, bitmaskValue);
349 } else {
350 qCDebug(PX4ParameterMetaDataLog) << "Invalid bitmask value, name:" << metaData->name()
351 << " type:" << metaData->type() << " value:" << bitmaskValue
352 << " error:" << bitmaskErrorString;
353 }
354 } else {
355 qCWarning(PX4ParameterMetaDataLog) << "Invalid value for bitmask bit, name:" << metaData->name() << " bit:" << bit;
356 }
357 }
358 } else {
359 qCDebug(PX4ParameterMetaDataLog) << "Unknown element in XML: " << elementName;
360 }
361 } else {
362 qWarning() << "Internal error";
363 }
364 }
365 }
366 } else if (xml.isEndElement()) {
367 QString elementName = xml.name().toString();
368
369 if (elementName == "parameter") {
370 // Done loading this parameter, validate default value
371 if (metaData->defaultValueAvailable()) {
372 QVariant var;
373
374 if (!metaData->convertAndValidateRaw(metaData->rawDefaultValue(), false /* convertOnly */, var, errorString)) {
375 qCWarning(PX4ParameterMetaDataLog) << "Invalid default value, name:" << metaData->name() << " type:" << metaData->type() << " default:" << metaData->rawDefaultValue() << " error:" << errorString;
376 }
377 }
378
379 // Reset for next parameter
380 metaData = nullptr;
381 badMetaData = false;
382 xmlState = XmlStateFoundGroup;
383 } else if (elementName == "group") {
384 xmlState = XmlStateFoundVersion;
385 } else if (elementName == "parameters") {
386 xmlState = XmlStateFoundParameters;
387 }
388 }
389 xml.readNext();
390 }
391
392#ifdef GENERATE_PARAMETER_JSON
393 _generateParameterJson();
394#endif
395}
396
397#ifdef GENERATE_PARAMETER_JSON
398void _jsonWriteLine(QFile& file, int indent, const QString& line)
399{
400 while (indent--) {
401 file.write(" ");
402 }
403 file.write(line.toLocal8Bit().constData());
404 file.write("\n");
405}
406
407void PX4ParameterMetaData::_generateParameterJson()
408{
409 qCDebug(PX4ParameterMetaDataLog) << "PX4ParameterMetaData::_generateParameterJson";
410
411 int indentLevel = 0;
412 QFile jsonFile(QDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)).absoluteFilePath("parameter.json"));
413 jsonFile.open(QFile::WriteOnly | QFile::Truncate | QFile::Text);
414
415 _jsonWriteLine(jsonFile, indentLevel++, "{");
416 _jsonWriteLine(jsonFile, indentLevel, "\"version\": 1,");
417 _jsonWriteLine(jsonFile, indentLevel, "\"uid\": 1,");
418 _jsonWriteLine(jsonFile, indentLevel, "\"scope\": \"Firmware\",");
419 _jsonWriteLine(jsonFile, indentLevel++, "\"parameters\": [");
420
421 int keyIndex = 0;
422 for (const QString& paramName: _mapParameterName2FactMetaData.keys()) {
423 const FactMetaData* metaData = _mapParameterName2FactMetaData[paramName];
424 _jsonWriteLine(jsonFile, indentLevel++, "{");
425 _jsonWriteLine(jsonFile, indentLevel, QStringLiteral("\"name\": \"%1\",").arg(paramName));
426 _jsonWriteLine(jsonFile, indentLevel, QStringLiteral("\"type\": \"%1\",").arg(metaData->typeToString(metaData->type())));
427 if (!metaData->group().isEmpty()) {
428 _jsonWriteLine(jsonFile, indentLevel, QStringLiteral("\"group\": \"%1\",").arg(metaData->group()));
429 }
430 if (!metaData->category().isEmpty()) {
431 _jsonWriteLine(jsonFile, indentLevel, QStringLiteral("\"category\": \"%1\",").arg(metaData->category()));
432 }
433 if (!metaData->shortDescription().isEmpty()) {
434 QString text = metaData->shortDescription();
435 text.replace("\"", "\\\"");
436 _jsonWriteLine(jsonFile, indentLevel, QStringLiteral("\"shortDescription\": \"%1\",").arg(text));
437 }
438 if (!metaData->longDescription().isEmpty()) {
439 QString text = metaData->longDescription();
440 text.replace("\"", "\\\"");
441 _jsonWriteLine(jsonFile, indentLevel, QStringLiteral("\"longDescription\": \"%1\",").arg(text));
442 }
443 if (!metaData->rawUnits().isEmpty()) {
444 _jsonWriteLine(jsonFile, indentLevel, QStringLiteral("\"units\": \"%1\",").arg(metaData->rawUnits()));
445 }
446 if (metaData->defaultValueAvailable()) {
447 _jsonWriteLine(jsonFile, indentLevel, QStringLiteral("\"defaultValue\": %1,").arg(metaData->rawDefaultValue().toDouble()));
448 }
449 if (!qIsNaN(metaData->rawIncrement())) {
450 _jsonWriteLine(jsonFile, indentLevel, QStringLiteral("\"increment\": %1,").arg(metaData->rawIncrement()));
451 }
452 if (metaData->enumValues().count()) {
453 _jsonWriteLine(jsonFile, indentLevel++, "\"values\": [");
454 for (int i=0; i<metaData->enumValues().count(); i++) {
455 _jsonWriteLine(jsonFile, indentLevel++, "{");
456 _jsonWriteLine(jsonFile, indentLevel, QStringLiteral("\"value\": %1,").arg(metaData->enumValues()[i].toDouble()));
457 QString text = metaData->enumStrings()[i];
458 text.replace("\"", "\\\"");
459 _jsonWriteLine(jsonFile, indentLevel, QStringLiteral("\"description\": \"%1\"").arg(text));
460 _jsonWriteLine(jsonFile, --indentLevel, QStringLiteral("}%1").arg(i == metaData->enumValues().count() - 1 ? "" : ","));
461 }
462 _jsonWriteLine(jsonFile, --indentLevel, "],");
463 }
464 if (metaData->vehicleRebootRequired()) {
465 _jsonWriteLine(jsonFile, indentLevel, "\"rebootRequired\": true,");
466 }
467 if (metaData->volatileValue()) {
468 _jsonWriteLine(jsonFile, indentLevel, "\"volatile\": true,");
469 }
470 _jsonWriteLine(jsonFile, indentLevel, QStringLiteral("\"decimalPlaces\": %1,").arg(metaData->decimalPlaces()));
471 _jsonWriteLine(jsonFile, indentLevel, QStringLiteral("\"minValue\": %1,").arg(metaData->rawMin().toDouble()));
472 _jsonWriteLine(jsonFile, indentLevel, QStringLiteral("\"maxValue\": %1").arg(metaData->rawMax().toDouble()));
473 _jsonWriteLine(jsonFile, --indentLevel, QStringLiteral("}%1").arg(++keyIndex == _mapParameterName2FactMetaData.keys().count() ? "" : ","));
474 }
475
476 _jsonWriteLine(jsonFile, --indentLevel, "]");
477 _jsonWriteLine(jsonFile, --indentLevel, "}");
478}
479#endif
480
482{
483 Q_UNUSED(vehicleType)
484
485 if (!_mapParameterName2FactMetaData.contains(name)) {
486 qCDebug(PX4ParameterMetaDataLog) << "No metaData for " << name << "using generic metadata";
487 FactMetaData* metaData = new FactMetaData(type, this);
488 _mapParameterName2FactMetaData[name] = metaData;
489 }
490
491 return _mapParameterName2FactMetaData[name];
492}
493
494void PX4ParameterMetaData::getParameterMetaDataVersionInfo(const QString& metaDataFile, int& majorVersion, int& minorVersion)
495{
496 QFile xmlFile(metaDataFile);
497 QString errorString;
498
499 majorVersion = -1;
500 minorVersion = -1;
501
502 if (!xmlFile.exists()) {
503 _outputFileWarning(metaDataFile, QStringLiteral("Does not exist"), QString());
504 return;
505 }
506
507 if (!xmlFile.open(QIODevice::ReadOnly)) {
508 _outputFileWarning(metaDataFile, QStringLiteral("Unable to open file"), xmlFile.errorString());
509 return;
510 }
511
512 QXmlStreamReader xml(xmlFile.readAll());
513 xmlFile.close();
514 if (xml.hasError()) {
515 _outputFileWarning(metaDataFile, QStringLiteral("Badly formed XML"), xml.errorString());
516 return;
517 }
518
519 while (!xml.atEnd() && (majorVersion == -1 || minorVersion == -1)) {
520 if (xml.isStartElement()) {
521 QString elementName = xml.name().toString();
522
523 if (elementName == "parameter_version_major") {
524 bool convertOk;
525 QString strVersion = xml.readElementText();
526 majorVersion = strVersion.toInt(&convertOk);
527 if (!convertOk) {
528 _outputFileWarning(metaDataFile, QStringLiteral("Unable to convert parameter_version_major value to int"), QString());
529 return;
530 }
531 } else if (elementName == "parameter_version_minor") {
532 bool convertOk;
533 QString strVersion = xml.readElementText();
534 minorVersion = strVersion.toInt(&convertOk);
535 if (!convertOk) {
536 _outputFileWarning(metaDataFile, QStringLiteral("Unable to convert parameter_version_minor value to int"), QString());
537 return;
538 }
539 }
540 }
541 xml.readNext();
542 }
543
544 if (majorVersion == -1) {
545 _outputFileWarning(metaDataFile, QStringLiteral("parameter_version_major is missing"), QString());
546 }
547 if (minorVersion == -1) {
548 _outputFileWarning(metaDataFile, QStringLiteral("parameter_version_minor tag is missing"), QString());
549 }
550}
551
552void PX4ParameterMetaData::_outputFileWarning(const QString& metaDataFile, const QString& error1, const QString& error2)
553{
554 qWarning() << QStringLiteral("Internal Error: Parameter meta data file '%1'. %2. error: %3").arg(metaDataFile).arg(error1).arg(error2);
555}
QString errorString
#define QGC_LOGGING_CATEGORY(name, categoryStr)
void setRawUnits(const QString &rawUnits)
void setDecimalPlaces(int decimalPlaces)
QStringList enumStrings() const
void setShortDescription(const QString &shortDescription)
static QString typeToString(ValueType_t type)
QString group() const
QVariant rawMin() const
@ valueTypeElapsedTimeInSeconds
void setName(const QString &name)
QVariantList enumValues() const
void addBitmaskInfo(const QString &name, const QVariant &value)
Used to add new values to the bitmask lists after the meta data has been loaded.
void setLongDescription(const QString &longDescription)
QString shortDescription() const
int decimalPlaces() const
void setVehicleRebootRequired(bool rebootRequired)
void setRawMin(const QVariant &rawMin)
void setCategory(const QString &category)
bool convertAndValidateRaw(const QVariant &rawValue, bool convertOnly, QVariant &typedValue, QString &errorString) const
QString rawUnits() const
void setRawDefaultValue(const QVariant &rawDefaultValue)
double rawIncrement() const
void setRawMax(const QVariant &rawMax)
QString category() const
QString longDescription() const
void setRawIncrement(double increment)
void addEnumInfo(const QString &name, const QVariant &value)
Used to add new values to the enum lists after the meta data has been loaded.
void setVolatileValue(bool bValue)
QString name() const
void setGroup(const QString &group)
QVariant rawDefaultValue() const
ValueType_t type() const
static ValueType_t stringToType(const QString &typeString, bool &unknownType)
bool vehicleRebootRequired() const
bool volatileValue() const
QVariant rawMax() const
bool defaultValueAvailable() const
void setReadOnly(bool bValue)
Loads and holds parameter fact meta data for PX4 stack.
static void getParameterMetaDataVersionInfo(const QString &metaDataFile, int &majorVersion, int &minorVersion)
void loadParameterFactMetaDataFile(const QString &metaDataFile)
FactMetaData * getMetaDataForFact(const QString &name, MAV_TYPE vehicleType, FactMetaData::ValueType_t type)