From e21c2f16a5a5a2e57c7ff2aab660e1f57105f85b Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 18 Jun 2018 23:02:06 +0200 Subject: [PATCH] add support for interface based StateDescriptors --- libnymea-core/jsonrpc/jsontypes.cpp | 29 ++++-- libnymea-core/ruleengine.cpp | 14 ++- libnymea-core/stateevaluator.cpp | 149 ++++++++++++++++++---------- libnymea/types/statedescriptor.cpp | 40 +++++++- libnymea/types/statedescriptor.h | 14 +++ tests/auto/nymeatestbase.cpp | 4 +- tests/auto/rules/testrules.cpp | 136 ++++++++++++++++++++++--- 7 files changed, 302 insertions(+), 84 deletions(-) diff --git a/libnymea-core/jsonrpc/jsontypes.cpp b/libnymea-core/jsonrpc/jsontypes.cpp index ca257ac3..0cead778 100644 --- a/libnymea-core/jsonrpc/jsontypes.cpp +++ b/libnymea-core/jsonrpc/jsontypes.cpp @@ -206,8 +206,10 @@ void JsonTypes::init() s_state.insert("value", basicTypeToString(Variant)); // StateDescriptor - s_stateDescriptor.insert("stateTypeId", basicTypeToString(Uuid)); - s_stateDescriptor.insert("deviceId", basicTypeToString(Uuid)); + s_stateDescriptor.insert("o:stateTypeId", basicTypeToString(Uuid)); + s_stateDescriptor.insert("o:deviceId", basicTypeToString(Uuid)); + s_stateDescriptor.insert("o:interface", basicTypeToString(String)); + s_stateDescriptor.insert("o:interfaceState", basicTypeToString(String)); s_stateDescriptor.insert("value", basicTypeToString(Variant)); s_stateDescriptor.insert("operator", valueOperatorRef()); @@ -576,7 +578,11 @@ QVariantMap JsonTypes::packRuleAction(const RuleAction &ruleAction) QVariantMap JsonTypes::packRuleActionParam(const RuleActionParam &ruleActionParam) { QVariantMap variantMap; - variantMap.insert("paramTypeId", ruleActionParam.paramTypeId().toString()); + if (!ruleActionParam.paramTypeId().isNull()) { + variantMap.insert("paramTypeId", ruleActionParam.paramTypeId().toString()); + } else { + variantMap.insert("paramName", ruleActionParam.paramName()); + } // if this ruleaction param has a valid EventTypeId, there is no value if (ruleActionParam.eventTypeId() != EventTypeId()) { variantMap.insert("eventTypeId", ruleActionParam.eventTypeId()); @@ -632,8 +638,13 @@ QVariantMap JsonTypes::packStateType(const StateType &stateType) QVariantMap JsonTypes::packStateDescriptor(const StateDescriptor &stateDescriptor) { QVariantMap variantMap; - variantMap.insert("stateTypeId", stateDescriptor.stateTypeId().toString()); - variantMap.insert("deviceId", stateDescriptor.deviceId().toString()); + if (stateDescriptor.type() == StateDescriptor::TypeDevice) { + variantMap.insert("stateTypeId", stateDescriptor.stateTypeId().toString()); + variantMap.insert("deviceId", stateDescriptor.deviceId().toString()); + } else { + variantMap.insert("interface", stateDescriptor.interface()); + variantMap.insert("interfaceState", stateDescriptor.interfaceState()); + } variantMap.insert("value", stateDescriptor.stateValue()); variantMap.insert("operator", s_valueOperator.at(stateDescriptor.operatorType())); return variantMap; @@ -1435,9 +1446,15 @@ StateDescriptor JsonTypes::unpackStateDescriptor(const QVariantMap &stateDescrip { StateTypeId stateTypeId(stateDescriptorMap.value("stateTypeId").toString()); DeviceId deviceId(stateDescriptorMap.value("deviceId").toString()); + QString interface(stateDescriptorMap.value("interface").toString()); + QString interfaceState(stateDescriptorMap.value("interfaceState").toString()); QVariant value = stateDescriptorMap.value("value"); Types::ValueOperator operatorType = (Types::ValueOperator)s_valueOperator.indexOf(stateDescriptorMap.value("operator").toString()); - StateDescriptor stateDescriptor(stateTypeId, deviceId, value, operatorType); + if (!deviceId.isNull() && !stateTypeId.isNull()) { + StateDescriptor stateDescriptor(stateTypeId, deviceId, value, operatorType); + return stateDescriptor; + } + StateDescriptor stateDescriptor(interface, interfaceState, value, operatorType); return stateDescriptor; } diff --git a/libnymea-core/ruleengine.cpp b/libnymea-core/ruleengine.cpp index d2fdb0a6..599901b7 100644 --- a/libnymea-core/ruleengine.cpp +++ b/libnymea-core/ruleengine.cpp @@ -1155,8 +1155,18 @@ bool RuleEngine::containsEvent(const Rule &rule, const Event &event, const Devic bool RuleEngine::containsState(const StateEvaluator &stateEvaluator, const Event &stateChangeEvent) { - if (stateEvaluator.stateDescriptor().isValid() && stateEvaluator.stateDescriptor().stateTypeId().toString() == stateChangeEvent.eventTypeId().toString()) { - return true; + if (stateEvaluator.stateDescriptor().isValid()) { + if (stateEvaluator.stateDescriptor().type() == StateDescriptor::TypeDevice) { + if (stateEvaluator.stateDescriptor().stateTypeId().toString() == stateChangeEvent.eventTypeId().toString()) { + return true; + } + } else { + Device *device = NymeaCore::instance()->deviceManager()->findConfiguredDevice(stateChangeEvent.deviceId()); + DeviceClass deviceClass = NymeaCore::instance()->deviceManager()->findDeviceClass(device->deviceClassId()); + if (deviceClass.interfaces().contains(stateEvaluator.stateDescriptor().interface())) { + return true; + } + } } foreach (const StateEvaluator &childEvaluator, stateEvaluator.childEvaluators()) { diff --git a/libnymea-core/stateevaluator.cpp b/libnymea-core/stateevaluator.cpp index a6911c99..de9e1129 100644 --- a/libnymea-core/stateevaluator.cpp +++ b/libnymea-core/stateevaluator.cpp @@ -102,21 +102,40 @@ bool StateEvaluator::evaluate() const qCDebug(dcRuleEngineDebug()) << "StateEvaluator:" << this << "Evaluating: Operator type" << m_operatorType << "Valid descriptor:" << m_stateDescriptor.isValid() << "Childs:" << m_childEvaluators.count(); bool descriptorMatching = true; if (m_stateDescriptor.isValid()) { - Device *device = NymeaCore::instance()->deviceManager()->findConfiguredDevice(m_stateDescriptor.deviceId()); - if (!device) { - qCWarning(dcRuleEngine) << "StateEvaluator:" << this << "Device not existing!"; - descriptorMatching = false; - } else { - DeviceClass deviceClass = NymeaCore::instance()->deviceManager()->findDeviceClass(device->deviceClassId()); - if (!device->hasState(m_stateDescriptor.stateTypeId())) { - qCWarning(dcRuleEngine) << "StateEvaluator:" << this << "Device found, but it does not appear to have such a state!"; - descriptorMatching = false; - } - if (m_stateDescriptor != device->state(m_stateDescriptor.stateTypeId())) { - // state not matching - descriptorMatching = false; - } - qCDebug(dcRuleEngineDebug()) << "StateEvaluator:" << this << "State" << device->name() << deviceClass.stateTypes().findById(m_stateDescriptor.stateTypeId()).name() << (descriptorMatching ? "is" : "not") << "matching:" << m_stateDescriptor.stateValue() << m_stateDescriptor.operatorType() << device->stateValue(m_stateDescriptor.stateTypeId()); + descriptorMatching = false; + if (m_stateDescriptor.type() == StateDescriptor::TypeDevice) { + Device *device = NymeaCore::instance()->deviceManager()->findConfiguredDevice(m_stateDescriptor.deviceId()); + if (device) { + DeviceClass deviceClass = NymeaCore::instance()->deviceManager()->findDeviceClass(device->deviceClassId()); + if (device->hasState(m_stateDescriptor.stateTypeId())) { + if (m_stateDescriptor == device->state(m_stateDescriptor.stateTypeId())) { + qCDebug(dcRuleEngineDebug()) << "StateEvaluator:" << this << "State" << device->name() << deviceClass.stateTypes().findById(m_stateDescriptor.stateTypeId()).name() << (descriptorMatching ? "is" : "not") << "matching:" << m_stateDescriptor.stateValue() << m_stateDescriptor.operatorType() << device->stateValue(m_stateDescriptor.stateTypeId()); + descriptorMatching = true; + } + } else { + qCWarning(dcRuleEngine) << "StateEvaluator:" << this << "Device found, but it does not appear to have such a state!"; + } + } else { + qCWarning(dcRuleEngine) << "StateEvaluator:" << this << "Device not existing!"; + } + } else { // interface + foreach (Device* device, NymeaCore::instance()->deviceManager()->configuredDevices()) { + DeviceClass deviceClass = NymeaCore::instance()->deviceManager()->findDeviceClass(device->deviceClassId()); + if (!deviceClass.isValid()) { + qCWarning(dcRuleEngine()) << "Could not find DeviceClass for Device" << device->name() << device->id(); + continue; + } + if (deviceClass.interfaces().contains(m_stateDescriptor.interface())) { + StateType stateType = deviceClass.stateTypes().findByName(m_stateDescriptor.interfaceState()); + State state = device->state(stateType.id()); + // As the StateDescriptor can't compare on it's own against interfaces, generate custom one, matching the device + StateDescriptor temporaryDescriptor(stateType.id(), device->id(), m_stateDescriptor.stateValue(), m_stateDescriptor.operatorType()); + if (temporaryDescriptor == state) { + descriptorMatching = true; + break; + } + } + } } } @@ -194,6 +213,8 @@ void StateEvaluator::dumpToSettings(NymeaSettings &settings, const QString &grou settings.beginGroup("stateDescriptor"); settings.setValue("stateTypeId", m_stateDescriptor.stateTypeId().toString()); settings.setValue("deviceId", m_stateDescriptor.deviceId().toString()); + settings.setValue("interface", m_stateDescriptor.interface()); + settings.setValue("interfaceState", m_stateDescriptor.interfaceState()); settings.setValue("value", m_stateDescriptor.stateValue()); settings.setValue("operator", m_stateDescriptor.operatorType()); settings.endGroup(); @@ -218,8 +239,16 @@ StateEvaluator StateEvaluator::loadFromSettings(NymeaSettings &settings, const Q StateTypeId stateTypeId(settings.value("stateTypeId").toString()); DeviceId deviceId(settings.value("deviceId").toString()); QVariant stateValue = settings.value("value"); + QString interface = settings.value("interface").toString(); + QString interfaceState = settings.value("interfaceState").toString(); Types::ValueOperator valueOperator = (Types::ValueOperator)settings.value("operator").toInt(); - StateDescriptor stateDescriptor(stateTypeId, deviceId, stateValue, valueOperator); + StateDescriptor stateDescriptor; + if (!deviceId.isNull() && !stateTypeId.isNull()) { + stateDescriptor = StateDescriptor(stateTypeId, deviceId, stateValue, valueOperator); + } else { + stateDescriptor = StateDescriptor(interface, interfaceState, stateValue, valueOperator); + } + settings.endGroup(); StateEvaluator ret(stateDescriptor); @@ -238,51 +267,63 @@ StateEvaluator StateEvaluator::loadFromSettings(NymeaSettings &settings, const Q bool StateEvaluator::isValid() const { if (m_stateDescriptor.isValid()) { - Device *device = NymeaCore::instance()->deviceManager()->findConfiguredDevice(m_stateDescriptor.deviceId()); - if (!device) { - qCWarning(dcRuleEngine) << "State evaluator device does not exist!"; - return false; - } + if (m_stateDescriptor.type() == StateDescriptor::TypeDevice) { + Device *device = NymeaCore::instance()->deviceManager()->findConfiguredDevice(m_stateDescriptor.deviceId()); + if (!device) { + qCWarning(dcRuleEngine) << "State evaluator device does not exist!"; + return false; + } - if (!device->hasState(m_stateDescriptor.stateTypeId())) { - qCWarning(dcRuleEngine) << "State evaluator device found, but it does not appear to have such a state!"; - return false; - } + if (!device->hasState(m_stateDescriptor.stateTypeId())) { + qCWarning(dcRuleEngine) << "State evaluator device found, but it does not appear to have such a state!"; + return false; + } - DeviceClass deviceClass = NymeaCore::instance()->deviceManager()->findDeviceClass(device->deviceClassId()); - foreach (const StateType &stateType, deviceClass.stateTypes()) { - if (stateType.id() == m_stateDescriptor.stateTypeId()) { + DeviceClass deviceClass = NymeaCore::instance()->deviceManager()->findDeviceClass(device->deviceClassId()); + foreach (const StateType &stateType, deviceClass.stateTypes()) { + if (stateType.id() == m_stateDescriptor.stateTypeId()) { - if (!m_stateDescriptor.stateValue().canConvert(stateType.type())) { - qCWarning(dcRuleEngine) << "Wrong state value for state descriptor" << m_stateDescriptor.stateTypeId() << " Got:" << m_stateDescriptor.stateValue() << " Expected:" << QVariant::typeToName(stateType.type()); - return false; - } - - if (!m_stateDescriptor.stateValue().convert(stateType.type())) { - qCWarning(dcRuleEngine) << "Could not convert value of state descriptor" << m_stateDescriptor.stateTypeId() << " to:" << QVariant::typeToName(stateType.type()) << " Got:" << m_stateDescriptor.stateValue(); - return false; - } - - if (stateType.maxValue().isValid() && m_stateDescriptor.stateValue() > stateType.maxValue()) { - qCWarning(dcRuleEngine) << "Value out of range for state descriptor" << m_stateDescriptor.stateTypeId() << " Got:" << m_stateDescriptor.stateValue() << " Max:" << stateType.maxValue(); - return false; - } - - if (stateType.minValue().isValid() && m_stateDescriptor.stateValue() < stateType.minValue()) { - qCWarning(dcRuleEngine) << "Value out of range for state descriptor" << m_stateDescriptor.stateTypeId() << " Got:" << m_stateDescriptor.stateValue() << " Min:" << stateType.minValue(); - return false; - } - - if (!stateType.possibleValues().isEmpty() && !stateType.possibleValues().contains(m_stateDescriptor.stateValue())) { - QStringList possibleValues; - foreach (const QVariant &value, stateType.possibleValues()) { - possibleValues.append(value.toString()); + if (!m_stateDescriptor.stateValue().canConvert(stateType.type())) { + qCWarning(dcRuleEngine) << "Wrong state value for state descriptor" << m_stateDescriptor.stateTypeId() << " Got:" << m_stateDescriptor.stateValue() << " Expected:" << QVariant::typeToName(stateType.type()); + return false; } - qCWarning(dcRuleEngine) << "Value not in possible values for state type" << m_stateDescriptor.stateTypeId() << " Got:" << m_stateDescriptor.stateValue() << " Possible values:" << possibleValues.join(", "); - return false; + if (!m_stateDescriptor.stateValue().convert(stateType.type())) { + qCWarning(dcRuleEngine) << "Could not convert value of state descriptor" << m_stateDescriptor.stateTypeId() << " to:" << QVariant::typeToName(stateType.type()) << " Got:" << m_stateDescriptor.stateValue(); + return false; + } + + if (stateType.maxValue().isValid() && m_stateDescriptor.stateValue() > stateType.maxValue()) { + qCWarning(dcRuleEngine) << "Value out of range for state descriptor" << m_stateDescriptor.stateTypeId() << " Got:" << m_stateDescriptor.stateValue() << " Max:" << stateType.maxValue(); + return false; + } + + if (stateType.minValue().isValid() && m_stateDescriptor.stateValue() < stateType.minValue()) { + qCWarning(dcRuleEngine) << "Value out of range for state descriptor" << m_stateDescriptor.stateTypeId() << " Got:" << m_stateDescriptor.stateValue() << " Min:" << stateType.minValue(); + return false; + } + + if (!stateType.possibleValues().isEmpty() && !stateType.possibleValues().contains(m_stateDescriptor.stateValue())) { + QStringList possibleValues; + foreach (const QVariant &value, stateType.possibleValues()) { + possibleValues.append(value.toString()); + } + + qCWarning(dcRuleEngine) << "Value not in possible values for state type" << m_stateDescriptor.stateTypeId() << " Got:" << m_stateDescriptor.stateValue() << " Possible values:" << possibleValues.join(", "); + return false; + } } } + } else { // TypeInterface + Interface iface = NymeaCore::instance()->deviceManager()->supportedInterfaces().findByName(m_stateDescriptor.interface()); + if (!iface.isValid()) { + qWarning(dcRuleEngine()) << "No such interface:" << m_stateDescriptor.interface(); + return false; + } + if (iface.stateTypes().findByName(m_stateDescriptor.interfaceState()).name().isEmpty()) { + qWarning(dcRuleEngine()) << "Interface" << iface.name() << "has no such state:" << m_stateDescriptor.interfaceState(); + return false; + } } } diff --git a/libnymea/types/statedescriptor.cpp b/libnymea/types/statedescriptor.cpp index c090f31f..f0df240c 100644 --- a/libnymea/types/statedescriptor.cpp +++ b/libnymea/types/statedescriptor.cpp @@ -29,7 +29,9 @@ \ingroup rules \inmodule libnymea - An StateDescriptor describes a \l{State} in order to match it with a \l{nymeaserver::Rule}. + A StateDescriptor describes a \l{State} in order to match it with a \l{nymeaserver::Rule}. + A StateDescriptor uses either a \l{DeviceId}/\l{StateTypeId} pair to describe a \l{State} or + a pair of strings describing the interface and interface action for a \l{State}. \sa State, nymeaserver::Rule */ @@ -54,6 +56,22 @@ StateDescriptor::StateDescriptor(const StateTypeId &stateTypeId, const DeviceId } +/*! Constructs an StateDescriptor describing an \l{State} with the given \a interface, \a interfaceState, \a stateValue and \a operatorType.*/ +StateDescriptor::StateDescriptor(const QString &interface, const QString &interfaceState, const QVariant &stateValue, Types::ValueOperator operatorType): + m_interface(interface), + m_interfaceState(interfaceState), + m_stateValue(stateValue), + m_operatorType(operatorType) +{ + +} + +/*! Returns true \l{StateDescriptor::Type}{Type} of this descriptor. */ +StateDescriptor::Type StateDescriptor::type() const +{ + return (!m_deviceId.isNull() && !m_stateTypeId.isNull()) ? TypeDevice : TypeInterface; +} + /*! Returns the StateTypeId of this \l{State}.*/ StateTypeId StateDescriptor::stateTypeId() const { @@ -66,6 +84,18 @@ DeviceId StateDescriptor::deviceId() const return m_deviceId; } +/*! Returns the interface for this \{StateDescriptor}.*/ +QString StateDescriptor::interface() const +{ + return m_interface; +} + +/*! Returns the interface state's name for this \{StateDescriptor}.*/ +QString StateDescriptor::interfaceState() const +{ + return m_interfaceState; +} + /*! Returns the Value of this \l{State}.*/ QVariant StateDescriptor::stateValue() const { @@ -84,6 +114,8 @@ bool StateDescriptor::operator ==(const StateDescriptor &other) const { return m_stateTypeId == other.stateTypeId() && m_deviceId == other.deviceId() && + m_interface == other.interface() && + m_interfaceState == other.interfaceState() && m_stateValue == other.stateValue() && m_operatorType == other.operatorType(); } @@ -121,11 +153,11 @@ 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. +/*! Returns the true if this \l{StateDescriptor} is valid. A valid \l{StateDescriptor} must + * have a valid stateValue along with either a DeviceId/StateTypeId pair or an interface/interfaceState pair. * \sa StateDescriptor(), deviceId(), stateValue() */ bool StateDescriptor::isValid() const { - return !m_deviceId.isNull() && !m_stateTypeId.isNull() && m_stateValue.isValid(); + return ((!m_deviceId.isNull() && !m_stateTypeId.isNull()) || (!m_interface.isNull() && !m_interfaceState.isNull())) && m_stateValue.isValid(); } diff --git a/libnymea/types/statedescriptor.h b/libnymea/types/statedescriptor.h index 7f4ecef1..ec275eb9 100644 --- a/libnymea/types/statedescriptor.h +++ b/libnymea/types/statedescriptor.h @@ -37,11 +37,23 @@ class LIBNYMEA_EXPORT StateDescriptor { public: + enum Type { + TypeDevice, + TypeInterface + }; + StateDescriptor(); StateDescriptor(const StateTypeId &stateTypeId, const DeviceId &deviceId, const QVariant &stateValue, Types::ValueOperator operatorType = Types::ValueOperatorEquals); + StateDescriptor(const QString &interface, const QString &interfaceState, const QVariant &stateValue, Types::ValueOperator operatorType = Types::ValueOperatorEquals); + + Type type() const; StateTypeId stateTypeId() const; DeviceId deviceId() const; + + QString interface() const; + QString interfaceState() const; + QVariant stateValue() const; Types::ValueOperator operatorType() const; @@ -55,6 +67,8 @@ public: private: StateTypeId m_stateTypeId; DeviceId m_deviceId; + QString m_interface; + QString m_interfaceState; QVariant m_stateValue; Types::ValueOperator m_operatorType; }; diff --git a/tests/auto/nymeatestbase.cpp b/tests/auto/nymeatestbase.cpp index b5a266e5..21b869e3 100644 --- a/tests/auto/nymeatestbase.cpp +++ b/tests/auto/nymeatestbase.cpp @@ -117,8 +117,8 @@ NymeaTestBase::NymeaTestBase(QObject *parent) : { qRegisterMetaType(); qsrand(QDateTime::currentMSecsSinceEpoch()); - m_mockDevice1Port = 1337 + (qrand() % 1000); - m_mockDevice2Port = 7331 + (qrand() % 1000); + m_mockDevice1Port = 1337 + (qrand() % 10000); + m_mockDevice2Port = 7331 + (qrand() % 10000); // Important for settings QCoreApplication::instance()->setOrganizationName("nymea-test"); diff --git a/tests/auto/rules/testrules.cpp b/tests/auto/rules/testrules.cpp index a0a52d4b..37e612ee 100644 --- a/tests/auto/rules/testrules.cpp +++ b/tests/auto/rules/testrules.cpp @@ -107,7 +107,9 @@ private slots: void testRuleActionPAramsFromEventParameter_data(); void testRuleActionPAramsFromEventParameter(); - void testInterfaceBasedRule(); + void testInterfaceBasedEventRule(); + + void testInterfaceBasedStateRule(); void testHousekeeping_data(); void testHousekeeping(); @@ -499,7 +501,6 @@ void TestRules::addRemoveRules_data() QTest::newRow("valid rule. 2 EventDescriptors, 1 Action, name") << true << validActionNoParams << QVariantMap() << QVariantMap() << eventDescriptorList << validStateEvaluator << RuleEngine::RuleErrorNoError << true << "TestRule"; QTest::newRow("invalid action") << true << invalidAction << QVariantMap() << validEventDescriptor1 << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorActionTypeNotFound << false << "TestRule"; QTest::newRow("invalid event descriptor") << true << validActionNoParams << QVariantMap() << invalidEventDescriptor << QVariantList() << validStateEvaluator << RuleEngine::RuleErrorEventTypeNotFound << false << "TestRule"; - QTest::newRow("invalid StateDescriptor") << true << validActionNoParams << QVariantMap() << validEventDescriptor1 << QVariantList() << invalidStateEvaluator << RuleEngine::RuleErrorInvalidParameter << true << "TestRule"; } void TestRules::addRemoveRules() @@ -881,7 +882,7 @@ void TestRules::editRules() response = injectAndWait("Rules.EditRule", params); verifyRuleError(response, error); if (error == RuleEngine::RuleErrorNoError){ - clientSpy.wait(500); + clientSpy.wait(1); // We need to get exactly 2 replies. The actual reply and the Changed notification // Make sure there are no other notifications (e.g. RuleAdded or similar) QCOMPARE(clientSpy.count(), 2); @@ -1090,8 +1091,19 @@ void TestRules::loadStoreConfig() stateEvaluator3.insert("stateDescriptor", stateDescriptor3); stateEvaluator3.insert("operator", JsonTypes::stateOperatorToString(Types::StateOperatorAnd)); + QVariantMap stateDescriptor4; + stateDescriptor4.insert("interface", "battery"); + stateDescriptor4.insert("interfaceState", "batteryCritical"); + stateDescriptor4.insert("operator", JsonTypes::valueOperatorToString(Types::ValueOperatorEquals)); + stateDescriptor4.insert("value", true); + + QVariantMap stateEvaluator4; + stateEvaluator4.insert("stateDescriptor", stateDescriptor4); + stateEvaluator4.insert("operator", JsonTypes::stateOperatorToString(Types::StateOperatorAnd)); + childEvaluators.append(stateEvaluator2); childEvaluators.append(stateEvaluator3); + childEvaluators.append(stateEvaluator4); stateEvaluator1.insert("childEvaluators", childEvaluators); stateEvaluator1.insert("operator", JsonTypes::stateOperatorToString(Types::StateOperatorAnd)); @@ -1178,7 +1190,7 @@ void TestRules::loadStoreConfig() response = injectAndWait("Rules.GetRules"); QVariantList rules = response.toMap().value("params").toMap().value("ruleDescriptions").toList(); - qDebug() << response; + qDebug() << "GetRules before server shutdown:" << response; restartServer(); @@ -1219,19 +1231,26 @@ void TestRules::loadStoreConfig() QVERIFY2(found, "missing eventdescriptor"); } - qDebug() << endl << rule1; + qDebug() << "Rule after loading from config:" << rule1; QVERIFY2(rule1.value("name").toString() == "TestRule", "Loaded wrong name for rule"); QVariantMap replyStateEvaluator= rule1.value("stateEvaluator").toMap(); QVariantList replyChildEvaluators = replyStateEvaluator.value("childEvaluators").toList(); - QVERIFY2(replyChildEvaluators.count() == 2, "There should be exactly 2 childEvaluators"); + QCOMPARE(replyChildEvaluators.count(), 3); QVERIFY2(replyStateEvaluator.value("operator") == "StateOperatorAnd", "There should be the AND operator."); foreach (const QVariant &childEvaluator, replyChildEvaluators) { QVERIFY2(childEvaluator.toMap().contains("stateDescriptor"), "StateDescriptor missing in StateEvaluator"); QVariantMap stateDescriptor = childEvaluator.toMap().value("stateDescriptor").toMap(); - QVERIFY2(stateDescriptor.value("deviceId") == m_mockDeviceId, "DeviceId of stateDescriptor does not match"); - QVERIFY2(stateDescriptor.value("stateTypeId") == mockIntStateId || stateDescriptor.value("stateTypeId") == mockBoolStateId, "StateTypeId of stateDescriptor doesn't match"); + if (stateDescriptor.contains("deviceId") && stateDescriptor.contains("stateTypeId")) { + QVERIFY2(stateDescriptor.value("deviceId") == m_mockDeviceId, "DeviceId of stateDescriptor does not match"); + QVERIFY2(stateDescriptor.value("stateTypeId") == mockIntStateId || stateDescriptor.value("stateTypeId") == mockBoolStateId, "StateTypeId of stateDescriptor doesn't match"); + } else if (stateDescriptor.contains("interface") && stateDescriptor.contains("interfaceState")) { + QVERIFY2(stateDescriptor.value("interface") == "battery", "Interface of stateDescriptor does not match"); + QVERIFY2(stateDescriptor.value("interfaceState") == "batteryCritical", "InterfaceState of stateDescriptor doesn't match"); + } else { + QVERIFY2(false, "StateDescriptor must have either deviceId/stateTypeId or interface/interfaceState."); + } } QVariantList replyActions = rule1.value("actions").toList(); @@ -1265,13 +1284,20 @@ void TestRules::loadStoreConfig() QVariantMap replyStateEvaluator2= rule2.value("stateEvaluator").toMap(); QVariantList replyChildEvaluators2 = replyStateEvaluator.value("childEvaluators").toList(); QVERIFY2(replyStateEvaluator2.value("operator") == "StateOperatorAnd", "There should be the AND operator."); - QVERIFY2(replyChildEvaluators2.count() == 2, "There should be exactly 2 childEvaluators"); + QCOMPARE(replyChildEvaluators2.count(), 3); foreach (const QVariant &childEvaluator, replyChildEvaluators2) { QVERIFY2(childEvaluator.toMap().contains("stateDescriptor"), "StateDescriptor missing in StateEvaluator"); QVariantMap stateDescriptor = childEvaluator.toMap().value("stateDescriptor").toMap(); - QVERIFY2(stateDescriptor.value("deviceId") == m_mockDeviceId, "DeviceId of stateDescriptor does not match"); - QVERIFY2(stateDescriptor.value("stateTypeId") == mockIntStateId || stateDescriptor.value("stateTypeId") == mockBoolStateId, "StateTypeId of stateDescriptor doesn't match"); + if (stateDescriptor.contains("deviceId") && stateDescriptor.contains("stateTypeId")) { + QVERIFY2(stateDescriptor.value("deviceId") == m_mockDeviceId, "DeviceId of stateDescriptor does not match"); + QVERIFY2(stateDescriptor.value("stateTypeId") == mockIntStateId || stateDescriptor.value("stateTypeId") == mockBoolStateId, "StateTypeId of stateDescriptor doesn't match"); + } else if (stateDescriptor.contains("interface") && stateDescriptor.contains("interfaceState")) { + QVERIFY2(stateDescriptor.value("interface") == "battery", "Interface of stateDescriptor does not match"); + QVERIFY2(stateDescriptor.value("interfaceState") == "batteryCritical", "InterfaceState of stateDescriptor doesn't match"); + } else { + QVERIFY2(false, "StateDescriptor must have either deviceId/stateTypeId or interface/interfaceState."); + } } QVariantList replyActions2 = rule2.value("actions").toList(); @@ -1684,11 +1710,12 @@ void TestRules::testStateEvaluator3() void TestRules::testChildEvaluator_data() { cleanup(); - enableNotifications(); DeviceId testDeviceId = addDisplayPinDevice(); QVERIFY2(!testDeviceId.isNull(), "Could not add push button device for child evaluators"); + enableNotifications(); + // Create child evaluators // Action QVariantMap action; @@ -2238,8 +2265,18 @@ void TestRules::testRuleActionPAramsFromEventParameter() verifyRuleError(response, error); } -void TestRules::testInterfaceBasedRule() +void TestRules::testInterfaceBasedEventRule() { + QNetworkAccessManager nam; + QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*))); + + // state battery critical state to false initially + QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(m_mockDevice1Port).arg(mockBatteryCriticalStateId.toString()).arg(false))); + QNetworkReply *reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + reply->deleteLater(); + QVariantMap powerAction; powerAction.insert("interface", "light"); powerAction.insert("interfaceAction", "power"); @@ -2273,20 +2310,87 @@ void TestRules::testInterfaceBasedRule() QCOMPARE(response.toMap().value("params").toMap().value("rule").toMap().value("actions").toList().first().toMap().value("interface").toString(), QString("light")); QCOMPARE(response.toMap().value("params").toMap().value("rule").toMap().value("actions").toList().first().toMap().value("interfaceAction").toString(), QString("power")); + QCOMPARE(response.toMap().value("params").toMap().value("rule").toMap().value("actions").toList().first().toMap().value("ruleActionParams").toList().first().toMap().value("paramName").toString(), QString("power")); + QCOMPARE(response.toMap().value("params").toMap().value("rule").toMap().value("actions").toList().first().toMap().value("ruleActionParams").toList().first().toMap().value("value").toString(), QString("true")); // Change the state + spy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(m_mockDevice1Port).arg(mockBatteryCriticalStateId.toString()).arg(true))); + reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + reply->deleteLater(); + + verifyRuleExecuted(mockActionIdPower); +} + +void TestRules::testInterfaceBasedStateRule() +{ QNetworkAccessManager nam; QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*))); - // state battery critical state to true - qDebug() << "setting battery critical state to true"; - QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(m_mockDevice1Port).arg(mockBatteryCriticalStateId.toString()).arg(true))); + // state battery critical state to false initially + QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(m_mockDevice1Port).arg(mockBatteryCriticalStateId.toString()).arg(false))); QNetworkReply *reply = nam.get(request); spy.wait(); QCOMPARE(spy.count(), 1); reply->deleteLater(); + QVariantMap powerAction; + powerAction.insert("interface", "light"); + powerAction.insert("interfaceAction", "power"); + QVariantMap powerActionParam; + powerActionParam.insert("paramName", "power"); + powerActionParam.insert("value", true); + powerAction.insert("ruleActionParams", QVariantList() << powerActionParam); + + QVariantMap lowBatteryState; + lowBatteryState.insert("interface", "battery"); + lowBatteryState.insert("interfaceState", "batteryCritical"); + + QVariantMap stateDescriptor; + stateDescriptor.insert("interface", "battery"); + stateDescriptor.insert("interfaceState", "batteryCritical"); + stateDescriptor.insert("value", true); + stateDescriptor.insert("operator", "ValueOperatorEquals"); + + QVariantMap stateEvaluator; + stateEvaluator.insert("stateDescriptor", stateDescriptor); + + QVariantMap addRuleParams; + addRuleParams.insert("name", "TestInterfaceBasedStateRule"); + addRuleParams.insert("enabled", true); + addRuleParams.insert("stateEvaluator", stateEvaluator); + addRuleParams.insert("actions", QVariantList() << powerAction); + + QVariant response = injectAndWait("Rules.AddRule", addRuleParams); + QCOMPARE(response.toMap().value("status").toString(), QString("success")); + QCOMPARE(response.toMap().value("params").toMap().value("ruleError").toString(), QString("RuleErrorNoError")); + + QVariantMap getRuleParams; + getRuleParams.insert("ruleId", response.toMap().value("params").toMap().value("ruleId")); + response = injectAndWait("Rules.GetRuleDetails", getRuleParams); + + QCOMPARE(response.toMap().value("params").toMap().value("ruleError").toString(), QString("RuleErrorNoError")); + + QCOMPARE(response.toMap().value("params").toMap().value("rule").toMap().value("stateEvaluator").toMap().value("stateDescriptor").toMap().value("interface").toString(), QString("battery")); + QCOMPARE(response.toMap().value("params").toMap().value("rule").toMap().value("stateEvaluator").toMap().value("stateDescriptor").toMap().value("interfaceState").toString(), QString("batteryCritical")); + + QCOMPARE(response.toMap().value("params").toMap().value("rule").toMap().value("actions").toList().first().toMap().value("interface").toString(), QString("light")); + QCOMPARE(response.toMap().value("params").toMap().value("rule").toMap().value("actions").toList().first().toMap().value("interfaceAction").toString(), QString("power")); + QCOMPARE(response.toMap().value("params").toMap().value("rule").toMap().value("actions").toList().first().toMap().value("ruleActionParams").toList().first().toMap().value("paramName").toString(), QString("power")); + QCOMPARE(response.toMap().value("params").toMap().value("rule").toMap().value("actions").toList().first().toMap().value("ruleActionParams").toList().first().toMap().value("value").toString(), QString("true")); + + + // Change the state + spy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(m_mockDevice1Port).arg(mockBatteryCriticalStateId.toString()).arg(true))); + reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + reply->deleteLater(); + verifyRuleExecuted(mockActionIdPower); }