mirror of https://github.com/nymea/nymea.git
Merge PR #349: Add state based value comparison in rules
commit
ee911e2824
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -84,6 +84,9 @@ public:
|
|||
bool isValid() const;
|
||||
bool isEmpty() const;
|
||||
|
||||
private:
|
||||
bool evaluateDescriptor(const StateDescriptor &descriptor) const;
|
||||
|
||||
private:
|
||||
StateDescriptor m_stateDescriptor;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue