mirror of https://github.com/nymea/nymea.git
use exitActions for else-action in event based rules
parent
16db97d182
commit
157b0b2b2c
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ public:
|
|||
static StateEvaluator loadFromSettings(NymeaSettings &settings, const QString &groupPrefix);
|
||||
|
||||
bool isValid() const;
|
||||
bool isEmpty() const;
|
||||
|
||||
private:
|
||||
StateDescriptor m_stateDescriptor;
|
||||
|
|
|
|||
|
|
@ -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}\\\" \
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue