Merge PR #726: Introduce optional stepSize for StateTypes and ParamTypes

This commit is contained in:
jenkins 2026-01-19 10:09:54 +01:00
commit 9898fbd6f1
11 changed files with 633 additions and 191 deletions

View File

@ -27,26 +27,26 @@
#include "types/interface.h"
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <QMetaObject>
#include <QJsonObject>
#include <QMetaEnum>
#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
#include <QMetaObject>
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#include <QColor>
#endif
PluginMetadata::PluginMetadata()
{
static const QList<QMetaType::Type> 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>("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<QStringList, QStringList> 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::Type>(QMetaType::fromName(QByteArray(st.value("type").toString().toUtf8())).id());
#else
QMetaType::Type t = static_cast<QMetaType::Type>(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 &paramType = 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<Types::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()));
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<Types::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()));
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<bool, Types::Unit> PluginMetadata::loadAndVerifyUnit(const QString &unitSt
return QPair<bool, Types::Unit>(false, Types::UnitNone);
}
return QPair<bool, Types::Unit>(true, (Types::Unit)enumValue);
return QPair<bool, Types::Unit>(true, (Types::Unit) enumValue);
}
QPair<QStringList, QStringList> PluginMetadata::verifyFields(const QStringList &possibleFields, const QStringList &mandatoryFields, const QJsonObject &value)
@ -1071,7 +1188,7 @@ QPair<bool, ParamTypes> 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::Type>(QMetaType::fromName(pt.value("type").toString().toUtf8()).id());
#else
QMetaType::Type t = static_cast<QMetaType::Type>(QVariant::nameToType(pt.value("type").toString().toLatin1().data()));
@ -1098,7 +1215,6 @@ QPair<bool, ParamTypes> 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<bool, ParamTypes> 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<bool, Types::InputType> PluginMetadata::loadAndVerifyInputType(const QStri
return QPair<bool, Types::InputType>(false, Types::InputTypeNone);
}
return QPair<bool, Types::InputType>(true, (Types::InputType)enumValue);
return QPair<bool, Types::InputType>(true, (Types::InputType) enumValue);
}
bool PluginMetadata::verifyDuplicateUuid(const QUuid &uuid)

View File

@ -125,33 +125,29 @@
*/
#include "thing.h"
#include "types/event.h"
#include "loggingcategories.h"
#include "statevaluefilters/statevaluefilteradaptive.h"
#include "thingutils.h"
#include "types/event.h"
#include <QJsonDocument>
#include <QDebug>
#include <QJsonDocument>
/*! Construct a Thing with the given \a pluginId, \a id, \a thingClassId and \a parent. */
Thing::Thing(const PluginId &pluginId, const ThingClass &thingClass, const ThingId &id, QObject *parent):
QObject(parent),
m_thingClass(thingClass),
m_pluginId(pluginId),
m_id(id)
{
}
Thing::Thing(const PluginId &pluginId, const ThingClass &thingClass, const ThingId &id, QObject *parent)
: QObject(parent)
, m_thingClass(thingClass)
, m_pluginId(pluginId)
, m_id(id)
{}
/*! Construct a Thing with the given \a pluginId, \a thingClassId and \a parent. A new ThingId will be created for this Thing. */
Thing::Thing(const PluginId &pluginId, const ThingClass &thingClass, QObject *parent):
QObject(parent),
m_thingClass(thingClass),
m_pluginId(pluginId),
m_id(ThingId::createThingId())
{
}
Thing::Thing(const PluginId &pluginId, const ThingClass &thingClass, QObject *parent)
: QObject(parent)
, m_thingClass(thingClass)
, m_pluginId(pluginId)
, m_id(ThingId::createThingId())
{}
Thing::~Thing()
{
@ -385,23 +381,99 @@ void Thing::setStateValue(const StateTypeId &stateTypeId, const QVariant &value)
if (m_states.at(i).stateTypeId() == stateTypeId) {
QVariant newValue = value;
if (!newValue.convert(stateType.type())) {
qCWarning(dcThing()).nospace() << this << ": Invalid value " << value << " for state " << stateType.name() << ". Type mismatch. Expected type: " << QVariant::typeToName(stateType.type()) << " (Discarding change)";
qCWarning(dcThing()).nospace()
<< this
<< ": Invalid value "
<< value
<< " for state "
<< stateType.name()
<< ". Type mismatch. Expected type: "
<< QVariant::typeToName(stateType.type())
<< " (Discarding change)";
return;
}
State state = m_states.at(i);
if (state.minValue().isValid() && ThingUtils::variantLessThan(value, state.minValue())) {
qCWarning(dcThing()).nospace() << this << ": Invalid value " << value << " for state " << stateType.name() << ". Out of range: " << state.minValue() << " - " << state.maxValue() << " (Correcting to closest value within range)";
qCWarning(dcThing()).nospace()
<< this
<< ": Invalid value "
<< value
<< " for state "
<< stateType.name()
<< ". Out of range: "
<< state.minValue()
<< " - "
<< state.maxValue()
<< " (Correcting to closest value within range)";
newValue = state.minValue();
}
if (state.maxValue().isValid() && ThingUtils::variantGreaterThan(value, state.maxValue())) {
qCWarning(dcThing()).nospace() << this << ": Invalid value " << value << " for state " << stateType.name() << ". Out of range: " << state.minValue() << " - " << state.maxValue() << " (Correcting to closest value within range)";
qCWarning(dcThing()).nospace()
<< this
<< ": Invalid value "
<< value
<< " for state "
<< stateType.name()
<< ". Out of range: "
<< state.minValue()
<< " - "
<< state.maxValue()
<< " (Correcting to closest value within range)";
newValue = state.maxValue();
}
if (!stateType.possibleValues().isEmpty() && !stateType.possibleValues().contains(value)) {
qCWarning(dcThing()).nospace() << this << ": Invalid value " << value << " for state " << stateType.name() << ". Not an accepted value. Possible values: " << stateType.possibleValues() << " (Discarding change)";
qCWarning(dcThing()).nospace()
<< this
<< ": Invalid value "
<< value
<< " for state "
<< stateType.name()
<< ". Not an accepted value. Possible values: "
<< stateType.possibleValues()
<< " (Discarding change)";
return;
}
QVariant clampedValue = ThingUtils::ensureValueClamping(newValue, stateType.type(), state.minValue(), state.maxValue(), stateType.stepSize());
if (stateType.stepSize() != 0) {
const double stepEpsilon = qMax(qAbs(stateType.stepSize()) * 1e-9, 1e-12);
bool stepAdjusted = false;
switch (stateType.type()) {
case QMetaType::Double:
stepAdjusted = qAbs(newValue.toDouble() - clampedValue.toDouble()) > stepEpsilon;
break;
case QMetaType::Float:
stepAdjusted = qAbs(newValue.toFloat() - clampedValue.toFloat()) > stepEpsilon;
break;
case QMetaType::Int:
case QMetaType::UInt:
case QMetaType::LongLong:
case QMetaType::ULongLong:
case QMetaType::Short:
case QMetaType::ULong:
case QMetaType::UShort:
stepAdjusted = (newValue != clampedValue);
break;
default:
break;
}
if (stepAdjusted) {
newValue = clampedValue;
qCWarning(dcThing()).nospace()
<< this
<< ": Invalid value "
<< value
<< " for state "
<< stateType.name()
<< ". Step size: "
<< stateType.stepSize()
<< " (Correcting to closest value within step size)";
}
} else {
newValue = clampedValue;
}
StateValueFilter *filter = m_stateValueFilters.value(stateTypeId);
if (filter) {
filter->addValue(newValue);
@ -411,7 +483,14 @@ void Thing::setStateValue(const StateTypeId &stateTypeId, const QVariant &value)
QVariant oldValue = m_states.at(i).value();
if (oldValue == newValue) {
qCDebug(dcThing()).nospace() << this << ": Discarding state change for " << stateType.name() << " as the value did not actually change. Old value:" << oldValue << "New value:" << newValue;
qCDebug(dcThing()).nospace()
<< this
<< ": Discarding state change for "
<< stateType.name()
<< " as the value did not actually change. Old value:"
<< oldValue
<< "New value:"
<< newValue;
return;
}
@ -457,7 +536,14 @@ void Thing::setStateMinValue(const StateTypeId &stateTypeId, const QVariant &min
// Sanity check for max >= min
if (ThingUtils::variantLessThan(m_states.at(i).maxValue(), newMin)) {
qCWarning(dcThing()).nospace() << this << ": Adjusting state maximum value for " << stateType.name() << " from " << m_states.at(i).maxValue() << " to new minimum value of " << newMin;
qCWarning(dcThing()).nospace()
<< this
<< ": Adjusting state maximum value for "
<< stateType.name()
<< " from "
<< m_states.at(i).maxValue()
<< " to new minimum value of "
<< newMin;
m_states[i].setMaxValue(newMin);
}
if (ThingUtils::variantLessThan(m_states.at(i).value(), newMin)) {
@ -501,12 +587,26 @@ void Thing::setStateMaxValue(const StateTypeId &stateTypeId, const QVariant &max
if (newMax.isValid()) {
// Sanity check for min <= max
if (ThingUtils::variantGreaterThan(m_states.at(i).minValue(), newMax)) {
qCWarning(dcThing()).nospace() << this << ": Adjusting minimum state value for " << stateType.name() << " from " << m_states.at(i).minValue() << " to new maximum value of " << newMax;
qCWarning(dcThing()).nospace()
<< this
<< ": Adjusting minimum state value for "
<< stateType.name()
<< " from "
<< m_states.at(i).minValue()
<< " to new maximum value of "
<< newMax;
m_states[i].setMinValue(newMax);
}
if (ThingUtils::variantGreaterThan(m_states.at(i).value(), newMax)) {
qCInfo(dcThing()).nospace() << this << ": Adjusting state value for " << stateType.name() << " from " << m_states.at(i).value() << " to new maximum value of " << newMax;
qCInfo(dcThing()).nospace()
<< this
<< ": Adjusting state value for "
<< stateType.name()
<< " from "
<< m_states.at(i).value()
<< " to new maximum value of "
<< newMax;
m_states[i].setValue(maxValue);
}
}
@ -548,16 +648,37 @@ void Thing::setStateMinMaxValues(const StateTypeId &stateTypeId, const QVariant
if (newMax.isValid() || newMax.isValid()) {
// Sanity check for min <= max
if (ThingUtils::variantGreaterThan(newMin, newMax)) {
qCWarning(dcThing()).nospace() << this << ": Adjusting maximum state value for " << stateType.name() << " from " << m_states.at(i).maxValue() << " to new minimum value of " << newMax;
qCWarning(dcThing()).nospace()
<< this
<< ": Adjusting maximum state value for "
<< stateType.name()
<< " from "
<< m_states.at(i).maxValue()
<< " to new minimum value of "
<< newMax;
m_states[i].setMaxValue(newMin);
}
if (ThingUtils::variantLessThan(m_states.at(i).value(), m_states.at(i).minValue())) {
qCInfo(dcThing()).nospace() << this << ": Adjusting state value for " << stateType.name() << " from " << m_states.at(i).value() << " to new minimum value of " << m_states.at(i).minValue();
qCInfo(dcThing()).nospace()
<< this
<< ": Adjusting state value for "
<< stateType.name()
<< " from "
<< m_states.at(i).value()
<< " to new minimum value of "
<< m_states.at(i).minValue();
m_states[i].setValue(m_states.at(i).minValue());
}
if (ThingUtils::variantGreaterThan(m_states.at(i).value(), m_states.at(i).maxValue())) {
qCInfo(dcThing()).nospace() << this << ": Adjusting state value for " << stateType.name() << " from " << m_states.at(i).value() << " to new maximum value of " << m_states.at(i).maxValue();
qCInfo(dcThing()).nospace()
<< this
<< ": Adjusting state value for "
<< stateType.name()
<< " from "
<< m_states.at(i).value()
<< " to new maximum value of "
<< m_states.at(i).maxValue();
m_states[i].setValue(m_states.at(i).maxValue());
}
}
@ -568,7 +689,6 @@ void Thing::setStateMinMaxValues(const StateTypeId &stateTypeId, const QVariant
}
Q_ASSERT_X(false, m_name.toUtf8(), QString("Failed setting maximum state value %1 to %2").arg(stateType.name()).arg(maxValue.toString()).toUtf8());
qCWarning(dcThing()).nospace() << this << ": Failed setting maximum state value " << stateType.name() << " to " << maxValue;
}
void Thing::setStateMinMaxValues(const QString &stateName, const QVariant &minValue, const QVariant &maxValue)
@ -594,10 +714,24 @@ void Thing::setStatePossibleValues(const StateTypeId &stateTypeId, const QVarian
if (!values.contains(m_states.value(i).value())) {
if (values.contains(stateType.defaultValue())) {
qCInfo(dcThing()).nospace() << this << ": Adjusting state value for " << stateType.name() << " from " << m_states.at(i).value() << " to default value of " << stateType.defaultValue();
qCInfo(dcThing()).nospace()
<< this
<< ": Adjusting state value for "
<< stateType.name()
<< " from "
<< m_states.at(i).value()
<< " to default value of "
<< stateType.defaultValue();
m_states[i].setValue(stateType.defaultValue());
} else if (!values.isEmpty()) {
qCInfo(dcThing()).nospace() << this << ": Adjusting state value for " << stateType.name() << " from " << m_states.at(i).value() << " to new value of " << values.first();
qCInfo(dcThing()).nospace()
<< this
<< ": Adjusting state value for "
<< stateType.name()
<< " from "
<< m_states.at(i).value()
<< " to new value of "
<< values.first();
m_states[i].setValue(values.first());
}
}
@ -606,8 +740,9 @@ void Thing::setStatePossibleValues(const StateTypeId &stateTypeId, const QVarian
}
}
qCWarning(dcThing()).nospace() << this << ": Failed setting maximum state value " << stateType.name() << " to " << values;
Q_ASSERT_X(false, m_name.toUtf8(), QString("Failed setting possible state values for %1 to %2").arg(stateType.name()).arg(QString(QJsonDocument::fromVariant(values).toJson())).toUtf8());
Q_ASSERT_X(false,
m_name.toUtf8(),
QString("Failed setting possible state values for %1 to %2").arg(stateType.name()).arg(QString(QJsonDocument::fromVariant(values).toJson())).toUtf8());
}
/*! Returns the \l{State} with the given \a stateTypeId of this thing. */
@ -740,9 +875,9 @@ void Thing::setStateValueFilter(const StateTypeId &stateTypeId, Types::StateValu
}
}
Things::Things(const QList<Thing*> &other)
Things::Things(const QList<Thing *> &other)
{
foreach (Thing* thing, other) {
foreach (Thing *thing, other) {
this->append(thing);
}
}
@ -792,7 +927,7 @@ QDebug operator<<(QDebug debug, Thing *thing)
Things Things::filterByParam(const ParamTypeId &paramTypeId, const QVariant &value)
{
Things ret;
foreach (Thing* thing, *this) {
foreach (Thing *thing, *this) {
if (!thing->thingClass().paramTypes().findById(paramTypeId).isValid()) {
continue;
}
@ -807,7 +942,7 @@ Things Things::filterByParam(const ParamTypeId &paramTypeId, const QVariant &val
Things Things::filterByThingClassId(const ThingClassId &thingClassId)
{
Things ret;
foreach (Thing* thing, *this) {
foreach (Thing *thing, *this) {
if (thing->thingClassId() == thingClassId) {
ret << thing;
}
@ -844,5 +979,5 @@ QVariant Things::get(int index) const
void Things::put(const QVariant &variant)
{
append(variant.value<Thing*>());
append(variant.value<Thing *>());
}

View File

@ -29,6 +29,29 @@
#include <QFileInfo>
#include <QJsonParseError>
#include <QMetaEnum>
#include <qmath.h>
namespace {
bool isStepSizeType(QMetaType::Type type)
{
switch (type) {
case QMetaType::Int:
case QMetaType::UInt:
case QMetaType::LongLong:
case QMetaType::ULongLong:
case QMetaType::Double:
case QMetaType::Float:
case QMetaType::Short:
case QMetaType::ULong:
case QMetaType::UShort:
return true;
default:
return false;
}
}
}
ThingUtils::ThingUtils()
{
@ -134,6 +157,39 @@ Thing::ThingError ThingUtils::verifyParam(const ParamType &paramType, const Para
}
}
if (paramType.stepSize() != 0) {
QVariant paramValue = param.value();
paramValue.convert(static_cast<int>(paramType.type()));
QVariant clampedValue = ThingUtils::ensureValueClamping(paramValue, paramType.type(), paramType.minValue(), paramType.maxValue(), paramType.stepSize());
const double stepEpsilon = qMax(qAbs(paramType.stepSize()) * 1e-9, 1e-12);
bool stepAdjusted = false;
switch (paramType.type()) {
case QMetaType::Double:
stepAdjusted = qAbs(paramValue.toDouble() - clampedValue.toDouble()) > stepEpsilon;
break;
case QMetaType::Float:
stepAdjusted = qAbs(paramValue.toFloat() - clampedValue.toFloat()) > stepEpsilon;
break;
case QMetaType::Int:
case QMetaType::UInt:
case QMetaType::LongLong:
case QMetaType::ULongLong:
case QMetaType::Short:
case QMetaType::ULong:
case QMetaType::UShort:
stepAdjusted = (paramValue != clampedValue);
break;
default:
break;
}
if (stepAdjusted) {
qCWarning(dcThing()) << "Value not matching step size for param" << param.paramTypeId().toString()
<< " Got:" << param.value() << " Step size:" << paramType.stepSize();
return Thing::ThingErrorInvalidParameter;
}
}
if (!paramType.allowedValues().isEmpty() && !paramType.allowedValues().contains(param.value())) {
QStringList allowedValues;
foreach (const QVariant &value, paramType.allowedValues()) {
@ -400,6 +456,73 @@ QStringList ThingUtils::generateInterfaceParentList(const QString &interface)
return ret;
}
QVariant ThingUtils::ensureValueClamping(const QVariant value, QMetaType::Type type, const QVariant &minValue, const QVariant &maxValue, double stepSize)
{
QVariant adjustedValue = value;
if (!adjustedValue.canConvert(static_cast<int>(type)) || !adjustedValue.convert(static_cast<int>(type))) {
return value;
}
if (stepSize == 0 || !isStepSizeType(type)) {
if (minValue.isValid() && ThingUtils::variantLessThan(adjustedValue, minValue)) {
return minValue;
}
if (maxValue.isValid() && ThingUtils::variantGreaterThan(adjustedValue, maxValue)) {
return maxValue;
}
return adjustedValue;
}
const double step = qAbs(stepSize);
const bool hasMinValue = minValue.isValid();
const bool hasMaxValue = maxValue.isValid();
const double baseValue = hasMinValue ? minValue.toDouble() : 0.0;
const double currentValue = adjustedValue.toDouble();
const qint64 roundedSteps = qRound64((currentValue - baseValue) / step);
double steppedValue = baseValue + roundedSteps * step;
if (hasMinValue) {
const double min = minValue.toDouble();
if (steppedValue < min) {
steppedValue = min;
}
}
if (hasMaxValue) {
const double max = maxValue.toDouble();
if (steppedValue > max) {
const qint64 maxSteps = static_cast<qint64>(qFloor((max - baseValue) / step));
steppedValue = baseValue + maxSteps * step;
if (hasMinValue && steppedValue < minValue.toDouble()) {
steppedValue = minValue.toDouble();
}
}
}
switch (type) {
case QMetaType::Int:
return QVariant(static_cast<int>(qRound64(steppedValue)));
case QMetaType::UInt:
return QVariant(static_cast<uint>(qRound64(steppedValue)));
case QMetaType::LongLong:
return QVariant(static_cast<qint64>(qRound64(steppedValue)));
case QMetaType::ULongLong:
return QVariant(static_cast<quint64>(qRound64(steppedValue)));
case QMetaType::Double:
return QVariant(steppedValue);
case QMetaType::Float:
return QVariant(static_cast<float>(steppedValue));
case QMetaType::Short:
return QVariant::fromValue(static_cast<short>(qRound64(steppedValue)));
case QMetaType::ULong:
return QVariant::fromValue(static_cast<ulong>(qRound64(steppedValue)));
case QMetaType::UShort:
return QVariant::fromValue(static_cast<ushort>(qRound64(steppedValue)));
default:
return adjustedValue;
}
}
bool ThingUtils::variantLessThan(const QVariant &leftHandSide, const QVariant &rightHandSide)
{
// Note: https://www.mail-archive.com/development@qt-project.org/msg39450.html

View File

@ -26,7 +26,6 @@
#define THINGUTILS_H
#include "thing.h"
#include "pluginmetadata.h"
#include "types/paramtype.h"
#include "types/interface.h"
@ -45,6 +44,8 @@ public:
static Interface mergeInterfaces(const Interface &iface1, const Interface &iface2);
static QStringList generateInterfaceParentList(const QString &interface);
static QVariant ensureValueClamping(const QVariant value, QMetaType::Type type, const QVariant &minValue, const QVariant &maxValue, double stepSize);
static bool variantLessThan(const QVariant &leftHandSide, const QVariant &rightHandSide);
static bool variantGreaterThan(const QVariant &leftHandSide, const QVariant &rightHandSide);
};

View File

@ -1,10 +1,10 @@
{
"description": "An EV-charger. Extends the power interface for charging/not charging an electric vehicle. Supports regulation of the max. charging current in addition to be powered on or off.",
"description": "An electric vehicle charger. Extends the power interface to report plug/charge state and session energy, and to optionally control charging current and phase count in addition to on/off power control.",
"extends": ["power"],
"states": [
{
"name": "maxChargingCurrent",
"type": "uint",
"type": "double",
"writable": true,
"unit": "Ampere",
"minValue": "any",

View File

@ -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 &paramType)
{
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<ParamType> &paramTypes)
{
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<ParamType> &other): QList<ParamType>(other)
{
}
ParamTypes::ParamTypes(const QList<ParamType> &other)
: QList<ParamType>(other)
{}
bool ParamTypes::contains(const ParamTypeId &paramTypeId)
{

View File

@ -25,8 +25,8 @@
#ifndef PARAMTYPE_H
#define PARAMTYPE_H
#include <QVariant>
#include <QDebug>
#include <QVariant>
#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<ParamType>
class ParamTypes : public QList<ParamType>
{
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<ParamType>)
Q_DECLARE_METATYPE(ParamTypes)

View File

@ -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
{

View File

@ -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<StateType>
class StateTypes : public QList<StateType>
{
Q_GADGET
Q_PROPERTY(int count READ count)

View File

@ -3039,6 +3039,7 @@
"o:maxValue": "Variant",
"o:minValue": "Variant",
"o:readOnly": "Bool",
"o:stepSize": "Double",
"o:unit": "$ref:Unit",
"r:id": "Uuid",
"type": "$ref:BasicType"
@ -3171,6 +3172,7 @@
"Variant"
],
"o:possibleValuesDisplayNames": "StringList",
"o:stepSize": "Double",
"o:unit": "$ref:Unit",
"r:id": "Uuid",
"type": "$ref:BasicType"

View File

@ -28,11 +28,14 @@
#include "integrations/thingdiscoveryinfo.h"
#include "integrations/thingsetupinfo.h"
#include "integrations/thingutils.h"
#include "servers/mocktcpserver.h"
#include "jsonrpc/integrationshandler.h"
#include "../plugins/mock/extern-plugininfo.h"
#include <QtMath>
using namespace nymeaserver;
class TestIntegrations : public NymeaTestBase
@ -136,6 +139,7 @@ private slots:
void triggerStateChangeSignal();
void params();
void ensureValueClamping();
void dynamicMinMax();
@ -2191,6 +2195,26 @@ void TestIntegrations::params()
QVERIFY(!event.param(ParamTypeId::createParamTypeId()).value().isValid());
}
void TestIntegrations::ensureValueClamping()
{
const double tolerance = 1e-6;
QVariant result = ThingUtils::ensureValueClamping(5.789, QMetaType::Double, 0.0, 10.0, 0.1);
QVERIFY(qAbs(result.toDouble() - 5.8) < tolerance);
result = ThingUtils::ensureValueClamping(1.3, QMetaType::Double, 1.0, 5.0, 0.5);
QVERIFY(qAbs(result.toDouble() - 1.5) < tolerance);
result = ThingUtils::ensureValueClamping(11.0, QMetaType::Double, 0.0, 10.0, 3.0);
QVERIFY(qAbs(result.toDouble() - 9.0) < tolerance);
result = ThingUtils::ensureValueClamping(7, QMetaType::Int, 0, 20, 5);
QCOMPARE(result.toInt(), 5);
result = ThingUtils::ensureValueClamping(-5, QMetaType::Int, 0, 20, 0);
QCOMPARE(result.toInt(), 0);
}
void TestIntegrations::dynamicMinMax()
{
enableNotifications({"Integrations"});
@ -2396,4 +2420,3 @@ void TestIntegrations::testTranslations()
#include "testintegrations.moc"
QTEST_MAIN(TestIntegrations)