mirror of https://github.com/nymea/nymea.git
377 lines
16 KiB
C++
377 lines
16 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"
|
|
|
|
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 = 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<ThingId> StateEvaluator::containedThings() const
|
|
{
|
|
QList<ThingId> 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<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>());
|
|
}
|
|
|
|
}
|