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)); s_state.insert("value", basicTypeToString(Variant));
// StateDescriptor // StateDescriptor
s_stateDescriptor.insert("stateTypeId", basicTypeToString(Uuid)); s_stateDescriptor.insert("o:stateTypeId", basicTypeToString(Uuid));
s_stateDescriptor.insert("deviceId", 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("value", basicTypeToString(Variant));
s_stateDescriptor.insert("operator", valueOperatorRef()); s_stateDescriptor.insert("operator", valueOperatorRef());
@ -576,7 +578,11 @@ QVariantMap JsonTypes::packRuleAction(const RuleAction &ruleAction)
QVariantMap JsonTypes::packRuleActionParam(const RuleActionParam &ruleActionParam) QVariantMap JsonTypes::packRuleActionParam(const RuleActionParam &ruleActionParam)
{ {
QVariantMap variantMap; 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 this ruleaction param has a valid EventTypeId, there is no value
if (ruleActionParam.eventTypeId() != EventTypeId()) { if (ruleActionParam.eventTypeId() != EventTypeId()) {
variantMap.insert("eventTypeId", ruleActionParam.eventTypeId()); variantMap.insert("eventTypeId", ruleActionParam.eventTypeId());
@ -632,8 +638,13 @@ QVariantMap JsonTypes::packStateType(const StateType &stateType)
QVariantMap JsonTypes::packStateDescriptor(const StateDescriptor &stateDescriptor) QVariantMap JsonTypes::packStateDescriptor(const StateDescriptor &stateDescriptor)
{ {
QVariantMap variantMap; QVariantMap variantMap;
variantMap.insert("stateTypeId", stateDescriptor.stateTypeId().toString()); if (stateDescriptor.type() == StateDescriptor::TypeDevice) {
variantMap.insert("deviceId", stateDescriptor.deviceId().toString()); 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("value", stateDescriptor.stateValue());
variantMap.insert("operator", s_valueOperator.at(stateDescriptor.operatorType())); variantMap.insert("operator", s_valueOperator.at(stateDescriptor.operatorType()));
return variantMap; return variantMap;
@ -1435,9 +1446,15 @@ StateDescriptor JsonTypes::unpackStateDescriptor(const QVariantMap &stateDescrip
{ {
StateTypeId stateTypeId(stateDescriptorMap.value("stateTypeId").toString()); StateTypeId stateTypeId(stateDescriptorMap.value("stateTypeId").toString());
DeviceId deviceId(stateDescriptorMap.value("deviceId").toString()); DeviceId deviceId(stateDescriptorMap.value("deviceId").toString());
QString interface(stateDescriptorMap.value("interface").toString());
QString interfaceState(stateDescriptorMap.value("interfaceState").toString());
QVariant value = stateDescriptorMap.value("value"); QVariant value = stateDescriptorMap.value("value");
Types::ValueOperator operatorType = (Types::ValueOperator)s_valueOperator.indexOf(stateDescriptorMap.value("operator").toString()); 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; 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) bool RuleEngine::containsState(const StateEvaluator &stateEvaluator, const Event &stateChangeEvent)
{ {
if (stateEvaluator.stateDescriptor().isValid() && stateEvaluator.stateDescriptor().stateTypeId().toString() == stateChangeEvent.eventTypeId().toString()) { if (stateEvaluator.stateDescriptor().isValid()) {
return true; 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()) { 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(); qCDebug(dcRuleEngineDebug()) << "StateEvaluator:" << this << "Evaluating: Operator type" << m_operatorType << "Valid descriptor:" << m_stateDescriptor.isValid() << "Childs:" << m_childEvaluators.count();
bool descriptorMatching = true; bool descriptorMatching = true;
if (m_stateDescriptor.isValid()) { if (m_stateDescriptor.isValid()) {
Device *device = NymeaCore::instance()->deviceManager()->findConfiguredDevice(m_stateDescriptor.deviceId()); descriptorMatching = false;
if (!device) { if (m_stateDescriptor.type() == StateDescriptor::TypeDevice) {
qCWarning(dcRuleEngine) << "StateEvaluator:" << this << "Device not existing!"; Device *device = NymeaCore::instance()->deviceManager()->findConfiguredDevice(m_stateDescriptor.deviceId());
descriptorMatching = false; if (device) {
} else { DeviceClass deviceClass = NymeaCore::instance()->deviceManager()->findDeviceClass(device->deviceClassId());
DeviceClass deviceClass = NymeaCore::instance()->deviceManager()->findDeviceClass(device->deviceClassId()); if (device->hasState(m_stateDescriptor.stateTypeId())) {
if (!device->hasState(m_stateDescriptor.stateTypeId())) { if (m_stateDescriptor == device->state(m_stateDescriptor.stateTypeId())) {
qCWarning(dcRuleEngine) << "StateEvaluator:" << this << "Device found, but it does not appear to have such a state!"; 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; descriptorMatching = true;
} }
if (m_stateDescriptor != device->state(m_stateDescriptor.stateTypeId())) { } else {
// state not matching qCWarning(dcRuleEngine) << "StateEvaluator:" << this << "Device found, but it does not appear to have such a state!";
descriptorMatching = false; }
} } else {
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()); 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.beginGroup("stateDescriptor");
settings.setValue("stateTypeId", m_stateDescriptor.stateTypeId().toString()); settings.setValue("stateTypeId", m_stateDescriptor.stateTypeId().toString());
settings.setValue("deviceId", m_stateDescriptor.deviceId().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("value", m_stateDescriptor.stateValue());
settings.setValue("operator", m_stateDescriptor.operatorType()); settings.setValue("operator", m_stateDescriptor.operatorType());
settings.endGroup(); settings.endGroup();
@ -218,8 +239,16 @@ StateEvaluator StateEvaluator::loadFromSettings(NymeaSettings &settings, const Q
StateTypeId stateTypeId(settings.value("stateTypeId").toString()); StateTypeId stateTypeId(settings.value("stateTypeId").toString());
DeviceId deviceId(settings.value("deviceId").toString()); DeviceId deviceId(settings.value("deviceId").toString());
QVariant stateValue = settings.value("value"); 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(); 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(); settings.endGroup();
StateEvaluator ret(stateDescriptor); StateEvaluator ret(stateDescriptor);
@ -238,51 +267,63 @@ StateEvaluator StateEvaluator::loadFromSettings(NymeaSettings &settings, const Q
bool StateEvaluator::isValid() const bool StateEvaluator::isValid() const
{ {
if (m_stateDescriptor.isValid()) { if (m_stateDescriptor.isValid()) {
Device *device = NymeaCore::instance()->deviceManager()->findConfiguredDevice(m_stateDescriptor.deviceId()); if (m_stateDescriptor.type() == StateDescriptor::TypeDevice) {
if (!device) { Device *device = NymeaCore::instance()->deviceManager()->findConfiguredDevice(m_stateDescriptor.deviceId());
qCWarning(dcRuleEngine) << "State evaluator device does not exist!"; if (!device) {
return false; qCWarning(dcRuleEngine) << "State evaluator device does not exist!";
} return false;
}
if (!device->hasState(m_stateDescriptor.stateTypeId())) { if (!device->hasState(m_stateDescriptor.stateTypeId())) {
qCWarning(dcRuleEngine) << "State evaluator device found, but it does not appear to have such a state!"; qCWarning(dcRuleEngine) << "State evaluator device found, but it does not appear to have such a state!";
return false; return false;
} }
DeviceClass deviceClass = NymeaCore::instance()->deviceManager()->findDeviceClass(device->deviceClassId()); DeviceClass deviceClass = NymeaCore::instance()->deviceManager()->findDeviceClass(device->deviceClassId());
foreach (const StateType &stateType, deviceClass.stateTypes()) { foreach (const StateType &stateType, deviceClass.stateTypes()) {
if (stateType.id() == m_stateDescriptor.stateTypeId()) { if (stateType.id() == m_stateDescriptor.stateTypeId()) {
if (!m_stateDescriptor.stateValue().canConvert(stateType.type())) { 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()); qCWarning(dcRuleEngine) << "Wrong state value for state descriptor" << m_stateDescriptor.stateTypeId() << " Got:" << m_stateDescriptor.stateValue() << " Expected:" << QVariant::typeToName(stateType.type());
return false; 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(", "); if (!m_stateDescriptor.stateValue().convert(stateType.type())) {
return false; 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 \ingroup rules
\inmodule libnymea \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 \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}.*/ /*! Returns the StateTypeId of this \l{State}.*/
StateTypeId StateDescriptor::stateTypeId() const StateTypeId StateDescriptor::stateTypeId() const
{ {
@ -66,6 +84,18 @@ DeviceId StateDescriptor::deviceId() const
return m_deviceId; 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}.*/ /*! Returns the Value of this \l{State}.*/
QVariant StateDescriptor::stateValue() const QVariant StateDescriptor::stateValue() const
{ {
@ -84,6 +114,8 @@ bool StateDescriptor::operator ==(const StateDescriptor &other) const
{ {
return m_stateTypeId == other.stateTypeId() && return m_stateTypeId == other.stateTypeId() &&
m_deviceId == other.deviceId() && m_deviceId == other.deviceId() &&
m_interface == other.interface() &&
m_interfaceState == other.interfaceState() &&
m_stateValue == other.stateValue() && m_stateValue == other.stateValue() &&
m_operatorType == other.operatorType(); m_operatorType == other.operatorType();
} }
@ -121,11 +153,11 @@ bool StateDescriptor::operator !=(const State &state) const
return !(operator==(state)); return !(operator==(state));
} }
/*! Returns the true if this \l{StateDescriptor} is valid. A \l{StateDescriptor} is valid /*! Returns the true if this \l{StateDescriptor} is valid. A valid \l{StateDescriptor} must
* if the DeviceId and the StateTypeId are set and the state value of this \l{StateDescriptor} is valid. * have a valid stateValue along with either a DeviceId/StateTypeId pair or an interface/interfaceState pair.
* \sa StateDescriptor(), deviceId(), stateValue() * \sa StateDescriptor(), deviceId(), stateValue()
*/ */
bool StateDescriptor::isValid() const 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 class LIBNYMEA_EXPORT StateDescriptor
{ {
public: public:
enum Type {
TypeDevice,
TypeInterface
};
StateDescriptor(); StateDescriptor();
StateDescriptor(const StateTypeId &stateTypeId, const DeviceId &deviceId, const QVariant &stateValue, Types::ValueOperator operatorType = Types::ValueOperatorEquals); 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; StateTypeId stateTypeId() const;
DeviceId deviceId() const; DeviceId deviceId() const;
QString interface() const;
QString interfaceState() const;
QVariant stateValue() const; QVariant stateValue() const;
Types::ValueOperator operatorType() const; Types::ValueOperator operatorType() const;
@ -55,6 +67,8 @@ public:
private: private:
StateTypeId m_stateTypeId; StateTypeId m_stateTypeId;
DeviceId m_deviceId; DeviceId m_deviceId;
QString m_interface;
QString m_interfaceState;
QVariant m_stateValue; QVariant m_stateValue;
Types::ValueOperator m_operatorType; Types::ValueOperator m_operatorType;
}; };

View File

@ -117,8 +117,8 @@ NymeaTestBase::NymeaTestBase(QObject *parent) :
{ {
qRegisterMetaType<QNetworkReply*>(); qRegisterMetaType<QNetworkReply*>();
qsrand(QDateTime::currentMSecsSinceEpoch()); qsrand(QDateTime::currentMSecsSinceEpoch());
m_mockDevice1Port = 1337 + (qrand() % 1000); m_mockDevice1Port = 1337 + (qrand() % 10000);
m_mockDevice2Port = 7331 + (qrand() % 1000); m_mockDevice2Port = 7331 + (qrand() % 10000);
// Important for settings // Important for settings
QCoreApplication::instance()->setOrganizationName("nymea-test"); QCoreApplication::instance()->setOrganizationName("nymea-test");

View File

@ -107,7 +107,9 @@ private slots:
void testRuleActionPAramsFromEventParameter_data(); void testRuleActionPAramsFromEventParameter_data();
void testRuleActionPAramsFromEventParameter(); void testRuleActionPAramsFromEventParameter();
void testInterfaceBasedRule(); void testInterfaceBasedEventRule();
void testInterfaceBasedStateRule();
void testHousekeeping_data(); void testHousekeeping_data();
void testHousekeeping(); 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("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 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 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() void TestRules::addRemoveRules()
@ -881,7 +882,7 @@ void TestRules::editRules()
response = injectAndWait("Rules.EditRule", params); response = injectAndWait("Rules.EditRule", params);
verifyRuleError(response, error); verifyRuleError(response, error);
if (error == RuleEngine::RuleErrorNoError){ if (error == RuleEngine::RuleErrorNoError){
clientSpy.wait(500); clientSpy.wait(1);
// We need to get exactly 2 replies. The actual reply and the Changed notification // 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) // Make sure there are no other notifications (e.g. RuleAdded or similar)
QCOMPARE(clientSpy.count(), 2); QCOMPARE(clientSpy.count(), 2);
@ -1090,8 +1091,19 @@ void TestRules::loadStoreConfig()
stateEvaluator3.insert("stateDescriptor", stateDescriptor3); stateEvaluator3.insert("stateDescriptor", stateDescriptor3);
stateEvaluator3.insert("operator", JsonTypes::stateOperatorToString(Types::StateOperatorAnd)); 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(stateEvaluator2);
childEvaluators.append(stateEvaluator3); childEvaluators.append(stateEvaluator3);
childEvaluators.append(stateEvaluator4);
stateEvaluator1.insert("childEvaluators", childEvaluators); stateEvaluator1.insert("childEvaluators", childEvaluators);
stateEvaluator1.insert("operator", JsonTypes::stateOperatorToString(Types::StateOperatorAnd)); stateEvaluator1.insert("operator", JsonTypes::stateOperatorToString(Types::StateOperatorAnd));
@ -1178,7 +1190,7 @@ void TestRules::loadStoreConfig()
response = injectAndWait("Rules.GetRules"); response = injectAndWait("Rules.GetRules");
QVariantList rules = response.toMap().value("params").toMap().value("ruleDescriptions").toList(); QVariantList rules = response.toMap().value("params").toMap().value("ruleDescriptions").toList();
qDebug() << response; qDebug() << "GetRules before server shutdown:" << response;
restartServer(); restartServer();
@ -1219,19 +1231,26 @@ void TestRules::loadStoreConfig()
QVERIFY2(found, "missing eventdescriptor"); 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"); QVERIFY2(rule1.value("name").toString() == "TestRule", "Loaded wrong name for rule");
QVariantMap replyStateEvaluator= rule1.value("stateEvaluator").toMap(); QVariantMap replyStateEvaluator= rule1.value("stateEvaluator").toMap();
QVariantList replyChildEvaluators = replyStateEvaluator.value("childEvaluators").toList(); 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."); QVERIFY2(replyStateEvaluator.value("operator") == "StateOperatorAnd", "There should be the AND operator.");
foreach (const QVariant &childEvaluator, replyChildEvaluators) { foreach (const QVariant &childEvaluator, replyChildEvaluators) {
QVERIFY2(childEvaluator.toMap().contains("stateDescriptor"), "StateDescriptor missing in StateEvaluator"); QVERIFY2(childEvaluator.toMap().contains("stateDescriptor"), "StateDescriptor missing in StateEvaluator");
QVariantMap stateDescriptor = childEvaluator.toMap().value("stateDescriptor").toMap(); QVariantMap stateDescriptor = childEvaluator.toMap().value("stateDescriptor").toMap();
QVERIFY2(stateDescriptor.value("deviceId") == m_mockDeviceId, "DeviceId of stateDescriptor does not match"); if (stateDescriptor.contains("deviceId") && stateDescriptor.contains("stateTypeId")) {
QVERIFY2(stateDescriptor.value("stateTypeId") == mockIntStateId || stateDescriptor.value("stateTypeId") == mockBoolStateId, "StateTypeId of stateDescriptor doesn't match"); 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(); QVariantList replyActions = rule1.value("actions").toList();
@ -1265,13 +1284,20 @@ void TestRules::loadStoreConfig()
QVariantMap replyStateEvaluator2= rule2.value("stateEvaluator").toMap(); QVariantMap replyStateEvaluator2= rule2.value("stateEvaluator").toMap();
QVariantList replyChildEvaluators2 = replyStateEvaluator.value("childEvaluators").toList(); QVariantList replyChildEvaluators2 = replyStateEvaluator.value("childEvaluators").toList();
QVERIFY2(replyStateEvaluator2.value("operator") == "StateOperatorAnd", "There should be the AND operator."); 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) { foreach (const QVariant &childEvaluator, replyChildEvaluators2) {
QVERIFY2(childEvaluator.toMap().contains("stateDescriptor"), "StateDescriptor missing in StateEvaluator"); QVERIFY2(childEvaluator.toMap().contains("stateDescriptor"), "StateDescriptor missing in StateEvaluator");
QVariantMap stateDescriptor = childEvaluator.toMap().value("stateDescriptor").toMap(); QVariantMap stateDescriptor = childEvaluator.toMap().value("stateDescriptor").toMap();
QVERIFY2(stateDescriptor.value("deviceId") == m_mockDeviceId, "DeviceId of stateDescriptor does not match"); if (stateDescriptor.contains("deviceId") && stateDescriptor.contains("stateTypeId")) {
QVERIFY2(stateDescriptor.value("stateTypeId") == mockIntStateId || stateDescriptor.value("stateTypeId") == mockBoolStateId, "StateTypeId of stateDescriptor doesn't match"); 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(); QVariantList replyActions2 = rule2.value("actions").toList();
@ -1684,11 +1710,12 @@ void TestRules::testStateEvaluator3()
void TestRules::testChildEvaluator_data() void TestRules::testChildEvaluator_data()
{ {
cleanup(); cleanup();
enableNotifications();
DeviceId testDeviceId = addDisplayPinDevice(); DeviceId testDeviceId = addDisplayPinDevice();
QVERIFY2(!testDeviceId.isNull(), "Could not add push button device for child evaluators"); QVERIFY2(!testDeviceId.isNull(), "Could not add push button device for child evaluators");
enableNotifications();
// Create child evaluators // Create child evaluators
// Action // Action
QVariantMap action; QVariantMap action;
@ -2238,8 +2265,18 @@ void TestRules::testRuleActionPAramsFromEventParameter()
verifyRuleError(response, error); 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; QVariantMap powerAction;
powerAction.insert("interface", "light"); powerAction.insert("interface", "light");
powerAction.insert("interfaceAction", "power"); 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("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("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 // 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; QNetworkAccessManager nam;
QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*))); QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*)));
// state battery critical state to true // state battery critical state to false initially
qDebug() << "setting battery critical state to true"; QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(m_mockDevice1Port).arg(mockBatteryCriticalStateId.toString()).arg(false)));
QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(m_mockDevice1Port).arg(mockBatteryCriticalStateId.toString()).arg(true)));
QNetworkReply *reply = nam.get(request); QNetworkReply *reply = nam.get(request);
spy.wait(); spy.wait();
QCOMPARE(spy.count(), 1); QCOMPARE(spy.count(), 1);
reply->deleteLater(); 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); verifyRuleExecuted(mockActionIdPower);
} }