mirror of https://github.com/nymea/nymea.git
889 lines
36 KiB
C++
889 lines
36 KiB
C++
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
* *
|
|
* Copyright (C) 2015-2018 Simon Stürz <simon.stuerz@guh.io> *
|
|
* Copyright (C) 2014 Michael Zanetti <michael_zanetti@gmx.net> *
|
|
* *
|
|
* 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 <http://www.gnu.org/licenses/>. *
|
|
* *
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
/*!
|
|
\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/jsonrpcserver.h"
|
|
#include "ruleengine/ruleengine.h"
|
|
#include "networkmanager/networkmanager.h"
|
|
#include "nymeasettings.h"
|
|
#include "tagging/tagsstorage.h"
|
|
#include "platform/platform.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 <QDir>
|
|
#include <QCoreApplication>
|
|
|
|
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);
|
|
|
|
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<Device::DeviceError, QList<RuleId> > NymeaCore::removeConfiguredDevice(const DeviceId &deviceId, const QHash<RuleId, RuleEngine::RemovePolicy> &removePolicyList)
|
|
{
|
|
Device *device = m_deviceManager->findConfiguredDevice(deviceId);
|
|
|
|
if (!device) {
|
|
return QPair<Device::DeviceError, QList<RuleId> > (Device::DeviceErrorDeviceNotFound, QList<RuleId>());
|
|
}
|
|
|
|
// 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::DeviceError, QList<RuleId> > (Device::DeviceErrorDeviceIsChild, QList<RuleId>());
|
|
}
|
|
|
|
// FIXME: Let's remove this for now. It will come back with more fine grained control, presumably introducing a RemoveMethod flag in the DeviceClass
|
|
// if (device->autoCreated()) {
|
|
// qCWarning(dcDeviceManager) << "This device has been auto-created and cannot be deleted manually.";
|
|
// return QPair<Device::DeviceError, QList<RuleId> >(Device::DeviceErrorCreationMethodNotSupported, {});
|
|
// }
|
|
|
|
// Check if this device has child devices
|
|
QList<Device *> devicesToRemove;
|
|
devicesToRemove.append(device);
|
|
QList<Device *> childDevices = m_deviceManager->findChildDevices(deviceId);
|
|
if (!childDevices.isEmpty()) {
|
|
foreach (Device *child, childDevices) {
|
|
devicesToRemove.append(child);
|
|
}
|
|
}
|
|
|
|
// check devices
|
|
QList<RuleId> 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<RuleId, RuleEngine::RemovePolicy> toBeChanged;
|
|
QList<RuleId> unhandledRules;
|
|
foreach (const RuleId &ruleId, offendingRules) {
|
|
bool found = false;
|
|
foreach (const RuleId &policyRuleId, removePolicyList.keys()) {
|
|
if (ruleId == policyRuleId) {
|
|
found = true;
|
|
toBeChanged.insert(ruleId, removePolicyList.value(ruleId));
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
unhandledRules.append(ruleId);
|
|
|
|
}
|
|
|
|
if (!unhandledRules.isEmpty()) {
|
|
qCWarning(dcDeviceManager) << "There are unhandled rules which depend on this device:\n" << unhandledRules;
|
|
return QPair<Device::DeviceError, QList<RuleId> > (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::DeviceError, QList<RuleId> > (Device::DeviceErrorNoError, QList<RuleId>());
|
|
}
|
|
|
|
|
|
/*! 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<Device *> devicesToRemove;
|
|
devicesToRemove.append(device);
|
|
QList<Device *> childDevices = m_deviceManager->findChildDevices(deviceId);
|
|
if (!childDevices.isEmpty()) {
|
|
foreach (Device *child, childDevices) {
|
|
devicesToRemove.append(child);
|
|
}
|
|
}
|
|
|
|
// check devices
|
|
QList<RuleId> 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<RuleAction> ruleActions)
|
|
{
|
|
QList<Action> actions;
|
|
QList<BrowserAction> 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<Device*> 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 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<RuleAction> actions;
|
|
QList<RuleAction> eventBasedActions;
|
|
foreach (const Rule &rule, m_ruleEngine->evaluateEvent(event)) {
|
|
if (m_executingRules.contains(rule.id())) {
|
|
qCWarning(dcRuleEngine()) << "WARNING: Loop detected in rule execution for rule" << rule.id() << rule.name();
|
|
break;
|
|
}
|
|
m_executingRules.append(rule.id());
|
|
|
|
// Event based
|
|
if (!rule.eventDescriptors().isEmpty()) {
|
|
m_logger->logRuleTriggered(rule);
|
|
QList<RuleAction> tmp;
|
|
if (rule.statesActive() && rule.timeActive()) {
|
|
qCDebug(dcRuleEngineDebug()) << "Executing actions";
|
|
tmp = rule.actions();
|
|
} else {
|
|
qCDebug(dcRuleEngineDebug()) << "Executing exitActions";
|
|
tmp = rule.exitActions();
|
|
}
|
|
// check if we have an event based action or a normal action
|
|
foreach (const RuleAction &action, tmp) {
|
|
if (action.isEventBased()) {
|
|
eventBasedActions.append(action);
|
|
} else {
|
|
actions.append(action);
|
|
}
|
|
}
|
|
} else {
|
|
// State based rule
|
|
m_logger->logRuleActiveChanged(rule);
|
|
emit ruleActiveChanged(rule);
|
|
if (rule.active()) {
|
|
actions.append(rule.actions());
|
|
} else {
|
|
actions.append(rule.exitActions());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set action params, depending on the event value
|
|
foreach (RuleAction ruleAction, eventBasedActions) {
|
|
RuleActionParamList newParams;
|
|
foreach (RuleActionParam ruleActionParam, ruleAction.ruleActionParams()) {
|
|
// if this event param should be taken over in this action
|
|
if (event.eventTypeId() == ruleActionParam.eventTypeId()) {
|
|
QVariant eventValue = event.params().paramValue(ruleActionParam.eventParamTypeId());
|
|
|
|
// TODO: limits / scale calculation -> actionValue = eventValue * x
|
|
// something like a EventParamDescriptor
|
|
|
|
ruleActionParam.setValue(eventValue);
|
|
qCDebug(dcRuleEngine) << "Using param value from event:" << ruleActionParam.value();
|
|
}
|
|
newParams.append(ruleActionParam);
|
|
}
|
|
ruleAction.setRuleActionParams(newParams);
|
|
actions.append(ruleAction);
|
|
}
|
|
|
|
executeRuleActions(actions);
|
|
m_executingRules.clear();
|
|
}
|
|
|
|
void NymeaCore::onDateTimeChanged(const QDateTime &dateTime)
|
|
{
|
|
QList<RuleAction> actions;
|
|
foreach (const Rule &rule, m_ruleEngine->evaluateTime(dateTime)) {
|
|
// TimeEvent based
|
|
if (!rule.timeDescriptor().timeEventItems().isEmpty()) {
|
|
m_logger->logRuleTriggered(rule);
|
|
if (rule.statesActive() && rule.timeActive()) {
|
|
actions.append(rule.actions());
|
|
} else {
|
|
actions.append(rule.exitActions());
|
|
}
|
|
} else {
|
|
// Calendar based rule
|
|
m_logger->logRuleActiveChanged(rule);
|
|
emit ruleActiveChanged(rule);
|
|
if (rule.active()) {
|
|
actions.append(rule.actions());
|
|
} else {
|
|
actions.append(rule.exitActions());
|
|
}
|
|
}
|
|
}
|
|
executeRuleActions(actions);
|
|
}
|
|
|
|
/*! Return the instance of the log engine */
|
|
LogEngine* NymeaCore::logEngine() const
|
|
{
|
|
return m_logger;
|
|
}
|
|
|
|
/*! Returns the pointer to the \l{JsonRPCServer} of this instance. */
|
|
JsonRPCServer *NymeaCore::jsonRPCServer() const
|
|
{
|
|
return m_serverManager->jsonServer();
|
|
}
|
|
|
|
/*! Returns the pointer to the \l{RestServer} of this instance. */
|
|
RestServer *NymeaCore::restServer() const
|
|
{
|
|
return m_serverManager->restServer();
|
|
}
|
|
|
|
void NymeaCore::onDeviceDisappeared(const DeviceId &deviceId)
|
|
{
|
|
Device *device = m_deviceManager->findConfiguredDevice(deviceId);
|
|
if (!device) {
|
|
return;
|
|
}
|
|
|
|
// Check if this device has child devices
|
|
QList<Device *> devicesToRemove;
|
|
devicesToRemove.append(device);
|
|
QList<Device *> childDevices = m_deviceManager->findChildDevices(deviceId);
|
|
if (!childDevices.isEmpty()) {
|
|
foreach (Device *child, childDevices) {
|
|
devicesToRemove.append(child);
|
|
}
|
|
}
|
|
|
|
// check devices
|
|
QList<RuleId> 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();
|
|
foreach (const DeviceId &deviceId, m_logger->devicesInLogs()) {
|
|
if (!m_deviceManager->findConfiguredDevice(deviceId)) {
|
|
qCDebug(dcApplication()) << "Cleaning stale device entries from log DB for device id" << deviceId;
|
|
m_logger->removeDeviceLogs(deviceId);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
qCDebug(dcApplication()) << "Housekeeping done in" << startTime.msecsTo(QDateTime::currentDateTime()) << "ms.";
|
|
|
|
}
|
|
|
|
}
|