add support for interface based StateDescriptors

This commit is contained in:
Michael Zanetti 2018-06-18 23:02:06 +02:00
parent 5f226df930
commit e21c2f16a5
7 changed files with 302 additions and 84 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -117,8 +117,8 @@ NymeaTestBase::NymeaTestBase(QObject *parent) :
{
qRegisterMetaType<QNetworkReply*>();
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");

View File

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