diff --git a/libguh/types/statedescriptor.cpp b/libguh/types/statedescriptor.cpp index e756512e..6fdb1baf 100644 --- a/libguh/types/statedescriptor.cpp +++ b/libguh/types/statedescriptor.cpp @@ -83,35 +83,38 @@ bool StateDescriptor::operator ==(const StateDescriptor &other) const } /*! Compare this StateDescriptor to the \l{State} given by \a state. - * States are equal (returns true) if stateTypeId, deviceId and ValueOperator match. */ + * Returns true if the given state matches the definition of the StateDescriptor */ bool StateDescriptor::operator ==(const State &state) const { if ((m_stateTypeId != state.stateTypeId()) || (m_deviceId != state.deviceId())) { return false; } + QVariant convertedValue = state.value(); + convertedValue.convert(m_stateValue.type()); switch (m_operatorType) { case Types::ValueOperatorEquals: - return m_stateValue == state.value(); + return m_stateValue == convertedValue; case Types::ValueOperatorGreater: - return state.value() > m_stateValue; + return convertedValue > m_stateValue; case Types::ValueOperatorGreaterOrEqual: - return state.value() >= m_stateValue; + return convertedValue >= m_stateValue; case Types::ValueOperatorLess: - return state.value() < m_stateValue; + return convertedValue < m_stateValue; case Types::ValueOperatorLessOrEqual: - return state.value() <= m_stateValue; + return convertedValue <= m_stateValue; case Types::ValueOperatorNotEquals: - return m_stateValue != state.value(); + return m_stateValue != convertedValue; } return false; } /*! Compare this StateDescriptor to the \l{State} given by \a state. - * Returns true, if stateTypeId, deviceId or ValueOreator are different from the given \a state. */ + * returns true if the given state does not match the definition of the StateDescriptor */ bool StateDescriptor::operator !=(const State &state) const { return !(operator==(state)); } + /*! Returns the true if this \l{StateDescriptor} is valid. A \l{StateDescriptor} is valid * if the DeviceId and the StateTypeId are set and the state value of this \l{StateDescriptor} is valid. * \sa StateDescriptor(), deviceId(), stateValue() diff --git a/libguh/types/statedescriptor.h b/libguh/types/statedescriptor.h index 44d6455e..9c6c45fc 100644 --- a/libguh/types/statedescriptor.h +++ b/libguh/types/statedescriptor.h @@ -22,6 +22,7 @@ #include "typeutils.h" #include "paramdescriptor.h" #include "state.h" +#include "event.h" #include #include diff --git a/plugins/deviceplugins/mock/httpdaemon.cpp b/plugins/deviceplugins/mock/httpdaemon.cpp index 555a2854..c1931e59 100644 --- a/plugins/deviceplugins/mock/httpdaemon.cpp +++ b/plugins/deviceplugins/mock/httpdaemon.cpp @@ -38,7 +38,6 @@ HttpDaemon::HttpDaemon(Device *device, DevicePlugin *parent): void HttpDaemon::incomingConnection(qintptr socket) { - qDebug() << "incoming connection"; if (disabled) return; @@ -70,14 +69,11 @@ void HttpDaemon::readClient() if (socket->canReadLine()) { QByteArray data = socket->readLine(); QStringList tokens = QString(data).split(QRegExp("[ \r\n][ \r\n]*")); - qDebug() << "incoming data" << tokens[1]; QUrl url("http://foo.bar" + tokens[1]); QUrlQuery query(url); - qDebug() << "query is" << url.path(); if (url.path() == "/setstate") { emit setState(StateTypeId(query.queryItems().first().first), QVariant(query.queryItems().first().second)); } else if (url.path() == "/generateevent") { - qDebug() << "got generateevent" << query.queryItemValue("eventtypeid"); emit triggerEvent(EventTypeId(query.queryItemValue("eventtypeid"))); } else if (url.path() == "/actionhistory") { QTextStream os(socket); @@ -97,11 +93,8 @@ void HttpDaemon::readClient() os << generateWebPage(); socket->close(); - qDebug() << "Wrote to client"; - if (socket->state() == QTcpSocket::UnconnectedState) { delete socket; - qDebug() << "Connection closed"; } } } diff --git a/server/guhcore.cpp b/server/guhcore.cpp index 9c19d26e..8530da7a 100644 --- a/server/guhcore.cpp +++ b/server/guhcore.cpp @@ -257,9 +257,9 @@ Rule GuhCore::findRule(const RuleId &ruleId) /*! Calls the metheod RuleEngine::addRule(\a id, \a eventDescriptorList, \a actionList). * \sa RuleEngine, */ -RuleEngine::RuleError GuhCore::addRule(const RuleId &id, const QList &eventDescriptorList, const QList &actionList) +RuleEngine::RuleError GuhCore::addRule(const RuleId &id, const QList &eventDescriptorList, const StateEvaluator &stateEvaluator, const QList &actionList, bool enabled) { - return m_ruleEngine->addRule(id, eventDescriptorList, actionList, enabled); + return m_ruleEngine->addRule(id, eventDescriptorList, stateEvaluator, actionList, enabled); } /*! Calls the metheod RuleEngine::removeRule(\a id). diff --git a/server/guhcore.h b/server/guhcore.h index 2f20e058..49ada964 100644 --- a/server/guhcore.h +++ b/server/guhcore.h @@ -66,7 +66,7 @@ public: QList rules() const; QList ruleIds() const; Rule findRule(const RuleId &ruleId); - RuleEngine::RuleError addRule(const RuleId &id, const QList &eventDescriptorList, const QList &actionList, bool enabled = true); + RuleEngine::RuleError addRule(const RuleId &id, const QList &eventDescriptorList, const StateEvaluator &stateEvaluator, const QList &actionList, bool enabled = true); RuleEngine::RuleError removeRule(const RuleId &id); QList findRules(const DeviceId &deviceId); RuleEngine::RuleError enableRule(const RuleId &ruleId); diff --git a/server/jsonrpc/jsontypes.cpp b/server/jsonrpc/jsontypes.cpp index 3402295d..7042a757 100644 --- a/server/jsonrpc/jsontypes.cpp +++ b/server/jsonrpc/jsontypes.cpp @@ -322,6 +322,7 @@ QVariantMap JsonTypes::packStateEvaluator(const StateEvaluator &stateEvaluator) foreach (const StateEvaluator &childEvaluator, stateEvaluator.childEvaluators()) { childEvaluators.append(packStateEvaluator(childEvaluator)); } + qDebug() << "state operator:" << stateOperator() << stateEvaluator.operatorType(); variantMap.insert("operator", stateOperator().at(stateEvaluator.operatorType())); if (childEvaluators.count() > 0) { variantMap.insert("childEvaluators", childEvaluators); @@ -460,7 +461,9 @@ QVariantMap JsonTypes::packRule(const Rule &rule) actionList.append(JsonTypes::packAction(action)); } ruleMap.insert("actions", actionList); + qDebug() << "packing state evaluator"; ruleMap.insert("stateEvaluator", JsonTypes::packStateEvaluator(rule.stateEvaluator())); + qDebug() << "done p se"; return ruleMap; } @@ -529,6 +532,30 @@ EventDescriptor JsonTypes::unpackEventDescriptor(const QVariantMap &eventDescrip return eventDescriptor; } +StateEvaluator JsonTypes::unpackStateEvaluator(const QVariantMap &stateEvaluatorMap) +{ + StateEvaluator ret(unpackStateDescriptor(stateEvaluatorMap.value("stateDescriptor").toMap())); + if (stateEvaluatorMap.contains("operator")) { + ret.setOperatorType((Types::StateOperator)s_stateOperator.indexOf(stateEvaluatorMap.value("operator").toString())); + } + QList childEvaluators; + foreach (const QVariant &childEvaluator, stateEvaluatorMap.value("childEvaluators").toList()) { + childEvaluators.append(unpackStateEvaluator(childEvaluator.toMap())); + } + ret.setChildEvaluators(childEvaluators); + return ret; +} + +StateDescriptor JsonTypes::unpackStateDescriptor(const QVariantMap &stateDescriptorMap) +{ + StateTypeId stateTypeId(stateDescriptorMap.value("stateTypeId").toString()); + DeviceId deviceId(stateDescriptorMap.value("deviceId").toString()); + QVariant value = stateDescriptorMap.value("value"); + Types::ValueOperator operatorType = (Types::ValueOperator)s_valueOperator.indexOf(stateDescriptorMap.value("operator").toString()); + StateDescriptor stateDescriptor(stateTypeId, deviceId, value, operatorType); + return stateDescriptor; +} + QPair JsonTypes::validateMap(const QVariantMap &templateMap, const QVariantMap &map) { s_lastError.clear(); diff --git a/server/jsonrpc/jsontypes.h b/server/jsonrpc/jsontypes.h index 1e5b8ebf..86a6306b 100644 --- a/server/jsonrpc/jsontypes.h +++ b/server/jsonrpc/jsontypes.h @@ -137,6 +137,8 @@ public: static ParamDescriptor unpackParamDescriptor(const QVariantMap ¶mDescriptorMap); static QList unpackParamDescriptors(const QVariantList ¶mDescriptorList); static EventDescriptor unpackEventDescriptor(const QVariantMap &eventDescriptorMap); + static StateEvaluator unpackStateEvaluator(const QVariantMap &stateEvaluatorMap); + static StateDescriptor unpackStateDescriptor(const QVariantMap &stateDescriptorMap); static QPair validateMap(const QVariantMap &templateMap, const QVariantMap &map); static QPair validateProperty(const QVariant &templateValue, const QVariant &value); diff --git a/server/jsonrpc/ruleshandler.cpp b/server/jsonrpc/ruleshandler.cpp index 32700a9b..83be6a74 100644 --- a/server/jsonrpc/ruleshandler.cpp +++ b/server/jsonrpc/ruleshandler.cpp @@ -112,7 +112,9 @@ JsonReply *RulesHandler::GetRuleDetails(const QVariantMap ¶ms) Rule rule = GuhCore::instance()->findRule(ruleId); QVariantMap ruleData; if (!rule.id().isNull()) { + qDebug() << "packing rule"; ruleData.insert("rule", JsonTypes::packRule(rule)); + qDebug() << "done packing"; } return createReply(ruleData); } @@ -137,6 +139,9 @@ JsonReply* RulesHandler::AddRule(const QVariantMap ¶ms) } } + qDebug() << "unpacking:" << params.value("stateEvaluator").toMap(); + StateEvaluator stateEvaluator = JsonTypes::unpackStateEvaluator(params.value("stateEvaluator").toMap()); + QList actions; QVariantList actionList = params.value("actions").toList(); foreach (const QVariant &actionVariant, actionList) { @@ -155,7 +160,7 @@ JsonReply* RulesHandler::AddRule(const QVariantMap ¶ms) bool enabled = params.value("enabled", true).toBool(); RuleId newRuleId = RuleId::createRuleId(); - RuleEngine::RuleError status = GuhCore::instance()->addRule(newRuleId, eventDescriptorList, actions, enabled); + RuleEngine::RuleError status = GuhCore::instance()->addRule(newRuleId, eventDescriptorList, stateEvaluator, actions, enabled); if (status == RuleEngine::RuleErrorNoError) { returns.insert("ruleId", newRuleId.toString()); } diff --git a/server/ruleengine.cpp b/server/ruleengine.cpp index f2ff0e94..015f6eb1 100644 --- a/server/ruleengine.cpp +++ b/server/ruleengine.cpp @@ -158,27 +158,49 @@ RuleEngine::RuleEngine(QObject *parent) : } /*! Ask the Engine to evaluate all the rules for the given \a event. - This will search all the \l{Rule}{Rules} evented by this \l{Event} - and evaluate it's states according to its type. It will return a + This will search all the \l{Rule}{Rules} triggered by this \l{Event} + and evaluate it's states in the system. It will return a list of all \l{Action}{Actions} that should be executed. */ QList RuleEngine::evaluateEvent(const Event &event) { Device *device = GuhCore::instance()->findConfiguredDevice(event.deviceId()); - qDebug() << "got event:" << event << device->name(); + qDebug() << "got event:" << event << device->name() << event.eventTypeId(); QList actions; foreach (const RuleId &id, m_ruleIds) { Rule rule = m_rules.value(id); + qDebug() << "have a rule:" << rule.id() << rule.stateEvaluator().stateDescriptor().isValid() << rule.stateEvaluator().stateDescriptor().stateTypeId(); if (!rule.enabled()) { qDebug() << "Not triggering rule because it is disabled:" << rule.id(); continue; } - if (containsEvent(rule, event)) { - if (rule.stateEvaluator().evaluate()) { - qDebug() << "states matching!"; - actions.append(rule.actions()); + if (rule.eventDescriptors().isEmpty()) { + // This rule seems to have only states, check on state changed + qDebug() << "***** checking state"; + if (containsState(rule.stateEvaluator(), event)) { + qDebug() << "Yep, this state triggers"; + if (rule.stateEvaluator().evaluate()) { + qDebug() << "Yep, all states match"; + if (m_activeRules.contains(rule.id())) { + qDebug() << "This has been executed before... not doing again"; + } else { + qDebug() << "exectuing"; + m_activeRules.append(rule.id()); + actions.append(rule.actions()); + } + } else { + qDebug() << "not all states matching any more!"; + m_activeRules.removeAll(rule.id()); + } + } + } else { + if (containsEvent(rule, event)) { + if (rule.stateEvaluator().evaluate()) { + qDebug() << "states matching!"; + actions.append(rule.actions()); + } } } } @@ -412,6 +434,20 @@ bool RuleEngine::containsEvent(const Rule &rule, const Event &event) return false; } +bool RuleEngine::containsState(const StateEvaluator &stateEvaluator, const Event &stateChangeEvent) +{ + if (stateEvaluator.stateDescriptor().isValid() && stateEvaluator.stateDescriptor().stateTypeId().toString() == stateChangeEvent.eventTypeId().toString()) { + return true; + } + foreach (const StateEvaluator &childEvaluator, stateEvaluator.childEvaluators()) { + if (containsState(childEvaluator, stateChangeEvent)) { + return true; + } + } + + return false; +} + void RuleEngine::appendRule(const Rule &rule) { m_rules.insert(rule.id(), rule); diff --git a/server/ruleengine.h b/server/ruleengine.h index 6d4b3888..7ff2d18c 100644 --- a/server/ruleengine.h +++ b/server/ruleengine.h @@ -75,6 +75,7 @@ signals: private: bool containsEvent(const Rule &rule, const Event &event); + bool containsState(const StateEvaluator &stateEvaluator, const Event &stateChangeEvent); void appendRule(const Rule &rule); @@ -82,6 +83,7 @@ private: QString m_settingsFile; QList m_ruleIds; // Keeping a list of RuleIds to keep sorting order... QHash m_rules; // ...but use a Hash for faster finding + QList m_activeRules; }; Q_DECLARE_METATYPE(RuleEngine::RuleError) diff --git a/server/stateevaluator.cpp b/server/stateevaluator.cpp index 1f92b33d..dc9309e3 100644 --- a/server/stateevaluator.cpp +++ b/server/stateevaluator.cpp @@ -66,8 +66,10 @@ void StateEvaluator::setOperatorType(Types::StateOperator operatorType) bool StateEvaluator::evaluate() const { - if (!m_stateDescriptor.stateTypeId().isNull() && !m_stateDescriptor.deviceId().isNull()) { + qDebug() << "evaluating:" ; + if (m_stateDescriptor.isValid()) { Device *device = GuhCore::instance()->findConfiguredDevice(m_stateDescriptor.deviceId()); + qDebug() << "have device"; if (!device) { qWarning() << "Device not existing!"; return false; @@ -78,6 +80,7 @@ bool StateEvaluator::evaluate() const } if (m_stateDescriptor != device->state(m_stateDescriptor.stateTypeId())) { // state not matching + qDebug() << "booo" << m_stateDescriptor.stateValue() << device->stateValue(m_stateDescriptor.stateTypeId()); return false; } } diff --git a/tests/auto/rules/testrules.cpp b/tests/auto/rules/testrules.cpp index 27058746..8a7b9a2e 100644 --- a/tests/auto/rules/testrules.cpp +++ b/tests/auto/rules/testrules.cpp @@ -37,6 +37,9 @@ private: void cleanupMockHistory(); void cleanupRules(); + void verifyRuleExecuted(const ActionTypeId &actionTypeId); + void verifyRuleNotExecuted(); + private slots: void cleanup(); @@ -56,6 +59,8 @@ private slots: void testStateEvaluator2_data(); void testStateEvaluator2(); + void testStateChange(); + void enableDisableRule(); }; @@ -83,6 +88,39 @@ void TestRules::cleanup() { cleanupRules(); } +void TestRules::verifyRuleExecuted(const ActionTypeId &actionTypeId) +{ + // Verify rule got executed + QNetworkAccessManager nam; + QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*))); + QNetworkRequest request(QUrl(QString("http://localhost:%1/actionhistory").arg(m_mockDevice1Port))); + QNetworkReply *reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + + QByteArray actionHistory = reply->readAll(); + qDebug() << "have action history" << actionHistory; + QVERIFY2(actionTypeId == ActionTypeId(actionHistory), "Action not triggered"); + reply->deleteLater(); + +} + +void TestRules::verifyRuleNotExecuted() +{ + QNetworkAccessManager nam; + QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*))); + QNetworkRequest request(QUrl(QString("http://localhost:%1/actionhistory").arg(m_mockDevice1Port))); + QNetworkReply *reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + + QByteArray actionHistory = reply->readAll(); + qDebug() << "have action history" << actionHistory; + QVERIFY2(actionHistory.isEmpty(), "Action is triggered while it should not have been."); + reply->deleteLater(); + +} + void TestRules::addRemoveRules_data() { QVariantMap validActionNoParams; @@ -103,6 +141,7 @@ void TestRules::addRemoveRules_data() QVariantMap validStateEvaluator; validStateEvaluator.insert("stateDescriptor", stateDescriptor); + validStateEvaluator.insert("operator", JsonTypes::stateOperatorToString(Types::StateOperatorAnd)); QVariantMap invalidStateEvaluator; stateDescriptor.remove("deviceId"); @@ -146,9 +185,7 @@ void TestRules::addRemoveRules_data() QTest::newRow("valid rule. 2 EventDescriptors, 1 Action") << true << validActionNoParams << QVariantMap() << eventDescriptorList << validStateEvaluator << RuleEngine::RuleErrorNoError; QTest::newRow("invalid rule: eventDescriptor and eventDescriptorList used") << true << validActionNoParams << validEventDescriptor1 << eventDescriptorList << validStateEvaluator << RuleEngine::RuleErrorInvalidParameter; QTest::newRow("invalid action") << true << invalidAction << validEventDescriptor1 << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorActionTypeNotFound; - QTest::newRow("invalid event descriptor") <deleteLater(); - // Verify rule got executed + verifyRuleExecuted(mockActionIdNoParams); +} + +void TestRules::testStateChange() { + // Add a rule + QVariantMap addRuleParams; + QVariantMap stateEvaluator; + QVariantMap stateDescriptor; + stateDescriptor.insert("deviceId", m_mockDeviceId); + stateDescriptor.insert("operator", JsonTypes::valueOperatorToString(Types::ValueOperatorGreaterOrEqual)); + stateDescriptor.insert("stateTypeId", mockIntStateId); + stateDescriptor.insert("value", 42); + stateEvaluator.insert("stateDescriptor", stateDescriptor); + addRuleParams.insert("stateEvaluator", stateEvaluator); + + QVariantList actions; + QVariantMap action; + action.insert("actionTypeId", mockActionIdNoParams); + action.insert("deviceId", m_mockDeviceId); + actions.append(action); + addRuleParams.insert("actions", actions); + QVariant response = injectAndWait("Rules.AddRule", addRuleParams); + verifyRuleError(response); + + + // Change the state + QNetworkAccessManager nam; + QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*))); + + // state state to 42 + qDebug() << "setting mock int state to 42"; + QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(m_mockDevice1Port).arg(mockIntStateId.toString()).arg(42))); + QNetworkReply *reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + reply->deleteLater(); + + verifyRuleExecuted(mockActionIdNoParams); + + cleanupMockHistory(); + + // set state to 45 + qDebug() << "setting mock int state to 45"; spy.clear(); - request.setUrl(QUrl(QString("http://localhost:%1/actionhistory").arg(m_mockDevice1Port))); + request.setUrl(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(m_mockDevice1Port).arg(mockIntStateId.toString()).arg(45))); + reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + reply->deleteLater(); + + verifyRuleNotExecuted(); + + cleanupMockHistory(); + + // set state to 30 + qDebug() << "setting mock int state to 30"; + spy.clear(); + request.setUrl(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(m_mockDevice1Port).arg(mockIntStateId.toString()).arg(30))); + reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + reply->deleteLater(); + + verifyRuleNotExecuted(); + + cleanupMockHistory(); + + // set state to 100 + qDebug() << "setting mock int state to 100"; + spy.clear(); + request.setUrl(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(m_mockDevice1Port).arg(mockIntStateId.toString()).arg(100))); reply = nam.get(request); spy.wait(); QCOMPARE(spy.count(), 1); + verifyRuleExecuted(mockActionIdNoParams); reply->deleteLater(); - - QByteArray actionHistory = reply->readAll(); - QVERIFY2(mockActionIdNoParams == ActionTypeId(actionHistory), "Action not triggered"); } void TestRules::testStateEvaluator_data() @@ -515,17 +618,7 @@ void TestRules::enableDisableRule() QCOMPARE(spy.count(), 1); reply->deleteLater(); - // Verify rule got executed - spy.clear(); - request.setUrl(QUrl(QString("http://localhost:%1/actionhistory").arg(m_mockDevice1Port))); - reply = nam.get(request); - spy.wait(); - QCOMPARE(spy.count(), 1); - - QByteArray actionHistory = reply->readAll(); - qDebug() << "have action history" << actionHistory; - QVERIFY2(mockActionIdNoParams == ActionTypeId(actionHistory), "Action not triggered"); - reply->deleteLater(); + verifyRuleExecuted(mockActionIdNoParams); cleanupMockHistory(); @@ -544,17 +637,7 @@ void TestRules::enableDisableRule() QCOMPARE(spy.count(), 1); reply->deleteLater(); - // Verify rule got *NOT* executed - spy.clear(); - request.setUrl(QUrl(QString("http://localhost:%1/actionhistory").arg(m_mockDevice1Port))); - reply = nam.get(request); - spy.wait(); - QCOMPARE(spy.count(), 1); - - actionHistory = reply->readAll(); - qDebug() << "have action history" << actionHistory; - QVERIFY2(actionHistory.isEmpty(), "Action is triggered while rule is disabled"); - reply->deleteLater(); + verifyRuleNotExecuted(); cleanupMockHistory(); @@ -571,18 +654,7 @@ void TestRules::enableDisableRule() QCOMPARE(spy.count(), 1); reply->deleteLater(); - // Verify rule got executed - spy.clear(); - request.setUrl(QUrl(QString("http://localhost:%1/actionhistory").arg(m_mockDevice1Port))); - reply = nam.get(request); - spy.wait(); - QCOMPARE(spy.count(), 1); - - actionHistory = reply->readAll(); - qDebug() << "have action history" << actionHistory; - QVERIFY2(mockActionIdNoParams == ActionTypeId(actionHistory), "Action not triggered"); - reply->deleteLater(); - + verifyRuleExecuted(mockActionIdNoParams); } #include "testrules.moc"