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 "unistd.h"
#include "plugintimer.h" #include "plugintimer.h"
#include "logging/logengine.h"
#include <QPluginLoader> #include <QPluginLoader>
#include <QStaticPlugin> #include <QStaticPlugin>
@ -70,9 +71,10 @@
#include <QDir> #include <QDir>
#include <QJsonDocument> #include <QJsonDocument>
ThingManagerImplementation::ThingManagerImplementation(HardwareManager *hardwareManager, const QLocale &locale, QObject *parent) : ThingManagerImplementation::ThingManagerImplementation(HardwareManager *hardwareManager, LogEngine *logEngine, const QLocale &locale, QObject *parent) :
ThingManager(parent), ThingManager(parent),
m_hardwareManager(hardwareManager), m_hardwareManager(hardwareManager),
m_logEngine(logEngine),
m_locale(locale), m_locale(locale),
m_translator(new Translator(this)) m_translator(new Translator(this))
{ {
@ -853,47 +855,66 @@ ThingSetupInfo* ThingManagerImplementation::addConfiguredThingInternal(const Thi
} }
Thing::ThingError ThingManagerImplementation::removeConfiguredThing(const ThingId &thingId) 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() // 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 // 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. // don't end up aborting an already finished setup instead of calling thingRemoved() on it.
qApp->processEvents(); qApp->processEvents();
Thing *thing = m_configuredThings.take(thingId); Things toBeRemoved = findChilds(thing->id());
if (!thing) { toBeRemoved.append(thing);
return Thing::ThingErrorThingNotFound; while (!toBeRemoved.isEmpty()) {
} Thing *t = m_configuredThings.take(toBeRemoved.takeFirst()->id());
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);
}
thing->deleteLater(); IntegrationPlugin *plugin = m_integrationPlugins.value(t->pluginId());
if (!plugin) {
NymeaSettings settings(NymeaSettings::SettingsRoleThings); qCWarning(dcThingManager()).nospace() << "Plugin not loaded for thing " << t << ". Not calling thingRemoved on plugin.";
settings.beginGroup("ThingConfig"); } else if (thing->setupStatus() == Thing::ThingSetupStatusInProgress) {
settings.beginGroup(thingId.toString()); qCWarning(dcThingManager()).nospace() << "Thing " << thing << " is still being set up. Aborting setup.";
settings.remove(""); ThingSetupInfo *setupInfo = m_pendingSetups.value(t->id());
settings.endGroup(); emit setupInfo->aborted();
} else if (thing->setupStatus() == Thing::ThingSetupStatusComplete) {
QFile::remove(statesCacheFile(thingId)); plugin->thingRemoved(t);
foreach (const IOConnectionId &ioConnectionId, m_ioConnections.keys()) {
IOConnection ioConnection = m_ioConnections.value(ioConnectionId);
if (ioConnection.inputThingId() == thing->id() || ioConnection.outputThingId() == thing->id()) {
disconnectIO(ioConnectionId);
} }
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) 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()); Thing *thing = m_configuredThings.value(browserAction.thingId());
BrowserActionInfo *info = new BrowserActionInfo(thing, this, browserAction, this, 30000); 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) { if (!thing) {
info->finish(Thing::ThingErrorThingNotFound); info->finish(Thing::ThingErrorThingNotFound);
@ -1009,6 +1033,9 @@ BrowserItemActionInfo* ThingManagerImplementation::executeBrowserItemAction(cons
Thing *thing = m_configuredThings.value(browserItemAction.thingId()); Thing *thing = m_configuredThings.value(browserItemAction.thingId());
BrowserItemActionInfo *info = new BrowserItemActionInfo(thing, this, browserItemAction, this, 30000); 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) { if (!thing) {
info->finish(Thing::ThingErrorThingNotFound); info->finish(Thing::ThingErrorThingNotFound);
@ -1359,6 +1386,7 @@ ThingActionInfo *ThingManagerImplementation::executeAction(const Action &action)
ThingActionInfo *info = new ThingActionInfo(thing, finalAction, this, 15000); ThingActionInfo *info = new ThingActionInfo(thing, finalAction, this, 15000);
connect(info, &ThingActionInfo::finished, this, [=](){ connect(info, &ThingActionInfo::finished, this, [=](){
m_logEngine->logAction(finalAction, info->status());
emit actionExecuted(action, info->status()); emit actionExecuted(action, info->status());
}); });
@ -1810,7 +1838,7 @@ void ThingManagerImplementation::onAutoThingDisappeared(const ThingId &thingId)
return; return;
} }
emit thingDisappeared(thingId); removeConfiguredThingInternal(thing);
} }
void ThingManagerImplementation::onLoaded() void ThingManagerImplementation::onLoaded()
@ -1849,7 +1877,7 @@ void ThingManagerImplementation::onEventTriggered(Event event)
} }
// configure logging // configure logging
if (thing->loggedEventTypeIds().contains(event.eventTypeId())) { if (thing->loggedEventTypeIds().contains(event.eventTypeId())) {
event.setLogged(true); m_logEngine->logEvent(event);
} }
// Forward the event // Forward the event
@ -1867,6 +1895,10 @@ void ThingManagerImplementation::slotThingStateValueChanged(const StateTypeId &s
storeThingState(thing, stateTypeId); storeThingState(thing, stateTypeId);
} }
if (thing->loggedStateTypeIds().contains(stateTypeId)) {
m_logEngine->logStateChange(thing, stateTypeId, value);
}
emit thingStateChanged(thing, stateTypeId, value, minValue, maxValue); emit thingStateChanged(thing, stateTypeId, value, minValue, maxValue);
syncIOConnection(thing, stateTypeId); syncIOConnection(thing, stateTypeId);

View File

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

View File

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

View File

@ -525,7 +525,7 @@ void JsonRPCServerImplementation::setup()
{ {
registerHandler(this); registerHandler(this);
registerHandler(new IntegrationsHandler(NymeaCore::instance()->thingManager(), 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 LoggingHandler(this));
registerHandler(new ConfigurationHandler(this)); registerHandler(new ConfigurationHandler(this));
registerHandler(new NetworkManagerHandler(NymeaCore::instance()->networkManager(), this)); registerHandler(new NetworkManagerHandler(NymeaCore::instance()->networkManager(), this));

View File

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

View File

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

View File

@ -106,14 +106,6 @@ LogEngine::~LogEngine()
m_db.close(); 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) LogEntriesFetchJob *LogEngine::fetchLogEntries(const LogFilter &filter)
{ {
QList<LogEntry> results; QList<LogEntry> results;
@ -233,10 +225,6 @@ void LogEngine::logSystemEvent(const QDateTime &dateTime, bool active, Logging::
void LogEngine::logEvent(const Event &event) void LogEngine::logEvent(const Event &event)
{ {
if (!event.logged()) {
return;
}
QVariantList valueList; QVariantList valueList;
foreach (const Param &param, event.params()) { foreach (const Param &param, event.params()) {
valueList << param.value(); valueList << param.value();
@ -255,10 +243,6 @@ void LogEngine::logEvent(const Event &event)
void LogEngine::logStateChange(Thing *thing, const StateTypeId &stateTypeId, const QVariant &value) void LogEngine::logStateChange(Thing *thing, const StateTypeId &stateTypeId, const QVariant &value)
{ {
if (!thing->loggedStateTypeIds().contains(stateTypeId)) {
return;
}
LogEntry entry(Logging::LoggingSourceStates); LogEntry entry(Logging::LoggingSourceStates);
entry.setTypeId(stateTypeId); entry.setTypeId(stateTypeId);
entry.setThingId(thing->id()); 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(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(); ~LogEngine();
void setThingManager(ThingManager *thingManager);
LogEntriesFetchJob *fetchLogEntries(const LogFilter &filter = LogFilter()); LogEntriesFetchJob *fetchLogEntries(const LogFilter &filter = LogFilter());
ThingsFetchJob *fetchThings(); ThingsFetchJob *fetchThings();
@ -76,6 +74,9 @@ public:
public slots: public slots:
void logSystemEvent(const QDateTime &dateTime, bool active, Logging::LoggingLevel level = Logging::LoggingLevelInfo); 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 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 logBrowserItemAction(const BrowserItemAction &browserItemAction, Logging::LoggingLevel level = Logging::LoggingLevelInfo, int errorCode = 0);
void logRuleTriggered(const Rule &rule); void logRuleTriggered(const Rule &rule);
@ -84,11 +85,6 @@ public slots:
void logRuleActionsExecuted(const Rule &rule); void logRuleActionsExecuted(const Rule &rule);
void logRuleExitActionsExecuted(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: signals:
void logEntryAdded(const LogEntry &logEntry); void logEntryAdded(const LogEntry &logEntry);
void logDatabaseUpdated(); void logDatabaseUpdated();
@ -122,8 +118,6 @@ private:
bool m_initialized = false; bool m_initialized = false;
bool m_dbMalformed = 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 // When maxQueueLength is exceeded, jobs will be flagged and discarded if this source logs more events
int m_maxQueueLength; int m_maxQueueLength;
QHash<QString, QList<DatabaseJob*>> m_flaggedJobs; QHash<QString, QList<DatabaseJob*>> m_flaggedJobs;

View File

@ -121,15 +121,14 @@ void NymeaCore::init(const QStringList &additionalInterfaces) {
qCDebug(dcCore) << "Creating Hardware Manager"; qCDebug(dcCore) << "Creating Hardware Manager";
m_hardwareManager = new HardwareManagerImplementation(m_platform, m_serverManager->mqttBroker(), m_zigbeeManager, m_zwaveManager, m_modbusRtuManager, this); 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"; 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 = 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"; qCDebug(dcCore()) << "Creating Script Engine";
m_scriptEngine = new scriptengine::ScriptEngine(m_thingManager, this); 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_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_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); m_logger->logSystemEvent(m_timeManager->currentDateTime(), true);
} }
@ -215,322 +200,6 @@ void NymeaCore::destroy()
s_instance = nullptr; 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 NymeaConfiguration *NymeaCore::configuration() const
{ {
return m_configuration; return m_configuration;
@ -663,117 +332,6 @@ ExperienceManager *NymeaCore::experienceManager() const
return m_experienceManager; 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 LogEngine* NymeaCore::logEngine() const
{ {
return m_logger; return m_logger;
@ -784,65 +342,8 @@ JsonRPCServerImplementation *NymeaCore::jsonRPCServer() const
return m_serverManager->jsonServer(); 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() void NymeaCore::thingManagerLoaded()
{ {
m_ruleEngine->init();
// Evaluate rules on current time
onDateTimeChanged(m_timeManager->currentDateTime());
// Tell hardare resources we're done with loading stuff... // Tell hardare resources we're done with loading stuff...
m_hardwareManager->thingsLoaded(); m_hardwareManager->thingsLoaded();
@ -881,7 +382,8 @@ void NymeaCore::thingManagerLoaded()
qCDebug(dcCore()) << "Cleaning up stale thing tag" << tag.tagId(); qCDebug(dcCore()) << "Cleaning up stale thing tag" << tag.tagId();
m_tagsStorage->removeTag(tag); m_tagsStorage->removeTag(tag);
} }
} }
} }
} }

View File

@ -87,15 +87,6 @@ public:
void init(const QStringList &additionalInterfaces = QStringList()); void init(const QStringList &additionalInterfaces = QStringList());
void destroy(); 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); RuleEngine::RuleError removeRule(const RuleId &id);
NymeaConfiguration *configuration() const; NymeaConfiguration *configuration() const;
@ -125,19 +116,6 @@ public:
signals: signals:
void initialized(); 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: private:
explicit NymeaCore(QObject *parent = nullptr); explicit NymeaCore(QObject *parent = nullptr);
static NymeaCore *s_instance; static NymeaCore *s_instance;
@ -165,14 +143,8 @@ private:
SerialPortMonitor *m_serialPortMonitor; SerialPortMonitor *m_serialPortMonitor;
ModbusRtuManager *m_modbusRtuManager; ModbusRtuManager *m_modbusRtuManager;
QList<RuleId> m_executingRules;
private slots: 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(); 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 // check if this rules is based on any event and contains exit actions
if (!eventDescriptors().isEmpty() && stateEvaluator().isEmpty() && timeDescriptor().calendarItems().isEmpty() && !exitActions().isEmpty()) { 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; return false;
} }
// check if this rules is based on any time events and contains exit actions // 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()) { 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; return false;
} }
// check if there are any actions // check if there are any actions
if (actions().isEmpty()) { if (actions().isEmpty()) {
qCWarning(dcRuleEngine) << "Rule not consistent. A rule without actions has no effect.";
return false; return false;
} }

View File

@ -110,31 +110,58 @@
#include "ruleengine.h" #include "ruleengine.h"
#include "nymeacore.h"
#include "loggingcategories.h" #include "loggingcategories.h"
#include "time/calendaritem.h" #include "time/calendaritem.h"
#include "time/repeatingoption.h" #include "time/repeatingoption.h"
#include "time/timeeventitem.h" #include "time/timeeventitem.h"
#include "time/timemanager.h"
#include "types/eventdescriptor.h" #include "types/eventdescriptor.h"
#include "types/paramdescriptor.h" #include "types/paramdescriptor.h"
#include "nymeasettings.h" #include "nymeasettings.h"
#include "integrations/thingmanager.h" #include "integrations/thingmanager.h"
#include "integrations/thing.h" #include "integrations/thing.h"
#include "logging/logengine.h"
#include <QDebug> #include <QDebug>
#include <QStringList> #include <QStringList>
#include <QStandardPaths> #include <QStandardPaths>
#include <QCoreApplication> #include <QCoreApplication>
NYMEA_LOGGING_CATEGORY(dcRuleEngine, "RuleEngine")
NYMEA_LOGGING_CATEGORY(dcRuleEngineDebug, "RuleEngineDebug")
namespace nymeaserver { namespace nymeaserver {
/*! Constructs the RuleEngine with the given \a parent. Although it wouldn't harm to have multiple RuleEngines, there is one /*! 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. instance available from \l{NymeaCore}. This one should be used instead of creating multiple ones.
*/ */
RuleEngine::RuleEngine(QObject *parent) : RuleEngine::RuleEngine(ThingManager *thingManager, TimeManager *timeManager, LogEngine *logEngine, QObject *parent) :
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}. */ /*! Destructor of the \l{RuleEngine}. */
@ -150,7 +177,7 @@ RuleEngine::~RuleEngine()
*/ */
QList<Rule> RuleEngine::evaluateEvent(const Event &event) QList<Rule> RuleEngine::evaluateEvent(const Event &event)
{ {
Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(event.thingId()); Thing *thing = m_thingManager->findConfiguredThing(event.thingId());
if (!thing) { if (!thing) {
qCWarning(dcRuleEngine()) << "Invalid event. ThingID does not reference a valid thing"; qCWarning(dcRuleEngine()) << "Invalid event. ThingID does not reference a valid thing";
return QList<Rule>(); return QList<Rule>();
@ -307,7 +334,7 @@ RuleEngine::RuleError RuleEngine::addRule(const Rule &rule, bool fromEdit)
} }
if (!rule.isConsistent()) { if (!rule.isConsistent()) {
qCWarning(dcRuleEngine) << "Rule inconsistent."; qCWarning(dcRuleEngine) << "Invalid rule format. (Either missing actions, or exitActions without condition given.)";
return RuleErrorInvalidRuleFormat; return RuleErrorInvalidRuleFormat;
} }
@ -319,14 +346,14 @@ RuleEngine::RuleError RuleEngine::addRule(const Rule &rule, bool fromEdit)
} }
if (eventDescriptor.type() == EventDescriptor::TypeThing) { if (eventDescriptor.type() == EventDescriptor::TypeThing) {
// check thingId // check thingId
Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(eventDescriptor.thingId()); Thing *thing = m_thingManager->findConfiguredThing(eventDescriptor.thingId());
if (!thing) { if (!thing) {
qCWarning(dcRuleEngine) << "Cannot create rule. No configured thing for eventTypeId" << eventDescriptor.eventTypeId(); qCWarning(dcRuleEngine) << "Cannot create rule. No configured thing for eventTypeId" << eventDescriptor.eventTypeId();
return RuleErrorThingNotFound; return RuleErrorThingNotFound;
} }
// Check eventTypeId for this deivce // Check eventTypeId for this deivce
ThingClass thingClass = NymeaCore::instance()->thingManager()->findThingClass(thing->thingClassId()); ThingClass thingClass = m_thingManager->findThingClass(thing->thingClassId());
bool eventTypeFound = false; bool eventTypeFound = false;
foreach (const EventType &eventType, thingClass.eventTypes()) { foreach (const EventType &eventType, thingClass.eventTypes()) {
if (eventType.id() == eventDescriptor.eventTypeId()) { if (eventType.id() == eventDescriptor.eventTypeId()) {
@ -345,7 +372,7 @@ RuleEngine::RuleError RuleEngine::addRule(const Rule &rule, bool fromEdit)
} }
} else { } else {
// Interface based event // Interface based event
Interface iface = NymeaCore::instance()->thingManager()->supportedInterfaces().findByName(eventDescriptor.interface()); Interface iface = m_thingManager->supportedInterfaces().findByName(eventDescriptor.interface());
if (!iface.isValid()) { if (!iface.isValid()) {
qWarning(dcRuleEngine()) << "No such interface:" << eventDescriptor.interface(); qWarning(dcRuleEngine()) << "No such interface:" << eventDescriptor.interface();
return RuleErrorInterfaceNotFound; return RuleErrorInterfaceNotFound;
@ -507,6 +534,8 @@ RuleEngine::RuleError RuleEngine::removeRule(const RuleId &ruleId, bool fromEdit
settings.remove(""); settings.remove("");
settings.endGroup(); settings.endGroup();
m_logEngine->removeRuleLogs(ruleId);
if (!fromEdit) if (!fromEdit)
emit ruleRemoved(ruleId); emit ruleRemoved(ruleId);
@ -536,7 +565,7 @@ RuleEngine::RuleError RuleEngine::enableRule(const RuleId &ruleId)
saveRule(rule); saveRule(rule);
emit ruleConfigurationChanged(rule); emit ruleConfigurationChanged(rule);
NymeaCore::instance()->logEngine()->logRuleEnabledChanged(rule, true); m_logEngine->logRuleEnabledChanged(rule, true);
qCDebug(dcRuleEngine()) << "Rule" << rule.name() << rule.id().toString() << "enabled."; qCDebug(dcRuleEngine()) << "Rule" << rule.name() << rule.id().toString() << "enabled.";
return RuleErrorNoError; return RuleErrorNoError;
@ -562,7 +591,7 @@ RuleEngine::RuleError RuleEngine::disableRule(const RuleId &ruleId)
saveRule(rule); saveRule(rule);
emit ruleConfigurationChanged(rule); emit ruleConfigurationChanged(rule);
NymeaCore::instance()->logEngine()->logRuleEnabledChanged(rule, false); m_logEngine->logRuleEnabledChanged(rule, false);
qCDebug(dcRuleEngine()) << "Rule" << rule.name() << rule.id().toString() << "disabled."; qCDebug(dcRuleEngine()) << "Rule" << rule.name() << rule.id().toString() << "disabled.";
return RuleErrorNoError; 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(); qCDebug(dcRuleEngine) << "Executing rule actions of rule" << rule.name() << rule.id().toString();
NymeaCore::instance()->logEngine()->logRuleActionsExecuted(rule); m_logEngine->logRuleActionsExecuted(rule);
NymeaCore::instance()->executeRuleActions(rule.actions()); executeRuleActions(rule.actions());
return RuleErrorNoError; 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(); qCDebug(dcRuleEngine) << "Executing rule exit actions of rule" << rule.name() << rule.id().toString();
NymeaCore::instance()->logEngine()->logRuleExitActionsExecuted(rule); m_logEngine->logRuleExitActionsExecuted(rule);
NymeaCore::instance()->executeRuleActions(rule.exitActions()); executeRuleActions(rule.exitActions());
return RuleErrorNoError; 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 this is a interface based rule, the thing must implement the interface
if (eventDescriptor.type() == EventDescriptor::TypeInterface) { if (eventDescriptor.type() == EventDescriptor::TypeInterface) {
ThingClass dc = NymeaCore::instance()->thingManager()->findThingClass(thingClassId); ThingClass dc = m_thingManager->findThingClass(thingClassId);
if (!dc.interfaces().contains(eventDescriptor.interface())) { if (!dc.interfaces().contains(eventDescriptor.interface())) {
// ThingClass for this event doesn't implement the interface for this eventDescriptor // ThingClass for this event doesn't implement the interface for this eventDescriptor
continue; continue;
@ -863,7 +892,7 @@ bool RuleEngine::containsEvent(const Rule &rule, const Event &event, const Thing
allOK = false; allOK = false;
continue; continue;
} }
ThingClass dc = NymeaCore::instance()->thingManager()->findThingClass(thingClassId); ThingClass dc = m_thingManager->findThingClass(thingClassId);
EventType et = dc.eventTypes().findById(event.eventTypeId()); EventType et = dc.eventTypes().findById(event.eventTypeId());
StateType st = dc.stateTypes().findById(event.eventTypeId()); StateType st = dc.stateTypes().findById(event.eventTypeId());
if (et.isValid()) { if (et.isValid()) {
@ -928,8 +957,8 @@ bool RuleEngine::containsState(const StateEvaluator &stateEvaluator, const Event
return true; return true;
} }
} else { } else {
Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(stateChangeEvent.thingId()); Thing *thing = m_thingManager->findConfiguredThing(stateChangeEvent.thingId());
ThingClass thingClass = NymeaCore::instance()->thingManager()->findThingClass(thing->thingClassId()); ThingClass thingClass = m_thingManager->findThingClass(thing->thingClassId());
if (thingClass.interfaces().contains(stateEvaluator.stateDescriptor().interface())) { if (thingClass.interfaces().contains(stateEvaluator.stateDescriptor().interface())) {
return true; return true;
} }
@ -954,13 +983,13 @@ RuleEngine::RuleError RuleEngine::checkRuleAction(const RuleAction &ruleAction,
ActionType actionType; ActionType actionType;
if (ruleAction.type() == RuleAction::TypeThing) { if (ruleAction.type() == RuleAction::TypeThing) {
Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(ruleAction.thingId()); Thing *thing = m_thingManager->findConfiguredThing(ruleAction.thingId());
if (!thing) { if (!thing) {
qCWarning(dcRuleEngine) << "Cannot create rule. No configured thing with ID" << ruleAction.thingId(); qCWarning(dcRuleEngine) << "Cannot create rule. No configured thing with ID" << ruleAction.thingId();
return RuleErrorThingNotFound; return RuleErrorThingNotFound;
} }
ThingClass thingClass = NymeaCore::instance()->thingManager()->findThingClass(thing->thingClassId()); ThingClass thingClass = m_thingManager->findThingClass(thing->thingClassId());
if (!thingClass.hasActionType(ruleAction.actionTypeId())) { if (!thingClass.hasActionType(ruleAction.actionTypeId())) {
qCWarning(dcRuleEngine) << "Cannot create rule. Thing " + thing->name() + " has no action type:" << ruleAction.actionTypeId(); qCWarning(dcRuleEngine) << "Cannot create rule. Thing " + thing->name() + " has no action type:" << ruleAction.actionTypeId();
return RuleErrorActionTypeNotFound; return RuleErrorActionTypeNotFound;
@ -968,7 +997,7 @@ RuleEngine::RuleError RuleEngine::checkRuleAction(const RuleAction &ruleAction,
actionType = thingClass.actionTypes().findById(ruleAction.actionTypeId()); actionType = thingClass.actionTypes().findById(ruleAction.actionTypeId());
} else if (ruleAction.type() == RuleAction::TypeInterface) { } 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()) { if (!iface.isValid()) {
qCWarning(dcRuleEngine()) << "Cannot create rule. No such interface:" << ruleAction.interface(); qCWarning(dcRuleEngine()) << "Cannot create rule. No such interface:" << ruleAction.interface();
return RuleError::RuleErrorInterfaceNotFound; return RuleError::RuleErrorInterfaceNotFound;
@ -979,7 +1008,7 @@ RuleEngine::RuleError RuleEngine::checkRuleAction(const RuleAction &ruleAction,
return RuleError::RuleErrorActionTypeNotFound; return RuleError::RuleErrorActionTypeNotFound;
} }
} else if (ruleAction.type() == RuleAction::TypeBrowser) { } else if (ruleAction.type() == RuleAction::TypeBrowser) {
Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(ruleAction.thingId()); Thing *thing = m_thingManager->findConfiguredThing(ruleAction.thingId());
if (!thing) { if (!thing) {
qCWarning(dcRuleEngine) << "Cannot create rule. No configured thing with ID" << ruleAction.thingId(); qCWarning(dcRuleEngine) << "Cannot create rule. No configured thing with ID" << ruleAction.thingId();
return RuleErrorThingNotFound; return RuleErrorThingNotFound;
@ -1057,12 +1086,12 @@ RuleEngine::RuleError RuleEngine::checkRuleActionParam(const RuleActionParam &ru
return RuleErrorTypesNotMatching; return RuleErrorTypesNotMatching;
} }
} else if (ruleActionParam.isStateBased()) { } else if (ruleActionParam.isStateBased()) {
Thing *d = NymeaCore::instance()->thingManager()->findConfiguredThing(ruleActionParam.stateThingId()); Thing *d = m_thingManager->findConfiguredThing(ruleActionParam.stateThingId());
if (!d) { if (!d) {
qCWarning(dcRuleEngine()) << "Cannot create Rule. ThingId from RuleActionParam" << ruleActionParam.paramTypeId() << "not found in system."; qCWarning(dcRuleEngine()) << "Cannot create Rule. ThingId from RuleActionParam" << ruleActionParam.paramTypeId() << "not found in system.";
return RuleErrorThingNotFound; return RuleErrorThingNotFound;
} }
ThingClass stateThingClass = NymeaCore::instance()->thingManager()->findThingClass(d->thingClassId()); ThingClass stateThingClass = m_thingManager->findThingClass(d->thingClassId());
StateType stateType = stateThingClass.stateTypes().findById(ruleActionParam.stateTypeId()); StateType stateType = stateThingClass.stateTypes().findById(ruleActionParam.stateTypeId());
QVariant::Type actionParamType = getActionParamType(actionType.id(), ruleActionParam.paramTypeId()); QVariant::Type actionParamType = getActionParamType(actionType.id(), ruleActionParam.paramTypeId());
QVariant v(stateType.type()); 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) 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()) { foreach (const ActionType &actionType, thingClass.actionTypes()) {
if (actionType.id() == actionTypeId) { if (actionType.id() == actionTypeId) {
foreach (const ParamType &paramType, actionType.paramTypes()) { 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) 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()) { foreach (const EventType &eventType, thingClass.eventTypes()) {
if (eventType.id() == eventTypeId) { if (eventType.id() == eventTypeId) {
foreach (const ParamType &paramType, eventType.paramTypes()) { foreach (const ParamType &paramType, eventType.paramTypes()) {
@ -1371,6 +1400,231 @@ QList<RuleAction> RuleEngine::loadRuleActions(NymeaSettings *settings)
return actions; 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() void RuleEngine::init()
{ {
NymeaSettings settings(NymeaSettings::SettingsRoleRules); NymeaSettings settings(NymeaSettings::SettingsRoleRules);

View File

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

View File

@ -110,11 +110,11 @@ protected:
virtual IOConnectionResult connectIO(const IOConnection &connection) = 0; virtual IOConnectionResult connectIO(const IOConnection &connection) = 0;
signals: signals:
void loaded();
void pluginConfigChanged(const PluginId &id, const ParamList &config); void pluginConfigChanged(const PluginId &id, const ParamList &config);
void eventTriggered(const Event &event); void eventTriggered(const Event &event);
void thingStateChanged(Thing *thing, const StateTypeId &stateTypeId, const QVariant &value, const QVariant &minValue, const QVariant &maxValue); void thingStateChanged(Thing *thing, const StateTypeId &stateTypeId, const QVariant &value, const QVariant &minValue, const QVariant &maxValue);
void thingRemoved(const ThingId &thingId); void thingRemoved(const ThingId &thingId);
void thingDisappeared(const ThingId &thingId);
void thingAdded(Thing *thing); void thingAdded(Thing *thing);
void thingChanged(Thing *thing); void thingChanged(Thing *thing);
void thingSettingChanged(const ThingId &thingId, const ParamTypeId &settingParamTypeId, const QVariant &value); 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(dcPlatformZeroConf, "PlatformZeroConf")
NYMEA_LOGGING_CATEGORY(dcExperiences, "Experiences") NYMEA_LOGGING_CATEGORY(dcExperiences, "Experiences")
NYMEA_LOGGING_CATEGORY(dcTimeManager, "TimeManager") NYMEA_LOGGING_CATEGORY(dcTimeManager, "TimeManager")
NYMEA_LOGGING_CATEGORY(dcRuleEngine, "RuleEngine")
NYMEA_LOGGING_CATEGORY(dcRuleEngineDebug, "RuleEngineDebug")
NYMEA_LOGGING_CATEGORY(dcHardware, "Hardware") NYMEA_LOGGING_CATEGORY(dcHardware, "Hardware")
NYMEA_LOGGING_CATEGORY(dcLogEngine, "LogEngine") NYMEA_LOGGING_CATEGORY(dcLogEngine, "LogEngine")
NYMEA_LOGGING_CATEGORY(dcServerManager, "ServerManager") 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(dcPlatformZeroConf)
Q_DECLARE_LOGGING_CATEGORY(dcExperiences) Q_DECLARE_LOGGING_CATEGORY(dcExperiences)
Q_DECLARE_LOGGING_CATEGORY(dcTimeManager) Q_DECLARE_LOGGING_CATEGORY(dcTimeManager)
Q_DECLARE_LOGGING_CATEGORY(dcRuleEngine)
Q_DECLARE_LOGGING_CATEGORY(dcRuleEngineDebug)
Q_DECLARE_LOGGING_CATEGORY(dcHardware) Q_DECLARE_LOGGING_CATEGORY(dcHardware)
Q_DECLARE_LOGGING_CATEGORY(dcLogEngine) Q_DECLARE_LOGGING_CATEGORY(dcLogEngine)
Q_DECLARE_LOGGING_CATEGORY(dcServerManager) Q_DECLARE_LOGGING_CATEGORY(dcServerManager)

View File

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

View File

@ -119,16 +119,6 @@ QVariant Event::paramValue(const ParamTypeId &paramTypeId) const
return QVariant(); 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. /*! Compare this Event to the Event given by \a other.
* Events are equal (returns true) if eventTypeId, deviceId and params match. */ * Events are equal (returns true) if eventTypeId, deviceId and params match. */
bool Event::operator ==(const Event &other) const bool Event::operator ==(const Event &other) const

View File

@ -62,15 +62,10 @@ public:
bool operator ==(const Event &other) const; bool operator ==(const Event &other) const;
bool logged() const;
void setLogged(bool logged);
private: private:
EventTypeId m_eventTypeId; EventTypeId m_eventTypeId;
ThingId m_thingId; ThingId m_thingId;
ParamList m_params; ParamList m_params;
bool m_logged = false;
}; };
Q_DECLARE_METATYPE(Event) Q_DECLARE_METATYPE(Event)
QDebug operator<<(QDebug dbg, const Event &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 # define protocol versions
JSON_PROTOCOL_VERSION_MAJOR=7 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}" JSON_PROTOCOL_VERSION="$${JSON_PROTOCOL_VERSION_MAJOR}.$${JSON_PROTOCOL_VERSION_MINOR}"
LIBNYMEA_API_VERSION_MAJOR=7 LIBNYMEA_API_VERSION_MAJOR=7
LIBNYMEA_API_VERSION_MINOR=3 LIBNYMEA_API_VERSION_MINOR=3

View File

@ -1,4 +1,4 @@
7.0 7.1
{ {
"enums": { "enums": {
"BasicType": [ "BasicType": [
@ -169,10 +169,6 @@
"PermissionScopeConfigureRules", "PermissionScopeConfigureRules",
"PermissionScopeAdmin" "PermissionScopeAdmin"
], ],
"RemovePolicy": [
"RemovePolicyCascade",
"RemovePolicyUpdate"
],
"RepeatingMode": [ "RepeatingMode": [
"RepeatingModeNone", "RepeatingModeNone",
"RepeatingModeHourly", "RepeatingModeHourly",
@ -1121,22 +1117,13 @@
} }
}, },
"Integrations.RemoveThing": { "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": { "params": {
"o:removePolicy": "$ref:RemovePolicy", "d:o:removePolicy": "String",
"o:removePolicyList": [
{
"policy": "$ref:RemovePolicy",
"ruleId": "Uuid"
}
],
"thingId": "Uuid" "thingId": "Uuid"
}, },
"permissionScope": "PermissionScopeConfigureThings", "permissionScope": "PermissionScopeConfigureThings",
"returns": { "returns": {
"o:ruleIds": [
"Uuid"
],
"thingError": "$ref:ThingError" "thingError": "$ref:ThingError"
} }
}, },

View File

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

View File

@ -115,9 +115,7 @@ private slots:
void testStateBasedAction(); void testStateBasedAction();
void removePolicyUpdate(); void removeThingCleansRule();
void removePolicyCascade();
void removePolicyUpdateRendersUselessRule();
void testRuleActionParams_data(); void testRuleActionParams_data();
void testRuleActionParams(); void testRuleActionParams();
@ -2385,7 +2383,7 @@ void TestRules::testStateBasedAction()
qCDebug(dcTests()) << "Log entries:" << entries; qCDebug(dcTests()) << "Log entries:" << entries;
} }
void TestRules::removePolicyUpdate() void TestRules::removeThingCleansRule()
{ {
// ADD parent // ADD parent
QVariantMap params; QVariantMap params;
@ -2441,16 +2439,10 @@ void TestRules::removePolicyUpdate()
response = injectAndWait("Integrations.RemoveThing", params); response = injectAndWait("Integrations.RemoveThing", params);
verifyThingError(response, Thing::ThingErrorThingIsChild); 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 // Remove policy
params.clear(); response.clear(); params.clear(); response.clear();
params.insert("thingId", parentId); 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); response = injectAndWait("Integrations.RemoveThing", params);
verifyThingError(response); verifyThingError(response);
@ -2458,182 +2450,6 @@ void TestRules::removePolicyUpdate()
params.clear(); params.clear();
params.insert("ruleId", ruleId); params.insert("ruleId", ruleId);
response = injectAndWait("Rules.GetRuleDetails", params); 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); verifyRuleError(response, RuleEngine::RuleErrorRuleNotFound);
} }