/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2015 Simon Stürz * * Copyright (C) 2014 Michael Zanetti * * * * This file is part of nymea. * * * * nymea is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, version 2 of the License. * * * * nymea 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 nymea. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /*! \class nymeaserver::StateEvaluator \brief This class helps to evaluate a \l{State} and . \ingroup rules \inmodule core The \l StateEvaluator class helps to evaluate a \l StateDescriptor and check if all \l {State}{States} from the given \l StateDescriptor are valid. A \l StateDescriptor is valid if conditions of the \l StateDescriptor are true. \sa StateDescriptor, State, RuleEngine */ #include "stateevaluator.h" #include "nymeacore.h" #include "devicemanager.h" #include "loggingcategories.h" #include "nymeasettings.h" namespace nymeaserver { /*! Constructs a new StateEvaluator for the given \a stateDescriptor. */ StateEvaluator::StateEvaluator(const StateDescriptor &stateDescriptor): m_stateDescriptor(stateDescriptor), m_operatorType(Types::StateOperatorAnd) { } /*! Constructs a new StateEvaluator for the given \a childEvaluators and \a stateOperator. */ StateEvaluator::StateEvaluator(QList childEvaluators, Types::StateOperator stateOperator): m_stateDescriptor(), m_childEvaluators(childEvaluators), m_operatorType(stateOperator) { } /*! Returns the \l StateDescriptor of this \l StateEvaluator. */ StateDescriptor StateEvaluator::stateDescriptor() const { return m_stateDescriptor; } /*! Returns the list of child \l {StateEvaluator}{StateEvaluators} of this \l StateEvaluator. */ QList StateEvaluator::childEvaluators() const { return m_childEvaluators; } /*! Sets the list of child evaluators of this \l StateEvaluator to the given \a stateEvaluators.*/ void StateEvaluator::setChildEvaluators(const QList &stateEvaluators) { m_childEvaluators = stateEvaluators; } /*! Appends the given \a stateEvaluator to the child evaluators of this \l StateEvaluator. \sa childEvaluators() */ void StateEvaluator::appendEvaluator(const StateEvaluator &stateEvaluator) { m_childEvaluators.append(stateEvaluator); } /*! Returns the \l {Types::StateOperator}{StateOperator} for this \l StateEvaluator.*/ Types::StateOperator StateEvaluator::operatorType() const { return m_operatorType; } /*! Sets the \l {Types::StateOperator}{StateOperator} for this \l StateEvaluator to the given. * \a operatorType. This operator will be used to evaluate the child evaluator list.*/ void StateEvaluator::setOperatorType(Types::StateOperator operatorType) { m_operatorType = operatorType; } /*! Returns true, if all child evaluator conditions are true depending on the \l {Types::StateOperator}{StateOperator}.*/ 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::TypeDevice) { Device *device = NymeaCore::instance()->deviceManager()->findConfiguredDevice(m_stateDescriptor.deviceId()); if (device) { DeviceClass deviceClass = NymeaCore::instance()->deviceManager()->findDeviceClass(device->deviceClassId()); if (device->hasState(m_stateDescriptor.stateTypeId())) { if (m_stateDescriptor == device->state(m_stateDescriptor.stateTypeId())) { qCDebug(dcRuleEngineDebug()) << "StateEvaluator:" << this << "State" << device->name() << deviceClass.stateTypes().findById(m_stateDescriptor.stateTypeId()).name() << (descriptorMatching ? "is" : "not") << "matching:" << m_stateDescriptor.stateValue() << m_stateDescriptor.operatorType() << device->stateValue(m_stateDescriptor.stateTypeId()); descriptorMatching = true; } } else { qCWarning(dcRuleEngine) << "StateEvaluator:" << this << "Device found, but it does not appear to have such a state!"; } } else { qCWarning(dcRuleEngine) << "StateEvaluator:" << this << "Device not existing!"; } } else { // interface foreach (Device* device, NymeaCore::instance()->deviceManager()->configuredDevices()) { DeviceClass deviceClass = NymeaCore::instance()->deviceManager()->findDeviceClass(device->deviceClassId()); if (!deviceClass.isValid()) { qCWarning(dcRuleEngine()) << "Could not find DeviceClass for Device" << device->name() << device->id(); continue; } if (deviceClass.interfaces().contains(m_stateDescriptor.interface())) { StateType stateType = deviceClass.stateTypes().findByName(m_stateDescriptor.interfaceState()); State state = device->state(stateType.id()); // As the StateDescriptor can't compare on it's own against interfaces, generate custom one, matching the device StateDescriptor temporaryDescriptor(stateType.id(), device->id(), m_stateDescriptor.stateValue(), m_stateDescriptor.operatorType()); if (temporaryDescriptor == state) { descriptorMatching = true; break; } } } } } 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; } /*! Returns true if this \l StateEvaluator has a \l Device in it with the given \a deviceId. */ bool StateEvaluator::containsDevice(const DeviceId &deviceId) const { if (m_stateDescriptor.deviceId() == deviceId) return true; foreach (const StateEvaluator &childEvaluator, m_childEvaluators) { if (childEvaluator.containsDevice(deviceId)) { return true; } } return false; } /*! Removes a \l Device with the given \a deviceId from this \l StateEvaluator. */ void StateEvaluator::removeDevice(const DeviceId &deviceId) { if (m_stateDescriptor.deviceId() == deviceId) m_stateDescriptor = StateDescriptor(); for (int i = 0; i < m_childEvaluators.count(); i++) { m_childEvaluators[i].removeDevice(deviceId); } } /*! Returns a list of \l{DeviceId}{DeviceIds} of this StateEvaluator. */ QList StateEvaluator::containedDevices() const { QList ret; if (!m_stateDescriptor.deviceId().isNull()) { ret.append(m_stateDescriptor.deviceId()); } foreach (const StateEvaluator &childEvaluator, m_childEvaluators) { ret.append(childEvaluator.containedDevices()); } return ret; } /*! This method will be used to save this \l StateEvaluator to the given \a settings. The \a groupName will normally be the corresponding \l Rule. */ void StateEvaluator::dumpToSettings(NymeaSettings &settings, const QString &groupName) const { settings.beginGroup(groupName); settings.beginGroup("stateDescriptor"); settings.setValue("stateTypeId", m_stateDescriptor.stateTypeId().toString()); settings.setValue("deviceId", m_stateDescriptor.deviceId().toString()); settings.setValue("interface", m_stateDescriptor.interface()); settings.setValue("interfaceState", m_stateDescriptor.interfaceState()); settings.setValue("value", m_stateDescriptor.stateValue()); settings.setValue("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(); } /*! This method will be used to load a \l StateEvaluator from the given \a settings. The \a groupName will be the corresponding \l RuleId. Returns the loaded \l StateEvaluator. */ StateEvaluator StateEvaluator::loadFromSettings(NymeaSettings &settings, const QString &groupName) { settings.beginGroup(groupName); settings.beginGroup("stateDescriptor"); StateTypeId stateTypeId(settings.value("stateTypeId").toString()); DeviceId deviceId(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 (!deviceId.isNull() && !stateTypeId.isNull()) { stateDescriptor = StateDescriptor(stateTypeId, deviceId, 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; } /*! Returns true, if all child evaluators are valid, the devices exist and all descriptors are in allowed paramerters.*/ bool StateEvaluator::isValid() const { if (m_stateDescriptor.isValid()) { if (m_stateDescriptor.type() == StateDescriptor::TypeDevice) { Device *device = NymeaCore::instance()->deviceManager()->findConfiguredDevice(m_stateDescriptor.deviceId()); if (!device) { qCWarning(dcRuleEngine) << "State evaluator device does not exist!"; return false; } if (!device->hasState(m_stateDescriptor.stateTypeId())) { qCWarning(dcRuleEngine) << "State evaluator device found, but it does not appear to have such a state!"; return false; } DeviceClass deviceClass = NymeaCore::instance()->deviceManager()->findDeviceClass(device->deviceClassId()); foreach (const StateType &stateType, deviceClass.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()->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; } } } 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; } /*! Returns true if the StateEvaluator is empty, that is, has no StateDescriptor and no ChildEvaluators */ bool StateEvaluator::isEmpty() const { return !m_stateDescriptor.isValid() && m_childEvaluators.isEmpty(); } /*! Print a StateEvaluator including childEvaluators recuresively to QDebug. */ 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; } }