nymea/libnymea-core/ruleengine/stateevaluator.cpp

376 lines
17 KiB
C++

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stürz <simon.stuerz@guh.io> *
* Copyright (C) 2014 Michael Zanetti <michael_zanetti@gmx.net> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*!
\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 "devices/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<StateEvaluator> 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> 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<StateEvaluator> &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<DeviceId> StateEvaluator::containedDevices() const
{
QList<DeviceId> 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;
}
}