Merge PR #726: Introduce optional stepSize for StateTypes and ParamTypes
This commit is contained in:
commit
9898fbd6f1
@ -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 ¶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<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)
|
||||
|
||||
@ -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 ¶mTypeId, 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 ¶mTypeId, 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 *>());
|
||||
}
|
||||
|
||||
@ -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 ¶mType, 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
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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<ParamType> ¶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<ParamType> &other): QList<ParamType>(other)
|
||||
{
|
||||
}
|
||||
ParamTypes::ParamTypes(const QList<ParamType> &other)
|
||||
: QList<ParamType>(other)
|
||||
{}
|
||||
|
||||
bool ParamTypes::contains(const ParamTypeId ¶mTypeId)
|
||||
{
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user