mirror of https://github.com/nymea/nymea.git
2134 lines
90 KiB
C++
2134 lines
90 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 "thingmanagerimplementation.h"
|
|
#include "translator.h"
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5,12,0)
|
|
#include "scriptintegrationplugin.h"
|
|
#endif
|
|
|
|
#include "loggingcategories.h"
|
|
#include "typeutils.h"
|
|
#include "nymeasettings.h"
|
|
#include "version.h"
|
|
#include "plugininfocache.h"
|
|
|
|
#include "integrations/thingdiscoveryinfo.h"
|
|
#include "integrations/thingpairinginfo.h"
|
|
#include "integrations/thingsetupinfo.h"
|
|
#include "integrations/thingactioninfo.h"
|
|
#include "integrations/integrationplugin.h"
|
|
#include "integrations/thingutils.h"
|
|
#include "integrations/browseresult.h"
|
|
#include "integrations/browseritemresult.h"
|
|
#include "integrations/browseractioninfo.h"
|
|
#include "integrations/browseritemactioninfo.h"
|
|
|
|
//#include "unistd.h"
|
|
|
|
#include "plugintimer.h"
|
|
|
|
#include <QPluginLoader>
|
|
#include <QStaticPlugin>
|
|
#include <QtPlugin>
|
|
#include <QDebug>
|
|
#include <QStringList>
|
|
#include <QCoreApplication>
|
|
#include <QStandardPaths>
|
|
#include <QDir>
|
|
|
|
ThingManagerImplementation::ThingManagerImplementation(HardwareManager *hardwareManager, const QLocale &locale, QObject *parent) :
|
|
ThingManager(parent),
|
|
m_hardwareManager(hardwareManager),
|
|
m_locale(locale),
|
|
m_translator(new Translator(this))
|
|
{
|
|
qRegisterMetaType<ThingClassId>();
|
|
qRegisterMetaType<ThingDescriptor>();
|
|
|
|
foreach (const Interface &interface, ThingUtils::allInterfaces()) {
|
|
m_supportedInterfaces.insert(interface.name(), interface);
|
|
}
|
|
|
|
// Migrate config from devices.conf (<0.20) to things.conf
|
|
QString settingsPath = NymeaSettings::settingsPath();
|
|
if (QFile::exists(settingsPath + "/devices.conf") && !QFile::exists(settingsPath + "/things.conf")) {
|
|
qCDebug(dcThingManager()) << "Migrating config from devices.conf to things.conf";
|
|
QFile oldFile(settingsPath + "/devices.conf");
|
|
oldFile.copy(settingsPath + "/things.conf");
|
|
QFile oldStateFile(settingsPath + "/devicestates.conf");
|
|
oldStateFile.copy(settingsPath + "/thingstates.conf");
|
|
}
|
|
|
|
// Give hardware a chance to start up before loading plugins etc.
|
|
QMetaObject::invokeMethod(this, "loadPlugins", Qt::QueuedConnection);
|
|
QMetaObject::invokeMethod(this, "loadConfiguredThings", Qt::QueuedConnection);
|
|
QMetaObject::invokeMethod(this, "startMonitoringAutoThings", Qt::QueuedConnection);
|
|
|
|
// Make sure this is always emitted after plugins and things are loaded
|
|
QMetaObject::invokeMethod(this, "onLoaded", Qt::QueuedConnection);
|
|
}
|
|
|
|
ThingManagerImplementation::~ThingManagerImplementation()
|
|
{
|
|
delete m_translator;
|
|
|
|
foreach (Thing *thing, m_configuredThings) {
|
|
storeThingStates(thing);
|
|
delete thing;
|
|
}
|
|
|
|
foreach (IntegrationPlugin *plugin, m_integrationPlugins) {
|
|
if (plugin->parent() == this) {
|
|
qCDebug(dcThingManager()) << "Deleting plugin" << plugin->pluginName();
|
|
delete plugin;
|
|
} else {
|
|
qCDebug(dcThingManager()) << "Not deleting plugin" << plugin->pluginName();
|
|
}
|
|
}
|
|
}
|
|
|
|
QStringList ThingManagerImplementation::pluginSearchDirs()
|
|
{
|
|
QStringList searchDirs;
|
|
QByteArray envPath = qgetenv("NYMEA_PLUGINS_PATH");
|
|
if (!envPath.isEmpty()) {
|
|
searchDirs << QString(envPath).split(':');
|
|
}
|
|
|
|
foreach (QString libraryPath, QCoreApplication::libraryPaths()) {
|
|
searchDirs << libraryPath.replace("qt5", "nymea");
|
|
}
|
|
foreach (QString libraryPath, QCoreApplication::libraryPaths()) {
|
|
searchDirs << libraryPath.replace("plugins", "nymea/plugins");
|
|
}
|
|
searchDirs << QDir(QCoreApplication::applicationDirPath() + "/../lib/nymea/plugins/").absolutePath();
|
|
searchDirs << QDir(QCoreApplication::applicationDirPath() + "/../plugins/").absolutePath();
|
|
searchDirs << QDir(QCoreApplication::applicationDirPath() + "/../../../plugins/").absolutePath();
|
|
searchDirs.removeDuplicates();
|
|
return searchDirs;
|
|
}
|
|
|
|
QList<QJsonObject> ThingManagerImplementation::pluginsMetadata()
|
|
{
|
|
QList<QJsonObject> pluginList;
|
|
foreach (const QString &path, pluginSearchDirs()) {
|
|
QDir dir(path);
|
|
foreach (const QString &entry, dir.entryList()) {
|
|
QFileInfo fi;
|
|
if (entry.startsWith("libnymea_integrationplugin") && entry.endsWith(".so")) {
|
|
fi.setFile(path + "/" + entry);
|
|
} else {
|
|
fi.setFile(path + "/" + entry + "/libnymea_integrationplugin" + entry + ".so");
|
|
}
|
|
if (!fi.exists()) {
|
|
continue;
|
|
}
|
|
QPluginLoader loader(fi.absoluteFilePath());
|
|
pluginList.append(loader.metaData().value("MetaData").toObject());
|
|
}
|
|
}
|
|
return pluginList;
|
|
}
|
|
|
|
void ThingManagerImplementation::registerStaticPlugin(IntegrationPlugin *plugin, const PluginMetadata &metaData)
|
|
{
|
|
if (!metaData.isValid()) {
|
|
qCWarning(dcThingManager()) << "Plugin metadata not valid. Not loading static plugin:" << plugin->pluginName();
|
|
return;
|
|
}
|
|
loadPlugin(plugin, metaData);
|
|
}
|
|
|
|
IntegrationPlugins ThingManagerImplementation::plugins() const
|
|
{
|
|
return m_integrationPlugins.values();
|
|
}
|
|
|
|
IntegrationPlugin *ThingManagerImplementation::plugin(const PluginId &pluginId) const
|
|
{
|
|
return m_integrationPlugins.value(pluginId);
|
|
}
|
|
|
|
Thing::ThingError ThingManagerImplementation::setPluginConfig(const PluginId &pluginId, const ParamList &pluginConfig)
|
|
{
|
|
IntegrationPlugin *plugin = m_integrationPlugins.value(pluginId);
|
|
if (!plugin) {
|
|
qCWarning(dcThingManager()) << "Could not set plugin configuration. There is no plugin with id" << pluginId.toString();
|
|
return Thing::ThingErrorPluginNotFound;
|
|
}
|
|
|
|
ParamList params = buildParams(plugin->configurationDescription(), pluginConfig);
|
|
Thing::ThingError verify = ThingUtils::verifyParams(plugin->configurationDescription(), params);
|
|
if (verify != Thing::ThingErrorNoError)
|
|
return verify;
|
|
|
|
Thing::ThingError result = plugin->setConfiguration(params);
|
|
if (result != Thing::ThingErrorNoError)
|
|
return result;
|
|
|
|
NymeaSettings settings(NymeaSettings::SettingsRolePlugins);
|
|
settings.beginGroup("PluginConfig");
|
|
settings.beginGroup(plugin->pluginId().toString());
|
|
|
|
foreach (const Param ¶m, pluginConfig) {
|
|
settings.beginGroup(param.paramTypeId().toString());
|
|
settings.setValue("type", static_cast<int>(param.value().type()));
|
|
settings.setValue("value", param.value());
|
|
settings.endGroup();
|
|
}
|
|
|
|
settings.endGroup();
|
|
settings.endGroup();
|
|
emit pluginConfigChanged(plugin->pluginId(), pluginConfig);
|
|
return result;
|
|
}
|
|
|
|
Vendors ThingManagerImplementation::supportedVendors() const
|
|
{
|
|
return m_supportedVendors.values();
|
|
}
|
|
|
|
Interfaces ThingManagerImplementation::supportedInterfaces() const
|
|
{
|
|
return m_supportedInterfaces.values();
|
|
}
|
|
|
|
ThingClasses ThingManagerImplementation::supportedThings(const VendorId &vendorId) const
|
|
{
|
|
if (vendorId.isNull()) {
|
|
return m_supportedThings.values();
|
|
}
|
|
QList<ThingClass> ret;
|
|
foreach (const ThingClass &thingClass, m_supportedThings) {
|
|
if (!vendorId.isNull() && thingClass.vendorId() != vendorId) {
|
|
continue;
|
|
}
|
|
ret.append(thingClass);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
ThingDiscoveryInfo* ThingManagerImplementation::discoverThings(const ThingClassId &thingClassId, const ParamList ¶ms)
|
|
{
|
|
ThingClass thingClass = findThingClass(thingClassId);
|
|
if (!thingClass.isValid()) {
|
|
qCWarning(dcThingManager) << "Thing discovery failed. Invalid thing class id:" << thingClassId.toString();
|
|
ThingDiscoveryInfo *discoveryInfo = new ThingDiscoveryInfo(thingClassId, params, this);
|
|
discoveryInfo->finish(Thing::ThingErrorThingClassNotFound);
|
|
return discoveryInfo;
|
|
}
|
|
if (!thingClass.createMethods().testFlag(ThingClass::CreateMethodDiscovery)) {
|
|
qCWarning(dcThingManager) << "Thing discovery failed. Thing class" << thingClass.name() << "cannot be discovered.";
|
|
ThingDiscoveryInfo *discoveryInfo = new ThingDiscoveryInfo(thingClassId, params, this);
|
|
discoveryInfo->finish(Thing::ThingErrorCreationMethodNotSupported);
|
|
return discoveryInfo;
|
|
}
|
|
IntegrationPlugin *plugin = m_integrationPlugins.value(thingClass.pluginId());
|
|
if (!plugin) {
|
|
qCWarning(dcThingManager) << "Thing discovery failed. Plugin not found for thing class" << thingClass.name();
|
|
ThingDiscoveryInfo *discoveryInfo = new ThingDiscoveryInfo(thingClassId, params, this);
|
|
discoveryInfo->finish(Thing::ThingErrorPluginNotFound, tr("The plugin for this thing is not loaded."));
|
|
return discoveryInfo;
|
|
}
|
|
|
|
ParamList effectiveParams = buildParams(thingClass.discoveryParamTypes(), params);
|
|
Thing::ThingError result = ThingUtils::verifyParams(thingClass.discoveryParamTypes(), effectiveParams);
|
|
if (result != Thing::ThingErrorNoError) {
|
|
qCWarning(dcThingManager) << "Thing discovery failed. Parameter verification failed.";
|
|
ThingDiscoveryInfo *discoveryInfo = new ThingDiscoveryInfo(thingClassId, params, this);
|
|
discoveryInfo->finish(result);
|
|
return discoveryInfo;
|
|
}
|
|
|
|
ThingDiscoveryInfo *discoveryInfo = new ThingDiscoveryInfo(thingClassId, effectiveParams, this, 30000);
|
|
connect(discoveryInfo, &ThingDiscoveryInfo::finished, this, [this, discoveryInfo](){
|
|
if (discoveryInfo->status() != Thing::ThingErrorNoError) {
|
|
qCWarning(dcThingManager()) << "Discovery failed:" << discoveryInfo->status() << discoveryInfo->displayMessage();
|
|
return;
|
|
}
|
|
qCDebug(dcThingManager()) << "Discovery finished. Found things:" << discoveryInfo->thingDescriptors().count();
|
|
foreach (const ThingDescriptor &descriptor, discoveryInfo->thingDescriptors()) {
|
|
if (!descriptor.isValid()) {
|
|
qCWarning(dcThingManager()) << "Descriptor is invalid. Not adding to results";
|
|
continue;
|
|
}
|
|
m_discoveredThings.insert(descriptor.id(), descriptor);
|
|
}
|
|
});
|
|
|
|
qCDebug(dcThingManager) << "Thing discovery for" << thingClass.name() << "started...";
|
|
plugin->discoverThings(discoveryInfo);
|
|
return discoveryInfo;
|
|
}
|
|
|
|
ThingSetupInfo* ThingManagerImplementation::addConfiguredThing(const ThingClassId &thingClassId, const ParamList ¶ms, const QString &name)
|
|
{
|
|
return addConfiguredThingInternal(thingClassId, name, params);
|
|
}
|
|
|
|
ThingSetupInfo *ThingManagerImplementation::addConfiguredThing(const ThingDescriptorId &thingDescriptorId, const ParamList ¶ms, const QString &name)
|
|
{
|
|
ThingDescriptor descriptor = m_discoveredThings.value(thingDescriptorId);
|
|
if (!descriptor.isValid()) {
|
|
qCWarning(dcThingManager()) << "Cannot add thing. ThingDescriptor" << thingDescriptorId << "not found.";
|
|
ThingSetupInfo *info = new ThingSetupInfo(nullptr, this);
|
|
info->finish(Thing::ThingErrorThingDescriptorNotFound);
|
|
return info;
|
|
}
|
|
|
|
ThingClass thingClass = findThingClass(descriptor.thingClassId());
|
|
if (!thingClass.isValid()) {
|
|
qCWarning(dcThingManager()) << "Cannot add thing. ThingClass" << descriptor.thingClassId() << "not found.";
|
|
ThingSetupInfo *info = new ThingSetupInfo(nullptr, this);
|
|
info->finish(Thing::ThingErrorThingClassNotFound);
|
|
return info;
|
|
}
|
|
if (!thingClass.createMethods().testFlag(ThingClass::CreateMethodDiscovery)) {
|
|
qCWarning(dcThingManager()) << "Cannot add thing. This thing cannot be added via discovery.";
|
|
ThingSetupInfo *info = new ThingSetupInfo(nullptr, this);
|
|
info->finish(Thing::ThingErrorCreationMethodNotSupported);
|
|
return info;
|
|
}
|
|
|
|
// Merging params from descriptor and user provided ones
|
|
ParamList finalParams = buildParams(thingClass.paramTypes(), params, descriptor.params());
|
|
|
|
return addConfiguredThingInternal(descriptor.thingClassId(), name, finalParams, descriptor.parentId());
|
|
}
|
|
|
|
ThingSetupInfo* ThingManagerImplementation::reconfigureThing(const ThingId &thingId, const ParamList ¶ms, const QString &name)
|
|
{
|
|
Thing *thing = findConfiguredThing(thingId);
|
|
if (!thing) {
|
|
qCWarning(dcThingManager()) << "Cannot reconfigure thing. Thing with id" << thingId.toString() << "not found.";
|
|
ThingSetupInfo *info = new ThingSetupInfo(nullptr, this);
|
|
info->finish(Thing::ThingErrorThingNotFound);
|
|
return info;
|
|
}
|
|
|
|
ThingClass thingClass = findThingClass(thing->thingClassId());
|
|
if (thingClass.id().isNull()) {
|
|
qCWarning(dcThingManager()) << "Cannot reconfigure thing. ThingClass for thing" << thing->name() << thingId.toString() << "not found.";
|
|
ThingSetupInfo *info = new ThingSetupInfo(nullptr, this);
|
|
info->finish(Thing::ThingErrorThingClassNotFound);
|
|
return info;
|
|
}
|
|
|
|
foreach (const ParamType ¶mType, thingClass.paramTypes()) {
|
|
if (paramType.readOnly() && params.hasParam(paramType.id())) {
|
|
ThingSetupInfo *info = new ThingSetupInfo(nullptr, this);
|
|
qCWarning(dcThingManager()) << "Parameter" << paramType.name() << paramType.id() << "is not writable";
|
|
info->finish(Thing::ThingErrorParameterNotWritable);
|
|
return info;
|
|
}
|
|
}
|
|
|
|
ParamList finalParams = buildParams(thingClass.paramTypes(), params);
|
|
|
|
return reconfigureThingInternal(thing, finalParams, name);
|
|
}
|
|
|
|
ThingSetupInfo *ThingManagerImplementation::reconfigureThing(const ThingDescriptorId &thingDescriptorId, const ParamList ¶ms, const QString &name)
|
|
{
|
|
ThingDescriptor descriptor = m_discoveredThings.value(thingDescriptorId);
|
|
if (!descriptor.isValid()) {
|
|
qCWarning(dcThingManager()) << "Cannot reconfigure thing. No thing descriptor with ID" << thingDescriptorId << "found.";
|
|
ThingSetupInfo *info = new ThingSetupInfo(nullptr, this);
|
|
info->finish(Thing::ThingErrorThingDescriptorNotFound);
|
|
return info;
|
|
}
|
|
|
|
Thing *thing = findConfiguredThing(descriptor.thingId());
|
|
if (!thing) {
|
|
ThingSetupInfo *info = new ThingSetupInfo(nullptr, this);
|
|
qCWarning(dcThingManager()) << "Cannot reconfigure thing. No thing with ID" << descriptor.thingId() << "found.";
|
|
info->finish(Thing::ThingErrorThingNotFound);
|
|
return info;
|
|
}
|
|
|
|
ThingClass thingClass = findThingClass(thing->thingClassId());
|
|
if (!thingClass.isValid()) {
|
|
qCWarning(dcThingManager()) << "Cannot reconfigure tning. No ThingClass with ID" << thing->thingClassId() << "found.";
|
|
ThingSetupInfo *info = new ThingSetupInfo(nullptr, this);
|
|
info->finish(Thing::ThingErrorThingClassNotFound);
|
|
return info;
|
|
}
|
|
|
|
ParamList finalParams = buildParams(thing->thingClass().paramTypes(), params, descriptor.params());
|
|
return reconfigureThingInternal(thing, finalParams, name);
|
|
}
|
|
|
|
ThingSetupInfo *ThingManagerImplementation::reconfigureThingInternal(Thing *thing, const ParamList ¶ms, const QString &name)
|
|
{
|
|
IntegrationPlugin *plugin = m_integrationPlugins.value(thing->thingClass().pluginId());
|
|
if (!plugin) {
|
|
qCWarning(dcThingManager()) << "Cannot reconfigure thing. Plugin for ThingClass" << thing->thingClassId().toString() << "not found.";
|
|
ThingSetupInfo *info = new ThingSetupInfo(nullptr, this);
|
|
info->finish(Thing::ThingErrorPluginNotFound);
|
|
return info;
|
|
}
|
|
|
|
ParamList finalParams = buildParams(thing->thingClass().paramTypes(), params);
|
|
Thing::ThingError result = ThingUtils::verifyParams(thing->thingClass().paramTypes(), finalParams);
|
|
if (result != Thing::ThingErrorNoError) {
|
|
qCWarning(dcThingManager()) << "Cannot reconfigure thing. Params failed validation.";
|
|
ThingSetupInfo *info = new ThingSetupInfo(nullptr, this);
|
|
info->finish(result);
|
|
return info;
|
|
}
|
|
|
|
// first remove the thing in the plugin
|
|
plugin->thingRemoved(thing);
|
|
|
|
// mark setup as incomplete
|
|
thing->setSetupStatus(Thing::ThingSetupStatusInProgress, Thing::ThingErrorNoError);
|
|
|
|
// set new params
|
|
foreach (const Param ¶m, params) {
|
|
thing->setParamValue(param.paramTypeId(), param.value());
|
|
}
|
|
|
|
if (!name.isEmpty()) {
|
|
thing->setName(name);
|
|
}
|
|
|
|
// try to setup the thing with the new params
|
|
ThingSetupInfo *info = new ThingSetupInfo(thing, this, 30000);
|
|
plugin->setupThing(info);
|
|
connect(info, &ThingSetupInfo::finished, this, [this, info](){
|
|
|
|
if (info->status() != Thing::ThingErrorNoError) {
|
|
qCWarning(dcThingManager()) << "Thing reconfiguration failed for" << info->thing()->name() << info->thing()->id().toString() << info->status() << info->displayMessage();
|
|
info->thing()->setSetupStatus(Thing::ThingSetupStatusFailed, info->status(), info->displayMessage());
|
|
// TODO: recover old params.??
|
|
return;
|
|
}
|
|
|
|
storeConfiguredThings();
|
|
|
|
postSetupThing(info->thing());
|
|
info->thing()->setSetupStatus(Thing::ThingSetupStatusComplete, Thing::ThingErrorNoError);
|
|
|
|
emit thingChanged(info->thing());
|
|
|
|
});
|
|
|
|
return info;
|
|
|
|
}
|
|
|
|
Thing::ThingError ThingManagerImplementation::editThing(const ThingId &thingId, const QString &name)
|
|
{
|
|
Thing *thing = findConfiguredThing(thingId);
|
|
if (!thing)
|
|
return Thing::ThingErrorThingNotFound;
|
|
|
|
thing->setName(name);
|
|
|
|
return Thing::ThingErrorNoError;
|
|
}
|
|
|
|
Thing::ThingError ThingManagerImplementation::setThingSettings(const ThingId &thingId, const ParamList &settings)
|
|
{
|
|
Thing *thing = findConfiguredThing(thingId);
|
|
if (!thing) {
|
|
qCWarning(dcThingManager()) << "Cannot set thing settings. Thing" << thingId.toString() << "not found";
|
|
return Thing::ThingErrorThingNotFound;
|
|
}
|
|
// build a list of settings using: a) provided new settings b) previous settings and c) default values
|
|
ParamList effectiveSettings = buildParams(thing->thingClass().settingsTypes(), settings, thing->settings());
|
|
Thing::ThingError status = ThingUtils::verifyParams(thing->thingClass().settingsTypes(), effectiveSettings);
|
|
if (status != Thing::ThingErrorNoError) {
|
|
qCWarning(dcThingManager()) << "Error setting thing settings for" << thing->name() << thing->id().toString();
|
|
return status;
|
|
}
|
|
thing->setSettings(effectiveSettings);
|
|
return Thing::ThingErrorNoError;
|
|
}
|
|
|
|
ThingPairingInfo* ThingManagerImplementation::pairThing(const ThingClassId &thingClassId, const ParamList ¶ms, const QString &name)
|
|
{
|
|
PairingTransactionId transactionId = PairingTransactionId::createPairingTransactionId();
|
|
|
|
ThingClass thingClass = m_supportedThings.value(thingClassId);
|
|
if (!thingClass.isValid()) {
|
|
qCWarning(dcThingManager) << "Cannot find a ThingClass with ID" << thingClassId.toString();
|
|
ThingPairingInfo *info = new ThingPairingInfo(transactionId, thingClassId, ThingId(), name, ParamList(), ThingId(), this);
|
|
info->finish(Thing::ThingErrorThingClassNotFound);
|
|
return info;
|
|
}
|
|
|
|
// Create new thing id
|
|
ThingId newThingId = ThingId::createThingId();
|
|
|
|
// Use given params, if there are missing some, use the defaults ones.
|
|
ParamList finalParams = buildParams(thingClass.paramTypes(), params);
|
|
|
|
ThingPairingInfo *info = new ThingPairingInfo(transactionId, thingClassId, newThingId, name, finalParams, ThingId(), this, 30000);
|
|
pairThingInternal(info);
|
|
return info;
|
|
}
|
|
|
|
ThingPairingInfo* ThingManagerImplementation::pairThing(const ThingDescriptorId &thingDescriptorId, const ParamList ¶ms, const QString &name)
|
|
{
|
|
PairingTransactionId pairingTransactionId = PairingTransactionId::createPairingTransactionId();
|
|
ThingDescriptor descriptor = m_discoveredThings.value(thingDescriptorId);
|
|
if (!descriptor.isValid()) {
|
|
qCWarning(dcThingManager) << "Cannot find a ThingDescriptor with ID" << thingDescriptorId.toString();
|
|
ThingPairingInfo *info = new ThingPairingInfo(pairingTransactionId, ThingClassId(), ThingId(), name, ParamList(), ThingId(), this);
|
|
info->finish(Thing::ThingErrorThingDescriptorNotFound);
|
|
return info;
|
|
}
|
|
|
|
ThingClass thingClass = m_supportedThings.value(descriptor.thingClassId());
|
|
if (!thingClass.isValid()) {
|
|
qCWarning(dcThingManager) << "Cannot find a ThingClass with ID" << descriptor.thingClassId().toString();
|
|
ThingPairingInfo *info = new ThingPairingInfo(pairingTransactionId, descriptor.thingClassId(), ThingId(), name, ParamList(), ThingId(), this);
|
|
info->finish(Thing::ThingErrorThingClassNotFound);
|
|
return info;
|
|
}
|
|
|
|
ThingId thingId = descriptor.thingId();
|
|
// If it's a new thing (not a reconfiguration), create a new ThingId now.
|
|
if (thingId.isNull()) {
|
|
thingId = ThingId::createThingId();
|
|
}
|
|
|
|
// Use given params, if there are missing some, use the discovered ones.
|
|
ParamList finalParams = buildParams(thingClass.paramTypes(), params, descriptor.params());
|
|
|
|
ThingPairingInfo *info = new ThingPairingInfo(pairingTransactionId, descriptor.thingClassId(), thingId, name, finalParams, descriptor.parentId(), this, 30000);
|
|
pairThingInternal(info);
|
|
return info;
|
|
}
|
|
|
|
ThingPairingInfo *ThingManagerImplementation::pairThing(const ThingId &thingId, const ParamList ¶ms, const QString &name)
|
|
{
|
|
PairingTransactionId pairingTransactionId = PairingTransactionId::createPairingTransactionId();
|
|
|
|
Thing *thing = findConfiguredThing(thingId);
|
|
if (!thing) {
|
|
qCWarning(dcThingManager) << "Cannot find a thing with ID" << thingId.toString();
|
|
ThingPairingInfo *info = new ThingPairingInfo(pairingTransactionId, ThingClassId(), thingId, name, ParamList(), ThingId(), this);
|
|
info->finish(Thing::ThingErrorThingDescriptorNotFound);
|
|
return info;
|
|
}
|
|
|
|
// Use new params, if there are missing some, use the existing ones.
|
|
ParamList finalParams = buildParams(thing->thingClass().paramTypes(), params, thing->params());
|
|
|
|
ThingPairingInfo *info = new ThingPairingInfo(pairingTransactionId, thing->thingClassId(), thingId, name, finalParams, ThingId(), this, 30000);
|
|
pairThingInternal(info);
|
|
return info;
|
|
}
|
|
|
|
ThingPairingInfo *ThingManagerImplementation::confirmPairing(const PairingTransactionId &pairingTransactionId, const QString &username, const QString &secret)
|
|
{
|
|
if (!m_pendingPairings.contains(pairingTransactionId)) {
|
|
qCWarning(dcThingManager()) << "No pairing transaction with id" << pairingTransactionId.toString();
|
|
ThingPairingInfo *info = new ThingPairingInfo(pairingTransactionId, ThingClassId(), ThingId(), QString(), ParamList(), ThingId(), this);
|
|
info->finish(Thing::ThingErrorPairingTransactionIdNotFound);
|
|
return info;
|
|
}
|
|
|
|
PairingContext context = m_pendingPairings.take(pairingTransactionId);
|
|
ThingClassId thingClassId = context.thingClassId;
|
|
|
|
ThingClass thingClass = m_supportedThings.value(thingClassId);
|
|
IntegrationPlugin *plugin = m_integrationPlugins.value(thingClass.pluginId());
|
|
if (!plugin) {
|
|
qCWarning(dcThingManager) << "Can't find a plugin for this thing class:" << thingClass;
|
|
ThingPairingInfo *info = new ThingPairingInfo(pairingTransactionId, thingClassId, context.thingId, context.thingName, context.params, context.parentId, this);
|
|
info->finish(Thing::ThingErrorPluginNotFound);
|
|
return info;
|
|
}
|
|
|
|
ThingId thingId = context.thingId;
|
|
// If we already have a thing for this ID, we're reconfiguring an existing thing, else we're adding a new one.
|
|
bool addNewThing = !m_configuredThings.contains(context.thingId);
|
|
|
|
// We're using two different info objects here, one to hand over to the plugin for the pairing, the other we give out
|
|
// to the user. After the internal one has finished, we'll start a setupThing job and finish the external pairingInfo only after
|
|
// both, the internal pairing and the setup have completed.
|
|
ThingPairingInfo *internalInfo = new ThingPairingInfo(pairingTransactionId, thingClassId, thingId, context.thingName, context.params, context.parentId, this);
|
|
ThingPairingInfo *externalInfo = new ThingPairingInfo(pairingTransactionId, thingClassId, thingId, context.thingName, context.params, context.parentId, this);
|
|
plugin->confirmPairing(internalInfo, username, secret);
|
|
|
|
connect(internalInfo, &ThingPairingInfo::finished, this, [this, internalInfo, externalInfo, plugin, addNewThing](){
|
|
|
|
// Internal pairing failed, so fail the exernal one too.
|
|
if (internalInfo->status() != Thing::ThingErrorNoError) {
|
|
qCWarning(dcThingManager()) << "ConfirmPairing failed for" << internalInfo->thingName() << internalInfo->thingClassId();
|
|
externalInfo->finish(internalInfo->status(), internalInfo->displayMessage());
|
|
return;
|
|
}
|
|
|
|
// Internal pairing succeeded, set up the thing.
|
|
if (!addNewThing && !m_configuredThings.contains(internalInfo->thingId())) {
|
|
qCWarning(dcThingManager) << "The thing to be reconfigured has disappeared!";
|
|
externalInfo->finish(Thing::ThingErrorThingNotFound);
|
|
return;
|
|
}
|
|
|
|
ThingClass thingClass = m_supportedThings.value(internalInfo->thingClassId());
|
|
Thing *thing = nullptr;
|
|
|
|
if (addNewThing) {
|
|
thing = new Thing(plugin->pluginId(), thingClass, internalInfo->thingId(), this);
|
|
if (internalInfo->thingName().isEmpty()) {
|
|
thing->setName(thingClass.displayName());
|
|
} else {
|
|
thing->setName(internalInfo->thingName());
|
|
}
|
|
} else {
|
|
thing = m_configuredThings.value(internalInfo->thingId());
|
|
thing->setSetupStatus(Thing::ThingSetupStatusInProgress, Thing::ThingErrorNoError);
|
|
qCDebug(dcThingManager()) << "Reconfiguring thing" << thing;
|
|
}
|
|
|
|
thing->setParams(internalInfo->params());
|
|
ParamList settings = buildParams(thingClass.settingsTypes(), ParamList());
|
|
thing->setSettings(settings);
|
|
|
|
ThingSetupInfo *info = setupThing(thing);
|
|
connect(info, &ThingSetupInfo::finished, thing, [this, info, externalInfo, addNewThing](){
|
|
|
|
externalInfo->finish(info->status(), info->displayMessage());
|
|
|
|
if (info->status() != Thing::ThingErrorNoError) {
|
|
if (addNewThing) {
|
|
qCWarning(dcThingManager()) << "Failed to set up thing" << info->thing()->name()
|
|
<< "Not adding thing to the system. Error:"
|
|
<< info->status() << info->displayMessage();
|
|
info->thing()->deleteLater();
|
|
|
|
} else {
|
|
qCWarning(dcThingManager()) << "Failed to reconfigure thing" << info->thing()->name() <<
|
|
"Error:" << info->status() << info->displayMessage();
|
|
info->thing()->setSetupStatus(Thing::ThingSetupStatusFailed, info->status(), info->displayMessage());
|
|
// TODO: restore parameters?
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
qCDebug(dcThingManager()) << "Setup complete for thing" << info->thing();
|
|
info->thing()->setSetupStatus(Thing::ThingSetupStatusComplete, Thing::ThingErrorNoError);
|
|
|
|
if (addNewThing) {
|
|
qCDebug(dcThingManager()) << "Thing added:" << info->thing();
|
|
m_configuredThings.insert(info->thing()->id(), info->thing());
|
|
emit thingAdded(info->thing());
|
|
} else {
|
|
emit thingChanged(info->thing());
|
|
}
|
|
|
|
storeConfiguredThings();
|
|
postSetupThing(info->thing());
|
|
});
|
|
|
|
});
|
|
|
|
return externalInfo;
|
|
}
|
|
|
|
ThingSetupInfo* ThingManagerImplementation::addConfiguredThingInternal(const ThingClassId &thingClassId, const QString &name, const ParamList ¶ms, const ThingId &parentId)
|
|
{
|
|
ThingClass thingClass = findThingClass(thingClassId);
|
|
if (thingClass.id().isNull()) {
|
|
qCWarning(dcThingManager()) << "Cannot add thing. ThingClass" << thingClassId << "not found.";
|
|
ThingSetupInfo *info = new ThingSetupInfo(nullptr, this);
|
|
info->finish(Thing::ThingErrorThingClassNotFound);
|
|
return info;
|
|
}
|
|
|
|
if (thingClass.setupMethod() != ThingClass::SetupMethodJustAdd) {
|
|
qCWarning(dcThingManager()) << "Cannot add thing. This thing cannot be added this way. (SetupMethodJustAdd)";
|
|
ThingSetupInfo *info = new ThingSetupInfo(nullptr, this);
|
|
info->finish(Thing::ThingErrorCreationMethodNotSupported);
|
|
return info;
|
|
}
|
|
|
|
ThingId thingId = ThingId::createThingId();
|
|
// Chances are like 0, but...
|
|
while (m_configuredThings.contains(thingId)) {
|
|
thingId = ThingId::createThingId();
|
|
}
|
|
|
|
IntegrationPlugin *plugin = m_integrationPlugins.value(thingClass.pluginId());
|
|
if (!plugin) {
|
|
qCWarning(dcThingManager()) << "Cannot add thing. Plugin for thing class" << thingClass.name() << "not found.";
|
|
ThingSetupInfo *info = new ThingSetupInfo(nullptr, this);
|
|
info->finish(Thing::ThingErrorPluginNotFound);
|
|
return info;
|
|
}
|
|
|
|
// set params
|
|
ParamList effectiveParams = buildParams(thingClass.paramTypes(), params);
|
|
Thing::ThingError paramsResult = ThingUtils::verifyParams(thingClass.paramTypes(), effectiveParams);
|
|
if (paramsResult != Thing::ThingErrorNoError) {
|
|
qCWarning(dcThingManager()) << "Cannot add thing. Parameter verification failed.";
|
|
ThingSetupInfo *info = new ThingSetupInfo(nullptr, this);
|
|
info->finish(paramsResult);
|
|
return info;
|
|
}
|
|
|
|
Thing *thing = new Thing(plugin->pluginId(), thingClass, thingId, this);
|
|
thing->setParentId(parentId);
|
|
if (name.isEmpty()) {
|
|
thing->setName(thingClass.name());
|
|
} else {
|
|
thing->setName(name);
|
|
}
|
|
thing->setParams(effectiveParams);
|
|
|
|
// set settings (init with defaults)
|
|
ParamList settings = buildParams(thingClass.settingsTypes(), ParamList());
|
|
qCDebug(dcThingManager()) << "Adding thing settings" << settings << thingId;
|
|
thing->setSettings(settings);
|
|
|
|
ThingSetupInfo *info = setupThing(thing);
|
|
connect(info, &ThingSetupInfo::finished, this, [this, info](){
|
|
if (info->status() != Thing::ThingErrorNoError) {
|
|
qCWarning(dcThingManager) << "Thing setup failed. Not adding thing to system.";
|
|
info->thing()->deleteLater();
|
|
return;
|
|
}
|
|
|
|
info->thing()->setSetupStatus(Thing::ThingSetupStatusComplete, Thing::ThingErrorNoError);
|
|
|
|
qCDebug(dcThingManager) << "Thing setup complete.";
|
|
m_configuredThings.insert(info->thing()->id(), info->thing());
|
|
storeConfiguredThings();
|
|
postSetupThing(info->thing());
|
|
|
|
emit thingAdded(info->thing());
|
|
});
|
|
|
|
return info;
|
|
}
|
|
|
|
Thing::ThingError ThingManagerImplementation::removeConfiguredThing(const ThingId &thingId)
|
|
{
|
|
Thing *thing = m_configuredThings.take(thingId);
|
|
if (!thing) {
|
|
return Thing::ThingErrorThingNotFound;
|
|
}
|
|
IntegrationPlugin *plugin = m_integrationPlugins.value(thing->pluginId());
|
|
if (!plugin) {
|
|
qCWarning(dcThingManager()).nospace() << "Plugin not loaded for thing " << thing->name() << ". Not calling thingRemoved on plugin.";
|
|
} else {
|
|
plugin->thingRemoved(thing);
|
|
}
|
|
|
|
thing->deleteLater();
|
|
|
|
NymeaSettings settings(NymeaSettings::SettingsRoleThings);
|
|
settings.beginGroup("ThingConfig");
|
|
settings.beginGroup(thingId.toString());
|
|
settings.remove("");
|
|
settings.endGroup();
|
|
|
|
NymeaSettings stateCache(NymeaSettings::SettingsRoleThingStates);
|
|
stateCache.remove(thingId.toString());
|
|
|
|
foreach (const IOConnectionId &ioConnectionId, m_ioConnections.keys()) {
|
|
IOConnection ioConnection = m_ioConnections.value(ioConnectionId);
|
|
if (ioConnection.inputThingId() == thing->id() || ioConnection.outputThingId() == thing->id()) {
|
|
disconnectIO(ioConnectionId);
|
|
}
|
|
}
|
|
|
|
emit thingRemoved(thingId);
|
|
|
|
return Thing::ThingErrorNoError;
|
|
}
|
|
|
|
BrowseResult *ThingManagerImplementation::browseThing(const ThingId &thingId, const QString &itemId, const QLocale &locale)
|
|
{
|
|
Thing *thing = m_configuredThings.value(thingId);
|
|
|
|
BrowseResult *result = new BrowseResult(thing, this, itemId, locale, this, 30000);
|
|
|
|
if (!thing) {
|
|
qCWarning(dcThingManager()) << "Cannot browse thing. No such thing:" << thingId.toString();
|
|
result->finish(Thing::ThingErrorThingNotFound);
|
|
return result;
|
|
}
|
|
|
|
IntegrationPlugin *plugin = m_integrationPlugins.value(thing->pluginId());
|
|
if (!plugin) {
|
|
qCWarning(dcThingManager()) << "Cannot browse thing. Plugin not found for thing" << thing;
|
|
return result;
|
|
}
|
|
|
|
if (!thing->setupComplete()) {
|
|
qCWarning(dcThingManager()) << "Cannot browse thing. Thing did not finish setup" << thing;
|
|
return result;
|
|
}
|
|
|
|
if (!thing->thingClass().browsable()) {
|
|
qCWarning(dcThingManager()) << "Cannot browse thing. ThingClass" << thing->thingClass().name() << "is not browsable.";
|
|
result->finish(Thing::ThingErrorUnsupportedFeature);
|
|
return result;
|
|
}
|
|
|
|
plugin->browseThing(result);
|
|
connect(result, &BrowseResult::finished, this, [result](){
|
|
if (result->status() != Thing::ThingErrorNoError) {
|
|
qCWarning(dcThingManager()) << "Browse thing failed:" << result->status();
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
|
|
BrowserItemResult *ThingManagerImplementation::browserItemDetails(const ThingId &thingId, const QString &itemId, const QLocale &locale)
|
|
{
|
|
Thing *thing = m_configuredThings.value(thingId);
|
|
|
|
BrowserItemResult *result = new BrowserItemResult(thing, this, itemId, locale, this, 30000);
|
|
|
|
if (!thing) {
|
|
qCWarning(dcThingManager()) << "Cannot browse thing. No such thing:" << thingId.toString();
|
|
result->finish(Thing::ThingErrorThingNotFound);
|
|
return result;
|
|
}
|
|
|
|
IntegrationPlugin *plugin = m_integrationPlugins.value(thing->pluginId());
|
|
if (!plugin) {
|
|
qCWarning(dcThingManager()) << "Cannot browse thing. Plugin not found for thing" << thing;
|
|
return result;
|
|
}
|
|
|
|
if (thing->setupStatus() != Thing::ThingSetupStatusComplete) {
|
|
qCWarning(dcThingManager()) << "Cannot browse thing. Thing did not finish setup" << thing;
|
|
return result;
|
|
}
|
|
|
|
if (!thing->thingClass().browsable()) {
|
|
qCWarning(dcThingManager()) << "Cannot browse thing. ThingClass" << thing->thingClass().name() << "is not browsable.";
|
|
result->finish(Thing::ThingErrorUnsupportedFeature);
|
|
return result;
|
|
}
|
|
|
|
plugin->browserItem(result);
|
|
connect(result, &BrowserItemResult::finished, this, [result](){
|
|
if (result->status() != Thing::ThingErrorNoError) {
|
|
qCWarning(dcThingManager()) << "Browsing thing failed:" << result->status();
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
|
|
BrowserActionInfo* ThingManagerImplementation::executeBrowserItem(const BrowserAction &browserAction)
|
|
{
|
|
Thing *thing = m_configuredThings.value(browserAction.thingId());
|
|
|
|
BrowserActionInfo *info = new BrowserActionInfo(thing, this, browserAction, this, 30000);
|
|
|
|
if (!thing) {
|
|
info->finish(Thing::ThingErrorThingNotFound);
|
|
return info;
|
|
}
|
|
|
|
IntegrationPlugin *plugin = m_integrationPlugins.value(thing->pluginId());
|
|
if (!plugin) {
|
|
qCWarning(dcThingManager()) << "Cannot browse thing. Plugin not found for thing" << thing;
|
|
info->finish(Thing::ThingErrorPluginNotFound);
|
|
return info;
|
|
}
|
|
|
|
if (thing->setupStatus() != Thing::ThingSetupStatusComplete) {
|
|
qCWarning(dcThingManager()) << "Cannot browse thing. Thing did not finish setup" << thing;
|
|
info->finish(Thing::ThingErrorSetupFailed);
|
|
return info;
|
|
}
|
|
|
|
if (!thing->thingClass().browsable()) {
|
|
info->finish(Thing::ThingErrorUnsupportedFeature);
|
|
return info;
|
|
}
|
|
plugin->executeBrowserItem(info);
|
|
return info;
|
|
}
|
|
|
|
BrowserItemActionInfo* ThingManagerImplementation::executeBrowserItemAction(const BrowserItemAction &browserItemAction)
|
|
{
|
|
Thing *thing = m_configuredThings.value(browserItemAction.thingId());
|
|
|
|
BrowserItemActionInfo *info = new BrowserItemActionInfo(thing, this, browserItemAction, this, 30000);
|
|
|
|
if (!thing) {
|
|
info->finish(Thing::ThingErrorThingNotFound);
|
|
return info;
|
|
}
|
|
|
|
IntegrationPlugin *plugin = m_integrationPlugins.value(thing->pluginId());
|
|
if (!plugin) {
|
|
qCWarning(dcThingManager()) << "Cannot execute browser item action. Plugin not found for thing" << thing;
|
|
info->finish(Thing::ThingErrorPluginNotFound);
|
|
return info;
|
|
}
|
|
|
|
if (thing->setupStatus() != Thing::ThingSetupStatusComplete) {
|
|
qCWarning(dcThingManager()) << "Cannot execute browser item action. Thing did not finish setup" << thing;
|
|
info->finish(Thing::ThingErrorSetupFailed);
|
|
return info;
|
|
}
|
|
|
|
if (!thing->thingClass().browsable()) {
|
|
info->finish(Thing::ThingErrorUnsupportedFeature);
|
|
return info;
|
|
}
|
|
// TODO: check browserItemAction.params with ThingClass
|
|
|
|
plugin->executeBrowserItemAction(info);
|
|
return info;
|
|
}
|
|
|
|
IOConnections ThingManagerImplementation::ioConnections(const ThingId &thingId) const
|
|
{
|
|
if (thingId.isNull()) {
|
|
return m_ioConnections.values();
|
|
}
|
|
IOConnections ioConnections;
|
|
foreach (const IOConnection &ioConnection, m_ioConnections) {
|
|
if (ioConnection.inputThingId() == thingId || ioConnection.outputThingId() == thingId) {
|
|
ioConnections.append(ioConnection);
|
|
}
|
|
}
|
|
return ioConnections;
|
|
}
|
|
|
|
IOConnectionResult ThingManagerImplementation::connectIO(const IOConnection &connection)
|
|
{
|
|
IOConnectionResult result;
|
|
|
|
// Do some sanity checks
|
|
Thing *inputThing = m_configuredThings.value(connection.inputThingId());
|
|
if (!inputThing) {
|
|
qCWarning(dcThingManager()) << "Could not find inputThing" << connection.inputThingId() << "in configured things. Not adding IO connection.";
|
|
result.error = Thing::ThingErrorThingNotFound;
|
|
return result;
|
|
}
|
|
if (!inputThing->thingClass().stateTypes().contains(connection.inputStateTypeId())) {
|
|
qCWarning(dcThingManager()) << "Input thing" << inputThing->name() << "does not have a state with id" << connection.inputStateTypeId();
|
|
result.error = Thing::ThingErrorStateTypeNotFound;
|
|
return result;
|
|
}
|
|
StateType inputStateType = inputThing->thingClass().stateTypes().findById(connection.inputStateTypeId());
|
|
|
|
// Check if this is actually an input
|
|
if (inputStateType.ioType() != Types::IOTypeDigitalInput && inputStateType.ioType() != Types::IOTypeAnalogInput) {
|
|
qCWarning(dcThingManager()) << "The given input state is neither a digital nor an analog input.";
|
|
result.error = Thing::ThingErrorInvalidParameter;
|
|
return result;
|
|
}
|
|
|
|
Thing *outputThing = m_configuredThings.value(connection.outputThingId());
|
|
if (!outputThing) {
|
|
qCWarning(dcThingManager()) << "Could not find outputThing" << connection.outputThingId() << "in configured things. Not adding IO connection.";
|
|
result.error = Thing::ThingErrorThingNotFound;
|
|
return result;
|
|
}
|
|
if (!outputThing->thingClass().stateTypes().contains(connection.outputStateTypeId())) {
|
|
qCWarning(dcThingManager()) << "Output thing" << outputThing->name() << "does not have a state with id" << connection.outputStateTypeId();
|
|
result.error = Thing::ThingErrorStateTypeNotFound;
|
|
return result;
|
|
}
|
|
StateType outputStateType = outputThing->thingClass().stateTypes().findById(connection.outputStateTypeId());
|
|
|
|
// Check if this is actually an output
|
|
if (outputStateType.ioType() != Types::IOTypeDigitalOutput && outputStateType.ioType() != Types::IOTypeAnalogOutput) {
|
|
qCWarning(dcThingManager()) << "The given output state is neither a digital nor an analog output.";
|
|
result.error = Thing::ThingErrorInvalidParameter;
|
|
return result;
|
|
}
|
|
|
|
// Check if io types are compatible
|
|
if (inputStateType.ioType() == Types::IOTypeDigitalInput && outputStateType.ioType() != Types::IOTypeDigitalOutput) {
|
|
qCWarning(dcThingManager()) << "Cannot connect IOs of different type:" << inputStateType.ioType() << "is not compatible with" << outputStateType.ioType();
|
|
result.error = Thing::ThingErrorInvalidParameter;
|
|
return result;
|
|
}
|
|
if (inputStateType.ioType() == Types::IOTypeAnalogInput && outputStateType.ioType() != Types::IOTypeAnalogOutput) {
|
|
qCWarning(dcThingManager()) << "Cannot connect IOs of different type:" << inputStateType.ioType() << "is not compatible with" << outputStateType.ioType();
|
|
result.error = Thing::ThingErrorInvalidParameter;
|
|
return result;
|
|
}
|
|
|
|
// Check if either input or output is already connected
|
|
foreach (const IOConnectionId &id, m_ioConnections.keys()) {
|
|
if (m_ioConnections.value(id).inputThingId() == connection.inputThingId() && m_ioConnections.value(id).inputStateTypeId() == connection.inputStateTypeId()) {
|
|
qCDebug(dcThingManager()).nospace() << "Thing " << inputThing->name() << " already has an IO connection on " << inputStateType.displayName() << ". Replacing old connection.";
|
|
disconnectIO(id);
|
|
continue;
|
|
}
|
|
if (m_ioConnections.value(id).outputThingId() == connection.outputThingId() && m_ioConnections.value(id).outputStateTypeId() == connection.outputStateTypeId()) {
|
|
qCDebug(dcThingManager()).nospace() << "Thing " << inputThing->name() << " already has an IO connection on " << inputStateType.displayName() << ". Replacing old connection.";
|
|
disconnectIO(id);
|
|
}
|
|
}
|
|
|
|
// Finally add the connection
|
|
m_ioConnections.insert(connection.id(), connection);
|
|
|
|
storeIOConnections();
|
|
|
|
emit ioConnectionAdded(connection);
|
|
|
|
qCDebug(dcThingManager()) << "IO connected added:" << inputThing << "->" << outputThing;
|
|
|
|
// Sync initial state
|
|
syncIOConnection(inputThing, connection.inputStateTypeId());
|
|
|
|
result.error = Thing::ThingErrorNoError;
|
|
result.ioConnectionId = connection.id();
|
|
return result;
|
|
}
|
|
|
|
Thing::ThingError ThingManagerImplementation::disconnectIO(const IOConnectionId &ioConnectionId)
|
|
{
|
|
if (!m_ioConnections.contains(ioConnectionId)) {
|
|
qCWarning(dcThingManager()) << "IO connection" << ioConnectionId << "not found. Cannot disconnect.";
|
|
return Thing::ThingErrorItemNotFound;
|
|
}
|
|
m_ioConnections.remove(ioConnectionId);
|
|
|
|
NymeaSettings settings(NymeaSettings::SettingsRoleIOConnections);
|
|
settings.beginGroup("IOConnections");
|
|
settings.remove(ioConnectionId.toString());
|
|
settings.endGroup();
|
|
|
|
qCDebug(dcThingManager()) << "IO connection disconnected:" << ioConnectionId;
|
|
|
|
emit ioConnectionRemoved(ioConnectionId);
|
|
return Thing::ThingErrorNoError;
|
|
}
|
|
|
|
QString ThingManagerImplementation::translate(const PluginId &pluginId, const QString &string, const QLocale &locale)
|
|
{
|
|
return m_translator->translate(pluginId, string, locale);
|
|
}
|
|
|
|
ParamType ThingManagerImplementation::translateParamType(const PluginId &pluginId, const ParamType ¶mType, const QLocale &locale)
|
|
{
|
|
ParamType translatedParamType = paramType;
|
|
translatedParamType.setDisplayName(translate(pluginId, paramType.displayName(), locale));
|
|
return translatedParamType;
|
|
}
|
|
|
|
StateType ThingManagerImplementation::translateStateType(const PluginId &pluginId, const StateType &stateType, const QLocale &locale)
|
|
{
|
|
StateType translatedStateType = stateType;
|
|
translatedStateType.setDisplayName(translate(pluginId, stateType.displayName(), locale));
|
|
return translatedStateType;
|
|
}
|
|
|
|
EventType ThingManagerImplementation::translateEventType(const PluginId &pluginId, const EventType &eventType, const QLocale &locale)
|
|
{
|
|
EventType translatedEventType = eventType;
|
|
translatedEventType.setDisplayName(translate(pluginId, eventType.displayName(), locale));
|
|
return translatedEventType;
|
|
}
|
|
|
|
ActionType ThingManagerImplementation::translateActionType(const PluginId &pluginId, const ActionType &actionType, const QLocale &locale)
|
|
{
|
|
ActionType translatedActionType = actionType;
|
|
translatedActionType.setDisplayName(translate(pluginId, actionType.displayName(), locale));
|
|
return translatedActionType;
|
|
}
|
|
|
|
ThingClass ThingManagerImplementation::translateThingClass(const ThingClass &thingClass, const QLocale &locale)
|
|
{
|
|
ThingClass translatedThingClass = thingClass;
|
|
translatedThingClass.setDisplayName(translate(thingClass.pluginId(), thingClass.displayName(), locale));
|
|
|
|
ParamTypes translatedSettingsTypes;
|
|
foreach (const ParamType ¶mType, thingClass.settingsTypes()) {
|
|
translatedSettingsTypes.append(translateParamType(thingClass.pluginId(), paramType, locale));
|
|
}
|
|
translatedThingClass.setSettingsTypes(translatedSettingsTypes);
|
|
|
|
ParamTypes translatedParamTypes;
|
|
foreach (const ParamType ¶mType, thingClass.paramTypes()) {
|
|
translatedParamTypes.append(translateParamType(thingClass.pluginId(), paramType, locale));
|
|
}
|
|
translatedThingClass.setParamTypes(translatedParamTypes);
|
|
|
|
StateTypes translatedStateTypes;
|
|
foreach (const StateType &stateType, thingClass.stateTypes()) {
|
|
translatedStateTypes.append(translateStateType(thingClass.pluginId(), stateType, locale));
|
|
}
|
|
translatedThingClass.setStateTypes(translatedStateTypes);
|
|
|
|
EventTypes translatedEventTypes;
|
|
foreach (const EventType &eventType, thingClass.eventTypes()) {
|
|
translatedEventTypes.append(translateEventType(thingClass.pluginId(), eventType, locale));
|
|
}
|
|
translatedThingClass.setEventTypes(translatedEventTypes);
|
|
|
|
ActionTypes translatedActionTypes;
|
|
foreach (const ActionType &actionType, thingClass.actionTypes()) {
|
|
translatedActionTypes.append(translateActionType(thingClass.pluginId(), actionType, locale));
|
|
}
|
|
translatedThingClass.setActionTypes(translatedActionTypes);
|
|
|
|
return translatedThingClass;
|
|
}
|
|
|
|
Vendor ThingManagerImplementation::translateVendor(const Vendor &vendor, const QLocale &locale)
|
|
{
|
|
IntegrationPlugin *plugin = nullptr;
|
|
foreach (IntegrationPlugin *p, m_integrationPlugins) {
|
|
if (p->supportedVendors().contains(vendor)) {
|
|
plugin = p;
|
|
}
|
|
}
|
|
if (!plugin) {
|
|
return vendor;
|
|
}
|
|
|
|
Vendor translatedVendor = vendor;
|
|
translatedVendor.setDisplayName(translate(plugin->pluginId(), vendor.displayName(), locale));
|
|
return translatedVendor;
|
|
}
|
|
|
|
Thing *ThingManagerImplementation::findConfiguredThing(const ThingId &id) const
|
|
{
|
|
foreach (Thing *thing, m_configuredThings) {
|
|
if (thing->id() == id) {
|
|
return thing;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Things ThingManagerImplementation::configuredThings() const
|
|
{
|
|
return m_configuredThings.values();
|
|
}
|
|
|
|
Things ThingManagerImplementation::findConfiguredThings(const ThingClassId &thingClassId) const
|
|
{
|
|
QList<Thing*> ret;
|
|
foreach (Thing *thing, m_configuredThings) {
|
|
if (thing->thingClassId() == thingClassId) {
|
|
ret << thing;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
Things ThingManagerImplementation::findConfiguredThings(const QString &interface) const
|
|
{
|
|
QList<Thing*> ret;
|
|
foreach (Thing *thing, m_configuredThings) {
|
|
ThingClass thingClass = m_supportedThings.value(thing->thingClassId());
|
|
if (thingClass.interfaces().contains(interface)) {
|
|
ret.append(thing);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
Things ThingManagerImplementation::findChilds(const ThingId &id) const
|
|
{
|
|
QList<Thing *> ret;
|
|
foreach (Thing *d, m_configuredThings) {
|
|
if (d->parentId() == id) {
|
|
ret.append(d);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
ThingClass ThingManagerImplementation::findThingClass(const ThingClassId &thingClassId) const
|
|
{
|
|
foreach (const ThingClass &thingClass, m_supportedThings) {
|
|
if (thingClass.id() == thingClassId) {
|
|
return thingClass;
|
|
}
|
|
}
|
|
return ThingClass();
|
|
}
|
|
|
|
ThingActionInfo *ThingManagerImplementation::executeAction(const Action &action)
|
|
{
|
|
Action finalAction = action;
|
|
Thing *thing = m_configuredThings.value(action.thingId());
|
|
if (!thing) {
|
|
qCWarning(dcThingManager()) << "Cannot execute action. No such thing:" << action.thingId();
|
|
ThingActionInfo *info = new ThingActionInfo(nullptr, action, this);
|
|
info->finish(Thing::ThingErrorThingNotFound);
|
|
return info;
|
|
}
|
|
|
|
if (!thing->setupComplete()) {
|
|
qCWarning(dcThingManager()) << "Cannot execute action. Thing" << thing->name() << "hasn't completed setup.";
|
|
ThingActionInfo *info = new ThingActionInfo(nullptr, action, this);
|
|
info->finish(Thing::ThingErrorSetupFailed);
|
|
return info;
|
|
}
|
|
|
|
// Make sure this thing has an action type with this id
|
|
ThingClass thingClass = findThingClass(thing->thingClassId());
|
|
ActionType actionType = thingClass.actionTypes().findById(action.actionTypeId());
|
|
if (actionType.id().isNull()) {
|
|
qCWarning(dcThingManager()) << "Cannot execute action. No such action type" << action.actionTypeId();
|
|
ThingActionInfo *info = new ThingActionInfo(thing, action, this);
|
|
info->finish(Thing::ThingErrorActionTypeNotFound);
|
|
return info;
|
|
}
|
|
|
|
ParamList finalParams = buildParams(actionType.paramTypes(), action.params());
|
|
Thing::ThingError paramCheck = ThingUtils::verifyParams(actionType.paramTypes(), finalParams);
|
|
if (paramCheck != Thing::ThingErrorNoError) {
|
|
qCWarning(dcThingManager()) << "Cannot execute action. Parameter verification failed.";
|
|
ThingActionInfo *info = new ThingActionInfo(thing, action, this);
|
|
info->finish(paramCheck);
|
|
return info;
|
|
}
|
|
finalAction.setParams(finalParams);
|
|
|
|
ThingActionInfo *info = new ThingActionInfo(thing, finalAction, this, 30000);
|
|
|
|
IntegrationPlugin *plugin = m_integrationPlugins.value(thing->pluginId());
|
|
if (!plugin) {
|
|
qCWarning(dcThingManager()) << "Cannot execute action. Plugin not found for device" << thing->name();
|
|
info->finish(Thing::ThingErrorPluginNotFound);
|
|
return info;
|
|
}
|
|
|
|
plugin->executeAction(info);
|
|
|
|
return info;
|
|
}
|
|
|
|
void ThingManagerImplementation::loadPlugins()
|
|
{
|
|
foreach (const QString &path, pluginSearchDirs()) {
|
|
QDir dir(path);
|
|
qCDebug(dcThingManager) << "Loading plugins from:" << dir.absolutePath();
|
|
foreach (const QString &entry, dir.entryList()) {
|
|
QFileInfo fi;
|
|
if (entry.startsWith("libnymea_integrationplugin") && entry.endsWith(".so")) {
|
|
fi.setFile(path + "/" + entry);
|
|
} else {
|
|
fi.setFile(path + "/" + entry + "/libnymea_integrationplugin" + entry + ".so");
|
|
}
|
|
|
|
if (!fi.exists())
|
|
continue;
|
|
|
|
// Check plugin API version compatibility
|
|
QLibrary lib(fi.absoluteFilePath());
|
|
if (!lib.load()) {
|
|
qCWarning(dcThingManager()).nospace() << "Error loading plugin " << fi.absoluteFilePath() << ": " << lib.errorString();
|
|
continue;
|
|
}
|
|
|
|
QFunctionPointer versionFunc = lib.resolve("libnymea_api_version");
|
|
if (!versionFunc) {
|
|
qCWarning(dcThingManager()).nospace() << "Unable to resolve version in plugin " << fi.absoluteFilePath() << ". Not loading plugin.";
|
|
lib.unload();
|
|
continue;
|
|
}
|
|
|
|
QString version = reinterpret_cast<QString(*)()>(versionFunc)();
|
|
lib.unload();
|
|
QStringList parts = version.split('.');
|
|
QStringList coreParts = QString(LIBNYMEA_API_VERSION).split('.');
|
|
if (parts.length() != 3 || parts.at(0).toInt() != coreParts.at(0).toInt() || parts.at(1).toInt() > coreParts.at(1).toInt()) {
|
|
qCWarning(dcThingManager()).nospace() << "Libnymea API mismatch for " << fi.absoluteFilePath() << ". Core API: " << LIBNYMEA_API_VERSION << ", Plugin API: " << version;
|
|
continue;
|
|
}
|
|
|
|
// Version is ok. Now load the plugin
|
|
QPluginLoader loader;
|
|
loader.setFileName(fi.absoluteFilePath());
|
|
loader.setLoadHints(QLibrary::ResolveAllSymbolsHint);
|
|
|
|
qCDebug(dcThingManager()) << "Loading plugin from:" << fi.absoluteFilePath();
|
|
if (!loader.load()) {
|
|
qCWarning(dcThingManager) << "Could not load plugin data of" << entry << "\n" << loader.errorString();
|
|
continue;
|
|
}
|
|
|
|
QJsonObject pluginInfo = loader.metaData().value("MetaData").toObject();
|
|
PluginMetadata metaData(pluginInfo, false, false);
|
|
if (!metaData.isValid()) {
|
|
foreach (const QString &error, metaData.validationErrors()) {
|
|
qCWarning(dcThingManager()) << error;
|
|
}
|
|
loader.unload();
|
|
continue;
|
|
}
|
|
|
|
IntegrationPlugin *pluginIface = qobject_cast<IntegrationPlugin *>(loader.instance());
|
|
if (!pluginIface) {
|
|
qCWarning(dcThingManager) << "Could not get plugin instance of" << entry;
|
|
loader.unload();
|
|
continue;
|
|
}
|
|
if (m_integrationPlugins.contains(pluginIface->pluginId())) {
|
|
qCWarning(dcThingManager()) << "A plugin with this ID is already loaded. Not loading" << entry;
|
|
continue;
|
|
}
|
|
loadPlugin(pluginIface, metaData);
|
|
PluginInfoCache::cachePluginInfo(pluginInfo);
|
|
}
|
|
}
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5,12,0)
|
|
foreach (const QString &path, pluginSearchDirs()) {
|
|
QDir dir(path);
|
|
qCDebug(dcThingManager) << "Loading JS plugins from:" << dir.absolutePath();
|
|
foreach (const QString &entry, dir.entryList()) {
|
|
QFileInfo jsFi;
|
|
QFileInfo jsonFi;
|
|
|
|
if (entry.endsWith(".js")) {
|
|
jsFi.setFile(path + "/" + entry);
|
|
} else {
|
|
jsFi.setFile(path + "/" + entry + "/" + entry + ".js");
|
|
}
|
|
|
|
if (!jsFi.exists()) {
|
|
continue;
|
|
}
|
|
|
|
ScriptIntegrationPlugin *plugin = new ScriptIntegrationPlugin(this);
|
|
bool ret = plugin->loadScript(jsFi.absoluteFilePath());
|
|
if (!ret) {
|
|
delete plugin;
|
|
qCWarning(dcThingManager()) << "JS plugin failed to load";
|
|
continue;
|
|
}
|
|
PluginMetadata metaData(plugin->metaData());
|
|
if (!metaData.isValid()) {
|
|
qCWarning(dcThingManager()) << "Not loading JS plugin. Invalid metadata.";
|
|
foreach (const QString &error, metaData.validationErrors()) {
|
|
qCWarning(dcThingManager()) << error;
|
|
}
|
|
}
|
|
loadPlugin(plugin, metaData);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void ThingManagerImplementation::loadPlugin(IntegrationPlugin *pluginIface, const PluginMetadata &metaData)
|
|
{
|
|
pluginIface->setParent(this);
|
|
pluginIface->initPlugin(metaData, this, m_hardwareManager);
|
|
|
|
qCDebug(dcThingManager) << "**** Loaded plugin" << pluginIface->pluginName();
|
|
foreach (const Vendor &vendor, pluginIface->supportedVendors()) {
|
|
qCDebug(dcThingManager) << "* Loaded vendor:" << vendor.name() << vendor.id();
|
|
if (m_supportedVendors.contains(vendor.id()))
|
|
continue;
|
|
|
|
m_supportedVendors.insert(vendor.id(), vendor);
|
|
}
|
|
|
|
foreach (const ThingClass &thingClass, pluginIface->supportedThings()) {
|
|
if (!m_supportedVendors.contains(thingClass.vendorId())) {
|
|
qCWarning(dcThingManager) << "Vendor not found. Ignoring thing. VendorId:" << thingClass.vendorId() << "ThingClass:" << thingClass.name() << thingClass.id();
|
|
continue;
|
|
}
|
|
m_vendorThingMap[thingClass.vendorId()].append(thingClass.id());
|
|
m_supportedThings.insert(thingClass.id(), thingClass);
|
|
qCDebug(dcThingManager) << "* Loaded thing class:" << thingClass.name();
|
|
}
|
|
|
|
NymeaSettings settings(NymeaSettings::SettingsRolePlugins);
|
|
settings.beginGroup("PluginConfig");
|
|
ParamList params;
|
|
if (settings.childGroups().contains(pluginIface->pluginId().toString())) {
|
|
settings.beginGroup(pluginIface->pluginId().toString());
|
|
|
|
if (!settings.childGroups().isEmpty()) {
|
|
// Note: since nymea 0.12.2 the param type gets saved too for better data converting
|
|
foreach (const QString ¶mTypeIdString, settings.childGroups()) {
|
|
ParamTypeId paramTypeId(paramTypeIdString);
|
|
ParamType paramType = pluginIface->configurationDescription().findById(paramTypeId);
|
|
if (!paramType.isValid()) {
|
|
qCWarning(dcThingManager()) << "Not loading Param for plugin" << pluginIface->pluginName() << "because the ParamType for the saved Param" << ParamTypeId(paramTypeIdString).toString() << "could not be found.";
|
|
continue;
|
|
}
|
|
|
|
QVariant paramValue;
|
|
settings.beginGroup(paramTypeIdString);
|
|
paramValue = settings.value("value", paramType.defaultValue());
|
|
paramValue.convert(settings.value("type").toInt());
|
|
params.append(Param(paramTypeId, paramValue));
|
|
settings.endGroup();
|
|
}
|
|
} else {
|
|
// Note: < nymea 0.12.2
|
|
foreach (const QString ¶mTypeIdString, settings.allKeys()) {
|
|
params.append(Param(ParamTypeId(paramTypeIdString), settings.value(paramTypeIdString)));
|
|
}
|
|
}
|
|
|
|
settings.endGroup();
|
|
} else if (!pluginIface->configurationDescription().isEmpty()){
|
|
// plugin requires config but none stored. Init with defaults
|
|
foreach (const ParamType ¶mType, pluginIface->configurationDescription()) {
|
|
Param param(paramType.id(), paramType.defaultValue());
|
|
params.append(param);
|
|
}
|
|
}
|
|
settings.endGroup();
|
|
|
|
if (params.count() > 0) {
|
|
Thing::ThingError status = pluginIface->setConfiguration(params);
|
|
if (status != Thing::ThingErrorNoError) {
|
|
qCWarning(dcThingManager) << "Error setting params to plugin. Broken configuration?";
|
|
}
|
|
}
|
|
|
|
// Call the init method of the plugin
|
|
pluginIface->init();
|
|
|
|
m_integrationPlugins.insert(pluginIface->pluginId(), pluginIface);
|
|
|
|
connect(pluginIface, &IntegrationPlugin::emitEvent, this, &ThingManagerImplementation::onEventTriggered, Qt::QueuedConnection);
|
|
connect(pluginIface, &IntegrationPlugin::autoThingsAppeared, this, &ThingManagerImplementation::onAutoThingsAppeared, Qt::QueuedConnection);
|
|
connect(pluginIface, &IntegrationPlugin::autoThingDisappeared, this, &ThingManagerImplementation::onAutoThingDisappeared, Qt::QueuedConnection);
|
|
}
|
|
|
|
void ThingManagerImplementation::loadConfiguredThings()
|
|
{
|
|
bool needsMigration = false;
|
|
NymeaSettings settings(NymeaSettings::SettingsRoleThings);
|
|
if (settings.childGroups().contains("ThingConfig")) {
|
|
settings.beginGroup("ThingConfig");
|
|
} else {
|
|
settings.beginGroup("DeviceConfig");
|
|
needsMigration = true;
|
|
}
|
|
qCDebug(dcThingManager) << "Loading things from" << settings.fileName();
|
|
foreach (const QString &idString, settings.childGroups()) {
|
|
settings.beginGroup(idString);
|
|
QString thingName = settings.value("thingName").toString();
|
|
if (!settings.contains("thingName")) { // nymea < 0.20
|
|
thingName = settings.value("devicename").toString();
|
|
}
|
|
PluginId pluginId = PluginId(settings.value("pluginid").toString());
|
|
IntegrationPlugin *plugin = m_integrationPlugins.value(pluginId);
|
|
if (!plugin) {
|
|
qCWarning(dcThingManager()) << "Plugin for thing" << thingName << idString << "not found. This thing will not be functional until the plugin can be loaded.";
|
|
}
|
|
ThingClassId thingClassId = ThingClassId(settings.value("thingClassId").toString());
|
|
// If ThingClassId isn't found in the config, retry with ThingClassId (nymea < 0.20)
|
|
if (thingClassId.isNull()) {
|
|
thingClassId = ThingClassId(settings.value("deviceClassId").toString());
|
|
}
|
|
ThingClass thingClass = findThingClass(thingClassId);
|
|
if (!thingClass.isValid()) {
|
|
// Try to load the device class from the cache
|
|
QJsonObject pluginInfo = PluginInfoCache::loadPluginInfo(pluginId);
|
|
if (!pluginInfo.empty()) {
|
|
PluginMetadata pluginMetadata(pluginInfo, false, false);
|
|
thingClass = pluginMetadata.thingClasses().findById(thingClassId);
|
|
if (thingClass.isValid()) {
|
|
m_supportedThings.insert(thingClassId, thingClass);
|
|
if (!m_supportedVendors.contains(thingClass.vendorId())) {
|
|
Vendor vendor = pluginMetadata.vendors().findById(thingClass.vendorId());
|
|
m_supportedVendors.insert(vendor.id(), vendor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!thingClass.isValid()) {
|
|
qCWarning(dcThingManager()) << "Not loading thing" << thingName << idString << "because the thing class for this thing could not be found.";
|
|
settings.endGroup(); // ThingId
|
|
continue;
|
|
}
|
|
|
|
// Cross-check if this plugin still implements this thing class
|
|
if (plugin && !plugin->supportedThings().contains(thingClass)) {
|
|
qCWarning(dcThingManager()) << "Not loading thing" << thingName << idString << "because plugin" << plugin->pluginName() << "has removed support for it.";
|
|
settings.endGroup(); // ThingId
|
|
continue;
|
|
}
|
|
Thing *thing = new Thing(pluginId, thingClass, ThingId(idString), this);
|
|
thing->m_autoCreated = settings.value("autoCreated").toBool();
|
|
thing->setName(thingName);
|
|
thing->setParentId(ThingId(settings.value("parentid", QUuid()).toString()));
|
|
|
|
ParamList params;
|
|
settings.beginGroup("Params");
|
|
if (!settings.childGroups().isEmpty()) {
|
|
foreach (const QString ¶mTypeIdString, settings.childGroups()) {
|
|
ParamTypeId paramTypeId(paramTypeIdString);
|
|
ParamType paramType = thingClass.paramTypes().findById(paramTypeId);
|
|
QVariant defaultValue;
|
|
if (!paramType.isValid()) {
|
|
// NOTE: We're not skipping unknown parameters to give plugins a chance to still access old values if they change their config and migrate things over.
|
|
qCWarning(dcThingManager()) << "Unknown param" << paramTypeIdString << "for" << thing << ". ParamType could not be found in device class.";
|
|
}
|
|
|
|
// Note: since nymea 0.12.2
|
|
QVariant paramValue;
|
|
settings.beginGroup(paramTypeIdString);
|
|
paramValue = settings.value("value", paramType.defaultValue());
|
|
paramValue.convert(settings.value("type").toInt());
|
|
params.append(Param(paramTypeId, paramValue));
|
|
settings.endGroup(); // ParamId
|
|
}
|
|
} else {
|
|
foreach (const QString ¶mTypeIdString, settings.allKeys()) {
|
|
params.append(Param(ParamTypeId(paramTypeIdString), settings.value(paramTypeIdString)));
|
|
}
|
|
}
|
|
// Make sure all params are around. if they aren't initialize with default values
|
|
foreach (const ParamType ¶mType, thingClass.paramTypes()) {
|
|
if (!params.hasParam(paramType.id())) {
|
|
params.append(Param(paramType.id(), paramType.defaultValue()));
|
|
}
|
|
}
|
|
thing->setParams(params);
|
|
settings.endGroup(); // Params
|
|
|
|
ParamList thingSettings;
|
|
settings.beginGroup("Settings");
|
|
if (!settings.childGroups().isEmpty()) {
|
|
foreach (const QString ¶mTypeIdString, settings.childGroups()) {
|
|
ParamTypeId paramTypeId(paramTypeIdString);
|
|
ParamType paramType = thingClass.settingsTypes().findById(paramTypeId);
|
|
if (!paramType.isValid()) {
|
|
qCWarning(dcThingManager()) << "Not loading Setting for thing" << thing << "because the ParamType for the saved Setting" << ParamTypeId(paramTypeIdString).toString() << "could not be found.";
|
|
continue;
|
|
}
|
|
|
|
// Note: since nymea 0.12.2
|
|
QVariant paramValue;
|
|
settings.beginGroup(paramTypeIdString);
|
|
paramValue = settings.value("value", paramType.defaultValue());
|
|
paramValue.convert(settings.value("type").toInt());
|
|
thingSettings.append(Param(paramTypeId, paramValue));
|
|
settings.endGroup(); // ParamId
|
|
}
|
|
} else {
|
|
foreach (const QString ¶mTypeIdString, settings.allKeys()) {
|
|
params.append(Param(ParamTypeId(paramTypeIdString), settings.value(paramTypeIdString)));
|
|
}
|
|
}
|
|
|
|
// Fill in any missing params with defaults
|
|
thingSettings = buildParams(thingClass.settingsTypes(), thingSettings);
|
|
|
|
thing->setSettings(thingSettings);
|
|
|
|
settings.endGroup(); // Settings
|
|
settings.endGroup(); // ThingId
|
|
|
|
// We always add the thing to the list in this case. If it's in the stored things
|
|
// it means that it was working at some point so lets still add it as there might
|
|
// be rules associated with this thing.
|
|
m_configuredThings.insert(thing->id(), thing);
|
|
|
|
emit thingAdded(thing);
|
|
}
|
|
settings.endGroup();
|
|
|
|
if (needsMigration) {
|
|
storeConfiguredThings();
|
|
settings.remove("DeviceConfig");
|
|
}
|
|
|
|
|
|
QHash<ThingId, Thing*> setupList = m_configuredThings;
|
|
while (!setupList.isEmpty()) {
|
|
Thing *thing = nullptr;
|
|
foreach (Thing *d, setupList) {
|
|
if (d->parentId().isNull() || !setupList.contains(d->parentId())) {
|
|
thing = d;
|
|
setupList.take(d->id());
|
|
break;
|
|
}
|
|
}
|
|
Q_ASSERT(thing != nullptr);
|
|
|
|
thing->setSetupStatus(Thing::ThingSetupStatusInProgress, Thing::ThingErrorNoError);
|
|
ThingSetupInfo *info = setupThing(thing);
|
|
// Set receiving object to "thing" because at startup we load it in any case, knowing that it worked at
|
|
// some point. However, it'll be marked as non-working until the setup succeeds so the user might delete
|
|
// it in the meantime... In that case we don't want to call postsetup on it.
|
|
connect(info, &ThingSetupInfo::finished, thing, [this, info](){
|
|
|
|
if (info->status() != Thing::ThingErrorNoError) {
|
|
qCWarning(dcThingManager()) << "Error setting up thing" << info->thing()->name() << info->thing()->id().toString() << info->status() << info->displayMessage();
|
|
info->thing()->setSetupStatus(Thing::ThingSetupStatusFailed, info->status(), info->displayMessage());
|
|
emit thingChanged(info->thing());
|
|
return;
|
|
}
|
|
|
|
qCDebug(dcThingManager()) << "Setup complete for thing" << info->thing();
|
|
info->thing()->setSetupStatus(Thing::ThingSetupStatusComplete, Thing::ThingErrorNoError);
|
|
emit thingChanged(info->thing());
|
|
postSetupThing(info->thing());
|
|
});
|
|
}
|
|
|
|
loadIOConnections();
|
|
}
|
|
|
|
void ThingManagerImplementation::storeConfiguredThings()
|
|
{
|
|
NymeaSettings settings(NymeaSettings::SettingsRoleThings);
|
|
settings.beginGroup("ThingConfig");
|
|
foreach (Thing *thing, m_configuredThings) {
|
|
settings.beginGroup(thing->id().toString());
|
|
// Note: clean thing settings before storing it for clean up
|
|
settings.remove("");
|
|
settings.setValue("autoCreated", thing->autoCreated());
|
|
settings.setValue("thingName", thing->name());
|
|
settings.setValue("thingClassId", thing->thingClassId().toString());
|
|
settings.setValue("pluginid", thing->pluginId().toString());
|
|
if (!thing->parentId().isNull())
|
|
settings.setValue("parentid", thing->parentId().toString());
|
|
|
|
settings.beginGroup("Params");
|
|
foreach (const Param ¶m, thing->params()) {
|
|
settings.beginGroup(param.paramTypeId().toString());
|
|
settings.setValue("type", static_cast<int>(param.value().type()));
|
|
settings.setValue("value", param.value());
|
|
settings.endGroup(); // ParamTypeId
|
|
}
|
|
settings.endGroup(); // Params
|
|
|
|
settings.beginGroup("Settings");
|
|
foreach (const Param ¶m, thing->settings()) {
|
|
settings.beginGroup(param.paramTypeId().toString());
|
|
settings.setValue("type", static_cast<int>(param.value().type()));
|
|
settings.setValue("value", param.value());
|
|
settings.endGroup(); // ParamTypeId
|
|
}
|
|
settings.endGroup(); // Settings
|
|
|
|
|
|
settings.endGroup(); // ThingId
|
|
}
|
|
settings.endGroup(); // ThingConfig
|
|
}
|
|
|
|
void ThingManagerImplementation::startMonitoringAutoThings()
|
|
{
|
|
foreach (IntegrationPlugin *plugin, m_integrationPlugins) {
|
|
plugin->startMonitoringAutoThings();
|
|
}
|
|
}
|
|
|
|
void ThingManagerImplementation::onAutoThingsAppeared(const ThingDescriptors &thingDescriptors)
|
|
{
|
|
foreach (const ThingDescriptor &thingDescriptor, thingDescriptors) {
|
|
|
|
ThingClass thingClass = findThingClass(thingDescriptor.thingClassId());
|
|
if (!thingClass.isValid()) {
|
|
qCWarning(dcThingManager()) << "Ignoring appearing auto thing for an unknown ThingClass" << thingDescriptor.thingClassId();
|
|
return;
|
|
}
|
|
|
|
IntegrationPlugin *plugin = m_integrationPlugins.value(thingClass.pluginId());
|
|
if (!plugin) {
|
|
return;
|
|
}
|
|
|
|
if (!thingDescriptor.parentId().isNull() && !m_configuredThings.contains(thingDescriptor.parentId())) {
|
|
qCWarning(dcThingManager()) << "Invalid parent thing id. Not adding thing to the system.";
|
|
continue;
|
|
}
|
|
|
|
Thing *thing = nullptr;
|
|
|
|
// If the appeared auto thing holds a valid thing id, do a reconfiguration for this thing
|
|
if (!thingDescriptor.thingId().isNull()) {
|
|
thing = findConfiguredThing(thingDescriptor.thingId());
|
|
if (!thing) {
|
|
qCWarning(dcThingManager()) << "Could not find thing for auto thing descriptor" << thingDescriptor.thingId();
|
|
continue;
|
|
}
|
|
qCDebug(dcThingManager()) << "Start reconfiguring auto thing" << thing;
|
|
ParamList finalParams = buildParams(thingClass.paramTypes(), thingDescriptor.params());
|
|
reconfigureThingInternal(thing, finalParams);
|
|
continue;
|
|
}
|
|
|
|
thing = new Thing(plugin->pluginId(), thingClass, this);
|
|
thing->m_autoCreated = true;
|
|
thing->setName(thingDescriptor.title());
|
|
thing->setParams(thingDescriptor.params());
|
|
ParamList settings = buildParams(thingClass.settingsTypes(), ParamList());
|
|
thing->setSettings(settings);
|
|
thing->setParentId(thingDescriptor.parentId());
|
|
|
|
qCDebug(dcThingManager()) << "Setting up auto thing:" << thing->name() << thing->id().toString();
|
|
|
|
ThingSetupInfo *info = setupThing(thing);
|
|
connect(info, &ThingSetupInfo::finished, thing, [this, info](){
|
|
|
|
if (info->status() != Thing::ThingErrorNoError) {
|
|
qCWarning(dcThingManager) << "Thing setup failed. Not adding auto thing to system.";
|
|
info->thing()->deleteLater();
|
|
return;
|
|
}
|
|
|
|
info->thing()->setSetupStatus(Thing::ThingSetupStatusComplete, Thing::ThingErrorNoError);
|
|
m_configuredThings.insert(info->thing()->id(), info->thing());
|
|
storeConfiguredThings();
|
|
|
|
emit thingAdded(info->thing());
|
|
|
|
postSetupThing(info->thing());
|
|
});
|
|
}
|
|
}
|
|
|
|
void ThingManagerImplementation::onAutoThingDisappeared(const ThingId &thingId)
|
|
{
|
|
IntegrationPlugin *plugin = static_cast<IntegrationPlugin*>(sender());
|
|
Thing *thing = m_configuredThings.value(thingId);
|
|
|
|
if (!thing) {
|
|
qWarning(dcThingManager) << "Received an autoThingDisappeared signal but this thing is unknown:" << thingId;
|
|
return;
|
|
}
|
|
|
|
ThingClass thingClass = m_supportedThings.value(thing->thingClassId());
|
|
|
|
if (thingClass.pluginId() != plugin->pluginId()) {
|
|
qWarning(dcThingManager) << "Received a autoThingDisappeared signal but emitting plugin does not own the thing";
|
|
return;
|
|
}
|
|
|
|
if (!thing->autoCreated()) {
|
|
qWarning(dcThingManager) << "Received an autoThingDisappeared signal but thing creationMethod is not CreateMothodAuto";
|
|
return;
|
|
}
|
|
|
|
emit thingDisappeared(thingId);
|
|
}
|
|
|
|
void ThingManagerImplementation::onLoaded()
|
|
{
|
|
qCDebug(dcThingManager()) << "Done loading plugins and things.";
|
|
emit loaded();
|
|
|
|
// schedule some housekeeping...
|
|
QTimer::singleShot(0, this, SLOT(cleanupThingStateCache()));
|
|
}
|
|
|
|
void ThingManagerImplementation::cleanupThingStateCache()
|
|
{
|
|
NymeaSettings settings(NymeaSettings::SettingsRoleThingStates);
|
|
foreach (const QString &entry, settings.childGroups()) {
|
|
ThingId thingId(entry);
|
|
if (!m_configuredThings.contains(thingId)) {
|
|
qCDebug(dcThingManager()) << "Thing ID" << thingId << "not found in configured things. Cleaning up stale thing state cache.";
|
|
settings.remove(entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ThingManagerImplementation::onEventTriggered(const Event &event)
|
|
{
|
|
// Doing some sanity checks here...
|
|
Thing *thing = m_configuredThings.value(event.thingId());
|
|
if (!thing) {
|
|
qCWarning(dcThingManager()) << "Invalid thing id in emitted event. Not forwarding event. Thing setup not complete yet?";
|
|
return;
|
|
}
|
|
EventType eventType = thing->thingClass().eventTypes().findById(event.eventTypeId());
|
|
if (!eventType.isValid()) {
|
|
qCWarning(dcThingManager()) << "The given thing does not have an event type of id " + event.eventTypeId().toString() + ". Not forwarding event.";
|
|
return;
|
|
}
|
|
// All good, forward the event
|
|
emit eventTriggered(event);
|
|
}
|
|
|
|
void ThingManagerImplementation::slotThingStateValueChanged(const StateTypeId &stateTypeId, const QVariant &value)
|
|
{
|
|
Thing *thing = qobject_cast<Thing*>(sender());
|
|
if (!thing || !m_configuredThings.contains(thing->id())) {
|
|
qCWarning(dcThingManager()) << "Invalid thing id in state change. Not forwarding event. Thing setup not complete yet?";
|
|
return;
|
|
}
|
|
storeThingStates(thing);
|
|
|
|
emit thingStateChanged(thing, stateTypeId, value);
|
|
|
|
Param valueParam(ParamTypeId(stateTypeId.toString()), value);
|
|
Event event(EventTypeId(stateTypeId.toString()), thing->id(), ParamList() << valueParam, true);
|
|
emit eventTriggered(event);
|
|
|
|
syncIOConnection(thing, stateTypeId);
|
|
}
|
|
|
|
void ThingManagerImplementation::syncIOConnection(Thing *thing, const StateTypeId &stateTypeId)
|
|
{
|
|
|
|
foreach (const IOConnection &ioConnection, m_ioConnections) {
|
|
// Check if this state is an input to an IO connection.
|
|
if (ioConnection.inputThingId() == thing->id() && ioConnection.inputStateTypeId() == stateTypeId) {
|
|
Thing *inputThing = thing;
|
|
QVariant inputValue = inputThing->stateValue(stateTypeId);
|
|
|
|
Thing *outputThing = m_configuredThings.value(ioConnection.outputThingId());
|
|
if (!outputThing) {
|
|
qCWarning(dcThingManager()) << "IO connection contains invalid output thing!";
|
|
continue;
|
|
}
|
|
IntegrationPlugin *plugin = m_integrationPlugins.value(outputThing->pluginId());
|
|
if (!plugin) {
|
|
qCWarning(dcThingManager()) << "Plugin not found for IO connection's output action.";
|
|
continue;
|
|
}
|
|
StateType inputStateType = inputThing->thingClass().getStateType(stateTypeId);
|
|
|
|
StateType outputStateType = outputThing->thingClass().getStateType(ioConnection.outputStateTypeId());
|
|
if (outputStateType.id().isNull()) {
|
|
qCWarning(dcThingManager()) << "Could not find output state type for IO connection.";
|
|
continue;
|
|
}
|
|
QVariant outputValue;
|
|
if (outputStateType.ioType() == Types::IOTypeDigitalOutput) {
|
|
// Digital IOs are mapped as-is
|
|
outputValue = ioConnection.inverted() xor inputValue.toBool();
|
|
|
|
// We're already in sync! Skipping action.
|
|
if (outputThing->stateValue(outputStateType.id()) == outputValue) {
|
|
continue;
|
|
}
|
|
} else {
|
|
// Analog IOs are mapped within the according min/max ranges
|
|
outputValue = mapValue(inputValue, inputStateType, outputStateType, ioConnection.inverted());
|
|
|
|
// We're already in sync (fuzzy, good enough)! Skipping action.
|
|
if (qFuzzyCompare(1.0 + outputThing->stateValue(outputStateType.id()).toDouble(), 1.0 + outputValue.toDouble())) {
|
|
continue;
|
|
}
|
|
}
|
|
Action outputAction(ActionTypeId(ioConnection.outputStateTypeId()), ioConnection.outputThingId());
|
|
|
|
Param outputParam(ioConnection.outputStateTypeId(), outputValue);
|
|
outputAction.setParams(ParamList() << outputParam);
|
|
qCDebug(dcThingManager()) << "Executing IO connection action on" << outputThing->name() << outputParam;
|
|
ThingActionInfo* info = executeAction(outputAction);
|
|
connect(info, &ThingActionInfo::finished, this, [=](){
|
|
if (info->status() != Thing::ThingErrorNoError) {
|
|
// An error happened... let's switch the input back to be in sync with the output
|
|
qCWarning(dcThingManager()) << "Error syncing IO connection state. Reverting input back to old value.";
|
|
if (inputStateType.ioType() == Types::IOTypeDigitalInput) {
|
|
inputThing->setStateValue(inputStateType.id(), outputThing->stateValue(outputStateType.id()));
|
|
} else {
|
|
inputThing->setStateValue(inputStateType.id(), mapValue(outputThing->stateValue(outputStateType.id()), outputStateType, inputStateType, ioConnection.inverted()));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Now check if this is an output state type and - if possible - update the inputs for bidirectional connections
|
|
if (ioConnection.outputThingId() == thing->id() && ioConnection.outputStateTypeId() == stateTypeId) {
|
|
Thing *outputThing = thing;
|
|
QVariant outputValue = outputThing->stateValue(stateTypeId);
|
|
|
|
Thing *inputThing = m_configuredThings.value(ioConnection.inputThingId());
|
|
if (!inputThing) {
|
|
qCWarning(dcThingManager()) << "IO connection contains invalid input thing!";
|
|
continue;
|
|
}
|
|
IntegrationPlugin *plugin = m_integrationPlugins.value(inputThing->pluginId());
|
|
if (!plugin) {
|
|
qCWarning(dcThingManager()) << "Plugin not found for IO connection's input action.";
|
|
continue;
|
|
}
|
|
StateType outputStateType = outputThing->thingClass().getStateType(stateTypeId);
|
|
|
|
StateType inputStateType = inputThing->thingClass().getStateType(ioConnection.inputStateTypeId());
|
|
if (inputStateType.id().isNull()) {
|
|
qCWarning(dcThingManager()) << "Could not find input state type for IO connection.";
|
|
continue;
|
|
}
|
|
|
|
if (!inputStateType.writable()) {
|
|
qCDebug(dcThingManager()) << "Input state is not writable. This connection is unidirectional.";
|
|
continue;
|
|
}
|
|
|
|
QVariant inputValue;
|
|
if (inputStateType.ioType() == Types::IOTypeDigitalInput) {
|
|
// Digital IOs are mapped as-is
|
|
inputValue = ioConnection.inverted() xor outputValue.toBool();
|
|
|
|
// Prevent looping
|
|
if (inputThing->stateValue(inputStateType.id()) == inputValue) {
|
|
continue;
|
|
}
|
|
} else {
|
|
// Analog IOs are mapped within the according min/max ranges
|
|
inputValue = mapValue(outputValue, outputStateType, inputStateType, ioConnection.inverted());
|
|
|
|
// Prevent looping even if the above calculation has rounding errors... Just skip this action if we're close enough already
|
|
if (qFuzzyCompare(1.0 + inputThing->stateValue(inputStateType.id()).toDouble(), 1.0 + inputValue.toDouble())) {
|
|
continue;
|
|
}
|
|
}
|
|
Action inputAction(ActionTypeId(ioConnection.inputStateTypeId()), ioConnection.inputThingId());
|
|
|
|
Param inputParam(ioConnection.inputStateTypeId(), inputValue);
|
|
inputAction.setParams(ParamList() << inputParam);
|
|
qCDebug(dcThingManager()) << "Executing reverse IO connection action on" << inputThing->name() << inputParam;
|
|
executeAction(inputAction);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ThingManagerImplementation::slotThingSettingChanged(const ParamTypeId ¶mTypeId, const QVariant &value)
|
|
{
|
|
Thing *thing = qobject_cast<Thing*>(sender());
|
|
if (!thing) {
|
|
return;
|
|
}
|
|
storeConfiguredThings();
|
|
emit thingSettingChanged(thing->id(), paramTypeId, value);
|
|
}
|
|
|
|
void ThingManagerImplementation::slotThingNameChanged()
|
|
{
|
|
Thing *thing = qobject_cast<Thing*>(sender());
|
|
if (!thing) {
|
|
return;
|
|
}
|
|
storeConfiguredThings();
|
|
emit thingChanged(thing);
|
|
}
|
|
|
|
ParamList ThingManagerImplementation::buildParams(const ParamTypes &types, const ParamList &first, const ParamList &second)
|
|
{
|
|
// Merge params from discovered descriptor and additional overrides provided on API call. User provided params have higher priority than discovery params.
|
|
ParamList finalParams;
|
|
foreach (const ParamType ¶mType, types) {
|
|
if (first.hasParam(paramType.id())) {
|
|
finalParams.append(Param(paramType.id(), first.paramValue(paramType.id())));
|
|
} else if (second.hasParam(paramType.id())) {
|
|
finalParams.append(Param(paramType.id(), second.paramValue(paramType.id())));
|
|
} else if (paramType.defaultValue().isValid()){
|
|
finalParams.append(Param(paramType.id(), paramType.defaultValue()));
|
|
}
|
|
}
|
|
return finalParams;
|
|
}
|
|
|
|
void ThingManagerImplementation::pairThingInternal(ThingPairingInfo *info)
|
|
{
|
|
ThingClass thingClass = m_supportedThings.value(info->thingClassId());
|
|
if (thingClass.id().isNull()) {
|
|
qCWarning(dcThingManager) << "Cannot find a thing class with id" << info->thingClassId();
|
|
info->finish(Thing::ThingErrorThingClassNotFound);
|
|
return;
|
|
}
|
|
|
|
if (thingClass.setupMethod() == ThingClass::SetupMethodJustAdd) {
|
|
qCWarning(dcThingManager) << "Cannot setup this thing this way. No need to pair this thing.";
|
|
info->finish(Thing::ThingErrorSetupMethodNotSupported);
|
|
return;
|
|
}
|
|
|
|
IntegrationPlugin *plugin = m_integrationPlugins.value(thingClass.pluginId());
|
|
if (!plugin) {
|
|
qCWarning(dcThingManager) << "Cannot pair thing class" << thingClass.name() << "because no plugin for it is loaded.";
|
|
info->finish(Thing::ThingErrorPluginNotFound);
|
|
return;
|
|
}
|
|
|
|
plugin->startPairing(info);
|
|
|
|
connect(info, &ThingPairingInfo::finished, this, [this, info, thingClass](){
|
|
if (info->status() != Thing::ThingErrorNoError) {
|
|
qCWarning(dcThingManager()) << "PairThing failed for" << thingClass << info->status() << info->displayMessage();
|
|
return;
|
|
}
|
|
|
|
qCDebug(dcThingManager()) << "Pairing started for" << thingClass << "using transaction id" << info->transactionId();
|
|
PairingContext context;
|
|
context.thingId = info->thingId();
|
|
context.thingClassId = info->thingClassId();
|
|
context.thingName = info->thingName();
|
|
context.params = info->params();
|
|
context.parentId = info->parentId();
|
|
m_pendingPairings.insert(info->transactionId(), context);
|
|
|
|
// TODO: Add a timer to clean up stuff if confirmPairing is never called.
|
|
});
|
|
}
|
|
|
|
ThingSetupInfo* ThingManagerImplementation::setupThing(Thing *thing)
|
|
{
|
|
ThingClass thingClass = findThingClass(thing->thingClassId());
|
|
IntegrationPlugin *plugin = m_integrationPlugins.value(thingClass.pluginId());
|
|
|
|
if (!plugin) {
|
|
qCWarning(dcThingManager) << "Can't find a plugin for this thing" << thing;
|
|
ThingSetupInfo *info = new ThingSetupInfo(thing, this);
|
|
info->finish(Thing::ThingErrorPluginNotFound, tr("The plugin for this thing is not loaded."));
|
|
return info;
|
|
}
|
|
|
|
QList<State> states;
|
|
foreach (const StateType &stateType, thingClass.stateTypes()) {
|
|
State state(stateType.id(), thing->id());
|
|
states.append(state);
|
|
}
|
|
thing->setStates(states);
|
|
loadThingStates(thing);
|
|
|
|
connect(thing, &Thing::stateValueChanged, this, &ThingManagerImplementation::slotThingStateValueChanged);
|
|
connect(thing, &Thing::settingChanged, this, &ThingManagerImplementation::slotThingSettingChanged);
|
|
connect(thing, &Thing::nameChanged, this, &ThingManagerImplementation::slotThingNameChanged);
|
|
|
|
|
|
ThingSetupInfo *info = new ThingSetupInfo(thing, this, 30000);
|
|
plugin->setupThing(info);
|
|
|
|
return info;
|
|
}
|
|
|
|
void ThingManagerImplementation::postSetupThing(Thing *thing)
|
|
{
|
|
ThingClass thingClass = findThingClass(thing->thingClassId());
|
|
IntegrationPlugin *plugin = m_integrationPlugins.value(thingClass.pluginId());
|
|
|
|
plugin->postSetupThing(thing);
|
|
}
|
|
|
|
void ThingManagerImplementation::loadThingStates(Thing *thing)
|
|
{
|
|
NymeaSettings settings(NymeaSettings::SettingsRoleThingStates);
|
|
settings.beginGroup(thing->id().toString());
|
|
ThingClass thingClass = m_supportedThings.value(thing->thingClassId());
|
|
foreach (const StateType &stateType, thingClass.stateTypes()) {
|
|
if (stateType.cached()) {
|
|
QVariant value;
|
|
// First try to load new style
|
|
if (settings.childGroups().contains(stateType.id().toString())) {
|
|
settings.beginGroup(stateType.id().toString());
|
|
value = settings.value("value", stateType.defaultValue());
|
|
value.convert(settings.value("type").toInt());
|
|
settings.endGroup();
|
|
} else { // Try to fall back to the pre 0.9.0 way of storing states
|
|
value = settings.value(stateType.id().toString(), stateType.defaultValue());
|
|
}
|
|
thing->setStateValue(stateType.id(), value);
|
|
} else {
|
|
thing->setStateValue(stateType.id(), stateType.defaultValue());
|
|
}
|
|
}
|
|
settings.endGroup();
|
|
}
|
|
|
|
void ThingManagerImplementation::storeIOConnections()
|
|
{
|
|
NymeaSettings connectionSettings(NymeaSettings::SettingsRoleIOConnections);
|
|
connectionSettings.beginGroup("IOConnections");
|
|
foreach (const IOConnection &ioConnection, m_ioConnections) {
|
|
connectionSettings.beginGroup(ioConnection.id().toString());
|
|
|
|
connectionSettings.setValue("inputThingId", ioConnection.inputThingId().toString());
|
|
connectionSettings.setValue("inputStateTypeId", ioConnection.inputStateTypeId().toString());
|
|
connectionSettings.setValue("outputThingId", ioConnection.outputThingId().toString());
|
|
connectionSettings.setValue("outputStateTypeId", ioConnection.outputStateTypeId().toString());
|
|
connectionSettings.setValue("inverted", ioConnection.inverted());
|
|
|
|
connectionSettings.endGroup();
|
|
}
|
|
connectionSettings.endGroup();
|
|
}
|
|
|
|
void ThingManagerImplementation::loadIOConnections()
|
|
{
|
|
NymeaSettings connectionSettings(NymeaSettings::SettingsRoleIOConnections);
|
|
connectionSettings.beginGroup("IOConnections");
|
|
foreach (const QString &idString, connectionSettings.childGroups()) {
|
|
connectionSettings.beginGroup(idString);
|
|
IOConnectionId id(idString);
|
|
ThingId inputThingId = connectionSettings.value("inputThingId").toUuid();
|
|
StateTypeId inputStateTypeId = connectionSettings.value("inputStateTypeId").toUuid();
|
|
ThingId outputThingId = connectionSettings.value("outputThingId").toUuid();
|
|
StateTypeId outputStateTypeId = connectionSettings.value("outputStateTypeId").toUuid();
|
|
bool inverted = connectionSettings.value("inverted").toBool();
|
|
IOConnection ioConnection(id, inputThingId, inputStateTypeId, outputThingId, outputStateTypeId, inverted);
|
|
m_ioConnections.insert(id, ioConnection);
|
|
connectionSettings.endGroup();
|
|
|
|
Thing *inputThing = m_configuredThings.value(inputThingId);
|
|
if (!inputThing) {
|
|
continue;
|
|
}
|
|
syncIOConnection(inputThing, inputStateTypeId);
|
|
}
|
|
connectionSettings.endGroup();
|
|
}
|
|
|
|
QVariant ThingManagerImplementation::mapValue(const QVariant &value, const StateType &fromStateType, const StateType &toStateType, bool inverted) const
|
|
{
|
|
double fromMin = fromStateType.minValue().toDouble();
|
|
double fromMax = fromStateType.maxValue().toDouble();
|
|
double toMin = toStateType.minValue().toDouble();
|
|
double toMax = toStateType.maxValue().toDouble();
|
|
double fromValue = value.toDouble();
|
|
double fromPercent = (fromValue - fromMin) / (fromMax - fromMin);
|
|
fromPercent = inverted ? 1 - fromPercent : fromPercent;
|
|
double toValue = toMin + (toMax - toMin) * fromPercent;
|
|
return toValue;
|
|
}
|
|
|
|
void ThingManagerImplementation::storeThingStates(Thing *thing)
|
|
{
|
|
NymeaSettings settings(NymeaSettings::SettingsRoleThingStates);
|
|
settings.beginGroup(thing->id().toString());
|
|
ThingClass thingClass = m_supportedThings.value(thing->thingClassId());
|
|
foreach (const StateType &stateType, thingClass.stateTypes()) {
|
|
if (stateType.cached()) {
|
|
settings.beginGroup(stateType.id().toString());
|
|
settings.setValue("type", static_cast<int>(thing->stateValue(stateType.id()).type()));
|
|
settings.setValue("value", thing->stateValue(stateType.id()));
|
|
settings.endGroup();
|
|
}
|
|
}
|
|
settings.endGroup();
|
|
}
|
|
|