This repository has been archived on 2026-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
Simon Stürz 2acf7784a6 Update the entire Qt6 code to new signal slot connections
Update logging cathegories and allign coding style
2025-09-11 10:09:24 +02:00

550 lines
24 KiB
C++

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, GNU version 3. This project is distributed in the hope that it
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "stateevaluator.h"
#include "nymeacore.h"
#include "integrations/thingmanager.h"
#include "loggingcategories.h"
#include "nymeasettings.h"
#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
#include <QColor>
#endif
namespace nymeaserver {
StateEvaluator::StateEvaluator(const StateDescriptor &stateDescriptor):
m_stateDescriptor(stateDescriptor),
m_operatorType(Types::StateOperatorAnd)
{
}
StateEvaluator::StateEvaluator(QList<StateEvaluator> childEvaluators, Types::StateOperator stateOperator):
m_stateDescriptor(),
m_childEvaluators(childEvaluators),
m_operatorType(stateOperator)
{
}
StateDescriptor StateEvaluator::stateDescriptor() const
{
return m_stateDescriptor;
}
void StateEvaluator::setStateDescriptor(const StateDescriptor &stateDescriptor)
{
m_stateDescriptor = stateDescriptor;
}
StateEvaluators StateEvaluator::childEvaluators() const
{
return m_childEvaluators;
}
void StateEvaluator::setChildEvaluators(const StateEvaluators &stateEvaluators)
{
m_childEvaluators = stateEvaluators;
}
void StateEvaluator::appendEvaluator(const StateEvaluator &stateEvaluator)
{
m_childEvaluators.append(stateEvaluator);
}
Types::StateOperator StateEvaluator::operatorType() const
{
return m_operatorType;
}
void StateEvaluator::setOperatorType(Types::StateOperator operatorType)
{
m_operatorType = operatorType;
}
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 = evaluateDescriptor(m_stateDescriptor);
}
if (m_operatorType == Types::StateOperatorOr) {
if (m_stateDescriptor.isValid() && descriptorMatching) {
qCDebug(dcRuleEngineDebug()) << "StateEvaluator:" << this << "Descriptor is matching. Operator is OR => Evaluation result: true";
return true;
}
foreach (const StateEvaluator &stateEvaluator, m_childEvaluators) {
if (stateEvaluator.evaluate()) {
qCDebug(dcRuleEngineDebug()) << "StateEvaluator:" << this << "Child evaluator evaluated to true. Operator is OR => Evaluation result: true";
return true;
}
}
qCDebug(dcRuleEngineDebug()) << "StateEvaluator:" << this << "No child evaluator evaluated to true => Evaluation result: false";
return false;
}
if (!descriptorMatching) {
qCDebug(dcRuleEngineDebug()) << "StateEvaluator:" << this << "StateDescriptor not matching and operator is AND => Evaluation result: false";
return false;
}
foreach (const StateEvaluator &stateEvaluator, m_childEvaluators) {
if (!stateEvaluator.evaluate()) {
qCDebug(dcRuleEngineDebug()) << "StateEvaluator:" << this << "Child evaluator not matching => Evaluation result: false";
return false;
}
}
qCDebug(dcRuleEngineDebug()) << "StateEvaluator:" << this << "StateDescriptor and all child evaluators matching => Evaluation result: true";
return true;
}
bool StateEvaluator::containsThing(const ThingId &thingId) const
{
if (m_stateDescriptor.thingId() == thingId || m_stateDescriptor.valueThingId() == thingId)
return true;
foreach (const StateEvaluator &childEvaluator, m_childEvaluators) {
if (childEvaluator.containsThing(thingId)) {
return true;
}
}
return false;
}
void StateEvaluator::removeThing(const ThingId &thingId)
{
if (m_stateDescriptor.thingId() == thingId || m_stateDescriptor.valueThingId() == thingId)
m_stateDescriptor = StateDescriptor();
for (int i = 0; i < m_childEvaluators.count(); i++) {
m_childEvaluators[i].removeThing(thingId);
}
}
QList<ThingId> StateEvaluator::containedThings() const
{
QList<ThingId> ret;
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());
}
return ret;
}
void StateEvaluator::dumpToSettings(NymeaSettings &settings, const QString &groupName) const
{
settings.beginGroup(groupName);
settings.beginGroup("stateDescriptor");
settings.setValue("stateTypeId", m_stateDescriptor.stateTypeId().toString());
settings.setValue("thingId", m_stateDescriptor.thingId().toString());
settings.setValue("interface", m_stateDescriptor.interface());
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();
settings.setValue("operator", m_operatorType);
settings.beginGroup("childEvaluators");
for (int i = 0; i < m_childEvaluators.count(); i++) {
m_childEvaluators.at(i).dumpToSettings(settings, "stateEvaluator-" + QString::number(i));
}
settings.endGroup();
settings.endGroup();
}
StateEvaluator StateEvaluator::loadFromSettings(NymeaSettings &settings, const QString &groupName)
{
settings.beginGroup(groupName);
settings.beginGroup("stateDescriptor");
StateTypeId stateTypeId(settings.value("stateTypeId").toString());
ThingId thingId(settings.value("thingId").toString());
if (thingId.isNull()) { // Retry with deviceId for backwards compatibility (<0.19)
thingId = ThingId(settings.value("deviceId").toString());
}
QVariant stateValue = settings.value("value");
if (settings.contains("valueType")) {
QMetaType::Type valueType = (QMetaType::Type)settings.value("valueType").toInt();
// Note: only warn, and continue with the QVariant guessed type
if (valueType == QMetaType::UnknownType) {
qCWarning(dcRuleEngine()) << "Could not load the value type of the state evaluator. The value type will be guessed by QVariant" << stateValue;
} else if (!stateValue.canConvert(valueType)) {
qCWarning(dcRuleEngine()) << "Could not convert the state evaluator value" << stateValue << "to the stored type" << valueType << ". The value type will be guessed by QVariant.";
} else {
stateValue.convert(valueType);
}
}
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();
StateDescriptor stateDescriptor;
if (!thingId.isNull() && !stateTypeId.isNull()) {
stateDescriptor = StateDescriptor(stateTypeId, thingId, stateValue, valueOperator);
} else {
stateDescriptor = StateDescriptor(interface, interfaceState, stateValue, valueOperator);
}
stateDescriptor.setValueThingId(valueThingId);
stateDescriptor.setValueStateTypeId(valueStateTypeId);
settings.endGroup();
StateEvaluator ret(stateDescriptor);
ret.setOperatorType((Types::StateOperator)settings.value("operator").toInt());
settings.beginGroup("childEvaluators");
foreach (const QString &evaluatorGroup, settings.childGroups()) {
ret.appendEvaluator(StateEvaluator::loadFromSettings(settings, evaluatorGroup));
}
settings.endGroup();
settings.endGroup();
return ret;
}
bool StateEvaluator::isValid() const
{
if (m_stateDescriptor.isValid()) {
if (m_stateDescriptor.type() == StateDescriptor::TypeThing) {
Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(m_stateDescriptor.thingId());
if (!thing) {
qCWarning(dcRuleEngine) << "State evaluator thing does not exist!";
return false;
}
if (!thing->hasState(m_stateDescriptor.stateTypeId())) {
qCWarning(dcRuleEngine) << "State evaluator thing found, but it does not appear to have such a state!";
return false;
}
ThingClass thingClass = NymeaCore::instance()->thingManager()->findThingClass(thing->thingClassId());
foreach (const StateType &stateType, thingClass.stateTypes()) {
if (stateType.id() == m_stateDescriptor.stateTypeId()) {
// If comparing to a static value
if (!m_stateDescriptor.stateValue().isNull()) {
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 QT_VERSION >= QT_VERSION_CHECK(6,0,0)
QPartialOrdering ordering = QPartialOrdering::Unordered;
ordering = QVariant::compare(stateValue, stateType.maxValue());
if (stateType.maxValue().isValid() && ordering == QPartialOrdering::Greater) {
qCWarning(dcRuleEngine) << "Value out of range for state descriptor" << m_stateDescriptor.stateTypeId() << " Got:" << m_stateDescriptor.stateValue() << " Max:" << stateType.maxValue();
return false;
}
ordering = QVariant::compare(stateValue, stateType.minValue());
if (stateType.minValue().isValid() && ordering == QPartialOrdering::Less) {
qCWarning(dcRuleEngine) << "Value out of range for state descriptor" << m_stateDescriptor.stateTypeId() << " Got:" << m_stateDescriptor.stateValue() << " Min:" << stateType.minValue();
return false;
}
#else
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;
}
#endif
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;
}
}
}
} else { // TypeInterface
Interface iface = NymeaCore::instance()->thingManager()->supportedInterfaces().findByName(m_stateDescriptor.interface());
if (!iface.isValid()) {
qCWarning(dcRuleEngine()) << "No such interface:" << m_stateDescriptor.interface();
return false;
}
if (iface.stateTypes().findByName(m_stateDescriptor.interfaceState()).name().isEmpty()) {
qCWarning(dcRuleEngine()) << "Interface" << iface.name() << "has no such state:" << m_stateDescriptor.interfaceState();
return false;
}
}
}
if (m_operatorType == Types::StateOperatorOr) {
foreach (const StateEvaluator &stateEvaluator, m_childEvaluators) {
if (stateEvaluator.isValid()) {
return true;
}
}
return false;
}
foreach (const StateEvaluator &stateEvaluator, m_childEvaluators) {
if (!stateEvaluator.isValid()) {
return false;
}
}
return true;
}
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 stateValue = state.value();
QVariant descriptorValue = descriptor.stateValue();
#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
bool res = descriptorValue.convert(state.value().metaType());
if (!res) {
return false;
}
// FIXME: not sure if this is a bug in Qt, but compairing two equivalient QVariant containing QColor
// with QPartialOrdering::equivalent results in false. Until this has been fixed or the error has been found,
// we convert colors to strings and compare them
// Comparing QVariant(QColor, QColor(ARGB 1, 0, 0, 0)) to QVariant(QColor, QColor(ARGB 1, 0, 1, 0)) with operator Types::ValueOperatorEquals --> false
if (state.value().typeId() == QMetaType::QColor) {
QColor stateColor = stateValue.value<QColor>();
QColor desciptorColor = descriptorValue.value<QColor>();
QString sateColorName = stateColor.name();
QString desciptorColorName = desciptorColor.name();
stateValue = QVariant(sateColorName);
descriptorValue = QVariant(desciptorColorName);
}
QPartialOrdering ordering = QVariant::compare(stateValue, descriptorValue);
bool result = false;
switch (descriptor.operatorType()) {
case Types::ValueOperatorEquals:
result = (ordering == QPartialOrdering::equivalent);
break;
case Types::ValueOperatorGreater:
result = (ordering == QPartialOrdering::greater);
break;
case Types::ValueOperatorGreaterOrEqual:
result = (ordering == QPartialOrdering::greater || ordering == QPartialOrdering::equivalent);
break;
case Types::ValueOperatorLess:
result = (ordering == QPartialOrdering::less);
break;
case Types::ValueOperatorLessOrEqual:
result = (ordering == QPartialOrdering::less || ordering == QPartialOrdering::equivalent);
break;
case Types::ValueOperatorNotEquals:
result = (ordering != QPartialOrdering::equivalent);
break;
}
qCDebug(dcRuleEngineDebug()) << "Comparing" << stateValue << "to" << descriptorValue << "with operator" << descriptor.operatorType() << "-->" << result;
return result;
#else
bool res = descriptorValue.convert(state.value().type());
if (!res) {
return false;
}
switch (descriptor.operatorType()) {
case Types::ValueOperatorEquals:
return state.value() == descriptorValue;
case Types::ValueOperatorGreater:
return state.value() > descriptorValue;
case Types::ValueOperatorGreaterOrEqual:
return state.value() >= descriptorValue;
case Types::ValueOperatorLess:
return state.value() < descriptorValue;
case Types::ValueOperatorLessOrEqual:
return state.value() <= descriptorValue;
case Types::ValueOperatorNotEquals:
return state.value() != descriptorValue;
}
#endif
} 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();
#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
QPartialOrdering ordering = QVariant::compare(state.value(), valueState.value());
switch (descriptor.operatorType()) {
case Types::ValueOperatorEquals:
return ordering == QPartialOrdering::Equivalent;
case Types::ValueOperatorGreater:
return ordering == QPartialOrdering::Greater;
case Types::ValueOperatorGreaterOrEqual:
return ordering == QPartialOrdering::Greater || ordering == QPartialOrdering::Equivalent;
case Types::ValueOperatorLess:
return ordering == QPartialOrdering::Less;
case Types::ValueOperatorLessOrEqual:
return ordering == QPartialOrdering::Less || ordering == QPartialOrdering::Equivalent;
case Types::ValueOperatorNotEquals:
return ordering != QPartialOrdering::Equivalent;
}
#else
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();
}
#endif
}
} 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)
{
QDebugStateSaver saver(dbg);
dbg.nospace() << "StateEvaluator: Operator:" << stateEvaluator.operatorType() << '\n' << " " << stateEvaluator.stateDescriptor() << '\n';
for (int i = 0; i < stateEvaluator.childEvaluators().count(); i++) {
dbg.nospace() << " " << i << ": " << stateEvaluator.childEvaluators().at(i);
}
return dbg;
}
StateEvaluators::StateEvaluators()
{
}
StateEvaluators::StateEvaluators(const QList<StateEvaluator> &other): QList<StateEvaluator>(other)
{
}
QVariant StateEvaluators::get(int index) const
{
return QVariant::fromValue(at(index));
}
void StateEvaluators::put(const QVariant &variant)
{
append(variant.value<StateEvaluator>());
}
}