/*===================================================================== QGroundControl Open Source Ground Control Station (c) 2009 - 2014 QGROUNDCONTROL PROJECT This file is part of the QGROUNDCONTROL project QGROUNDCONTROL is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. QGROUNDCONTROL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with QGROUNDCONTROL. If not, see . ======================================================================*/ /// @file /// @author Don Gagne #include "PX4ParameterFacts.h" #include Q_LOGGING_CATEGORY(PX4ParameterFactsLog, "PX4ParameterFactsLog") Q_LOGGING_CATEGORY(PX4ParameterFactsMetaDataLog, "PX4ParameterFactsMetaDataLog") QMap PX4ParameterFacts::_mapParameterName2FactMetaData; PX4ParameterFacts::PX4ParameterFacts(UASInterface* uas, QObject* parent) : QObject(parent), _lastSeenComponent(-1), _paramMgr(NULL) { Q_ASSERT(uas); _uasId = uas->getUASID(); _paramMgr = uas->getParamManager(); Q_ASSERT(_paramMgr); // UASInterface::parameterChanged has multiple overrides so we need to use SIGNAL/SLOT style connect connect(uas, SIGNAL(parameterChanged(int, int, QString, QVariant)), this, SLOT(_parameterChanged(int, int, QString, QVariant))); // Fact meta data should already be loaded Q_ASSERT(_mapParameterName2FactMetaData.count() != 0); } PX4ParameterFacts::~PX4ParameterFacts() { foreach(Fact* fact, _mapFact2ParameterName.keys()) { delete fact; } _mapParameterName2Fact.clear(); _mapFact2ParameterName.clear(); } void PX4ParameterFacts::deleteParameterFactMetaData(void) { foreach (QString param, _mapParameterName2FactMetaData.keys()) { delete _mapParameterName2FactMetaData[param]; } _mapParameterName2FactMetaData.clear(); } /// Connected to QGCUASParmManager::parameterChanged /// /// When a new parameter is seen it is added to the system. If the parameter is already known it is updated. void PX4ParameterFacts::_parameterChanged(int uas, int component, QString parameterName, QVariant value) { // Is this for our uas? if (uas != _uasId) { return; } if (_lastSeenComponent == -1) { _lastSeenComponent = component; } else { // Code cannot handle parameters coming form different components yets Q_ASSERT(component == _lastSeenComponent); } // If we don't have meta data for the parameter it can't be part of the FactSystem if (!_mapParameterName2FactMetaData.contains(parameterName)) { // FIXME: Debug or Warning. Warning will fail TC qCDebug(PX4ParameterFactsLog) << "FactSystem meta data out of date. Missing parameter:" << parameterName; return; } if (!_mapParameterName2Fact.contains(parameterName)) { Fact* fact = new Fact(this); fact->setMetaData(_mapParameterName2FactMetaData[parameterName]); // We need to know when the fact changes so that we can send the new value to the parameter managers connect(fact, &Fact::valueUpdated, this, &PX4ParameterFacts::_valueUpdated); _mapParameterName2Fact[parameterName] = fact; _mapFact2ParameterName[fact] = parameterName; } _mapParameterName2Fact[parameterName]->updateValue(value); } /// Connected to Fact::valueUpdated /// /// Sets the new value into the Parameter Manager. Paramter is persisted after send. void PX4ParameterFacts::_valueUpdated(QVariant& value) { Fact* fact = qobject_cast(sender()); Q_ASSERT(fact); Q_ASSERT(_lastSeenComponent != -1); Q_ASSERT(_paramMgr); Q_ASSERT(_mapFact2ParameterName.contains(fact)); QVariant typedValue; switch (fact->type()) { case FactMetaData::valueTypeInt8: case FactMetaData::valueTypeInt16: case FactMetaData::valueTypeInt32: typedValue = QVariant(value.value()); case FactMetaData::valueTypeUint8: case FactMetaData::valueTypeUint16: case FactMetaData::valueTypeUint32: typedValue = QVariant(value.value()); break; case FactMetaData::valueTypeFloat: typedValue = QVariant(value.toFloat()); break; case FactMetaData::valueTypeDouble: typedValue = QVariant(value.toDouble()); break; } _paramMgr->setParameter(_lastSeenComponent, _mapFact2ParameterName[fact], typedValue); _paramMgr->sendPendingParameters(true /* persistAfterSend */, false /* forceSend */); } /// Parse the Parameter element of parameter xml meta data /// @param[in] xml stream reader /// @param[in] group fact group associated with this Param element /// @return Returns the meta data object for this parameter FactMetaData* PX4ParameterFacts::_parseParameter(QXmlStreamReader& xml, const QString& group) { Q_UNUSED(group); QString name = xml.attributes().value("name").toString(); QString type = xml.attributes().value("type").toString(); qCDebug(PX4ParameterFactsMetaDataLog) << "Parsed parameter: " << name << type; Q_ASSERT(!name.isEmpty()); Q_ASSERT(!type.isEmpty()); FactMetaData* metaData = new FactMetaData(); Q_CHECK_PTR(metaData); // Convert type from string to FactMetaData::ValueType_t struct String2Type { const char* strType; FactMetaData::ValueType_t type; }; static const struct String2Type rgString2Type[] = { { "FLOAT", FactMetaData::valueTypeFloat }, { "INT32", FactMetaData::valueTypeInt32 }, }; static const size_t crgString2Type = sizeof(rgString2Type) / sizeof(rgString2Type[0]); bool found = false; for (size_t i=0; istrType) { found = true; metaData->type = info->type; break; } } Q_UNUSED(found); Q_ASSERT(found); _initMetaData(metaData); // FIXME: Change to change the parameter build scheme in Firmware to get rid of ifdef dup problem if (!_mapParameterName2FactMetaData.contains(name)) { _mapParameterName2FactMetaData[name] = metaData; } return metaData; } /// This will fill in missing meta data such as range info void PX4ParameterFacts::_initMetaData(FactMetaData* metaData) { switch (metaData->type) { case FactMetaData::valueTypeUint8: metaData->min = QVariant(std::numeric_limits::min()); metaData->max = QVariant(std::numeric_limits::max()); break; case FactMetaData::valueTypeInt8: metaData->min = QVariant(std::numeric_limits::min()); metaData->max = QVariant(std::numeric_limits::max()); break; case FactMetaData::valueTypeUint16: metaData->min = QVariant(std::numeric_limits::min()); metaData->max = QVariant(std::numeric_limits::max()); break; case FactMetaData::valueTypeInt16: metaData->min = QVariant(std::numeric_limits::min()); metaData->max = QVariant(std::numeric_limits::max()); break; case FactMetaData::valueTypeUint32: metaData->min = QVariant(std::numeric_limits::min()); metaData->max = QVariant(std::numeric_limits::max()); break; case FactMetaData::valueTypeInt32: metaData->min = QVariant(std::numeric_limits::min()); metaData->max = QVariant(std::numeric_limits::max()); break; case FactMetaData::valueTypeFloat: metaData->min = QVariant(std::numeric_limits::min()); metaData->max = QVariant(std::numeric_limits::max()); break; case FactMetaData::valueTypeDouble: metaData->min = QVariant(std::numeric_limits::min()); metaData->max = QVariant(std::numeric_limits::max()); break; } } /// Converts a string to a typed QVariant /// @param type Type for Fact which dictates the QVariant type as well /// @param failOk - true: Don't assert if convert fails /// @return Returns the correctly type QVariant QVariant PX4ParameterFacts::_stringToTypedVariant(const QString& string, FactMetaData::ValueType_t type, bool failOk) { QVariant var(string); bool convertOk; Q_UNUSED(convertOk); switch (type) { case FactMetaData::valueTypeUint8: case FactMetaData::valueTypeUint16: case FactMetaData::valueTypeUint32: convertOk = var.convert(QVariant::UInt); if (!failOk) { Q_ASSERT(convertOk); } break; case FactMetaData::valueTypeInt8: case FactMetaData::valueTypeInt16: case FactMetaData::valueTypeInt32: convertOk = var.convert(QVariant::Int); if (!failOk) { Q_ASSERT(convertOk); } break; case FactMetaData::valueTypeFloat: case FactMetaData::valueTypeDouble: convertOk = var.convert(QVariant::Double); if (!failOk) { Q_ASSERT(convertOk); } break; } return var; } /// Load Parameter Fact meta data /// /// The meta data comes from firmware parameters.xml file. void PX4ParameterFacts::loadParameterFactMetaData(void) { qCDebug(PX4ParameterFactsMetaDataLog) << "Loading PX4 parameter fact meta data"; Q_ASSERT(_mapParameterName2FactMetaData.count() == 0); QFile xmlFile(":/AutoPilotPlugins/PX4/ParameterFactMetaData.xml"); Q_ASSERT(xmlFile.exists()); bool success = xmlFile.open(QIODevice::ReadOnly); Q_UNUSED(success); Q_ASSERT(success); QXmlStreamReader xml(xmlFile.readAll()); xmlFile.close(); QString factGroup; FactMetaData* metaData = NULL; while (!xml.atEnd()) { if (xml.isStartElement()) { QString elementName = xml.name().toString(); if (elementName == "parameters") { // Just move to next state } else if (elementName == "group") { factGroup = xml.attributes().value("name").toString(); qCDebug(PX4ParameterFactsLog) << "Found group: " << factGroup; } else if (elementName == "parameter") { metaData = _parseParameter(xml, factGroup); } else if (elementName == "short_desc") { Q_ASSERT(metaData); QString text = xml.readElementText(); metaData->shortDescription = text; } else if (elementName == "long_desc") { Q_ASSERT(metaData); QString text = xml.readElementText(); metaData->longDescription = text; } else if (elementName == "default") { Q_ASSERT(metaData); QString text = xml.readElementText(); // FIXME: failOk=true Is a hack to handle enum values in default value. Need // to implement enums in the meta data. metaData->defaultValue = _stringToTypedVariant(text, metaData->type, true /* failOk */); } else if (elementName == "min") { Q_ASSERT(metaData); QString text = xml.readElementText(); metaData->min = _stringToTypedVariant(text, metaData->type); } else if (elementName == "max") { Q_ASSERT(metaData); QString text = xml.readElementText(); metaData->max = _stringToTypedVariant(text, metaData->type); metaData->shortDescription = text; } else if (elementName == "unit") { Q_ASSERT(metaData); QString text = xml.readElementText(); metaData->units = text; } } else if (xml.isEndElement() && xml.name() == "parameter") { metaData = NULL; } xml.readNext(); } }