diff --git a/libnymea-core/ruleengine/ruleengine.cpp b/libnymea-core/ruleengine/ruleengine.cpp index 9a75797d..1c7694d2 100644 --- a/libnymea-core/ruleengine/ruleengine.cpp +++ b/libnymea-core/ruleengine/ruleengine.cpp @@ -898,7 +898,10 @@ bool RuleEngine::containsState(const StateEvaluator &stateEvaluator, const Event { if (stateEvaluator.stateDescriptor().isValid()) { if (stateEvaluator.stateDescriptor().type() == StateDescriptor::TypeThing) { - if (stateEvaluator.stateDescriptor().stateTypeId().toString() == stateChangeEvent.eventTypeId().toString()) { + if (stateEvaluator.stateDescriptor().thingId() == stateChangeEvent.thingId() && stateEvaluator.stateDescriptor().stateTypeId() == stateChangeEvent.eventTypeId()) { + return true; + } + if (stateEvaluator.stateDescriptor().valueThingId() == stateChangeEvent.thingId() && stateEvaluator.stateDescriptor().valueStateTypeId() == stateChangeEvent.eventTypeId()) { return true; } } else { diff --git a/libnymea-core/ruleengine/stateevaluator.cpp b/libnymea-core/ruleengine/stateevaluator.cpp index 2f57f0f8..07d2fea1 100644 --- a/libnymea-core/ruleengine/stateevaluator.cpp +++ b/libnymea-core/ruleengine/stateevaluator.cpp @@ -90,41 +90,7 @@ 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()) { - descriptorMatching = false; - if (m_stateDescriptor.type() == StateDescriptor::TypeThing) { - Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(m_stateDescriptor.thingId()); - if (thing) { - ThingClass thingClass = NymeaCore::instance()->thingManager()->findThingClass(thing->thingClassId()); - if (thing->hasState(m_stateDescriptor.stateTypeId())) { - if (m_stateDescriptor == thing->state(m_stateDescriptor.stateTypeId())) { - qCDebug(dcRuleEngineDebug()) << "StateEvaluator:" << this << "State" << thing->name() << thingClass.stateTypes().findById(m_stateDescriptor.stateTypeId()).name() << (descriptorMatching ? "is" : "not") << "matching:" << m_stateDescriptor.stateValue() << m_stateDescriptor.operatorType() << thing->stateValue(m_stateDescriptor.stateTypeId()); - descriptorMatching = true; - } - } else { - qCWarning(dcRuleEngine) << "StateEvaluator:" << this << "Thing found, but it does not appear to have such a state!"; - } - } else { - qCWarning(dcRuleEngine) << "StateEvaluator:" << this << "Thing not existing!"; - } - } else { // interface - foreach (Thing* thing, NymeaCore::instance()->thingManager()->configuredThings()) { - ThingClass thingClass = NymeaCore::instance()->thingManager()->findThingClass(thing->thingClassId()); - if (!thingClass.isValid()) { - qCWarning(dcRuleEngine()) << "Could not find ThingClass for Thing" << thing->name() << thing->id(); - continue; - } - if (thingClass.interfaces().contains(m_stateDescriptor.interface())) { - StateType stateType = thingClass.stateTypes().findByName(m_stateDescriptor.interfaceState()); - State state = thing->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(), thing->id(), m_stateDescriptor.stateValue(), m_stateDescriptor.operatorType()); - if (temporaryDescriptor == state) { - descriptorMatching = true; - break; - } - } - } - } + descriptorMatching = evaluateDescriptor(m_stateDescriptor); } if (m_operatorType == Types::StateOperatorOr) { @@ -159,7 +125,7 @@ bool StateEvaluator::evaluate() const bool StateEvaluator::containsThing(const ThingId &thingId) const { - if (m_stateDescriptor.thingId() == thingId) + if (m_stateDescriptor.thingId() == thingId || m_stateDescriptor.valueThingId() == thingId) return true; foreach (const StateEvaluator &childEvaluator, m_childEvaluators) { @@ -172,7 +138,7 @@ bool StateEvaluator::containsThing(const ThingId &thingId) const void StateEvaluator::removeThing(const ThingId &thingId) { - if (m_stateDescriptor.thingId() == thingId) + if (m_stateDescriptor.thingId() == thingId || m_stateDescriptor.valueThingId() == thingId) m_stateDescriptor = StateDescriptor(); for (int i = 0; i < m_childEvaluators.count(); i++) { @@ -186,6 +152,9 @@ QList StateEvaluator::containedThings() const if (!m_stateDescriptor.thingId().isNull()) { ret.append(m_stateDescriptor.thingId()); } + if (!m_stateDescriptor.valueThingId().isNull()) { + ret.append(m_stateDescriptor.valueThingId()); + } foreach (const StateEvaluator &childEvaluator, m_childEvaluators) { ret.append(childEvaluator.containedThings()); } @@ -203,6 +172,8 @@ void StateEvaluator::dumpToSettings(NymeaSettings &settings, const QString &grou settings.setValue("interfaceState", m_stateDescriptor.interfaceState()); settings.setValue("value", m_stateDescriptor.stateValue()); settings.setValue("valueType", (int)m_stateDescriptor.stateValue().type()); + settings.setValue("valueThingId", m_stateDescriptor.valueThingId().toString()); + settings.setValue("valueStateTypeId", m_stateDescriptor.valueStateTypeId().toString()); settings.setValue("operator", m_stateDescriptor.operatorType()); settings.endGroup(); @@ -239,6 +210,9 @@ StateEvaluator StateEvaluator::loadFromSettings(NymeaSettings &settings, const Q } } + ThingId valueThingId = settings.value("valueThingId").toUuid(); + StateTypeId valueStateTypeId = settings.value("valueStateTypeId").toUuid(); + QString interface = settings.value("interface").toString(); QString interfaceState = settings.value("interfaceState").toString(); Types::ValueOperator valueOperator = (Types::ValueOperator)settings.value("operator").toInt(); @@ -248,6 +222,8 @@ StateEvaluator StateEvaluator::loadFromSettings(NymeaSettings &settings, const Q } else { stateDescriptor = StateDescriptor(interface, interfaceState, stateValue, valueOperator); } + stateDescriptor.setValueThingId(valueThingId); + stateDescriptor.setValueStateTypeId(valueStateTypeId); settings.endGroup(); @@ -282,29 +258,48 @@ bool StateEvaluator::isValid() const foreach (const StateType &stateType, thingClass.stateTypes()) { if (stateType.id() == m_stateDescriptor.stateTypeId()) { - QVariant stateValue = m_stateDescriptor.stateValue(); - if (!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 comparing to a static value + if (!m_stateDescriptor.stateValue().isNull()) { - if (stateType.maxValue().isValid() && 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() && 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(stateValue)) { - QStringList possibleValues; - foreach (const QVariant &value, stateType.possibleValues()) { - possibleValues.append(value.toString()); + QVariant stateValue = m_stateDescriptor.stateValue(); + if (!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() && stateValue > stateType.maxValue()) { + qCWarning(dcRuleEngine) << "Value out of range for state descriptor" << m_stateDescriptor.stateTypeId() << " Got:" << m_stateDescriptor.stateValue() << " Max:" << stateType.maxValue(); + return false; } - qCWarning(dcRuleEngine) << "Value not in possible values for state type" << m_stateDescriptor.stateTypeId() << " Got:" << m_stateDescriptor.stateValue() << " Possible values:" << possibleValues.join(", "); + if (stateType.minValue().isValid() && 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(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; + } + + // if comparing to another state + } else if (!m_stateDescriptor.valueThingId().isNull() && !m_stateDescriptor.valueStateTypeId().isNull()) { + Thing *valueThing = NymeaCore::instance()->thingManager()->findConfiguredThing(m_stateDescriptor.valueThingId()); + if (!valueThing) { + qCWarning(dcRuleEngine()) << "State descriptor valueThing does not exist" << m_stateDescriptor.valueThingId().toString(); + return false; + } + StateType valueStateType = valueThing->thingClass().stateTypes().findById(m_stateDescriptor.valueStateTypeId()); + if (!valueStateType.isValid()) { + qCWarning(dcRuleEngine()) << "State descriptor value state type" << m_stateDescriptor.valueStateTypeId().toString() << "does not exist in thing" << valueThing->name(); + return false; + } + } else { + qCWarning(dcRuleEngine()) << "State descriptor contains neither stateValue nor valueThingId and valueStateTypeId"; return false; } } @@ -344,6 +339,94 @@ bool StateEvaluator::isEmpty() const return !m_stateDescriptor.isValid() && m_childEvaluators.isEmpty(); } +bool StateEvaluator::evaluateDescriptor(const StateDescriptor &descriptor) const +{ + if (descriptor.type() == StateDescriptor::TypeThing) { + qCDebug(dcRuleEngineDebug()) << "Evaluating thing based state descriptor"; + + Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(descriptor.thingId()); + if (!thing) { + qCWarning(dcRuleEngine()) << "Thing listed in state descriptor not found in system."; + return false; + } + State state = thing->state(descriptor.stateTypeId()); + if (state.stateTypeId().isNull()) { + qCWarning(dcRuleEngine()) << "State" << descriptor.stateTypeId() << "not found in thing" << thing->name() << thing->id().toString(); + return false; + } + + if (!descriptor.stateValue().isNull()) { + QVariant convertedValue = descriptor.stateValue(); + bool res = convertedValue.convert(state.value().type()); + if (!res) { + return false; + } + switch (descriptor.operatorType()) { + case Types::ValueOperatorEquals: + return state.value() == convertedValue; + case Types::ValueOperatorGreater: + return state.value() > convertedValue; + case Types::ValueOperatorGreaterOrEqual: + return state.value() >= convertedValue; + case Types::ValueOperatorLess: + return state.value() < convertedValue; + case Types::ValueOperatorLessOrEqual: + return state.value() <= convertedValue; + case Types::ValueOperatorNotEquals: + return state.value() != convertedValue; + } + + } else if (!descriptor.valueThingId().isNull() && !descriptor.valueStateTypeId().isNull()) { + Thing *valueThing = NymeaCore::instance()->thingManager()->findConfiguredThing(descriptor.valueThingId()); + if (!valueThing) { + qCWarning(dcRuleEngine()) << "Thing" << descriptor.valueThingId().toString() << "defined in statedescriptor value but not found in system."; + return false; + } + State valueState = valueThing->state(descriptor.valueStateTypeId()); + if (valueState.stateTypeId().isNull()) { + qCWarning(dcRuleEngine()) << "State" << descriptor.valueStateTypeId().toString() << "defined in statedescriptor value not found in thing" << thing->name() << thing->id().toString(); + return false; + } + + qCDebug(dcRuleEngineDebug()) << "Comparing" << state.value() << "to" << valueState.value() << "with operator" << descriptor.operatorType(); + switch (descriptor.operatorType()) { + case Types::ValueOperatorEquals: + return state.value() == valueState.value(); + case Types::ValueOperatorGreater: + return state.value() > valueState.value(); + case Types::ValueOperatorGreaterOrEqual: + return state.value() >= valueState.value(); + case Types::ValueOperatorLess: + return state.value() < valueState.value(); + case Types::ValueOperatorLessOrEqual: + return state.value() <= valueState.value(); + case Types::ValueOperatorNotEquals: + return state.value() != valueState.value(); + } + } + + } else { // Interface based + qCDebug(dcRuleEngineDebug()) << "Evaluating interface based state descriptor" << descriptor.interface(); + + foreach (Thing* thing, NymeaCore::instance()->thingManager()->configuredThings()) { + if (thing->thingClass().interfaces().contains(descriptor.interface())) { + qCDebug(dcRuleEngineDebug()) << "Thing" << thing->name() << "has matching interface"; + StateType stateType = thing->thingClass().stateTypes().findByName(descriptor.interfaceState()); + State state = thing->state(stateType.id()); + // Generate a thing based state descriptor and run again + StateDescriptor temporaryDescriptor(stateType.id(), thing->id(), descriptor.stateValue(), descriptor.operatorType()); + temporaryDescriptor.setValueThingId(descriptor.valueThingId()); + temporaryDescriptor.setValueStateTypeId(descriptor.valueStateTypeId()); + if (evaluateDescriptor(temporaryDescriptor)) { + return true; + } + } + } + } + + return false; +} + QDebug operator<<(QDebug dbg, const StateEvaluator &stateEvaluator) { dbg.nospace() << "StateEvaluator: Operator:" << stateEvaluator.operatorType() << endl << " " << stateEvaluator.stateDescriptor() << endl; diff --git a/libnymea-core/ruleengine/stateevaluator.h b/libnymea-core/ruleengine/stateevaluator.h index 68121d70..569e74e9 100644 --- a/libnymea-core/ruleengine/stateevaluator.h +++ b/libnymea-core/ruleengine/stateevaluator.h @@ -84,6 +84,9 @@ public: bool isValid() const; bool isEmpty() const; +private: + bool evaluateDescriptor(const StateDescriptor &descriptor) const; + private: StateDescriptor m_stateDescriptor; diff --git a/libnymea/types/statedescriptor.cpp b/libnymea/types/statedescriptor.cpp index 2dc1e84b..b0040420 100644 --- a/libnymea/types/statedescriptor.cpp +++ b/libnymea/types/statedescriptor.cpp @@ -141,6 +141,26 @@ void StateDescriptor::setStateValue(const QVariant &value) m_stateValue = value; } +ThingId StateDescriptor::valueThingId() const +{ + return m_valueThingId; +} + +void StateDescriptor::setValueThingId(const ThingId &valueThingId) +{ + m_valueThingId = valueThingId; +} + +StateTypeId StateDescriptor::valueStateTypeId() const +{ + return m_valueStateTypeId; +} + +void StateDescriptor::setValueStateTypeId(const StateTypeId &valueStateTypeId) +{ + m_valueStateTypeId = valueStateTypeId; +} + /*! Returns the ValueOperator of this \l{State}.*/ Types::ValueOperator StateDescriptor::operatorType() const { @@ -164,49 +184,14 @@ bool StateDescriptor::operator ==(const StateDescriptor &other) const m_operatorType == other.operatorType(); } -/*! Compare this StateDescriptor to the \l{State} given by \a state. - * Returns true if the given \a state matches the definition of the StateDescriptor */ -bool StateDescriptor::operator ==(const State &state) const -{ - if ((m_stateTypeId != state.stateTypeId()) || (m_thingId != state.thingId())) { - return false; - } - QVariant convertedValue = m_stateValue; - bool res = convertedValue.convert(state.value().type()); - if (!res) { - return false; - } - switch (m_operatorType) { - case Types::ValueOperatorEquals: - return convertedValue == state.value(); - case Types::ValueOperatorGreater: - return state.value() > convertedValue; - case Types::ValueOperatorGreaterOrEqual: - return state.value() >= convertedValue; - case Types::ValueOperatorLess: - return state.value() < convertedValue; - case Types::ValueOperatorLessOrEqual: - return state.value() <= convertedValue; - case Types::ValueOperatorNotEquals: - return convertedValue != state.value(); - } - return false; -} - -/*! Compare this StateDescriptor to the \l{State} given by \a state. - * Returns true if the given \a state does not match the definition of the StateDescriptor */ -bool StateDescriptor::operator !=(const State &state) const -{ - return !(operator==(state)); -} - /*! Returns the true if this \l{StateDescriptor} is valid. A 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_thingId.isNull() && !m_stateTypeId.isNull()) || (!m_interface.isNull() && !m_interfaceState.isNull())) && m_stateValue.isValid(); + return ((!m_thingId.isNull() && !m_stateTypeId.isNull()) || (!m_interface.isNull() && !m_interfaceState.isNull())) + && (m_stateValue.isValid() || (!m_valueThingId.isNull() && !m_valueStateTypeId.isNull())); } /*! Print a StateDescriptor with all its contents to QDebug. */ @@ -214,6 +199,7 @@ QDebug operator<<(QDebug dbg, const StateDescriptor &stateDescriptor) { dbg.nospace() << "StateDescriptor(ThingId:" << stateDescriptor.thingId().toString() << ", StateTypeId:" << stateDescriptor.stateTypeId().toString() << ", Interface:" << stateDescriptor.interface() - << ", InterfaceState:" << stateDescriptor.interfaceState() << ", Operator:" << stateDescriptor.operatorType() << ", Value:" << stateDescriptor.stateValue(); + << ", InterfaceState:" << stateDescriptor.interfaceState() << ", Operator:" << stateDescriptor.operatorType() << ", Value:" << stateDescriptor.stateValue() + << ", ValueThing:" << stateDescriptor.valueThingId().toString() << ", ValueStateTypeId:" << stateDescriptor.valueStateTypeId().toString(); return dbg; } diff --git a/libnymea/types/statedescriptor.h b/libnymea/types/statedescriptor.h index 649a0c52..7f608bc1 100644 --- a/libnymea/types/statedescriptor.h +++ b/libnymea/types/statedescriptor.h @@ -49,7 +49,9 @@ class LIBNYMEA_EXPORT StateDescriptor Q_PROPERTY(QUuid deviceId READ thingId WRITE setThingId USER true REVISION 1) Q_PROPERTY(QString interface READ interface WRITE setInterface USER true) Q_PROPERTY(QString interfaceState READ interfaceState WRITE setInterfaceState USER true) - Q_PROPERTY(QVariant value READ stateValue WRITE setStateValue) + Q_PROPERTY(QVariant value READ stateValue WRITE setStateValue USER true) + Q_PROPERTY(QUuid valueThingId READ valueThingId WRITE setValueThingId USER true) + Q_PROPERTY(QUuid valueStateTypeId READ valueStateTypeId WRITE setValueStateTypeId USER true) Q_PROPERTY(Types::ValueOperator operator READ operatorType WRITE setOperatorType) public: enum Type { @@ -78,6 +80,12 @@ public: QVariant stateValue() const; void setStateValue(const QVariant &value); + ThingId valueThingId() const; + void setValueThingId(const ThingId &valueThingId); + + StateTypeId valueStateTypeId() const; + void setValueStateTypeId(const StateTypeId &valueStateTypeId); + Types::ValueOperator operatorType() const; void setOperatorType(Types::ValueOperator opertatorType); @@ -85,15 +93,14 @@ public: bool operator ==(const StateDescriptor &other) const; - bool operator ==(const State &state) const; - bool operator !=(const State &state) const; - private: StateTypeId m_stateTypeId; ThingId m_thingId; QString m_interface; QString m_interfaceState; QVariant m_stateValue; + ThingId m_valueThingId; + StateTypeId m_valueStateTypeId; Types::ValueOperator m_operatorType = Types::ValueOperatorEquals; }; Q_DECLARE_METATYPE(StateDescriptor) diff --git a/nymea.pro b/nymea.pro index 4a8c3cda..9f9e0cd3 100644 --- a/nymea.pro +++ b/nymea.pro @@ -5,7 +5,7 @@ NYMEA_VERSION_STRING=$$system('dpkg-parsechangelog | sed -n -e "s/^Version: //p" # define protocol versions JSON_PROTOCOL_VERSION_MAJOR=5 -JSON_PROTOCOL_VERSION_MINOR=2 +JSON_PROTOCOL_VERSION_MINOR=3 JSON_PROTOCOL_VERSION="$${JSON_PROTOCOL_VERSION_MAJOR}.$${JSON_PROTOCOL_VERSION_MINOR}" LIBNYMEA_API_VERSION_MAJOR=7 LIBNYMEA_API_VERSION_MINOR=0 diff --git a/plugins/mock/httpdaemon.cpp b/plugins/mock/httpdaemon.cpp index ed2ea1f7..4d684002 100644 --- a/plugins/mock/httpdaemon.cpp +++ b/plugins/mock/httpdaemon.cpp @@ -99,6 +99,8 @@ void HttpDaemon::readClient() stateValue.convert(QVariant::Bool); } else if (stateTypeId == mockIntStateTypeId) { stateValue.convert(QVariant::Int); + } else if (stateTypeId == mockSignalStrengthStateTypeId) { + stateValue.convert(QVariant::UInt); } else if (stateTypeId == mockDoubleStateTypeId) { stateValue.convert(QVariant::Double); } diff --git a/tests/auto/api.json b/tests/auto/api.json index 1a25139c..bb753453 100644 --- a/tests/auto/api.json +++ b/tests/auto/api.json @@ -1,4 +1,4 @@ -5.2 +5.3 { "enums": { "BasicType": [ @@ -2664,8 +2664,10 @@ "o:interfaceState": "String", "o:stateTypeId": "Uuid", "o:thingId": "Uuid", - "operator": "$ref:ValueOperator", - "value": "Variant" + "o:value": "Variant", + "o:valueStateTypeId": "Uuid", + "o:valueThingId": "Uuid", + "operator": "$ref:ValueOperator" }, "StateEvaluator": { "o:childEvaluators": "$ref:StateEvaluators", diff --git a/tests/auto/rules/testrules.cpp b/tests/auto/rules/testrules.cpp index df196e67..94ff1c6e 100644 --- a/tests/auto/rules/testrules.cpp +++ b/tests/auto/rules/testrules.cpp @@ -130,6 +130,8 @@ private slots: void testInterfaceBasedStateRule(); + void testThingBasedAndThingValueStateDescriptor(); + void testLoopingRules(); void testScene(); @@ -409,7 +411,7 @@ void TestRules::initTestCase() QLoggingCategory::setFilterRules("*.debug=false\n" "Tests.debug=true\n" "RuleEngine.debug=true\n" -// "RuleEngineDebug.debug=true\n" + "RuleEngineDebug.debug=true\n" "JsonRpc.debug=true\n" "Mock.*=true"); } @@ -3014,6 +3016,116 @@ void TestRules::testInterfaceBasedStateRule() verifyRuleExecuted(mockPowerActionTypeId); } +void TestRules::testThingBasedAndThingValueStateDescriptor() +{ + QNetworkAccessManager nam; + QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*))); + + // set int state to 10 initially + QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(m_mockThing1Port).arg(mockIntStateTypeId.toString()).arg(10))); + QNetworkReply *reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + reply->deleteLater(); + + // set signalStrength state to 20 initially + spy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(m_mockThing1Port).arg(mockSignalStrengthStateTypeId.toString()).arg(20))); + reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + reply->deleteLater(); + + // set power to false intially + spy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(m_mockThing1Port).arg(mockPowerStateTypeId.toString()).arg(false))); + reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + reply->deleteLater(); + + // Create a new rule + QVariantMap addRuleParams; + addRuleParams.insert("name", "TestThingBasedAndThingValueStateRule"); + addRuleParams.insert("enabled", true); + + // with state descriptor "intState > signalStrength" + QVariantMap stateDescriptor; + stateDescriptor.insert("thingId", m_mockThingId); + stateDescriptor.insert("stateTypeId", mockIntStateTypeId); + stateDescriptor.insert("operator", "ValueOperatorGreater"); + stateDescriptor.insert("valueThingId", m_mockThingId); + stateDescriptor.insert("valueStateTypeId", mockSignalStrengthStateTypeId); + + QVariantMap stateEvaluator; + stateEvaluator.insert("stateDescriptor", stateDescriptor); + + addRuleParams.insert("stateEvaluator", stateEvaluator); + + // action to turn on power if state matches + QVariantMap powerAction; + powerAction.insert("thingId", m_mockThingId); + powerAction.insert("actionTypeId", mockPowerActionTypeId); + QVariantMap powerActionParam; + powerActionParam.insert("paramTypeId", mockPowerActionPowerParamTypeId); + powerActionParam.insert("value", true); + powerAction.insert("ruleActionParams", QVariantList() << powerActionParam); + + addRuleParams.insert("actions", QVariantList() << powerAction); + + // and exit action to turn power off again when state doesn't match any more + powerActionParam["value"] = false; + powerAction["ruleActionParams"] = (QVariantList() << powerActionParam); + + addRuleParams.insert("exitActions", QVariantList() << powerAction); + + // Add the rule + 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")); + RuleId ruleId = response.toMap().value("params").toMap().value("ruleId").toUuid(); + QVERIFY(!ruleId.isNull()); + + // Verify the rule is not active + QVariantMap getRuleParams; + getRuleParams.insert("ruleId", ruleId); + response = injectAndWait("Rules.GetRuleDetails", getRuleParams); + QCOMPARE(response.toMap().value("status").toString(), QString("success")); + QCOMPARE(response.toMap().value("params").toMap().value("ruleError").toString(), QString("RuleErrorNoError")); + QCOMPARE(response.toMap().value("params").toMap().value("rule").toMap().value("active").toBool(), false); + + // Now set Int state to 30 => should cause rule to become active + qCDebug(dcTests()) << "Setting state to 30"; + spy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(m_mockThing1Port).arg(mockIntStateTypeId.toString()).arg(30))); + reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + reply->deleteLater(); + + verifyRuleExecuted(mockPowerActionTypeId); + + response = injectAndWait("Rules.GetRuleDetails", getRuleParams); + QCOMPARE(response.toMap().value("status").toString(), QString("success")); + QCOMPARE(response.toMap().value("params").toMap().value("ruleError").toString(), QString("RuleErrorNoError")); + QCOMPARE(response.toMap().value("params").toMap().value("rule").toMap().value("active").toBool(), true); + + // Set signalStrength to 40 => should cause rule to become inactive + spy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(m_mockThing1Port).arg(mockSignalStrengthStateTypeId.toString()).arg(40))); + reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + reply->deleteLater(); + + verifyRuleExecuted(mockPowerActionTypeId); + + response = injectAndWait("Rules.GetRuleDetails", getRuleParams); + QCOMPARE(response.toMap().value("status").toString(), QString("success")); + QCOMPARE(response.toMap().value("params").toMap().value("ruleError").toString(), QString("RuleErrorNoError")); + QCOMPARE(response.toMap().value("params").toMap().value("rule").toMap().value("active").toBool(), false); +} + void TestRules::testLoopingRules() { QVariantMap powerOnActionParam;