diff --git a/libnymea-core/integrations/thingmanagerimplementation.cpp b/libnymea-core/integrations/thingmanagerimplementation.cpp index bb8d29a2..b3a0b2b9 100644 --- a/libnymea-core/integrations/thingmanagerimplementation.cpp +++ b/libnymea-core/integrations/thingmanagerimplementation.cpp @@ -59,6 +59,7 @@ //#include "unistd.h" #include "plugintimer.h" +#include "logging/logengine.h" #include #include @@ -70,9 +71,10 @@ #include #include -ThingManagerImplementation::ThingManagerImplementation(HardwareManager *hardwareManager, const QLocale &locale, QObject *parent) : +ThingManagerImplementation::ThingManagerImplementation(HardwareManager *hardwareManager, LogEngine *logEngine, const QLocale &locale, QObject *parent) : ThingManager(parent), m_hardwareManager(hardwareManager), + m_logEngine(logEngine), m_locale(locale), m_translator(new Translator(this)) { @@ -853,47 +855,66 @@ ThingSetupInfo* ThingManagerImplementation::addConfiguredThingInternal(const Thi } Thing::ThingError ThingManagerImplementation::removeConfiguredThing(const ThingId &thingId) +{ + Thing *thing = m_configuredThings.value(thingId); + if (!thing) { + return Thing::ThingErrorThingNotFound; + } + + if (!thing->parentId().isNull() && thing->autoCreated()) { + qCWarning(dcThingManager) << "Thing is an autocreated child of" << thing->parentId().toString() << ". Remove the parent instead."; + return Thing::ThingErrorThingIsChild; + } + + removeConfiguredThingInternal(thing); + + return Thing::ThingErrorNoError; +} + +void ThingManagerImplementation::removeConfiguredThingInternal(Thing *thing) { // We're checking thingSetupStatus and abort any pending setup here. As setup finished() // comes in as a QueuedConnection, make sure to process all events before going on so we // don't end up aborting an already finished setup instead of calling thingRemoved() on it. qApp->processEvents(); - Thing *thing = m_configuredThings.take(thingId); - if (!thing) { - return Thing::ThingErrorThingNotFound; - } - IntegrationPlugin *plugin = m_integrationPlugins.value(thing->pluginId()); - if (!plugin) { - qCWarning(dcThingManager()).nospace() << "Plugin not loaded for thing " << thing << ". Not calling thingRemoved on plugin."; - } else if (thing->setupStatus() == Thing::ThingSetupStatusInProgress) { - qCWarning(dcThingManager()).nospace() << "Thing " << thing << " is still being set up. Aborting setup."; - ThingSetupInfo *setupInfo = m_pendingSetups.value(thingId); - emit setupInfo->aborted(); - } else if (thing->setupStatus() == Thing::ThingSetupStatusComplete) { - plugin->thingRemoved(thing); - } + Things toBeRemoved = findChilds(thing->id()); + toBeRemoved.append(thing); + while (!toBeRemoved.isEmpty()) { + Thing *t = m_configuredThings.take(toBeRemoved.takeFirst()->id()); - thing->deleteLater(); - - NymeaSettings settings(NymeaSettings::SettingsRoleThings); - settings.beginGroup("ThingConfig"); - settings.beginGroup(thingId.toString()); - settings.remove(""); - settings.endGroup(); - - QFile::remove(statesCacheFile(thingId)); - - foreach (const IOConnectionId &ioConnectionId, m_ioConnections.keys()) { - IOConnection ioConnection = m_ioConnections.value(ioConnectionId); - if (ioConnection.inputThingId() == thing->id() || ioConnection.outputThingId() == thing->id()) { - disconnectIO(ioConnectionId); + IntegrationPlugin *plugin = m_integrationPlugins.value(t->pluginId()); + if (!plugin) { + qCWarning(dcThingManager()).nospace() << "Plugin not loaded for thing " << t << ". Not calling thingRemoved on plugin."; + } else if (thing->setupStatus() == Thing::ThingSetupStatusInProgress) { + qCWarning(dcThingManager()).nospace() << "Thing " << thing << " is still being set up. Aborting setup."; + ThingSetupInfo *setupInfo = m_pendingSetups.value(t->id()); + emit setupInfo->aborted(); + } else if (thing->setupStatus() == Thing::ThingSetupStatusComplete) { + plugin->thingRemoved(t); } + + t->deleteLater(); + + NymeaSettings settings(NymeaSettings::SettingsRoleThings); + settings.beginGroup("ThingConfig"); + settings.beginGroup(t->id().toString()); + settings.remove(""); + settings.endGroup(); + + QFile::remove(statesCacheFile(t->id())); + + foreach (const IOConnectionId &ioConnectionId, m_ioConnections.keys()) { + IOConnection ioConnection = m_ioConnections.value(ioConnectionId); + if (ioConnection.inputThingId() == t->id() || ioConnection.outputThingId() == t->id()) { + disconnectIO(ioConnectionId); + } + } + + m_logEngine->removeThingLogs(thing->id()); + + emit thingRemoved(t->id()); } - - emit thingRemoved(thingId); - - return Thing::ThingErrorNoError; } BrowseResult *ThingManagerImplementation::browseThing(const ThingId &thingId, const QString &itemId, const QLocale &locale) @@ -977,6 +998,9 @@ BrowserActionInfo* ThingManagerImplementation::executeBrowserItem(const BrowserA Thing *thing = m_configuredThings.value(browserAction.thingId()); BrowserActionInfo *info = new BrowserActionInfo(thing, this, browserAction, this, 30000); + connect(info, &BrowserActionInfo::finished, info->thing(), [this, info](){ + m_logEngine->logBrowserAction(info->browserAction(), info->status() == Thing::ThingErrorNoError ? Logging::LoggingLevelInfo : Logging::LoggingLevelAlert, info->status()); + }); if (!thing) { info->finish(Thing::ThingErrorThingNotFound); @@ -1009,6 +1033,9 @@ BrowserItemActionInfo* ThingManagerImplementation::executeBrowserItemAction(cons Thing *thing = m_configuredThings.value(browserItemAction.thingId()); BrowserItemActionInfo *info = new BrowserItemActionInfo(thing, this, browserItemAction, this, 30000); + connect(info, &BrowserItemActionInfo::finished, info->thing(), [this, info](){ + m_logEngine->logBrowserItemAction(info->browserItemAction(), info->status() == Thing::ThingErrorNoError ? Logging::LoggingLevelInfo : Logging::LoggingLevelAlert, info->status()); + }); if (!thing) { info->finish(Thing::ThingErrorThingNotFound); @@ -1359,6 +1386,7 @@ ThingActionInfo *ThingManagerImplementation::executeAction(const Action &action) ThingActionInfo *info = new ThingActionInfo(thing, finalAction, this, 15000); connect(info, &ThingActionInfo::finished, this, [=](){ + m_logEngine->logAction(finalAction, info->status()); emit actionExecuted(action, info->status()); }); @@ -1810,7 +1838,7 @@ void ThingManagerImplementation::onAutoThingDisappeared(const ThingId &thingId) return; } - emit thingDisappeared(thingId); + removeConfiguredThingInternal(thing); } void ThingManagerImplementation::onLoaded() @@ -1849,7 +1877,7 @@ void ThingManagerImplementation::onEventTriggered(Event event) } // configure logging if (thing->loggedEventTypeIds().contains(event.eventTypeId())) { - event.setLogged(true); + m_logEngine->logEvent(event); } // Forward the event @@ -1867,6 +1895,10 @@ void ThingManagerImplementation::slotThingStateValueChanged(const StateTypeId &s storeThingState(thing, stateTypeId); } + if (thing->loggedStateTypeIds().contains(stateTypeId)) { + m_logEngine->logStateChange(thing, stateTypeId, value); + } + emit thingStateChanged(thing, stateTypeId, value, minValue, maxValue); syncIOConnection(thing, stateTypeId); diff --git a/libnymea-core/integrations/thingmanagerimplementation.h b/libnymea-core/integrations/thingmanagerimplementation.h index 41cdea4e..50badd3c 100644 --- a/libnymea-core/integrations/thingmanagerimplementation.h +++ b/libnymea-core/integrations/thingmanagerimplementation.h @@ -61,6 +61,11 @@ class HardwareManager; class Translator; class ApiKeysProvidersLoader; +namespace nymeaserver { +class LogEngine; +} +using namespace nymeaserver; + class ThingManagerImplementation: public ThingManager { Q_OBJECT @@ -68,7 +73,7 @@ class ThingManagerImplementation: public ThingManager friend class IntegrationPlugin; public: - explicit ThingManagerImplementation(HardwareManager *hardwareManager, const QLocale &locale, QObject *parent = nullptr); + explicit ThingManagerImplementation(HardwareManager *hardwareManager, LogEngine *logEngine, const QLocale &locale, QObject *parent = nullptr); ~ThingManagerImplementation() override; static QStringList pluginSearchDirs(); @@ -131,9 +136,6 @@ public: ThingClass translateThingClass(const ThingClass &thingClass, const QLocale &locale) override; Vendor translateVendor(const Vendor &vendor, const QLocale &locale) override; -signals: - void loaded(); - private slots: void loadPlugins(); void loadPlugin(IntegrationPlugin *pluginIface); @@ -157,6 +159,7 @@ private: ParamList buildParams(const ParamTypes &types, const ParamList &first, const ParamList &second = ParamList()); void pairThingInternal(ThingPairingInfo *info); ThingSetupInfo *addConfiguredThingInternal(const ThingClassId &thingClassId, const QString &name, const ParamList ¶ms, const ThingId &parentId = ThingId()); + void removeConfiguredThingInternal(Thing *thing); ThingSetupInfo *reconfigureThingInternal(Thing *thing, const ParamList ¶ms, const QString &name = QString()); ThingSetupInfo *setupThing(Thing *thing); void initThing(Thing *thing); @@ -176,6 +179,7 @@ private: private: HardwareManager *m_hardwareManager; + nymeaserver::LogEngine *m_logEngine; QLocale m_locale; Translator *m_translator = nullptr; diff --git a/libnymea-core/jsonrpc/integrationshandler.cpp b/libnymea-core/jsonrpc/integrationshandler.cpp index e10add0a..3ff642b9 100644 --- a/libnymea-core/jsonrpc/integrationshandler.cpp +++ b/libnymea-core/jsonrpc/integrationshandler.cpp @@ -29,7 +29,6 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "integrationshandler.h" -#include "nymeacore.h" #include "integrations/thingmanager.h" #include "integrations/thing.h" #include "integrations/integrationplugin.h" @@ -43,9 +42,11 @@ #include "integrations/thingsetupinfo.h" #include "integrations/browseresult.h" #include "integrations/browseritemresult.h" +#include "ruleengine/ruleengine.h" #include #include +#include namespace nymeaserver { @@ -63,7 +64,6 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa registerEnum(); registerEnum(); registerEnum(); - registerEnum(); registerEnum(); registerEnum(); @@ -268,17 +268,10 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa registerMethod("SetStateFilter", description, params, returns, Types::PermissionScopeConfigureThings); params.clear(); returns.clear(); - description = "Remove a thing from the system."; + description = "Remove a thing and all its childs from the system. RemovePolicy is deprecated and has no effect any more."; params.insert("thingId", enumValueName(Uuid)); - params.insert("o:removePolicy", enumRef()); - QVariantMap policy; - policy.insert("ruleId", enumValueName(Uuid)); - policy.insert("policy", enumRef()); - QVariantList removePolicyList; - removePolicyList.append(policy); - params.insert("o:removePolicyList", removePolicyList); + params.insert("d:o:removePolicy", enumValueName(String)); returns.insert("thingError", enumRef()); - returns.insert("o:ruleIds", QVariantList() << enumValueName(Uuid)); registerMethod("RemoveThing", description, params, returns, Types::PermissionScopeConfigureThings); params.clear(); returns.clear(); @@ -443,7 +436,7 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa description = "Emitted whenever an Event is triggered."; params.insert("event", objectRef()); registerNotification("EventTriggered", description, params); - connect(NymeaCore::instance(), &NymeaCore::eventTriggered, this, [this](const Event &event){ + connect(m_thingManager, &ThingManager::eventTriggered, this, [this](const Event &event){ QVariantMap params; params.insert("event", pack(event)); emit EventTriggered(params); @@ -469,14 +462,14 @@ IntegrationsHandler::IntegrationsHandler(ThingManager *thingManager, QObject *pa emit IOConnectionRemoved(params); }); - connect(NymeaCore::instance(), &NymeaCore::pluginConfigChanged, this, &IntegrationsHandler::pluginConfigChanged); - connect(NymeaCore::instance(), &NymeaCore::thingStateChanged, this, &IntegrationsHandler::thingStateChanged); - connect(NymeaCore::instance(), &NymeaCore::thingRemoved, this, &IntegrationsHandler::thingRemovedNotification); - connect(NymeaCore::instance(), &NymeaCore::thingAdded, this, &IntegrationsHandler::thingAddedNotification); - connect(NymeaCore::instance(), &NymeaCore::thingChanged, this, &IntegrationsHandler::thingChangedNotification); - connect(NymeaCore::instance(), &NymeaCore::thingSettingChanged, this, &IntegrationsHandler::thingSettingChangedNotification); + connect(m_thingManager, &ThingManager::pluginConfigChanged, this, &IntegrationsHandler::pluginConfigChanged); + connect(m_thingManager, &ThingManager::thingStateChanged, this, &IntegrationsHandler::thingStateChanged); + connect(m_thingManager, &ThingManager::thingRemoved, this, &IntegrationsHandler::thingRemovedNotification); + connect(m_thingManager, &ThingManager::thingAdded, this, &IntegrationsHandler::thingAddedNotification); + connect(m_thingManager, &ThingManager::thingChanged, this, &IntegrationsHandler::thingChangedNotification); + connect(m_thingManager, &ThingManager::thingSettingChanged, this, &IntegrationsHandler::thingSettingChangedNotification); - connect(NymeaCore::instance(), &NymeaCore::initialized, this, [=](){ + connect(m_thingManager, &ThingManager::loaded, this, [=](){ // Generating cache hashes. // NOTE: We need to sort the lists to get a stable result QHash thingClassesMap; @@ -535,8 +528,8 @@ JsonReply* IntegrationsHandler::GetVendors(const QVariantMap ¶ms, const Json { Q_UNUSED(params) QVariantList vendors; - foreach (const Vendor &vendor, NymeaCore::instance()->thingManager()->supportedVendors()) { - Vendor translatedVendor = NymeaCore::instance()->thingManager()->translateVendor(vendor, context.locale()); + foreach (const Vendor &vendor, m_thingManager->supportedVendors()) { + Vendor translatedVendor = m_thingManager->translateVendor(vendor, context.locale()); vendors.append(pack(translatedVendor)); } @@ -550,7 +543,7 @@ JsonReply* IntegrationsHandler::GetThingClasses(const QVariantMap ¶ms, const QVariantMap returns; QVariantList thingClasses; - foreach (const ThingClass &thingClass, NymeaCore::instance()->thingManager()->supportedThings()) { + foreach (const ThingClass &thingClass, m_thingManager->supportedThings()) { if (params.contains("vendorId") && thingClass.vendorId() != VendorId(params.value("vendorId").toUuid())) { continue; } @@ -567,7 +560,7 @@ JsonReply* IntegrationsHandler::GetThingClasses(const QVariantMap ¶ms, const } } - ThingClass translatedThingClass = NymeaCore::instance()->thingManager()->translateThingClass(thingClass, context.locale()); + ThingClass translatedThingClass = m_thingManager->translateThingClass(thingClass, context.locale()); thingClasses.append(pack(translatedThingClass)); } @@ -586,7 +579,7 @@ JsonReply *IntegrationsHandler::DiscoverThings(const QVariantMap ¶ms, const ParamList discoveryParams = unpack(params.value("discoveryParams")); JsonReply *reply = createAsyncReply("DiscoverThings"); - ThingDiscoveryInfo *info = NymeaCore::instance()->thingManager()->discoverThings(thingClassId, discoveryParams); + ThingDiscoveryInfo *info = m_thingManager->discoverThings(thingClassId, discoveryParams); connect(info, &ThingDiscoveryInfo::finished, reply, [this, reply, info, locale](){ QVariantMap returns; returns.insert("thingError", enumValueName(info->status())); @@ -614,9 +607,9 @@ JsonReply* IntegrationsHandler::GetPlugins(const QVariantMap ¶ms, const Json { Q_UNUSED(params) QVariantList plugins; - foreach (IntegrationPlugin* plugin, NymeaCore::instance()->thingManager()->plugins()) { + foreach (IntegrationPlugin* plugin, m_thingManager->plugins()) { QVariantMap packedPlugin = pack(*plugin).toMap(); - packedPlugin["displayName"] = NymeaCore::instance()->thingManager()->translate(plugin->pluginId(), plugin->pluginDisplayName(), context.locale()); + packedPlugin["displayName"] = m_thingManager->translate(plugin->pluginId(), plugin->pluginDisplayName(), context.locale()); plugins.append(packedPlugin); } @@ -629,7 +622,7 @@ JsonReply *IntegrationsHandler::GetPluginConfiguration(const QVariantMap ¶ms { QVariantMap returns; - IntegrationPlugin *plugin = NymeaCore::instance()->thingManager()->plugins().findById(PluginId(params.value("pluginId").toString())); + IntegrationPlugin *plugin = m_thingManager->plugins().findById(PluginId(params.value("pluginId").toString())); if (!plugin) { returns.insert("thingError", enumValueName(Thing::ThingErrorPluginNotFound)); return createReply(returns); @@ -649,7 +642,7 @@ JsonReply* IntegrationsHandler::SetPluginConfiguration(const QVariantMap ¶ms QVariantMap returns; PluginId pluginId = PluginId(params.value("pluginId").toString()); ParamList pluginParams = unpack(params.value("configuration")); - Thing::ThingError result = NymeaCore::instance()->thingManager()->setPluginConfig(pluginId, pluginParams); + Thing::ThingError result = m_thingManager->setPluginConfig(pluginId, pluginParams); returns.insert("thingError",enumValueName(result)); return createReply(returns); } @@ -674,10 +667,10 @@ JsonReply* IntegrationsHandler::AddThing(const QVariantMap ¶ms, const JsonCo jsonReply->finished(); return jsonReply; } - info = NymeaCore::instance()->thingManager()->addConfiguredThing(thingClassId, thingParams, thingName); + info = m_thingManager->addConfiguredThing(thingClassId, thingParams, thingName); } else { - info = NymeaCore::instance()->thingManager()->addConfiguredThing(thingDescriptorId, thingParams, thingName); + info = m_thingManager->addConfiguredThing(thingDescriptorId, thingParams, thingName); } connect(info, &ThingSetupInfo::finished, jsonReply, [info, jsonReply, locale](){ QVariantMap returns; @@ -706,24 +699,24 @@ JsonReply *IntegrationsHandler::PairThing(const QVariantMap ¶ms, const JsonC ThingPairingInfo *info; if (params.contains("thingDescriptorId")) { ThingDescriptorId thingDescriptorId = ThingDescriptorId(params.value("thingDescriptorId").toString()); - info = NymeaCore::instance()->thingManager()->pairThing(thingDescriptorId, thingParams, thingName); + info = m_thingManager->pairThing(thingDescriptorId, thingParams, thingName); } else if (params.contains("thingId")) { ThingId thingId = ThingId(params.value("thingId").toString()); - info = NymeaCore::instance()->thingManager()->pairThing(thingId, thingParams, thingName); + info = m_thingManager->pairThing(thingId, thingParams, thingName); } else { ThingClassId thingClassId(params.value("thingClassId").toString()); - info = NymeaCore::instance()->thingManager()->pairThing(thingClassId, thingParams, thingName); + info = m_thingManager->pairThing(thingClassId, thingParams, thingName); } JsonReply *jsonReply = createAsyncReply("PairThing"); - connect(info, &ThingPairingInfo::finished, jsonReply, [jsonReply, info, locale](){ + connect(info, &ThingPairingInfo::finished, jsonReply, [jsonReply, info, locale, this](){ QVariantMap returns; returns.insert("thingError", enumValueName(info->status())); returns.insert("pairingTransactionId", info->transactionId().toString()); if (info->status() == Thing::ThingErrorNoError) { - ThingClass thingClass = NymeaCore::instance()->thingManager()->findThingClass(info->thingClassId()); + ThingClass thingClass = m_thingManager->findThingClass(info->thingClassId()); returns.insert("setupMethod", enumValueName(thingClass.setupMethod())); } @@ -751,7 +744,7 @@ JsonReply *IntegrationsHandler::ConfirmPairing(const QVariantMap ¶ms) JsonReply *jsonReply = createAsyncReply("ConfirmPairing"); - ThingPairingInfo *info = NymeaCore::instance()->thingManager()->confirmPairing(pairingTransactionId, username, secret); + ThingPairingInfo *info = m_thingManager->confirmPairing(pairingTransactionId, username, secret); connect(info, &ThingPairingInfo::finished, jsonReply, [info, jsonReply, locale](){ QVariantMap returns; @@ -774,22 +767,22 @@ JsonReply* IntegrationsHandler::GetThings(const QVariantMap ¶ms, const JsonC QVariantMap returns; QVariantList things; if (params.contains("thingId")) { - Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(ThingId(params.value("thingId").toString())); + Thing *thing = m_thingManager->findConfiguredThing(ThingId(params.value("thingId").toString())); if (!thing) { returns.insert("thingError", enumValueName(Thing::ThingErrorThingNotFound)); return createReply(returns); } else { QVariantMap packedThing = pack(thing).toMap(); - QString translatedSetupStatus = NymeaCore::instance()->thingManager()->translate(thing->pluginId(), thing->setupDisplayMessage(), context.locale()); + QString translatedSetupStatus = m_thingManager->translate(thing->pluginId(), thing->setupDisplayMessage(), context.locale()); if (!translatedSetupStatus.isEmpty()) { packedThing["setupDisplayMessage"] = translatedSetupStatus; } things.append(packedThing); } } else { - foreach (Thing *thing, NymeaCore::instance()->thingManager()->configuredThings()) { + foreach (Thing *thing, m_thingManager->configuredThings()) { QVariantMap packedThing = pack(thing).toMap(); - QString translatedSetupStatus = NymeaCore::instance()->thingManager()->translate(thing->pluginId(), thing->setupDisplayMessage(), context.locale()); + QString translatedSetupStatus = m_thingManager->translate(thing->pluginId(), thing->setupDisplayMessage(), context.locale()); if (!translatedSetupStatus.isEmpty()) { packedThing["setupDisplayMessage"] = translatedSetupStatus; } @@ -812,9 +805,9 @@ JsonReply *IntegrationsHandler::ReconfigureThing(const QVariantMap ¶ms, cons ThingSetupInfo *info; if (!thingDescriptorId.isNull()) { - info = NymeaCore::instance()->thingManager()->reconfigureThing(thingDescriptorId, thingParams); + info = m_thingManager->reconfigureThing(thingDescriptorId, thingParams); } else if (!thingId.isNull()){ - info = NymeaCore::instance()->thingManager()->reconfigureThing(thingId, thingParams); + info = m_thingManager->reconfigureThing(thingId, thingParams); } else { qCWarning(dcJsonRpc()) << "Either thingId or thingDescriptorId are required"; QVariantMap ret; @@ -842,7 +835,7 @@ JsonReply *IntegrationsHandler::EditThing(const QVariantMap ¶ms) qCDebug(dcJsonRpc()) << "Edit thing" << thingId << name; - Thing::ThingError status = NymeaCore::instance()->thingManager()->editThing(thingId, name); + Thing::ThingError status = m_thingManager->editThing(thingId, name); return createReply(statusToReply(status)); } @@ -851,33 +844,8 @@ JsonReply* IntegrationsHandler::RemoveThing(const QVariantMap ¶ms) { QVariantMap returns; ThingId thingId = ThingId(params.value("thingId").toString()); - - // global removePolicy has priority - if (params.contains("removePolicy")) { - RuleEngine::RemovePolicy removePolicy = params.value("removePolicy").toString() == "RemovePolicyCascade" ? RuleEngine::RemovePolicyCascade : RuleEngine::RemovePolicyUpdate; - Thing::ThingError status = NymeaCore::instance()->removeConfiguredThing(thingId, removePolicy); - returns.insert("thingError", enumValueName(status)); - return createReply(returns); - } - - QHash 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 > status = NymeaCore::instance()->removeConfiguredThing(thingId, removePolicyList); - returns.insert("thingError", enumValueName(status.first)); - - if (!status.second.isEmpty()) { - QVariantList ruleIdList; - foreach (const RuleId &ruleId, status.second) { - ruleIdList.append(ruleId.toString()); - } - returns.insert("ruleIds", ruleIdList); - } - + Thing::ThingError status = m_thingManager->removeConfiguredThing(thingId); + returns.insert("thingError", enumValueName(status)); return createReply(returns); } @@ -885,7 +853,7 @@ JsonReply *IntegrationsHandler::SetThingSettings(const QVariantMap ¶ms) { ThingId thingId = ThingId(params.value("thingId").toString()); ParamList settings = unpack(params.value("settings")); - Thing::ThingError status = NymeaCore::instance()->thingManager()->setThingSettings(thingId, settings); + Thing::ThingError status = m_thingManager->setThingSettings(thingId, settings); return createReply(statusToReply(status)); } @@ -894,7 +862,7 @@ JsonReply *IntegrationsHandler::SetStateLogging(const QVariantMap ¶ms) ThingId thingId = ThingId(params.value("thingId").toString()); StateTypeId stateTypeId = StateTypeId(params.value("stateTypeId").toUuid()); bool enabled = params.value("enabled").toBool(); - Thing::ThingError status = NymeaCore::instance()->thingManager()->setStateLogging(thingId, stateTypeId, enabled); + Thing::ThingError status = m_thingManager->setStateLogging(thingId, stateTypeId, enabled); return createReply(statusToReply(status)); } @@ -903,7 +871,7 @@ JsonReply *IntegrationsHandler::SetEventLogging(const QVariantMap ¶ms) ThingId thingId = ThingId(params.value("thingId").toString()); EventTypeId eventTypeId = EventTypeId(params.value("eventTypeId").toUuid()); bool enabled = params.value("enabled").toBool(); - Thing::ThingError status = NymeaCore::instance()->thingManager()->setEventLogging(thingId, eventTypeId, enabled); + Thing::ThingError status = m_thingManager->setEventLogging(thingId, eventTypeId, enabled); return createReply(statusToReply(status)); } @@ -914,14 +882,14 @@ JsonReply *IntegrationsHandler::SetStateFilter(const QVariantMap ¶ms) QString filterString = params.value("filter").toString(); QMetaEnum metaEnum = QMetaEnum::fromType(); Types::StateValueFilter filter = static_cast(metaEnum.keyToValue(filterString.toUtf8())); - Thing::ThingError status = NymeaCore::instance()->thingManager()->setStateFilter(thingId, stateTypeId, filter); + Thing::ThingError status = m_thingManager->setStateFilter(thingId, stateTypeId, filter); return createReply(statusToReply(status)); } JsonReply* IntegrationsHandler::GetEventTypes(const QVariantMap ¶ms, const JsonContext &context) const { - ThingClass thingClass = NymeaCore::instance()->thingManager()->findThingClass(ThingClassId(params.value("thingClassId").toString())); - ThingClass translatedThingClass = NymeaCore::instance()->thingManager()->translateThingClass(thingClass, context.locale()); + ThingClass thingClass = m_thingManager->findThingClass(ThingClassId(params.value("thingClassId").toString())); + ThingClass translatedThingClass = m_thingManager->translateThingClass(thingClass, context.locale()); QVariantMap returns; returns.insert("eventTypes", pack(translatedThingClass.eventTypes())); @@ -930,8 +898,8 @@ JsonReply* IntegrationsHandler::GetEventTypes(const QVariantMap ¶ms, const J JsonReply* IntegrationsHandler::GetActionTypes(const QVariantMap ¶ms, const JsonContext &context) const { - ThingClass thingClass = NymeaCore::instance()->thingManager()->findThingClass(ThingClassId(params.value("thingClassId").toString())); - ThingClass translatedThingClass = NymeaCore::instance()->thingManager()->translateThingClass(thingClass, context.locale()); + ThingClass thingClass = m_thingManager->findThingClass(ThingClassId(params.value("thingClassId").toString())); + ThingClass translatedThingClass = m_thingManager->translateThingClass(thingClass, context.locale()); QVariantMap returns; returns.insert("actionTypes", pack(translatedThingClass.actionTypes())); @@ -940,8 +908,8 @@ JsonReply* IntegrationsHandler::GetActionTypes(const QVariantMap ¶ms, const JsonReply* IntegrationsHandler::GetStateTypes(const QVariantMap ¶ms, const JsonContext &context) const { - ThingClass thingClass = NymeaCore::instance()->thingManager()->findThingClass(ThingClassId(params.value("thingClassId").toString())); - ThingClass translatedThingClass = NymeaCore::instance()->thingManager()->translateThingClass(thingClass, context.locale()); + ThingClass thingClass = m_thingManager->findThingClass(ThingClassId(params.value("thingClassId").toString())); + ThingClass translatedThingClass = m_thingManager->translateThingClass(thingClass, context.locale()); QVariantMap returns; returns.insert("stateTypes", pack(translatedThingClass.stateTypes())); @@ -950,7 +918,7 @@ JsonReply* IntegrationsHandler::GetStateTypes(const QVariantMap ¶ms, const J JsonReply* IntegrationsHandler::GetStateValue(const QVariantMap ¶ms) const { - Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(ThingId(params.value("thingId").toString())); + Thing *thing = m_thingManager->findConfiguredThing(ThingId(params.value("thingId").toString())); if (!thing) { return createReply(statusToReply(Thing::ThingErrorThingNotFound)); } @@ -966,7 +934,7 @@ JsonReply* IntegrationsHandler::GetStateValue(const QVariantMap ¶ms) const JsonReply *IntegrationsHandler::GetStateValues(const QVariantMap ¶ms) const { - Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(ThingId(params.value("thingId").toString())); + Thing *thing = m_thingManager->findConfiguredThing(ThingId(params.value("thingId").toString())); if (!thing) { return createReply(statusToReply(Thing::ThingErrorThingNotFound)); } @@ -983,7 +951,7 @@ JsonReply *IntegrationsHandler::BrowseThing(const QVariantMap ¶ms, const Jso JsonReply *jsonReply = createAsyncReply("BrowseThing"); - BrowseResult *result = NymeaCore::instance()->thingManager()->browseThing(thingId, itemId, context.locale()); + BrowseResult *result = m_thingManager->browseThing(thingId, itemId, context.locale()); connect(result, &BrowseResult::finished, jsonReply, [this, jsonReply, result, context](){ QVariantMap returns = statusToReply(result->status()); @@ -1010,7 +978,7 @@ JsonReply *IntegrationsHandler::GetBrowserItem(const QVariantMap ¶ms, const JsonReply *jsonReply = createAsyncReply("GetBrowserItem"); - BrowserItemResult *result = NymeaCore::instance()->thingManager()->browserItemDetails(thingId, itemId, context.locale()); + BrowserItemResult *result = m_thingManager->browserItemDetails(thingId, itemId, context.locale()); connect(result, &BrowserItemResult::finished, jsonReply, [this, jsonReply, result, context](){ QVariantMap params = statusToReply(result->status()); if (result->status() == Thing::ThingErrorNoError) { @@ -1038,7 +1006,7 @@ JsonReply *IntegrationsHandler::ExecuteAction(const QVariantMap ¶ms, const J JsonReply *jsonReply = createAsyncReply("ExecuteAction"); - ThingActionInfo *info = NymeaCore::instance()->thingManager()->executeAction(action); + ThingActionInfo *info = m_thingManager->executeAction(action); connect(info, &ThingActionInfo::finished, jsonReply, [info, jsonReply, locale](){ QVariantMap data; data.insert("thingError", enumValueName(info->status())); @@ -1060,7 +1028,7 @@ JsonReply *IntegrationsHandler::ExecuteBrowserItem(const QVariantMap ¶ms, co JsonReply *jsonReply = createAsyncReply("ExecuteBrowserItem"); - BrowserActionInfo *info = NymeaCore::instance()->executeBrowserItem(action); + BrowserActionInfo *info = m_thingManager->executeBrowserItem(action); connect(info, &BrowserActionInfo::finished, jsonReply, [info, jsonReply, context](){ QVariantMap data; data.insert("thingError", enumValueName(info->status())); @@ -1084,7 +1052,7 @@ JsonReply *IntegrationsHandler::ExecuteBrowserItemAction(const QVariantMap ¶ JsonReply *jsonReply = createAsyncReply("ExecuteBrowserItemAction"); - BrowserItemActionInfo *info = NymeaCore::instance()->executeBrowserItemAction(browserItemAction); + BrowserItemActionInfo *info = m_thingManager->executeBrowserItemAction(browserItemAction); connect(info, &BrowserItemActionInfo::finished, jsonReply, [info, jsonReply, context](){ QVariantMap data; data.insert("thingError", enumValueName(info->status())); diff --git a/libnymea-core/jsonrpc/jsonrpcserverimplementation.cpp b/libnymea-core/jsonrpc/jsonrpcserverimplementation.cpp index 2517fa77..1db3d974 100644 --- a/libnymea-core/jsonrpc/jsonrpcserverimplementation.cpp +++ b/libnymea-core/jsonrpc/jsonrpcserverimplementation.cpp @@ -525,7 +525,7 @@ void JsonRPCServerImplementation::setup() { registerHandler(this); registerHandler(new IntegrationsHandler(NymeaCore::instance()->thingManager(), this)); - registerHandler(new RulesHandler(this)); + registerHandler(new RulesHandler(NymeaCore::instance()->ruleEngine(), this)); registerHandler(new LoggingHandler(this)); registerHandler(new ConfigurationHandler(this)); registerHandler(new NetworkManagerHandler(NymeaCore::instance()->networkManager(), this)); diff --git a/libnymea-core/jsonrpc/ruleshandler.cpp b/libnymea-core/jsonrpc/ruleshandler.cpp index 4fb12fd2..83987703 100644 --- a/libnymea-core/jsonrpc/ruleshandler.cpp +++ b/libnymea-core/jsonrpc/ruleshandler.cpp @@ -62,7 +62,6 @@ */ #include "ruleshandler.h" -#include "nymeacore.h" #include "ruleengine/ruleengine.h" #include "loggingcategories.h" @@ -72,8 +71,9 @@ namespace nymeaserver { /*! Constructs a new \l{RulesHandler} with the given \a parent. */ -RulesHandler::RulesHandler(QObject *parent) : - JsonHandler(parent) +RulesHandler::RulesHandler(RuleEngine *ruleEngine, QObject *parent) : + JsonHandler(parent), + m_ruleEngine(ruleEngine) { // Enums registerEnum(); @@ -218,10 +218,10 @@ RulesHandler::RulesHandler(QObject *parent) : params.insert("rule", objectRef("Rule")); registerNotification("RuleConfigurationChanged", description, params); - connect(NymeaCore::instance(), &NymeaCore::ruleAdded, this, &RulesHandler::ruleAddedNotification); - connect(NymeaCore::instance(), &NymeaCore::ruleRemoved, this, &RulesHandler::ruleRemovedNotification); - connect(NymeaCore::instance(), &NymeaCore::ruleActiveChanged, this, &RulesHandler::ruleActiveChangedNotification); - connect(NymeaCore::instance(), &NymeaCore::ruleConfigurationChanged, this, &RulesHandler::ruleConfigurationChangedNotification); + connect(m_ruleEngine, &RuleEngine::ruleAdded, this, &RulesHandler::ruleAddedNotification); + connect(m_ruleEngine, &RuleEngine::ruleRemoved, this, &RulesHandler::ruleRemovedNotification); + connect(m_ruleEngine, &RuleEngine::ruleActiveChanged, this, &RulesHandler::ruleActiveChangedNotification); + connect(m_ruleEngine, &RuleEngine::ruleConfigurationChanged, this, &RulesHandler::ruleConfigurationChangedNotification); } /*! Returns the name of the \l{RulesHandler}. In this case \b Rules.*/ @@ -235,7 +235,7 @@ JsonReply* RulesHandler::GetRules(const QVariantMap ¶ms) Q_UNUSED(params) QVariantList rulesList; - foreach (const Rule &rule, NymeaCore::instance()->ruleEngine()->rules()) { + foreach (const Rule &rule, m_ruleEngine->rules()) { rulesList.append(packRuleDescription(rule)); } @@ -247,7 +247,7 @@ JsonReply* RulesHandler::GetRules(const QVariantMap ¶ms) JsonReply *RulesHandler::GetRuleDetails(const QVariantMap ¶ms) { RuleId ruleId = RuleId(params.value("ruleId").toString()); - Rule rule = NymeaCore::instance()->ruleEngine()->findRule(ruleId); + Rule rule = m_ruleEngine->findRule(ruleId); if (rule.id().isNull()) { QVariantMap data; data.insert("ruleError", enumValueName(RuleEngine::RuleErrorRuleNotFound)); @@ -264,7 +264,7 @@ JsonReply* RulesHandler::AddRule(const QVariantMap ¶ms) Rule rule = unpack(params); rule.setId(RuleId::createRuleId()); - RuleEngine::RuleError status = NymeaCore::instance()->ruleEngine()->addRule(rule); + RuleEngine::RuleError status = m_ruleEngine->addRule(rule); QVariantMap returns; if (status == RuleEngine::RuleErrorNoError) { returns.insert("ruleId", rule.id().toString()); @@ -280,10 +280,10 @@ JsonReply *RulesHandler::EditRule(const QVariantMap ¶ms) // FIXME: Edit rule API currently has "ruleId" while the Rule type has "id". Auto unpacking will fail for this property rule.setId(params.value("ruleId").toUuid()); - RuleEngine::RuleError status = NymeaCore::instance()->ruleEngine()->editRule(rule); + RuleEngine::RuleError status = m_ruleEngine->editRule(rule); QVariantMap returns; if (status == RuleEngine::RuleErrorNoError) { - returns.insert("rule", pack(NymeaCore::instance()->ruleEngine()->findRule(rule.id()))); + returns.insert("rule", pack(m_ruleEngine->findRule(rule.id()))); } returns.insert("ruleError", enumValueName(status)); return createReply(returns); @@ -293,7 +293,7 @@ JsonReply* RulesHandler::RemoveRule(const QVariantMap ¶ms) { QVariantMap returns; RuleId ruleId(params.value("ruleId").toString()); - RuleEngine::RuleError status = NymeaCore::instance()->removeRule(ruleId); + RuleEngine::RuleError status = m_ruleEngine->removeRule(ruleId); returns.insert("ruleError", enumValueName(status)); return createReply(returns); } @@ -301,7 +301,7 @@ JsonReply* RulesHandler::RemoveRule(const QVariantMap ¶ms) JsonReply *RulesHandler::FindRules(const QVariantMap ¶ms) { ThingId thingId = ThingId(params.value("thingId").toString()); - QList rules = NymeaCore::instance()->ruleEngine()->findRules(thingId); + QList rules = m_ruleEngine->findRules(thingId); QVariantList rulesList; foreach (const RuleId &ruleId, rules) { @@ -315,7 +315,7 @@ JsonReply *RulesHandler::FindRules(const QVariantMap ¶ms) JsonReply *RulesHandler::EnableRule(const QVariantMap ¶ms) { - RuleEngine::RuleError status = NymeaCore::instance()->ruleEngine()->enableRule(RuleId(params.value("ruleId").toString())); + RuleEngine::RuleError status = m_ruleEngine->enableRule(RuleId(params.value("ruleId").toString())); QVariantMap ret; ret.insert("ruleError", enumValueName(status)); return createReply(ret); @@ -323,7 +323,7 @@ JsonReply *RulesHandler::EnableRule(const QVariantMap ¶ms) JsonReply *RulesHandler::DisableRule(const QVariantMap ¶ms) { - RuleEngine::RuleError status = NymeaCore::instance()->ruleEngine()->disableRule(RuleId(params.value("ruleId").toString())); + RuleEngine::RuleError status = m_ruleEngine->disableRule(RuleId(params.value("ruleId").toString())); QVariantMap ret; ret.insert("ruleError", enumValueName(status)); return createReply(ret); @@ -333,7 +333,7 @@ JsonReply *RulesHandler::ExecuteActions(const QVariantMap ¶ms) { QVariantMap returns; RuleId ruleId(params.value("ruleId").toString()); - RuleEngine::RuleError status = NymeaCore::instance()->ruleEngine()->executeActions(ruleId); + RuleEngine::RuleError status = m_ruleEngine->executeActions(ruleId); returns.insert("ruleError", enumValueName(status)); return createReply(returns); } @@ -342,7 +342,7 @@ JsonReply *RulesHandler::ExecuteExitActions(const QVariantMap ¶ms) { QVariantMap returns; RuleId ruleId(params.value("ruleId").toString()); - RuleEngine::RuleError status = NymeaCore::instance()->ruleEngine()->executeExitActions(ruleId); + RuleEngine::RuleError status = m_ruleEngine->executeExitActions(ruleId); returns.insert("ruleError", enumValueName(status)); return createReply(returns); } diff --git a/libnymea-core/jsonrpc/ruleshandler.h b/libnymea-core/jsonrpc/ruleshandler.h index fe54875a..bcbae501 100644 --- a/libnymea-core/jsonrpc/ruleshandler.h +++ b/libnymea-core/jsonrpc/ruleshandler.h @@ -37,11 +37,13 @@ namespace nymeaserver { +class RuleEngine; + class RulesHandler : public JsonHandler { Q_OBJECT public: - explicit RulesHandler(QObject *parent = nullptr); + explicit RulesHandler(RuleEngine *ruleEngine, QObject *parent = nullptr); QString name() const override; @@ -73,6 +75,9 @@ private slots: private: QVariantMap packRuleDescription(const Rule &rule); + +private: + RuleEngine *m_ruleEngine = nullptr; }; } diff --git a/libnymea-core/logging/logengine.cpp b/libnymea-core/logging/logengine.cpp index b30f18c2..3794ee51 100644 --- a/libnymea-core/logging/logengine.cpp +++ b/libnymea-core/logging/logengine.cpp @@ -106,14 +106,6 @@ LogEngine::~LogEngine() m_db.close(); } -void LogEngine::setThingManager(ThingManager *thingManager) -{ - m_thingManager = thingManager; - connect(thingManager, &ThingManager::eventTriggered, this, &LogEngine::logEvent); - connect(thingManager, &ThingManager::thingStateChanged, this, &LogEngine::logStateChange); - connect(thingManager, &ThingManager::actionExecuted, this, &LogEngine::logAction); -} - LogEntriesFetchJob *LogEngine::fetchLogEntries(const LogFilter &filter) { QList results; @@ -233,10 +225,6 @@ void LogEngine::logSystemEvent(const QDateTime &dateTime, bool active, Logging:: void LogEngine::logEvent(const Event &event) { - if (!event.logged()) { - return; - } - QVariantList valueList; foreach (const Param ¶m, event.params()) { valueList << param.value(); @@ -255,10 +243,6 @@ void LogEngine::logEvent(const Event &event) void LogEngine::logStateChange(Thing *thing, const StateTypeId &stateTypeId, const QVariant &value) { - if (!thing->loggedStateTypeIds().contains(stateTypeId)) { - return; - } - LogEntry entry(Logging::LoggingSourceStates); entry.setTypeId(stateTypeId); entry.setThingId(thing->id()); diff --git a/libnymea-core/logging/logengine.h b/libnymea-core/logging/logengine.h index b1b13372..b8a9d584 100644 --- a/libnymea-core/logging/logengine.h +++ b/libnymea-core/logging/logengine.h @@ -61,8 +61,6 @@ public: LogEngine(const QString &driver, const QString &dbName, const QString &hostname = QString("127.0.0.1"), const QString &username = QString(), const QString &password = QString(), int maxDBSize = 50000, QObject *parent = nullptr); ~LogEngine(); - void setThingManager(ThingManager *thingManager); - LogEntriesFetchJob *fetchLogEntries(const LogFilter &filter = LogFilter()); ThingsFetchJob *fetchThings(); @@ -76,6 +74,9 @@ public: public slots: void logSystemEvent(const QDateTime &dateTime, bool active, Logging::LoggingLevel level = Logging::LoggingLevelInfo); + void logEvent(const Event &event); + void logStateChange(Thing *thing, const StateTypeId &stateTypeId, const QVariant &value); + void logAction(const Action &action, Thing::ThingError status); void logBrowserAction(const BrowserAction &browserAction, Logging::LoggingLevel level = Logging::LoggingLevelInfo, int errorCode = 0); void logBrowserItemAction(const BrowserItemAction &browserItemAction, Logging::LoggingLevel level = Logging::LoggingLevelInfo, int errorCode = 0); void logRuleTriggered(const Rule &rule); @@ -84,11 +85,6 @@ public slots: void logRuleActionsExecuted(const Rule &rule); void logRuleExitActionsExecuted(const Rule &rule); -private slots: - void logEvent(const Event &event); - void logStateChange(Thing *thing, const StateTypeId &stateTypeId, const QVariant &value); - void logAction(const Action &action, Thing::ThingError status); - signals: void logEntryAdded(const LogEntry &logEntry); void logDatabaseUpdated(); @@ -122,8 +118,6 @@ private: bool m_initialized = false; bool m_dbMalformed = false; - ThingManager *m_thingManager = nullptr; - // When maxQueueLength is exceeded, jobs will be flagged and discarded if this source logs more events int m_maxQueueLength; QHash> m_flaggedJobs; diff --git a/libnymea-core/nymeacore.cpp b/libnymea-core/nymeacore.cpp index c1ff4619..011ea748 100644 --- a/libnymea-core/nymeacore.cpp +++ b/libnymea-core/nymeacore.cpp @@ -121,15 +121,14 @@ void NymeaCore::init(const QStringList &additionalInterfaces) { qCDebug(dcCore) << "Creating Hardware Manager"; m_hardwareManager = new HardwareManagerImplementation(m_platform, m_serverManager->mqttBroker(), m_zigbeeManager, m_zwaveManager, m_modbusRtuManager, this); - qCDebug(dcCore) << "Creating Thing Manager (locale:" << m_configuration->locale() << ")"; - m_thingManager = new ThingManagerImplementation(m_hardwareManager, m_configuration->locale(), this); - - qCDebug(dcCore) << "Creating Rule Engine"; - m_ruleEngine = new RuleEngine(this); - qCDebug(dcCore) << "Creating Log Engine"; m_logger = new LogEngine(m_configuration->logDBDriver(), m_configuration->logDBName(), m_configuration->logDBHost(), m_configuration->logDBUser(), m_configuration->logDBPassword(), m_configuration->logDBMaxEntries(), this); - m_logger->setThingManager(m_thingManager); + + qCDebug(dcCore) << "Creating Thing Manager (locale:" << m_configuration->locale() << ")"; + m_thingManager = new ThingManagerImplementation(m_hardwareManager, m_logger, m_configuration->locale(), this); + + qCDebug(dcCore) << "Creating Rule Engine"; + m_ruleEngine = new RuleEngine(m_thingManager, m_timeManager, m_logger, this); qCDebug(dcCore()) << "Creating Script Engine"; m_scriptEngine = new scriptengine::ScriptEngine(m_thingManager, this); @@ -151,22 +150,8 @@ void NymeaCore::init(const QStringList &additionalInterfaces) { connect(m_configuration, &NymeaConfiguration::serverNameChanged, m_serverManager, &ServerManager::setServerName); - connect(m_thingManager, &ThingManagerImplementation::pluginConfigChanged, this, &NymeaCore::pluginConfigChanged); - connect(m_thingManager, &ThingManagerImplementation::eventTriggered, this, &NymeaCore::onEventTriggered); - connect(m_thingManager, &ThingManagerImplementation::thingStateChanged, this, &NymeaCore::onThingStateChanged); - connect(m_thingManager, &ThingManagerImplementation::thingAdded, this, &NymeaCore::thingAdded); - connect(m_thingManager, &ThingManagerImplementation::thingChanged, this, &NymeaCore::thingChanged); - connect(m_thingManager, &ThingManagerImplementation::thingSettingChanged, this, &NymeaCore::thingSettingChanged); - connect(m_thingManager, &ThingManagerImplementation::thingRemoved, this, &NymeaCore::thingRemoved); - connect(m_thingManager, &ThingManagerImplementation::thingDisappeared, this, &NymeaCore::onThingDisappeared); connect(m_thingManager, &ThingManagerImplementation::loaded, this, &NymeaCore::thingManagerLoaded); - connect(m_ruleEngine, &RuleEngine::ruleAdded, this, &NymeaCore::ruleAdded); - connect(m_ruleEngine, &RuleEngine::ruleRemoved, this, &NymeaCore::ruleRemoved); - connect(m_ruleEngine, &RuleEngine::ruleConfigurationChanged, this, &NymeaCore::ruleConfigurationChanged); - - connect(m_timeManager, &TimeManager::dateTimeChanged, this, &NymeaCore::onDateTimeChanged); - m_logger->logSystemEvent(m_timeManager->currentDateTime(), true); } @@ -215,322 +200,6 @@ void NymeaCore::destroy() s_instance = nullptr; } -QPair > NymeaCore::removeConfiguredThing(const ThingId &thingId, const QHash &removePolicyList) -{ - Thing *thing = m_thingManager->findConfiguredThing(thingId); - - if (!thing) { - return QPair > (Thing::ThingErrorThingNotFound, QList()); - } - - // 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::ThingErrorThingIsChild, QList()); - } - - // 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::ThingErrorCreationMethodNotSupported, {}); -// } - - // Check if this thing has childs - QList thingsToRemove; - thingsToRemove.append(thing); - QList childs = m_thingManager->findChilds(thingId); - if (!childs.isEmpty()) { - foreach (Thing *child, childs) { - thingsToRemove.append(child); - } - } - - // check things - QList 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 toBeChanged; - QList 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::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::ThingErrorNoError, QList()); -} - - -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 thingsToRemove; - thingsToRemove.append(thing); - QList childs = m_thingManager->findChilds(thingId); - if (!childs.isEmpty()) { - foreach (Thing *child, childs) { - thingsToRemove.append(child); - } - } - - // check things - QList 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 ruleActions) -{ - QList actions; - QList browserActions; - foreach (const RuleAction &ruleAction, ruleActions) { - if (ruleAction.type() == RuleAction::TypeThing) { - Thing *thing = m_thingManager->findConfiguredThing(ruleAction.thingId()); - if (!thing) { - qCWarning(dcRuleEngine()) << "Unable to find thing" << ruleAction.thingId() << "for rule action" << ruleAction; - continue; - } - ActionTypeId actionTypeId = ruleAction.actionTypeId(); - ParamList params; - bool ok = true; - foreach (const RuleActionParam &ruleActionParam, ruleAction.ruleActionParams()) { - if (ruleActionParam.isValueBased()) { - params.append(Param(ruleActionParam.paramTypeId(), ruleActionParam.value())); - } else if (ruleActionParam.isStateBased()) { - Thing *stateThing = m_thingManager->findConfiguredThing(ruleActionParam.stateThingId()); - if (!stateThing) { - qCWarning(dcRuleEngine()) << "Cannot find thing" << ruleActionParam.stateThingId() << "required by rule action"; - ok = false; - break; - } - ThingClass stateThingClass = m_thingManager->findThingClass(stateThing->thingClassId()); - if (!stateThingClass.hasStateType(ruleActionParam.stateTypeId())) { - qCWarning(dcRuleEngine()) << "Device" << thing->name() << thing->id() << "does not have a state type" << ruleActionParam.stateTypeId(); - ok = false; - break; - } - params.append(Param(ruleActionParam.paramTypeId(), stateThing->stateValue(ruleActionParam.stateTypeId()))); - } - } - if (!ok) { - qCWarning(dcRuleEngine()) << "Not executing rule action"; - continue; - } - Action action(actionTypeId, thing->id(), Action::TriggeredByRule); - action.setParams(params); - actions.append(action); - } else if (ruleAction.type() == RuleAction::TypeBrowser) { - Thing *thing = m_thingManager->findConfiguredThing(ruleAction.thingId()); - if (!thing) { - qCWarning(dcRuleEngine()) << "Unable to find thing" << ruleAction.thingId() << "for rule action" << ruleAction; - continue; - } - BrowserAction browserAction(ruleAction.thingId(), ruleAction.browserItemId()); - browserActions.append(browserAction); - } else { - Things things = m_thingManager->findConfiguredThings(ruleAction.interface()); - foreach (Thing* thing, things) { - ThingClass thingClass = m_thingManager->findThingClass(thing->thingClassId()); - ActionType actionType = thingClass.actionTypes().findByName(ruleAction.interfaceAction()); - if (actionType.id().isNull()) { - qCWarning(dcRuleEngine()) << "Error creating Action. The given ThingClass does not implement action:" << ruleAction.interfaceAction(); - continue; - } - - ParamList params; - bool ok = true; - foreach (const RuleActionParam &ruleActionParam, ruleAction.ruleActionParams()) { - ParamType paramType = actionType.paramTypes().findByName(ruleActionParam.paramName()); - if (paramType.id().isNull()) { - qCWarning(dcRuleEngine()) << "Error creating Action. The given ActionType does not have a parameter:" << ruleActionParam.paramName(); - ok = false; - continue; - } - if (ruleActionParam.isValueBased()) { - params.append(Param(paramType.id(), ruleActionParam.value())); - } else if (ruleActionParam.isStateBased()) { - Thing *stateThing = m_thingManager->findConfiguredThing(ruleActionParam.stateThingId()); - if (!stateThing) { - qCWarning(dcRuleEngine()) << "Cannot find thing" << ruleActionParam.stateThingId() << "required by rule action"; - ok = false; - break; - } - ThingClass stateThingClass = m_thingManager->findThingClass(stateThing->thingClassId()); - if (!stateThingClass.hasStateType(ruleActionParam.stateTypeId())) { - qCWarning(dcRuleEngine()) << "Thing" << thing->name() << thing->id() << "does not have a state type" << ruleActionParam.stateTypeId(); - ok = false; - break; - } - params.append(Param(paramType.id(), stateThing->stateValue(ruleActionParam.stateTypeId()))); - } - } - if (!ok) { - qCWarning(dcRuleEngine()) << "Not executing rule action"; - continue; - } - - Action action = Action(actionType.id(), thing->id(), Action::TriggeredByRule); - action.setParams(params); - actions.append(action); - } - } - } - - foreach (const Action &action, actions) { - qCDebug(dcRuleEngine) << "Executing action" << action.actionTypeId() << action.params(); - ThingActionInfo *info = m_thingManager->executeAction(action); - connect(info, &ThingActionInfo::finished, this, [info](){ - if (info->status() != Thing::ThingErrorNoError) { - qCWarning(dcRuleEngine) << "Error executing action:" << info->status() << info->displayMessage(); - } - }); - } - - foreach (const BrowserAction &browserAction, browserActions) { - BrowserActionInfo *info = executeBrowserItem(browserAction); - connect(info, &BrowserActionInfo::finished, this, [info](){ - if (info->status() != Thing::ThingErrorNoError) { - qCWarning(dcRuleEngine) << "Error executing browser action:" << info->status(); - } - }); - } -} - -/*! Calls the metheod RuleEngine::removeRule(\a id). - * \sa RuleEngine, */ -RuleEngine::RuleError NymeaCore::removeRule(const RuleId &id) -{ - RuleEngine::RuleError removeError = m_ruleEngine->removeRule(id); - if (removeError == RuleEngine::RuleErrorNoError) - m_logger->removeRuleLogs(id); - - return removeError; -} - NymeaConfiguration *NymeaCore::configuration() const { return m_configuration; @@ -663,117 +332,6 @@ ExperienceManager *NymeaCore::experienceManager() const return m_experienceManager; } -void NymeaCore::onEventTriggered(const Event &event) -{ - emit eventTriggered(event); - evaluateRules(event); -} - -void NymeaCore::onThingStateChanged(Thing *thing, const StateTypeId &stateTypeId, const QVariant &value, const QVariant &minValue, const QVariant &maxValue) -{ - emit thingStateChanged(thing, stateTypeId, value, minValue, maxValue); - - // The rule engine can have event based rules that would trigger when a state changes - // without "binding" to the state (as a stateEvaluator would do). So generate a fake event - // for every state change. - // TODO: This whole rule engine related code in this file should probably move into the RuleEngine itself. - Param valueParam(ParamTypeId(stateTypeId.toString()), value); - Event event(EventTypeId(stateTypeId.toString()), thing->id(), ParamList() << valueParam); - evaluateRules(event); -} - -void NymeaCore::evaluateRules(const Event &event) -{ - QList actions; - QList 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 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 actions; - foreach (const Rule &rule, m_ruleEngine->evaluateTime(dateTime)) { - // TimeEvent based - if (!rule.timeDescriptor().timeEventItems().isEmpty()) { - m_logger->logRuleTriggered(rule); - if (rule.statesActive() && rule.timeActive()) { - actions.append(rule.actions()); - } else { - actions.append(rule.exitActions()); - } - } else { - // Calendar based rule - m_logger->logRuleActiveChanged(rule); - emit ruleActiveChanged(rule); - if (rule.active()) { - actions.append(rule.actions()); - } else { - actions.append(rule.exitActions()); - } - } - } - executeRuleActions(actions); -} - LogEngine* NymeaCore::logEngine() const { return m_logger; @@ -784,65 +342,8 @@ JsonRPCServerImplementation *NymeaCore::jsonRPCServer() const return m_serverManager->jsonServer(); } -void NymeaCore::onThingDisappeared(const ThingId &thingId) -{ - Thing *thing = m_thingManager->findConfiguredThing(thingId); - if (!thing) { - return; - } - - // Check if this thing has childs - Things thingsToRemove; - thingsToRemove.append(thing); - QList childs = m_thingManager->findChilds(thingId); - if (!childs.isEmpty()) { - foreach (Thing *child, childs) { - thingsToRemove.append(child); - } - } - - // check things - QList offendingRules; - qCDebug(dcThingManager) << "Thing to remove:"; - foreach (Thing *d, thingsToRemove) { - qCDebug(dcThingManager) << " -> " << d->name() << d->id().toString(); - - // Check if thing is in a rule - foreach (const RuleId &ruleId, m_ruleEngine->findRules(d->id())) { - qCDebug(dcThingManager) << " -> in rule:" << ruleId.toString(); - if (!offendingRules.contains(ruleId)) { - offendingRules.append(ruleId); - } - } - } - - // update involved rules - foreach (const RuleId &ruleId, offendingRules) { - foreach (Thing *thing, thingsToRemove) { - m_ruleEngine->removeThingFromRule(ruleId, thing->id()); - } - } - - // remove the child devices - foreach (Thing *d, childs) { - Thing::ThingError removeError = m_thingManager->removeConfiguredThing(d->id()); - if (removeError == Thing::ThingErrorNoError) { - m_logger->removeThingLogs(d->id()); - } - } - - // delete the thing - Thing::ThingError removeError = m_thingManager->removeConfiguredThing(thingId); - if (removeError == Thing::ThingErrorNoError) { - m_logger->removeThingLogs(thingId); - } -} - void NymeaCore::thingManagerLoaded() { - m_ruleEngine->init(); - // Evaluate rules on current time - onDateTimeChanged(m_timeManager->currentDateTime()); // Tell hardare resources we're done with loading stuff... m_hardwareManager->thingsLoaded(); @@ -881,7 +382,8 @@ void NymeaCore::thingManagerLoaded() qCDebug(dcCore()) << "Cleaning up stale thing tag" << tag.tagId(); m_tagsStorage->removeTag(tag); } - } + } + } } diff --git a/libnymea-core/nymeacore.h b/libnymea-core/nymeacore.h index 7b421223..3a952117 100644 --- a/libnymea-core/nymeacore.h +++ b/libnymea-core/nymeacore.h @@ -87,15 +87,6 @@ public: void init(const QStringList &additionalInterfaces = QStringList()); void destroy(); - // Thing handling - QPair >removeConfiguredThing(const ThingId &thingId, const QHash &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 ruleActions); - RuleEngine::RuleError removeRule(const RuleId &id); NymeaConfiguration *configuration() const; @@ -125,19 +116,6 @@ public: signals: void initialized(); - void pluginConfigChanged(const PluginId &id, const ParamList &config); - void eventTriggered(const Event &event); - void thingStateChanged(Thing *thing, const StateTypeId &stateTypeId, const QVariant &value, const QVariant &minValue, const QVariant &maxValue); - void thingRemoved(const ThingId &thingId); - void thingAdded(Thing *thing); - void thingChanged(Thing *thing); - void thingSettingChanged(const ThingId &thingId, const ParamTypeId &settingParamTypeId, const QVariant &value); - - void ruleRemoved(const RuleId &ruleId); - void ruleAdded(const Rule &rule); - void ruleActiveChanged(const Rule &rule); - void ruleConfigurationChanged(const Rule &rule); - private: explicit NymeaCore(QObject *parent = nullptr); static NymeaCore *s_instance; @@ -165,14 +143,8 @@ private: SerialPortMonitor *m_serialPortMonitor; ModbusRtuManager *m_modbusRtuManager; - QList m_executingRules; private slots: - void onEventTriggered(const Event &event); - void onThingStateChanged(Thing *thing, const StateTypeId &stateTypeId, const QVariant &value, const QVariant &minValue, const QVariant &maxValue); - void evaluateRules(const Event &event); - void onDateTimeChanged(const QDateTime &dateTime); - void onThingDisappeared(const ThingId &thingId); void thingManagerLoaded(); }; diff --git a/libnymea-core/ruleengine/rule.cpp b/libnymea-core/ruleengine/rule.cpp index b7a97aa4..9025df2b 100644 --- a/libnymea-core/ruleengine/rule.cpp +++ b/libnymea-core/ruleengine/rule.cpp @@ -207,19 +207,16 @@ bool Rule::isConsistent() const { // check if this rules is based on any event and contains exit actions if (!eventDescriptors().isEmpty() && stateEvaluator().isEmpty() && timeDescriptor().calendarItems().isEmpty() && !exitActions().isEmpty()) { - qCWarning(dcRuleEngine) << "Rule not consistent. The exitActions will never be executed if the rule contains an eventDescriptor but no stateEvaluator or calendarItem."; return false; } // check if this rules is based on any time events and contains exit actions if (!timeDescriptor().timeEventItems().isEmpty() && stateEvaluator().isEmpty() && timeDescriptor().calendarItems().isEmpty() && !exitActions().isEmpty()) { - qCWarning(dcRuleEngine) << "Rule not consistent. The exitActions will never be executed if the rule contains a timeEvent but no stateEvaluator or calendarItem."; return false; } // check if there are any actions if (actions().isEmpty()) { - qCWarning(dcRuleEngine) << "Rule not consistent. A rule without actions has no effect."; return false; } diff --git a/libnymea-core/ruleengine/ruleengine.cpp b/libnymea-core/ruleengine/ruleengine.cpp index da27fed1..de3cc97d 100644 --- a/libnymea-core/ruleengine/ruleengine.cpp +++ b/libnymea-core/ruleengine/ruleengine.cpp @@ -110,31 +110,58 @@ #include "ruleengine.h" -#include "nymeacore.h" #include "loggingcategories.h" #include "time/calendaritem.h" #include "time/repeatingoption.h" #include "time/timeeventitem.h" +#include "time/timemanager.h" #include "types/eventdescriptor.h" #include "types/paramdescriptor.h" #include "nymeasettings.h" #include "integrations/thingmanager.h" #include "integrations/thing.h" +#include "logging/logengine.h" #include #include #include #include +NYMEA_LOGGING_CATEGORY(dcRuleEngine, "RuleEngine") +NYMEA_LOGGING_CATEGORY(dcRuleEngineDebug, "RuleEngineDebug") + namespace nymeaserver { /*! Constructs the RuleEngine with the given \a parent. Although it wouldn't harm to have multiple RuleEngines, there is one instance available from \l{NymeaCore}. This one should be used instead of creating multiple ones. */ -RuleEngine::RuleEngine(QObject *parent) : - QObject(parent) +RuleEngine::RuleEngine(ThingManager *thingManager, TimeManager *timeManager, LogEngine *logEngine, QObject *parent) : + QObject(parent), + m_thingManager(thingManager), + m_timeManager(timeManager), + m_logEngine(logEngine) { + + connect(m_thingManager, &ThingManager::eventTriggered, this, &RuleEngine::onEventTriggered); + + connect(m_thingManager, &ThingManager::thingStateChanged, this, [this](Thing *thing, const StateTypeId &stateTypeId, const QVariant &value, const QVariant &/*minValue*/, const QVariant &/*maxValue*/){ + // There can be event based rules that would trigger when a state changes + // without "binding" to the state (as a stateEvaluator would do). So generate a fake event + // for every state change. + Param valueParam(ParamTypeId(stateTypeId.toString()), value); + Event event(EventTypeId(stateTypeId.toString()), thing->id(), ParamList() << valueParam); + onEventTriggered(event); + }); + + connect(m_thingManager, &ThingManager::thingRemoved, this, &RuleEngine::onThingRemoved); + + connect(m_timeManager, &TimeManager::dateTimeChanged, this, &RuleEngine::onDateTimeChanged); + + connect(m_thingManager, &ThingManager::loaded, this, [=](){ + init(); + onDateTimeChanged(m_timeManager->currentDateTime()); + }); } /*! Destructor of the \l{RuleEngine}. */ @@ -150,7 +177,7 @@ RuleEngine::~RuleEngine() */ QList RuleEngine::evaluateEvent(const Event &event) { - Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(event.thingId()); + Thing *thing = m_thingManager->findConfiguredThing(event.thingId()); if (!thing) { qCWarning(dcRuleEngine()) << "Invalid event. ThingID does not reference a valid thing"; return QList(); @@ -307,7 +334,7 @@ RuleEngine::RuleError RuleEngine::addRule(const Rule &rule, bool fromEdit) } if (!rule.isConsistent()) { - qCWarning(dcRuleEngine) << "Rule inconsistent."; + qCWarning(dcRuleEngine) << "Invalid rule format. (Either missing actions, or exitActions without condition given.)"; return RuleErrorInvalidRuleFormat; } @@ -319,14 +346,14 @@ RuleEngine::RuleError RuleEngine::addRule(const Rule &rule, bool fromEdit) } if (eventDescriptor.type() == EventDescriptor::TypeThing) { // check thingId - Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(eventDescriptor.thingId()); + Thing *thing = m_thingManager->findConfiguredThing(eventDescriptor.thingId()); if (!thing) { qCWarning(dcRuleEngine) << "Cannot create rule. No configured thing for eventTypeId" << eventDescriptor.eventTypeId(); return RuleErrorThingNotFound; } // Check eventTypeId for this deivce - ThingClass thingClass = NymeaCore::instance()->thingManager()->findThingClass(thing->thingClassId()); + ThingClass thingClass = m_thingManager->findThingClass(thing->thingClassId()); bool eventTypeFound = false; foreach (const EventType &eventType, thingClass.eventTypes()) { if (eventType.id() == eventDescriptor.eventTypeId()) { @@ -345,7 +372,7 @@ RuleEngine::RuleError RuleEngine::addRule(const Rule &rule, bool fromEdit) } } else { // Interface based event - Interface iface = NymeaCore::instance()->thingManager()->supportedInterfaces().findByName(eventDescriptor.interface()); + Interface iface = m_thingManager->supportedInterfaces().findByName(eventDescriptor.interface()); if (!iface.isValid()) { qWarning(dcRuleEngine()) << "No such interface:" << eventDescriptor.interface(); return RuleErrorInterfaceNotFound; @@ -507,6 +534,8 @@ RuleEngine::RuleError RuleEngine::removeRule(const RuleId &ruleId, bool fromEdit settings.remove(""); settings.endGroup(); + m_logEngine->removeRuleLogs(ruleId); + if (!fromEdit) emit ruleRemoved(ruleId); @@ -536,7 +565,7 @@ RuleEngine::RuleError RuleEngine::enableRule(const RuleId &ruleId) saveRule(rule); emit ruleConfigurationChanged(rule); - NymeaCore::instance()->logEngine()->logRuleEnabledChanged(rule, true); + m_logEngine->logRuleEnabledChanged(rule, true); qCDebug(dcRuleEngine()) << "Rule" << rule.name() << rule.id().toString() << "enabled."; return RuleErrorNoError; @@ -562,7 +591,7 @@ RuleEngine::RuleError RuleEngine::disableRule(const RuleId &ruleId) saveRule(rule); emit ruleConfigurationChanged(rule); - NymeaCore::instance()->logEngine()->logRuleEnabledChanged(rule, false); + m_logEngine->logRuleEnabledChanged(rule, false); qCDebug(dcRuleEngine()) << "Rule" << rule.name() << rule.id().toString() << "disabled."; return RuleErrorNoError; } @@ -597,8 +626,8 @@ RuleEngine::RuleError RuleEngine::executeActions(const RuleId &ruleId) } qCDebug(dcRuleEngine) << "Executing rule actions of rule" << rule.name() << rule.id().toString(); - NymeaCore::instance()->logEngine()->logRuleActionsExecuted(rule); - NymeaCore::instance()->executeRuleActions(rule.actions()); + m_logEngine->logRuleActionsExecuted(rule); + executeRuleActions(rule.actions()); return RuleErrorNoError; } @@ -629,8 +658,8 @@ RuleEngine::RuleError RuleEngine::executeExitActions(const RuleId &ruleId) } qCDebug(dcRuleEngine) << "Executing rule exit actions of rule" << rule.name() << rule.id().toString(); - NymeaCore::instance()->logEngine()->logRuleExitActionsExecuted(rule); - NymeaCore::instance()->executeRuleActions(rule.exitActions()); + m_logEngine->logRuleExitActionsExecuted(rule); + executeRuleActions(rule.exitActions()); return RuleErrorNoError; } @@ -830,7 +859,7 @@ bool RuleEngine::containsEvent(const Rule &rule, const Event &event, const Thing // If this is a interface based rule, the thing must implement the interface if (eventDescriptor.type() == EventDescriptor::TypeInterface) { - ThingClass dc = NymeaCore::instance()->thingManager()->findThingClass(thingClassId); + ThingClass dc = m_thingManager->findThingClass(thingClassId); if (!dc.interfaces().contains(eventDescriptor.interface())) { // ThingClass for this event doesn't implement the interface for this eventDescriptor continue; @@ -863,7 +892,7 @@ bool RuleEngine::containsEvent(const Rule &rule, const Event &event, const Thing allOK = false; continue; } - ThingClass dc = NymeaCore::instance()->thingManager()->findThingClass(thingClassId); + ThingClass dc = m_thingManager->findThingClass(thingClassId); EventType et = dc.eventTypes().findById(event.eventTypeId()); StateType st = dc.stateTypes().findById(event.eventTypeId()); if (et.isValid()) { @@ -928,8 +957,8 @@ bool RuleEngine::containsState(const StateEvaluator &stateEvaluator, const Event return true; } } else { - Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(stateChangeEvent.thingId()); - ThingClass thingClass = NymeaCore::instance()->thingManager()->findThingClass(thing->thingClassId()); + Thing *thing = m_thingManager->findConfiguredThing(stateChangeEvent.thingId()); + ThingClass thingClass = m_thingManager->findThingClass(thing->thingClassId()); if (thingClass.interfaces().contains(stateEvaluator.stateDescriptor().interface())) { return true; } @@ -954,13 +983,13 @@ RuleEngine::RuleError RuleEngine::checkRuleAction(const RuleAction &ruleAction, ActionType actionType; if (ruleAction.type() == RuleAction::TypeThing) { - Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(ruleAction.thingId()); + Thing *thing = m_thingManager->findConfiguredThing(ruleAction.thingId()); if (!thing) { qCWarning(dcRuleEngine) << "Cannot create rule. No configured thing with ID" << ruleAction.thingId(); return RuleErrorThingNotFound; } - ThingClass thingClass = NymeaCore::instance()->thingManager()->findThingClass(thing->thingClassId()); + ThingClass thingClass = m_thingManager->findThingClass(thing->thingClassId()); if (!thingClass.hasActionType(ruleAction.actionTypeId())) { qCWarning(dcRuleEngine) << "Cannot create rule. Thing " + thing->name() + " has no action type:" << ruleAction.actionTypeId(); return RuleErrorActionTypeNotFound; @@ -968,7 +997,7 @@ RuleEngine::RuleError RuleEngine::checkRuleAction(const RuleAction &ruleAction, actionType = thingClass.actionTypes().findById(ruleAction.actionTypeId()); } else if (ruleAction.type() == RuleAction::TypeInterface) { - Interface iface = NymeaCore::instance()->thingManager()->supportedInterfaces().findByName(ruleAction.interface()); + Interface iface = m_thingManager->supportedInterfaces().findByName(ruleAction.interface()); if (!iface.isValid()) { qCWarning(dcRuleEngine()) << "Cannot create rule. No such interface:" << ruleAction.interface(); return RuleError::RuleErrorInterfaceNotFound; @@ -979,7 +1008,7 @@ RuleEngine::RuleError RuleEngine::checkRuleAction(const RuleAction &ruleAction, return RuleError::RuleErrorActionTypeNotFound; } } else if (ruleAction.type() == RuleAction::TypeBrowser) { - Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(ruleAction.thingId()); + Thing *thing = m_thingManager->findConfiguredThing(ruleAction.thingId()); if (!thing) { qCWarning(dcRuleEngine) << "Cannot create rule. No configured thing with ID" << ruleAction.thingId(); return RuleErrorThingNotFound; @@ -1057,12 +1086,12 @@ RuleEngine::RuleError RuleEngine::checkRuleActionParam(const RuleActionParam &ru return RuleErrorTypesNotMatching; } } else if (ruleActionParam.isStateBased()) { - Thing *d = NymeaCore::instance()->thingManager()->findConfiguredThing(ruleActionParam.stateThingId()); + Thing *d = m_thingManager->findConfiguredThing(ruleActionParam.stateThingId()); if (!d) { qCWarning(dcRuleEngine()) << "Cannot create Rule. ThingId from RuleActionParam" << ruleActionParam.paramTypeId() << "not found in system."; return RuleErrorThingNotFound; } - ThingClass stateThingClass = NymeaCore::instance()->thingManager()->findThingClass(d->thingClassId()); + ThingClass stateThingClass = m_thingManager->findThingClass(d->thingClassId()); StateType stateType = stateThingClass.stateTypes().findById(ruleActionParam.stateTypeId()); QVariant::Type actionParamType = getActionParamType(actionType.id(), ruleActionParam.paramTypeId()); QVariant v(stateType.type()); @@ -1090,7 +1119,7 @@ RuleEngine::RuleError RuleEngine::checkRuleActionParam(const RuleActionParam &ru QVariant::Type RuleEngine::getActionParamType(const ActionTypeId &actionTypeId, const ParamTypeId ¶mTypeId) { - foreach (const ThingClass &thingClass, NymeaCore::instance()->thingManager()->supportedThings()) { + foreach (const ThingClass &thingClass, m_thingManager->supportedThings()) { foreach (const ActionType &actionType, thingClass.actionTypes()) { if (actionType.id() == actionTypeId) { foreach (const ParamType ¶mType, actionType.paramTypes()) { @@ -1107,7 +1136,7 @@ QVariant::Type RuleEngine::getActionParamType(const ActionTypeId &actionTypeId, QVariant::Type RuleEngine::getEventParamType(const EventTypeId &eventTypeId, const ParamTypeId ¶mTypeId) { - foreach (const ThingClass &thingClass, NymeaCore::instance()->thingManager()->supportedThings()) { + foreach (const ThingClass &thingClass, m_thingManager->supportedThings()) { foreach (const EventType &eventType, thingClass.eventTypes()) { if (eventType.id() == eventTypeId) { foreach (const ParamType ¶mType, eventType.paramTypes()) { @@ -1371,6 +1400,231 @@ QList RuleEngine::loadRuleActions(NymeaSettings *settings) return actions; } +void RuleEngine::executeRuleActions(const QList ruleActions) +{ + QList actions; + QList 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 actions; + QList 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 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 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 affectedRules; + + foreach (const RuleId &ruleId, findRules(thingId)) { + if (!affectedRules.contains(ruleId)) { + affectedRules.append(ruleId); + } + } + + while (!affectedRules.isEmpty()) { + removeRule(affectedRules.takeFirst()); + } +} + void RuleEngine::init() { NymeaSettings settings(NymeaSettings::SettingsRoleRules); diff --git a/libnymea-core/ruleengine/ruleengine.h b/libnymea-core/ruleengine/ruleengine.h index 7dbd6b3b..1c091370 100644 --- a/libnymea-core/ruleengine/ruleengine.h +++ b/libnymea-core/ruleengine/ruleengine.h @@ -34,15 +34,24 @@ #include "rule.h" #include "stateevaluator.h" #include "types/event.h" -#include "types/thingclass.h" + +#include "integrations/thingmanager.h" #include #include #include #include +Q_DECLARE_LOGGING_CATEGORY(dcRuleEngine) +Q_DECLARE_LOGGING_CATEGORY(dcRuleEngineDebug) + +class ThingManager; + namespace nymeaserver { +class LogEngine; +class TimeManager; + class RuleEngine : public QObject { Q_OBJECT @@ -72,18 +81,8 @@ public: }; Q_ENUM(RuleError) - enum RemovePolicy { - RemovePolicyCascade, - RemovePolicyUpdate - }; - Q_ENUM(RemovePolicy) - - explicit RuleEngine(QObject *parent = nullptr); + explicit RuleEngine(ThingManager *thingManager, TimeManager *timeManager, LogEngine *logEngine, QObject *parent = nullptr); ~RuleEngine(); - void init(); - - QList evaluateEvent(const Event &event); - QList evaluateTime(const QDateTime &dateTime); RuleError addRule(const Rule &rule, bool fromEdit = false); RuleError editRule(const Rule &rule); @@ -109,8 +108,18 @@ signals: void ruleAdded(const Rule &rule); void ruleRemoved(const RuleId &ruleId); void ruleConfigurationChanged(const Rule &rule); + void ruleActiveChanged(const Rule &rule); + +private slots: + void init(); + void onEventTriggered(const Event &event); + void onDateTimeChanged(const QDateTime &dateTime); + void onThingRemoved(const ThingId &thingId); + +private: + QList evaluateEvent(const Event &event); + QList evaluateTime(const QDateTime &dateTime); -private: bool containsEvent(const Rule &rule, const Event &event, const ThingClassId &thingClassId); bool containsState(const StateEvaluator &stateEvaluator, const Event &stateChangeEvent); @@ -125,12 +134,22 @@ private: void saveRuleActions(NymeaSettings *settings, const QList &ruleActions); QList loadRuleActions(NymeaSettings *settings); + void executeRuleActions(const QList ruleActions); + + private: + ThingManager *m_thingManager = nullptr; + TimeManager *m_timeManager = nullptr; + LogEngine *m_logEngine = nullptr; + QList m_ruleIds; // Keeping a list of RuleIds to keep sorting order... QHash m_rules; // ...but use a Hash for faster finding QList m_activeRules; QDateTime m_lastEvaluationTime; + + QList m_executingRules; + }; } diff --git a/libnymea/integrations/thingmanager.h b/libnymea/integrations/thingmanager.h index f5fea03d..32d1dd7e 100644 --- a/libnymea/integrations/thingmanager.h +++ b/libnymea/integrations/thingmanager.h @@ -110,11 +110,11 @@ protected: virtual IOConnectionResult connectIO(const IOConnection &connection) = 0; signals: + void loaded(); void pluginConfigChanged(const PluginId &id, const ParamList &config); void eventTriggered(const Event &event); void thingStateChanged(Thing *thing, const StateTypeId &stateTypeId, const QVariant &value, const QVariant &minValue, const QVariant &maxValue); void thingRemoved(const ThingId &thingId); - void thingDisappeared(const ThingId &thingId); void thingAdded(Thing *thing); void thingChanged(Thing *thing); void thingSettingChanged(const ThingId &thingId, const ParamTypeId &settingParamTypeId, const QVariant &value); diff --git a/libnymea/loggingcategories.cpp b/libnymea/loggingcategories.cpp index ba5e3a20..f6553bb0 100644 --- a/libnymea/loggingcategories.cpp +++ b/libnymea/loggingcategories.cpp @@ -48,8 +48,6 @@ NYMEA_LOGGING_CATEGORY(dcPlatformUpdate, "PlatformUpdate") NYMEA_LOGGING_CATEGORY(dcPlatformZeroConf, "PlatformZeroConf") NYMEA_LOGGING_CATEGORY(dcExperiences, "Experiences") NYMEA_LOGGING_CATEGORY(dcTimeManager, "TimeManager") -NYMEA_LOGGING_CATEGORY(dcRuleEngine, "RuleEngine") -NYMEA_LOGGING_CATEGORY(dcRuleEngineDebug, "RuleEngineDebug") NYMEA_LOGGING_CATEGORY(dcHardware, "Hardware") NYMEA_LOGGING_CATEGORY(dcLogEngine, "LogEngine") NYMEA_LOGGING_CATEGORY(dcServerManager, "ServerManager") diff --git a/libnymea/loggingcategories.h b/libnymea/loggingcategories.h index 0bdf9f25..ddf735bc 100644 --- a/libnymea/loggingcategories.h +++ b/libnymea/loggingcategories.h @@ -56,8 +56,6 @@ Q_DECLARE_LOGGING_CATEGORY(dcPlatformUpdate) Q_DECLARE_LOGGING_CATEGORY(dcPlatformZeroConf) Q_DECLARE_LOGGING_CATEGORY(dcExperiences) Q_DECLARE_LOGGING_CATEGORY(dcTimeManager) -Q_DECLARE_LOGGING_CATEGORY(dcRuleEngine) -Q_DECLARE_LOGGING_CATEGORY(dcRuleEngineDebug) Q_DECLARE_LOGGING_CATEGORY(dcHardware) Q_DECLARE_LOGGING_CATEGORY(dcLogEngine) Q_DECLARE_LOGGING_CATEGORY(dcServerManager) diff --git a/libnymea/time/repeatingoption.cpp b/libnymea/time/repeatingoption.cpp index 4ac34c25..1ccc07a7 100644 --- a/libnymea/time/repeatingoption.cpp +++ b/libnymea/time/repeatingoption.cpp @@ -154,7 +154,6 @@ bool RepeatingOption::isValid() const // Validate weekdays range foreach (const uint &weekDay, m_weekDays) { if (weekDay <= 0 || weekDay > 7) { - qCWarning(dcRuleEngine()) << "Invalid week day value:" << weekDay << ". Value out of range [1,7]."; return false; } } @@ -162,7 +161,6 @@ bool RepeatingOption::isValid() const // Validate monthdays range foreach (const uint &monthDay, m_monthDays) { if (monthDay <= 0 || monthDay > 31) { - qCWarning(dcRuleEngine()) << "Invalid month day value:" << monthDay << ". Value out of range [1,31]."; return false; } } diff --git a/libnymea/types/event.cpp b/libnymea/types/event.cpp index 02ec341e..0cd18e22 100644 --- a/libnymea/types/event.cpp +++ b/libnymea/types/event.cpp @@ -119,16 +119,6 @@ QVariant Event::paramValue(const ParamTypeId ¶mTypeId) const return QVariant(); } -bool Event::logged() const -{ - return m_logged; -} - -void Event::setLogged(bool logged) -{ - m_logged = logged; -} - /*! Compare this Event to the Event given by \a other. * Events are equal (returns true) if eventTypeId, deviceId and params match. */ bool Event::operator ==(const Event &other) const diff --git a/libnymea/types/event.h b/libnymea/types/event.h index 793bf2f7..7a546dd5 100644 --- a/libnymea/types/event.h +++ b/libnymea/types/event.h @@ -62,15 +62,10 @@ public: bool operator ==(const Event &other) const; - bool logged() const; - void setLogged(bool logged); - private: EventTypeId m_eventTypeId; ThingId m_thingId; ParamList m_params; - - bool m_logged = false; }; Q_DECLARE_METATYPE(Event) QDebug operator<<(QDebug dbg, const Event &event); diff --git a/nymea.pro b/nymea.pro index c7e29087..8e63ce27 100644 --- a/nymea.pro +++ b/nymea.pro @@ -5,7 +5,7 @@ NYMEA_VERSION_STRING=$$system('dpkg-parsechangelog | sed -n -e "s/^Version: //p" # define protocol versions JSON_PROTOCOL_VERSION_MAJOR=7 -JSON_PROTOCOL_VERSION_MINOR=0 +JSON_PROTOCOL_VERSION_MINOR=1 JSON_PROTOCOL_VERSION="$${JSON_PROTOCOL_VERSION_MAJOR}.$${JSON_PROTOCOL_VERSION_MINOR}" LIBNYMEA_API_VERSION_MAJOR=7 LIBNYMEA_API_VERSION_MINOR=3 diff --git a/tests/auto/api.json b/tests/auto/api.json index 8f0eabb8..85877f6e 100644 --- a/tests/auto/api.json +++ b/tests/auto/api.json @@ -1,4 +1,4 @@ -7.0 +7.1 { "enums": { "BasicType": [ @@ -169,10 +169,6 @@ "PermissionScopeConfigureRules", "PermissionScopeAdmin" ], - "RemovePolicy": [ - "RemovePolicyCascade", - "RemovePolicyUpdate" - ], "RepeatingMode": [ "RepeatingModeNone", "RepeatingModeHourly", @@ -1121,22 +1117,13 @@ } }, "Integrations.RemoveThing": { - "description": "Remove a thing from the system.", + "description": "Remove a thing and all its childs from the system. RemovePolicy is deprecated and has no effect any more.", "params": { - "o:removePolicy": "$ref:RemovePolicy", - "o:removePolicyList": [ - { - "policy": "$ref:RemovePolicy", - "ruleId": "Uuid" - } - ], + "d:o:removePolicy": "String", "thingId": "Uuid" }, "permissionScope": "PermissionScopeConfigureThings", "returns": { - "o:ruleIds": [ - "Uuid" - ], "thingError": "$ref:ThingError" } }, diff --git a/tests/auto/integrations/testintegrations.cpp b/tests/auto/integrations/testintegrations.cpp index be4b4d3a..d49456c6 100644 --- a/tests/auto/integrations/testintegrations.cpp +++ b/tests/auto/integrations/testintegrations.cpp @@ -1910,9 +1910,9 @@ void TestIntegrations::discoverThingsParenting() QVERIFY(childThing->thingClassId() == childMockThingClassId); // Now delete the parent and make sure the child will be deleted too - QSignalSpy removeSpy(NymeaCore::instance(), &NymeaCore::thingRemoved); - QPair > ret = NymeaCore::instance()->removeConfiguredThing(parentThing->id(), QHash()); - QCOMPARE(ret.first, Thing::ThingErrorNoError); + QSignalSpy removeSpy(NymeaCore::instance()->thingManager(), &ThingManager::thingRemoved); + Thing::ThingError ret = NymeaCore::instance()->thingManager()->removeConfiguredThing(parentThing->id()); + QCOMPARE(ret, Thing::ThingErrorNoError); QCOMPARE(removeSpy.count(), 3); // The parent, the auto-mock and the discovered mock } @@ -2107,8 +2107,8 @@ void TestIntegrations::triggerEvent() QVERIFY2(things.count() > 0, "There needs to be at least one configured Mock Device for this test"); Thing *thing = things.first(); - QSignalSpy spy(NymeaCore::instance(), SIGNAL(eventTriggered(const Event&))); - QSignalSpy notificationSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray))); + QSignalSpy spy(NymeaCore::instance()->thingManager(), &ThingManager::eventTriggered); + QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData); // Setup connection to mock client QNetworkAccessManager nam; @@ -2148,8 +2148,8 @@ void TestIntegrations::triggerStateChangeSignal() QVERIFY2(things.count() > 0, "There needs to be at least one configured Mock for this test"); Thing *thing = things.first(); - QSignalSpy spy(NymeaCore::instance(), SIGNAL(thingStateChanged(Thing *, const StateTypeId &, const QVariant &, const QVariant &, const QVariant &))); - QSignalSpy notificationSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray))); + QSignalSpy spy(NymeaCore::instance()->thingManager(), &ThingManager::thingStateChanged); + QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData); // Setup connection to mock client QNetworkAccessManager nam; diff --git a/tests/auto/rules/testrules.cpp b/tests/auto/rules/testrules.cpp index 1e5d1217..0a90ec27 100644 --- a/tests/auto/rules/testrules.cpp +++ b/tests/auto/rules/testrules.cpp @@ -115,9 +115,7 @@ private slots: void testStateBasedAction(); - void removePolicyUpdate(); - void removePolicyCascade(); - void removePolicyUpdateRendersUselessRule(); + void removeThingCleansRule(); void testRuleActionParams_data(); void testRuleActionParams(); @@ -2385,7 +2383,7 @@ void TestRules::testStateBasedAction() qCDebug(dcTests()) << "Log entries:" << entries; } -void TestRules::removePolicyUpdate() +void TestRules::removeThingCleansRule() { // ADD parent QVariantMap params; @@ -2441,16 +2439,10 @@ void TestRules::removePolicyUpdate() response = injectAndWait("Integrations.RemoveThing", params); verifyThingError(response, Thing::ThingErrorThingIsChild); - // Try to remove child - params.clear(); response.clear(); - params.insert("thingId", parentId); - response = injectAndWait("Integrations.RemoveThing", params); - verifyThingError(response, Thing::ThingErrorThingInRule); - // Remove policy params.clear(); response.clear(); params.insert("thingId", parentId); - params.insert("removePolicy", "RemovePolicyUpdate"); + params.insert("removePolicy", "RemovePolicyCascade"); // This is deprecated and doesn't do anything any more, keeping it as clients may pass it too still response = injectAndWait("Integrations.RemoveThing", params); verifyThingError(response); @@ -2458,182 +2450,6 @@ void TestRules::removePolicyUpdate() params.clear(); params.insert("ruleId", ruleId); response = injectAndWait("Rules.GetRuleDetails", params); - verifyRuleError(response); - - QVariantMap rule = response.toMap().value("params").toMap().value("rule").toMap(); - qDebug() << "Updated rule:" << QJsonDocument::fromVariant(rule).toJson(); - QVERIFY(rule.value("eventDescriptors").toList().count() == 1); - - // REMOVE rule - QVariantMap removeParams; - removeParams.insert("ruleId", ruleId); - response = injectAndWait("Rules.RemoveRule", removeParams); - verifyRuleError(response); -} - -void TestRules::removePolicyCascade() -{ - // ADD parent - QVariantMap params; - params.insert("thingClassId", parentMockThingClassId); - params.insert("name", "Parent"); - - QSignalSpy addedSpy(NymeaCore::instance()->thingManager(), &ThingManager::thingAdded); - - QVariant response = injectAndWait("Integrations.AddThing", params); - verifyThingError(response); - - ThingId parentId = ThingId(response.toMap().value("params").toMap().value("thingId").toString()); - QVERIFY(!parentId.isNull()); - - addedSpy.wait(); - - // find child - response = injectAndWait("Integrations.GetThings"); - - QVariantList things = response.toMap().value("params").toMap().value("things").toList(); - - ThingId childId; - foreach (const QVariant thingVariant, things) { - QVariantMap thingMap = thingVariant.toMap(); - - if (thingMap.value("thingClassId").toUuid() == childMockThingClassId) { - if (thingMap.value("parentId").toUuid() == parentId) { - childId = ThingId(thingMap.value("id").toString()); - } - } - } - QVERIFY2(!childId.isNull(), "Could not find child"); - - // Add rule with child - QVariantList eventDescriptors; - eventDescriptors.append(createEventDescriptor(childId, childMockEvent1EventTypeId)); - eventDescriptors.append(createEventDescriptor(parentId, parentMockEvent1EventTypeId)); - eventDescriptors.append(createEventDescriptor(m_mockThingId, mockEvent1EventTypeId)); - - params.clear(); response.clear(); - params.insert("name", "RemovePolicy"); - params.insert("eventDescriptors", eventDescriptors); - params.insert("actions", QVariantList() << createActionWithParams(m_mockThingId)); - - response = injectAndWait("Rules.AddRule", params); - verifyRuleError(response); - RuleId ruleId = RuleId(response.toMap().value("params").toMap().value("ruleId").toString()); - QVERIFY2(!ruleId.isNull(), "Could not get ruleId"); - - // Try to remove child - params.clear(); response.clear(); - params.insert("thingId", childId); - response = injectAndWait("Integrations.RemoveThing", params); - verifyThingError(response, Thing::ThingErrorThingIsChild); - - // Try to remove child by removing parent - params.clear(); response.clear(); - params.insert("thingId", parentId); - response = injectAndWait("Integrations.RemoveThing", params); - verifyThingError(response, Thing::ThingErrorThingInRule); - - // Remove policy - params.clear(); response.clear(); - params.insert("thingId", parentId); - params.insert("removePolicy", "RemovePolicyCascade"); - response = injectAndWait("Integrations.RemoveThing", params); - verifyThingError(response); - - // get updated rule - params.clear(); - params.insert("ruleId", ruleId); - response = injectAndWait("Rules.GetRuleDetails", params); - verifyRuleError(response, RuleEngine::RuleErrorRuleNotFound); -} - -void TestRules::removePolicyUpdateRendersUselessRule() -{ - // ADD parent - QVariantMap params; - params.insert("thingClassId", parentMockThingClassId); - params.insert("name", "Parent"); - - QSignalSpy addedSpy(NymeaCore::instance()->thingManager(), &ThingManager::thingAdded); - - QVariant response = injectAndWait("Integrations.AddThing", params); - verifyThingError(response); - - ThingId parentId = ThingId(response.toMap().value("params").toMap().value("thingId").toString()); - QVERIFY(!parentId.isNull()); - - addedSpy.wait(); - - // find child - qCDebug(dcTests()) << "Get things"; - response = injectAndWait("Integrations.GetThings"); - - QVariantList things = response.toMap().value("params").toMap().value("things").toList(); - - ThingId childId; - foreach (const QVariant thingVariant, things) { - QVariantMap thingMap = thingVariant.toMap(); - - if (thingMap.value("thingClassId").toUuid() == childMockThingClassId) { - if (thingMap.value("parentId").toUuid() == parentId) { - childId = ThingId(thingMap.value("id").toString()); - } - } - } - QVERIFY2(!childId.isNull(), "Could not find child"); - - // Add rule with child - QVariantList eventDescriptors; - eventDescriptors.append(createEventDescriptor(childId, childMockEvent1EventTypeId)); - eventDescriptors.append(createEventDescriptor(parentId, parentMockEvent1EventTypeId)); - eventDescriptors.append(createEventDescriptor(m_mockThingId, mockEvent1EventTypeId)); - - params.clear(); response.clear(); - params.insert("name", "RemovePolicy"); - params.insert("eventDescriptors", eventDescriptors); - - QVariantMap action; - action.insert("thingId", childId); - action.insert("actionTypeId", childMockBoolValueActionTypeId); - QVariantMap ruleActionParam; - ruleActionParam.insert("paramTypeId", childMockBoolValueActionBoolValueParamTypeId); - ruleActionParam.insert("value", true); - action.insert("ruleActionParams", QVariantList() << ruleActionParam); - params.insert("actions", QVariantList() << action); - - qCDebug(dcTests()) << "Adding Rule"; - response = injectAndWait("Rules.AddRule", params); - verifyRuleError(response); - RuleId ruleId = RuleId(response.toMap().value("params").toMap().value("ruleId").toString()); - QVERIFY2(!ruleId.isNull(), "Could not get ruleId"); - - // Try to remove child - qCDebug(dcTests()) << "Removing thing (expecing failure - thing is child)"; - params.clear(); response.clear(); - params.insert("thingId", childId); - response = injectAndWait("Integrations.RemoveThing", params); - verifyThingError(response, Thing::ThingErrorThingIsChild); - - // Try to remove child by removing parent - qCDebug(dcTests()) << "Removing thing (expeciting failure - thing in use)"; - params.clear(); response.clear(); - params.insert("thingId", parentId); - response = injectAndWait("Integrations.RemoveThing", params); - verifyThingError(response, Thing::ThingErrorThingInRule); - - // Remove policy - qCDebug(dcTests()) << "Removing thing with update policy"; - params.clear(); response.clear(); - params.insert("thingId", parentId); - params.insert("removePolicy", "RemovePolicyUpdate"); - response = injectAndWait("Integrations.RemoveThing", params); - verifyThingError(response); - - // get updated rule. It should've been deleted given it ended up with no actions - qCDebug(dcTests()) << "Getting details"; - params.clear(); - params.insert("ruleId", ruleId); - response = injectAndWait("Rules.GetRuleDetails", params); verifyRuleError(response, RuleEngine::RuleErrorRuleNotFound); }