update plugins and add state desciptor validation

This commit is contained in:
Simon Stürz 2015-12-01 10:08:33 +01:00 committed by Michael Zanetti
parent 057c1cf065
commit 02a6e2cb96
19 changed files with 194 additions and 98 deletions

View File

@ -2,7 +2,7 @@
GUH_VERSION_STRING=$$system('dpkg-parsechangelog | sed -n -e "s/^Version: //p"')
# define protocol versions
JSON_PROTOCOL_VERSION=33
JSON_PROTOCOL_VERSION=34
REST_API_VERSION=1
DEFINES += GUH_VERSION_STRING=\\\"$${GUH_VERSION_STRING}\\\" \

View File

@ -37,7 +37,6 @@
The \a value parameter describes the new value of the State.
*/
#include "device.h"
#include "types/event.h"
#include "loggingcategories.h"
@ -187,9 +186,13 @@ void Device::setStateValue(const StateTypeId &stateTypeId, const QVariant &value
{
for (int i = 0; i < m_states.count(); ++i) {
if (m_states.at(i).stateTypeId() == stateTypeId) {
if (m_states.at(i).value() == value) {
if (m_states.at(i).value() == value)
return;
}
// TODO: check min/max value + possible values
// to prevent an invalid state type
State newState(stateTypeId, m_id);
newState.setValue(value);
m_states[i] = newState;

View File

@ -251,25 +251,30 @@ QList<DeviceClass> DevicePlugin::supportedDevices() const
stateType.setType(t);
stateType.setUnit(unitStringToUnit(st.value("unit").toString()));
stateType.setDefaultValue(st.value("defaultValue").toVariant());
if (st.contains("minValue"))
stateType.setMinValue(st.value("minValue").toVariant());
if (st.contains("maxValue"))
stateType.setMaxValue(st.value("maxValue").toVariant());
if (st.contains("possibleValues")) {
QVariantList possibleValues;
foreach (const QJsonValue &possibleValueJson, st.value("possibleValues").toArray()) {
possibleValues.append(possibleValueJson.toVariant());
}
stateType.setPossibleValues(possibleValues);
}
stateTypes.append(stateType);
// create ActionType if this StateType is writable
if (st.contains("writable")) {
ActionType actionType(st.value("id").toString());
actionType.setName("set " + st.value("name").toString());
if (st.contains("writable") && st.value("writable").toBool()) {
// Note: fields already checked in StateType
ParamType paramType(st.value("name").toString(), t, st.value("defaultValue").toVariant());
if (st.value("writable").toObject().contains("allowedValues")) {
QVariantList allowedValues;
foreach (const QJsonValue &allowedTypesJson, st.value("writable").toObject().value("allowedValues").toArray()) {
allowedValues.append(allowedTypesJson.toVariant());
}
paramType.setAllowedValues(allowedValues);
}
paramType.setInputType(inputTypeStringToInputType(st.value("writable").toObject().value("inputType").toString()));
paramType.setUnit(unitStringToUnit(st.value("unit").toString()));
paramType.setLimits(st.value("writable").toObject().value("minValue").toVariant(),
st.value("writable").toObject().value("maxValue").toVariant());
ActionType actionType(ActionTypeId(stateType.id().toString()));
actionType.setName("set " + stateType.name());
ParamType paramType(stateType.name(), t, stateType.defaultValue());
paramType.setAllowedValues(stateType.possibleValues());
paramType.setUnit(stateType.unit());
paramType.setLimits(stateType.minValue(), stateType.maxValue());
actionType.setParamTypes(QList<ParamType>() << paramType);
actionTypes.append(actionType);
}

View File

@ -212,7 +212,7 @@
"type": "double",
"unit": "DegreeCelsius",
"defaultValue": 0,
"writable": {}
"writable": true
},
{
@ -375,7 +375,7 @@
"unit": "DegreeCelsius",
"type": "double",
"defaultValue": 0,
"writable": {}
"writable": true
},
{
"id": "576da571-9a65-478f-96bf-19256c8b9ece",

View File

@ -27,7 +27,7 @@
"name": "state",
"type": "bool",
"defaultValue": false,
"writable": {}
"writable": true
}
]
},

View File

@ -41,7 +41,7 @@
"idName": "mute",
"name": "mute",
"type": "bool",
"writable": {}
"writable": true
},
{
"id": "9dfe5d78-4c3f-497c-bab1-bb9fdf7e93a9",
@ -49,10 +49,9 @@
"name": "volume",
"unit": "Percentage",
"type": "int",
"writable": {
"minValue": 0,
"maxValue": 100
}
"minValue": 0,
"maxValue": 100,
"writable": true
}
],
"eventTypes": [

View File

@ -234,7 +234,7 @@
"name": "color",
"type": "QColor",
"defaultValue": "#000000",
"writable": {}
"writable": true
},
{
"id": "72981c04-267a-4ba0-a59e-9921d2f3af9c",
@ -243,10 +243,9 @@
"type": "int",
"unit": "Percentage",
"defaultValue": 0,
"writable": {
"minValue": 0,
"maxValue": 100
}
"minValue": 0,
"maxValue": 100,
"writable": true
},
{
"id": "05f63f9c-f61e-4dcf-ad55-3f13fde2765b",
@ -254,14 +253,13 @@
"name": "allowed values",
"type": "QString",
"defaultValue": "String value 1",
"writable": {
"allowedValues": [
"String value 1",
"String value 2",
"String value 3",
"String value 4"
]
}
"possibleValues": [
"String value 1",
"String value 2",
"String value 3",
"String value 4"
],
"writable": true
}
],
"actionTypes": [

View File

@ -116,7 +116,7 @@
"name": "power",
"type": "bool",
"defaultValue": false,
"writable": {}
"writable": true
},
{
"id": "c0f4206f-f219-4f06-93c4-4ca515a56f79",
@ -125,10 +125,9 @@
"type": "int",
"unit": "Mired",
"defaultValue": 170,
"writable": {
"minValue": 153,
"maxValue": 500
}
"minValue": 153,
"maxValue": 500,
"writable": true
},
{
"id": "d25423e7-b924-4b20-80b6-77eecc65d089",
@ -136,7 +135,7 @@
"name": "color",
"type": "QColor",
"defaultValue": "#000000",
"writable": {}
"writable": true
},
{
@ -146,10 +145,10 @@
"type": "int",
"unit": "Percentage",
"defaultValue": 0,
"writable": {
"minValue": 0,
"maxValue": 100
}
"minValue": 0,
"maxValue": 100,
"writable": true
},
{
"id": "0b7cdd8d-4db8-4183-abe2-f3c01d1c9afc",
@ -157,13 +156,11 @@
"name": "effect",
"type": "QString",
"defaultValue": "none",
"writable": {
"allowedValues": [
"none",
"color loop"
]
}
"possibleValues": [
"none",
"color loop"
],
"writable": true
}
],
"actionTypes": [

View File

@ -55,7 +55,7 @@
"name": "active",
"type": "bool",
"defaultValue": false,
"writable": {}
"writable": true
},
{
"id": "cb8a89c2-dc12-4965-b047-57896058b421",
@ -64,10 +64,9 @@
"type": "int",
"unit": "Percentage",
"defaultValue": 0,
"writable": {
"minValue": 0,
"maxValue": 100
}
"minValue": 0,
"maxValue": 100,
"writable": true
}
]
},
@ -124,7 +123,7 @@
"name": "power",
"type": "bool",
"defaultValue": 0,
"writable": {}
"writable": true
},
{
"id": "677cd9ec-c264-47ee-9d2e-d3662237792c",
@ -133,10 +132,9 @@
"type": "int",
"unit": "Percentage",
"defaultValue": 0,
"writable": {
"minValue": 0,
"maxValue": 100
}
"minValue": 0,
"maxValue": 100,
"writable": true
}
]
}

View File

@ -41,7 +41,7 @@
"name": "power",
"type": "bool",
"defaultValue": false,
"writable": {}
"writable": true
},
{

View File

@ -104,14 +104,15 @@ def extractStateTypes(deviceClassMap):
print("duplicated variable name \"%s\" for StateTypeId %s -> skipping") % (variableName, stateType["id"])
# create ActionTypeId if the state is writable
if 'writable' in stateType:
vName = "%sActionTypeId" % (stateType["idName"])
if not vName in variableNames:
variableNames.append(vName)
print("define ActionTypeId %s for writable StateType %s = %s" % (vName, variableName, stateType["id"]))
writePluginInfo("ActionTypeId %s = ActionTypeId(\"%s\");" % (vName, stateType["id"]))
createExternDefinition("ActionTypeId", vName)
else:
print("duplicated variable name \"%s\" for ActionTypeId %s -> skipping") % (variableName, stateType["id"])
if stateType['writable']:
vName = "%sActionTypeId" % (stateType["idName"])
if not vName in variableNames:
variableNames.append(vName)
print("define ActionTypeId %s for writable StateType %s = %s" % (vName, variableName, stateType["id"]))
writePluginInfo("ActionTypeId %s = ActionTypeId(\"%s\");" % (vName, stateType["id"]))
createExternDefinition("ActionTypeId", vName)
else:
print("duplicated variable name \"%s\" for ActionTypeId %s -> skipping") % (variableName, stateType["id"])
except:
pass
except:

View File

@ -227,7 +227,6 @@ void JsonTypes::init()
s_ruleDescription.insert("active", basicTypeToString(Bool));
s_ruleDescription.insert("executable", basicTypeToString(Bool));
// LogEntry
s_logEntry.insert("timestamp", basicTypeToString(Int));
s_logEntry.insert("loggingLevel", loggingLevelRef());
@ -411,9 +410,17 @@ QVariantMap JsonTypes::packStateType(const StateType &stateType)
variantMap.insert("type", basicTypeToString(stateType.type()));
variantMap.insert("defaultValue", stateType.defaultValue());
if(stateType.unit() != Types::UnitNone) {
if (stateType.maxValue().isValid())
variantMap.insert("maxValue", stateType.maxValue());
if (stateType.minValue().isValid())
variantMap.insert("minValue", stateType.minValue());
if (!stateType.possibleValues().isEmpty())
variantMap.insert("possibleValues", stateType.possibleValues());
if(stateType.unit() != Types::UnitNone)
variantMap.insert("unit", s_unit.at(stateType.unit()));
}
return variantMap;
}
@ -851,9 +858,8 @@ ParamList JsonTypes::unpackParams(const QVariantList &paramList)
RuleActionParam JsonTypes::unpackRuleActionParam(const QVariantMap &ruleActionParamMap)
{
if (ruleActionParamMap.keys().count() == 0) {
if (ruleActionParamMap.keys().count() == 0)
return RuleActionParam();
}
QString name = ruleActionParamMap.value("name").toString();
QVariant value = ruleActionParamMap.value("value");

View File

@ -222,6 +222,11 @@ JsonReply* RulesHandler::AddRule(const QVariantMap &params)
// Check and unpack stateEvaluator
qCDebug(dcJsonRpc) << "unpacking stateEvaluator:" << params.value("stateEvaluator").toMap();
StateEvaluator stateEvaluator = JsonTypes::unpackStateEvaluator(params.value("stateEvaluator").toMap());
if (!stateEvaluator.isValid()) {
QVariantMap returns;
returns.insert("ruleError", JsonTypes::ruleErrorToString(RuleEngine::RuleErrorInvalidStateEvaluatorValue));
return createReply(returns);
}
// Check and unpack actions
QPair<QList<RuleAction>, RuleEngine::RuleError> actionsVerification = JsonTypes::verifyActions(params, eventDescriptorList);
@ -277,6 +282,11 @@ JsonReply *RulesHandler::EditRule(const QVariantMap &params)
// Check and unpack stateEvaluator
qCDebug(dcJsonRpc) << "unpacking stateEvaluator:" << params.value("stateEvaluator").toMap();
StateEvaluator stateEvaluator = JsonTypes::unpackStateEvaluator(params.value("stateEvaluator").toMap());
if (!stateEvaluator.isValid()) {
QVariantMap returns;
returns.insert("ruleError", JsonTypes::ruleErrorToString(RuleEngine::RuleErrorInvalidStateEvaluatorValue));
return createReply(returns);
}
// Check and unpack actions
QPair<QList<RuleAction>, RuleEngine::RuleError> actionsVerification = JsonTypes::verifyActions(params, eventDescriptorList);

View File

@ -259,6 +259,8 @@ HttpReply *RulesResource::addRule(const QByteArray &payload) const
// Check and unpack stateEvaluator
qCDebug(dcRest) << "unpacking stateEvaluator:" << params.value("stateEvaluator").toMap();
StateEvaluator stateEvaluator = JsonTypes::unpackStateEvaluator(params.value("stateEvaluator").toMap());
if (!stateEvaluator.isValid())
return createRuleErrorReply(HttpReply::BadRequest, RuleEngine::RuleErrorInvalidStateEvaluatorValue);
// Check and unpack actions
QPair<QList<RuleAction>, RuleEngine::RuleError> actionsVerification = JsonTypes::verifyActions(params, eventDescriptorList);
@ -362,6 +364,8 @@ HttpReply *RulesResource::editRule(const RuleId &ruleId, const QByteArray &paylo
// Check and unpack stateEvaluator
qCDebug(dcRest) << "unpacking stateEvaluator:" << params.value("stateEvaluator").toMap();
StateEvaluator stateEvaluator = JsonTypes::unpackStateEvaluator(params.value("stateEvaluator").toMap());
if (!stateEvaluator.isValid())
return createRuleErrorReply(HttpReply::BadRequest, RuleEngine::RuleErrorInvalidStateEvaluatorValue);
// Check and unpack actions
QPair<QList<RuleAction>, RuleEngine::RuleError> actionsVerification = JsonTypes::verifyActions(params, eventDescriptorList);

View File

@ -104,7 +104,7 @@ RuleEngine::RuleEngine(QObject *parent) :
QObject(parent)
{
GuhSettings settings(GuhSettings::SettingsRoleRules);
qCDebug(dcRuleEngine) << "laoding rules from" << settings.fileName();
qCDebug(dcRuleEngine) << "loading rules from" << settings.fileName();
foreach (const QString &idString, settings.childGroups()) {
settings.beginGroup(idString);
@ -217,9 +217,8 @@ QList<Rule> RuleEngine::evaluateEvent(const Event &event)
QList<Rule> rules;
foreach (const RuleId &id, m_ruleIds) {
Rule rule = m_rules.value(id);
if (!rule.enabled()) {
if (!rule.enabled())
continue;
}
if (rule.eventDescriptors().isEmpty()) {
// This rule seems to have only states, check on state changed
@ -269,9 +268,9 @@ RuleEngine::RuleError RuleEngine::addRule(const RuleId &ruleId, const QString &n
*/
RuleEngine::RuleError RuleEngine::addRule(const RuleId &ruleId, const QString &name, const QList<EventDescriptor> &eventDescriptorList, const StateEvaluator &stateEvaluator, const QList<RuleAction> &actions, const QList<RuleAction> &exitActions, bool enabled, bool executable, bool fromEdit)
{
if (ruleId.isNull()) {
if (ruleId.isNull())
return RuleErrorInvalidRuleId;
}
if (!findRule(ruleId).id().isNull()) {
qCWarning(dcRuleEngine) << "Already have a rule with this id!";
return RuleErrorInvalidRuleId;
@ -298,6 +297,7 @@ RuleEngine::RuleError RuleEngine::addRule(const RuleId &ruleId, const QString &n
}
foreach (const RuleAction &action, actions) {
Device *device = GuhCore::instance()->findConfiguredDevice(action.deviceId());
if (!device) {
@ -322,11 +322,10 @@ RuleEngine::RuleError RuleEngine::addRule(const RuleId &ruleId, const QString &n
}
}
}
}
}
if (actions.count() > 0) {
if (actions.count() > 0)
qCDebug(dcRuleEngine) << "actions" << actions.last().actionTypeId() << actions.last().ruleActionParams();
}
foreach (const RuleAction &action, exitActions) {
Device *device = GuhCore::instance()->findConfiguredDevice(action.deviceId());
@ -351,9 +350,9 @@ RuleEngine::RuleError RuleEngine::addRule(const RuleId &ruleId, const QString &n
}
}
}
if (exitActions.count() > 0) {
if (exitActions.count() > 0)
qCDebug(dcRuleEngine) << "exit actions" << exitActions.last().actionTypeId() << exitActions.last().ruleActionParams();
}
Rule rule = Rule(ruleId, name, eventDescriptorList, stateEvaluator, actions, exitActions);
rule.setEnabled(enabled);
@ -475,6 +474,7 @@ RuleEngine::RuleError RuleEngine::disableRule(const RuleId &ruleId)
qCWarning(dcRuleEngine) << "Rule not found. Can't disable it";
return RuleErrorRuleNotFound;
}
Rule rule = m_rules.value(ruleId);
rule.setEnabled(false);
m_rules[ruleId] = rule;
@ -538,7 +538,6 @@ RuleEngine::RuleError RuleEngine::executeExitActions(const RuleId &ruleId)
return RuleErrorNoError;
}
/*! Returns the \l{Rule} with the given \a ruleId. If the \l{Rule} does not exist, it will return \l{Rule::Rule()} */
Rule RuleEngine::findRule(const RuleId &ruleId)
{

View File

@ -24,6 +24,7 @@
#include "rule.h"
#include "types/event.h"
#include "plugin/deviceclass.h"
#include "stateevaluator.h"
#include <QObject>
@ -44,11 +45,13 @@ public:
RuleErrorRuleNotFound,
RuleErrorDeviceNotFound,
RuleErrorEventTypeNotFound,
RuleErrorStateTypeNotFound,
RuleErrorActionTypeNotFound,
RuleErrorInvalidParameter,
RuleErrorInvalidRuleFormat,
RuleErrorMissingParameter,
RuleErrorInvalidRuleActionParameter,
RuleErrorInvalidStateEvaluatorValue,
RuleErrorTypesNotMatching,
RuleErrorNotExecutable,
RuleErrorContainsEventBasesAction,

View File

@ -47,7 +47,6 @@ StateEvaluator::StateEvaluator(const StateDescriptor &stateDescriptor):
m_stateDescriptor(stateDescriptor),
m_operatorType(Types::StateOperatorAnd)
{
}
/*! Constructs a new StateEvaluator for the given \a childEvaluators and \a stateOperator. */
@ -136,9 +135,9 @@ bool StateEvaluator::evaluate() const
/*! Returns true if this \l StateEvaluator has a \l Device in it with the given \a deviceId. */
bool StateEvaluator::containsDevice(const DeviceId &deviceId) const
{
if (m_stateDescriptor.deviceId() == deviceId) {
if (m_stateDescriptor.deviceId() == deviceId)
return true;
}
foreach (const StateEvaluator &childEvaluator, m_childEvaluators) {
if (childEvaluator.containsDevice(deviceId)) {
return true;
@ -150,9 +149,9 @@ bool StateEvaluator::containsDevice(const DeviceId &deviceId) const
/*! Removes a \l Device with the given \a deviceId from this \l StateEvaluator. */
void StateEvaluator::removeDevice(const DeviceId &deviceId)
{
if (m_stateDescriptor.deviceId() == deviceId) {
if (m_stateDescriptor.deviceId() == deviceId)
m_stateDescriptor = StateDescriptor();
}
for (int i = 0; i < m_childEvaluators.count(); i++) {
m_childEvaluators[i].removeDevice(deviceId);
}
@ -207,4 +206,74 @@ StateEvaluator StateEvaluator::loadFromSettings(GuhSettings &settings, const QSt
return ret;
}
/*! Returns true, if all child evaluators are valid, the devices exist and all descriptors are in allowed paramerters.*/
bool StateEvaluator::isValid() const
{
if (m_stateDescriptor.isValid()) {
Device *device = GuhCore::instance()->findConfiguredDevice(m_stateDescriptor.deviceId());
if (!device) {
qCWarning(dcRuleEngine) << "State evaluator device does not exist!";
return false;
}
if (!device->hasState(m_stateDescriptor.stateTypeId())) {
qCWarning(dcRuleEngine) << "State evaluator device found, but it does not appear to have such a state!";
return false;
}
DeviceClass deviceClass = GuhCore::instance()->findDeviceClass(device->deviceClassId());
foreach (const StateType &stateType, deviceClass.stateTypes()) {
if (stateType.id() == m_stateDescriptor.stateTypeId()) {
if (!m_stateDescriptor.stateValue().canConvert(stateType.type())) {
qCWarning(dcRuleEngine) << "Wrong state value for state descriptor" << m_stateDescriptor.stateTypeId() << " Got:" << m_stateDescriptor.stateValue() << " Expected:" << QVariant::typeToName(stateType.type());
return false;
}
if (!m_stateDescriptor.stateValue().convert(stateType.type())) {
qCWarning(dcRuleEngine) << "Could not convert value of state descriptor" << m_stateDescriptor.stateTypeId() << " to:" << QVariant::typeToName(stateType.type()) << " Got:" << m_stateDescriptor.stateValue();
return false;
}
if (stateType.maxValue().isValid() && m_stateDescriptor.stateValue() > stateType.maxValue()) {
qCWarning(dcRuleEngine) << "Value out of range for state descriptor" << m_stateDescriptor.stateTypeId() << " Got:" << m_stateDescriptor.stateValue() << " Max:" << stateType.maxValue();
return false;
}
if (stateType.minValue().isValid() && m_stateDescriptor.stateValue() < stateType.minValue()) {
qCWarning(dcRuleEngine) << "Value out of range for state descriptor" << m_stateDescriptor.stateTypeId() << " Got:" << m_stateDescriptor.stateValue() << " Min:" << stateType.minValue();
return false;
}
if (!stateType.possibleValues().isEmpty() && !stateType.possibleValues().contains(m_stateDescriptor.stateValue())) {
QStringList possibleValues;
foreach (const QVariant &value, stateType.possibleValues()) {
possibleValues.append(value.toString());
}
qCWarning(dcRuleEngine) << "Value not in possible values for state type" << m_stateDescriptor.stateTypeId() << " Got:" << m_stateDescriptor.stateValue() << " Possible values:" << possibleValues.join(", ");
return false;
}
}
}
}
if (m_operatorType == Types::StateOperatorOr) {
foreach (const StateEvaluator &stateEvaluator, m_childEvaluators) {
if (stateEvaluator.isValid()) {
return true;
}
}
return false;
}
foreach (const StateEvaluator &stateEvaluator, m_childEvaluators) {
if (!stateEvaluator.isValid()) {
return false;
}
}
return true;
}
}

View File

@ -54,6 +54,8 @@ public:
void dumpToSettings(GuhSettings &settings, const QString &groupName) const;
static StateEvaluator loadFromSettings(GuhSettings &settings, const QString &groupPrefix);
bool isValid() const;
private:
StateDescriptor m_stateDescriptor;

View File

@ -1,4 +1,4 @@
33
34
{
"methods": {
"Actions.ExecuteAction": {
@ -756,11 +756,13 @@
"RuleErrorRuleNotFound",
"RuleErrorDeviceNotFound",
"RuleErrorEventTypeNotFound",
"RuleErrorStateTypeNotFound",
"RuleErrorActionTypeNotFound",
"RuleErrorInvalidParameter",
"RuleErrorInvalidRuleFormat",
"RuleErrorMissingParameter",
"RuleErrorInvalidRuleActionParameter",
"RuleErrorInvalidStateEvaluatorValue",
"RuleErrorTypesNotMatching",
"RuleErrorNotExecutable",
"RuleErrorContainsEventBasesAction",