use exitActions for else-action in event based rules

This commit is contained in:
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); setReturns("GetRuleDetails", returns);
params.clear(); returns.clear(); params.clear(); returns.clear();
setDescription("AddRule", "Add a rule. You can describe rules by one or many EventDesciptors and a StateEvaluator. Note that only " setDescription("AddRule", "Add a rule. You can describe rules by one or many EventDesciptors and a StateEvaluator. "
"one of either eventDescriptor or eventDescriptorList may be passed at a time. A rule can be created but left disabled, " "Note that only one of either eventDescriptor or eventDescriptorList may be passed at a time. "
"meaning it won't actually be executed until set to enabled. If not given, enabled defaults to true."); "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("name", JsonTypes::basicTypeToString(JsonTypes::String));
params.insert("actions", QVariantList() << JsonTypes::ruleActionRef()); params.insert("actions", QVariantList() << JsonTypes::ruleActionRef());
params.insert("o:timeDescriptor", JsonTypes::timeDescriptorRef()); params.insert("o:timeDescriptor", JsonTypes::timeDescriptorRef());

View File

@ -593,8 +593,16 @@ void NymeaCore::gotEvent(const Event &event)
// Event based // Event based
if (!rule.eventDescriptors().isEmpty()) { if (!rule.eventDescriptors().isEmpty()) {
m_logger->logRuleTriggered(rule); 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 // 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()) { if (action.isEventBased()) {
eventBasedActions.append(action); eventBasedActions.append(action);
} else { } else {
@ -645,8 +653,10 @@ void NymeaCore::onDateTimeChanged(const QDateTime &dateTime)
// TimeEvent based // TimeEvent based
if (!rule.timeDescriptor().timeEventItems().isEmpty()) { if (!rule.timeDescriptor().timeEventItems().isEmpty()) {
m_logger->logRuleTriggered(rule); m_logger->logRuleTriggered(rule);
foreach (const RuleAction &action, rule.actions()) { if (rule.statesActive()) {
actions.append(action); actions.append(rule.actions());
} else {
actions.append(rule.exitActions());
} }
} else { } else {
// Calendar based rule // Calendar based rule

View File

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

View File

@ -390,7 +390,7 @@ QList<Rule> RuleEngine::evaluateEvent(const Event &event)
foreach (const RuleId &id, ruleIds()) { foreach (const RuleId &id, ruleIds()) {
Rule rule = m_rules.value(id); Rule rule = m_rules.value(id);
if (!rule.enabled()) { 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; continue;
} }
@ -404,7 +404,7 @@ QList<Rule> RuleEngine::evaluateEvent(const Event &event)
if (rule.eventDescriptors().isEmpty() && rule.timeDescriptor().timeEventItems().isEmpty()) { if (rule.eventDescriptors().isEmpty() && rule.timeDescriptor().timeEventItems().isEmpty()) {
if (rule.timeActive() && rule.statesActive()) { if (rule.timeActive() && rule.statesActive()) {
if (!m_activeRules.contains(rule.id())) { 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); rule.setActive(true);
m_rules[rule.id()] = rule; m_rules[rule.id()] = rule;
m_activeRules.append(rule.id()); m_activeRules.append(rule.id());
@ -412,7 +412,7 @@ QList<Rule> RuleEngine::evaluateEvent(const Event &event)
} }
} else { } else {
if (m_activeRules.contains(rule.id())) { 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); rule.setActive(false);
m_rules[rule.id()] = rule; m_rules[rule.id()] = rule;
m_activeRules.removeAll(rule.id()); m_activeRules.removeAll(rule.id());
@ -421,9 +421,15 @@ QList<Rule> RuleEngine::evaluateEvent(const Event &event)
} }
} else { } else {
// Event based rule // Event based rule
if (containsEvent(rule, event, device->deviceClassId()) && rule.statesActive() && rule.timeActive()) { if (containsEvent(rule, event, device->deviceClassId())) {
qCDebug(dcRuleEngine) << "Rule" << rule.id() << "contains event" << event.eventId() << "and all states match."; qCDebug(dcRuleEngineDebug()).nospace().noquote() << "Rule " << rule.name() << " (" << rule.id().toString() << ") contains event " << event.eventId();
rules.append(rule); 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 we have timeEvent items
if (!rule.timeDescriptor().timeEventItems().isEmpty()) { if (!rule.timeDescriptor().timeEventItems().isEmpty()) {
bool valid = rule.timeDescriptor().evaluate(m_lastEvaluationTime, dateTime); bool valid = rule.timeDescriptor().evaluate(m_lastEvaluationTime, dateTime);
if (valid && rule.statesActive() && rule.timeActive()) { if (valid && rule.timeActive()) {
qCDebug(dcRuleEngine) << "Rule" << rule.id() << "time event triggert and all states match."; qCDebug(dcRuleEngine) << "Rule" << rule.id() << "time event triggert.";
rules.append(rule); rules.append(rule);
} }
} }

View File

@ -346,6 +346,12 @@ bool StateEvaluator::isValid() const
return true; 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. */ /*! Print a StateEvaluator including childEvaluators recuresively to QDebug. */
QDebug operator<<(QDebug dbg, const StateEvaluator &stateEvaluator) QDebug operator<<(QDebug dbg, const StateEvaluator &stateEvaluator)
{ {

View File

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

View File

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

View File

@ -1,4 +1,4 @@
1.6 1.7
{ {
"methods": { "methods": {
"Actions.ExecuteAction": { "Actions.ExecuteAction": {
@ -658,7 +658,7 @@
} }
}, },
"Rules.AddRule": { "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": { "params": {
"actions": [ "actions": [
"$ref:RuleAction" "$ref:RuleAction"

View File

@ -98,6 +98,8 @@ private slots:
void testEventBasedAction(); void testEventBasedAction();
void testEventBasedRuleWithExitAction();
void removePolicyUpdate(); void removePolicyUpdate();
void removePolicyCascade(); 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. 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("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. 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("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, 1 StateEvaluator, name") << true << validActionNoParams << validExitActionNoParams << QVariantMap() << eventDescriptorList << validStateEvaluator << RuleEngine::RuleErrorInvalidRuleFormat << 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 // 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"; 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 // 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. 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("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("valid rule. 1 Action, 1 Exit Action, 1 EventDescriptor, 1 StateEvaluator, name") << true << validActionNoParams << validExitActionNoParams << validEventDescriptor1 << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorNoError << "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, eventDescriptorList, 1 StateEvaluator, name") << true << validActionNoParams << validExitActionNoParams << QVariantMap() << eventDescriptorList << validStateEvaluator << RuleEngine::RuleErrorNoError << "TestRule";
// Rules without exit actions // Rules without exit actions
QTest::newRow("valid rule. enabled, 1 EventDescriptor, StateEvaluator, 1 Action, name") << true << validActionNoParams << QVariantMap() << validEventDescriptor1 << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorNoError << "TestRule"; 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 // 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() void TestRules::removePolicyUpdate()
{ {
// ADD parent device // ADD parent device