use exitActions for else-action in event based rules

pull/135/head
Michael Zanetti 2018-07-04 14:01:37 +02:00
parent 16db97d182
commit 157b0b2b2c
9 changed files with 151 additions and 25 deletions

View File

@ -84,9 +84,17 @@ RulesHandler::RulesHandler(QObject *parent) :
setReturns("GetRuleDetails", returns);
params.clear(); returns.clear();
setDescription("AddRule", "Add a rule. You can describe rules by one or many EventDesciptors and a StateEvaluator. Note that only "
"one of either eventDescriptor or eventDescriptorList may be passed at a time. A rule can be created but left disabled, "
"meaning it won't actually be executed until set to enabled. If not given, enabled defaults to true.");
setDescription("AddRule", "Add a rule. You can describe rules by one or many EventDesciptors and a StateEvaluator. "
"Note that only one of either eventDescriptor or eventDescriptorList may be passed at a time. "
"A rule can be created but left disabled, meaning it won't actually be executed until set to enabled. "
"If not given, enabled defaults to true. A rule can have a list of actions and exitActions. "
"It must have at least one Action. For state based rules, actions will be executed when the system "
"enters a state matching the stateDescriptor. The exitActions will be executed when the system leaves "
"the described state again. For event based rules, actions will be executed when a matching event "
"happens and if the stateEvaluator matches the system's state. ExitActions for such rules will be "
"executed when a matching event happens and the stateEvaluator is not matching the system's state. "
"A rule marked as executable can be executed via the API using Rules.ExecuteRule, that means, its "
"actions will be executed regardless of the the eventDescriptor and stateEvaluators.");
params.insert("name", JsonTypes::basicTypeToString(JsonTypes::String));
params.insert("actions", QVariantList() << JsonTypes::ruleActionRef());
params.insert("o:timeDescriptor", JsonTypes::timeDescriptorRef());

View File

@ -593,8 +593,16 @@ void NymeaCore::gotEvent(const Event &event)
// Event based
if (!rule.eventDescriptors().isEmpty()) {
m_logger->logRuleTriggered(rule);
QList<RuleAction> tmp;
if (rule.statesActive()) {
qCDebug(dcRuleEngineDebug()) << "Executing actions";
tmp = rule.actions();
} else {
qCDebug(dcRuleEngineDebug()) << "Executing exitActions";
tmp = rule.exitActions();
}
// check if we have an event based action or a normal action
foreach (const RuleAction &action, rule.actions()) {
foreach (const RuleAction &action, tmp) {
if (action.isEventBased()) {
eventBasedActions.append(action);
} else {
@ -645,8 +653,10 @@ void NymeaCore::onDateTimeChanged(const QDateTime &dateTime)
// TimeEvent based
if (!rule.timeDescriptor().timeEventItems().isEmpty()) {
m_logger->logRuleTriggered(rule);
foreach (const RuleAction &action, rule.actions()) {
actions.append(action);
if (rule.statesActive()) {
actions.append(rule.actions());
} else {
actions.append(rule.exitActions());
}
} else {
// Calendar based rule

View File

@ -195,14 +195,14 @@ bool Rule::isValid() const
bool Rule::isConsistent() const
{
// check if this rules is based on any event and contains exit actions
if (!eventDescriptors().isEmpty() && !exitActions().isEmpty()) {
qCWarning(dcRuleEngine) << "Rule not consistent. The exitActions will never be executed if the rule contains an eventDescriptor.";
if (!eventDescriptors().isEmpty() && stateEvaluator().isEmpty() && !exitActions().isEmpty()) {
qCWarning(dcRuleEngine) << "Rule not consistent. The exitActions will never be executed if the rule contains an eventDescriptor but no stateEvaluator.";
return false;
}
// check if this rules is based on any time events and contains exit actions
if (!timeDescriptor().timeEventItems().isEmpty() && !exitActions().isEmpty()) {
qCWarning(dcRuleEngine) << "Rule not consistent. The exitActions will never be executed if the rule contains an timeEvents.";
if (!timeDescriptor().timeEventItems().isEmpty() && stateEvaluator().isEmpty() && !exitActions().isEmpty()) {
qCWarning(dcRuleEngine) << "Rule not consistent. The exitActions will never be executed if the rule contains a timeEvents but no stateEvaluator.";
return false;
}

View File

@ -390,7 +390,7 @@ QList<Rule> RuleEngine::evaluateEvent(const Event &event)
foreach (const RuleId &id, ruleIds()) {
Rule rule = m_rules.value(id);
if (!rule.enabled()) {
qCDebug(dcRuleEngineDebug()) << "Skipping rule" << rule.name() << "because it is disabled";
qCDebug(dcRuleEngineDebug()).nospace().noquote() << "Skipping rule " << rule.name() << " (" << rule.id().toString() << ") " << " because it is disabled.";
continue;
}
@ -404,7 +404,7 @@ QList<Rule> RuleEngine::evaluateEvent(const Event &event)
if (rule.eventDescriptors().isEmpty() && rule.timeDescriptor().timeEventItems().isEmpty()) {
if (rule.timeActive() && rule.statesActive()) {
if (!m_activeRules.contains(rule.id())) {
qCDebug(dcRuleEngine) << "Rule" << rule.id().toString() << "active.";
qCDebug(dcRuleEngine).nospace().noquote() << "Rule " << rule.name() << " (" << rule.id().toString() << ") active.";
rule.setActive(true);
m_rules[rule.id()] = rule;
m_activeRules.append(rule.id());
@ -412,7 +412,7 @@ QList<Rule> RuleEngine::evaluateEvent(const Event &event)
}
} else {
if (m_activeRules.contains(rule.id())) {
qCDebug(dcRuleEngine) << "Rule" << rule.id().toString() << "inactive.";
qCDebug(dcRuleEngine).nospace().noquote() << "Rule " << rule.name() << " (" << rule.id().toString() << ") inactive.";
rule.setActive(false);
m_rules[rule.id()] = rule;
m_activeRules.removeAll(rule.id());
@ -421,9 +421,15 @@ QList<Rule> RuleEngine::evaluateEvent(const Event &event)
}
} else {
// Event based rule
if (containsEvent(rule, event, device->deviceClassId()) && rule.statesActive() && rule.timeActive()) {
qCDebug(dcRuleEngine) << "Rule" << rule.id() << "contains event" << event.eventId() << "and all states match.";
rules.append(rule);
if (containsEvent(rule, event, device->deviceClassId())) {
qCDebug(dcRuleEngineDebug()).nospace().noquote() << "Rule " << rule.name() << " (" << rule.id().toString() << ") contains event " << event.eventId();
if (rule.statesActive() && rule.timeActive()) {
qCDebug(dcRuleEngine).nospace().noquote() << "Rule " << rule.name() << " (" + rule.id().toString() << ") contains event" << event.eventId() << "and all states match.";
rules.append(rule);
} else {
qCDebug(dcRuleEngine).nospace().noquote() << "Rule " << rule.name() << " (" + rule.id().toString() << ") contains event" << event.eventId() << "but state are not matching.";
rules.append(rule);
}
}
}
}
@ -486,8 +492,8 @@ QList<Rule> RuleEngine::evaluateTime(const QDateTime &dateTime)
// If we have timeEvent items
if (!rule.timeDescriptor().timeEventItems().isEmpty()) {
bool valid = rule.timeDescriptor().evaluate(m_lastEvaluationTime, dateTime);
if (valid && rule.statesActive() && rule.timeActive()) {
qCDebug(dcRuleEngine) << "Rule" << rule.id() << "time event triggert and all states match.";
if (valid && rule.timeActive()) {
qCDebug(dcRuleEngine) << "Rule" << rule.id() << "time event triggert.";
rules.append(rule);
}
}

View File

@ -346,6 +346,12 @@ bool StateEvaluator::isValid() const
return true;
}
/*! Returns true if the StateEvaluator is empty, that is, has no StateDescriptor and no ChildEvaluators */
bool StateEvaluator::isEmpty() const
{
return !m_stateDescriptor.isValid() && m_childEvaluators.isEmpty();
}
/*! Print a StateEvaluator including childEvaluators recuresively to QDebug. */
QDebug operator<<(QDebug dbg, const StateEvaluator &stateEvaluator)
{

View File

@ -56,6 +56,7 @@ public:
static StateEvaluator loadFromSettings(NymeaSettings &settings, const QString &groupPrefix);
bool isValid() const;
bool isEmpty() const;
private:
StateDescriptor m_stateDescriptor;

View File

@ -6,7 +6,7 @@ NYMEA_PLUGINS_PATH=/usr/lib/$$system('dpkg-architecture -q DEB_HOST_MULTIARCH')/
# define protocol versions
JSON_PROTOCOL_VERSION_MAJOR=1
JSON_PROTOCOL_VERSION_MINOR=6
JSON_PROTOCOL_VERSION_MINOR=7
REST_API_VERSION=1
DEFINES += NYMEA_VERSION_STRING=\\\"$${NYMEA_VERSION_STRING}\\\" \

View File

@ -1,4 +1,4 @@
1.6
1.7
{
"methods": {
"Actions.ExecuteAction": {
@ -658,7 +658,7 @@
}
},
"Rules.AddRule": {
"description": "Add a rule. You can describe rules by one or many EventDesciptors and a StateEvaluator. Note that only one of either eventDescriptor or eventDescriptorList may be passed at a time. A rule can be created but left disabled, meaning it won't actually be executed until set to enabled. If not given, enabled defaults to true.",
"description": "Add a rule. You can describe rules by one or many EventDesciptors and a StateEvaluator. Note that only one of either eventDescriptor or eventDescriptorList may be passed at a time. A rule can be created but left disabled, meaning it won't actually be executed until set to enabled. If not given, enabled defaults to true. A rule can have a list of actions and exitActions. It must have at least one Action. For state based rules, actions will be executed when the system enters a state matching the stateDescriptor. The exitActions will be executed when the system leaves the described state again. For event based rules, actions will be executed when a matching event happens and if the stateEvaluator matches the system's state. ExitActions for such rules will be executed when a matching event happens and the stateEvaluator is not matching the system's state. A rule marked as executable can be executed via the API using Rules.ExecuteRule, that means, its actions will be executed regardless of the the eventDescriptor and stateEvaluators.",
"params": {
"actions": [
"$ref:RuleAction"

View File

@ -98,6 +98,8 @@ private slots:
void testEventBasedAction();
void testEventBasedRuleWithExitAction();
void removePolicyUpdate();
void removePolicyCascade();
@ -492,8 +494,9 @@ void TestRules::addRemoveRules_data()
QTest::newRow("valid rule. enabled, 1 Action, 1 Exit Action, 1 StateEvaluator, name") << true << validActionNoParams << validExitActionNoParams << QVariantMap() << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorNoError << true << "TestRule";
QTest::newRow("valid rule. disabled, 1 Action, 1 Exit Action, 1 StateEvaluator, name") << false << validActionNoParams << validExitActionNoParams << QVariantMap() << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorNoError << true << "TestRule";
QTest::newRow("invalid rule. disabled, 1 Action, 1 invalid Exit Action, 1 StateEvaluator, name") << false << validActionNoParams << invalidExitAction << QVariantMap() << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorActionTypeNotFound << false << "TestRule";
QTest::newRow("invalid rule. 1 Action, 1 Exit Action, 1 EventDescriptor, 1 StateEvaluator, name") << true << validActionNoParams << validExitActionNoParams << validEventDescriptor1 << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorInvalidRuleFormat << false << "TestRule";
QTest::newRow("invalid rule. 1 Action, 1 Exit Action, eventDescriptorList, 1 StateEvaluator, name") << true << validActionNoParams << validExitActionNoParams << QVariantMap() << eventDescriptorList << validStateEvaluator << RuleEngine::RuleErrorInvalidRuleFormat << false << "TestRule";
QTest::newRow("valid rule. 1 Action, 1 Exit Action, 1 EventDescriptor, 1 StateEvaluator, name") << true << validActionNoParams << validExitActionNoParams << validEventDescriptor1 << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorNoError << false << "TestRule";
QTest::newRow("invalid rule. 1 Action, 1 Exit Action, eventDescriptorList, NO StateEvaluator, name")<< true << validActionNoParams << validExitActionNoParams << QVariantMap() << eventDescriptorList << QVariantMap() << RuleEngine::RuleErrorInvalidRuleFormat << false << "TestRule";
QTest::newRow("valid rule. 1 Action, 1 Exit Action, eventDescriptorList, 1 StateEvaluator, name") << true << validActionNoParams << validExitActionNoParams << QVariantMap() << eventDescriptorList << validStateEvaluator << RuleEngine::RuleErrorNoError << false << "TestRule";
// Rules without exit actions
QTest::newRow("valid rule. enabled, 1 EventDescriptor, StateEvaluator, 1 Action, name") << true << validActionNoParams << QVariantMap() << validEventDescriptor1 << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorNoError << true << "TestRule";
@ -732,8 +735,8 @@ void TestRules::editRules_data()
// Rules with exit actions
QTest::newRow("valid rule. enabled, 1 Action, 1 Exit Action, 1 StateEvaluator, name") << true << validActionNoParams << validExitActionNoParams << QVariantMap() << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorNoError << "TestRule";
QTest::newRow("valid rule. disabled, 1 Action, 1 Exit Action, 1 StateEvaluator, name") << false << validActionNoParams << validExitActionNoParams << QVariantMap() << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorNoError << "TestRule";
QTest::newRow("invalid rule. 1 Action, 1 Exit Action, 1 EventDescriptor, 1 StateEvaluator, name") << true << validActionNoParams << validExitActionNoParams << validEventDescriptor1 << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorInvalidRuleFormat << "TestRule";
QTest::newRow("invalid rule. 1 Action, 1 Exit Action, eventDescriptorList, 1 StateEvaluator, name") << true << validActionNoParams << validExitActionNoParams << QVariantMap() << eventDescriptorList << validStateEvaluator << RuleEngine::RuleErrorInvalidRuleFormat << "TestRule";
QTest::newRow("valid rule. 1 Action, 1 Exit Action, 1 EventDescriptor, 1 StateEvaluator, name") << true << validActionNoParams << validExitActionNoParams << validEventDescriptor1 << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorNoError << "TestRule";
QTest::newRow("valid rule. 1 Action, 1 Exit Action, eventDescriptorList, 1 StateEvaluator, name") << true << validActionNoParams << validExitActionNoParams << QVariantMap() << eventDescriptorList << validStateEvaluator << RuleEngine::RuleErrorNoError << "TestRule";
// Rules without exit actions
QTest::newRow("valid rule. enabled, 1 EventDescriptor, StateEvaluator, 1 Action, name") << true << validActionNoParams << QVariantMap() << validEventDescriptor1 << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorNoError << "TestRule";
@ -2083,6 +2086,98 @@ void TestRules::testEventBasedAction()
// TODO: check if this action was really executed with the int state value 42
}
void TestRules::testEventBasedRuleWithExitAction()
{
QNetworkAccessManager nam;
QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*)));
// Init bool state to true
spy.clear();
QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(m_mockDevice1Port).arg(mockBoolStateId.toString()).arg(true)));
QNetworkReply *reply = nam.get(request);
spy.wait();
QCOMPARE(spy.count(), 1);
reply->deleteLater();
// Add a rule
QVariantMap addRuleParams;
QVariantMap eventDescriptor;
eventDescriptor.insert("eventTypeId", mockEvent1Id);
eventDescriptor.insert("deviceId", m_mockDeviceId);
addRuleParams.insert("eventDescriptors", QVariantList() << eventDescriptor);
addRuleParams.insert("name", "TestRule");
addRuleParams.insert("enabled", true);
QVariantMap stateEvaluator;
QVariantMap stateDescriptor;
stateDescriptor.insert("deviceId", m_mockDeviceId);
stateDescriptor.insert("stateTypeId", mockBoolStateId);
stateDescriptor.insert("operator", "ValueOperatorEquals");
stateDescriptor.insert("value", true);
stateEvaluator.insert("stateDescriptor", stateDescriptor);
stateEvaluator.insert("operator", "StateOperatorAnd");
addRuleParams.insert("stateEvaluator", stateEvaluator);
QVariantList actions;
QVariantMap action;
QVariantList ruleActionParams;
QVariantMap param1;
param1.insert("paramTypeId", mockActionParam1ParamTypeId);
param1.insert("value", true);
QVariantMap param2;
param2.insert("paramTypeId", mockActionParam2ParamTypeId);
param2.insert("value", true);
ruleActionParams.append(param1);
ruleActionParams.append(param2);
action.insert("actionTypeId", mockActionIdNoParams);
action.insert("deviceId", m_mockDeviceId);
actions.append(action);
addRuleParams.insert("actions", actions);
actions.clear();
action.insert("actionTypeId", mockActionIdWithParams);
action.insert("ruleActionParams", ruleActionParams);
actions.append(action);
addRuleParams.insert("exitActions", actions);
qDebug() << addRuleParams;
QVariant response = injectAndWait("Rules.AddRule", addRuleParams);
verifyRuleError(response);
// trigger event
spy.clear();
request = QNetworkRequest(QUrl(QString("http://localhost:%1/generateevent?eventtypeid=%2").arg(m_mockDevice1Port).arg(mockEvent1Id.toString())));
reply = nam.get(request);
spy.wait();
QCOMPARE(spy.count(), 1);
reply->deleteLater();
// Verify the actions got executed
verifyRuleExecuted(mockActionIdNoParams);
// set bool state to false
spy.clear();
request = QNetworkRequest(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(m_mockDevice1Port).arg(mockBoolStateId.toString()).arg(false)));
reply = nam.get(request);
spy.wait();
QCOMPARE(spy.count(), 1);
reply->deleteLater();
// trigger event
spy.clear();
request = QNetworkRequest(QUrl(QString("http://localhost:%1/generateevent?eventtypeid=%2").arg(m_mockDevice1Port).arg(mockEvent1Id.toString())));
reply = nam.get(request);
spy.wait();
QCOMPARE(spy.count(), 1);
reply->deleteLater();
// Verify the exit actions got executed
verifyRuleExecuted(mockActionIdNoParams);
}
void TestRules::removePolicyUpdate()
{
// ADD parent device