From 9865265a5d2705e86238a6536283def0b218a5ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 7 Jan 2026 15:15:56 +0100 Subject: [PATCH] Add optional stepSize for numeric StateTypes and ParamTypes --- libnymea/integrations/pluginmetadata.cpp | 337 ++++++++++++++++------- libnymea/types/paramtype.cpp | 67 +++-- libnymea/types/paramtype.h | 18 +- libnymea/types/statetype.cpp | 25 +- libnymea/types/statetype.h | 7 +- nymea.pro | 2 +- tests/auto/api.json | 4 +- 7 files changed, 310 insertions(+), 150 deletions(-) diff --git a/libnymea/integrations/pluginmetadata.cpp b/libnymea/integrations/pluginmetadata.cpp index 0101d1f8..17b7269d 100644 --- a/libnymea/integrations/pluginmetadata.cpp +++ b/libnymea/integrations/pluginmetadata.cpp @@ -27,26 +27,26 @@ #include "types/interface.h" -#include #include #include -#include +#include #include -#if QT_VERSION >= QT_VERSION_CHECK(6,0,0) +#include +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #include #endif -PluginMetadata::PluginMetadata() -{ +static const QList s_validTypesForStepSize + = {QMetaType::Int, QMetaType::UInt, QMetaType::LongLong, QMetaType::ULongLong, QMetaType::Double, QMetaType::Float, QMetaType::Short, QMetaType::ULong, QMetaType::UShort}; -} +PluginMetadata::PluginMetadata() {} -PluginMetadata::PluginMetadata(const QJsonObject &jsonObject, bool isBuiltIn, bool strict): - m_jsonObject(jsonObject), - m_isBuiltIn(isBuiltIn), - m_strictRun(strict) +PluginMetadata::PluginMetadata(const QJsonObject &jsonObject, bool isBuiltIn, bool strict) + : m_jsonObject(jsonObject) + , m_isBuiltIn(isBuiltIn) + , m_strictRun(strict) { -#if QT_VERSION >= QT_VERSION_CHECK(6,0,0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) qRegisterMetaType("QColor"); #endif @@ -165,7 +165,8 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) // Check mandatory fields if (!verificationResult.first.isEmpty()) { - m_validationErrors.append("Vendor metadata has missing fields: " + verificationResult.first.join(", ") + "\n" + qUtf8Printable(QJsonDocument::fromVariant(vendorObject.toVariantMap()).toJson(QJsonDocument::Indented))); + m_validationErrors.append("Vendor metadata has missing fields: " + verificationResult.first.join(", ") + "\n" + + qUtf8Printable(QJsonDocument::fromVariant(vendorObject.toVariantMap()).toJson(QJsonDocument::Indented))); hasError = true; // Not continuing parsing vendor as we rely on mandatory fields being around. break; @@ -176,7 +177,8 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) // Check if there are any unknown fields if (!verificationResult.second.isEmpty()) { - m_validationErrors.append("Vendor \"" + vendorName + "\" has unknown fields: \"" + verificationResult.second.join("\", \"") + "\"\n" + qUtf8Printable(QJsonDocument::fromVariant(vendorObject.toVariantMap()).toJson(QJsonDocument::Indented))); + m_validationErrors.append("Vendor \"" + vendorName + "\" has unknown fields: \"" + verificationResult.second.join("\", \"") + "\"\n" + + qUtf8Printable(QJsonDocument::fromVariant(vendorObject.toVariantMap()).toJson(QJsonDocument::Indented))); hasError = true; } @@ -194,15 +196,26 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) // Load thing classes of this vendor foreach (const QJsonValue &thingClassJson, vendorJson.toObject().value("thingClasses").toArray()) { - // FIXME: Drop this when possible, see .h for context m_currentScopUuids.clear(); QJsonObject thingClassObject = thingClassJson.toObject(); /*! Returns a list of all valid JSON properties a ThingClass JSON definition can have. */ - QStringList thingClassProperties = QStringList() << "id" << "name" << "displayName" << "createMethods" << "setupMethod" - << "interfaces" << "providedInterfaces" << "browsable" << "discoveryParamTypes" - << "paramTypes" << "settingsTypes" << "stateTypes" << "actionTypes" << "eventTypes" << "browserItemActionTypes" + QStringList thingClassProperties = QStringList() << "id" + << "name" + << "displayName" + << "createMethods" + << "setupMethod" + << "interfaces" + << "providedInterfaces" + << "browsable" + << "discoveryParamTypes" + << "paramTypes" + << "settingsTypes" + << "stateTypes" + << "actionTypes" + << "eventTypes" + << "browserItemActionTypes" << "discoveryType"; QStringList mandatoryThingClassProperties = QStringList() << "id" << "name" << "displayName"; @@ -210,7 +223,8 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) // Check mandatory fields if (!verificationResult.first.isEmpty()) { - m_validationErrors.append("Thing class has missing fields: \"" + verificationResult.first.join("\", \"") + "\"\n" + qUtf8Printable(QJsonDocument::fromVariant(thingClassObject.toVariantMap()).toJson(QJsonDocument::Indented))); + m_validationErrors.append("Thing class has missing fields: \"" + verificationResult.first.join("\", \"") + "\"\n" + + qUtf8Printable(QJsonDocument::fromVariant(thingClassObject.toVariantMap()).toJson(QJsonDocument::Indented))); hasError = true; // Stop parsing this thingClass as we rely on mandatory fields being around. continue; @@ -221,7 +235,8 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) // Check if there are any unknown fields if (!verificationResult.second.isEmpty()) { - m_validationErrors.append("Thing class \"" + thingClassName + "\" has unknown fields: \"" + verificationResult.second.join("\", \"") + "\"\n" + qUtf8Printable(QJsonDocument::fromVariant(thingClassObject.toVariantMap()).toJson(QJsonDocument::Indented))); + m_validationErrors.append("Thing class \"" + thingClassName + "\" has unknown fields: \"" + verificationResult.second.join("\", \"") + "\"\n" + + qUtf8Printable(QJsonDocument::fromVariant(thingClassObject.toVariantMap()).toJson(QJsonDocument::Indented))); hasError = true; } @@ -253,7 +268,7 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) } else if (createMethodValue.toString().toLower() == "user") { createMethods |= ThingClass::CreateMethodUser; } else { - m_validationErrors.append("Unknown createMehtod \"" + createMethodValue.toString() + "\" in thingClass \"" + thingClass.name() + "\"."); + m_validationErrors.append("Unknown createMehtod \"" + createMethodValue.toString() + "\" in thingClass \"" + thingClass.name() + "\"."); hasError = true; } } @@ -267,7 +282,7 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) } else if (discoveryTypeString == "weak") { thingClass.setDiscoveryType(ThingClass::DiscoveryTypeWeak); } else { - m_validationErrors.append("Unknown discoveryType \"" + discoveryTypeString + "\" in thingClass \"" + thingClass.name() + "\"."); + m_validationErrors.append("Unknown discoveryType \"" + discoveryTypeString + "\" in thingClass \"" + thingClass.name() + "\"."); hasError = true; } } else { @@ -333,15 +348,31 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) bool writableState = false; // TODO: DEPRECATED 1.2: Remove displayNameEvent eventually (requires updating all plugins) - QStringList stateTypeProperties = {"id", "name", "displayName", "displayNameEvent", "type", "defaultValue", "cached", - "unit", "minValue", "maxValue", "possibleValues", "writable", "displayNameAction", - "ioType", "suggestLogging", "filter"}; + QStringList stateTypeProperties = {"id", + "name", + "displayName", + "displayNameEvent", + "type", + "defaultValue", + "cached", + "unit", + "minValue", + "maxValue", + "stepSize", + "possibleValues", + "writable", + "displayNameAction", + "ioType", + "suggestLogging", + "filter"}; + QStringList mandatoryStateTypeProperties = {"id", "name", "displayName", "type", "defaultValue"}; QPair verificationResult = verifyFields(stateTypeProperties, mandatoryStateTypeProperties, st); // Check mandatory fields if (!verificationResult.first.isEmpty()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" has missing properties \"" + verificationResult.first.join("\", \"") + "\" in stateType definition\n" + qUtf8Printable(QJsonDocument::fromVariant(st.toVariantMap()).toJson(QJsonDocument::Indented))); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" has missing properties \"" + verificationResult.first.join("\", \"") + + "\" in stateType definition\n" + qUtf8Printable(QJsonDocument::fromVariant(st.toVariantMap()).toJson(QJsonDocument::Indented))); hasError = true; // Not processing further as mandatory fields are expected to be here continue; @@ -352,7 +383,8 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) // Check if there are any unknown fields if (!verificationResult.second.isEmpty()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" has unknown properties \"" + verificationResult.second.join("\", \"") + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" has unknown properties \"" + + verificationResult.second.join("\", \"") + "\""); hasError = true; } // Print warning on deprecated fields @@ -365,17 +397,19 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) if (st.contains("writable") && st.value("writable").toBool()) { writableState = true; if (!st.contains("displayNameAction")) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" has writable state but does not define the displayNameAction property"); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + + "\" has writable state but does not define the displayNameAction property"); hasError = true; } } -#if QT_VERSION >= QT_VERSION_CHECK(6,0,0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QMetaType::Type t = static_cast(QMetaType::fromName(QByteArray(st.value("type").toString().toUtf8())).id()); #else QMetaType::Type t = static_cast(QVariant::nameToType(st.value("type").toString().toLatin1().data())); #endif if (t == QMetaType::UnknownType) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" has invalid type: \"" + st.value("type").toString() + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" has invalid type: \"" + st.value("type").toString() + + "\""); hasError = true; } @@ -417,6 +451,23 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) stateType.setMaxValue(maxValue); } + if (st.contains("stepSize")) { + double stepSize = st.value("stepSize").toDouble(); + if (stepSize != 0) { + if (!s_validTypesForStepSize.contains(stateType.type())) { + m_validationErrors.append("Thing \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" has configured a step size but the data type " + + st.value("type").toString() + " does not support that. Only numeric state types can have a stepSize."); + hasError = true; + } else if (stepSize < 0) { + m_validationErrors.append("Thing \"" + thingClass.name() + "\" state type \"" + stateTypeName + + "\" has configured a negative stepSize which is not allowed."); + hasError = true; + } else { + stateType.setStepSize(stepSize); + } + } + } + if (st.contains("possibleValues")) { QVariantList possibleValues; QStringList possibleValuesDisplayNames; @@ -427,7 +478,8 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) if (possibleValueJson.isObject()) { if (!possibleValue.toMap().contains("value") || !possibleValue.toMap().contains("displayName")) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" has invalid possible value \"" + possibleValueJson.toString() + "\" which is of object type but does not have \"value\" and \"displayName\" properties."); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" has invalid possible value \"" + + possibleValueJson.toString() + "\" which is of object type but does not have \"value\" and \"displayName\" properties."); hasError = true; break; } @@ -445,7 +497,8 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) stateType.setPossibleValuesDisplayNames(possibleValuesDisplayNames); if (!stateType.possibleValues().contains(stateType.defaultValue())) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" has invalid default value \"" + stateType.defaultValue().toString() + "\" which is not in the list of possible values."); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" has invalid default value \"" + + stateType.defaultValue().toString() + "\" which is not in the list of possible values."); hasError = true; break; } @@ -463,54 +516,64 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) Types::IOType ioType = Types::IOTypeNone; if (ioTypeString == "digitalInput") { if (stateType.type() != QMetaType::Bool) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" is marked as digital input but type is not \"bool\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + + "\" is marked as digital input but type is not \"bool\""); hasError = true; break; } ioType = Types::IOTypeDigitalInput; } else if (ioTypeString == "digitalOutput") { if (stateType.type() != QMetaType::Bool) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" is marked as digital output but type is not \"bool\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + + "\" is marked as digital output but type is not \"bool\""); hasError = true; break; } if (!stateType.writable()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" is marked as digital output but is not writable"); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + + "\" is marked as digital output but is not writable"); hasError = true; break; } ioType = Types::IOTypeDigitalOutput; } else if (ioTypeString == "analogInput") { if (stateType.type() != QMetaType::Double && stateType.type() != QMetaType::Int && stateType.type() != QMetaType::UInt) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" is marked as analog input but type is not \"double\", \"int\" or \"uint\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + + "\" is marked as analog input but type is not \"double\", \"int\" or \"uint\""); hasError = true; break; } if (stateType.minValue().isNull() || stateType.maxValue().isNull()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" is marked as analog input but it does not define \"minValue\" and \"maxValue\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + + "\" is marked as analog input but it does not define \"minValue\" and \"maxValue\""); hasError = true; break; } ioType = Types::IOTypeAnalogInput; } else if (ioTypeString == "analogOutput") { if (stateType.type() != QMetaType::Double && stateType.type() != QMetaType::Int && stateType.type() != QMetaType::UInt) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" is marked as analog output but type is not \"double\", \"int\" or \"uint\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + + "\" is marked as analog output but type is not \"double\", \"int\" or \"uint\""); hasError = true; break; } if (!stateType.writable()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" is marked as analog output but is not writable"); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + + "\" is marked as analog output but is not writable"); hasError = true; break; } if (stateType.minValue().isNull() || stateType.maxValue().isNull()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" is marked as analog output but it does not define \"minValue\" and \"maxValue\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + + "\" is marked as analog output but it does not define \"minValue\" and \"maxValue\""); hasError = true; break; } ioType = Types::IOTypeAnalogOutput; } else { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" has invalid ioType value \"IOTypeNone\" which is not any of \"digitalInput\", \"digitalOutput\", \"analogInput\" or \"analogOutput\""); + m_validationErrors.append( + "Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + + "\" has invalid ioType value \"IOTypeNone\" which is not any of \"digitalInput\", \"digitalOutput\", \"analogInput\" or \"analogOutput\""); hasError = true; break; } @@ -524,7 +587,8 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) if (filter == "adaptive") { stateType.setFilter(Types::StateValueFilterAdaptive); } else if (!filter.isEmpty()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" has invalid filter value \"" + filter + "\". Supported filters are: \"adaptive\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" state type \"" + stateTypeName + "\" has invalid filter value \"" + filter + + "\". Supported filters are: \"adaptive\""); hasError = true; } } @@ -538,6 +602,7 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) paramType.setDefaultValue(stateType.defaultValue()); paramType.setMinValue(stateType.minValue()); paramType.setMaxValue(stateType.maxValue()); + paramType.setStepSize(stateType.stepSize()); paramType.setUnit(stateType.unit()); ActionType actionType(ActionTypeId(stateType.id().toString())); @@ -560,7 +625,8 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) // Check mandatory fields if (!verificationResult.first.isEmpty()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" has missing fields \"" + verificationResult.first.join("\", \"") + "\" in action type definition."); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" has missing fields \"" + verificationResult.first.join("\", \"") + + "\" in action type definition."); hasError = true; continue; } @@ -570,7 +636,8 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) // Check if there are any unknown fields if (!verificationResult.second.isEmpty()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" action type \"" + actionTypeName + "\" has unknown fields \"" + verificationResult.second.join("\", \"") + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" action type \"" + actionTypeName + "\" has unknown fields \"" + + verificationResult.second.join("\", \"") + "\""); hasError = true; } @@ -610,7 +677,8 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) // Check mandatory fields if (!verificationResult.first.isEmpty()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" has missing fields \"" + verificationResult.first.join("\", \"") + "\" in event type defintion"); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" has missing fields \"" + verificationResult.first.join("\", \"") + + "\" in event type defintion"); hasError = true; continue; } @@ -620,7 +688,8 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) // Check if there are any unknown fields if (!verificationResult.second.isEmpty()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" event type \"" + eventTypeName + "\" has unknown fields \"" + verificationResult.second.join("\", \"") + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" event type \"" + eventTypeName + "\" has unknown fields \"" + + verificationResult.second.join("\", \"") + "\""); hasError = true; } @@ -657,7 +726,8 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) // Check mandatory fields if (!verificationResult.first.isEmpty()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" has missing fields \"" + verificationResult.first.join("\", \"") + "\" in browser item action type definition"); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" has missing fields \"" + verificationResult.first.join("\", \"") + + "\" in browser item action type definition"); hasError = true; continue; } @@ -667,16 +737,19 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) // Check if there are any unknown fields if (!verificationResult.second.isEmpty()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" browser action type \"" + actionTypeName + "\" has unknown fields \"" + verificationResult.first.join("\", \"") + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" browser action type \"" + actionTypeName + "\" has unknown fields \"" + + verificationResult.first.join("\", \"") + "\""); hasError = true; } if (actionTypeId.isNull()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" browser action type \"" + actionTypeName + "\" has invalid UUID: " + at.value("id").toString()); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" browser action type \"" + actionTypeName + + "\" has invalid UUID: " + at.value("id").toString()); hasError = true; } if (!verifyDuplicateUuid(actionTypeId)) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" browser action type \"" + actionTypeName + "\" has duplicate UUID: " + actionTypeId.toString()); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" browser action type \"" + actionTypeName + + "\" has duplicate UUID: " + actionTypeId.toString()); hasError = true; } ActionType actionType(actionTypeId); @@ -708,105 +781,117 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) foreach (const InterfaceParamType &ifaceParamType, iface.paramTypes()) { if (!thingClass.paramTypes().contains(ifaceParamType.name())) { if (!ifaceParamType.optional()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + - "\" but doesn't implement param \"" + ifaceParamType.name() + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + + "\" but doesn't implement param \"" + ifaceParamType.name() + "\""); hasError = true; } continue; } ParamType ¶mType = thingClass.paramTypes()[ifaceParamType.name()]; if (ifaceParamType.type() != paramType.type()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + - "\" but param \"" + paramType.name() + "\" has not matching type: \"" + - QVariant::typeToName(paramType.type()) + "\" != \"" + QVariant::typeToName(ifaceParamType.type()) + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but param \"" + + paramType.name() + "\" has not matching type: \"" + QVariant::typeToName(paramType.type()) + "\" != \"" + + QVariant::typeToName(ifaceParamType.type()) + "\""); hasError = true; } if (ifaceParamType.minValue().isValid() && !ifaceParamType.minValue().isNull()) { if (ifaceParamType.minValue().toString() == "any") { if (paramType.minValue().isNull()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + - "\" but param \"" + paramType.name() + "\" has no minimum value defined."); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but param \"" + + paramType.name() + "\" has no minimum value defined."); hasError = true; } } else if (ifaceParamType.minValue() != paramType.minValue()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + - "\" but param \"" + paramType.name() + "\" has not matching minimum value: \"" + - ifaceParamType.minValue().toString() + "\" != \"" + paramType.minValue().toString() + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but param \"" + + paramType.name() + "\" has not matching minimum value: \"" + ifaceParamType.minValue().toString() + "\" != \"" + + paramType.minValue().toString() + "\""); hasError = true; } } if (ifaceParamType.maxValue().isValid() && !ifaceParamType.maxValue().isNull()) { if (ifaceParamType.maxValue().toString() == "any") { if (paramType.maxValue().isNull()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + - "\" but param \"" + paramType.name() + "\" has no maximum value defined."); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but param \"" + + paramType.name() + "\" has no maximum value defined."); hasError = true; } } else if (ifaceParamType.maxValue() != paramType.maxValue()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + - "\" but param \"" + paramType.name() + "\" has not matching maximum value: \"" + - ifaceParamType.maxValue().toString() + "\" != \"" + paramType.minValue().toString() + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but param \"" + + paramType.name() + "\" has not matching maximum value: \"" + ifaceParamType.maxValue().toString() + "\" != \"" + + paramType.minValue().toString() + "\""); hasError = true; } } if (!ifaceParamType.allowedValues().isEmpty() && ifaceParamType.allowedValues() != paramType.allowedValues()) { qCritical() << ifaceParamType.allowedValues(); - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but param \"" + - paramType.name() + "\" has not matching allowed values."); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but param \"" + + paramType.name() + "\" has not matching allowed values."); hasError = true; } if (ifaceParamType.unit() != Types::UnitNone && ifaceParamType.unit() != paramType.unit()) { QMetaEnum unitEnum = QMetaEnum::fromType(); - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but param \"" + - paramType.name() + "\" has not matching unit: \"" + unitEnum.valueToKey(ifaceParamType.unit()) + "\" != \"" + unitEnum.valueToKey(paramType.unit())); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but param \"" + + paramType.name() + "\" has not matching unit: \"" + unitEnum.valueToKey(ifaceParamType.unit()) + "\" != \"" + + unitEnum.valueToKey(paramType.unit())); hasError = true; } } - foreach (const InterfaceStateType &ifaceStateType, iface.stateTypes()) { if (!stateTypes.contains(ifaceStateType.name())) { if (!ifaceStateType.optional()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but doesn't implement state \"" + ifaceStateType.name() + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + + "\" but doesn't implement state \"" + ifaceStateType.name() + "\""); hasError = true; } continue; } StateType &stateType = stateTypes[ifaceStateType.name()]; if (ifaceStateType.type() != stateType.type()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but state \"" + stateType.name() + "\" has not matching type: \"" + QVariant::typeToName(stateType.type()) + "\" != \"" + QVariant::typeToName(ifaceStateType.type()) + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but state \"" + + stateType.name() + "\" has not matching type: \"" + QVariant::typeToName(stateType.type()) + "\" != \"" + + QVariant::typeToName(ifaceStateType.type()) + "\""); hasError = true; } if (ifaceStateType.minValue().isValid() && !ifaceStateType.minValue().isNull()) { if (ifaceStateType.minValue().toString() == "any") { if (stateType.minValue().isNull()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but state \"" + stateType.name() + "\" has no minimum value defined."); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but state \"" + + stateType.name() + "\" has no minimum value defined."); hasError = true; } } else if (ifaceStateType.minValue() != stateType.minValue()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but state \"" + stateType.name() + "\" has not matching minimum value: \"" + ifaceStateType.minValue().toString() + "\" != \"" + stateType.minValue().toString() + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but state \"" + + stateType.name() + "\" has not matching minimum value: \"" + ifaceStateType.minValue().toString() + "\" != \"" + + stateType.minValue().toString() + "\""); hasError = true; } } if (ifaceStateType.maxValue().isValid() && !ifaceStateType.maxValue().isNull()) { if (ifaceStateType.maxValue().toString() == "any") { if (stateType.maxValue().isNull()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but state \"" + stateType.name() + "\" has no maximum value defined."); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but state \"" + + stateType.name() + "\" has no maximum value defined."); hasError = true; } } else if (ifaceStateType.maxValue() != stateType.maxValue()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but state \"" + stateType.name() + "\" has not matching maximum value: \"" + ifaceStateType.maxValue().toString() + "\" != \"" + stateType.minValue().toString() + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but state \"" + + stateType.name() + "\" has not matching maximum value: \"" + ifaceStateType.maxValue().toString() + "\" != \"" + + stateType.minValue().toString() + "\""); hasError = true; } } if (!ifaceStateType.possibleValues().isEmpty() && ifaceStateType.possibleValues() != stateType.possibleValues()) { qCritical() << ifaceStateType.possibleValues(); - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but state \"" + stateType.name() + "\" has not matching allowed values."); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but state \"" + + stateType.name() + "\" has not matching allowed values."); hasError = true; } if (ifaceStateType.unit() != Types::UnitNone && ifaceStateType.unit() != stateType.unit()) { QMetaEnum unitEnum = QMetaEnum::fromType(); - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but state \"" + stateType.name() + "\" has not matching unit: \"" + unitEnum.valueToKey(ifaceStateType.unit()) + "\" != \"" + unitEnum.valueToKey(stateType.unit())); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but state \"" + + stateType.name() + "\" has not matching unit: \"" + unitEnum.valueToKey(ifaceStateType.unit()) + "\" != \"" + + unitEnum.valueToKey(stateType.unit())); hasError = true; } @@ -819,7 +904,8 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) foreach (const InterfaceActionType &ifaceActionType, iface.actionTypes()) { if (!actionTypes.contains(ifaceActionType.name())) { if (!ifaceActionType.optional()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but doesn't implement action \"" + ifaceActionType.name() + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + + "\" but doesn't implement action \"" + ifaceActionType.name() + "\""); hasError = true; } continue; @@ -829,49 +915,63 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) foreach (const ParamType &ifaceActionParamType, ifaceActionType.paramTypes()) { ParamType paramType = actionType.paramTypes().findByName(ifaceActionParamType.name()); if (!paramType.isValid()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but action \"" + actionType.name() + "\" doesn't implement action param \"" + ifaceActionParamType.name() + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but action \"" + + actionType.name() + "\" doesn't implement action param \"" + ifaceActionParamType.name() + "\""); hasError = true; } else { if (paramType.type() != ifaceActionParamType.type()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but action \"" + actionType.name() + "\" param \"" + paramType.name() + "\" is of wrong type: \"" + QVariant::typeToName(paramType.type()) + "\" expected: \"" + QVariant::typeToName(ifaceActionParamType.type()) + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but action \"" + + actionType.name() + "\" param \"" + paramType.name() + "\" is of wrong type: \"" + + QVariant::typeToName(paramType.type()) + "\" expected: \"" + QVariant::typeToName(ifaceActionParamType.type()) + "\""); hasError = true; } foreach (const QVariant &allowedValue, ifaceActionParamType.allowedValues()) { if (!paramType.allowedValues().contains(allowedValue)) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but action \"" + actionType.name() + "\" param \"" + paramType.name() + "\" is missing allowed value \"" + allowedValue.toString() + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but action \"" + + actionType.name() + "\" param \"" + paramType.name() + "\" is missing allowed value \"" + allowedValue.toString() + + "\""); hasError = true; } } if (ifaceActionParamType.minValue() == "any") { if (paramType.minValue().isNull()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but action \"" + actionType.name() + "\" param \"" + paramType.name() + "\" is missing a minimum value"); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but action \"" + + actionType.name() + "\" param \"" + paramType.name() + "\" is missing a minimum value"); hasError = true; } } else if (!ifaceActionParamType.minValue().isNull()) { if (paramType.minValue() != ifaceActionParamType.minValue()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but action \"" + actionType.name() + "\" param \"" + paramType.name() + "\" has not matching minimum value: \"" + paramType.minValue().toString() + "\" != \"" + ifaceActionParamType.minValue().toString() + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but action \"" + + actionType.name() + "\" param \"" + paramType.name() + "\" has not matching minimum value: \"" + + paramType.minValue().toString() + "\" != \"" + ifaceActionParamType.minValue().toString() + "\""); hasError = true; } } if (ifaceActionParamType.maxValue() == "any") { if (paramType.maxValue().isNull()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but action \"" + actionType.name() + "\" param \"" + paramType.name() + "\" is missing a maximum value"); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but action \"" + + actionType.name() + "\" param \"" + paramType.name() + "\" is missing a maximum value"); hasError = true; } } else if (!ifaceActionParamType.maxValue().isNull()) { if (paramType.maxValue() != ifaceActionParamType.maxValue()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but action \"" + actionType.name() + "\" param \"" + paramType.name() + "\" has not matching maximum value: \"" + paramType.maxValue().toString() + "\" != \"" + ifaceActionParamType.maxValue().toString() + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but action \"" + + actionType.name() + "\" param \"" + paramType.name() + "\" has not matching maximum value: \"" + + paramType.maxValue().toString() + "\" != \"" + ifaceActionParamType.maxValue().toString() + "\""); hasError = true; } } if (ifaceActionParamType.defaultValue() == "any") { if (paramType.defaultValue().isNull()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but action \"" + actionType.name() + "\" param \"" + paramType.name() + "\" is missing a default value"); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but action \"" + + actionType.name() + "\" param \"" + paramType.name() + "\" is missing a default value"); hasError = true; } } else if (!ifaceActionParamType.defaultValue().isNull()) { if (paramType.defaultValue() != ifaceActionParamType.defaultValue()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but action \"" + actionType.name() + "\" param \"" + paramType.name() + "\" is has incompatible default value: \"" + paramType.defaultValue().toString() + "\" != \"" + ifaceActionParamType.defaultValue().toString() + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but action \"" + + actionType.name() + "\" param \"" + paramType.name() + "\" is has incompatible default value: \"" + + paramType.defaultValue().toString() + "\" != \"" + ifaceActionParamType.defaultValue().toString() + "\""); hasError = true; } } @@ -887,7 +987,9 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) // name is set or not. if (ifaceActionType.paramTypes().findByName(paramType.name()).name().isEmpty()) { if (paramType.defaultValue().isNull()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but action \"" + actionType.name() + "\" param \"" + paramType.name() + "\" is missing a default value as the interface requires this action to be executable without params."); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but action \"" + + actionType.name() + "\" param \"" + paramType.name() + + "\" is missing a default value as the interface requires this action to be executable without params."); hasError = true; } } @@ -897,7 +999,8 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) foreach (const InterfaceEventType &ifaceEventType, iface.eventTypes()) { if (!eventTypes.contains(ifaceEventType.name())) { if (!ifaceEventType.optional()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but doesn't implement event \"" + ifaceEventType.name() + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + + "\" but doesn't implement event \"" + ifaceEventType.name() + "\""); hasError = true; } continue; @@ -908,49 +1011,63 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) foreach (const ParamType &ifaceEventParamType, ifaceEventType.paramTypes()) { ParamType paramType = eventType.paramTypes().findByName(ifaceEventParamType.name()); if (!paramType.isValid()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but event \"" + eventType.name() + "\" doesn't implement event param \"" + ifaceEventParamType.name() + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but event \"" + + eventType.name() + "\" doesn't implement event param \"" + ifaceEventParamType.name() + "\""); hasError = true; } else { if (paramType.type() != ifaceEventParamType.type()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but event \"" + eventType.name() + "\" param \"" + paramType.name() + "\" is of wrong type: \"" + QVariant::typeToName(paramType.type()) + "\" expected: \"" + QVariant::typeToName(ifaceEventParamType.type()) + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but event \"" + + eventType.name() + "\" param \"" + paramType.name() + "\" is of wrong type: \"" + QVariant::typeToName(paramType.type()) + + "\" expected: \"" + QVariant::typeToName(ifaceEventParamType.type()) + "\""); hasError = true; } foreach (const QVariant &allowedValue, ifaceEventParamType.allowedValues()) { if (!paramType.allowedValues().contains(allowedValue)) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but event \"" + eventType.name() + "\" param \"" + paramType.name() + "\" is missing allowed value \"" + allowedValue.toString() + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but event \"" + + eventType.name() + "\" param \"" + paramType.name() + "\" is missing allowed value \"" + allowedValue.toString() + + "\""); hasError = true; } } if (ifaceEventParamType.minValue() == "any") { if (paramType.minValue().isNull()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but event \"" + eventType.name() + "\" param \"" + paramType.name() + "\" is missing a minimum value"); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but event \"" + + eventType.name() + "\" param \"" + paramType.name() + "\" is missing a minimum value"); hasError = true; } } else if (!ifaceEventParamType.minValue().isNull()) { if (paramType.minValue() != ifaceEventParamType.minValue()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but event \"" + eventType.name() + "\" param \"" + paramType.name() + "\" has not matching minimum value: \"" + paramType.minValue().toString() + "\" != \"" + ifaceEventParamType.minValue().toString() + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but event \"" + + eventType.name() + "\" param \"" + paramType.name() + "\" has not matching minimum value: \"" + + paramType.minValue().toString() + "\" != \"" + ifaceEventParamType.minValue().toString() + "\""); hasError = true; } } if (ifaceEventParamType.maxValue() == "any") { if (paramType.maxValue().isNull()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but event \"" + eventType.name() + "\" param \"" + paramType.name() + "\" is missing a maximum value"); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but event \"" + + eventType.name() + "\" param \"" + paramType.name() + "\" is missing a maximum value"); hasError = true; } } else if (!ifaceEventParamType.maxValue().isNull()) { if (paramType.maxValue() != ifaceEventParamType.maxValue()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but event \"" + eventType.name() + "\" param \"" + paramType.name() + "\" has not matching maximum value: \"" + paramType.maxValue().toString() + "\" != \"" + ifaceEventParamType.maxValue().toString() + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but event \"" + + eventType.name() + "\" param \"" + paramType.name() + "\" has not matching maximum value: \"" + + paramType.maxValue().toString() + "\" != \"" + ifaceEventParamType.maxValue().toString() + "\""); hasError = true; } } if (ifaceEventParamType.defaultValue().toString() == "any") { if (paramType.defaultValue().isNull()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but event \"" + eventType.name() + "\" param \"" + paramType.name() + "\" is missing a default value"); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but event \"" + + eventType.name() + "\" param \"" + paramType.name() + "\" is missing a default value"); hasError = true; } } else if (!ifaceEventParamType.defaultValue().isNull()) { if (paramType.defaultValue() != ifaceEventParamType.defaultValue()) { - m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but event \"" + eventType.name() + "\" param \"" + paramType.name() + "\" is has incompatible default value: \"" + paramType.defaultValue().toString() + "\" != \"" + ifaceEventParamType.defaultValue().toString() + "\""); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but event \"" + + eventType.name() + "\" param \"" + paramType.name() + "\" is has incompatible default value: \"" + + paramType.defaultValue().toString() + "\" != \"" + ifaceEventParamType.defaultValue().toString() + "\""); hasError = true; } } @@ -1019,7 +1136,7 @@ QPair PluginMetadata::loadAndVerifyUnit(const QString &unitSt return QPair(false, Types::UnitNone); } - return QPair(true, (Types::Unit)enumValue); + return QPair(true, (Types::Unit) enumValue); } QPair PluginMetadata::verifyFields(const QStringList &possibleFields, const QStringList &mandatoryFields, const QJsonObject &value) @@ -1071,7 +1188,7 @@ QPair PluginMetadata::parseParamTypes(const QJsonArray &array) } // Check type -#if QT_VERSION >= QT_VERSION_CHECK(6,0,0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QMetaType::Type t = static_cast(QMetaType::fromName(pt.value("type").toString().toUtf8()).id()); #else QMetaType::Type t = static_cast(QVariant::nameToType(pt.value("type").toString().toLatin1().data())); @@ -1098,7 +1215,6 @@ QPair PluginMetadata::parseParamTypes(const QJsonArray &array) ParamType paramType(paramTypeId, paramName, t, defaultValue); paramType.setDisplayName(pt.value("displayName").toString()); - // Set allowed values QVariantList allowedValues; foreach (const QJsonValue &allowedTypesJson, pt.value("allowedValues").toArray()) { @@ -1144,6 +1260,23 @@ QPair PluginMetadata::parseParamTypes(const QJsonArray &array) // explicitly and convert() would initialize it to the variant's default value maxValue.convert(t); } + + if (pt.contains("stepSize")) { + double stepSize = pt.value("stepSize").toDouble(); + if (stepSize != 0) { + if (!s_validTypesForStepSize.contains(paramType.type())) { + m_validationErrors.append("Param type \"" + paramName + "\" has configured a step size but the data type " + pt.value("type").toString() + + " does not support that. Only numeric state types can have a stepSize."); + hasErrors = true; + } else if (stepSize < 0) { + m_validationErrors.append("Param type \"" + paramName + "\" has configured a negative stepSize which is not allowed."); + hasErrors = true; + } else { + paramType.setStepSize(stepSize); + } + } + } + paramType.setLimits(minValue, maxValue); paramType.setIndex(index++); paramTypes.append(paramType); @@ -1174,7 +1307,7 @@ QPair PluginMetadata::loadAndVerifyInputType(const QStri return QPair(false, Types::InputTypeNone); } - return QPair(true, (Types::InputType)enumValue); + return QPair(true, (Types::InputType) enumValue); } bool PluginMetadata::verifyDuplicateUuid(const QUuid &uuid) diff --git a/libnymea/types/paramtype.cpp b/libnymea/types/paramtype.cpp index ae849f4b..b072d7ee 100644 --- a/libnymea/types/paramtype.cpp +++ b/libnymea/types/paramtype.cpp @@ -38,22 +38,15 @@ \sa isValid() */ - #include "paramtype.h" /*! Constructs a ParamType object with the given \a id, \a name, \a type and \a defaultValue. */ -ParamType::ParamType(const ParamTypeId &id, const QString &name, const QMetaType::Type type, const QVariant &defaultValue): - m_id(id), - m_name(name), - m_index(0), - m_type(type), - m_defaultValue(defaultValue), - m_inputType(Types::InputTypeNone), - m_unit(Types::UnitNone), - m_readOnly(false) -{ - -} +ParamType::ParamType(const ParamTypeId &id, const QString &name, const QMetaType::Type type, const QVariant &defaultValue) + : m_id(id) + , m_name(name) + , m_type(type) + , m_defaultValue(defaultValue) +{} /*! Returns the \l{ParamTypeId} of this ParamType. */ ParamTypeId ParamType::id() const @@ -145,6 +138,18 @@ void ParamType::setMaxValue(const QVariant &maxValue) m_maxValue = maxValue; } +/*! Returns the step size for the value of this ParamType. If there are no steps to consider, it returns 0. */ +double ParamType::stepSize() const +{ + return m_stepSize; +} + +/*! Sets the step size for the value of this ParamType to the given \a stepSize. If there are no steps to consider, it can be set to 0. */ +void ParamType::setStepSize(double stepSize) +{ + m_stepSize = stepSize; +} + /*! Returns the input type of this ParamType. */ Types::InputType ParamType::inputType() const { @@ -215,8 +220,18 @@ bool ParamType::isValid() const /*! Returns a list of all valid JSON properties a ParamType JSON definition can have. */ QStringList ParamType::typeProperties() { - return QStringList() << "id" << "name" << "displayName" << "type" << "defaultValue" << "inputType" - << "unit" << "minValue" << "maxValue" << "allowedValues" << "readOnly"; + return QStringList() << "id" + << "name" + << "displayName" + << "type" + << "defaultValue" + << "inputType" + << "unit" + << "minValue" + << "maxValue" + << "stepSize" + << "allowedValues" + << "readOnly"; } /*! Returns a list of mandatory JSON properties a ParamType JSON definition must have. */ @@ -229,22 +244,16 @@ QStringList ParamType::mandatoryTypeProperties() QDebug operator<<(QDebug dbg, const ParamType ¶mType) { QString typeName; -#if QT_VERSION >= QT_VERSION_CHECK(6,0,0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) typeName = QString(QMetaType(paramType.type()).name()); #else typeName = QVariant::typeToName(paramType.type()); #endif QDebugStateSaver saver(dbg); - dbg.nospace() << "ParamType(Id" << paramType.id().toString() - << " Name: " << paramType.name() - << ", Type:" << typeName - << ", Default:" << paramType.defaultValue() - << ", Min:" << paramType.minValue() - << ", Max:" << paramType.maxValue() - << ", Allowed values:" << paramType.allowedValues() - << ", ReadOnly:" << paramType.readOnly() - << ")"; + dbg.nospace() << "ParamType(Id" << paramType.id().toString() << " Name: " << paramType.name() << ", Type:" << typeName << ", Default:" << paramType.defaultValue() + << ", Min:" << paramType.minValue() << ", Max:" << paramType.maxValue() << ", Allowed values:" << paramType.allowedValues() + << ", ReadOnly:" << paramType.readOnly() << ")"; return dbg; } @@ -254,16 +263,16 @@ QDebug operator<<(QDebug dbg, const QList ¶mTypes) { QDebugStateSaver saver(dbg); dbg.nospace() << "ParamTypeList (count:" << paramTypes.count() << ")" << '\n'; - for (int i = 0; i < paramTypes.count(); i++ ) { + for (int i = 0; i < paramTypes.count(); i++) { dbg.nospace() << " " << i << ": " << paramTypes.at(i) << '\n'; } return dbg; } -ParamTypes::ParamTypes(const QList &other): QList(other) -{ -} +ParamTypes::ParamTypes(const QList &other) + : QList(other) +{} bool ParamTypes::contains(const ParamTypeId ¶mTypeId) { diff --git a/libnymea/types/paramtype.h b/libnymea/types/paramtype.h index a9f975d4..2b3101f8 100644 --- a/libnymea/types/paramtype.h +++ b/libnymea/types/paramtype.h @@ -25,8 +25,8 @@ #ifndef PARAMTYPE_H #define PARAMTYPE_H -#include #include +#include #include "libnymea.h" #include "typeutils.h" @@ -42,6 +42,7 @@ class LIBNYMEA_EXPORT ParamType Q_PROPERTY(QVariant defaultValue READ defaultValue WRITE setDefaultValue USER true) Q_PROPERTY(QVariant minValue READ minValue WRITE setMinValue USER true) Q_PROPERTY(QVariant maxValue READ maxValue WRITE setMaxValue USER true) + Q_PROPERTY(double stepSize READ stepSize WRITE setStepSize USER true) Q_PROPERTY(QVariantList allowedValues READ allowedValues WRITE setAllowedValues USER true) Q_PROPERTY(Types::InputType inputType READ inputType WRITE setInputType USER true) Q_PROPERTY(Types::Unit unit READ unit WRITE setUnit USER true) @@ -74,6 +75,9 @@ public: QVariant maxValue() const; void setMaxValue(const QVariant &maxValue); + double stepSize() const; + void setStepSize(double stepSize); + Types::InputType inputType() const; void setInputType(const Types::InputType &inputType); @@ -98,18 +102,19 @@ private: ParamTypeId m_id; QString m_name; QString m_displayName; - int m_index; + int m_index = 0; QMetaType::Type m_type; QVariant m_defaultValue; QVariant m_minValue; QVariant m_maxValue; - Types::InputType m_inputType; - Types::Unit m_unit; + double m_stepSize = 0; + Types::InputType m_inputType = Types::InputTypeNone; + Types::Unit m_unit = Types::UnitNone; QVariantList m_allowedValues; - bool m_readOnly; + bool m_readOnly = false; }; -class ParamTypes: public QList +class ParamTypes : public QList { Q_GADGET Q_PROPERTY(int count READ count) @@ -123,7 +128,6 @@ public: ParamType findByName(const QString &name) const; ParamType findById(const ParamTypeId &id) const; ParamType &operator[](const QString &name); - }; Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(ParamTypes) diff --git a/libnymea/types/statetype.cpp b/libnymea/types/statetype.cpp index 79f94570..d946f3ff 100644 --- a/libnymea/types/statetype.cpp +++ b/libnymea/types/statetype.cpp @@ -34,19 +34,14 @@ #include "statetype.h" -StateType::StateType() -{ - -} +StateType::StateType() {} /*! Constructs a StateType with the given \a id. * When creating a \l{DevicePlugin} generate a new uuid for each StateType you define and * hardcode it into the plugin json file. */ -StateType::StateType(const StateTypeId &id): - m_id(id) -{ - -} +StateType::StateType(const StateTypeId &id) + : m_id(id) +{} /*! Returns the id of the StateType. */ StateTypeId StateType::id() const @@ -141,6 +136,18 @@ void StateType::setMaxValue(const QVariant &maxValue) m_maxValue = maxValue; } +/*! Returns the step size for the value of this StateType. If there are no steps to consider, the value will be 0. */ +double StateType::stepSize() const +{ + return m_stepSize; +} + +/*! Set the step size for the value of this StateType to \a stepSize. If there are no steps to consider, the value can be set to 0. */ +void StateType::setStepSize(double stepSize) +{ + m_stepSize = stepSize; +} + /*! Returns the list of possible values of this StateType. If the list is empty or invalid the \l{State} value can take every value. */ QVariantList StateType::possibleValues() const { diff --git a/libnymea/types/statetype.h b/libnymea/types/statetype.h index 6591a7d8..116ba0f4 100644 --- a/libnymea/types/statetype.h +++ b/libnymea/types/statetype.h @@ -43,6 +43,7 @@ class LIBNYMEA_EXPORT StateType Q_PROPERTY(Types::IOType ioType READ ioType WRITE setIOType USER true) Q_PROPERTY(QVariant minValue READ minValue WRITE setMinValue USER true) Q_PROPERTY(QVariant maxValue READ maxValue WRITE setMaxValue USER true) + Q_PROPERTY(double stepSize READ stepSize WRITE setStepSize USER true) Q_PROPERTY(QVariantList possibleValues READ possibleValues WRITE setPossibleValues USER true) Q_PROPERTY(QStringList possibleValuesDisplayNames READ possibleValuesDisplayNames WRITE setPossibleValuesDisplayNames USER true) @@ -73,6 +74,9 @@ public: QVariant maxValue() const; void setMaxValue(const QVariant &maxValue); + double stepSize() const; + void setStepSize(double stepSize); + QVariantList possibleValues() const; void setPossibleValues(const QVariantList &possibleValues); @@ -108,6 +112,7 @@ private: QVariant m_defaultValue; QVariant m_minValue; QVariant m_maxValue; + double m_stepSize = 0; QVariantList m_possibleValues; QStringList m_possibleValuesDisplayNames; Types::Unit m_unit = Types::UnitNone; @@ -119,7 +124,7 @@ private: }; Q_DECLARE_METATYPE(StateType) -class StateTypes: public QList +class StateTypes : public QList { Q_GADGET Q_PROPERTY(int count READ count) diff --git a/nymea.pro b/nymea.pro index 066cdcab..6cfd8184 100644 --- a/nymea.pro +++ b/nymea.pro @@ -11,7 +11,7 @@ isEmpty(NYMEA_VERSION) { # define protocol versions JSON_PROTOCOL_VERSION_MAJOR=8 -JSON_PROTOCOL_VERSION_MINOR=3 +JSON_PROTOCOL_VERSION_MINOR=5 JSON_PROTOCOL_VERSION="$${JSON_PROTOCOL_VERSION_MAJOR}.$${JSON_PROTOCOL_VERSION_MINOR}" LIBNYMEA_API_VERSION_MAJOR=9 LIBNYMEA_API_VERSION_MINOR=0 diff --git a/tests/auto/api.json b/tests/auto/api.json index ad82c2fb..ecbc9cc6 100644 --- a/tests/auto/api.json +++ b/tests/auto/api.json @@ -1,4 +1,4 @@ -8.3 +8.5 { "enums": { "BasicType": [ @@ -3008,6 +3008,7 @@ "o:maxValue": "Variant", "o:minValue": "Variant", "o:readOnly": "Bool", + "o:stepSize": "Double", "o:unit": "$ref:Unit", "r:id": "Uuid", "type": "$ref:BasicType" @@ -3140,6 +3141,7 @@ "Variant" ], "o:possibleValuesDisplayNames": "StringList", + "o:stepSize": "Double", "o:unit": "$ref:Unit", "r:id": "Uuid", "type": "$ref:BasicType"