Merge PR #349: Add state based value comparison in rules

pull/175/head
Jenkins nymea 2020-12-21 18:00:43 +01:00
commit ee911e2824
8 changed files with 301 additions and 103 deletions

View File

@ -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 {

View File

@ -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<ThingId> 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;

View File

@ -84,6 +84,9 @@ public:
bool isValid() const;
bool isEmpty() const;
private:
bool evaluateDescriptor(const StateDescriptor &descriptor) const;
private:
StateDescriptor m_stateDescriptor;

View File

@ -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;
}

View File

@ -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)

View File

@ -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);
}

View File

@ -2779,8 +2779,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",

View File

@ -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;