This repository has been archived on 2026-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
Michael Zanetti 2a91dad1f0 Don't generate events for state changes any more
Up until now, nymea would generate EventTypes for every StateType
as well as emit an Event (along with a StateChanged notification) for
every change. This results in a lot of duplicated network traffic
which is of not much use. The StateChanged notification contains
all the information in the Event too and nymea:app actually never
really used Events for state changes.

This commit removes the events from the ThingClass, making it a lot
smaller and stops emitting Events for state changes.

As this is breaking the behavior, the JSONRPC API major version
is bumped.
2022-02-08 10:38:41 +01:00

887 lines
34 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::onEventTriggered);
connect(m_thingManager, &ThingManagerImplementation::thingStateChanged, this, &NymeaCore::onThingStateChanged);
connect(m_thingManager, &ThingManagerImplementation::thingAdded, this, &NymeaCore::thingAdded);
connect(m_thingManager, &ThingManagerImplementation::thingChanged, this, &NymeaCore::thingChanged);
connect(m_thingManager, &ThingManagerImplementation::thingSettingChanged, this, &NymeaCore::thingSettingChanged);
connect(m_thingManager, &ThingManagerImplementation::thingRemoved, this, &NymeaCore::thingRemoved);
connect(m_thingManager, &ThingManagerImplementation::thingDisappeared, this, &NymeaCore::onThingDisappeared);
connect(m_thingManager, &ThingManagerImplementation::loaded, this, &NymeaCore::thingManagerLoaded);
connect(m_ruleEngine, &RuleEngine::ruleAdded, this, &NymeaCore::ruleAdded);
connect(m_ruleEngine, &RuleEngine::ruleRemoved, this, &NymeaCore::ruleRemoved);
connect(m_ruleEngine, &RuleEngine::ruleConfigurationChanged, this, &NymeaCore::ruleConfigurationChanged);
connect(m_timeManager, &TimeManager::dateTimeChanged, this, &NymeaCore::onDateTimeChanged);
m_logger->logSystemEvent(m_timeManager->currentDateTime(), true);
}
/*! 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::onEventTriggered(const Event &event)
{
emit eventTriggered(event);
evaluateRules(event);
}
void NymeaCore::onThingStateChanged(Thing *thing, const StateTypeId &stateTypeId, const QVariant &value, const QVariant &minValue, const QVariant &maxValue)
{
emit thingStateChanged(thing, stateTypeId, value, minValue, maxValue);
// The rule engine can have event based rules that would trigger when a state changes
// without "binding" to the state (as a stateEvaluator would do). So generate a fake event
// for every state change.
// TODO: This whole rule engine related code in this file should probably move into the RuleEngine itself.
Param valueParam(ParamTypeId(stateTypeId.toString()), value);
Event event(EventTypeId(stateTypeId.toString()), thing->id(), ParamList() << valueParam);
evaluateRules(event);
}
void NymeaCore::evaluateRules(const Event &event)
{
QList<RuleAction> actions;
QList<RuleAction> eventBasedActions;
foreach (const Rule &rule, m_ruleEngine->evaluateEvent(event)) {
if (m_executingRules.contains(rule.id())) {
qCWarning(dcRuleEngine()) << "WARNING: Loop detected in rule execution for rule" << rule.id() << 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);
}
}
}
}