/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 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 . * * 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" namespace nymeaserver { StateEvaluator::StateEvaluator(const StateDescriptor &stateDescriptor): m_stateDescriptor(stateDescriptor), m_operatorType(Types::StateOperatorAnd) { } StateEvaluator::StateEvaluator(QList 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 = 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; } } } } } 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) 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 = StateDescriptor(); for (int i = 0; i < m_childEvaluators.count(); i++) { m_childEvaluators[i].removeThing(thingId); } } QList StateEvaluator::containedThings() const { QList ret; if (!m_stateDescriptor.thingId().isNull()) { ret.append(m_stateDescriptor.thingId()); } 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("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")) { QVariant::Type valueType = (QVariant::Type)settings.value("valueType").toInt(); // Note: only warn, and continue with the QVariant guessed type if (valueType == QVariant::Invalid) { 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); } } 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); } 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()) { 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; } 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; } } } } else { // TypeInterface Interface iface = NymeaCore::instance()->thingManager()->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; } } } 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(); } QDebug operator<<(QDebug dbg, const StateEvaluator &stateEvaluator) { dbg.nospace() << "StateEvaluator: Operator:" << stateEvaluator.operatorType() << endl << " " << stateEvaluator.stateDescriptor() << endl; for (int i = 0; i < stateEvaluator.childEvaluators().count(); i++) { dbg.nospace() << " " << i << ": " << stateEvaluator.childEvaluators().at(i); } return dbg; } StateEvaluators::StateEvaluators() { } StateEvaluators::StateEvaluators(const QList &other): QList(other) { } QVariant StateEvaluators::get(int index) const { return QVariant::fromValue(at(index)); } void StateEvaluators::put(const QVariant &variant) { append(variant.value()); } }