Note: This doesn't remove 100% of the related code yet, just keeps the minimum required to emit autoThingDisappeared() for all the things and clean up users setups. The rest of the CloudNotifications class code shall be removed with 0.31 (or soonish after that).
870 lines
33 KiB
C++
870 lines
33 KiB
C++
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
*
|
|
* Copyright 2013 - 2020, nymea GmbH
|
|
* Contact: contact@nymea.io
|
|
*
|
|
* This file is part of nymea.
|
|
* This project including source code and documentation is protected by
|
|
* copyright law, and remains the property of nymea GmbH. All rights, including
|
|
* reproduction, publication, editing and translation, are reserved. The use of
|
|
* this project is subject to the terms of a license agreement to be concluded
|
|
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
|
* under https://nymea.io/license
|
|
*
|
|
* GNU General Public License Usage
|
|
* Alternatively, this project may be redistributed and/or modified under the
|
|
* terms of the GNU General Public License as published by the Free Software
|
|
* Foundation, GNU version 3. This project 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
|
|
* this project. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
* For any further details and any questions please contact us under
|
|
* contact@nymea.io or see our FAQ/Licensing Information on
|
|
* https://nymea.io/license/faq
|
|
*
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
#include "nymeacore.h"
|
|
#include "loggingcategories.h"
|
|
#include "platform/platform.h"
|
|
#include "jsonrpc/jsonrpcserverimplementation.h"
|
|
#include "ruleengine/ruleengine.h"
|
|
#include "nymeasettings.h"
|
|
#include "tagging/tagsstorage.h"
|
|
#include "platform/platform.h"
|
|
#include "experiences/experiencemanager.h"
|
|
#include "platform/platformsystemcontroller.h"
|
|
|
|
#include "scriptengine/scriptengine.h"
|
|
#include "jsonrpc/scriptshandler.h"
|
|
|
|
#include "integrations/thingmanagerimplementation.h"
|
|
#include "integrations/thing.h"
|
|
#include "integrations/thingactioninfo.h"
|
|
#include "integrations/browseractioninfo.h"
|
|
#include "integrations/browseritemactioninfo.h"
|
|
#include "cloud/cloudmanager.h"
|
|
#include "cloud/cloudnotifications.h"
|
|
#include "cloud/cloudtransport.h"
|
|
|
|
#include "zigbee/zigbeemanager.h"
|
|
|
|
#include "hardware/modbus/modbusrtumanager.h"
|
|
#include "hardware/serialport/serialportmonitor.h"
|
|
|
|
#include <networkmanager.h>
|
|
|
|
#include <QDir>
|
|
#include <QCoreApplication>
|
|
|
|
NYMEA_LOGGING_CATEGORY(dcCore, "Core")
|
|
|
|
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(const QStringList &additionalInterfaces) {
|
|
qCDebug(dcCore()) << "Initializing NymeaCore";
|
|
|
|
qCDebug(dcPlatform()) << "Loading platform abstraction";
|
|
m_platform = new Platform(this);
|
|
|
|
qCDebug(dcCore()) << "Loading nymea configurations" << NymeaSettings(NymeaSettings::SettingsRoleGlobal).fileName();
|
|
m_configuration = new NymeaConfiguration(this);
|
|
|
|
qCDebug(dcCore()) << "Creating Time Manager";
|
|
// Migration path: nymea < 0.18 doesn't use system time zone but stores its own time zone in the config
|
|
// For migration, let's set the system's time zone to the config now to upgrade to the system time zone based nymea >= 0.18
|
|
if (QTimeZone(m_configuration->timeZone()).isValid()) {
|
|
if (m_platform->systemController()->setTimeZone(QTimeZone(m_configuration->timeZone()))) {
|
|
m_configuration->setTimeZone("");
|
|
}
|
|
}
|
|
m_timeManager = new TimeManager(this);
|
|
|
|
qCDebug(dcCore()) << "Creating User Manager";
|
|
m_userManager = new UserManager(NymeaSettings::settingsPath() + "/user-db.sqlite", this);
|
|
|
|
qCDebug(dcCore) << "Creating Server Manager";
|
|
m_serverManager = new ServerManager(m_platform, m_configuration, additionalInterfaces, this);
|
|
|
|
qCDebug(dcCore()) << "Create Zigbee Manager";
|
|
m_zigbeeManager = new ZigbeeManager(this);
|
|
|
|
qCDebug(dcCore()) << "Create Serial Port Monitor";
|
|
m_serialPortMonitor = new SerialPortMonitor(this);
|
|
|
|
qCDebug(dcCore()) << "Create Modbus RTU Manager";
|
|
m_modbusRtuManager = new ModbusRtuManager(m_serialPortMonitor, this);
|
|
|
|
qCDebug(dcCore) << "Creating Hardware Manager";
|
|
m_hardwareManager = new HardwareManagerImplementation(m_platform, m_serverManager->mqttBroker(), m_zigbeeManager, 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 Script Engine";
|
|
m_scriptEngine = new ScriptEngine(m_thingManager, this);
|
|
m_serverManager->jsonServer()->registerHandler(new ScriptsHandler(m_scriptEngine, m_scriptEngine));
|
|
|
|
qCDebug(dcCore()) << "Creating Tags Storage";
|
|
m_tagsStorage = new TagsStorage(m_thingManager, m_ruleEngine, this);
|
|
|
|
qCDebug(dcCore) << "Creating Network Manager";
|
|
m_networkManager = new NetworkManager(this);
|
|
m_networkManager->start();
|
|
|
|
qCDebug(dcCore) << "Creating Debug Server Handler";
|
|
m_debugServerHandler = new DebugServerHandler(this);
|
|
|
|
qCDebug(dcCore) << "Creating Cloud Manager";
|
|
m_cloudManager = new CloudManager(m_configuration, m_networkManager, this);
|
|
|
|
qCDebug(dcCore()) << "Loading experiences";
|
|
m_experienceManager = new ExperienceManager(m_thingManager, m_serverManager->jsonServer(), this);
|
|
|
|
|
|
// To be removed with 0.31 or later.
|
|
// Most of the cloud push notifications code has been removed with 0.30, this is just kept for a release or two
|
|
// to auto-remove all the cloud based push notification things users might have in their systems
|
|
CloudNotifications *cloudNotifications = m_cloudManager->createNotificationsPlugin();
|
|
m_thingManager->registerStaticPlugin(cloudNotifications);
|
|
|
|
CloudTransport *cloudTransport = m_cloudManager->createTransportInterface();
|
|
m_serverManager->jsonServer()->registerTransportInterface(cloudTransport, false);
|
|
|
|
connect(m_configuration, &NymeaConfiguration::serverNameChanged, m_serverManager, &ServerManager::setServerName);
|
|
|
|
connect(m_thingManager, &ThingManagerImplementation::pluginConfigChanged, this, &NymeaCore::pluginConfigChanged);
|
|
connect(m_thingManager, &ThingManagerImplementation::eventTriggered, this, &NymeaCore::gotEvent);
|
|
connect(m_thingManager, &ThingManagerImplementation::thingStateChanged, this, &NymeaCore::thingStateChanged);
|
|
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);
|
|
}
|
|
|
|
/*! Destructor of the \l{NymeaCore}. */
|
|
NymeaCore::~NymeaCore()
|
|
{
|
|
qCDebug(dcCore()) << "Shutting down NymeaCore";
|
|
m_logger->logSystemEvent(m_timeManager->currentDateTime(), false);
|
|
|
|
// Disconnect all signals/slots, we're going down now
|
|
m_timeManager->disconnect(this);
|
|
m_thingManager->disconnect(this);
|
|
m_ruleEngine->disconnect(this);
|
|
|
|
// Then stop magic from happening
|
|
qCDebug(dcCore) << "Shutting down \"Rule Engine\"";
|
|
delete m_ruleEngine;
|
|
|
|
// Next, ThingManager, so plugins don't access any resources any more.
|
|
qCDebug(dcCore) << "Shutting down \"Thing Manager\"";
|
|
delete m_thingManager;
|
|
|
|
// Destroy resources used by things
|
|
qCDebug(dcCore) << "Shutting down \"Server Manager\"";
|
|
delete m_serverManager;
|
|
qCDebug(dcCore) << "Shutting down \"CloudManager\"";
|
|
delete m_cloudManager;
|
|
qCDebug(dcCore()) << "Shutting down \"Hardware Manager\"";
|
|
delete m_hardwareManager;
|
|
|
|
// Now go ahead and clean up stuff.
|
|
qCDebug(dcCore) << "Shutting down \"Log Engine\"";
|
|
delete m_logger;
|
|
|
|
qCDebug(dcCore) << "Done shutting down NymeaCore";
|
|
}
|
|
|
|
void NymeaCore::destroy()
|
|
{
|
|
if (s_instance) {
|
|
delete s_instance;
|
|
}
|
|
|
|
s_instance = nullptr;
|
|
}
|
|
|
|
QPair<Thing::ThingError, QList<RuleId> > NymeaCore::removeConfiguredThing(const ThingId &thingId, const QHash<RuleId, RuleEngine::RemovePolicy> &removePolicyList)
|
|
{
|
|
Thing *thing = m_thingManager->findConfiguredThing(thingId);
|
|
|
|
if (!thing) {
|
|
return QPair<Thing::ThingError, QList<RuleId> > (Thing::ThingErrorThingNotFound, QList<RuleId>());
|
|
}
|
|
|
|
// Check if this is a child
|
|
if (!thing->parentId().isNull() && thing->autoCreated()) {
|
|
qCWarning(dcThingManager) << "Thing is an autocreated child of" << thing->parentId().toString() << ". Please remove the parent.";
|
|
return QPair<Thing::ThingError, QList<RuleId> > (Thing::ThingErrorThingIsChild, QList<RuleId>());
|
|
}
|
|
|
|
// FIXME: Let's remove this for now. It will come back with more fine grained control, presumably introducing a RemoveMethod flag in the DeviceClass
|
|
// if (thing->autoCreated()) {
|
|
// qCWarning(dcThingManager) << "This thing has been auto-created and cannot be deleted manually.";
|
|
// return QPair<Thing::ThingError, QList<RuleId> >(Thing::ThingErrorCreationMethodNotSupported, {});
|
|
// }
|
|
|
|
// Check if this thing has childs
|
|
QList<Thing *> thingsToRemove;
|
|
thingsToRemove.append(thing);
|
|
QList<Thing *> childs = m_thingManager->findChilds(thingId);
|
|
if (!childs.isEmpty()) {
|
|
foreach (Thing *child, childs) {
|
|
thingsToRemove.append(child);
|
|
}
|
|
}
|
|
|
|
// check things
|
|
QList<RuleId> offendingRules;
|
|
qCDebug(dcThingManager) << "Things to remove:";
|
|
foreach (Thing *d, thingsToRemove) {
|
|
qCDebug(dcThingManager) << " -> " << d->name() << d->id().toString();
|
|
|
|
// Check if thing is in a rule
|
|
foreach (const RuleId &ruleId, m_ruleEngine->findRules(d->id())) {
|
|
qCDebug(dcThingManager) << " -> in rule:" << ruleId.toString();
|
|
if (!offendingRules.contains(ruleId)) {
|
|
offendingRules.append(ruleId);
|
|
}
|
|
}
|
|
}
|
|
|
|
// check each offending rule if there is a corresponding remove policy
|
|
QHash<RuleId, RuleEngine::RemovePolicy> toBeChanged;
|
|
QList<RuleId> unhandledRules;
|
|
foreach (const RuleId &ruleId, offendingRules) {
|
|
bool found = false;
|
|
foreach (const RuleId &policyRuleId, removePolicyList.keys()) {
|
|
if (ruleId == policyRuleId) {
|
|
found = true;
|
|
toBeChanged.insert(ruleId, removePolicyList.value(ruleId));
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
unhandledRules.append(ruleId);
|
|
|
|
}
|
|
|
|
if (!unhandledRules.isEmpty()) {
|
|
qCWarning(dcThingManager) << "There are unhandled rules which depend on this thing:\n" << unhandledRules;
|
|
return QPair<Thing::ThingError, QList<RuleId> > (Thing::ThingErrorThingInRule, unhandledRules);
|
|
}
|
|
|
|
// Update the rules...
|
|
foreach (const RuleId &ruleId, toBeChanged.keys()) {
|
|
if (toBeChanged.value(ruleId) == RuleEngine::RemovePolicyCascade) {
|
|
m_ruleEngine->removeRule(ruleId);
|
|
} else if (toBeChanged.value(ruleId) == RuleEngine::RemovePolicyUpdate){
|
|
foreach (Thing *thing, thingsToRemove) {
|
|
m_ruleEngine->removeThingFromRule(ruleId, thing->id());
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove the childs
|
|
foreach (Thing *d, childs) {
|
|
Thing::ThingError removeError = m_thingManager->removeConfiguredThing(d->id());
|
|
if (removeError == Thing::ThingErrorNoError) {
|
|
m_logger->removeThingLogs(d->id());
|
|
}
|
|
}
|
|
|
|
// delete the things
|
|
Thing::ThingError removeError = m_thingManager->removeConfiguredThing(thingId);
|
|
if (removeError == Thing::ThingErrorNoError) {
|
|
m_logger->removeThingLogs(thingId);
|
|
}
|
|
|
|
return QPair<Thing::ThingError, QList<RuleId> > (Thing::ThingErrorNoError, QList<RuleId>());
|
|
}
|
|
|
|
|
|
Thing::ThingError NymeaCore::removeConfiguredThing(const ThingId &thingId, const RuleEngine::RemovePolicy &removePolicy)
|
|
{
|
|
Thing *thing = m_thingManager->findConfiguredThing(thingId);
|
|
|
|
if (!thing) {
|
|
return Thing::ThingErrorThingNotFound;
|
|
}
|
|
|
|
// Check if this is a child
|
|
if (!thing->parentId().isNull() && thing->autoCreated()) {
|
|
qCWarning(dcThingManager) << "Thing is an autocreated child of" << thing->parentId().toString() << ". Please remove the parent.";
|
|
return Thing::ThingErrorThingIsChild;
|
|
}
|
|
|
|
// FIXME: Let's remove this for now. It will come back with more fine grained control, presumably introducing a RemoveMethod flag in the DeviceClass
|
|
// if (thing->autoCreated()) {
|
|
// qCWarning(dcThingManager) << "This thing has been auto-created and cannot be deleted manually.";
|
|
// return Thing::ThingErrorCreationMethodNotSupported;
|
|
// }
|
|
|
|
// Check if this thing has childs
|
|
QList<Thing *> thingsToRemove;
|
|
thingsToRemove.append(thing);
|
|
QList<Thing *> childs = m_thingManager->findChilds(thingId);
|
|
if (!childs.isEmpty()) {
|
|
foreach (Thing *child, childs) {
|
|
thingsToRemove.append(child);
|
|
}
|
|
}
|
|
|
|
// check things
|
|
QList<RuleId> offendingRules;
|
|
qCDebug(dcThingManager) << "Things to remove:";
|
|
foreach (Thing *d, thingsToRemove) {
|
|
qCDebug(dcThingManager) << " -> " << d->name() << d->id().toString();
|
|
|
|
// Check if thing is in a rule
|
|
foreach (const RuleId &ruleId, m_ruleEngine->findRules(d->id())) {
|
|
qCDebug(dcThingManager) << " -> in rule:" << ruleId.toString();
|
|
if (!offendingRules.contains(ruleId)) {
|
|
offendingRules.append(ruleId);
|
|
}
|
|
}
|
|
}
|
|
|
|
// apply removepolicy for foreach rule
|
|
foreach (const RuleId &ruleId, offendingRules) {
|
|
if (removePolicy == RuleEngine::RemovePolicyCascade) {
|
|
m_ruleEngine->removeRule(ruleId);
|
|
} else if (removePolicy == RuleEngine::RemovePolicyUpdate){
|
|
foreach (Thing *thing, thingsToRemove) {
|
|
m_ruleEngine->removeThingFromRule(ruleId, thing->id());
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove the childs
|
|
foreach (Thing *d, childs) {
|
|
Thing::ThingError removeError = m_thingManager->removeConfiguredThing(d->id());
|
|
if (removeError == Thing::ThingErrorNoError) {
|
|
m_logger->removeThingLogs(d->id());
|
|
}
|
|
}
|
|
|
|
// delete the things
|
|
Thing::ThingError removeError = m_thingManager->removeConfiguredThing(thingId);
|
|
if (removeError == Thing::ThingErrorNoError) {
|
|
m_logger->removeThingLogs(thingId);
|
|
}
|
|
|
|
return removeError;
|
|
}
|
|
|
|
BrowserActionInfo* NymeaCore::executeBrowserItem(const BrowserAction &browserAction)
|
|
{
|
|
BrowserActionInfo *info = m_thingManager->executeBrowserItem(browserAction);
|
|
connect(info, &BrowserActionInfo::finished, info->thing(), [this, info](){
|
|
m_logger->logBrowserAction(info->browserAction(), info->status() == Thing::ThingErrorNoError ? Logging::LoggingLevelInfo : Logging::LoggingLevelAlert, info->status());
|
|
});
|
|
return info;
|
|
}
|
|
|
|
BrowserItemActionInfo *NymeaCore::executeBrowserItemAction(const BrowserItemAction &browserItemAction)
|
|
{
|
|
BrowserItemActionInfo *info = m_thingManager->executeBrowserItemAction(browserItemAction);
|
|
connect(info, &BrowserItemActionInfo::finished, info->thing(), [this, info](){
|
|
m_logger->logBrowserItemAction(info->browserItemAction(), info->status() == Thing::ThingErrorNoError ? Logging::LoggingLevelInfo : Logging::LoggingLevelAlert, info->status());
|
|
});
|
|
return info;
|
|
}
|
|
|
|
/*! Execute the given \a ruleActions. */
|
|
void NymeaCore::executeRuleActions(const QList<RuleAction> ruleActions)
|
|
{
|
|
QList<Action> actions;
|
|
QList<BrowserAction> browserActions;
|
|
foreach (const RuleAction &ruleAction, ruleActions) {
|
|
if (ruleAction.type() == RuleAction::TypeThing) {
|
|
Thing *thing = m_thingManager->findConfiguredThing(ruleAction.thingId());
|
|
if (!thing) {
|
|
qCWarning(dcRuleEngine()) << "Unable to find thing" << ruleAction.thingId() << "for rule action" << ruleAction;
|
|
continue;
|
|
}
|
|
ActionTypeId actionTypeId = ruleAction.actionTypeId();
|
|
ParamList params;
|
|
bool ok = true;
|
|
foreach (const RuleActionParam &ruleActionParam, ruleAction.ruleActionParams()) {
|
|
if (ruleActionParam.isValueBased()) {
|
|
params.append(Param(ruleActionParam.paramTypeId(), ruleActionParam.value()));
|
|
} else if (ruleActionParam.isStateBased()) {
|
|
Thing *stateThing = m_thingManager->findConfiguredThing(ruleActionParam.stateThingId());
|
|
if (!stateThing) {
|
|
qCWarning(dcRuleEngine()) << "Cannot find thing" << ruleActionParam.stateThingId() << "required by rule action";
|
|
ok = false;
|
|
break;
|
|
}
|
|
ThingClass stateThingClass = m_thingManager->findThingClass(stateThing->thingClassId());
|
|
if (!stateThingClass.hasStateType(ruleActionParam.stateTypeId())) {
|
|
qCWarning(dcRuleEngine()) << "Device" << thing->name() << thing->id() << "does not have a state type" << ruleActionParam.stateTypeId();
|
|
ok = false;
|
|
break;
|
|
}
|
|
params.append(Param(ruleActionParam.paramTypeId(), stateThing->stateValue(ruleActionParam.stateTypeId())));
|
|
}
|
|
}
|
|
if (!ok) {
|
|
qCWarning(dcRuleEngine()) << "Not executing rule action";
|
|
continue;
|
|
}
|
|
Action action(actionTypeId, thing->id(), Action::TriggeredByRule);
|
|
action.setParams(params);
|
|
actions.append(action);
|
|
} else if (ruleAction.type() == RuleAction::TypeBrowser) {
|
|
Thing *thing = m_thingManager->findConfiguredThing(ruleAction.thingId());
|
|
if (!thing) {
|
|
qCWarning(dcRuleEngine()) << "Unable to find thing" << ruleAction.thingId() << "for rule action" << ruleAction;
|
|
continue;
|
|
}
|
|
BrowserAction browserAction(ruleAction.thingId(), ruleAction.browserItemId());
|
|
browserActions.append(browserAction);
|
|
} else {
|
|
Things things = m_thingManager->findConfiguredThings(ruleAction.interface());
|
|
foreach (Thing* thing, things) {
|
|
ThingClass thingClass = m_thingManager->findThingClass(thing->thingClassId());
|
|
ActionType actionType = thingClass.actionTypes().findByName(ruleAction.interfaceAction());
|
|
if (actionType.id().isNull()) {
|
|
qCWarning(dcRuleEngine()) << "Error creating Action. The given ThingClass does not implement action:" << ruleAction.interfaceAction();
|
|
continue;
|
|
}
|
|
|
|
ParamList params;
|
|
bool ok = true;
|
|
foreach (const RuleActionParam &ruleActionParam, ruleAction.ruleActionParams()) {
|
|
ParamType paramType = actionType.paramTypes().findByName(ruleActionParam.paramName());
|
|
if (paramType.id().isNull()) {
|
|
qCWarning(dcRuleEngine()) << "Error creating Action. The given ActionType does not have a parameter:" << ruleActionParam.paramName();
|
|
ok = false;
|
|
continue;
|
|
}
|
|
if (ruleActionParam.isValueBased()) {
|
|
params.append(Param(paramType.id(), ruleActionParam.value()));
|
|
} else if (ruleActionParam.isStateBased()) {
|
|
Thing *stateThing = m_thingManager->findConfiguredThing(ruleActionParam.stateThingId());
|
|
if (!stateThing) {
|
|
qCWarning(dcRuleEngine()) << "Cannot find thing" << ruleActionParam.stateThingId() << "required by rule action";
|
|
ok = false;
|
|
break;
|
|
}
|
|
ThingClass stateThingClass = m_thingManager->findThingClass(stateThing->thingClassId());
|
|
if (!stateThingClass.hasStateType(ruleActionParam.stateTypeId())) {
|
|
qCWarning(dcRuleEngine()) << "Thing" << thing->name() << thing->id() << "does not have a state type" << ruleActionParam.stateTypeId();
|
|
ok = false;
|
|
break;
|
|
}
|
|
params.append(Param(paramType.id(), stateThing->stateValue(ruleActionParam.stateTypeId())));
|
|
}
|
|
}
|
|
if (!ok) {
|
|
qCWarning(dcRuleEngine()) << "Not executing rule action";
|
|
continue;
|
|
}
|
|
|
|
Action action = Action(actionType.id(), thing->id(), Action::TriggeredByRule);
|
|
action.setParams(params);
|
|
actions.append(action);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (const Action &action, actions) {
|
|
qCDebug(dcRuleEngine) << "Executing action" << action.actionTypeId() << action.params();
|
|
ThingActionInfo *info = m_thingManager->executeAction(action);
|
|
connect(info, &ThingActionInfo::finished, this, [info](){
|
|
if (info->status() != Thing::ThingErrorNoError) {
|
|
qCWarning(dcRuleEngine) << "Error executing action:" << info->status() << info->displayMessage();
|
|
}
|
|
});
|
|
}
|
|
|
|
foreach (const BrowserAction &browserAction, browserActions) {
|
|
BrowserActionInfo *info = executeBrowserItem(browserAction);
|
|
connect(info, &BrowserActionInfo::finished, this, [info](){
|
|
if (info->status() != Thing::ThingErrorNoError) {
|
|
qCWarning(dcRuleEngine) << "Error executing browser action:" << info->status();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/*! Calls the metheod RuleEngine::removeRule(\a id).
|
|
* \sa RuleEngine, */
|
|
RuleEngine::RuleError NymeaCore::removeRule(const RuleId &id)
|
|
{
|
|
RuleEngine::RuleError removeError = m_ruleEngine->removeRule(id);
|
|
if (removeError == RuleEngine::RuleErrorNoError)
|
|
m_logger->removeRuleLogs(id);
|
|
|
|
return removeError;
|
|
}
|
|
|
|
NymeaConfiguration *NymeaCore::configuration() const
|
|
{
|
|
return m_configuration;
|
|
}
|
|
|
|
ThingManager *NymeaCore::thingManager() const
|
|
{
|
|
return m_thingManager;
|
|
}
|
|
|
|
RuleEngine *NymeaCore::ruleEngine() const
|
|
{
|
|
return m_ruleEngine;
|
|
}
|
|
|
|
ScriptEngine *NymeaCore::scriptEngine() const
|
|
{
|
|
return m_scriptEngine;
|
|
}
|
|
|
|
TimeManager *NymeaCore::timeManager() const
|
|
{
|
|
return m_timeManager;
|
|
}
|
|
|
|
ServerManager *NymeaCore::serverManager() const
|
|
{
|
|
return m_serverManager;
|
|
}
|
|
|
|
QStringList NymeaCore::getAvailableLanguages()
|
|
{
|
|
qCDebug(dcCore()) << "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;
|
|
}
|
|
|
|
QStringList NymeaCore::loggingFilters()
|
|
{
|
|
return nymeaLoggingCategories();
|
|
}
|
|
|
|
QStringList NymeaCore::loggingFiltersPlugins()
|
|
{
|
|
QStringList loggingFiltersPlugins;
|
|
foreach (const QJsonObject &pluginMetadata, ThingManagerImplementation::pluginsMetadata()) {
|
|
QString pluginName = pluginMetadata.value("name").toString();
|
|
loggingFiltersPlugins << pluginName.left(1).toUpper() + pluginName.mid(1);
|
|
}
|
|
return loggingFiltersPlugins;
|
|
}
|
|
|
|
BluetoothServer *NymeaCore::bluetoothServer() const
|
|
{
|
|
return m_serverManager->bluetoothServer();
|
|
}
|
|
|
|
NetworkManager *NymeaCore::networkManager() const
|
|
{
|
|
return m_networkManager;
|
|
}
|
|
|
|
UserManager *NymeaCore::userManager() const
|
|
{
|
|
return m_userManager;
|
|
}
|
|
|
|
CloudManager *NymeaCore::cloudManager() const
|
|
{
|
|
return m_cloudManager;
|
|
}
|
|
|
|
DebugServerHandler *NymeaCore::debugServerHandler() const
|
|
{
|
|
return m_debugServerHandler;
|
|
}
|
|
|
|
TagsStorage *NymeaCore::tagsStorage() const
|
|
{
|
|
return m_tagsStorage;
|
|
}
|
|
|
|
Platform *NymeaCore::platform() const
|
|
{
|
|
return m_platform;
|
|
}
|
|
|
|
ZigbeeManager *NymeaCore::zigbeeManager() const
|
|
{
|
|
return m_zigbeeManager;
|
|
}
|
|
|
|
ModbusRtuManager *NymeaCore::modbusRtuManager() const
|
|
{
|
|
return m_modbusRtuManager;
|
|
}
|
|
|
|
void NymeaCore::gotEvent(const Event &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) {
|
|
RuleActionParams newParams;
|
|
foreach (RuleActionParam ruleActionParam, ruleAction.ruleActionParams()) {
|
|
// if this event param should be taken over in this action
|
|
if (event.eventTypeId() == ruleActionParam.eventTypeId()) {
|
|
QVariant eventValue = event.params().paramValue(ruleActionParam.eventParamTypeId());
|
|
|
|
// TODO: limits / scale calculation -> actionValue = eventValue * x
|
|
// something like a EventParamDescriptor
|
|
|
|
ruleActionParam.setValue(eventValue);
|
|
qCDebug(dcRuleEngine) << "Using param value from event:" << ruleActionParam.value();
|
|
}
|
|
newParams.append(ruleActionParam);
|
|
}
|
|
ruleAction.setRuleActionParams(newParams);
|
|
actions.append(ruleAction);
|
|
}
|
|
|
|
executeRuleActions(actions);
|
|
m_executingRules.clear();
|
|
}
|
|
|
|
void NymeaCore::onDateTimeChanged(const QDateTime &dateTime)
|
|
{
|
|
QList<RuleAction> actions;
|
|
foreach (const Rule &rule, m_ruleEngine->evaluateTime(dateTime)) {
|
|
// TimeEvent based
|
|
if (!rule.timeDescriptor().timeEventItems().isEmpty()) {
|
|
m_logger->logRuleTriggered(rule);
|
|
if (rule.statesActive() && rule.timeActive()) {
|
|
actions.append(rule.actions());
|
|
} else {
|
|
actions.append(rule.exitActions());
|
|
}
|
|
} else {
|
|
// Calendar based rule
|
|
m_logger->logRuleActiveChanged(rule);
|
|
emit ruleActiveChanged(rule);
|
|
if (rule.active()) {
|
|
actions.append(rule.actions());
|
|
} else {
|
|
actions.append(rule.exitActions());
|
|
}
|
|
}
|
|
}
|
|
executeRuleActions(actions);
|
|
}
|
|
|
|
LogEngine* NymeaCore::logEngine() const
|
|
{
|
|
return m_logger;
|
|
}
|
|
|
|
JsonRPCServerImplementation *NymeaCore::jsonRPCServer() const
|
|
{
|
|
return m_serverManager->jsonServer();
|
|
}
|
|
|
|
void NymeaCore::onThingDisappeared(const ThingId &thingId)
|
|
{
|
|
Thing *thing = m_thingManager->findConfiguredThing(thingId);
|
|
if (!thing) {
|
|
return;
|
|
}
|
|
|
|
// Check if this thing has childs
|
|
Things thingsToRemove;
|
|
thingsToRemove.append(thing);
|
|
QList<Thing *> childs = m_thingManager->findChilds(thingId);
|
|
if (!childs.isEmpty()) {
|
|
foreach (Thing *child, childs) {
|
|
thingsToRemove.append(child);
|
|
}
|
|
}
|
|
|
|
// check things
|
|
QList<RuleId> offendingRules;
|
|
qCDebug(dcThingManager) << "Thing to remove:";
|
|
foreach (Thing *d, thingsToRemove) {
|
|
qCDebug(dcThingManager) << " -> " << d->name() << d->id().toString();
|
|
|
|
// Check if thing is in a rule
|
|
foreach (const RuleId &ruleId, m_ruleEngine->findRules(d->id())) {
|
|
qCDebug(dcThingManager) << " -> in rule:" << ruleId.toString();
|
|
if (!offendingRules.contains(ruleId)) {
|
|
offendingRules.append(ruleId);
|
|
}
|
|
}
|
|
}
|
|
|
|
// update involved rules
|
|
foreach (const RuleId &ruleId, offendingRules) {
|
|
foreach (Thing *thing, thingsToRemove) {
|
|
m_ruleEngine->removeThingFromRule(ruleId, thing->id());
|
|
}
|
|
}
|
|
|
|
// remove the child devices
|
|
foreach (Thing *d, childs) {
|
|
Thing::ThingError removeError = m_thingManager->removeConfiguredThing(d->id());
|
|
if (removeError == Thing::ThingErrorNoError) {
|
|
m_logger->removeThingLogs(d->id());
|
|
}
|
|
}
|
|
|
|
// delete the thing
|
|
Thing::ThingError removeError = m_thingManager->removeConfiguredThing(thingId);
|
|
if (removeError == Thing::ThingErrorNoError) {
|
|
m_logger->removeThingLogs(thingId);
|
|
}
|
|
}
|
|
|
|
void NymeaCore::thingManagerLoaded()
|
|
{
|
|
m_ruleEngine->init();
|
|
// Evaluate rules on current time
|
|
onDateTimeChanged(m_timeManager->currentDateTime());
|
|
|
|
// Tell hardare resources we're done with loading stuff...
|
|
m_hardwareManager->thingsLoaded();
|
|
|
|
emit initialized();
|
|
|
|
// Do some houskeeping...
|
|
qCDebug(dcCore()) << "Starting housekeeping...";
|
|
QDateTime startTime = QDateTime::currentDateTime();
|
|
ThingsFetchJob *job = m_logger->fetchThings();
|
|
connect(job, &ThingsFetchJob::finished, m_thingManager, [this, job, startTime](){
|
|
foreach (const ThingId &thingId, job->results()) {
|
|
if (!m_thingManager->findConfiguredThing(thingId)) {
|
|
qCDebug(dcCore()) << "Cleaning stale thing entries from log DB for thing id" << thingId;
|
|
m_logger->removeThingLogs(thingId);
|
|
}
|
|
}
|
|
qCDebug(dcCore()) << "Housekeeping done in" << startTime.msecsTo(QDateTime::currentDateTime()) << "ms.";
|
|
});
|
|
|
|
foreach (const ThingId &thingId, m_ruleEngine->thingsInRules()) {
|
|
if (!m_thingManager->findConfiguredThing(thingId)) {
|
|
qCDebug(dcCore()) << "Cleaning stale rule entries for thing id" << thingId;
|
|
foreach (const RuleId &ruleId, m_ruleEngine->findRules(thingId)) {
|
|
m_ruleEngine->removeThingFromRule(ruleId, thingId);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (const Tag &tag, m_tagsStorage->tags()) {
|
|
if (!tag.ruleId().isNull() && !m_ruleEngine->findRule(tag.ruleId()).isValid()) {
|
|
qCDebug(dcCore()) << "Cleaning up stale rule tag" << tag;
|
|
m_tagsStorage->removeTag(tag);
|
|
}
|
|
if (!tag.thingId().isNull() && !m_thingManager->findConfiguredThing(tag.thingId())) {
|
|
qCDebug(dcCore()) << "Cleaning up stale thing tag" << tag.tagId();
|
|
m_tagsStorage->removeTag(tag);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|