Merge PR #314: Fix conversion of values

This commit is contained in:
Jenkins nymea 2020-08-14 13:09:34 +02:00
commit 768b6a877e
13 changed files with 207 additions and 167 deletions

View File

@ -185,10 +185,10 @@ Thing::ThingError ThingManagerImplementation::setPluginConfig(const PluginId &pl
return Thing::ThingErrorPluginNotFound; return Thing::ThingErrorPluginNotFound;
} }
ParamList params = buildParams(plugin->configurationDescription(), pluginConfig); Thing::ThingError verify = ThingUtils::verifyParams(plugin->configurationDescription(), pluginConfig);
Thing::ThingError verify = ThingUtils::verifyParams(plugin->configurationDescription(), params);
if (verify != Thing::ThingErrorNoError) if (verify != Thing::ThingErrorNoError)
return verify; return verify;
ParamList params = buildParams(plugin->configurationDescription(), pluginConfig);
Thing::ThingError result = plugin->setConfiguration(params); Thing::ThingError result = plugin->setConfiguration(params);
if (result != Thing::ThingErrorNoError) if (result != Thing::ThingErrorNoError)
@ -199,10 +199,7 @@ Thing::ThingError ThingManagerImplementation::setPluginConfig(const PluginId &pl
settings.beginGroup(plugin->pluginId().toString()); settings.beginGroup(plugin->pluginId().toString());
foreach (const Param &param, pluginConfig) { foreach (const Param &param, pluginConfig) {
settings.beginGroup(param.paramTypeId().toString()); settings.setValue(param.paramTypeId().toString(), param.value());
settings.setValue("type", static_cast<int>(param.value().type()));
settings.setValue("value", param.value());
settings.endGroup();
} }
settings.endGroup(); settings.endGroup();
@ -259,14 +256,14 @@ ThingDiscoveryInfo* ThingManagerImplementation::discoverThings(const ThingClassI
return discoveryInfo; return discoveryInfo;
} }
ParamList effectiveParams = buildParams(thingClass.discoveryParamTypes(), params); Thing::ThingError result = ThingUtils::verifyParams(thingClass.discoveryParamTypes(), params);
Thing::ThingError result = ThingUtils::verifyParams(thingClass.discoveryParamTypes(), effectiveParams);
if (result != Thing::ThingErrorNoError) { if (result != Thing::ThingErrorNoError) {
qCWarning(dcThingManager) << "Thing discovery failed. Parameter verification failed."; qCWarning(dcThingManager) << "Thing discovery failed. Parameter verification failed.";
ThingDiscoveryInfo *discoveryInfo = new ThingDiscoveryInfo(thingClassId, params, this); ThingDiscoveryInfo *discoveryInfo = new ThingDiscoveryInfo(thingClassId, params, this);
discoveryInfo->finish(result); discoveryInfo->finish(result);
return discoveryInfo; return discoveryInfo;
} }
ParamList effectiveParams = buildParams(thingClass.discoveryParamTypes(), params);
ThingDiscoveryInfo *discoveryInfo = new ThingDiscoveryInfo(thingClassId, effectiveParams, this, 30000); ThingDiscoveryInfo *discoveryInfo = new ThingDiscoveryInfo(thingClassId, effectiveParams, this, 30000);
connect(discoveryInfo, &ThingDiscoveryInfo::finished, this, [this, discoveryInfo](){ connect(discoveryInfo, &ThingDiscoveryInfo::finished, this, [this, discoveryInfo](){
@ -396,14 +393,14 @@ ThingSetupInfo *ThingManagerImplementation::reconfigureThingInternal(Thing *thin
return info; return info;
} }
ParamList finalParams = buildParams(thing->thingClass().paramTypes(), params); Thing::ThingError result = ThingUtils::verifyParams(thing->thingClass().paramTypes(), params);
Thing::ThingError result = ThingUtils::verifyParams(thing->thingClass().paramTypes(), finalParams);
if (result != Thing::ThingErrorNoError) { if (result != Thing::ThingErrorNoError) {
qCWarning(dcThingManager()) << "Cannot reconfigure thing. Params failed validation."; qCWarning(dcThingManager()) << "Cannot reconfigure thing. Params failed validation.";
ThingSetupInfo *info = new ThingSetupInfo(nullptr, this); ThingSetupInfo *info = new ThingSetupInfo(nullptr, this);
info->finish(result); info->finish(result);
return info; return info;
} }
ParamList finalParams = buildParams(thing->thingClass().paramTypes(), params);
// first remove the thing in the plugin // first remove the thing in the plugin
plugin->thingRemoved(thing); plugin->thingRemoved(thing);
@ -463,13 +460,13 @@ Thing::ThingError ThingManagerImplementation::setThingSettings(const ThingId &th
qCWarning(dcThingManager()) << "Cannot set thing settings. Thing" << thingId.toString() << "not found"; qCWarning(dcThingManager()) << "Cannot set thing settings. Thing" << thingId.toString() << "not found";
return Thing::ThingErrorThingNotFound; return Thing::ThingErrorThingNotFound;
} }
// build a list of settings using: a) provided new settings b) previous settings and c) default values Thing::ThingError status = ThingUtils::verifyParams(thing->thingClass().settingsTypes(), settings);
ParamList effectiveSettings = buildParams(thing->thingClass().settingsTypes(), settings, thing->settings());
Thing::ThingError status = ThingUtils::verifyParams(thing->thingClass().settingsTypes(), effectiveSettings);
if (status != Thing::ThingErrorNoError) { if (status != Thing::ThingErrorNoError) {
qCWarning(dcThingManager()) << "Error setting thing settings for" << thing->name() << thing->id().toString(); qCWarning(dcThingManager()) << "Error setting thing settings for" << thing->name() << thing->id().toString();
return status; return status;
} }
// build a list of settings using: a) provided new settings b) previous settings and c) default values
ParamList effectiveSettings = buildParams(thing->thingClass().settingsTypes(), settings, thing->settings());
thing->setSettings(effectiveSettings); thing->setSettings(effectiveSettings);
return Thing::ThingErrorNoError; return Thing::ThingErrorNoError;
} }
@ -693,8 +690,7 @@ ThingSetupInfo* ThingManagerImplementation::addConfiguredThingInternal(const Thi
} }
// set params // set params
ParamList effectiveParams = buildParams(thingClass.paramTypes(), params); Thing::ThingError paramsResult = ThingUtils::verifyParams(thingClass.paramTypes(), params);
Thing::ThingError paramsResult = ThingUtils::verifyParams(thingClass.paramTypes(), effectiveParams);
if (paramsResult != Thing::ThingErrorNoError) { if (paramsResult != Thing::ThingErrorNoError) {
qCWarning(dcThingManager()) << "Cannot add thing. Parameter verification failed."; qCWarning(dcThingManager()) << "Cannot add thing. Parameter verification failed.";
ThingSetupInfo *info = new ThingSetupInfo(nullptr, this); ThingSetupInfo *info = new ThingSetupInfo(nullptr, this);
@ -702,6 +698,7 @@ ThingSetupInfo* ThingManagerImplementation::addConfiguredThingInternal(const Thi
return info; return info;
} }
ParamList effectiveParams = buildParams(thingClass.paramTypes(), params);
Thing *thing = new Thing(plugin->pluginId(), thingClass, thingId, this); Thing *thing = new Thing(plugin->pluginId(), thingClass, thingId, this);
thing->setParentId(parentId); thing->setParentId(parentId);
if (name.isEmpty()) { if (name.isEmpty()) {
@ -1211,14 +1208,14 @@ ThingActionInfo *ThingManagerImplementation::executeAction(const Action &action)
return info; return info;
} }
ParamList finalParams = buildParams(actionType.paramTypes(), action.params()); Thing::ThingError paramCheck = ThingUtils::verifyParams(actionType.paramTypes(), action.params());
Thing::ThingError paramCheck = ThingUtils::verifyParams(actionType.paramTypes(), finalParams);
if (paramCheck != Thing::ThingErrorNoError) { if (paramCheck != Thing::ThingErrorNoError) {
qCWarning(dcThingManager()) << "Cannot execute action. Parameter verification failed."; qCWarning(dcThingManager()) << "Cannot execute action. Parameter verification failed.";
ThingActionInfo *info = new ThingActionInfo(thing, action, this); ThingActionInfo *info = new ThingActionInfo(thing, action, this);
info->finish(paramCheck); info->finish(paramCheck);
return info; return info;
} }
ParamList finalParams = buildParams(actionType.paramTypes(), action.params());
finalAction.setParams(finalParams); finalAction.setParams(finalParams);
ThingActionInfo *info = new ThingActionInfo(thing, finalAction, this, 30000); ThingActionInfo *info = new ThingActionInfo(thing, finalAction, this, 30000);
@ -1375,42 +1372,25 @@ void ThingManagerImplementation::loadPlugin(IntegrationPlugin *pluginIface, cons
NymeaSettings settings(NymeaSettings::SettingsRolePlugins); NymeaSettings settings(NymeaSettings::SettingsRolePlugins);
settings.beginGroup("PluginConfig"); settings.beginGroup("PluginConfig");
ParamList params; ParamList params;
if (settings.childGroups().contains(pluginIface->pluginId().toString())) {
settings.beginGroup(pluginIface->pluginId().toString());
if (!settings.childGroups().isEmpty()) { settings.beginGroup(pluginIface->pluginId().toString());
// Note: since nymea 0.12.2 the param type gets saved too for better data converting foreach (const ParamType &paramType, pluginIface->configurationDescription()) {
foreach (const QString &paramTypeIdString, settings.childGroups()) { QVariant value = paramType.defaultValue();
ParamTypeId paramTypeId(paramTypeIdString); if (settings.contains(paramType.id().toString())) {
ParamType paramType = pluginIface->configurationDescription().findById(paramTypeId); value = settings.value(paramType.id().toString());
if (!paramType.isValid()) { } else if (settings.childGroups().contains(paramType.id().toString())) {
qCWarning(dcThingManager()) << "Not loading Param for plugin" << pluginIface->pluginName() << "because the ParamType for the saved Param" << ParamTypeId(paramTypeIdString).toString() << "could not be found."; // 0.12.2 - 0.22 used to store it in subgroups
continue; settings.beginGroup(paramType.id().toString());
} value = settings.value("value");
settings.endGroup();
QVariant paramValue;
settings.beginGroup(paramTypeIdString);
paramValue = settings.value("value", paramType.defaultValue());
paramValue.convert(settings.value("type").toInt());
params.append(Param(paramTypeId, paramValue));
settings.endGroup();
}
} else {
// Note: < nymea 0.12.2
foreach (const QString &paramTypeIdString, settings.allKeys()) {
params.append(Param(ParamTypeId(paramTypeIdString), settings.value(paramTypeIdString)));
}
}
settings.endGroup();
} else if (!pluginIface->configurationDescription().isEmpty()){
// plugin requires config but none stored. Init with defaults
foreach (const ParamType &paramType, pluginIface->configurationDescription()) {
Param param(paramType.id(), paramType.defaultValue());
params.append(param);
} }
value.convert(paramType.type());
Param param(paramType.id(), value);
params.append(param);
} }
settings.endGroup(); settings.endGroup(); // pluginId
settings.endGroup(); // PluginConfig
if (params.count() > 0) { if (params.count() > 0) {
Thing::ThingError status = pluginIface->setConfiguration(params); Thing::ThingError status = pluginIface->setConfiguration(params);
@ -1491,69 +1471,70 @@ void ThingManagerImplementation::loadConfiguredThings()
ParamList params; ParamList params;
settings.beginGroup("Params"); settings.beginGroup("Params");
if (!settings.childGroups().isEmpty()) {
foreach (const QString &paramTypeIdString, settings.childGroups()) {
ParamTypeId paramTypeId(paramTypeIdString);
ParamType paramType = thingClass.paramTypes().findById(paramTypeId);
QVariant defaultValue;
if (!paramType.isValid()) {
// NOTE: We're not skipping unknown parameters to give plugins a chance to still access old values if they change their config and migrate things over.
qCWarning(dcThingManager()) << "Unknown param" << paramTypeIdString << "for" << thing << ". ParamType could not be found in device class.";
}
// Note: since nymea 0.12.2
QVariant paramValue;
settings.beginGroup(paramTypeIdString);
paramValue = settings.value("value", paramType.defaultValue());
paramValue.convert(settings.value("type").toInt());
params.append(Param(paramTypeId, paramValue));
settings.endGroup(); // ParamId
}
} else {
foreach (const QString &paramTypeIdString, settings.allKeys()) {
params.append(Param(ParamTypeId(paramTypeIdString), settings.value(paramTypeIdString)));
}
}
// Make sure all params are around. if they aren't initialize with default values
foreach (const ParamType &paramType, thingClass.paramTypes()) { foreach (const ParamType &paramType, thingClass.paramTypes()) {
if (!params.hasParam(paramType.id())) { QVariant value = paramType.defaultValue();
params.append(Param(paramType.id(), paramType.defaultValue())); if (settings.contains(paramType.id().toString())) {
value = settings.value(paramType.id().toString());
} else if (settings.childGroups().contains(paramType.id().toString())) {
// 0.12.2 - 0.22 used to store in subgroups
settings.beginGroup(paramType.id().toString());
value = settings.value("value");
settings.endGroup(); // paramTypeId
}
value.convert(paramType.type());
Param param(paramType.id(), value);
params.append(param);
}
// In order to give plugins a chance to migrate stuff stored in the params (to e.g. pluginStorage()) we'll load
// params that might have disappeared from the ParamTypes but still have stuff stored in the config
foreach (const QString paramTypeIdString, settings.childKeys()) {
ParamTypeId paramTypeId(paramTypeIdString);
if (!params.hasParam(paramTypeId)) {
qCDebug(dcThingManager()) << "Loading legacy param" << paramTypeIdString << "for thing" << thing->name();
Param param(paramTypeId, settings.value(paramTypeIdString));
params.append(param);
} }
} }
thing->setParams(params); // 0.12.2 - 0.22 used to store in subgroups
foreach (const QString &paramTypeIdString, settings.childGroups()) {
settings.beginGroup(paramTypeIdString);
ParamTypeId paramTypeId(paramTypeIdString);
if (!params.hasParam(paramTypeId)) {
qCDebug(dcThingManager()) << "Loading legacy param" << paramTypeIdString << "for thing" << thing->name();
Param param(paramTypeId, settings.value("value"));
params.append(param);
}
settings.endGroup(); // paramTypeId
}
settings.endGroup(); // Params settings.endGroup(); // Params
thing->setParams(params);
ParamList thingSettings; ParamList thingSettings;
settings.beginGroup("Settings"); settings.beginGroup("Settings");
if (!settings.childGroups().isEmpty()) {
foreach (const QString &paramTypeIdString, settings.childGroups()) {
ParamTypeId paramTypeId(paramTypeIdString);
ParamType paramType = thingClass.settingsTypes().findById(paramTypeId);
if (!paramType.isValid()) {
qCWarning(dcThingManager()) << "Not loading Setting for thing" << thing << "because the ParamType for the saved Setting" << ParamTypeId(paramTypeIdString).toString() << "could not be found.";
continue;
}
// Note: since nymea 0.12.2 foreach (const ParamType &paramType, thingClass.settingsTypes()) {
QVariant paramValue; QVariant value = paramType.defaultValue();
settings.beginGroup(paramTypeIdString); if (settings.contains(paramType.id().toString())) {
paramValue = settings.value("value", paramType.defaultValue()); value = settings.value(paramType.id().toString());
paramValue.convert(settings.value("type").toInt()); } else if (settings.childGroups().contains(paramType.id().toString())) {
thingSettings.append(Param(paramTypeId, paramValue)); // 0.12.2 - 0.22 used to store in subgroups
settings.endGroup(); // ParamId settings.beginGroup(paramType.id().toString());
} value = settings.value("value");
} else { settings.endGroup(); // paramTypeId
foreach (const QString &paramTypeIdString, settings.allKeys()) {
params.append(Param(ParamTypeId(paramTypeIdString), settings.value(paramTypeIdString)));
} }
value.convert(paramType.type());
Param param(paramType.id(), value);
thingSettings.append(param);
} }
// Fill in any missing params with defaults settings.endGroup(); // Settings
thingSettings = buildParams(thingClass.settingsTypes(), thingSettings);
thing->setSettings(thingSettings); thing->setSettings(thingSettings);
settings.endGroup(); // Settings
settings.endGroup(); // ThingId settings.endGroup(); // ThingId
// We always add the thing to the list in this case. If it's in the stored things // We always add the thing to the list in this case. If it's in the stored things
@ -1626,19 +1607,13 @@ void ThingManagerImplementation::storeConfiguredThings()
settings.beginGroup("Params"); settings.beginGroup("Params");
foreach (const Param &param, thing->params()) { foreach (const Param &param, thing->params()) {
settings.beginGroup(param.paramTypeId().toString()); settings.setValue(param.paramTypeId().toString(), param.value());
settings.setValue("type", static_cast<int>(param.value().type()));
settings.setValue("value", param.value());
settings.endGroup(); // ParamTypeId
} }
settings.endGroup(); // Params settings.endGroup(); // Params
settings.beginGroup("Settings"); settings.beginGroup("Settings");
foreach (const Param &param, thing->settings()) { foreach (const Param &param, thing->settings()) {
settings.beginGroup(param.paramTypeId().toString()); settings.setValue(param.paramTypeId().toString(), param.value());
settings.setValue("type", static_cast<int>(param.value().type()));
settings.setValue("value", param.value());
settings.endGroup(); // ParamTypeId
} }
settings.endGroup(); // Settings settings.endGroup(); // Settings
@ -1726,19 +1701,19 @@ void ThingManagerImplementation::onAutoThingDisappeared(const ThingId &thingId)
Thing *thing = m_configuredThings.value(thingId); Thing *thing = m_configuredThings.value(thingId);
if (!thing) { if (!thing) {
qWarning(dcThingManager) << "Received an autoThingDisappeared signal but this thing is unknown:" << thingId; qCWarning(dcThingManager) << "Received an autoThingDisappeared signal but this thing is unknown:" << thingId;
return; return;
} }
ThingClass thingClass = m_supportedThings.value(thing->thingClassId()); ThingClass thingClass = m_supportedThings.value(thing->thingClassId());
if (thingClass.pluginId() != plugin->pluginId()) { if (thingClass.pluginId() != plugin->pluginId()) {
qWarning(dcThingManager) << "Received a autoThingDisappeared signal but emitting plugin does not own the thing"; qCWarning(dcThingManager) << "Received a autoThingDisappeared signal but emitting plugin does not own the thing";
return; return;
} }
if (!thing->autoCreated()) { if (!thing->autoCreated()) {
qWarning(dcThingManager) << "Received an autoThingDisappeared signal but thing creationMethod is not CreateMothodAuto"; qCWarning(dcThingManager) << "Received an autoThingDisappeared signal but thing creationMethod is not CreateMothodAuto";
return; return;
} }
@ -1940,17 +1915,25 @@ void ThingManagerImplementation::slotThingNameChanged()
emit thingChanged(thing); emit thingChanged(thing);
} }
// Merges params from first and second. First has higher priority than second. If neither are given, the default is used - if any
ParamList ThingManagerImplementation::buildParams(const ParamTypes &types, const ParamList &first, const ParamList &second) ParamList ThingManagerImplementation::buildParams(const ParamTypes &types, const ParamList &first, const ParamList &second)
{ {
// Merge params from discovered descriptor and additional overrides provided on API call. User provided params have higher priority than discovery params.
ParamList finalParams; ParamList finalParams;
foreach (const ParamType &paramType, types) { foreach (const ParamType &paramType, types) {
QVariant value;
if (first.hasParam(paramType.id())) { if (first.hasParam(paramType.id())) {
finalParams.append(Param(paramType.id(), first.paramValue(paramType.id()))); value = first.paramValue(paramType.id());
} else if (second.hasParam(paramType.id())) { } else if (second.hasParam(paramType.id())) {
finalParams.append(Param(paramType.id(), second.paramValue(paramType.id()))); value = second.paramValue(paramType.id());
} else if (paramType.defaultValue().isValid()){ } else if (paramType.defaultValue().isValid()){
finalParams.append(Param(paramType.id(), paramType.defaultValue())); value = paramType.defaultValue();
}
if (!value.isNull()) {
bool success = value.convert(paramType.type());
if (!success) {
qCWarning(dcThingManager()) << "Type mismatch in param" << paramType.name() << "for value" << value;
}
finalParams.append(Param(paramType.id(), value));
} }
} }
return finalParams; return finalParams;
@ -2004,13 +1987,6 @@ ThingSetupInfo* ThingManagerImplementation::setupThing(Thing *thing)
ThingClass thingClass = findThingClass(thing->thingClassId()); ThingClass thingClass = findThingClass(thing->thingClassId());
IntegrationPlugin *plugin = m_integrationPlugins.value(thingClass.pluginId()); IntegrationPlugin *plugin = m_integrationPlugins.value(thingClass.pluginId());
if (!plugin) {
qCWarning(dcThingManager) << "Can't find a plugin for this thing" << thing;
ThingSetupInfo *info = new ThingSetupInfo(thing, this);
info->finish(Thing::ThingErrorPluginNotFound, tr("The plugin for this thing is not loaded."));
return info;
}
QList<State> states; QList<State> states;
foreach (const StateType &stateType, thingClass.stateTypes()) { foreach (const StateType &stateType, thingClass.stateTypes()) {
State state(stateType.id(), thing->id()); State state(stateType.id(), thing->id());
@ -2023,8 +1999,14 @@ ThingSetupInfo* ThingManagerImplementation::setupThing(Thing *thing)
connect(thing, &Thing::settingChanged, this, &ThingManagerImplementation::slotThingSettingChanged); connect(thing, &Thing::settingChanged, this, &ThingManagerImplementation::slotThingSettingChanged);
connect(thing, &Thing::nameChanged, this, &ThingManagerImplementation::slotThingNameChanged); connect(thing, &Thing::nameChanged, this, &ThingManagerImplementation::slotThingNameChanged);
ThingSetupInfo *info = new ThingSetupInfo(thing, this, 30000); ThingSetupInfo *info = new ThingSetupInfo(thing, this, 30000);
if (!plugin) {
qCWarning(dcThingManager) << "Can't find a plugin for this thing" << thing;
info->finish(Thing::ThingErrorPluginNotFound, tr("The plugin for this thing is not loaded."));
return info;
}
plugin->setupThing(info); plugin->setupThing(info);
return info; return info;
@ -2045,16 +2027,17 @@ void ThingManagerImplementation::loadThingStates(Thing *thing)
ThingClass thingClass = m_supportedThings.value(thing->thingClassId()); ThingClass thingClass = m_supportedThings.value(thing->thingClassId());
foreach (const StateType &stateType, thingClass.stateTypes()) { foreach (const StateType &stateType, thingClass.stateTypes()) {
if (stateType.cached()) { if (stateType.cached()) {
QVariant value; QVariant value(stateType.defaultValue());
// First try to load new style
if (settings.childGroups().contains(stateType.id().toString())) { if (settings.contains(stateType.id().toString())) {
value = settings.value(stateType.id().toString());
} else if (settings.childGroups().contains(stateType.id().toString())) {
// 0.9 - 0.22 used to store in a subgroup
settings.beginGroup(stateType.id().toString()); settings.beginGroup(stateType.id().toString());
value = settings.value("value", stateType.defaultValue()); value = settings.value("value");
value.convert(settings.value("type").toInt());
settings.endGroup(); settings.endGroup();
} else { // Try to fall back to the pre 0.9.0 way of storing states
value = settings.value(stateType.id().toString(), stateType.defaultValue());
} }
value.convert(stateType.type());
thing->setStateValue(stateType.id(), value); thing->setStateValue(stateType.id(), value);
} else { } else {
thing->setStateValue(stateType.id(), stateType.defaultValue()); thing->setStateValue(stateType.id(), stateType.defaultValue());
@ -2125,12 +2108,7 @@ void ThingManagerImplementation::storeThingStates(Thing *thing)
settings.beginGroup(thing->id().toString()); settings.beginGroup(thing->id().toString());
ThingClass thingClass = m_supportedThings.value(thing->thingClassId()); ThingClass thingClass = m_supportedThings.value(thing->thingClassId());
foreach (const StateType &stateType, thingClass.stateTypes()) { foreach (const StateType &stateType, thingClass.stateTypes()) {
if (stateType.cached()) { settings.setValue(stateType.id().toString(), thing->stateValue(stateType.id()));
settings.beginGroup(stateType.id().toString());
settings.setValue("type", static_cast<int>(thing->stateValue(stateType.id()).type()));
settings.setValue("value", thing->stateValue(stateType.id()));
settings.endGroup();
}
} }
settings.endGroup(); settings.endGroup();
} }

View File

@ -359,17 +359,28 @@ void PluginMetadata::parse(const QJsonObject &jsonObject)
stateType.setUnit(unitVerification.second); stateType.setUnit(unitVerification.second);
} }
stateType.setDefaultValue(st.value("defaultValue").toVariant()); QVariant defaultValue = st.value("defaultValue").toVariant();
if (st.contains("minValue")) defaultValue.convert(stateType.type());
stateType.setMinValue(st.value("minValue").toVariant()); stateType.setDefaultValue(defaultValue);
if (st.contains("maxValue")) if (st.contains("minValue")) {
stateType.setMaxValue(st.value("maxValue").toVariant()); QVariant minValue = st.value("minValue").toVariant();
minValue.convert(stateType.type());
stateType.setMinValue(minValue);
}
if (st.contains("maxValue")) {
QVariant maxValue = st.value("maxValue").toVariant();
maxValue.convert(stateType.type());
stateType.setMaxValue(maxValue);
}
if (st.contains("possibleValues")) { if (st.contains("possibleValues")) {
QVariantList possibleValues; QVariantList possibleValues;
foreach (const QJsonValue &possibleValueJson, st.value("possibleValues").toArray()) { foreach (const QJsonValue &possibleValueJson, st.value("possibleValues").toArray()) {
possibleValues.append(possibleValueJson.toVariant()); QVariant possibleValue = possibleValueJson.toVariant();
possibleValue.convert(stateType.type());
possibleValues.append(possibleValue);
} }
stateType.setPossibleValues(possibleValues); stateType.setPossibleValues(possibleValues);
@ -824,7 +835,13 @@ QPair<bool, ParamTypes> PluginMetadata::parseParamTypes(const QJsonArray &array)
m_validationErrors.append("Param type \"" + paramName + "\" has duplicate UUID: " + paramTypeId.toString()); m_validationErrors.append("Param type \"" + paramName + "\" has duplicate UUID: " + paramTypeId.toString());
hasErrors = true; hasErrors = true;
} }
ParamType paramType(paramTypeId, paramName, t, pt.value("defaultValue").toVariant()); QVariant defaultValue = pt.value("defaultValue").toVariant();
if (!defaultValue.isNull()) {
// Only convert if there actually is a value as we want it to be null if it isn't specced
// explicitly and convert() would initialize it to the variant's default value
defaultValue.convert(t);
}
ParamType paramType(paramTypeId, paramName, t, defaultValue);
paramType.setDisplayName(pt.value("displayName").toString()); paramType.setDisplayName(pt.value("displayName").toString());
@ -861,7 +878,19 @@ QPair<bool, ParamTypes> PluginMetadata::parseParamTypes(const QJsonArray &array)
paramType.setReadOnly(pt.value("readOnly").toBool()); paramType.setReadOnly(pt.value("readOnly").toBool());
paramType.setAllowedValues(allowedValues); paramType.setAllowedValues(allowedValues);
paramType.setLimits(pt.value("minValue").toVariant(), pt.value("maxValue").toVariant()); QVariant minValue = pt.value("minValue").toVariant();
if (!minValue.isNull()) {
// Only convert if there actually is a value as we want it to be null if it isn't specced
// explicitly and convert() would initialize it to the variant's default value
minValue.convert(t);
}
QVariant maxValue = pt.value("maxValue").toVariant();
if (!maxValue.isNull()) {
// Only convert if there actually is a value as we want it to be null if it isn't specced
// explicitly and convert() would initialize it to the variant's default value
maxValue.convert(t);
}
paramType.setLimits(minValue, maxValue);
paramType.setIndex(index++); paramType.setIndex(index++);
paramTypes.append(paramType); paramTypes.append(paramType);
} }

View File

@ -327,23 +327,49 @@ QVariant Thing::stateValue(const StateTypeId &stateTypeId) const
/*! For convenience, this finds the \l{State} matching the given \a stateTypeId in this thing and sets the current value to \a value. */ /*! For convenience, this finds the \l{State} matching the given \a stateTypeId in this thing and sets the current value to \a value. */
void Thing::setStateValue(const StateTypeId &stateTypeId, const QVariant &value) void Thing::setStateValue(const StateTypeId &stateTypeId, const QVariant &value)
{ {
StateType stateType = m_thingClass.stateTypes().findById(stateTypeId);
if (!stateType.isValid()) {
qCWarning(dcThing()) << "No such state type" << stateTypeId.toString() << "in" << m_name << "(" + thingClass().name() + ")";
return;
}
for (int i = 0; i < m_states.count(); ++i) { for (int i = 0; i < m_states.count(); ++i) {
if (m_states.at(i).stateTypeId() == stateTypeId) { if (m_states.at(i).stateTypeId() == stateTypeId) {
if (m_states.at(i).value() == value) if (m_states.at(i).value() == value)
return; return;
QVariant newValue = value;
if (!newValue.convert(stateType.type())) {
qCWarning(dcThing()).nospace() << m_name << ": Invalid value " << value << " for state " << stateType.name() << ". Type mismatch. Expected type: " << QVariant::typeToName(stateType.type()) << " (Discarding change)";
return;
}
if (stateType.minValue().isValid() && value < stateType.minValue()) {
qCWarning(dcThing()).nospace() << m_name << ": Invalid value " << value << " for state " << stateType.name() << ". Out of range: " << stateType.minValue() << " - " << stateType.maxValue() << " (Correcting to closest value within range)";
newValue = stateType.minValue();
}
if (stateType.maxValue().isValid() && value > stateType.maxValue()) {
qCWarning(dcThing()).nospace() << m_name << ": Invalid value " << value << " for state " << stateType.name() << ". Out of range: " << stateType.minValue() << " - " << stateType.maxValue() << " (Correcting to closest value within range)";
newValue = stateType.maxValue();
}
if (!stateType.possibleValues().isEmpty() && !stateType.possibleValues().contains(value)) {
qCWarning(dcThing()).nospace() << m_name << ": Invalid value " << value << " for state " << stateType.name() << ". Not an accepted value. Possible values: " << stateType.possibleValues() << " (Discarding change)";
return;
}
// TODO: check min/max value + possible values QVariant oldValue = m_states.at(i).value();
// to prevent an invalid state type from the plugin side
State newState(stateTypeId, m_id); if (oldValue == newValue) {
newState.setValue(value); qCDebug(dcThing()).nospace() << m_name << ": Discarding state change for " << stateType.name() << " as the value did not actually change. Old value:" << oldValue << "New value:" << newValue;
m_states[i] = newState; return;
}
qCDebug(dcThing()).nospace() << m_name << ": State " << stateType.name() << " changed from " << oldValue << " to " << newValue;
m_states[i].setValue(newValue);
emit stateValueChanged(stateTypeId, value); emit stateValueChanged(stateTypeId, value);
return; return;
} }
} }
qCWarning(dcThingManager) << "Failed setting state for" << m_name << value; Q_ASSERT_X(false, m_name.toUtf8(), QString("Failed setting state %1 to %2").arg(stateType.name()).arg(value.toString()).toUtf8());
qCWarning(dcThing).nospace() << m_name << ": Failed setting state " << stateType.name() << "to" << value;
} }
/*! Returns the \l{State} with the given \a stateTypeId of this thing. */ /*! Returns the \l{State} with the given \a stateTypeId of this thing. */

View File

@ -41,8 +41,7 @@ ThingUtils::ThingUtils()
} }
/*! Verify if the given \a params matches the given \a paramTypes. Ith \a requireAll /*! Verify if the given \a params matches the given \a paramTypes.*/
* is true, all \l{ParamList}{Params} has to be valid. Returns \l{Device::DeviceError} to inform about the result.*/
Thing::ThingError ThingUtils::verifyParams(const QList<ParamType> paramTypes, const ParamList &params) Thing::ThingError ThingUtils::verifyParams(const QList<ParamType> paramTypes, const ParamList &params)
{ {
foreach (const Param &param, params) { foreach (const Param &param, params) {
@ -52,7 +51,7 @@ Thing::ThingError ThingUtils::verifyParams(const QList<ParamType> paramTypes, co
} }
} }
foreach (const ParamType &paramType, paramTypes) { foreach (const ParamType &paramType, paramTypes) {
bool found = false; bool found = !paramType.defaultValue().isNull();
foreach (const Param &param, params) { foreach (const Param &param, params) {
if (paramType.id() == param.paramTypeId()) { if (paramType.id() == param.paramTypeId()) {
found = true; found = true;

View File

@ -171,11 +171,11 @@ bool StateDescriptor::operator ==(const State &state) const
if ((m_stateTypeId != state.stateTypeId()) || (m_thingId != state.thingId())) { if ((m_stateTypeId != state.stateTypeId()) || (m_thingId != state.thingId())) {
return false; return false;
} }
if (!m_stateValue.canConvert(state.value().type())) { QVariant convertedValue = m_stateValue;
bool res = convertedValue.convert(state.value().type());
if (!res) {
return false; return false;
} }
QVariant convertedValue = m_stateValue;
convertedValue.convert(state.value().type());
switch (m_operatorType) { switch (m_operatorType) {
case Types::ValueOperatorEquals: case Types::ValueOperatorEquals:
return convertedValue == state.value(); return convertedValue == state.value();

View File

@ -222,6 +222,12 @@ QStringList StateType::mandatoryTypeProperties()
return QStringList() << "id" << "name" << "displayName" << "displayNameEvent" << "type" << "defaultValue"; return QStringList() << "id" << "name" << "displayName" << "displayNameEvent" << "type" << "defaultValue";
} }
/*! Returns true if this state type has an ID, a type and a name set. */
bool StateType::isValid() const
{
return !m_id.isNull() && m_type != QVariant::Invalid && !m_name.isEmpty();
}
StateTypes::StateTypes(const QList<StateType> &other) StateTypes::StateTypes(const QList<StateType> &other)
{ {
foreach (const StateType &st, other) { foreach (const StateType &st, other) {

View File

@ -96,6 +96,8 @@ public:
static QStringList typeProperties(); static QStringList typeProperties();
static QStringList mandatoryTypeProperties(); static QStringList mandatoryTypeProperties();
bool isValid() const;
private: private:
StateTypeId m_id; StateTypeId m_id;
QString m_name; QString m_name;

View File

@ -98,7 +98,7 @@
"name": "double", "name": "double",
"displayName": "Dummy double state", "displayName": "Dummy double state",
"displayNameEvent": "Dummy double state changed", "displayNameEvent": "Dummy double state changed",
"type": "int", "type": "double",
"minValue": 0, "minValue": 0,
"maxValue": 100, "maxValue": 100,
"defaultValue": 2.7 "defaultValue": 2.7

View File

@ -85,7 +85,6 @@ void TestActions::executeAction()
params.insert("deviceId", deviceId); params.insert("deviceId", deviceId);
params.insert("params", actionParams); params.insert("params", actionParams);
QVariant response = injectAndWait("Actions.ExecuteAction", params); QVariant response = injectAndWait("Actions.ExecuteAction", params);
qDebug() << "executeActionresponse" << response;
verifyError(response, "deviceError", enumValueName(error)); verifyError(response, "deviceError", enumValueName(error));
// Fetch action execution history from mock device // Fetch action execution history from mock device

View File

@ -382,10 +382,10 @@ void TestDevices::addConfiguredDevice_data()
QTest::newRow("User, JustAdd, wrong param") << mockThingClassId << invalidDeviceParams << true << Device::DeviceErrorInvalidParameter; QTest::newRow("User, JustAdd, wrong param") << mockThingClassId << invalidDeviceParams << true << Device::DeviceErrorInvalidParameter;
deviceParams.clear(); deviceParams << httpportParam << fakeparam; deviceParams.clear(); deviceParams << httpportParam << fakeparam;
QTest::newRow("USer, JustAdd, additional invalid param") << mockThingClassId << deviceParams << false << Device::DeviceErrorNoError; QTest::newRow("User, JustAdd, additional invalid param") << mockThingClassId << deviceParams << false << Device::DeviceErrorInvalidParameter;
deviceParams.clear(); deviceParams << httpportParam << fakeparam2; deviceParams.clear(); deviceParams << httpportParam << fakeparam2;
QTest::newRow("USer, JustAdd, additional param, valid but unused") << mockThingClassId << deviceParams << true << Device::DeviceErrorNoError; QTest::newRow("User, JustAdd, duplicate param") << mockThingClassId << deviceParams << true << Device::DeviceErrorInvalidParameter;
} }

View File

@ -382,10 +382,10 @@ void TestIntegrations::addThing_data()
QTest::newRow("User, JustAdd, wrong param") << mockThingClassId << invalidThingParams << true << Thing::ThingErrorInvalidParameter; QTest::newRow("User, JustAdd, wrong param") << mockThingClassId << invalidThingParams << true << Thing::ThingErrorInvalidParameter;
thingParams.clear(); thingParams << httpportParam << fakeparam; thingParams.clear(); thingParams << httpportParam << fakeparam;
QTest::newRow("USer, JustAdd, additional invalid param") << mockThingClassId << thingParams << false << Thing::ThingErrorNoError; QTest::newRow("USer, JustAdd, additional invalid param") << mockThingClassId << thingParams << false << Thing::ThingErrorInvalidParameter;
thingParams.clear(); thingParams << httpportParam << fakeparam2; thingParams.clear(); thingParams << httpportParam << fakeparam2;
QTest::newRow("USer, JustAdd, additional param, valid but unused") << mockThingClassId << thingParams << true << Thing::ThingErrorNoError; QTest::newRow("USer, JustAdd, duplicate param") << mockThingClassId << thingParams << true << Thing::ThingErrorInvalidParameter;
} }

View File

@ -265,13 +265,14 @@ void TestRules::setWritableStateValue(const ThingId &thingId, const StateTypeId
QVariantList stateChangedVariants = checkNotifications(stateSpy, "Integrations.StateChanged"); QVariantList stateChangedVariants = checkNotifications(stateSpy, "Integrations.StateChanged");
QVERIFY2(stateChangedVariants.count() == 1, "Did not get Integrations.StateChanged notification."); QVERIFY2(stateChangedVariants.count() == 1, "Did not get Integrations.StateChanged notification.");
qCDebug(dcTests()) << "Notification content:" << qUtf8Printable(QJsonDocument::fromVariant(stateChangedVariants).toJson());
QVariantMap notification = stateChangedVariants.first().toMap().value("params").toMap(); QVariantMap notification = stateChangedVariants.first().toMap().value("params").toMap();
QVERIFY2(notification.contains("thingId"), "Integrations.StateChanged notification does not contain thingId"); QVERIFY2(notification.contains("thingId"), "Integrations.StateChanged notification does not contain thingId");
QVERIFY2(ThingId(notification.value("thingId").toString()) == thingId, "Integrations.StateChanged notification does not contain the correct thingId"); QVERIFY2(ThingId(notification.value("thingId").toString()) == thingId, "Integrations.StateChanged notification does not contain the correct thingId");
QVERIFY2(notification.contains("stateTypeId"), "Integrations.StateChanged notification does not contain stateTypeId"); QVERIFY2(notification.contains("stateTypeId"), "Integrations.StateChanged notification does not contain stateTypeId");
QVERIFY2(StateTypeId(notification.value("stateTypeId").toString()) == stateTypeId, "Integrations.StateChanged notification does not contain the correct stateTypeId"); QVERIFY2(StateTypeId(notification.value("stateTypeId").toString()) == stateTypeId, "Integrations.StateChanged notification does not contain the correct stateTypeId");
QVERIFY2(notification.contains("value"), "Integrations.StateChanged notification does not contain new state value"); QVERIFY2(notification.contains("value"), "Integrations.StateChanged notification does not contain new state value");
QVERIFY2(notification.value("value") == QVariant(value), "Integrations.StateChanged notification does not contain the new value"); QVERIFY2(notification.value("value") == QVariant(value), QString("Integrations.StateChanged notification does not contain the new value. Got: %1, Expected: %2").arg(notification.value("value").toString()).arg(QVariant(value).toString()).toUtf8());
} }
} }
@ -1990,11 +1991,11 @@ void TestRules::testChildEvaluator_data()
QTest::addColumn<bool>("trigger"); QTest::addColumn<bool>("trigger");
QTest::addColumn<bool>("active"); QTest::addColumn<bool>("active");
QTest::newRow("Unchanged | 2 | 2.5 | String value 1 | #FF0000") << testThingId << ruleMap << 2 << 2.5 << "String value 1" << "#FF0000" << false << false; QTest::newRow("Unchanged | 2 | 2.5 | String value 1 | #FF0000") << testThingId << ruleMap << 2 << 2.5 << "String value 1" << "#ff0000" << false << false;
QTest::newRow("Unchanged | 60 | 2.5 | String value 2 | #FF0000") << testThingId << ruleMap << 60 << 2.5 << "String value 2" << "#FF0000" << false << false; QTest::newRow("Unchanged | 60 | 2.5 | String value 2 | #FF0000") << testThingId << ruleMap << 60 << 2.5 << "String value 2" << "#ff0000" << false << false;
QTest::newRow("Unchanged | 60 | 20.5 | String value 2 | #FF0000") << testThingId << ruleMap << 60 << 20.5 << "String value 2" << "#FF0000" << false << false; QTest::newRow("Unchanged | 60 | 20.5 | String value 2 | #FF0000") << testThingId << ruleMap << 60 << 20.5 << "String value 2" << "#ff0000" << false << false;
QTest::newRow("Active | 60 | 20.5 | String value 2 | #00FF00") << testThingId << ruleMap << 60 << 20.5 << "String value 2" << "#00FF00" << true << true; QTest::newRow("Active | 60 | 20.5 | String value 2 | #00FF00") << testThingId << ruleMap << 60 << 20.5 << "String value 2" << "#00ff00" << true << true;
QTest::newRow("Active | 60 | 20.5 | String value 2 | #00FF00") << testThingId << ruleMap << 60 << 20.5 << "String value 2" << "#00FF00" << true << true; QTest::newRow("Active | 60 | 20.5 | String value 2 | #00FF00") << testThingId << ruleMap << 60 << 20.5 << "String value 2" << "#00ff00" << true << true;
} }
void TestRules::testChildEvaluator() void TestRules::testChildEvaluator()

View File

@ -118,7 +118,7 @@ protected:
// just for debugging // just for debugging
inline void printJson(const QVariant &response) { inline void printJson(const QVariant &response) {
QJsonDocument jsonDoc = QJsonDocument::fromVariant(response); QJsonDocument jsonDoc = QJsonDocument::fromVariant(response);
qDebug() << jsonDoc.toJson(); qCDebug(dcTests()) << jsonDoc.toJson();
} }
void waitForDBSync(); void waitForDBSync();