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 92197cb97c Refactor NymeaCore class
This moves all the things and rules logic away from NymeaCore
into their respective modules where it belongs.

One major change is the removal of the removePolicy functionality.
This was somewhat broken as it was only working for rules but not
for all the other modules like scripts, experiences etc. After
an attempt to create something that works with all modules it
really seemed that this does not make a lot of sence after all,
given that updating rules would in most cases leave something
very broken behind and removing them was the only sane thing to do.

On the other hand, experience plugins may not work well with such
a policy eithre as they may require to do their own special thing.

So in the end the removePolicy was dropped altogether. Apps should
instead figure out themselves what removal of a thing may imply and
inform the user about that beforehand.
2023-01-24 17:56:04 +01:00

390 lines
12 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 "zigbee/zigbeemanager.h"
#include "zwave/zwavemanager.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 Serial Port Monitor";
m_serialPortMonitor = new SerialPortMonitor(this);
qCDebug(dcCore()) << "Create Zigbee Manager";
m_zigbeeManager = new ZigbeeManager(this);
qCDebug(dcCore()) << "Creating ZWave Manager";
m_zwaveManager = new ZWaveManager(m_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_zwaveManager, m_modbusRtuManager, 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);
qCDebug(dcCore) << "Creating Thing Manager (locale:" << m_configuration->locale() << ")";
m_thingManager = new ThingManagerImplementation(m_hardwareManager, m_logger, m_configuration->locale(), this);
qCDebug(dcCore) << "Creating Rule Engine";
m_ruleEngine = new RuleEngine(m_thingManager, m_timeManager, m_logger, this);
qCDebug(dcCore()) << "Creating Script Engine";
m_scriptEngine = new scriptengine::ScriptEngine(m_thingManager, this);
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()) << "Loading experiences";
m_experienceManager = new ExperienceManager(m_thingManager, m_serverManager->jsonServer(), this);
connect(m_configuration, &NymeaConfiguration::serverNameChanged, m_serverManager, &ServerManager::setServerName);
connect(m_thingManager, &ThingManagerImplementation::loaded, this, &NymeaCore::thingManagerLoaded);
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;
qCDebug(dcCore()) << "Shutting down \"Experiences\"";
delete m_experienceManager;
// 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 \"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;
}
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;
}
ZWaveManager *NymeaCore::zwaveManager() const
{
return m_zwaveManager;
}
ModbusRtuManager *NymeaCore::modbusRtuManager() const
{
return m_modbusRtuManager;
}
ExperienceManager *NymeaCore::experienceManager() const
{
return m_experienceManager;
}
LogEngine* NymeaCore::logEngine() const
{
return m_logger;
}
JsonRPCServerImplementation *NymeaCore::jsonRPCServer() const
{
return m_serverManager->jsonServer();
}
void NymeaCore::thingManagerLoaded()
{
// 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);
}
}
}
}