Refactor NymeaCore class

This moves all the things and rules logic away from NymeaCore
into their respective modules where it belongs.

One major change is the removal of the removePolicy functionality.
This was somewhat broken as it was only working for rules but not
for all the other modules like scripts, experiences etc. After
an attempt to create something that works with all modules it
really seemed that this does not make a lot of sence after all,
given that updating rules would in most cases leave something
very broken behind and removing them was the only sane thing to do.

On the other hand, experience plugins may not work well with such
a policy eithre as they may require to do their own special thing.

So in the end the removePolicy was dropped altogether. Apps should
instead figure out themselves what removal of a thing may imply and
inform the user about that beforehand.
This commit is contained in:
Michael Zanetti 2022-11-23 09:35:50 +01:00
parent d43b9dc737
commit 92197cb97c
23 changed files with 493 additions and 980 deletions

View File

@ -59,6 +59,7 @@
//#include "unistd.h"
#include "plugintimer.h"
#include "logging/logengine.h"
#include <QPluginLoader>
#include <QStaticPlugin>
@ -70,9 +71,10 @@
#include <QDir>
#include <QJsonDocument>
ThingManagerImplementation::ThingManagerImplementation(HardwareManager *hardwareManager, const QLocale &locale, QObject *parent) :
ThingManagerImplementation::ThingManagerImplementation(HardwareManager *hardwareManager, LogEngine *logEngine, const QLocale &locale, QObject *parent) :
ThingManager(parent),
m_hardwareManager(hardwareManager),
m_logEngine(logEngine),
m_locale(locale),
m_translator(new Translator(this))
{
@ -853,47 +855,66 @@ ThingSetupInfo* ThingManagerImplementation::addConfiguredThingInternal(const Thi
}
Thing::ThingError ThingManagerImplementation::removeConfiguredThing(const ThingId &thingId)
{
Thing *thing = m_configuredThings.value(thingId);
if (!thing) {
return Thing::ThingErrorThingNotFound;
}
if (!thing->parentId().isNull() && thing->autoCreated()) {
qCWarning(dcThingManager) << "Thing is an autocreated child of" << thing->parentId().toString() << ". Remove the parent instead.";
return Thing::ThingErrorThingIsChild;
}
removeConfiguredThingInternal(thing);
return Thing::ThingErrorNoError;
}
void ThingManagerImplementation::removeConfiguredThingInternal(Thing *thing)
{
// We're checking thingSetupStatus and abort any pending setup here. As setup finished()
// comes in as a QueuedConnection, make sure to process all events before going on so we
// don't end up aborting an already finished setup instead of calling thingRemoved() on it.
qApp->processEvents();
Thing *thing = m_configuredThings.take(thingId);
if (!thing) {
return Thing::ThingErrorThingNotFound;
}
IntegrationPlugin *plugin = m_integrationPlugins.value(thing->pluginId());
if (!plugin) {
qCWarning(dcThingManager()).nospace() << "Plugin not loaded for thing " << thing << ". Not calling thingRemoved on plugin.";
} else if (thing->setupStatus() == Thing::ThingSetupStatusInProgress) {
qCWarning(dcThingManager()).nospace() << "Thing " << thing << " is still being set up. Aborting setup.";
ThingSetupInfo *setupInfo = m_pendingSetups.value(thingId);
emit setupInfo->aborted();
} else if (thing->setupStatus() == Thing::ThingSetupStatusComplete) {
plugin->thingRemoved(thing);
}
Things toBeRemoved = findChilds(thing->id());
toBeRemoved.append(thing);
while (!toBeRemoved.isEmpty()) {
Thing *t = m_configuredThings.take(toBeRemoved.takeFirst()->id());
thing->deleteLater();
NymeaSettings settings(NymeaSettings::SettingsRoleThings);
settings.beginGroup("ThingConfig");
settings.beginGroup(thingId.toString());
settings.remove("");
settings.endGroup();
QFile::remove(statesCacheFile(thingId));
foreach (const IOConnectionId &ioConnectionId, m_ioConnections.keys()) {
IOConnection ioConnection = m_ioConnections.value(ioConnectionId);
if (ioConnection.inputThingId() == thing->id() || ioConnection.outputThingId() == thing->id()) {
disconnectIO(ioConnectionId);
IntegrationPlugin *plugin = m_integrationPlugins.value(t->pluginId());
if (!plugin) {
qCWarning(dcThingManager()).nospace() << "Plugin not loaded for thing " << t << ". Not calling thingRemoved on plugin.";
} else if (thing->setupStatus() == Thing::ThingSetupStatusInProgress) {
qCWarning(dcThingManager()).nospace() << "Thing " << thing << " is still being set up. Aborting setup.";
ThingSetupInfo *setupInfo = m_pendingSetups.value(t->id());
emit setupInfo->aborted();
} else if (thing->setupStatus() == Thing::ThingSetupStatusComplete) {
plugin->thingRemoved(t);
}
t->deleteLater();
NymeaSettings settings(NymeaSettings::SettingsRoleThings);
settings.beginGroup("ThingConfig");
settings.beginGroup(t->id().toString());
settings.remove("");
settings.endGroup();
QFile::remove(statesCacheFile(t->id()));
foreach (const IOConnectionId &ioConnectionId, m_ioConnections.keys()) {
IOConnection ioConnection = m_ioConnections.value(ioConnectionId);
if (ioConnection.inputThingId() == t->id() || ioConnection.outputThingId() == t->id()) {
disconnectIO(ioConnectionId);
}
}
m_logEngine->removeThingLogs(thing->id());
emit thingRemoved(t->id());
}
emit thingRemoved(thingId);
return Thing::ThingErrorNoError;
}
BrowseResult *ThingManagerImplementation::browseThing(const ThingId &thingId, const QString &itemId, const QLocale &locale)
@ -977,6 +998,9 @@ BrowserActionInfo* ThingManagerImplementation::executeBrowserItem(const BrowserA
Thing *thing = m_configuredThings.value(browserAction.thingId());
BrowserActionInfo *info = new BrowserActionInfo(thing, this, browserAction, this, 30000);
connect(info, &BrowserActionInfo::finished, info->thing(), [this, info](){
m_logEngine->logBrowserAction(info->browserAction(), info->status() == Thing::ThingErrorNoError ? Logging::LoggingLevelInfo : Logging::LoggingLevelAlert, info->status());
});
if (!thing) {
info->finish(Thing::ThingErrorThingNotFound);
@ -1009,6 +1033,9 @@ BrowserItemActionInfo* ThingManagerImplementation::executeBrowserItemAction(cons
Thing *thing = m_configuredThings.value(browserItemAction.thingId());
BrowserItemActionInfo *info = new BrowserItemActionInfo(thing, this, browserItemAction, this, 30000);
connect(info, &BrowserItemActionInfo::finished, info->thing(), [this, info](){
m_logEngine->logBrowserItemAction(info->browserItemAction(), info->status() == Thing::ThingErrorNoError ? Logging::LoggingLevelInfo : Logging::LoggingLevelAlert, info->status());
});
if (!thing) {
info->finish(Thing::ThingErrorThingNotFound);
@ -1359,6 +1386,7 @@ ThingActionInfo *ThingManagerImplementation::executeAction(const Action &action)
ThingActionInfo *info = new ThingActionInfo(thing, finalAction, this, 15000);
connect(info, &ThingActionInfo::finished, this, [=](){
m_logEngine->logAction(finalAction, info->status());
emit actionExecuted(action, info->status());
});
@ -1810,7 +1838,7 @@ void ThingManagerImplementation::onAutoThingDisappeared(const ThingId &thingId)
return;
}
emit thingDisappeared(thingId);
removeConfiguredThingInternal(thing);
}
void ThingManagerImplementation::onLoaded()
@ -1849,7 +1877,7 @@ void ThingManagerImplementation::onEventTriggered(Event event)
}
// configure logging
if (thing->loggedEventTypeIds().contains(event.eventTypeId())) {
event.setLogged(true);
m_logEngine->logEvent(event);
}
// Forward the event
@ -1867,6 +1895,10 @@ void ThingManagerImplementation::slotThingStateValueChanged(const StateTypeId &s
storeThingState(thing, stateTypeId);
}
if (thing->loggedStateTypeIds().contains(stateTypeId)) {
m_logEngine->logStateChange(thing, stateTypeId, value);
}
emit thingStateChanged(thing, stateTypeId, value, minValue, maxValue);
syncIOConnection(thing, stateTypeId);

View File

@ -61,6 +61,11 @@ class HardwareManager;
class Translator;
class ApiKeysProvidersLoader;
namespace nymeaserver {
class LogEngine;
}
using namespace nymeaserver;
class ThingManagerImplementation: public ThingManager
{
Q_OBJECT
@ -68,7 +73,7 @@ class ThingManagerImplementation: public ThingManager
friend class IntegrationPlugin;
public:
explicit ThingManagerImplementation(HardwareManager *hardwareManager, const QLocale &locale, QObject *parent = nullptr);
explicit ThingManagerImplementation(HardwareManager *hardwareManager, LogEngine *logEngine, const QLocale &locale, QObject *parent = nullptr);
~ThingManagerImplementation() override;
static QStringList pluginSearchDirs();
@ -131,9 +136,6 @@ public:
ThingClass translateThingClass(const ThingClass &thingClass, const QLocale &locale) override;
Vendor translateVendor(const Vendor &vendor, const QLocale &locale) override;
signals:
void loaded();
private slots:
void loadPlugins();
void loadPlugin(IntegrationPlugin *pluginIface);
@ -157,6 +159,7 @@ private:
ParamList buildParams(const ParamTypes &types, const ParamList &first, const ParamList &second = ParamList());
void pairThingInternal(ThingPairingInfo *info);
ThingSetupInfo *addConfiguredThingInternal(const ThingClassId &thingClassId, const QString &name, const ParamList &params, const ThingId &parentId = ThingId());
void removeConfiguredThingInternal(Thing *thing);
ThingSetupInfo *reconfigureThingInternal(Thing *thing, const ParamList &params, const QString &name = QString());
ThingSetupInfo *setupThing(Thing *thing);
void initThing(Thing *thing);
@ -176,6 +179,7 @@ private:
private:
HardwareManager *m_hardwareManager;
nymeaserver::LogEngine *m_logEngine;
QLocale m_locale;
Translator *m_translator = nullptr;

View File

@ -29,7 +29,6 @@
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "integrationshandler.h"
#include "nymeacore.h"
#include "integrations/thingmanager.h"
#include "integrations/thing.h"
#include "integrations/integrationplugin.h"
@ -43,9 +42,11 @@
#include "integrations/thingsetupinfo.h"
#include "integrations/browseresult.h"
#include "integrations/browseritemresult.h"
#include "ruleengine/ruleengine.h"
#include <QDebug>
#include <QJsonDocument>
#include <QCryptographicHash>
namespace nymeaserver {
@ -63,7 +64,6 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa
registerEnum<Types::InputType>();
registerEnum<Types::IOType>();
registerEnum<Types::StateValueFilter>();
registerEnum<RuleEngine::RemovePolicy>();
registerEnum<BrowserItem::BrowserIcon>();
registerEnum<MediaBrowserItem::MediaBrowserIcon>();
@ -268,17 +268,10 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa
registerMethod("SetStateFilter", description, params, returns, Types::PermissionScopeConfigureThings);
params.clear(); returns.clear();
description = "Remove a thing from the system.";
description = "Remove a thing and all its childs from the system. RemovePolicy is deprecated and has no effect any more.";
params.insert("thingId", enumValueName(Uuid));
params.insert("o:removePolicy", enumRef<RuleEngine::RemovePolicy>());
QVariantMap policy;
policy.insert("ruleId", enumValueName(Uuid));
policy.insert("policy", enumRef<RuleEngine::RemovePolicy>());
QVariantList removePolicyList;
removePolicyList.append(policy);
params.insert("o:removePolicyList", removePolicyList);
params.insert("d:o:removePolicy", enumValueName(String));
returns.insert("thingError", enumRef<Thing::ThingError>());
returns.insert("o:ruleIds", QVariantList() << enumValueName(Uuid));
registerMethod("RemoveThing", description, params, returns, Types::PermissionScopeConfigureThings);
params.clear(); returns.clear();
@ -443,7 +436,7 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa
description = "Emitted whenever an Event is triggered.";
params.insert("event", objectRef<Event>());
registerNotification("EventTriggered", description, params);
connect(NymeaCore::instance(), &NymeaCore::eventTriggered, this, [this](const Event &event){
connect(m_thingManager, &ThingManager::eventTriggered, this, [this](const Event &event){
QVariantMap params;
params.insert("event", pack(event));
emit EventTriggered(params);
@ -469,14 +462,14 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa
emit IOConnectionRemoved(params);
});
connect(NymeaCore::instance(), &NymeaCore::pluginConfigChanged, this, &IntegrationsHandler::pluginConfigChanged);
connect(NymeaCore::instance(), &NymeaCore::thingStateChanged, this, &IntegrationsHandler::thingStateChanged);
connect(NymeaCore::instance(), &NymeaCore::thingRemoved, this, &IntegrationsHandler::thingRemovedNotification);
connect(NymeaCore::instance(), &NymeaCore::thingAdded, this, &IntegrationsHandler::thingAddedNotification);
connect(NymeaCore::instance(), &NymeaCore::thingChanged, this, &IntegrationsHandler::thingChangedNotification);
connect(NymeaCore::instance(), &NymeaCore::thingSettingChanged, this, &IntegrationsHandler::thingSettingChangedNotification);
connect(m_thingManager, &ThingManager::pluginConfigChanged, this, &IntegrationsHandler::pluginConfigChanged);
connect(m_thingManager, &ThingManager::thingStateChanged, this, &IntegrationsHandler::thingStateChanged);
connect(m_thingManager, &ThingManager::thingRemoved, this, &IntegrationsHandler::thingRemovedNotification);
connect(m_thingManager, &ThingManager::thingAdded, this, &IntegrationsHandler::thingAddedNotification);
connect(m_thingManager, &ThingManager::thingChanged, this, &IntegrationsHandler::thingChangedNotification);
connect(m_thingManager, &ThingManager::thingSettingChanged, this, &IntegrationsHandler::thingSettingChangedNotification);
connect(NymeaCore::instance(), &NymeaCore::initialized, this, [=](){
connect(m_thingManager, &ThingManager::loaded, this, [=](){
// Generating cache hashes.
// NOTE: We need to sort the lists to get a stable result
QHash<ThingClassId, ThingClass> thingClassesMap;
@ -535,8 +528,8 @@ JsonReply* IntegrationsHandler::GetVendors(const QVariantMap &params, const Json
{
Q_UNUSED(params)
QVariantList vendors;
foreach (const Vendor &vendor, NymeaCore::instance()->thingManager()->supportedVendors()) {
Vendor translatedVendor = NymeaCore::instance()->thingManager()->translateVendor(vendor, context.locale());
foreach (const Vendor &vendor, m_thingManager->supportedVendors()) {
Vendor translatedVendor = m_thingManager->translateVendor(vendor, context.locale());
vendors.append(pack(translatedVendor));
}
@ -550,7 +543,7 @@ JsonReply* IntegrationsHandler::GetThingClasses(const QVariantMap &params, const
QVariantMap returns;
QVariantList thingClasses;
foreach (const ThingClass &thingClass, NymeaCore::instance()->thingManager()->supportedThings()) {
foreach (const ThingClass &thingClass, m_thingManager->supportedThings()) {
if (params.contains("vendorId") && thingClass.vendorId() != VendorId(params.value("vendorId").toUuid())) {
continue;
}
@ -567,7 +560,7 @@ JsonReply* IntegrationsHandler::GetThingClasses(const QVariantMap &params, const
}
}
ThingClass translatedThingClass = NymeaCore::instance()->thingManager()->translateThingClass(thingClass, context.locale());
ThingClass translatedThingClass = m_thingManager->translateThingClass(thingClass, context.locale());
thingClasses.append(pack(translatedThingClass));
}
@ -586,7 +579,7 @@ JsonReply *IntegrationsHandler::DiscoverThings(const QVariantMap &params, const
ParamList discoveryParams = unpack<ParamList>(params.value("discoveryParams"));
JsonReply *reply = createAsyncReply("DiscoverThings");
ThingDiscoveryInfo *info = NymeaCore::instance()->thingManager()->discoverThings(thingClassId, discoveryParams);
ThingDiscoveryInfo *info = m_thingManager->discoverThings(thingClassId, discoveryParams);
connect(info, &ThingDiscoveryInfo::finished, reply, [this, reply, info, locale](){
QVariantMap returns;
returns.insert("thingError", enumValueName<Thing::ThingError>(info->status()));
@ -614,9 +607,9 @@ JsonReply* IntegrationsHandler::GetPlugins(const QVariantMap &params, const Json
{
Q_UNUSED(params)
QVariantList plugins;
foreach (IntegrationPlugin* plugin, NymeaCore::instance()->thingManager()->plugins()) {
foreach (IntegrationPlugin* plugin, m_thingManager->plugins()) {
QVariantMap packedPlugin = pack(*plugin).toMap();
packedPlugin["displayName"] = NymeaCore::instance()->thingManager()->translate(plugin->pluginId(), plugin->pluginDisplayName(), context.locale());
packedPlugin["displayName"] = m_thingManager->translate(plugin->pluginId(), plugin->pluginDisplayName(), context.locale());
plugins.append(packedPlugin);
}
@ -629,7 +622,7 @@ JsonReply *IntegrationsHandler::GetPluginConfiguration(const QVariantMap &params
{
QVariantMap returns;
IntegrationPlugin *plugin = NymeaCore::instance()->thingManager()->plugins().findById(PluginId(params.value("pluginId").toString()));
IntegrationPlugin *plugin = m_thingManager->plugins().findById(PluginId(params.value("pluginId").toString()));
if (!plugin) {
returns.insert("thingError", enumValueName<Thing::ThingError>(Thing::ThingErrorPluginNotFound));
return createReply(returns);
@ -649,7 +642,7 @@ JsonReply* IntegrationsHandler::SetPluginConfiguration(const QVariantMap &params
QVariantMap returns;
PluginId pluginId = PluginId(params.value("pluginId").toString());
ParamList pluginParams = unpack<ParamList>(params.value("configuration"));
Thing::ThingError result = NymeaCore::instance()->thingManager()->setPluginConfig(pluginId, pluginParams);
Thing::ThingError result = m_thingManager->setPluginConfig(pluginId, pluginParams);
returns.insert("thingError",enumValueName<Thing::ThingError>(result));
return createReply(returns);
}
@ -674,10 +667,10 @@ JsonReply* IntegrationsHandler::AddThing(const QVariantMap &params, const JsonCo
jsonReply->finished();
return jsonReply;
}
info = NymeaCore::instance()->thingManager()->addConfiguredThing(thingClassId, thingParams, thingName);
info = m_thingManager->addConfiguredThing(thingClassId, thingParams, thingName);
} else {
info = NymeaCore::instance()->thingManager()->addConfiguredThing(thingDescriptorId, thingParams, thingName);
info = m_thingManager->addConfiguredThing(thingDescriptorId, thingParams, thingName);
}
connect(info, &ThingSetupInfo::finished, jsonReply, [info, jsonReply, locale](){
QVariantMap returns;
@ -706,24 +699,24 @@ JsonReply *IntegrationsHandler::PairThing(const QVariantMap &params, const JsonC
ThingPairingInfo *info;
if (params.contains("thingDescriptorId")) {
ThingDescriptorId thingDescriptorId = ThingDescriptorId(params.value("thingDescriptorId").toString());
info = NymeaCore::instance()->thingManager()->pairThing(thingDescriptorId, thingParams, thingName);
info = m_thingManager->pairThing(thingDescriptorId, thingParams, thingName);
} else if (params.contains("thingId")) {
ThingId thingId = ThingId(params.value("thingId").toString());
info = NymeaCore::instance()->thingManager()->pairThing(thingId, thingParams, thingName);
info = m_thingManager->pairThing(thingId, thingParams, thingName);
} else {
ThingClassId thingClassId(params.value("thingClassId").toString());
info = NymeaCore::instance()->thingManager()->pairThing(thingClassId, thingParams, thingName);
info = m_thingManager->pairThing(thingClassId, thingParams, thingName);
}
JsonReply *jsonReply = createAsyncReply("PairThing");
connect(info, &ThingPairingInfo::finished, jsonReply, [jsonReply, info, locale](){
connect(info, &ThingPairingInfo::finished, jsonReply, [jsonReply, info, locale, this](){
QVariantMap returns;
returns.insert("thingError", enumValueName<Thing::ThingError>(info->status()));
returns.insert("pairingTransactionId", info->transactionId().toString());
if (info->status() == Thing::ThingErrorNoError) {
ThingClass thingClass = NymeaCore::instance()->thingManager()->findThingClass(info->thingClassId());
ThingClass thingClass = m_thingManager->findThingClass(info->thingClassId());
returns.insert("setupMethod", enumValueName<ThingClass::SetupMethod>(thingClass.setupMethod()));
}
@ -751,7 +744,7 @@ JsonReply *IntegrationsHandler::ConfirmPairing(const QVariantMap &params)
JsonReply *jsonReply = createAsyncReply("ConfirmPairing");
ThingPairingInfo *info = NymeaCore::instance()->thingManager()->confirmPairing(pairingTransactionId, username, secret);
ThingPairingInfo *info = m_thingManager->confirmPairing(pairingTransactionId, username, secret);
connect(info, &ThingPairingInfo::finished, jsonReply, [info, jsonReply, locale](){
QVariantMap returns;
@ -774,22 +767,22 @@ JsonReply* IntegrationsHandler::GetThings(const QVariantMap &params, const JsonC
QVariantMap returns;
QVariantList things;
if (params.contains("thingId")) {
Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(ThingId(params.value("thingId").toString()));
Thing *thing = m_thingManager->findConfiguredThing(ThingId(params.value("thingId").toString()));
if (!thing) {
returns.insert("thingError", enumValueName<Thing::ThingError>(Thing::ThingErrorThingNotFound));
return createReply(returns);
} else {
QVariantMap packedThing = pack(thing).toMap();
QString translatedSetupStatus = NymeaCore::instance()->thingManager()->translate(thing->pluginId(), thing->setupDisplayMessage(), context.locale());
QString translatedSetupStatus = m_thingManager->translate(thing->pluginId(), thing->setupDisplayMessage(), context.locale());
if (!translatedSetupStatus.isEmpty()) {
packedThing["setupDisplayMessage"] = translatedSetupStatus;
}
things.append(packedThing);
}
} else {
foreach (Thing *thing, NymeaCore::instance()->thingManager()->configuredThings()) {
foreach (Thing *thing, m_thingManager->configuredThings()) {
QVariantMap packedThing = pack(thing).toMap();
QString translatedSetupStatus = NymeaCore::instance()->thingManager()->translate(thing->pluginId(), thing->setupDisplayMessage(), context.locale());
QString translatedSetupStatus = m_thingManager->translate(thing->pluginId(), thing->setupDisplayMessage(), context.locale());
if (!translatedSetupStatus.isEmpty()) {
packedThing["setupDisplayMessage"] = translatedSetupStatus;
}
@ -812,9 +805,9 @@ JsonReply *IntegrationsHandler::ReconfigureThing(const QVariantMap &params, cons
ThingSetupInfo *info;
if (!thingDescriptorId.isNull()) {
info = NymeaCore::instance()->thingManager()->reconfigureThing(thingDescriptorId, thingParams);
info = m_thingManager->reconfigureThing(thingDescriptorId, thingParams);
} else if (!thingId.isNull()){
info = NymeaCore::instance()->thingManager()->reconfigureThing(thingId, thingParams);
info = m_thingManager->reconfigureThing(thingId, thingParams);
} else {
qCWarning(dcJsonRpc()) << "Either thingId or thingDescriptorId are required";
QVariantMap ret;
@ -842,7 +835,7 @@ JsonReply *IntegrationsHandler::EditThing(const QVariantMap &params)
qCDebug(dcJsonRpc()) << "Edit thing" << thingId << name;
Thing::ThingError status = NymeaCore::instance()->thingManager()->editThing(thingId, name);
Thing::ThingError status = m_thingManager->editThing(thingId, name);
return createReply(statusToReply(status));
}
@ -851,33 +844,8 @@ JsonReply* IntegrationsHandler::RemoveThing(const QVariantMap &params)
{
QVariantMap returns;
ThingId thingId = ThingId(params.value("thingId").toString());
// global removePolicy has priority
if (params.contains("removePolicy")) {
RuleEngine::RemovePolicy removePolicy = params.value("removePolicy").toString() == "RemovePolicyCascade" ? RuleEngine::RemovePolicyCascade : RuleEngine::RemovePolicyUpdate;
Thing::ThingError status = NymeaCore::instance()->removeConfiguredThing(thingId, removePolicy);
returns.insert("thingError", enumValueName<Thing::ThingError>(status));
return createReply(returns);
}
QHash<RuleId, RuleEngine::RemovePolicy> removePolicyList;
foreach (const QVariant &variant, params.value("removePolicyList").toList()) {
RuleId ruleId = RuleId(variant.toMap().value("ruleId").toString());
RuleEngine::RemovePolicy policy = variant.toMap().value("policy").toString() == "RemovePolicyCascade" ? RuleEngine::RemovePolicyCascade : RuleEngine::RemovePolicyUpdate;
removePolicyList.insert(ruleId, policy);
}
QPair<Thing::ThingError, QList<RuleId> > status = NymeaCore::instance()->removeConfiguredThing(thingId, removePolicyList);
returns.insert("thingError", enumValueName<Thing::ThingError>(status.first));
if (!status.second.isEmpty()) {
QVariantList ruleIdList;
foreach (const RuleId &ruleId, status.second) {
ruleIdList.append(ruleId.toString());
}
returns.insert("ruleIds", ruleIdList);
}
Thing::ThingError status = m_thingManager->removeConfiguredThing(thingId);
returns.insert("thingError", enumValueName<Thing::ThingError>(status));
return createReply(returns);
}
@ -885,7 +853,7 @@ JsonReply *IntegrationsHandler::SetThingSettings(const QVariantMap &params)
{
ThingId thingId = ThingId(params.value("thingId").toString());
ParamList settings = unpack<ParamList>(params.value("settings"));
Thing::ThingError status = NymeaCore::instance()->thingManager()->setThingSettings(thingId, settings);
Thing::ThingError status = m_thingManager->setThingSettings(thingId, settings);
return createReply(statusToReply(status));
}
@ -894,7 +862,7 @@ JsonReply *IntegrationsHandler::SetStateLogging(const QVariantMap &params)
ThingId thingId = ThingId(params.value("thingId").toString());
StateTypeId stateTypeId = StateTypeId(params.value("stateTypeId").toUuid());
bool enabled = params.value("enabled").toBool();
Thing::ThingError status = NymeaCore::instance()->thingManager()->setStateLogging(thingId, stateTypeId, enabled);
Thing::ThingError status = m_thingManager->setStateLogging(thingId, stateTypeId, enabled);
return createReply(statusToReply(status));
}
@ -903,7 +871,7 @@ JsonReply *IntegrationsHandler::SetEventLogging(const QVariantMap &params)
ThingId thingId = ThingId(params.value("thingId").toString());
EventTypeId eventTypeId = EventTypeId(params.value("eventTypeId").toUuid());
bool enabled = params.value("enabled").toBool();
Thing::ThingError status = NymeaCore::instance()->thingManager()->setEventLogging(thingId, eventTypeId, enabled);
Thing::ThingError status = m_thingManager->setEventLogging(thingId, eventTypeId, enabled);
return createReply(statusToReply(status));
}
@ -914,14 +882,14 @@ JsonReply *IntegrationsHandler::SetStateFilter(const QVariantMap &params)
QString filterString = params.value("filter").toString();
QMetaEnum metaEnum = QMetaEnum::fromType<Types::StateValueFilter>();
Types::StateValueFilter filter = static_cast<Types::StateValueFilter>(metaEnum.keyToValue(filterString.toUtf8()));
Thing::ThingError status = NymeaCore::instance()->thingManager()->setStateFilter(thingId, stateTypeId, filter);
Thing::ThingError status = m_thingManager->setStateFilter(thingId, stateTypeId, filter);
return createReply(statusToReply(status));
}
JsonReply* IntegrationsHandler::GetEventTypes(const QVariantMap &params, const JsonContext &context) const
{
ThingClass thingClass = NymeaCore::instance()->thingManager()->findThingClass(ThingClassId(params.value("thingClassId").toString()));
ThingClass translatedThingClass = NymeaCore::instance()->thingManager()->translateThingClass(thingClass, context.locale());
ThingClass thingClass = m_thingManager->findThingClass(ThingClassId(params.value("thingClassId").toString()));
ThingClass translatedThingClass = m_thingManager->translateThingClass(thingClass, context.locale());
QVariantMap returns;
returns.insert("eventTypes", pack(translatedThingClass.eventTypes()));
@ -930,8 +898,8 @@ JsonReply* IntegrationsHandler::GetEventTypes(const QVariantMap &params, const J
JsonReply* IntegrationsHandler::GetActionTypes(const QVariantMap &params, const JsonContext &context) const
{
ThingClass thingClass = NymeaCore::instance()->thingManager()->findThingClass(ThingClassId(params.value("thingClassId").toString()));
ThingClass translatedThingClass = NymeaCore::instance()->thingManager()->translateThingClass(thingClass, context.locale());
ThingClass thingClass = m_thingManager->findThingClass(ThingClassId(params.value("thingClassId").toString()));
ThingClass translatedThingClass = m_thingManager->translateThingClass(thingClass, context.locale());
QVariantMap returns;
returns.insert("actionTypes", pack(translatedThingClass.actionTypes()));
@ -940,8 +908,8 @@ JsonReply* IntegrationsHandler::GetActionTypes(const QVariantMap &params, const
JsonReply* IntegrationsHandler::GetStateTypes(const QVariantMap &params, const JsonContext &context) const
{
ThingClass thingClass = NymeaCore::instance()->thingManager()->findThingClass(ThingClassId(params.value("thingClassId").toString()));
ThingClass translatedThingClass = NymeaCore::instance()->thingManager()->translateThingClass(thingClass, context.locale());
ThingClass thingClass = m_thingManager->findThingClass(ThingClassId(params.value("thingClassId").toString()));
ThingClass translatedThingClass = m_thingManager->translateThingClass(thingClass, context.locale());
QVariantMap returns;
returns.insert("stateTypes", pack(translatedThingClass.stateTypes()));
@ -950,7 +918,7 @@ JsonReply* IntegrationsHandler::GetStateTypes(const QVariantMap &params, const J
JsonReply* IntegrationsHandler::GetStateValue(const QVariantMap &params) const
{
Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(ThingId(params.value("thingId").toString()));
Thing *thing = m_thingManager->findConfiguredThing(ThingId(params.value("thingId").toString()));
if (!thing) {
return createReply(statusToReply(Thing::ThingErrorThingNotFound));
}
@ -966,7 +934,7 @@ JsonReply* IntegrationsHandler::GetStateValue(const QVariantMap &params) const
JsonReply *IntegrationsHandler::GetStateValues(const QVariantMap &params) const
{
Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(ThingId(params.value("thingId").toString()));
Thing *thing = m_thingManager->findConfiguredThing(ThingId(params.value("thingId").toString()));
if (!thing) {
return createReply(statusToReply(Thing::ThingErrorThingNotFound));
}
@ -983,7 +951,7 @@ JsonReply *IntegrationsHandler::BrowseThing(const QVariantMap &params, const Jso
JsonReply *jsonReply = createAsyncReply("BrowseThing");
BrowseResult *result = NymeaCore::instance()->thingManager()->browseThing(thingId, itemId, context.locale());
BrowseResult *result = m_thingManager->browseThing(thingId, itemId, context.locale());
connect(result, &BrowseResult::finished, jsonReply, [this, jsonReply, result, context](){
QVariantMap returns = statusToReply(result->status());
@ -1010,7 +978,7 @@ JsonReply *IntegrationsHandler::GetBrowserItem(const QVariantMap &params, const
JsonReply *jsonReply = createAsyncReply("GetBrowserItem");
BrowserItemResult *result = NymeaCore::instance()->thingManager()->browserItemDetails(thingId, itemId, context.locale());
BrowserItemResult *result = m_thingManager->browserItemDetails(thingId, itemId, context.locale());
connect(result, &BrowserItemResult::finished, jsonReply, [this, jsonReply, result, context](){
QVariantMap params = statusToReply(result->status());
if (result->status() == Thing::ThingErrorNoError) {
@ -1038,7 +1006,7 @@ JsonReply *IntegrationsHandler::ExecuteAction(const QVariantMap &params, const J
JsonReply *jsonReply = createAsyncReply("ExecuteAction");
ThingActionInfo *info = NymeaCore::instance()->thingManager()->executeAction(action);
ThingActionInfo *info = m_thingManager->executeAction(action);
connect(info, &ThingActionInfo::finished, jsonReply, [info, jsonReply, locale](){
QVariantMap data;
data.insert("thingError", enumValueName(info->status()));
@ -1060,7 +1028,7 @@ JsonReply *IntegrationsHandler::ExecuteBrowserItem(const QVariantMap &params, co
JsonReply *jsonReply = createAsyncReply("ExecuteBrowserItem");
BrowserActionInfo *info = NymeaCore::instance()->executeBrowserItem(action);
BrowserActionInfo *info = m_thingManager->executeBrowserItem(action);
connect(info, &BrowserActionInfo::finished, jsonReply, [info, jsonReply, context](){
QVariantMap data;
data.insert("thingError", enumValueName<Thing::ThingError>(info->status()));
@ -1084,7 +1052,7 @@ JsonReply *IntegrationsHandler::ExecuteBrowserItemAction(const QVariantMap &para
JsonReply *jsonReply = createAsyncReply("ExecuteBrowserItemAction");
BrowserItemActionInfo *info = NymeaCore::instance()->executeBrowserItemAction(browserItemAction);
BrowserItemActionInfo *info = m_thingManager->executeBrowserItemAction(browserItemAction);
connect(info, &BrowserItemActionInfo::finished, jsonReply, [info, jsonReply, context](){
QVariantMap data;
data.insert("thingError", enumValueName<Thing::ThingError>(info->status()));

View File

@ -525,7 +525,7 @@ void JsonRPCServerImplementation::setup()
{
registerHandler(this);
registerHandler(new IntegrationsHandler(NymeaCore::instance()->thingManager(), this));
registerHandler(new RulesHandler(this));
registerHandler(new RulesHandler(NymeaCore::instance()->ruleEngine(), this));
registerHandler(new LoggingHandler(this));
registerHandler(new ConfigurationHandler(this));
registerHandler(new NetworkManagerHandler(NymeaCore::instance()->networkManager(), this));

View File

@ -62,7 +62,6 @@
*/
#include "ruleshandler.h"
#include "nymeacore.h"
#include "ruleengine/ruleengine.h"
#include "loggingcategories.h"
@ -72,8 +71,9 @@
namespace nymeaserver {
/*! Constructs a new \l{RulesHandler} with the given \a parent. */
RulesHandler::RulesHandler(QObject *parent) :
JsonHandler(parent)
RulesHandler::RulesHandler(RuleEngine *ruleEngine, QObject *parent) :
JsonHandler(parent),
m_ruleEngine(ruleEngine)
{
// Enums
registerEnum<RuleEngine::RuleError>();
@ -218,10 +218,10 @@ RulesHandler::RulesHandler(QObject *parent) :
params.insert("rule", objectRef("Rule"));
registerNotification("RuleConfigurationChanged", description, params);
connect(NymeaCore::instance(), &NymeaCore::ruleAdded, this, &RulesHandler::ruleAddedNotification);
connect(NymeaCore::instance(), &NymeaCore::ruleRemoved, this, &RulesHandler::ruleRemovedNotification);
connect(NymeaCore::instance(), &NymeaCore::ruleActiveChanged, this, &RulesHandler::ruleActiveChangedNotification);
connect(NymeaCore::instance(), &NymeaCore::ruleConfigurationChanged, this, &RulesHandler::ruleConfigurationChangedNotification);
connect(m_ruleEngine, &RuleEngine::ruleAdded, this, &RulesHandler::ruleAddedNotification);
connect(m_ruleEngine, &RuleEngine::ruleRemoved, this, &RulesHandler::ruleRemovedNotification);
connect(m_ruleEngine, &RuleEngine::ruleActiveChanged, this, &RulesHandler::ruleActiveChangedNotification);
connect(m_ruleEngine, &RuleEngine::ruleConfigurationChanged, this, &RulesHandler::ruleConfigurationChangedNotification);
}
/*! Returns the name of the \l{RulesHandler}. In this case \b Rules.*/
@ -235,7 +235,7 @@ JsonReply* RulesHandler::GetRules(const QVariantMap &params)
Q_UNUSED(params)
QVariantList rulesList;
foreach (const Rule &rule, NymeaCore::instance()->ruleEngine()->rules()) {
foreach (const Rule &rule, m_ruleEngine->rules()) {
rulesList.append(packRuleDescription(rule));
}
@ -247,7 +247,7 @@ JsonReply* RulesHandler::GetRules(const QVariantMap &params)
JsonReply *RulesHandler::GetRuleDetails(const QVariantMap &params)
{
RuleId ruleId = RuleId(params.value("ruleId").toString());
Rule rule = NymeaCore::instance()->ruleEngine()->findRule(ruleId);
Rule rule = m_ruleEngine->findRule(ruleId);
if (rule.id().isNull()) {
QVariantMap data;
data.insert("ruleError", enumValueName<RuleEngine::RuleError>(RuleEngine::RuleErrorRuleNotFound));
@ -264,7 +264,7 @@ JsonReply* RulesHandler::AddRule(const QVariantMap &params)
Rule rule = unpack<Rule>(params);
rule.setId(RuleId::createRuleId());
RuleEngine::RuleError status = NymeaCore::instance()->ruleEngine()->addRule(rule);
RuleEngine::RuleError status = m_ruleEngine->addRule(rule);
QVariantMap returns;
if (status == RuleEngine::RuleErrorNoError) {
returns.insert("ruleId", rule.id().toString());
@ -280,10 +280,10 @@ JsonReply *RulesHandler::EditRule(const QVariantMap &params)
// FIXME: Edit rule API currently has "ruleId" while the Rule type has "id". Auto unpacking will fail for this property
rule.setId(params.value("ruleId").toUuid());
RuleEngine::RuleError status = NymeaCore::instance()->ruleEngine()->editRule(rule);
RuleEngine::RuleError status = m_ruleEngine->editRule(rule);
QVariantMap returns;
if (status == RuleEngine::RuleErrorNoError) {
returns.insert("rule", pack(NymeaCore::instance()->ruleEngine()->findRule(rule.id())));
returns.insert("rule", pack(m_ruleEngine->findRule(rule.id())));
}
returns.insert("ruleError", enumValueName<RuleEngine::RuleError>(status));
return createReply(returns);
@ -293,7 +293,7 @@ JsonReply* RulesHandler::RemoveRule(const QVariantMap &params)
{
QVariantMap returns;
RuleId ruleId(params.value("ruleId").toString());
RuleEngine::RuleError status = NymeaCore::instance()->removeRule(ruleId);
RuleEngine::RuleError status = m_ruleEngine->removeRule(ruleId);
returns.insert("ruleError", enumValueName<RuleEngine::RuleError>(status));
return createReply(returns);
}
@ -301,7 +301,7 @@ JsonReply* RulesHandler::RemoveRule(const QVariantMap &params)
JsonReply *RulesHandler::FindRules(const QVariantMap &params)
{
ThingId thingId = ThingId(params.value("thingId").toString());
QList<RuleId> rules = NymeaCore::instance()->ruleEngine()->findRules(thingId);
QList<RuleId> rules = m_ruleEngine->findRules(thingId);
QVariantList rulesList;
foreach (const RuleId &ruleId, rules) {
@ -315,7 +315,7 @@ JsonReply *RulesHandler::FindRules(const QVariantMap &params)
JsonReply *RulesHandler::EnableRule(const QVariantMap &params)
{
RuleEngine::RuleError status = NymeaCore::instance()->ruleEngine()->enableRule(RuleId(params.value("ruleId").toString()));
RuleEngine::RuleError status = m_ruleEngine->enableRule(RuleId(params.value("ruleId").toString()));
QVariantMap ret;
ret.insert("ruleError", enumValueName<RuleEngine::RuleError>(status));
return createReply(ret);
@ -323,7 +323,7 @@ JsonReply *RulesHandler::EnableRule(const QVariantMap &params)
JsonReply *RulesHandler::DisableRule(const QVariantMap &params)
{
RuleEngine::RuleError status = NymeaCore::instance()->ruleEngine()->disableRule(RuleId(params.value("ruleId").toString()));
RuleEngine::RuleError status = m_ruleEngine->disableRule(RuleId(params.value("ruleId").toString()));
QVariantMap ret;
ret.insert("ruleError", enumValueName<RuleEngine::RuleError>(status));
return createReply(ret);
@ -333,7 +333,7 @@ JsonReply *RulesHandler::ExecuteActions(const QVariantMap &params)
{
QVariantMap returns;
RuleId ruleId(params.value("ruleId").toString());
RuleEngine::RuleError status = NymeaCore::instance()->ruleEngine()->executeActions(ruleId);
RuleEngine::RuleError status = m_ruleEngine->executeActions(ruleId);
returns.insert("ruleError", enumValueName<RuleEngine::RuleError>(status));
return createReply(returns);
}
@ -342,7 +342,7 @@ JsonReply *RulesHandler::ExecuteExitActions(const QVariantMap &params)
{
QVariantMap returns;
RuleId ruleId(params.value("ruleId").toString());
RuleEngine::RuleError status = NymeaCore::instance()->ruleEngine()->executeExitActions(ruleId);
RuleEngine::RuleError status = m_ruleEngine->executeExitActions(ruleId);
returns.insert("ruleError", enumValueName<RuleEngine::RuleError>(status));
return createReply(returns);
}

View File

@ -37,11 +37,13 @@
namespace nymeaserver {
class RuleEngine;
class RulesHandler : public JsonHandler
{
Q_OBJECT
public:
explicit RulesHandler(QObject *parent = nullptr);
explicit RulesHandler(RuleEngine *ruleEngine, QObject *parent = nullptr);
QString name() const override;
@ -73,6 +75,9 @@ private slots:
private:
QVariantMap packRuleDescription(const Rule &rule);
private:
RuleEngine *m_ruleEngine = nullptr;
};
}

View File

@ -106,14 +106,6 @@ LogEngine::~LogEngine()
m_db.close();
}
void LogEngine::setThingManager(ThingManager *thingManager)
{
m_thingManager = thingManager;
connect(thingManager, &ThingManager::eventTriggered, this, &LogEngine::logEvent);
connect(thingManager, &ThingManager::thingStateChanged, this, &LogEngine::logStateChange);
connect(thingManager, &ThingManager::actionExecuted, this, &LogEngine::logAction);
}
LogEntriesFetchJob *LogEngine::fetchLogEntries(const LogFilter &filter)
{
QList<LogEntry> results;
@ -233,10 +225,6 @@ void LogEngine::logSystemEvent(const QDateTime &dateTime, bool active, Logging::
void LogEngine::logEvent(const Event &event)
{
if (!event.logged()) {
return;
}
QVariantList valueList;
foreach (const Param &param, event.params()) {
valueList << param.value();
@ -255,10 +243,6 @@ void LogEngine::logEvent(const Event &event)
void LogEngine::logStateChange(Thing *thing, const StateTypeId &stateTypeId, const QVariant &value)
{
if (!thing->loggedStateTypeIds().contains(stateTypeId)) {
return;
}
LogEntry entry(Logging::LoggingSourceStates);
entry.setTypeId(stateTypeId);
entry.setThingId(thing->id());

View File

@ -61,8 +61,6 @@ public:
LogEngine(const QString &driver, const QString &dbName, const QString &hostname = QString("127.0.0.1"), const QString &username = QString(), const QString &password = QString(), int maxDBSize = 50000, QObject *parent = nullptr);
~LogEngine();
void setThingManager(ThingManager *thingManager);
LogEntriesFetchJob *fetchLogEntries(const LogFilter &filter = LogFilter());
ThingsFetchJob *fetchThings();
@ -76,6 +74,9 @@ public:
public slots:
void logSystemEvent(const QDateTime &dateTime, bool active, Logging::LoggingLevel level = Logging::LoggingLevelInfo);
void logEvent(const Event &event);
void logStateChange(Thing *thing, const StateTypeId &stateTypeId, const QVariant &value);
void logAction(const Action &action, Thing::ThingError status);
void logBrowserAction(const BrowserAction &browserAction, Logging::LoggingLevel level = Logging::LoggingLevelInfo, int errorCode = 0);
void logBrowserItemAction(const BrowserItemAction &browserItemAction, Logging::LoggingLevel level = Logging::LoggingLevelInfo, int errorCode = 0);
void logRuleTriggered(const Rule &rule);
@ -84,11 +85,6 @@ public slots:
void logRuleActionsExecuted(const Rule &rule);
void logRuleExitActionsExecuted(const Rule &rule);
private slots:
void logEvent(const Event &event);
void logStateChange(Thing *thing, const StateTypeId &stateTypeId, const QVariant &value);
void logAction(const Action &action, Thing::ThingError status);
signals:
void logEntryAdded(const LogEntry &logEntry);
void logDatabaseUpdated();
@ -122,8 +118,6 @@ private:
bool m_initialized = false;
bool m_dbMalformed = false;
ThingManager *m_thingManager = nullptr;
// When maxQueueLength is exceeded, jobs will be flagged and discarded if this source logs more events
int m_maxQueueLength;
QHash<QString, QList<DatabaseJob*>> m_flaggedJobs;

View File

@ -121,15 +121,14 @@ void NymeaCore::init(const QStringList &additionalInterfaces) {
qCDebug(dcCore) << "Creating Hardware Manager";
m_hardwareManager = new HardwareManagerImplementation(m_platform, m_serverManager->mqttBroker(), m_zigbeeManager, m_zwaveManager, m_modbusRtuManager, this);
qCDebug(dcCore) << "Creating Thing Manager (locale:" << m_configuration->locale() << ")";
m_thingManager = new ThingManagerImplementation(m_hardwareManager, m_configuration->locale(), this);
qCDebug(dcCore) << "Creating Rule Engine";
m_ruleEngine = new RuleEngine(this);
qCDebug(dcCore) << "Creating Log Engine";
m_logger = new LogEngine(m_configuration->logDBDriver(), m_configuration->logDBName(), m_configuration->logDBHost(), m_configuration->logDBUser(), m_configuration->logDBPassword(), m_configuration->logDBMaxEntries(), this);
m_logger->setThingManager(m_thingManager);
qCDebug(dcCore) << "Creating Thing Manager (locale:" << m_configuration->locale() << ")";
m_thingManager = new ThingManagerImplementation(m_hardwareManager, m_logger, m_configuration->locale(), this);
qCDebug(dcCore) << "Creating Rule Engine";
m_ruleEngine = new RuleEngine(m_thingManager, m_timeManager, m_logger, this);
qCDebug(dcCore()) << "Creating Script Engine";
m_scriptEngine = new scriptengine::ScriptEngine(m_thingManager, this);
@ -151,22 +150,8 @@ void NymeaCore::init(const QStringList &additionalInterfaces) {
connect(m_configuration, &NymeaConfiguration::serverNameChanged, m_serverManager, &ServerManager::setServerName);
connect(m_thingManager, &ThingManagerImplementation::pluginConfigChanged, this, &NymeaCore::pluginConfigChanged);
connect(m_thingManager, &ThingManagerImplementation::eventTriggered, this, &NymeaCore::onEventTriggered);
connect(m_thingManager, &ThingManagerImplementation::thingStateChanged, this, &NymeaCore::onThingStateChanged);
connect(m_thingManager, &ThingManagerImplementation::thingAdded, this, &NymeaCore::thingAdded);
connect(m_thingManager, &ThingManagerImplementation::thingChanged, this, &NymeaCore::thingChanged);
connect(m_thingManager, &ThingManagerImplementation::thingSettingChanged, this, &NymeaCore::thingSettingChanged);
connect(m_thingManager, &ThingManagerImplementation::thingRemoved, this, &NymeaCore::thingRemoved);
connect(m_thingManager, &ThingManagerImplementation::thingDisappeared, this, &NymeaCore::onThingDisappeared);
connect(m_thingManager, &ThingManagerImplementation::loaded, this, &NymeaCore::thingManagerLoaded);
connect(m_ruleEngine, &RuleEngine::ruleAdded, this, &NymeaCore::ruleAdded);
connect(m_ruleEngine, &RuleEngine::ruleRemoved, this, &NymeaCore::ruleRemoved);
connect(m_ruleEngine, &RuleEngine::ruleConfigurationChanged, this, &NymeaCore::ruleConfigurationChanged);
connect(m_timeManager, &TimeManager::dateTimeChanged, this, &NymeaCore::onDateTimeChanged);
m_logger->logSystemEvent(m_timeManager->currentDateTime(), true);
}
@ -215,322 +200,6 @@ void NymeaCore::destroy()
s_instance = nullptr;
}
QPair<Thing::ThingError, QList<RuleId> > NymeaCore::removeConfiguredThing(const ThingId &thingId, const QHash<RuleId, RuleEngine::RemovePolicy> &removePolicyList)
{
Thing *thing = m_thingManager->findConfiguredThing(thingId);
if (!thing) {
return QPair<Thing::ThingError, QList<RuleId> > (Thing::ThingErrorThingNotFound, QList<RuleId>());
}
// Check if this is a child
if (!thing->parentId().isNull() && thing->autoCreated()) {
qCWarning(dcThingManager) << "Thing is an autocreated child of" << thing->parentId().toString() << ". Please remove the parent.";
return QPair<Thing::ThingError, QList<RuleId> > (Thing::ThingErrorThingIsChild, QList<RuleId>());
}
// FIXME: Let's remove this for now. It will come back with more fine grained control, presumably introducing a RemoveMethod flag in the DeviceClass
// if (thing->autoCreated()) {
// qCWarning(dcThingManager) << "This thing has been auto-created and cannot be deleted manually.";
// return QPair<Thing::ThingError, QList<RuleId> >(Thing::ThingErrorCreationMethodNotSupported, {});
// }
// Check if this thing has childs
QList<Thing *> thingsToRemove;
thingsToRemove.append(thing);
QList<Thing *> childs = m_thingManager->findChilds(thingId);
if (!childs.isEmpty()) {
foreach (Thing *child, childs) {
thingsToRemove.append(child);
}
}
// check things
QList<RuleId> offendingRules;
qCDebug(dcThingManager) << "Things to remove:";
foreach (Thing *d, thingsToRemove) {
qCDebug(dcThingManager) << " -> " << d->name() << d->id().toString();
// Check if thing is in a rule
foreach (const RuleId &ruleId, m_ruleEngine->findRules(d->id())) {
qCDebug(dcThingManager) << " -> in rule:" << ruleId.toString();
if (!offendingRules.contains(ruleId)) {
offendingRules.append(ruleId);
}
}
}
// check each offending rule if there is a corresponding remove policy
QHash<RuleId, RuleEngine::RemovePolicy> toBeChanged;
QList<RuleId> unhandledRules;
foreach (const RuleId &ruleId, offendingRules) {
bool found = false;
foreach (const RuleId &policyRuleId, removePolicyList.keys()) {
if (ruleId == policyRuleId) {
found = true;
toBeChanged.insert(ruleId, removePolicyList.value(ruleId));
break;
}
}
if (!found)
unhandledRules.append(ruleId);
}
if (!unhandledRules.isEmpty()) {
qCWarning(dcThingManager) << "There are unhandled rules which depend on this thing:\n" << unhandledRules;
return QPair<Thing::ThingError, QList<RuleId> > (Thing::ThingErrorThingInRule, unhandledRules);
}
// Update the rules...
foreach (const RuleId &ruleId, toBeChanged.keys()) {
if (toBeChanged.value(ruleId) == RuleEngine::RemovePolicyCascade) {
m_ruleEngine->removeRule(ruleId);
} else if (toBeChanged.value(ruleId) == RuleEngine::RemovePolicyUpdate){
foreach (Thing *thing, thingsToRemove) {
m_ruleEngine->removeThingFromRule(ruleId, thing->id());
}
}
}
// remove the childs
foreach (Thing *d, childs) {
Thing::ThingError removeError = m_thingManager->removeConfiguredThing(d->id());
if (removeError == Thing::ThingErrorNoError) {
m_logger->removeThingLogs(d->id());
}
}
// delete the things
Thing::ThingError removeError = m_thingManager->removeConfiguredThing(thingId);
if (removeError == Thing::ThingErrorNoError) {
m_logger->removeThingLogs(thingId);
}
return QPair<Thing::ThingError, QList<RuleId> > (Thing::ThingErrorNoError, QList<RuleId>());
}
Thing::ThingError NymeaCore::removeConfiguredThing(const ThingId &thingId, const RuleEngine::RemovePolicy &removePolicy)
{
Thing *thing = m_thingManager->findConfiguredThing(thingId);
if (!thing) {
return Thing::ThingErrorThingNotFound;
}
// Check if this is a child
if (!thing->parentId().isNull() && thing->autoCreated()) {
qCWarning(dcThingManager) << "Thing is an autocreated child of" << thing->parentId().toString() << ". Please remove the parent.";
return Thing::ThingErrorThingIsChild;
}
// FIXME: Let's remove this for now. It will come back with more fine grained control, presumably introducing a RemoveMethod flag in the DeviceClass
// if (thing->autoCreated()) {
// qCWarning(dcThingManager) << "This thing has been auto-created and cannot be deleted manually.";
// return Thing::ThingErrorCreationMethodNotSupported;
// }
// Check if this thing has childs
QList<Thing *> thingsToRemove;
thingsToRemove.append(thing);
QList<Thing *> childs = m_thingManager->findChilds(thingId);
if (!childs.isEmpty()) {
foreach (Thing *child, childs) {
thingsToRemove.append(child);
}
}
// check things
QList<RuleId> offendingRules;
qCDebug(dcThingManager) << "Things to remove:";
foreach (Thing *d, thingsToRemove) {
qCDebug(dcThingManager) << " -> " << d->name() << d->id().toString();
// Check if thing is in a rule
foreach (const RuleId &ruleId, m_ruleEngine->findRules(d->id())) {
qCDebug(dcThingManager) << " -> in rule:" << ruleId.toString();
if (!offendingRules.contains(ruleId)) {
offendingRules.append(ruleId);
}
}
}
// apply removepolicy for foreach rule
foreach (const RuleId &ruleId, offendingRules) {
if (removePolicy == RuleEngine::RemovePolicyCascade) {
m_ruleEngine->removeRule(ruleId);
} else if (removePolicy == RuleEngine::RemovePolicyUpdate){
foreach (Thing *thing, thingsToRemove) {
m_ruleEngine->removeThingFromRule(ruleId, thing->id());
}
}
}
// remove the childs
foreach (Thing *d, childs) {
Thing::ThingError removeError = m_thingManager->removeConfiguredThing(d->id());
if (removeError == Thing::ThingErrorNoError) {
m_logger->removeThingLogs(d->id());
}
}
// delete the things
Thing::ThingError removeError = m_thingManager->removeConfiguredThing(thingId);
if (removeError == Thing::ThingErrorNoError) {
m_logger->removeThingLogs(thingId);
}
return removeError;
}
BrowserActionInfo* NymeaCore::executeBrowserItem(const BrowserAction &browserAction)
{
BrowserActionInfo *info = m_thingManager->executeBrowserItem(browserAction);
connect(info, &BrowserActionInfo::finished, info->thing(), [this, info](){
m_logger->logBrowserAction(info->browserAction(), info->status() == Thing::ThingErrorNoError ? Logging::LoggingLevelInfo : Logging::LoggingLevelAlert, info->status());
});
return info;
}
BrowserItemActionInfo *NymeaCore::executeBrowserItemAction(const BrowserItemAction &browserItemAction)
{
BrowserItemActionInfo *info = m_thingManager->executeBrowserItemAction(browserItemAction);
connect(info, &BrowserItemActionInfo::finished, info->thing(), [this, info](){
m_logger->logBrowserItemAction(info->browserItemAction(), info->status() == Thing::ThingErrorNoError ? Logging::LoggingLevelInfo : Logging::LoggingLevelAlert, info->status());
});
return info;
}
/*! Execute the given \a ruleActions. */
void NymeaCore::executeRuleActions(const QList<RuleAction> ruleActions)
{
QList<Action> actions;
QList<BrowserAction> browserActions;
foreach (const RuleAction &ruleAction, ruleActions) {
if (ruleAction.type() == RuleAction::TypeThing) {
Thing *thing = m_thingManager->findConfiguredThing(ruleAction.thingId());
if (!thing) {
qCWarning(dcRuleEngine()) << "Unable to find thing" << ruleAction.thingId() << "for rule action" << ruleAction;
continue;
}
ActionTypeId actionTypeId = ruleAction.actionTypeId();
ParamList params;
bool ok = true;
foreach (const RuleActionParam &ruleActionParam, ruleAction.ruleActionParams()) {
if (ruleActionParam.isValueBased()) {
params.append(Param(ruleActionParam.paramTypeId(), ruleActionParam.value()));
} else if (ruleActionParam.isStateBased()) {
Thing *stateThing = m_thingManager->findConfiguredThing(ruleActionParam.stateThingId());
if (!stateThing) {
qCWarning(dcRuleEngine()) << "Cannot find thing" << ruleActionParam.stateThingId() << "required by rule action";
ok = false;
break;
}
ThingClass stateThingClass = m_thingManager->findThingClass(stateThing->thingClassId());
if (!stateThingClass.hasStateType(ruleActionParam.stateTypeId())) {
qCWarning(dcRuleEngine()) << "Device" << thing->name() << thing->id() << "does not have a state type" << ruleActionParam.stateTypeId();
ok = false;
break;
}
params.append(Param(ruleActionParam.paramTypeId(), stateThing->stateValue(ruleActionParam.stateTypeId())));
}
}
if (!ok) {
qCWarning(dcRuleEngine()) << "Not executing rule action";
continue;
}
Action action(actionTypeId, thing->id(), Action::TriggeredByRule);
action.setParams(params);
actions.append(action);
} else if (ruleAction.type() == RuleAction::TypeBrowser) {
Thing *thing = m_thingManager->findConfiguredThing(ruleAction.thingId());
if (!thing) {
qCWarning(dcRuleEngine()) << "Unable to find thing" << ruleAction.thingId() << "for rule action" << ruleAction;
continue;
}
BrowserAction browserAction(ruleAction.thingId(), ruleAction.browserItemId());
browserActions.append(browserAction);
} else {
Things things = m_thingManager->findConfiguredThings(ruleAction.interface());
foreach (Thing* thing, things) {
ThingClass thingClass = m_thingManager->findThingClass(thing->thingClassId());
ActionType actionType = thingClass.actionTypes().findByName(ruleAction.interfaceAction());
if (actionType.id().isNull()) {
qCWarning(dcRuleEngine()) << "Error creating Action. The given ThingClass does not implement action:" << ruleAction.interfaceAction();
continue;
}
ParamList params;
bool ok = true;
foreach (const RuleActionParam &ruleActionParam, ruleAction.ruleActionParams()) {
ParamType paramType = actionType.paramTypes().findByName(ruleActionParam.paramName());
if (paramType.id().isNull()) {
qCWarning(dcRuleEngine()) << "Error creating Action. The given ActionType does not have a parameter:" << ruleActionParam.paramName();
ok = false;
continue;
}
if (ruleActionParam.isValueBased()) {
params.append(Param(paramType.id(), ruleActionParam.value()));
} else if (ruleActionParam.isStateBased()) {
Thing *stateThing = m_thingManager->findConfiguredThing(ruleActionParam.stateThingId());
if (!stateThing) {
qCWarning(dcRuleEngine()) << "Cannot find thing" << ruleActionParam.stateThingId() << "required by rule action";
ok = false;
break;
}
ThingClass stateThingClass = m_thingManager->findThingClass(stateThing->thingClassId());
if (!stateThingClass.hasStateType(ruleActionParam.stateTypeId())) {
qCWarning(dcRuleEngine()) << "Thing" << thing->name() << thing->id() << "does not have a state type" << ruleActionParam.stateTypeId();
ok = false;
break;
}
params.append(Param(paramType.id(), stateThing->stateValue(ruleActionParam.stateTypeId())));
}
}
if (!ok) {
qCWarning(dcRuleEngine()) << "Not executing rule action";
continue;
}
Action action = Action(actionType.id(), thing->id(), Action::TriggeredByRule);
action.setParams(params);
actions.append(action);
}
}
}
foreach (const Action &action, actions) {
qCDebug(dcRuleEngine) << "Executing action" << action.actionTypeId() << action.params();
ThingActionInfo *info = m_thingManager->executeAction(action);
connect(info, &ThingActionInfo::finished, this, [info](){
if (info->status() != Thing::ThingErrorNoError) {
qCWarning(dcRuleEngine) << "Error executing action:" << info->status() << info->displayMessage();
}
});
}
foreach (const BrowserAction &browserAction, browserActions) {
BrowserActionInfo *info = executeBrowserItem(browserAction);
connect(info, &BrowserActionInfo::finished, this, [info](){
if (info->status() != Thing::ThingErrorNoError) {
qCWarning(dcRuleEngine) << "Error executing browser action:" << info->status();
}
});
}
}
/*! Calls the metheod RuleEngine::removeRule(\a id).
* \sa RuleEngine, */
RuleEngine::RuleError NymeaCore::removeRule(const RuleId &id)
{
RuleEngine::RuleError removeError = m_ruleEngine->removeRule(id);
if (removeError == RuleEngine::RuleErrorNoError)
m_logger->removeRuleLogs(id);
return removeError;
}
NymeaConfiguration *NymeaCore::configuration() const
{
return m_configuration;
@ -663,117 +332,6 @@ ExperienceManager *NymeaCore::experienceManager() const
return m_experienceManager;
}
void NymeaCore::onEventTriggered(const Event &event)
{
emit eventTriggered(event);
evaluateRules(event);
}
void NymeaCore::onThingStateChanged(Thing *thing, const StateTypeId &stateTypeId, const QVariant &value, const QVariant &minValue, const QVariant &maxValue)
{
emit thingStateChanged(thing, stateTypeId, value, minValue, maxValue);
// The rule engine can have event based rules that would trigger when a state changes
// without "binding" to the state (as a stateEvaluator would do). So generate a fake event
// for every state change.
// TODO: This whole rule engine related code in this file should probably move into the RuleEngine itself.
Param valueParam(ParamTypeId(stateTypeId.toString()), value);
Event event(EventTypeId(stateTypeId.toString()), thing->id(), ParamList() << valueParam);
evaluateRules(event);
}
void NymeaCore::evaluateRules(const Event &event)
{
QList<RuleAction> actions;
QList<RuleAction> eventBasedActions;
foreach (const Rule &rule, m_ruleEngine->evaluateEvent(event)) {
if (m_executingRules.contains(rule.id())) {
qCWarning(dcRuleEngine()) << "WARNING: Loop detected in rule execution for rule" << rule.id().toString() << rule.name();
break;
}
m_executingRules.append(rule.id());
// Event based
if (!rule.eventDescriptors().isEmpty()) {
m_logger->logRuleTriggered(rule);
QList<RuleAction> tmp;
if (rule.statesActive() && rule.timeActive()) {
qCDebug(dcRuleEngineDebug()) << "Executing actions";
tmp = rule.actions();
} else {
qCDebug(dcRuleEngineDebug()) << "Executing exitActions";
tmp = rule.exitActions();
}
// check if we have an event based action or a normal action
foreach (const RuleAction &action, tmp) {
if (action.isEventBased()) {
eventBasedActions.append(action);
} else {
actions.append(action);
}
}
} else {
// State based rule
m_logger->logRuleActiveChanged(rule);
emit ruleActiveChanged(rule);
if (rule.active()) {
actions.append(rule.actions());
} else {
actions.append(rule.exitActions());
}
}
}
// Set action params, depending on the event value
foreach (RuleAction ruleAction, eventBasedActions) {
RuleActionParams newParams;
foreach (RuleActionParam ruleActionParam, ruleAction.ruleActionParams()) {
// if this event param should be taken over in this action
if (event.eventTypeId() == ruleActionParam.eventTypeId()) {
QVariant eventValue = event.params().paramValue(ruleActionParam.eventParamTypeId());
// TODO: limits / scale calculation -> actionValue = eventValue * x
// something like a EventParamDescriptor
ruleActionParam.setValue(eventValue);
qCDebug(dcRuleEngine) << "Using param value from event:" << ruleActionParam.value();
}
newParams.append(ruleActionParam);
}
ruleAction.setRuleActionParams(newParams);
actions.append(ruleAction);
}
executeRuleActions(actions);
m_executingRules.clear();
}
void NymeaCore::onDateTimeChanged(const QDateTime &dateTime)
{
QList<RuleAction> actions;
foreach (const Rule &rule, m_ruleEngine->evaluateTime(dateTime)) {
// TimeEvent based
if (!rule.timeDescriptor().timeEventItems().isEmpty()) {
m_logger->logRuleTriggered(rule);
if (rule.statesActive() && rule.timeActive()) {
actions.append(rule.actions());
} else {
actions.append(rule.exitActions());
}
} else {
// Calendar based rule
m_logger->logRuleActiveChanged(rule);
emit ruleActiveChanged(rule);
if (rule.active()) {
actions.append(rule.actions());
} else {
actions.append(rule.exitActions());
}
}
}
executeRuleActions(actions);
}
LogEngine* NymeaCore::logEngine() const
{
return m_logger;
@ -784,65 +342,8 @@ JsonRPCServerImplementation *NymeaCore::jsonRPCServer() const
return m_serverManager->jsonServer();
}
void NymeaCore::onThingDisappeared(const ThingId &thingId)
{
Thing *thing = m_thingManager->findConfiguredThing(thingId);
if (!thing) {
return;
}
// Check if this thing has childs
Things thingsToRemove;
thingsToRemove.append(thing);
QList<Thing *> childs = m_thingManager->findChilds(thingId);
if (!childs.isEmpty()) {
foreach (Thing *child, childs) {
thingsToRemove.append(child);
}
}
// check things
QList<RuleId> offendingRules;
qCDebug(dcThingManager) << "Thing to remove:";
foreach (Thing *d, thingsToRemove) {
qCDebug(dcThingManager) << " -> " << d->name() << d->id().toString();
// Check if thing is in a rule
foreach (const RuleId &ruleId, m_ruleEngine->findRules(d->id())) {
qCDebug(dcThingManager) << " -> in rule:" << ruleId.toString();
if (!offendingRules.contains(ruleId)) {
offendingRules.append(ruleId);
}
}
}
// update involved rules
foreach (const RuleId &ruleId, offendingRules) {
foreach (Thing *thing, thingsToRemove) {
m_ruleEngine->removeThingFromRule(ruleId, thing->id());
}
}
// remove the child devices
foreach (Thing *d, childs) {
Thing::ThingError removeError = m_thingManager->removeConfiguredThing(d->id());
if (removeError == Thing::ThingErrorNoError) {
m_logger->removeThingLogs(d->id());
}
}
// delete the thing
Thing::ThingError removeError = m_thingManager->removeConfiguredThing(thingId);
if (removeError == Thing::ThingErrorNoError) {
m_logger->removeThingLogs(thingId);
}
}
void NymeaCore::thingManagerLoaded()
{
m_ruleEngine->init();
// Evaluate rules on current time
onDateTimeChanged(m_timeManager->currentDateTime());
// Tell hardare resources we're done with loading stuff...
m_hardwareManager->thingsLoaded();
@ -881,7 +382,8 @@ void NymeaCore::thingManagerLoaded()
qCDebug(dcCore()) << "Cleaning up stale thing tag" << tag.tagId();
m_tagsStorage->removeTag(tag);
}
}
}
}
}

View File

@ -87,15 +87,6 @@ public:
void init(const QStringList &additionalInterfaces = QStringList());
void destroy();
// Thing handling
QPair<Thing::ThingError, QList<RuleId> >removeConfiguredThing(const ThingId &thingId, const QHash<RuleId, RuleEngine::RemovePolicy> &removePolicyList);
Thing::ThingError removeConfiguredThing(const ThingId &thingId, const RuleEngine::RemovePolicy &removePolicy);
BrowserActionInfo* executeBrowserItem(const BrowserAction &browserAction);
BrowserItemActionInfo* executeBrowserItemAction(const BrowserItemAction &browserItemAction);
void executeRuleActions(const QList<RuleAction> ruleActions);
RuleEngine::RuleError removeRule(const RuleId &id);
NymeaConfiguration *configuration() const;
@ -125,19 +116,6 @@ public:
signals:
void initialized();
void pluginConfigChanged(const PluginId &id, const ParamList &config);
void eventTriggered(const Event &event);
void thingStateChanged(Thing *thing, const StateTypeId &stateTypeId, const QVariant &value, const QVariant &minValue, const QVariant &maxValue);
void thingRemoved(const ThingId &thingId);
void thingAdded(Thing *thing);
void thingChanged(Thing *thing);
void thingSettingChanged(const ThingId &thingId, const ParamTypeId &settingParamTypeId, const QVariant &value);
void ruleRemoved(const RuleId &ruleId);
void ruleAdded(const Rule &rule);
void ruleActiveChanged(const Rule &rule);
void ruleConfigurationChanged(const Rule &rule);
private:
explicit NymeaCore(QObject *parent = nullptr);
static NymeaCore *s_instance;
@ -165,14 +143,8 @@ private:
SerialPortMonitor *m_serialPortMonitor;
ModbusRtuManager *m_modbusRtuManager;
QList<RuleId> m_executingRules;
private slots:
void onEventTriggered(const Event &event);
void onThingStateChanged(Thing *thing, const StateTypeId &stateTypeId, const QVariant &value, const QVariant &minValue, const QVariant &maxValue);
void evaluateRules(const Event &event);
void onDateTimeChanged(const QDateTime &dateTime);
void onThingDisappeared(const ThingId &thingId);
void thingManagerLoaded();
};

View File

@ -207,19 +207,16 @@ bool Rule::isConsistent() const
{
// check if this rules is based on any event and contains exit actions
if (!eventDescriptors().isEmpty() && stateEvaluator().isEmpty() && timeDescriptor().calendarItems().isEmpty() && !exitActions().isEmpty()) {
qCWarning(dcRuleEngine) << "Rule not consistent. The exitActions will never be executed if the rule contains an eventDescriptor but no stateEvaluator or calendarItem.";
return false;
}
// check if this rules is based on any time events and contains exit actions
if (!timeDescriptor().timeEventItems().isEmpty() && stateEvaluator().isEmpty() && timeDescriptor().calendarItems().isEmpty() && !exitActions().isEmpty()) {
qCWarning(dcRuleEngine) << "Rule not consistent. The exitActions will never be executed if the rule contains a timeEvent but no stateEvaluator or calendarItem.";
return false;
}
// check if there are any actions
if (actions().isEmpty()) {
qCWarning(dcRuleEngine) << "Rule not consistent. A rule without actions has no effect.";
return false;
}

View File

@ -110,31 +110,58 @@
#include "ruleengine.h"
#include "nymeacore.h"
#include "loggingcategories.h"
#include "time/calendaritem.h"
#include "time/repeatingoption.h"
#include "time/timeeventitem.h"
#include "time/timemanager.h"
#include "types/eventdescriptor.h"
#include "types/paramdescriptor.h"
#include "nymeasettings.h"
#include "integrations/thingmanager.h"
#include "integrations/thing.h"
#include "logging/logengine.h"
#include <QDebug>
#include <QStringList>
#include <QStandardPaths>
#include <QCoreApplication>
NYMEA_LOGGING_CATEGORY(dcRuleEngine, "RuleEngine")
NYMEA_LOGGING_CATEGORY(dcRuleEngineDebug, "RuleEngineDebug")
namespace nymeaserver {
/*! Constructs the RuleEngine with the given \a parent. Although it wouldn't harm to have multiple RuleEngines, there is one
instance available from \l{NymeaCore}. This one should be used instead of creating multiple ones.
*/
RuleEngine::RuleEngine(QObject *parent) :
QObject(parent)
RuleEngine::RuleEngine(ThingManager *thingManager, TimeManager *timeManager, LogEngine *logEngine, QObject *parent) :
QObject(parent),
m_thingManager(thingManager),
m_timeManager(timeManager),
m_logEngine(logEngine)
{
connect(m_thingManager, &ThingManager::eventTriggered, this, &RuleEngine::onEventTriggered);
connect(m_thingManager, &ThingManager::thingStateChanged, this, [this](Thing *thing, const StateTypeId &stateTypeId, const QVariant &value, const QVariant &/*minValue*/, const QVariant &/*maxValue*/){
// There can be event based rules that would trigger when a state changes
// without "binding" to the state (as a stateEvaluator would do). So generate a fake event
// for every state change.
Param valueParam(ParamTypeId(stateTypeId.toString()), value);
Event event(EventTypeId(stateTypeId.toString()), thing->id(), ParamList() << valueParam);
onEventTriggered(event);
});
connect(m_thingManager, &ThingManager::thingRemoved, this, &RuleEngine::onThingRemoved);
connect(m_timeManager, &TimeManager::dateTimeChanged, this, &RuleEngine::onDateTimeChanged);
connect(m_thingManager, &ThingManager::loaded, this, [=](){
init();
onDateTimeChanged(m_timeManager->currentDateTime());
});
}
/*! Destructor of the \l{RuleEngine}. */
@ -150,7 +177,7 @@ RuleEngine::~RuleEngine()
*/
QList<Rule> RuleEngine::evaluateEvent(const Event &event)
{
Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(event.thingId());
Thing *thing = m_thingManager->findConfiguredThing(event.thingId());
if (!thing) {
qCWarning(dcRuleEngine()) << "Invalid event. ThingID does not reference a valid thing";
return QList<Rule>();
@ -307,7 +334,7 @@ RuleEngine::RuleError RuleEngine::addRule(const Rule &rule, bool fromEdit)
}
if (!rule.isConsistent()) {
qCWarning(dcRuleEngine) << "Rule inconsistent.";
qCWarning(dcRuleEngine) << "Invalid rule format. (Either missing actions, or exitActions without condition given.)";
return RuleErrorInvalidRuleFormat;
}
@ -319,14 +346,14 @@ RuleEngine::RuleError RuleEngine::addRule(const Rule &rule, bool fromEdit)
}
if (eventDescriptor.type() == EventDescriptor::TypeThing) {
// check thingId
Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(eventDescriptor.thingId());
Thing *thing = m_thingManager->findConfiguredThing(eventDescriptor.thingId());
if (!thing) {
qCWarning(dcRuleEngine) << "Cannot create rule. No configured thing for eventTypeId" << eventDescriptor.eventTypeId();
return RuleErrorThingNotFound;
}
// Check eventTypeId for this deivce
ThingClass thingClass = NymeaCore::instance()->thingManager()->findThingClass(thing->thingClassId());
ThingClass thingClass = m_thingManager->findThingClass(thing->thingClassId());
bool eventTypeFound = false;
foreach (const EventType &eventType, thingClass.eventTypes()) {
if (eventType.id() == eventDescriptor.eventTypeId()) {
@ -345,7 +372,7 @@ RuleEngine::RuleError RuleEngine::addRule(const Rule &rule, bool fromEdit)
}
} else {
// Interface based event
Interface iface = NymeaCore::instance()->thingManager()->supportedInterfaces().findByName(eventDescriptor.interface());
Interface iface = m_thingManager->supportedInterfaces().findByName(eventDescriptor.interface());
if (!iface.isValid()) {
qWarning(dcRuleEngine()) << "No such interface:" << eventDescriptor.interface();
return RuleErrorInterfaceNotFound;
@ -507,6 +534,8 @@ RuleEngine::RuleError RuleEngine::removeRule(const RuleId &ruleId, bool fromEdit
settings.remove("");
settings.endGroup();
m_logEngine->removeRuleLogs(ruleId);
if (!fromEdit)
emit ruleRemoved(ruleId);
@ -536,7 +565,7 @@ RuleEngine::RuleError RuleEngine::enableRule(const RuleId &ruleId)
saveRule(rule);
emit ruleConfigurationChanged(rule);
NymeaCore::instance()->logEngine()->logRuleEnabledChanged(rule, true);
m_logEngine->logRuleEnabledChanged(rule, true);
qCDebug(dcRuleEngine()) << "Rule" << rule.name() << rule.id().toString() << "enabled.";
return RuleErrorNoError;
@ -562,7 +591,7 @@ RuleEngine::RuleError RuleEngine::disableRule(const RuleId &ruleId)
saveRule(rule);
emit ruleConfigurationChanged(rule);
NymeaCore::instance()->logEngine()->logRuleEnabledChanged(rule, false);
m_logEngine->logRuleEnabledChanged(rule, false);
qCDebug(dcRuleEngine()) << "Rule" << rule.name() << rule.id().toString() << "disabled.";
return RuleErrorNoError;
}
@ -597,8 +626,8 @@ RuleEngine::RuleError RuleEngine::executeActions(const RuleId &ruleId)
}
qCDebug(dcRuleEngine) << "Executing rule actions of rule" << rule.name() << rule.id().toString();
NymeaCore::instance()->logEngine()->logRuleActionsExecuted(rule);
NymeaCore::instance()->executeRuleActions(rule.actions());
m_logEngine->logRuleActionsExecuted(rule);
executeRuleActions(rule.actions());
return RuleErrorNoError;
}
@ -629,8 +658,8 @@ RuleEngine::RuleError RuleEngine::executeExitActions(const RuleId &ruleId)
}
qCDebug(dcRuleEngine) << "Executing rule exit actions of rule" << rule.name() << rule.id().toString();
NymeaCore::instance()->logEngine()->logRuleExitActionsExecuted(rule);
NymeaCore::instance()->executeRuleActions(rule.exitActions());
m_logEngine->logRuleExitActionsExecuted(rule);
executeRuleActions(rule.exitActions());
return RuleErrorNoError;
}
@ -830,7 +859,7 @@ bool RuleEngine::containsEvent(const Rule &rule, const Event &event, const Thing
// If this is a interface based rule, the thing must implement the interface
if (eventDescriptor.type() == EventDescriptor::TypeInterface) {
ThingClass dc = NymeaCore::instance()->thingManager()->findThingClass(thingClassId);
ThingClass dc = m_thingManager->findThingClass(thingClassId);
if (!dc.interfaces().contains(eventDescriptor.interface())) {
// ThingClass for this event doesn't implement the interface for this eventDescriptor
continue;
@ -863,7 +892,7 @@ bool RuleEngine::containsEvent(const Rule &rule, const Event &event, const Thing
allOK = false;
continue;
}
ThingClass dc = NymeaCore::instance()->thingManager()->findThingClass(thingClassId);
ThingClass dc = m_thingManager->findThingClass(thingClassId);
EventType et = dc.eventTypes().findById(event.eventTypeId());
StateType st = dc.stateTypes().findById(event.eventTypeId());
if (et.isValid()) {
@ -928,8 +957,8 @@ bool RuleEngine::containsState(const StateEvaluator &stateEvaluator, const Event
return true;
}
} else {
Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(stateChangeEvent.thingId());
ThingClass thingClass = NymeaCore::instance()->thingManager()->findThingClass(thing->thingClassId());
Thing *thing = m_thingManager->findConfiguredThing(stateChangeEvent.thingId());
ThingClass thingClass = m_thingManager->findThingClass(thing->thingClassId());
if (thingClass.interfaces().contains(stateEvaluator.stateDescriptor().interface())) {
return true;
}
@ -954,13 +983,13 @@ RuleEngine::RuleError RuleEngine::checkRuleAction(const RuleAction &ruleAction,
ActionType actionType;
if (ruleAction.type() == RuleAction::TypeThing) {
Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(ruleAction.thingId());
Thing *thing = m_thingManager->findConfiguredThing(ruleAction.thingId());
if (!thing) {
qCWarning(dcRuleEngine) << "Cannot create rule. No configured thing with ID" << ruleAction.thingId();
return RuleErrorThingNotFound;
}
ThingClass thingClass = NymeaCore::instance()->thingManager()->findThingClass(thing->thingClassId());
ThingClass thingClass = m_thingManager->findThingClass(thing->thingClassId());
if (!thingClass.hasActionType(ruleAction.actionTypeId())) {
qCWarning(dcRuleEngine) << "Cannot create rule. Thing " + thing->name() + " has no action type:" << ruleAction.actionTypeId();
return RuleErrorActionTypeNotFound;
@ -968,7 +997,7 @@ RuleEngine::RuleError RuleEngine::checkRuleAction(const RuleAction &ruleAction,
actionType = thingClass.actionTypes().findById(ruleAction.actionTypeId());
} else if (ruleAction.type() == RuleAction::TypeInterface) {
Interface iface = NymeaCore::instance()->thingManager()->supportedInterfaces().findByName(ruleAction.interface());
Interface iface = m_thingManager->supportedInterfaces().findByName(ruleAction.interface());
if (!iface.isValid()) {
qCWarning(dcRuleEngine()) << "Cannot create rule. No such interface:" << ruleAction.interface();
return RuleError::RuleErrorInterfaceNotFound;
@ -979,7 +1008,7 @@ RuleEngine::RuleError RuleEngine::checkRuleAction(const RuleAction &ruleAction,
return RuleError::RuleErrorActionTypeNotFound;
}
} else if (ruleAction.type() == RuleAction::TypeBrowser) {
Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(ruleAction.thingId());
Thing *thing = m_thingManager->findConfiguredThing(ruleAction.thingId());
if (!thing) {
qCWarning(dcRuleEngine) << "Cannot create rule. No configured thing with ID" << ruleAction.thingId();
return RuleErrorThingNotFound;
@ -1057,12 +1086,12 @@ RuleEngine::RuleError RuleEngine::checkRuleActionParam(const RuleActionParam &ru
return RuleErrorTypesNotMatching;
}
} else if (ruleActionParam.isStateBased()) {
Thing *d = NymeaCore::instance()->thingManager()->findConfiguredThing(ruleActionParam.stateThingId());
Thing *d = m_thingManager->findConfiguredThing(ruleActionParam.stateThingId());
if (!d) {
qCWarning(dcRuleEngine()) << "Cannot create Rule. ThingId from RuleActionParam" << ruleActionParam.paramTypeId() << "not found in system.";
return RuleErrorThingNotFound;
}
ThingClass stateThingClass = NymeaCore::instance()->thingManager()->findThingClass(d->thingClassId());
ThingClass stateThingClass = m_thingManager->findThingClass(d->thingClassId());
StateType stateType = stateThingClass.stateTypes().findById(ruleActionParam.stateTypeId());
QVariant::Type actionParamType = getActionParamType(actionType.id(), ruleActionParam.paramTypeId());
QVariant v(stateType.type());
@ -1090,7 +1119,7 @@ RuleEngine::RuleError RuleEngine::checkRuleActionParam(const RuleActionParam &ru
QVariant::Type RuleEngine::getActionParamType(const ActionTypeId &actionTypeId, const ParamTypeId &paramTypeId)
{
foreach (const ThingClass &thingClass, NymeaCore::instance()->thingManager()->supportedThings()) {
foreach (const ThingClass &thingClass, m_thingManager->supportedThings()) {
foreach (const ActionType &actionType, thingClass.actionTypes()) {
if (actionType.id() == actionTypeId) {
foreach (const ParamType &paramType, actionType.paramTypes()) {
@ -1107,7 +1136,7 @@ QVariant::Type RuleEngine::getActionParamType(const ActionTypeId &actionTypeId,
QVariant::Type RuleEngine::getEventParamType(const EventTypeId &eventTypeId, const ParamTypeId &paramTypeId)
{
foreach (const ThingClass &thingClass, NymeaCore::instance()->thingManager()->supportedThings()) {
foreach (const ThingClass &thingClass, m_thingManager->supportedThings()) {
foreach (const EventType &eventType, thingClass.eventTypes()) {
if (eventType.id() == eventTypeId) {
foreach (const ParamType &paramType, eventType.paramTypes()) {
@ -1371,6 +1400,231 @@ QList<RuleAction> RuleEngine::loadRuleActions(NymeaSettings *settings)
return actions;
}
void RuleEngine::executeRuleActions(const QList<RuleAction> ruleActions)
{
QList<Action> actions;
QList<BrowserAction> browserActions;
foreach (const RuleAction &ruleAction, ruleActions) {
if (ruleAction.type() == RuleAction::TypeThing) {
Thing *thing = m_thingManager->findConfiguredThing(ruleAction.thingId());
if (!thing) {
qCWarning(dcRuleEngine()) << "Unable to find thing" << ruleAction.thingId() << "for rule action" << ruleAction;
continue;
}
ActionTypeId actionTypeId = ruleAction.actionTypeId();
ParamList params;
bool ok = true;
foreach (const RuleActionParam &ruleActionParam, ruleAction.ruleActionParams()) {
if (ruleActionParam.isValueBased()) {
params.append(Param(ruleActionParam.paramTypeId(), ruleActionParam.value()));
} else if (ruleActionParam.isStateBased()) {
Thing *stateThing = m_thingManager->findConfiguredThing(ruleActionParam.stateThingId());
if (!stateThing) {
qCWarning(dcRuleEngine()) << "Cannot find thing" << ruleActionParam.stateThingId() << "required by rule action";
ok = false;
break;
}
ThingClass stateThingClass = m_thingManager->findThingClass(stateThing->thingClassId());
if (!stateThingClass.hasStateType(ruleActionParam.stateTypeId())) {
qCWarning(dcRuleEngine()) << "Device" << thing->name() << thing->id() << "does not have a state type" << ruleActionParam.stateTypeId();
ok = false;
break;
}
params.append(Param(ruleActionParam.paramTypeId(), stateThing->stateValue(ruleActionParam.stateTypeId())));
}
}
if (!ok) {
qCWarning(dcRuleEngine()) << "Not executing rule action";
continue;
}
Action action(actionTypeId, thing->id(), Action::TriggeredByRule);
action.setParams(params);
actions.append(action);
} else if (ruleAction.type() == RuleAction::TypeBrowser) {
Thing *thing = m_thingManager->findConfiguredThing(ruleAction.thingId());
if (!thing) {
qCWarning(dcRuleEngine()) << "Unable to find thing" << ruleAction.thingId() << "for rule action" << ruleAction;
continue;
}
BrowserAction browserAction(ruleAction.thingId(), ruleAction.browserItemId());
browserActions.append(browserAction);
} else {
Things things = m_thingManager->findConfiguredThings(ruleAction.interface());
foreach (Thing* thing, things) {
ThingClass thingClass = m_thingManager->findThingClass(thing->thingClassId());
ActionType actionType = thingClass.actionTypes().findByName(ruleAction.interfaceAction());
if (actionType.id().isNull()) {
qCWarning(dcRuleEngine()) << "Error creating Action. The given ThingClass does not implement action:" << ruleAction.interfaceAction();
continue;
}
ParamList params;
bool ok = true;
foreach (const RuleActionParam &ruleActionParam, ruleAction.ruleActionParams()) {
ParamType paramType = actionType.paramTypes().findByName(ruleActionParam.paramName());
if (paramType.id().isNull()) {
qCWarning(dcRuleEngine()) << "Error creating Action. The given ActionType does not have a parameter:" << ruleActionParam.paramName();
ok = false;
continue;
}
if (ruleActionParam.isValueBased()) {
params.append(Param(paramType.id(), ruleActionParam.value()));
} else if (ruleActionParam.isStateBased()) {
Thing *stateThing = m_thingManager->findConfiguredThing(ruleActionParam.stateThingId());
if (!stateThing) {
qCWarning(dcRuleEngine()) << "Cannot find thing" << ruleActionParam.stateThingId() << "required by rule action";
ok = false;
break;
}
ThingClass stateThingClass = m_thingManager->findThingClass(stateThing->thingClassId());
if (!stateThingClass.hasStateType(ruleActionParam.stateTypeId())) {
qCWarning(dcRuleEngine()) << "Thing" << thing->name() << thing->id() << "does not have a state type" << ruleActionParam.stateTypeId();
ok = false;
break;
}
params.append(Param(paramType.id(), stateThing->stateValue(ruleActionParam.stateTypeId())));
}
}
if (!ok) {
qCWarning(dcRuleEngine()) << "Not executing rule action";
continue;
}
Action action = Action(actionType.id(), thing->id(), Action::TriggeredByRule);
action.setParams(params);
actions.append(action);
}
}
}
foreach (const Action &action, actions) {
qCDebug(dcRuleEngine) << "Executing action" << action.actionTypeId() << action.params();
ThingActionInfo *info = m_thingManager->executeAction(action);
connect(info, &ThingActionInfo::finished, this, [info](){
if (info->status() != Thing::ThingErrorNoError) {
qCWarning(dcRuleEngine) << "Error executing action:" << info->status() << info->displayMessage();
}
});
}
foreach (const BrowserAction &browserAction, browserActions) {
BrowserActionInfo *info = m_thingManager->executeBrowserItem(browserAction);
connect(info, &BrowserActionInfo::finished, this, [info, this](){
m_logEngine->logBrowserAction(info->browserAction(), info->status() == Thing::ThingErrorNoError ? Logging::LoggingLevelInfo : Logging::LoggingLevelAlert, info->status());
if (info->status() != Thing::ThingErrorNoError) {
qCWarning(dcRuleEngine) << "Error executing browser action:" << info->status();
}
});
}
}
void RuleEngine::onEventTriggered(const Event &event)
{
QList<RuleAction> actions;
QList<RuleAction> eventBasedActions;
foreach (const Rule &rule, evaluateEvent(event)) {
if (m_executingRules.contains(rule.id())) {
qCWarning(dcRuleEngine()) << "WARNING: Loop detected in rule execution for rule" << rule.id().toString() << rule.name();
break;
}
m_executingRules.append(rule.id());
// Event based
if (!rule.eventDescriptors().isEmpty()) {
m_logEngine->logRuleTriggered(rule);
QList<RuleAction> tmp;
if (rule.statesActive() && rule.timeActive()) {
qCDebug(dcRuleEngineDebug()) << "Executing actions";
tmp = rule.actions();
} else {
qCDebug(dcRuleEngineDebug()) << "Executing exitActions";
tmp = rule.exitActions();
}
// check if we have an event based action or a normal action
foreach (const RuleAction &action, tmp) {
if (action.isEventBased()) {
eventBasedActions.append(action);
} else {
actions.append(action);
}
}
} else {
// State based rule
m_logEngine->logRuleActiveChanged(rule);
emit ruleActiveChanged(rule);
if (rule.active()) {
actions.append(rule.actions());
} else {
actions.append(rule.exitActions());
}
}
}
// Set action params, depending on the event value
foreach (RuleAction ruleAction, eventBasedActions) {
RuleActionParams newParams;
foreach (RuleActionParam ruleActionParam, ruleAction.ruleActionParams()) {
// if this event param should be taken over in this action
if (event.eventTypeId() == ruleActionParam.eventTypeId()) {
QVariant eventValue = event.params().paramValue(ruleActionParam.eventParamTypeId());
// TODO: limits / scale calculation -> actionValue = eventValue * x
// something like a EventParamDescriptor
ruleActionParam.setValue(eventValue);
qCDebug(dcRuleEngine) << "Using param value from event:" << ruleActionParam.value();
}
newParams.append(ruleActionParam);
}
ruleAction.setRuleActionParams(newParams);
actions.append(ruleAction);
}
executeRuleActions(actions);
m_executingRules.clear();
}
void RuleEngine::onDateTimeChanged(const QDateTime &dateTime)
{
QList<RuleAction> actions;
foreach (const Rule &rule, evaluateTime(dateTime)) {
// TimeEvent based
if (!rule.timeDescriptor().timeEventItems().isEmpty()) {
m_logEngine->logRuleTriggered(rule);
if (rule.statesActive() && rule.timeActive()) {
actions.append(rule.actions());
} else {
actions.append(rule.exitActions());
}
} else {
// Calendar based rule
m_logEngine->logRuleActiveChanged(rule);
emit ruleActiveChanged(rule);
if (rule.active()) {
actions.append(rule.actions());
} else {
actions.append(rule.exitActions());
}
}
}
executeRuleActions(actions);
}
void RuleEngine::onThingRemoved(const ThingId &thingId)
{
QList<RuleId> affectedRules;
foreach (const RuleId &ruleId, findRules(thingId)) {
if (!affectedRules.contains(ruleId)) {
affectedRules.append(ruleId);
}
}
while (!affectedRules.isEmpty()) {
removeRule(affectedRules.takeFirst());
}
}
void RuleEngine::init()
{
NymeaSettings settings(NymeaSettings::SettingsRoleRules);

View File

@ -34,15 +34,24 @@
#include "rule.h"
#include "stateevaluator.h"
#include "types/event.h"
#include "types/thingclass.h"
#include "integrations/thingmanager.h"
#include <QObject>
#include <QList>
#include <QUuid>
#include <QSettings>
Q_DECLARE_LOGGING_CATEGORY(dcRuleEngine)
Q_DECLARE_LOGGING_CATEGORY(dcRuleEngineDebug)
class ThingManager;
namespace nymeaserver {
class LogEngine;
class TimeManager;
class RuleEngine : public QObject
{
Q_OBJECT
@ -72,18 +81,8 @@ public:
};
Q_ENUM(RuleError)
enum RemovePolicy {
RemovePolicyCascade,
RemovePolicyUpdate
};
Q_ENUM(RemovePolicy)
explicit RuleEngine(QObject *parent = nullptr);
explicit RuleEngine(ThingManager *thingManager, TimeManager *timeManager, LogEngine *logEngine, QObject *parent = nullptr);
~RuleEngine();
void init();
QList<Rule> evaluateEvent(const Event &event);
QList<Rule> evaluateTime(const QDateTime &dateTime);
RuleError addRule(const Rule &rule, bool fromEdit = false);
RuleError editRule(const Rule &rule);
@ -109,8 +108,18 @@ signals:
void ruleAdded(const Rule &rule);
void ruleRemoved(const RuleId &ruleId);
void ruleConfigurationChanged(const Rule &rule);
void ruleActiveChanged(const Rule &rule);
private slots:
void init();
void onEventTriggered(const Event &event);
void onDateTimeChanged(const QDateTime &dateTime);
void onThingRemoved(const ThingId &thingId);
private:
QList<Rule> evaluateEvent(const Event &event);
QList<Rule> evaluateTime(const QDateTime &dateTime);
private:
bool containsEvent(const Rule &rule, const Event &event, const ThingClassId &thingClassId);
bool containsState(const StateEvaluator &stateEvaluator, const Event &stateChangeEvent);
@ -125,12 +134,22 @@ private:
void saveRuleActions(NymeaSettings *settings, const QList<RuleAction> &ruleActions);
QList<RuleAction> loadRuleActions(NymeaSettings *settings);
void executeRuleActions(const QList<RuleAction> ruleActions);
private:
ThingManager *m_thingManager = nullptr;
TimeManager *m_timeManager = nullptr;
LogEngine *m_logEngine = nullptr;
QList<RuleId> m_ruleIds; // Keeping a list of RuleIds to keep sorting order...
QHash<RuleId, Rule> m_rules; // ...but use a Hash for faster finding
QList<RuleId> m_activeRules;
QDateTime m_lastEvaluationTime;
QList<RuleId> m_executingRules;
};
}

View File

@ -110,11 +110,11 @@ protected:
virtual IOConnectionResult connectIO(const IOConnection &connection) = 0;
signals:
void loaded();
void pluginConfigChanged(const PluginId &id, const ParamList &config);
void eventTriggered(const Event &event);
void thingStateChanged(Thing *thing, const StateTypeId &stateTypeId, const QVariant &value, const QVariant &minValue, const QVariant &maxValue);
void thingRemoved(const ThingId &thingId);
void thingDisappeared(const ThingId &thingId);
void thingAdded(Thing *thing);
void thingChanged(Thing *thing);
void thingSettingChanged(const ThingId &thingId, const ParamTypeId &settingParamTypeId, const QVariant &value);

View File

@ -48,8 +48,6 @@ NYMEA_LOGGING_CATEGORY(dcPlatformUpdate, "PlatformUpdate")
NYMEA_LOGGING_CATEGORY(dcPlatformZeroConf, "PlatformZeroConf")
NYMEA_LOGGING_CATEGORY(dcExperiences, "Experiences")
NYMEA_LOGGING_CATEGORY(dcTimeManager, "TimeManager")
NYMEA_LOGGING_CATEGORY(dcRuleEngine, "RuleEngine")
NYMEA_LOGGING_CATEGORY(dcRuleEngineDebug, "RuleEngineDebug")
NYMEA_LOGGING_CATEGORY(dcHardware, "Hardware")
NYMEA_LOGGING_CATEGORY(dcLogEngine, "LogEngine")
NYMEA_LOGGING_CATEGORY(dcServerManager, "ServerManager")

View File

@ -56,8 +56,6 @@ Q_DECLARE_LOGGING_CATEGORY(dcPlatformUpdate)
Q_DECLARE_LOGGING_CATEGORY(dcPlatformZeroConf)
Q_DECLARE_LOGGING_CATEGORY(dcExperiences)
Q_DECLARE_LOGGING_CATEGORY(dcTimeManager)
Q_DECLARE_LOGGING_CATEGORY(dcRuleEngine)
Q_DECLARE_LOGGING_CATEGORY(dcRuleEngineDebug)
Q_DECLARE_LOGGING_CATEGORY(dcHardware)
Q_DECLARE_LOGGING_CATEGORY(dcLogEngine)
Q_DECLARE_LOGGING_CATEGORY(dcServerManager)

View File

@ -154,7 +154,6 @@ bool RepeatingOption::isValid() const
// Validate weekdays range
foreach (const uint &weekDay, m_weekDays) {
if (weekDay <= 0 || weekDay > 7) {
qCWarning(dcRuleEngine()) << "Invalid week day value:" << weekDay << ". Value out of range [1,7].";
return false;
}
}
@ -162,7 +161,6 @@ bool RepeatingOption::isValid() const
// Validate monthdays range
foreach (const uint &monthDay, m_monthDays) {
if (monthDay <= 0 || monthDay > 31) {
qCWarning(dcRuleEngine()) << "Invalid month day value:" << monthDay << ". Value out of range [1,31].";
return false;
}
}

View File

@ -119,16 +119,6 @@ QVariant Event::paramValue(const ParamTypeId &paramTypeId) const
return QVariant();
}
bool Event::logged() const
{
return m_logged;
}
void Event::setLogged(bool logged)
{
m_logged = logged;
}
/*! Compare this Event to the Event given by \a other.
* Events are equal (returns true) if eventTypeId, deviceId and params match. */
bool Event::operator ==(const Event &other) const

View File

@ -62,15 +62,10 @@ public:
bool operator ==(const Event &other) const;
bool logged() const;
void setLogged(bool logged);
private:
EventTypeId m_eventTypeId;
ThingId m_thingId;
ParamList m_params;
bool m_logged = false;
};
Q_DECLARE_METATYPE(Event)
QDebug operator<<(QDebug dbg, const Event &event);

View File

@ -5,7 +5,7 @@ NYMEA_VERSION_STRING=$$system('dpkg-parsechangelog | sed -n -e "s/^Version: //p"
# define protocol versions
JSON_PROTOCOL_VERSION_MAJOR=7
JSON_PROTOCOL_VERSION_MINOR=0
JSON_PROTOCOL_VERSION_MINOR=1
JSON_PROTOCOL_VERSION="$${JSON_PROTOCOL_VERSION_MAJOR}.$${JSON_PROTOCOL_VERSION_MINOR}"
LIBNYMEA_API_VERSION_MAJOR=7
LIBNYMEA_API_VERSION_MINOR=3

View File

@ -1,4 +1,4 @@
7.0
7.1
{
"enums": {
"BasicType": [
@ -169,10 +169,6 @@
"PermissionScopeConfigureRules",
"PermissionScopeAdmin"
],
"RemovePolicy": [
"RemovePolicyCascade",
"RemovePolicyUpdate"
],
"RepeatingMode": [
"RepeatingModeNone",
"RepeatingModeHourly",
@ -1121,22 +1117,13 @@
}
},
"Integrations.RemoveThing": {
"description": "Remove a thing from the system.",
"description": "Remove a thing and all its childs from the system. RemovePolicy is deprecated and has no effect any more.",
"params": {
"o:removePolicy": "$ref:RemovePolicy",
"o:removePolicyList": [
{
"policy": "$ref:RemovePolicy",
"ruleId": "Uuid"
}
],
"d:o:removePolicy": "String",
"thingId": "Uuid"
},
"permissionScope": "PermissionScopeConfigureThings",
"returns": {
"o:ruleIds": [
"Uuid"
],
"thingError": "$ref:ThingError"
}
},

View File

@ -1910,9 +1910,9 @@ void TestIntegrations::discoverThingsParenting()
QVERIFY(childThing->thingClassId() == childMockThingClassId);
// Now delete the parent and make sure the child will be deleted too
QSignalSpy removeSpy(NymeaCore::instance(), &NymeaCore::thingRemoved);
QPair<Thing::ThingError, QList<RuleId> > ret = NymeaCore::instance()->removeConfiguredThing(parentThing->id(), QHash<RuleId, RuleEngine::RemovePolicy>());
QCOMPARE(ret.first, Thing::ThingErrorNoError);
QSignalSpy removeSpy(NymeaCore::instance()->thingManager(), &ThingManager::thingRemoved);
Thing::ThingError ret = NymeaCore::instance()->thingManager()->removeConfiguredThing(parentThing->id());
QCOMPARE(ret, Thing::ThingErrorNoError);
QCOMPARE(removeSpy.count(), 3); // The parent, the auto-mock and the discovered mock
}
@ -2107,8 +2107,8 @@ void TestIntegrations::triggerEvent()
QVERIFY2(things.count() > 0, "There needs to be at least one configured Mock Device for this test");
Thing *thing = things.first();
QSignalSpy spy(NymeaCore::instance(), SIGNAL(eventTriggered(const Event&)));
QSignalSpy notificationSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
QSignalSpy spy(NymeaCore::instance()->thingManager(), &ThingManager::eventTriggered);
QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);
// Setup connection to mock client
QNetworkAccessManager nam;
@ -2148,8 +2148,8 @@ void TestIntegrations::triggerStateChangeSignal()
QVERIFY2(things.count() > 0, "There needs to be at least one configured Mock for this test");
Thing *thing = things.first();
QSignalSpy spy(NymeaCore::instance(), SIGNAL(thingStateChanged(Thing *, const StateTypeId &, const QVariant &, const QVariant &, const QVariant &)));
QSignalSpy notificationSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
QSignalSpy spy(NymeaCore::instance()->thingManager(), &ThingManager::thingStateChanged);
QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);
// Setup connection to mock client
QNetworkAccessManager nam;

View File

@ -115,9 +115,7 @@ private slots:
void testStateBasedAction();
void removePolicyUpdate();
void removePolicyCascade();
void removePolicyUpdateRendersUselessRule();
void removeThingCleansRule();
void testRuleActionParams_data();
void testRuleActionParams();
@ -2385,7 +2383,7 @@ void TestRules::testStateBasedAction()
qCDebug(dcTests()) << "Log entries:" << entries;
}
void TestRules::removePolicyUpdate()
void TestRules::removeThingCleansRule()
{
// ADD parent
QVariantMap params;
@ -2441,16 +2439,10 @@ void TestRules::removePolicyUpdate()
response = injectAndWait("Integrations.RemoveThing", params);
verifyThingError(response, Thing::ThingErrorThingIsChild);
// Try to remove child
params.clear(); response.clear();
params.insert("thingId", parentId);
response = injectAndWait("Integrations.RemoveThing", params);
verifyThingError(response, Thing::ThingErrorThingInRule);
// Remove policy
params.clear(); response.clear();
params.insert("thingId", parentId);
params.insert("removePolicy", "RemovePolicyUpdate");
params.insert("removePolicy", "RemovePolicyCascade"); // This is deprecated and doesn't do anything any more, keeping it as clients may pass it too still
response = injectAndWait("Integrations.RemoveThing", params);
verifyThingError(response);
@ -2458,182 +2450,6 @@ void TestRules::removePolicyUpdate()
params.clear();
params.insert("ruleId", ruleId);
response = injectAndWait("Rules.GetRuleDetails", params);
verifyRuleError(response);
QVariantMap rule = response.toMap().value("params").toMap().value("rule").toMap();
qDebug() << "Updated rule:" << QJsonDocument::fromVariant(rule).toJson();
QVERIFY(rule.value("eventDescriptors").toList().count() == 1);
// REMOVE rule
QVariantMap removeParams;
removeParams.insert("ruleId", ruleId);
response = injectAndWait("Rules.RemoveRule", removeParams);
verifyRuleError(response);
}
void TestRules::removePolicyCascade()
{
// ADD parent
QVariantMap params;
params.insert("thingClassId", parentMockThingClassId);
params.insert("name", "Parent");
QSignalSpy addedSpy(NymeaCore::instance()->thingManager(), &ThingManager::thingAdded);
QVariant response = injectAndWait("Integrations.AddThing", params);
verifyThingError(response);
ThingId parentId = ThingId(response.toMap().value("params").toMap().value("thingId").toString());
QVERIFY(!parentId.isNull());
addedSpy.wait();
// find child
response = injectAndWait("Integrations.GetThings");
QVariantList things = response.toMap().value("params").toMap().value("things").toList();
ThingId childId;
foreach (const QVariant thingVariant, things) {
QVariantMap thingMap = thingVariant.toMap();
if (thingMap.value("thingClassId").toUuid() == childMockThingClassId) {
if (thingMap.value("parentId").toUuid() == parentId) {
childId = ThingId(thingMap.value("id").toString());
}
}
}
QVERIFY2(!childId.isNull(), "Could not find child");
// Add rule with child
QVariantList eventDescriptors;
eventDescriptors.append(createEventDescriptor(childId, childMockEvent1EventTypeId));
eventDescriptors.append(createEventDescriptor(parentId, parentMockEvent1EventTypeId));
eventDescriptors.append(createEventDescriptor(m_mockThingId, mockEvent1EventTypeId));
params.clear(); response.clear();
params.insert("name", "RemovePolicy");
params.insert("eventDescriptors", eventDescriptors);
params.insert("actions", QVariantList() << createActionWithParams(m_mockThingId));
response = injectAndWait("Rules.AddRule", params);
verifyRuleError(response);
RuleId ruleId = RuleId(response.toMap().value("params").toMap().value("ruleId").toString());
QVERIFY2(!ruleId.isNull(), "Could not get ruleId");
// Try to remove child
params.clear(); response.clear();
params.insert("thingId", childId);
response = injectAndWait("Integrations.RemoveThing", params);
verifyThingError(response, Thing::ThingErrorThingIsChild);
// Try to remove child by removing parent
params.clear(); response.clear();
params.insert("thingId", parentId);
response = injectAndWait("Integrations.RemoveThing", params);
verifyThingError(response, Thing::ThingErrorThingInRule);
// Remove policy
params.clear(); response.clear();
params.insert("thingId", parentId);
params.insert("removePolicy", "RemovePolicyCascade");
response = injectAndWait("Integrations.RemoveThing", params);
verifyThingError(response);
// get updated rule
params.clear();
params.insert("ruleId", ruleId);
response = injectAndWait("Rules.GetRuleDetails", params);
verifyRuleError(response, RuleEngine::RuleErrorRuleNotFound);
}
void TestRules::removePolicyUpdateRendersUselessRule()
{
// ADD parent
QVariantMap params;
params.insert("thingClassId", parentMockThingClassId);
params.insert("name", "Parent");
QSignalSpy addedSpy(NymeaCore::instance()->thingManager(), &ThingManager::thingAdded);
QVariant response = injectAndWait("Integrations.AddThing", params);
verifyThingError(response);
ThingId parentId = ThingId(response.toMap().value("params").toMap().value("thingId").toString());
QVERIFY(!parentId.isNull());
addedSpy.wait();
// find child
qCDebug(dcTests()) << "Get things";
response = injectAndWait("Integrations.GetThings");
QVariantList things = response.toMap().value("params").toMap().value("things").toList();
ThingId childId;
foreach (const QVariant thingVariant, things) {
QVariantMap thingMap = thingVariant.toMap();
if (thingMap.value("thingClassId").toUuid() == childMockThingClassId) {
if (thingMap.value("parentId").toUuid() == parentId) {
childId = ThingId(thingMap.value("id").toString());
}
}
}
QVERIFY2(!childId.isNull(), "Could not find child");
// Add rule with child
QVariantList eventDescriptors;
eventDescriptors.append(createEventDescriptor(childId, childMockEvent1EventTypeId));
eventDescriptors.append(createEventDescriptor(parentId, parentMockEvent1EventTypeId));
eventDescriptors.append(createEventDescriptor(m_mockThingId, mockEvent1EventTypeId));
params.clear(); response.clear();
params.insert("name", "RemovePolicy");
params.insert("eventDescriptors", eventDescriptors);
QVariantMap action;
action.insert("thingId", childId);
action.insert("actionTypeId", childMockBoolValueActionTypeId);
QVariantMap ruleActionParam;
ruleActionParam.insert("paramTypeId", childMockBoolValueActionBoolValueParamTypeId);
ruleActionParam.insert("value", true);
action.insert("ruleActionParams", QVariantList() << ruleActionParam);
params.insert("actions", QVariantList() << action);
qCDebug(dcTests()) << "Adding Rule";
response = injectAndWait("Rules.AddRule", params);
verifyRuleError(response);
RuleId ruleId = RuleId(response.toMap().value("params").toMap().value("ruleId").toString());
QVERIFY2(!ruleId.isNull(), "Could not get ruleId");
// Try to remove child
qCDebug(dcTests()) << "Removing thing (expecing failure - thing is child)";
params.clear(); response.clear();
params.insert("thingId", childId);
response = injectAndWait("Integrations.RemoveThing", params);
verifyThingError(response, Thing::ThingErrorThingIsChild);
// Try to remove child by removing parent
qCDebug(dcTests()) << "Removing thing (expeciting failure - thing in use)";
params.clear(); response.clear();
params.insert("thingId", parentId);
response = injectAndWait("Integrations.RemoveThing", params);
verifyThingError(response, Thing::ThingErrorThingInRule);
// Remove policy
qCDebug(dcTests()) << "Removing thing with update policy";
params.clear(); response.clear();
params.insert("thingId", parentId);
params.insert("removePolicy", "RemovePolicyUpdate");
response = injectAndWait("Integrations.RemoveThing", params);
verifyThingError(response);
// get updated rule. It should've been deleted given it ended up with no actions
qCDebug(dcTests()) << "Getting details";
params.clear();
params.insert("ruleId", ruleId);
response = injectAndWait("Rules.GetRuleDetails", params);
verifyRuleError(response, RuleEngine::RuleErrorRuleNotFound);
}