update credits
[librepilot.git] / ground / uavobjgenerator / uavobjectparser.cpp
blobef9b9cd97cf3ec82aeecadd131e5b4223112a207
1 /**
2 ******************************************************************************
4 * @file uavobjectparser.cpp
5 * @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2016.
6 * The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
7 * @brief Parses XML files and extracts object information.
9 * @see The GNU Public License (GPL) Version 3
11 *****************************************************************************/
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 3 of the License, or
16 * (at your option) any later version.
18 * This program is distributed in the hope that it will be useful, but
19 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
20 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21 * for more details.
23 * You should have received a copy of the GNU General Public License along
24 * with this program; if not, write to the Free Software Foundation, Inc.,
25 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28 #include "uavobjectparser.h"
29 #include <QDomDocument>
30 #include <QDomElement>
31 #include <QDebug>
32 /**
33 * Constructor
35 UAVObjectParser::UAVObjectParser()
37 fieldTypeStrXML << "int8" << "int16" << "int32" << "uint8"
38 << "uint16" << "uint32" << "float" << "enum";
40 updateModeStrXML << "manual" << "periodic" << "onchange" << "throttled";
42 accessModeStr << "ACCESS_READWRITE" << "ACCESS_READONLY";
44 fieldTypeNumBytes << int(1) << int(2) << int(4) <<
45 int(1) << int(2) << int(4) <<
46 int(4) << int(1);
48 accessModeStrXML << "readwrite" << "readonly";
51 /**
52 * Get number of objects
54 int UAVObjectParser::getNumObjects()
56 return objInfo.length();
59 /**
60 * Get the detailed object information
62 QList<ObjectInfo *> UAVObjectParser::getObjectInfo()
64 return objInfo;
67 ObjectInfo *UAVObjectParser::getObjectByIndex(int objIndex)
69 return objInfo[objIndex];
72 ObjectInfo *UAVObjectParser::getObjectByName(const QString & objName)
74 foreach(ObjectInfo * info, objInfo) {
75 if (objName == info->name) {
76 return info;
79 return 0;
81 /**
82 * Get the name of the object
84 QString UAVObjectParser::getObjectName(int objIndex)
86 ObjectInfo *info = objInfo[objIndex];
88 if (info == NULL) {
89 return QString();
92 return info->name;
95 /**
96 * Get the ID of the object
98 quint32 UAVObjectParser::getObjectID(int objIndex)
100 ObjectInfo *info = objInfo[objIndex];
102 if (info == NULL) {
103 return 0;
105 return info->id;
109 * Get the number of bytes in the data fields of this object
111 int UAVObjectParser::getNumBytes(int objIndex)
113 ObjectInfo *info = objInfo[objIndex];
115 if (info == NULL) {
116 return 0;
117 } else {
118 int numBytes = 0;
119 for (int n = 0; n < info->fields.length(); ++n) {
120 numBytes += info->fields[n]->numBytes * info->fields[n]->numElements;
122 return numBytes;
126 bool fieldTypeLessThan(const FieldInfo *f1, const FieldInfo *f2)
128 return f1->numBytes > f2->numBytes;
132 * Parse supplied XML file
133 * @param xml The xml text
134 * @param filename The xml filename
135 * @returns Null QString() on success, error message on failure
137 QString UAVObjectParser::parseXML(QString & xml, QString & filename)
139 // Create DOM document and parse it
140 QDomDocument doc("UAVObjects");
141 bool parsed = doc.setContent(xml);
143 if (!parsed) {
144 return QString("Improperly formated XML file");
147 // Read all objects contained in the XML file, creating an new ObjectInfo for each
148 QDomElement docElement = doc.documentElement();
149 QDomNode node = docElement.firstChild();
150 while (!node.isNull()) {
151 // Create new object entry
152 ObjectInfo *info = new ObjectInfo;
154 info->filename = filename;
155 // Process object attributes
156 QString status = processObjectAttributes(node, info);
157 if (!status.isNull()) {
158 return status;
161 // Process child elements (fields and metadata)
162 QDomNode childNode = node.firstChild();
163 bool fieldFound = false;
164 bool accessFound = false;
165 bool telGCSFound = false;
166 bool telFlightFound = false;
167 bool logFound = false;
168 bool descriptionFound = false;
169 while (!childNode.isNull()) {
170 // Process element depending on its type
171 if (childNode.nodeName().compare(QString("field")) == 0) {
172 QString status = processObjectFields(childNode, info);
173 if (!status.isNull()) {
174 return status;
177 fieldFound = true;
178 } else if (childNode.nodeName().compare(QString("access")) == 0) {
179 QString status = processObjectAccess(childNode, info);
180 if (!status.isNull()) {
181 return status;
184 accessFound = true;
185 } else if (childNode.nodeName().compare(QString("telemetrygcs")) == 0) {
186 QString status = processObjectMetadata(childNode, &info->gcsTelemetryUpdateMode,
187 &info->gcsTelemetryUpdatePeriod, &info->gcsTelemetryAcked);
188 if (!status.isNull()) {
189 return status;
192 telGCSFound = true;
193 } else if (childNode.nodeName().compare(QString("telemetryflight")) == 0) {
194 QString status = processObjectMetadata(childNode, &info->flightTelemetryUpdateMode,
195 &info->flightTelemetryUpdatePeriod, &info->flightTelemetryAcked);
196 if (!status.isNull()) {
197 return status;
200 telFlightFound = true;
201 } else if (childNode.nodeName().compare(QString("logging")) == 0) {
202 QString status = processObjectMetadata(childNode, &info->loggingUpdateMode,
203 &info->loggingUpdatePeriod, NULL);
204 if (!status.isNull()) {
205 return status;
208 logFound = true;
209 } else if (childNode.nodeName().compare(QString("description")) == 0) {
210 QString status = processObjectDescription(childNode, &info->description);
212 if (!status.isNull()) {
213 return status;
216 descriptionFound = true;
217 } else if (!childNode.isComment()) {
218 return QString("Unknown object element");
221 // Get next element
222 childNode = childNode.nextSibling();
225 // Sort all fields according to size
226 qStableSort(info->fields.begin(), info->fields.end(), fieldTypeLessThan);
228 // Make sure that required elements were found
229 if (!fieldFound) {
230 return QString("Object::field element is missing");
233 if (!accessFound) {
234 return QString("Object::access element is missing");
237 if (!telGCSFound) {
238 return QString("Object::telemetrygcs element is missing");
241 if (!telFlightFound) {
242 return QString("Object::telemetryflight element is missing");
245 if (!logFound) {
246 return QString("Object::logging element is missing");
249 // TODO: Make into error once all objects updated
250 if (!descriptionFound) {
251 return QString("Object::description element is missing");
254 // Calculate ID
255 calculateID(info);
257 // Add object
258 objInfo.append(info);
260 // Get next object
261 node = node.nextSibling();
264 all_units.removeDuplicates();
265 // Done, return null string
266 return QString();
270 * Calculate the unique object ID based on the object information.
271 * The ID will change if the object definition changes, this is intentional
272 * and is used to avoid connecting objects with incompatible configurations.
273 * The LSB is set to zero and is reserved for metadata
275 void UAVObjectParser::calculateID(ObjectInfo *info)
277 // Hash object name
278 quint32 hash = updateHash(info->name, 0);
280 // Hash object attributes
281 hash = updateHash(info->isSettings, hash);
282 hash = updateHash(info->isSingleInst, hash);
283 // Hash field information
284 for (int n = 0; n < info->fields.length(); ++n) {
285 hash = updateHash(info->fields[n]->name, hash);
286 hash = updateHash(info->fields[n]->numElements, hash);
287 hash = updateHash(info->fields[n]->type, hash);
288 if (info->fields[n]->type == FIELDTYPE_ENUM) {
289 QStringList options = info->fields[n]->options;
290 for (int m = 0; m < options.length(); m++) {
291 hash = updateHash(options[m], hash);
295 // Done
296 info->id = hash & 0xFFFFFFFE;
300 * Shift-Add-XOR hash implementation. LSB is set to zero, it is reserved
301 * for the ID of the metaobject.
303 * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx
305 quint32 UAVObjectParser::updateHash(quint32 value, quint32 hash)
307 return hash ^ ((hash << 5) + (hash >> 2) + value);
311 * Update the hash given a string
313 quint32 UAVObjectParser::updateHash(QString & value, quint32 hash)
315 QByteArray bytes = value.toLatin1();
316 quint32 hashout = hash;
318 for (int n = 0; n < bytes.length(); ++n) {
319 hashout = updateHash(bytes[n], hashout);
322 return hashout;
326 * Process the metadata part of the XML
328 QString UAVObjectParser::processObjectMetadata(QDomNode & childNode, UpdateMode *mode, int *period, bool *acked)
330 // Get updatemode attribute
331 QDomNamedNodeMap elemAttributes = childNode.attributes();
332 QDomNode elemAttr = elemAttributes.namedItem("updatemode");
334 if (elemAttr.isNull()) {
335 return QString("Object:telemetrygcs:updatemode attribute is missing");
338 int index = updateModeStrXML.indexOf(elemAttr.nodeValue());
340 if (index < 0) {
341 return QString("Object:telemetrygcs:updatemode attribute value is invalid");
344 *mode = (UpdateMode)index;
346 // Get period attribute
347 elemAttr = elemAttributes.namedItem("period");
348 if (elemAttr.isNull()) {
349 return QString("Object:telemetrygcs:period attribute is missing");
352 *period = elemAttr.nodeValue().toInt();
355 // Get acked attribute (only if acked parameter is not null, not applicable for logging metadata)
356 if (acked != NULL) {
357 elemAttr = elemAttributes.namedItem("acked");
358 if (elemAttr.isNull()) {
359 return QString("Object:telemetrygcs:acked attribute is missing");
362 if (elemAttr.nodeValue().compare(QString("true")) == 0) {
363 *acked = true;
364 } else if (elemAttr.nodeValue().compare(QString("false")) == 0) {
365 *acked = false;
366 } else {
367 return QString("Object:telemetrygcs:acked attribute value is invalid");
370 // Done
371 return QString();
375 * Process the object access tag of the XML
377 QString UAVObjectParser::processObjectAccess(QDomNode & childNode, ObjectInfo *info)
379 // Get gcs attribute
380 QDomNamedNodeMap elemAttributes = childNode.attributes();
381 QDomNode elemAttr = elemAttributes.namedItem("gcs");
383 if (elemAttr.isNull()) {
384 return QString("Object:access:gcs attribute is missing");
387 int index = accessModeStrXML.indexOf(elemAttr.nodeValue());
388 if (index >= 0) {
389 info->gcsAccess = (AccessMode)index;
390 } else {
391 return QString("Object:access:gcs attribute value is invalid");
394 // Get flight attribute
395 elemAttr = elemAttributes.namedItem("flight");
396 if (elemAttr.isNull()) {
397 return QString("Object:access:flight attribute is missing");
400 index = accessModeStrXML.indexOf(elemAttr.nodeValue());
401 if (index >= 0) {
402 info->flightAccess = (AccessMode)index;
403 } else {
404 return QString("Object:access:flight attribute value is invalid");
407 // Done
408 return QString();
412 * Process the object fields of the XML
414 QString UAVObjectParser::processObjectFields(QDomNode & childNode, ObjectInfo *info)
416 bool isClone = false;
417 // Create field
418 FieldInfo *field = new FieldInfo;
419 // Get name attribute
420 QDomNamedNodeMap elemAttributes = childNode.attributes();
421 QDomNode elemAttr = elemAttributes.namedItem("name");
423 if (elemAttr.isNull()) {
424 return QString("Object:field:name attribute is missing");
426 QString name = elemAttr.nodeValue();
428 // Check to see is this field is a clone of another
429 // field that has already been declared
430 elemAttr = elemAttributes.namedItem("cloneof");
431 if (!elemAttr.isNull()) {
432 QString parentName = elemAttr.nodeValue().section('.', 1);
434 ObjectInfo *parentObject;
436 if (parentName.isEmpty()) {
437 parentName = elemAttr.nodeValue();
438 parentObject = info;
439 } else {
440 QString objName = elemAttr.nodeValue().section('.', 0, 0);
441 parentObject = getObjectByName(objName);
442 if (!parentObject) {
443 return QString("Object:field:cloneof parent object unknown");
447 if (!parentName.isEmpty()) {
448 foreach(FieldInfo * parent, parentObject->fields) {
449 if (parent->name == parentName) {
450 // clone from this parent
451 *field = *parent; // safe shallow copy, no ptrs in struct
452 field->name = name; // set our name
453 // Done, but allow certain overrides
454 field->parentObjectName = parentObject->name;
455 field->parentFieldName = parent->name;
456 isClone = true;
458 break;
462 if (!isClone) {
463 return QString("Object:field:cloneof parent unknown");
465 } else {
466 return QString("Object:field:cloneof attribute is empty");
468 } else {
469 // this field is not a clone, so remember its name
470 field->name = name;
473 // Get description attribute if any
474 elemAttr = elemAttributes.namedItem("description");
475 if (!elemAttr.isNull()) {
476 field->description = elemAttr.nodeValue();
477 } else {
478 // Look for a child description node
479 QDomNode node = childNode.firstChildElement("description");
480 if (!node.isNull()) {
481 QDomNode description = node.firstChild();
482 if (!description.isNull()) {
483 field->description = description.nodeValue();
488 // Get units attribute
489 elemAttr = elemAttributes.namedItem("units");
490 if (elemAttr.isNull()) {
491 if (!isClone) {
492 return QString("Object:field:units attribute is missing");
494 } else {
495 if (isClone) {
496 return QString("Object:field:units attribute is not allowed for cloned fields");
498 field->units = elemAttr.nodeValue();
499 all_units << field->units;
501 // Get type attribute
502 elemAttr = elemAttributes.namedItem("type");
503 if (elemAttr.isNull()) {
504 if (!isClone) {
505 return QString("Object:field:type attribute is missing");
507 } else {
508 if (isClone) {
509 return QString("Object:field:type attribute is not allowed for cloned fields");
511 int index = fieldTypeStrXML.indexOf(elemAttr.nodeValue());
512 if (index >= 0) {
513 field->type = (FieldType)index;
514 field->numBytes = fieldTypeNumBytes[index];
515 } else {
516 return QString("Object:field:type attribute value is invalid");
520 // Get numelements or elementnames attribute
521 if (!isClone) {
522 field->numElements = 0;
524 // Look for element names as an attribute first
525 elemAttr = elemAttributes.namedItem("elementnames");
526 if (!elemAttr.isNull()) {
527 if (isClone) {
528 return QString("Object:field:elementnames attribute is not allowed for cloned fields");
530 // Get element names
531 QStringList names = elemAttr.nodeValue().split(",", QString::SkipEmptyParts);
532 for (int n = 0; n < names.length(); ++n) {
533 names[n] = names[n].trimmed();
536 field->elementNames = names;
537 field->numElements = names.length();
538 field->defaultElementNames = false;
539 } else {
540 // Look for a list of child elementname nodes
541 QDomNode listNode = childNode.firstChildElement("elementnames");
542 if (!listNode.isNull()) {
543 if (isClone) {
544 return QString("Object:field:elementnames element is not allowed for cloned fields");
546 for (QDomElement node = listNode.firstChildElement("elementname");
547 !node.isNull(); node = node.nextSiblingElement("elementname")) {
548 QDomNode name = node.firstChild();
549 if (!name.isNull() && name.isText() && !name.nodeValue().isEmpty()) {
550 field->elementNames.append(name.nodeValue());
553 field->numElements = field->elementNames.length();
554 field->defaultElementNames = false;
557 // If no element names were found, then fall back to looking
558 // for the number of elements in the 'elements' attribute
559 if (field->numElements == 0) {
560 elemAttr = elemAttributes.namedItem("elements");
561 if (elemAttr.isNull()) {
562 return QString("Object:field:elements and Object:field:elementnames attribute/element is missing");
563 } else {
564 if (isClone) {
565 return QString("Object:field:elements attribute is not allowed for cloned fields");
567 field->numElements = elemAttr.nodeValue().toInt();
568 for (int n = 0; n < field->numElements; ++n) {
569 field->elementNames.append(QString("%1").arg(n));
572 field->defaultElementNames = true;
575 // Get options attribute or child elements (only if an enum type)
576 // We allow "options" attribute/element for cloned fields also, but they work slightly different here -
577 // they set limits on parent options.
579 if (field->type == FIELDTYPE_ENUM) {
580 // Look for options attribute
581 QStringList options;
582 elemAttr = elemAttributes.namedItem("options");
583 if (!elemAttr.isNull()) {
584 options = elemAttr.nodeValue().split(",", QString::SkipEmptyParts);
585 for (int n = 0; n < options.length(); ++n) {
586 options[n] = options[n].trimmed();
588 } else {
589 // Look for a list of child 'option' nodes
590 QDomNode listNode = childNode.firstChildElement("options");
591 if (!listNode.isNull()) {
592 for (QDomElement node = listNode.firstChildElement("option");
593 !node.isNull(); node = node.nextSiblingElement("option")) {
594 QDomNode name = node.firstChild();
595 if (!name.isNull() && name.isText() && !name.nodeValue().isEmpty()) {
596 options.append(name.nodeValue());
602 if (isClone) {
603 if (!options.isEmpty()) {
604 // Verify options subset and build limits value from it.
605 foreach(const QString &option, options) {
606 if (!field->options.contains(option)) {
607 return QString("Object:field:options is not a subset of parent options");
610 field->limitValues = QString("%EQ:") + options.join(':');
611 qDebug() << "Created field->limitValues: " << field->limitValues;
613 } else {
614 field->numOptions = options.size();
615 field->options = options;
618 if (field->numOptions == 0) {
619 return QString("Object:field:options attribute/element is missing");
623 // Get the default value attribute (required for settings objects, optional for the rest)
624 elemAttr = elemAttributes.namedItem("defaultvalue");
625 if (!elemAttr.isNull()) {
626 QStringList defaults = elemAttr.nodeValue().split(",", QString::SkipEmptyParts);
627 for (int n = 0; n < defaults.length(); ++n) {
628 defaults[n] = defaults[n].trimmed();
631 if (defaults.length() != field->numElements) {
632 if (defaults.length() != 1) {
633 return QString("Object:field:incorrect number of default values");
636 /*support legacy single default for multiple elements
637 We should really issue a warning*/
638 for (int ct = 1; ct < field->numElements; ct++) {
639 defaults.append(defaults[0]);
642 field->defaultValues = defaults;
644 if (field->defaultValues.isEmpty() && info->isSettings) {
645 return QString("Object:field:defaultvalue attribute is missing (required for settings objects)");
648 // Limits attribute
649 elemAttr = elemAttributes.namedItem("limits");
650 if (elemAttr.isNull()) {
651 field->limitValues = QString();
652 } else {
653 field->limitValues = elemAttr.nodeValue();
655 // Add field to object
656 info->fields.append(field);
657 // Done
658 return QString();
662 * Process the object attributes from the XML
664 QString UAVObjectParser::processObjectAttributes(QDomNode & node, ObjectInfo *info)
666 // Get name attribute
667 QDomNamedNodeMap attributes = node.attributes();
668 QDomNode attr = attributes.namedItem("name");
670 if (attr.isNull()) {
671 return QString("Object:name attribute is missing");
674 info->name = attr.nodeValue();
675 info->namelc = attr.nodeValue().toLower();
677 // Get category attribute if present
678 attr = attributes.namedItem("category");
679 if (!attr.isNull()) {
680 info->category = attr.nodeValue();
683 // Get singleinstance attribute
684 attr = attributes.namedItem("singleinstance");
685 if (attr.isNull()) {
686 return QString("Object:singleinstance attribute is missing");
689 if (attr.nodeValue().compare(QString("true")) == 0) {
690 info->isSingleInst = true;
691 } else if (attr.nodeValue().compare(QString("false")) == 0) {
692 info->isSingleInst = false;
693 } else {
694 return QString("Object:singleinstance attribute value is invalid");
697 // Get settings attribute
698 attr = attributes.namedItem("settings");
699 if (attr.isNull()) {
700 return QString("Object:settings attribute is missing");
703 if (attr.nodeValue().compare(QString("true")) == 0) {
704 info->isSettings = true;
705 } else if (attr.nodeValue().compare(QString("false")) == 0) {
706 info->isSettings = false;
707 } else {
708 return QString("Object:settings attribute value is invalid (true|false)");
711 // Get priority attribute
712 attr = attributes.namedItem("priority");
713 info->isPriority = false;
714 if (!attr.isNull()) {
715 if (attr.nodeValue().compare(QString("true")) == 0) {
716 info->isPriority = true;
717 } else if (attr.nodeValue().compare(QString("false")) != 0) {
718 return QString("Object:priority attribute value is invalid (true|false)");
722 // Settings objects can only have a single instance
723 if (info->isSettings && !info->isSingleInst) {
724 return QString("Object: Settings objects can not have multiple instances");
727 // Done
728 return QString();
732 * Process the description field from the XML file
734 QString UAVObjectParser::processObjectDescription(QDomNode & childNode, QString *description)
736 description->append(childNode.firstChild().nodeValue());
737 return QString();