/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2015-2018 Simon Stürz * * Copyright (C) 2014 Michael Zanetti * * * * This file is part of nymea. * * * * nymea is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, version 2 of the License. * * * * nymea is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with nymea. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /*! \class nymeaserver::NymeaCore \brief The main entry point for the nymea Server and the place where all the messages are dispatched. \inmodule core NymeaCore is a singleton instance and the main entry point of the nymea daemon. It is responsible to instantiate, set up and connect all the other components. */ /*! \fn void nymeaserver::NymeaCore::eventTriggered(const Event &event); This signal is emitted when an \a event happend. */ /*! \fn void nymeaserver::NymeaCore::deviceStateChanged(Device *device, const QUuid &stateTypeId, const QVariant &value); This signal is emitted when the \l{State} of a \a device changed. The \a stateTypeId parameter describes the \l{StateType} and the \a value parameter holds the new value. */ /*! \fn void nymeaserver::NymeaCore::deviceRemoved(const DeviceId &deviceId); This signal is emitted when a \l{Device} with the given \a deviceId was removed. */ /*! \fn void nymeaserver::NymeaCore::deviceAdded(Device *device); This signal is emitted when a \a device was added to the system. */ /*! \fn void nymeaserver::NymeaCore::deviceChanged(Device *device); This signal is emitted when the \l{ParamList}{Params} of a \a device have been changed. */ /*! \fn void nymeaserver::NymeaCore::actionExecuted(const ActionId &id, Device::DeviceError status); This signal is emitted when the \l{Action} with the given \a id is finished. The \a status of the \l{Action} execution will be described as \l{Device::DeviceError}{DeviceError}. */ /*! \fn void nymeaserver::NymeaCore::pairingFinished(const PairingTransactionId &pairingTransactionId, Device::DeviceError status, const DeviceId &deviceId); The DeviceManager will emit a this Signal when the pairing of a \l{Device} with the \a deviceId and \a pairingTransactionId is finished. The \a status of the pairing will be described as \l{Device::DeviceError}{DeviceError}. */ /*! \fn void nymeaserver::NymeaCore::ruleRemoved(const RuleId &ruleId); This signal is emitted when a \l{Rule} with the given \a ruleId was removed. */ /*! \fn void nymeaserver::NymeaCore::ruleAdded(const Rule &rule); This signal is emitted when a \a rule was added to the system. */ /*! \fn void nymeaserver::NymeaCore::ruleConfigurationChanged(const Rule &rule); This signal is emitted when the configuration of \a rule changed. */ /*! \fn void nymeaserver::NymeaCore::initialized(); This signal is emitted when the core is initialized. */ /*! \fn void nymeaserver::NymeaCore::pluginConfigChanged(const PluginId &id, const ParamList &config); This signal is emitted when the plugin \a config of the plugin with the given \a id changed. */ /*! \fn void ruleActiveChanged(const Rule &rule); This signal is emitted when a \a rule changed the active state. A \l{Rule} is active, when all \l{State}{States} match with the \l{StateDescriptor} conditions. \sa Rule::active() */ #include "nymeacore.h" #include "loggingcategories.h" #include "platform/platform.h" #include "jsonrpc/jsonrpcserverimplementation.h" #include "ruleengine/ruleengine.h" #include "networkmanager/networkmanager.h" #include "nymeasettings.h" #include "tagging/tagsstorage.h" #include "platform/platform.h" #include "experiences/experiencemanager.h" #include "devices/devicemanagerimplementation.h" #include "devices/device.h" #include "devices/deviceactioninfo.h" #include "devices/browseractioninfo.h" #include "devices/browseritemactioninfo.h" #include "cloud/cloudnotifications.h" #include "cloud/cloudtransport.h" #include #include namespace nymeaserver { NymeaCore* NymeaCore::s_instance = nullptr; /*! Returns a pointer to the single \l{NymeaCore} instance. */ NymeaCore *NymeaCore::instance() { if (!s_instance) { s_instance = new NymeaCore(); } return s_instance; } /*! Constructs NymeaCore with the given \a parent. This is private. Use \l{NymeaCore::instance()} to access the single instance.*/ NymeaCore::NymeaCore(QObject *parent) : QObject(parent) { } void NymeaCore::init() { qCDebug(dcApplication()) << "Initializing NymeaCore"; qCDebug(dcPlatform()) << "Loading platform abstraction"; m_platform = new Platform(this); qCDebug(dcApplication()) << "Loading nymea configurations" << NymeaSettings(NymeaSettings::SettingsRoleGlobal).fileName(); m_configuration = new NymeaConfiguration(this); qCDebug(dcApplication()) << "Creating Time Manager"; m_timeManager = new TimeManager(m_configuration->timeZone(), this); qCDebug(dcApplication) << "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); qCDebug(dcApplication()) << "Creating User Manager"; m_userManager = new UserManager(NymeaSettings::settingsPath() + "/user-db.sqlite", this); qCDebug(dcApplication) << "Creating Server Manager"; m_serverManager = new ServerManager(m_platform, m_configuration, this); qCDebug(dcApplication) << "Creating Hardware Manager"; m_hardwareManager = new HardwareManagerImplementation(m_platform, m_serverManager->mqttBroker(), this); qCDebug(dcApplication) << "Creating Device Manager (locale:" << m_configuration->locale() << ")"; m_deviceManager = new DeviceManagerImplementation(m_hardwareManager, m_configuration->locale(), this); qCDebug(dcApplication) << "Creating Rule Engine"; m_ruleEngine = new RuleEngine(this); qCDebug(dcApplication()) << "Creating Tags Storage"; m_tagsStorage = new TagsStorage(m_deviceManager, m_ruleEngine, this); qCDebug(dcApplication) << "Creating Network Manager"; m_networkManager = new NetworkManager(this); qCDebug(dcApplication) << "Creating Debug Server Handler"; m_debugServerHandler = new DebugServerHandler(this); qCDebug(dcApplication) << "Creating Cloud Manager"; m_cloudManager = new CloudManager(m_configuration, m_networkManager, this); qCDebug(dcApplication()) << "Loading experiences"; m_experienceManager = new ExperienceManager(m_deviceManager, m_serverManager->jsonServer(), this); CloudNotifications *cloudNotifications = m_cloudManager->createNotificationsPlugin(); m_deviceManager->registerStaticPlugin(cloudNotifications, cloudNotifications->metaData()); CloudTransport *cloudTransport = m_cloudManager->createTransportInterface(); m_serverManager->jsonServer()->registerTransportInterface(cloudTransport, false); connect(m_configuration, &NymeaConfiguration::serverNameChanged, m_serverManager, &ServerManager::setServerName); connect(m_deviceManager, &DeviceManagerImplementation::pluginConfigChanged, this, &NymeaCore::pluginConfigChanged); connect(m_deviceManager, &DeviceManagerImplementation::eventTriggered, this, &NymeaCore::gotEvent); connect(m_deviceManager, &DeviceManagerImplementation::deviceStateChanged, this, &NymeaCore::deviceStateChanged); connect(m_deviceManager, &DeviceManagerImplementation::deviceAdded, this, &NymeaCore::deviceAdded); connect(m_deviceManager, &DeviceManagerImplementation::deviceChanged, this, &NymeaCore::deviceChanged); connect(m_deviceManager, &DeviceManagerImplementation::deviceSettingChanged, this, &NymeaCore::deviceSettingChanged); connect(m_deviceManager, &DeviceManagerImplementation::deviceRemoved, this, &NymeaCore::deviceRemoved); connect(m_deviceManager, &DeviceManagerImplementation::deviceDisappeared, this, &NymeaCore::onDeviceDisappeared); connect(m_deviceManager, &DeviceManagerImplementation::loaded, this, &NymeaCore::deviceManagerLoaded); 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); connect(m_timeManager, &TimeManager::tick, m_deviceManager, &DeviceManagerImplementation::timeTick); m_logger->logSystemEvent(m_timeManager->currentDateTime(), true); } /*! Destructor of the \l{NymeaCore}. */ NymeaCore::~NymeaCore() { m_logger->logSystemEvent(m_timeManager->currentDateTime(), false); // Disconnect everything that could still spawn events disconnect(m_deviceManager); disconnect(m_ruleEngine); disconnect(m_timeManager); // At very first, cut off the outside world qCDebug(dcApplication) << "Shutting down \"Server Manager\""; delete m_serverManager; qCDebug(dcApplication) << "Shutting down \"CloudManager\""; delete m_cloudManager; // Then stop magic from happening qCDebug(dcApplication) << "Shutting down \"Rule Engine\""; delete m_ruleEngine; // Next, DeviceManager, so plugins don't access any resources any more. qCDebug(dcApplication) << "Shutting down \"Device Manager\""; delete m_deviceManager; // Now go ahead and clean up stuff. qCDebug(dcApplication) << "Shutting down \"Log Engine\""; delete m_logger; qCDebug(dcApplication()) << "Shutting down \"Hardware Manager\""; delete m_hardwareManager; qCDebug(dcApplication) << "Done shutting down NymeaCore"; } /*! Destroyes the \l{NymeaCore} instance. */ void NymeaCore::destroy() { if (s_instance) { delete s_instance; } s_instance = nullptr; } /*! Removes a configured \l{Device} with the given \a deviceId and \a removePolicyList. */ QPair > NymeaCore::removeConfiguredDevice(const DeviceId &deviceId, const QHash &removePolicyList) { Device *device = m_deviceManager->findConfiguredDevice(deviceId); if (!device) { return QPair > (Device::DeviceErrorDeviceNotFound, QList()); } // Check if this is a child device if (!device->parentId().isNull()) { qCWarning(dcDeviceManager) << "The device is a child of" << device->parentId().toString() << ". Please remove the parent device."; return QPair > (Device::DeviceErrorDeviceIsChild, 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 (device->autoCreated()) { // qCWarning(dcDeviceManager) << "This device has been auto-created and cannot be deleted manually."; // return QPair >(Device::DeviceErrorCreationMethodNotSupported, {}); // } // Check if this device has child devices QList devicesToRemove; devicesToRemove.append(device); QList childDevices = m_deviceManager->findChildDevices(deviceId); if (!childDevices.isEmpty()) { foreach (Device *child, childDevices) { devicesToRemove.append(child); } } // check devices QList offendingRules; qCDebug(dcDeviceManager) << "Devices to remove:"; foreach (Device *d, devicesToRemove) { qCDebug(dcDeviceManager) << " -> " << d->name() << d->id().toString(); // Check if device is in a rule foreach (const RuleId &ruleId, m_ruleEngine->findRules(d->id())) { qCDebug(dcDeviceManager) << " -> 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(dcDeviceManager) << "There are unhandled rules which depend on this device:\n" << unhandledRules; return QPair > (Device::DeviceErrorDeviceInRule, 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 (Device *d, devicesToRemove) { m_ruleEngine->removeDeviceFromRule(ruleId, d->id()); } } } // remove the child devices foreach (Device *d, childDevices) { Device::DeviceError removeError = m_deviceManager->removeConfiguredDevice(d->id()); if (removeError == Device::DeviceErrorNoError) { m_logger->removeDeviceLogs(d->id()); } } // delete the devices Device::DeviceError removeError = m_deviceManager->removeConfiguredDevice(deviceId); if (removeError == Device::DeviceErrorNoError) { m_logger->removeDeviceLogs(deviceId); } return QPair > (Device::DeviceErrorNoError, QList()); } /*! Removes a configured \l{Device} with the given \a deviceId and \a removePolicy. */ Device::DeviceError NymeaCore::removeConfiguredDevice(const DeviceId &deviceId, const RuleEngine::RemovePolicy &removePolicy) { Device *device = m_deviceManager->findConfiguredDevice(deviceId); if (!device) { return Device::DeviceErrorDeviceNotFound; } // Check if this is a child device if (!device->parentId().isNull()) { qCWarning(dcDeviceManager) << "The device is a child of" << device->parentId().toString() << ". Please remove the parent device."; return Device::DeviceErrorDeviceIsChild; } // 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 (device->autoCreated()) { // qCWarning(dcDeviceManager) << "This device has been auto-created and cannot be deleted manually."; // return Device::DeviceErrorCreationMethodNotSupported; // } // Check if this device has child devices QList devicesToRemove; devicesToRemove.append(device); QList childDevices = m_deviceManager->findChildDevices(deviceId); if (!childDevices.isEmpty()) { foreach (Device *child, childDevices) { devicesToRemove.append(child); } } // check devices QList offendingRules; qCDebug(dcDeviceManager) << "Devices to remove:"; foreach (Device *d, devicesToRemove) { qCDebug(dcDeviceManager) << " -> " << d->name() << d->id().toString(); // Check if device is in a rule foreach (const RuleId &ruleId, m_ruleEngine->findRules(d->id())) { qCDebug(dcDeviceManager) << " -> 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 (Device *d, devicesToRemove) { m_ruleEngine->removeDeviceFromRule(ruleId, d->id()); } } } // remove the child devices foreach (Device *d, childDevices) { Device::DeviceError removeError = m_deviceManager->removeConfiguredDevice(d->id()); if (removeError == Device::DeviceErrorNoError) { m_logger->removeDeviceLogs(d->id()); } } // delete the devices Device::DeviceError removeError = m_deviceManager->removeConfiguredDevice(deviceId); if (removeError == Device::DeviceErrorNoError) { m_logger->removeDeviceLogs(deviceId); } return removeError; } /*! Calls the metheod DeviceManager::executeAction(\a action). * \sa DeviceManager::executeAction(), */ DeviceActionInfo* NymeaCore::executeAction(const Action &action) { DeviceActionInfo *info = m_deviceManager->executeAction(action); connect(info, &DeviceActionInfo::finished, this, [this, info](){ if (info->status() == Device::DeviceErrorNoError) { m_logger->logAction(info->action()); } else { m_logger->logAction(info->action(), Logging::LoggingLevelAlert, info->status()); } }); return info; } BrowserActionInfo* NymeaCore::executeBrowserItem(const BrowserAction &browserAction) { BrowserActionInfo *info = m_deviceManager->executeBrowserItem(browserAction); connect(info, &BrowserActionInfo::finished, info->device(), [this, info](){ m_logger->logBrowserAction(info->browserAction(), info->status() == Device::DeviceErrorNoError ? Logging::LoggingLevelInfo : Logging::LoggingLevelAlert, info->status()); }); return info; } BrowserItemActionInfo *NymeaCore::executeBrowserItemAction(const BrowserItemAction &browserItemAction) { BrowserItemActionInfo *info = m_deviceManager->executeBrowserItemAction(browserItemAction); connect(info, &BrowserItemActionInfo::finished, info->device(), [this, info](){ m_logger->logBrowserItemAction(info->browserItemAction(), info->status() == Device::DeviceErrorNoError ? 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::TypeDevice) { Device *device = m_deviceManager->findConfiguredDevice(ruleAction.deviceId()); if (!device) { qCWarning(dcRuleEngine()) << "Unable to find device" << ruleAction.deviceId() << "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()) { Device *stateDevice = m_deviceManager->findConfiguredDevice(ruleActionParam.stateDeviceId()); if (!stateDevice) { qCWarning(dcRuleEngine()) << "Cannot find device" << ruleActionParam.stateDeviceId() << "required by rule action" << ruleAction.id(); ok = false; break; } DeviceClass stateDeviceClass = m_deviceManager->findDeviceClass(stateDevice->deviceClassId()); if (!stateDeviceClass.hasStateType(ruleActionParam.stateTypeId())) { qCWarning(dcRuleEngine()) << "Device" << device->name() << device->id() << "does not have a state type" << ruleActionParam.stateTypeId(); ok = false; break; } params.append(Param(ruleActionParam.paramTypeId(), stateDevice->stateValue(ruleActionParam.stateTypeId()))); } } if (!ok) { qCWarning(dcRuleEngine()) << "Not executing rule action" << ruleAction.id(); continue; } Action action(actionTypeId, device->id()); action.setParams(params); actions.append(action); } else if (ruleAction.type() == RuleAction::TypeBrowser) { Device *device = m_deviceManager->findConfiguredDevice(ruleAction.deviceId()); if (!device) { qCWarning(dcRuleEngine()) << "Unable to find device" << ruleAction.deviceId() << "for rule action" << ruleAction; continue; } BrowserAction browserAction(ruleAction.deviceId(), ruleAction.browserItemId()); browserActions.append(browserAction); } else { QList devices = m_deviceManager->findConfiguredDevices(ruleAction.interface()); foreach (Device* device, devices) { DeviceClass deviceClass = m_deviceManager->findDeviceClass(device->deviceClassId()); ActionType actionType = deviceClass.actionTypes().findByName(ruleAction.interfaceAction()); if (actionType.id().isNull()) { qCWarning(dcRuleEngine()) << "Error creating Action. The given DeviceClass 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()) { Device *stateDevice = m_deviceManager->findConfiguredDevice(ruleActionParam.stateDeviceId()); if (!stateDevice) { qCWarning(dcRuleEngine()) << "Cannot find device" << ruleActionParam.stateDeviceId() << "required by rule action" << ruleAction.id(); ok = false; break; } DeviceClass stateDeviceClass = m_deviceManager->findDeviceClass(stateDevice->deviceClassId()); if (!stateDeviceClass.hasStateType(ruleActionParam.stateTypeId())) { qCWarning(dcRuleEngine()) << "Device" << device->name() << device->id() << "does not have a state type" << ruleActionParam.stateTypeId(); ok = false; break; } params.append(Param(paramType.id(), stateDevice->stateValue(ruleActionParam.stateTypeId()))); } } if (!ok) { qCWarning(dcRuleEngine()) << "Not executing rule action" << ruleAction.id(); continue; } Action action = Action(actionType.id(), device->id()); action.setParams(params); actions.append(action); } } } foreach (const Action &action, actions) { qCDebug(dcRuleEngine) << "Executing action" << action.actionTypeId() << action.params(); DeviceActionInfo *info = executeAction(action); connect(info, &DeviceActionInfo::finished, this, [info](){ if (info->status() != Device::DeviceErrorNoError) { 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() != Device::DeviceErrorNoError) { 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; } /*! Returns a pointer to the \l{NymeaConfiguration} instance owned by NymeaCore.*/ NymeaConfiguration *NymeaCore::configuration() const { return m_configuration; } /*! Returns a pointer to the \l{DeviceManager} instance owned by NymeaCore.*/ DeviceManager *NymeaCore::deviceManager() const { return m_deviceManager; } /*! Returns a pointer to the \l{RuleEngine} instance owned by NymeaCore.*/ RuleEngine *NymeaCore::ruleEngine() const { return m_ruleEngine; } /*! Returns a pointer to the \l{TimeManager} instance owned by NymeaCore.*/ TimeManager *NymeaCore::timeManager() const { return m_timeManager; } /*! Returns a pointer to the \l{ServerManager} instance owned by NymeaCore. */ ServerManager *NymeaCore::serverManager() const { return m_serverManager; } /*! Returns the list of available system languages. */ QStringList NymeaCore::getAvailableLanguages() { qCDebug(dcApplication()) << "Loading translations from" << NymeaSettings::translationsPath(); QStringList searchPaths; searchPaths << QCoreApplication::applicationDirPath() + "/../translations"; searchPaths << NymeaSettings::translationsPath(); QStringList translationFiles; foreach (const QString &path, searchPaths) { QDir translationDirectory(path); translationDirectory.setNameFilters(QStringList() << "*.qm"); translationFiles = translationDirectory.entryList(); qCDebug(dcTranslations()) << translationFiles.count() << "translations in" << path; if (translationFiles.count() > 0) { break; } } QStringList availableLanguages; foreach (QString translationFile, translationFiles) { if (!translationFile.startsWith("nymead-")) continue; QString language = translationFile.remove("nymead-").remove(".qm"); QLocale languageLocale(language); availableLanguages.append(languageLocale.name()); } return availableLanguages; } /*! Returns the list of logging categories from the core and the libnymea. */ QStringList NymeaCore::loggingFilters() { QStringList loggingFilters = { "Warnings", "Application", "System", "Platform", "PlatformUpdate", "PlatformZeroConf", "Experiences", "Device", "DeviceManager", "RuleEngine", "RuleEngineDebug", "Hardware", "Bluetooth", "LogEngine", "ServerManager", "TcpServer", "TcpServerTraffic", "WebServer", "WebServerTraffic", "DebugServer", "WebSocketServer", "WebSocketServerTraffic", "JsonRpc", "JsonRpcTraffic", "Rest", "OAuth2", "TimeManager", "Coap", "Avahi", "AvahiDebug", "UPnP", "Cloud", "CloudTraffic", "NetworkManager", "UserManager", "AWS", "AWSTraffic", "BluetoothServer", "BluetoothServerTraffic", "Mqtt", "Translations" }; return loggingFilters; } QStringList NymeaCore::loggingFiltersPlugins() { QStringList loggingFiltersPlugins; foreach (const QJsonObject &pluginMetadata, DeviceManagerImplementation::pluginsMetadata()) { QString pluginName = pluginMetadata.value("name").toString(); loggingFiltersPlugins << pluginName.left(1).toUpper() + pluginName.mid(1); } return loggingFiltersPlugins; } /*! Returns a pointer to the \l{BluetoothServer} instance owned by NymeaCore. */ BluetoothServer *NymeaCore::bluetoothServer() const { return m_serverManager->bluetoothServer(); } /*! Returns a pointer to the \l{NetworkManager} instance owned by NymeaCore. */ NetworkManager *NymeaCore::networkManager() const { return m_networkManager; } /*! Returns a pointer to the \l{UserManager} instance owned by NymeaCore. */ UserManager *NymeaCore::userManager() const { return m_userManager; } /*! Returns a pointer to the CloudManager instance owned by NymeaCore. */ CloudManager *NymeaCore::cloudManager() const { return m_cloudManager; } /*! Returns a pointer to the \l{DebugServerHandler} instance owned by NymeaCore. */ DebugServerHandler *NymeaCore::debugServerHandler() const { return m_debugServerHandler; } /*! Returns a pointer to the \l{TagsStorage} instance owned by NymeaCore. */ TagsStorage *NymeaCore::tagsStorage() const { return m_tagsStorage; } /*! Returns a pointer to the \l{Platform} instance owned by NymeaCore. The Platform represents the host system this nymea instance is running on. */ Platform *NymeaCore::platform() const { return m_platform; } /*! Connected to the DeviceManager's emitEvent signal. Events received in here will be evaluated by the \l{RuleEngine} and the according \l{RuleAction}{RuleActions} are executed.*/ void NymeaCore::gotEvent(const Event &event) { m_logger->logEvent(event); emit eventTriggered(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() << 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); } /*! Return the instance of the log engine */ LogEngine* NymeaCore::logEngine() const { return m_logger; } /*! Returns the pointer to the \l{JsonRPCServer} of this instance. */ JsonRPCServerImplementation *NymeaCore::jsonRPCServer() const { return m_serverManager->jsonServer(); } void NymeaCore::onDeviceDisappeared(const DeviceId &deviceId) { Device *device = m_deviceManager->findConfiguredDevice(deviceId); if (!device) { return; } // Check if this device has child devices QList devicesToRemove; devicesToRemove.append(device); QList childDevices = m_deviceManager->findChildDevices(deviceId); if (!childDevices.isEmpty()) { foreach (Device *child, childDevices) { devicesToRemove.append(child); } } // check devices QList offendingRules; qCDebug(dcDeviceManager) << "Devices to remove:"; foreach (Device *d, devicesToRemove) { qCDebug(dcDeviceManager) << " -> " << d->name() << d->id().toString(); // Check if device is in a rule foreach (const RuleId &ruleId, m_ruleEngine->findRules(d->id())) { qCDebug(dcDeviceManager) << " -> in rule:" << ruleId.toString(); if (!offendingRules.contains(ruleId)) { offendingRules.append(ruleId); } } } // update involved rules foreach (const RuleId &ruleId, offendingRules) { foreach (Device *d, devicesToRemove) { m_ruleEngine->removeDeviceFromRule(ruleId, d->id()); } } // remove the child devices foreach (Device *d, childDevices) { Device::DeviceError removeError = m_deviceManager->removeConfiguredDevice(d->id()); if (removeError == Device::DeviceErrorNoError) { m_logger->removeDeviceLogs(d->id()); } } // delete the device Device::DeviceError removeError = m_deviceManager->removeConfiguredDevice(deviceId); if (removeError == Device::DeviceErrorNoError) { m_logger->removeDeviceLogs(deviceId); } } void NymeaCore::deviceManagerLoaded() { m_ruleEngine->init(); // Evaluate rules on current time onDateTimeChanged(m_timeManager->currentDateTime()); emit initialized(); // Do some houskeeping... qCDebug(dcApplication()) << "Starting housekeeping..."; QDateTime startTime = QDateTime::currentDateTime(); DevicesFetchJob *job = m_logger->fetchDevices(); connect(job, &DevicesFetchJob::finished, this, [this, job, startTime](){ foreach (const DeviceId &deviceId, job->results()) { if (!m_deviceManager->findConfiguredDevice(deviceId)) { qCDebug(dcApplication()) << "Cleaning stale device entries from log DB for device id" << deviceId; m_logger->removeDeviceLogs(deviceId); } } qCDebug(dcApplication()) << "Housekeeping done in" << startTime.msecsTo(QDateTime::currentDateTime()) << "ms."; }); foreach (const DeviceId &deviceId, m_ruleEngine->devicesInRules()) { if (!m_deviceManager->findConfiguredDevice(deviceId)) { qCDebug(dcApplication()) << "Cleaning stale rule entries for device id" << deviceId; foreach (const RuleId &ruleId, m_ruleEngine->findRules(deviceId)) { m_ruleEngine->removeDeviceFromRule(ruleId, deviceId); } } } } }