finish off the stateevaluator handling

pull/135/head
Michael Zanetti 2014-12-20 01:17:52 +01:00 committed by Michael Zanetti
parent d1b638017b
commit 56ca42e1ca
12 changed files with 213 additions and 69 deletions

View File

@ -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()

View File

@ -22,6 +22,7 @@
#include "typeutils.h"
#include "paramdescriptor.h"
#include "state.h"
#include "event.h"
#include <QString>
#include <QVariantList>

View File

@ -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";
}
}
}

View File

@ -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<EventDescriptor> &eventDescriptorList, const QList<Action> &actionList)
RuleEngine::RuleError GuhCore::addRule(const RuleId &id, const QList<EventDescriptor> &eventDescriptorList, const StateEvaluator &stateEvaluator, const QList<Action> &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).

View File

@ -66,7 +66,7 @@ public:
QList<Rule> rules() const;
QList<RuleId> ruleIds() const;
Rule findRule(const RuleId &ruleId);
RuleEngine::RuleError addRule(const RuleId &id, const QList<EventDescriptor> &eventDescriptorList, const QList<Action> &actionList, bool enabled = true);
RuleEngine::RuleError addRule(const RuleId &id, const QList<EventDescriptor> &eventDescriptorList, const StateEvaluator &stateEvaluator, const QList<Action> &actionList, bool enabled = true);
RuleEngine::RuleError removeRule(const RuleId &id);
QList<RuleId> findRules(const DeviceId &deviceId);
RuleEngine::RuleError enableRule(const RuleId &ruleId);

View File

@ -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<StateEvaluator> 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<bool, QString> JsonTypes::validateMap(const QVariantMap &templateMap, const QVariantMap &map)
{
s_lastError.clear();

View File

@ -137,6 +137,8 @@ public:
static ParamDescriptor unpackParamDescriptor(const QVariantMap &paramDescriptorMap);
static QList<ParamDescriptor> unpackParamDescriptors(const QVariantList &paramDescriptorList);
static EventDescriptor unpackEventDescriptor(const QVariantMap &eventDescriptorMap);
static StateEvaluator unpackStateEvaluator(const QVariantMap &stateEvaluatorMap);
static StateDescriptor unpackStateDescriptor(const QVariantMap &stateDescriptorMap);
static QPair<bool, QString> validateMap(const QVariantMap &templateMap, const QVariantMap &map);
static QPair<bool, QString> validateProperty(const QVariant &templateValue, const QVariant &value);

View File

@ -112,7 +112,9 @@ JsonReply *RulesHandler::GetRuleDetails(const QVariantMap &params)
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 &params)
}
}
qDebug() << "unpacking:" << params.value("stateEvaluator").toMap();
StateEvaluator stateEvaluator = JsonTypes::unpackStateEvaluator(params.value("stateEvaluator").toMap());
QList<Action> actions;
QVariantList actionList = params.value("actions").toList();
foreach (const QVariant &actionVariant, actionList) {
@ -155,7 +160,7 @@ JsonReply* RulesHandler::AddRule(const QVariantMap &params)
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());
}

View File

@ -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<Action> 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<Action> 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);

View File

@ -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<RuleId> m_ruleIds; // Keeping a list of RuleIds to keep sorting order...
QHash<RuleId, Rule> m_rules; // ...but use a Hash for faster finding
QList<RuleId> m_activeRules;
};
Q_DECLARE_METATYPE(RuleEngine::RuleError)

View File

@ -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;
}
}

View File

@ -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") <<true << validActionNoParams << invalidEventDescriptor << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorDeviceNotFound;
// QTest::newRow("invalid state evaluator") << validActionNoParams << invalidEventDescriptor << QVariantList() << invalidStateEvaluator << false;
QTest::newRow("invalid event descriptor") << true << validActionNoParams << invalidEventDescriptor << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorDeviceNotFound;
}
void TestRules::addRemoveRules()
@ -380,17 +417,83 @@ void TestRules::evaluateEvent()
QCOMPARE(spy.count(), 1);
reply->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"