diff --git a/libnymea-core/devices/devicemanagerimplementation.cpp b/libnymea-core/devices/devicemanagerimplementation.cpp
index ffe245a0..70fd15a5 100644
--- a/libnymea-core/devices/devicemanagerimplementation.cpp
+++ b/libnymea-core/devices/devicemanagerimplementation.cpp
@@ -38,6 +38,7 @@
#include "typeutils.h"
#include "nymeasettings.h"
#include "version.h"
+#include "plugininfocache.h"
#include "devices/devicediscoveryinfo.h"
#include "devices/devicepairinginfo.h"
@@ -259,7 +260,7 @@ DeviceDiscoveryInfo* DeviceManagerImplementation::discoverDevices(const DeviceCl
if (!plugin) {
qCWarning(dcDeviceManager) << "Device discovery failed. Plugin not found for device class" << deviceClass.name();
DeviceDiscoveryInfo *discoveryInfo = new DeviceDiscoveryInfo(deviceClassId, params, this);
- discoveryInfo->finish(Device::DeviceErrorPluginNotFound);
+ discoveryInfo->finish(Device::DeviceErrorPluginNotFound, tr("The plugin for this device or service is not loaded."));
return discoveryInfo;
}
@@ -595,9 +596,10 @@ DevicePairingInfo *DeviceManagerImplementation::confirmPairing(const PairingTran
PairingContext context = m_pendingPairings.take(pairingTransactionId);
DeviceClassId deviceClassId = context.deviceClassId;
- DevicePlugin *plugin = m_devicePlugins.value(m_supportedDevices.value(deviceClassId).pluginId());
+ DeviceClass deviceClass = m_supportedDevices.value(deviceClassId);
+ DevicePlugin *plugin = m_devicePlugins.value(deviceClass.pluginId());
if (!plugin) {
- qCWarning(dcDeviceManager) << "Can't find a plugin for this device class";
+ qCWarning(dcDeviceManager) << "Can't find a plugin for this device class:" << deviceClass;
DevicePairingInfo *info = new DevicePairingInfo(pairingTransactionId, deviceClassId, context.deviceId, context.deviceName, context.params, context.parentDeviceId, this);
info->finish(Device::DeviceErrorPluginNotFound);
return info;
@@ -634,7 +636,7 @@ DevicePairingInfo *DeviceManagerImplementation::confirmPairing(const PairingTran
Device *device = nullptr;
if (addNewDevice) {
- device = new Device(plugin, deviceClass, internalInfo->deviceId(), this);
+ device = new Device(plugin->pluginId(), deviceClass, internalInfo->deviceId(), this);
if (internalInfo->deviceName().isEmpty()) {
device->setName(deviceClass.displayName());
} else {
@@ -736,7 +738,7 @@ DeviceSetupInfo* DeviceManagerImplementation::addConfiguredDeviceInternal(const
return info;
}
- Device *device = new Device(plugin, deviceClass, deviceId, this);
+ Device *device = new Device(plugin->pluginId(), deviceClass, deviceId, this);
device->setParentId(parentDeviceId);
if (name.isEmpty()) {
device->setName(deviceClass.name());
@@ -810,13 +812,24 @@ BrowseResult *DeviceManagerImplementation::browseDevice(const DeviceId &deviceId
return result;
}
+ DevicePlugin *plugin = m_devicePlugins.value(device->pluginId());
+ if (!plugin) {
+ qCWarning(dcDeviceManager()) << "Cannot browse device. Plugin not found for device" << device;
+ return result;
+ }
+
+ if (!device->setupComplete()) {
+ qCWarning(dcDeviceManager()) << "Cannot browse device. Device did not finish setup" << device;
+ return result;
+ }
+
if (!device->deviceClass().browsable()) {
qCWarning(dcDeviceManager()) << "Cannot browse device. DeviceClass" << device->deviceClass().name() << "is not browsable.";
result->finish(Device::DeviceErrorUnsupportedFeature);
return result;
}
- device->plugin()->browseDevice(result);
+ plugin->browseDevice(result);
connect(result, &BrowseResult::finished, this, [result](){
if (result->status() != Device::DeviceErrorNoError) {
qCWarning(dcDeviceManager()) << "Browse device failed:" << result->status();
@@ -837,13 +850,24 @@ BrowserItemResult *DeviceManagerImplementation::browserItemDetails(const DeviceI
return result;
}
+ DevicePlugin *plugin = m_devicePlugins.value(device->pluginId());
+ if (!plugin) {
+ qCWarning(dcDeviceManager()) << "Cannot browse device. Plugin not found for device" << device;
+ return result;
+ }
+
+ if (device->setupStatus() != Device::DeviceSetupStatusComplete) {
+ qCWarning(dcDeviceManager()) << "Cannot browse device. Device did not finish setup" << device;
+ return result;
+ }
+
if (!device->deviceClass().browsable()) {
qCWarning(dcDeviceManager()) << "Cannot browse device. DeviceClass" << device->deviceClass().name() << "is not browsable.";
result->finish(Device::DeviceErrorUnsupportedFeature);
return result;
}
- device->plugin()->browserItem(result);
+ plugin->browserItem(result);
connect(result, &BrowserItemResult::finished, this, [result](){
if (result->status() != Device::DeviceErrorNoError) {
qCWarning(dcDeviceManager()) << "Browse device failed:" << result->status();
@@ -863,11 +887,24 @@ BrowserActionInfo* DeviceManagerImplementation::executeBrowserItem(const Browser
return info;
}
+ DevicePlugin *plugin = m_devicePlugins.value(device->pluginId());
+ if (!plugin) {
+ qCWarning(dcDeviceManager()) << "Cannot browse device. Plugin not found for device" << device;
+ info->finish(Device::DeviceErrorPluginNotFound);
+ return info;
+ }
+
+ if (device->setupStatus() != Device::DeviceSetupStatusComplete) {
+ qCWarning(dcDeviceManager()) << "Cannot browse device. Device did not finish setup" << device;
+ info->finish(Device::DeviceErrorSetupFailed);
+ return info;
+ }
+
if (!device->deviceClass().browsable()) {
info->finish(Device::DeviceErrorUnsupportedFeature);
return info;
}
- device->plugin()->executeBrowserItem(info);
+ plugin->executeBrowserItem(info);
return info;
}
@@ -882,13 +919,26 @@ BrowserItemActionInfo* DeviceManagerImplementation::executeBrowserItemAction(con
return info;
}
+ DevicePlugin *plugin = m_devicePlugins.value(device->pluginId());
+ if (!plugin) {
+ qCWarning(dcDeviceManager()) << "Cannot execute browser item action. Plugin not found for device" << device;
+ info->finish(Device::DeviceErrorPluginNotFound);
+ return info;
+ }
+
+ if (device->setupStatus() != Device::DeviceSetupStatusComplete) {
+ qCWarning(dcDeviceManager()) << "Cannot execute browser item action. Device did not finish setup" << device;
+ info->finish(Device::DeviceErrorSetupFailed);
+ return info;
+ }
+
if (!device->deviceClass().browsable()) {
info->finish(Device::DeviceErrorUnsupportedFeature);
return info;
}
// TODO: check browserItemAction.params with deviceClass
- device->plugin()->executeBrowserItemAction(info);
+ plugin->executeBrowserItemAction(info);
return info;
}
@@ -1100,7 +1150,8 @@ void DeviceManagerImplementation::loadPlugins()
continue;
}
- PluginMetadata metaData(loader.metaData().value("MetaData").toObject());
+ QJsonObject pluginInfo = loader.metaData().value("MetaData").toObject();
+ PluginMetadata metaData(pluginInfo);
if (!metaData.isValid()) {
foreach (const QString &error, metaData.validationErrors()) {
qCWarning(dcDeviceManager()) << error;
@@ -1120,6 +1171,7 @@ void DeviceManagerImplementation::loadPlugins()
continue;
}
loadPlugin(pluginIface, metaData);
+ PluginInfoCache::cachePluginInfo(pluginInfo);
}
}
@@ -1250,13 +1302,28 @@ void DeviceManagerImplementation::loadConfiguredDevices()
foreach (const QString &idString, settings.childGroups()) {
settings.beginGroup(idString);
QString deviceName = settings.value("devicename").toString();
- DevicePlugin *plugin = m_devicePlugins.value(PluginId(settings.value("pluginid").toString()));
+ PluginId pluginId = PluginId(settings.value("pluginid").toString());
+ DevicePlugin *plugin = m_devicePlugins.value(pluginId);
if (!plugin) {
- qCWarning(dcDeviceManager()) << "Not loading device" << deviceName << idString << "because the plugin for this device could not be found.";
- settings.endGroup(); // DeviceId
- continue;
+ qCWarning(dcDeviceManager()) << "Plugin for device" << deviceName << idString << "not found. This device will not be functional until the plugin can be loaded.";
+ }
+ DeviceClassId deviceClassId = DeviceClassId(settings.value("deviceClassId").toString());
+ DeviceClass deviceClass = findDeviceClass(deviceClassId);
+ if (!deviceClass.isValid()) {
+ // Try to load the device class from the cache
+ QJsonObject pluginInfo = PluginInfoCache::loadPluginInfo(pluginId);
+ if (!pluginInfo.empty()) {
+ PluginMetadata pluginMetadata(pluginInfo);
+ deviceClass = pluginMetadata.deviceClasses().findById(deviceClassId);
+ if (deviceClass.isValid()) {
+ m_supportedDevices.insert(deviceClassId, deviceClass);
+ if (!m_supportedVendors.contains(deviceClass.vendorId())) {
+ Vendor vendor = pluginMetadata.vendors().findById(deviceClass.vendorId());
+ m_supportedVendors.insert(vendor.id(), vendor);
+ }
+ }
+ }
}
- DeviceClass deviceClass = findDeviceClass(DeviceClassId(settings.value("deviceClassId").toString()));
if (!deviceClass.isValid()) {
qCWarning(dcDeviceManager()) << "Not loading device" << deviceName << idString << "because the device class for this device could not be found.";
settings.endGroup(); // DeviceId
@@ -1264,12 +1331,12 @@ void DeviceManagerImplementation::loadConfiguredDevices()
}
// Cross-check if this plugin still implements this device class
- if (!plugin->supportedDevices().contains(deviceClass)) {
+ if (plugin && !plugin->supportedDevices().contains(deviceClass)) {
qCWarning(dcDeviceManager()) << "Not loading device" << deviceName << idString << "because plugin" << plugin->pluginName() << "has removed support for it.";
settings.endGroup(); // DeviceId
continue;
}
- Device *device = new Device(plugin, deviceClass, DeviceId(idString), this);
+ Device *device = new Device(pluginId, deviceClass, DeviceId(idString), this);
device->m_autoCreated = settings.value("autoCreated").toBool();
device->setName(deviceName);
device->setParentId(DeviceId(settings.value("parentid", QUuid()).toString()));
@@ -1464,7 +1531,7 @@ void DeviceManagerImplementation::onAutoDevicesAppeared(const DeviceDescriptors
continue;
}
- device = new Device(plugin, deviceClass, this);
+ device = new Device(plugin->pluginId(), deviceClass, this);
device->m_autoCreated = true;
device->setName(deviceDescriptor.title());
device->setParams(deviceDescriptor.params());
@@ -1656,9 +1723,9 @@ DeviceSetupInfo* DeviceManagerImplementation::setupDevice(Device *device)
DevicePlugin *plugin = m_devicePlugins.value(deviceClass.pluginId());
if (!plugin) {
- qCWarning(dcDeviceManager) << "Can't find a plugin for this device" << device->id();
- DeviceSetupInfo *info = new DeviceSetupInfo(nullptr, this);
- info->finish(Device::DeviceErrorPluginNotFound);
+ qCWarning(dcDeviceManager) << "Can't find a plugin for this device" << device;
+ DeviceSetupInfo *info = new DeviceSetupInfo(device, this);
+ info->finish(Device::DeviceErrorPluginNotFound, tr("The plugin for this device or service is not loaded."));
return info;
}
diff --git a/libnymea-core/devices/plugininfocache.cpp b/libnymea-core/devices/plugininfocache.cpp
new file mode 100644
index 00000000..5e173476
--- /dev/null
+++ b/libnymea-core/devices/plugininfocache.cpp
@@ -0,0 +1,82 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 .
+*
+* 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 "plugininfocache.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include "loggingcategories.h"
+
+PluginInfoCache::PluginInfoCache()
+{
+
+}
+
+void PluginInfoCache::cachePluginInfo(const QJsonObject &metaData)
+{
+ QString fileName = metaData.value("id").toString().remove(QRegExp("[{}]")) + ".cache";
+ QDir path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/plugininfo/";
+ if (!path.exists()) {
+ if (!path.mkpath(path.absolutePath())) {
+ qCWarning(dcDeviceManager()) << "Error creating device class cache dir at" << path.absolutePath();
+ }
+ }
+ QFile file(path.absoluteFilePath(fileName));
+ if (!file.open(QFile::WriteOnly | QFile::Truncate)) {
+ qCWarning(dcDeviceManager()) << "Error opening device class cache for writing at" << path.absoluteFilePath(fileName);
+ return;
+ }
+
+ file.write(QJsonDocument::fromVariant(metaData.toVariantMap()).toJson(QJsonDocument::Compact));
+ file.close();
+}
+
+QJsonObject PluginInfoCache::loadPluginInfo(const PluginId &pluginId)
+{
+ QString fileName = pluginId.toString().remove(QRegExp("[{}]")) + ".cache";
+ QDir path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/plugininfo/";
+ QFile file(path.absoluteFilePath(fileName));
+ if (!file.open(QFile::ReadOnly)) {
+ return QJsonObject();
+ }
+
+ QByteArray data = file.readAll();
+ QJsonParseError error;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
+ if (error.error != QJsonParseError::NoError) {
+ qCWarning(dcDeviceManager()) << "Error parsing plugin info cache entry:" << path.absoluteFilePath(fileName);
+ return QJsonObject();
+ }
+ return QJsonObject::fromVariantMap(jsonDoc.toVariant().toMap());
+}
diff --git a/libnymea-core/devices/plugininfocache.h b/libnymea-core/devices/plugininfocache.h
new file mode 100644
index 00000000..7db29954
--- /dev/null
+++ b/libnymea-core/devices/plugininfocache.h
@@ -0,0 +1,46 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 .
+*
+* 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
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef PLUGININFOCACHE_H
+#define PLUGININFOCACHE_H
+
+#include "types/deviceclass.h"
+#include "devices/deviceplugin.h"
+
+class PluginInfoCache
+{
+public:
+ PluginInfoCache();
+
+ static void cachePluginInfo(const QJsonObject &metaData);
+ static QJsonObject loadPluginInfo(const PluginId &pluginId);
+};
+
+#endif // PLUGININFOCACHE_H
diff --git a/libnymea-core/libnymea-core.pro b/libnymea-core/libnymea-core.pro
index c7494177..0e9e8d25 100644
--- a/libnymea-core/libnymea-core.pro
+++ b/libnymea-core/libnymea-core.pro
@@ -17,6 +17,7 @@ RESOURCES += $$top_srcdir/icons.qrc \
HEADERS += nymeacore.h \
devices/devicemanagerimplementation.h \
+ devices/plugininfocache.h \
devices/translator.h \
experiences/experiencemanager.h \
ruleengine/ruleengine.h \
@@ -101,6 +102,7 @@ HEADERS += nymeacore.h \
SOURCES += nymeacore.cpp \
devices/devicemanagerimplementation.cpp \
+ devices/plugininfocache.cpp \
devices/translator.cpp \
experiences/experiencemanager.cpp \
ruleengine/ruleengine.cpp \
diff --git a/libnymea/devices/device.cpp b/libnymea/devices/device.cpp
index c58e7ceb..a7a9976f 100644
--- a/libnymea/devices/device.cpp
+++ b/libnymea/devices/device.cpp
@@ -119,20 +119,20 @@
#include
/*! Construct an Device with the given \a pluginId, \a id, \a deviceClassId and \a parent. */
-Device::Device(DevicePlugin *plugin, const DeviceClass &deviceClass, const DeviceId &id, QObject *parent):
+Device::Device(const PluginId &pluginId, const DeviceClass &deviceClass, const DeviceId &id, QObject *parent):
QObject(parent),
m_deviceClass(deviceClass),
- m_plugin(plugin),
+ m_pluginId(pluginId),
m_id(id)
{
}
/*! Construct an Device with the given \a pluginId, \a deviceClassId and \a parent. A new DeviceId will be created for this Device. */
-Device::Device(DevicePlugin *plugin, const DeviceClass &deviceClass, QObject *parent):
+Device::Device(const PluginId &pluginId, const DeviceClass &deviceClass, QObject *parent):
QObject(parent),
m_deviceClass(deviceClass),
- m_plugin(plugin),
+ m_pluginId(pluginId),
m_id(DeviceId::createDeviceId())
{
@@ -153,7 +153,7 @@ DeviceClassId Device::deviceClassId() const
/*! Returns the id of the \l{DevicePlugin} this Device is managed by. */
PluginId Device::pluginId() const
{
- return m_plugin->pluginId();
+ return m_pluginId;
}
/*! Returns the \l{DeviceClass} of this device. */
@@ -162,12 +162,6 @@ DeviceClass Device::deviceClass() const
return m_deviceClass;
}
-/*! Returns the the \l{DevicePlugin} this Device is managed by. */
-DevicePlugin *Device::plugin() const
-{
- return m_plugin;
-}
-
/*! Returns the name of this Device. This is visible to the user. */
QString Device::name() const
{
diff --git a/libnymea/devices/device.h b/libnymea/devices/device.h
index 70768ce6..d00054da 100644
--- a/libnymea/devices/device.h
+++ b/libnymea/devices/device.h
@@ -105,7 +105,6 @@ public:
PluginId pluginId() const;
DeviceClass deviceClass() const;
- DevicePlugin* plugin() const;
QString name() const;
void setName(const QString &name);
@@ -153,14 +152,14 @@ signals:
private:
friend class DeviceManager;
friend class DeviceManagerImplementation;
- Device(DevicePlugin *plugin, const DeviceClass &deviceClass, const DeviceId &id, QObject *parent = nullptr);
- Device(DevicePlugin *plugin, const DeviceClass &deviceClass, QObject *parent = nullptr);
+ Device(const PluginId &pluginId, const DeviceClass &deviceClass, const DeviceId &id, QObject *parent = nullptr);
+ Device(const PluginId &pluginId, const DeviceClass &deviceClass, QObject *parent = nullptr);
void setSetupStatus(Device::DeviceSetupStatus status, Device::DeviceError setupError, const QString &displayMessage = QString());
private:
DeviceClass m_deviceClass;
- DevicePlugin* m_plugin = nullptr;
+ PluginId m_pluginId;
DeviceId m_id;
DeviceId m_parentId;
diff --git a/translations/nymead-cs.ts b/translations/nymead-cs.ts
index fb9b137e..6b26105d 100644
--- a/translations/nymead-cs.ts
+++ b/translations/nymead-cs.ts
@@ -45,6 +45,15 @@
Připojeno změněno
+
+ DeviceManagerImplementation
+
+
+
+ The plugin for this device or service is not loaded.
+
+
+
nymea
diff --git a/translations/nymead-da.ts b/translations/nymead-da.ts
index fc97d568..8e6252fd 100644
--- a/translations/nymead-da.ts
+++ b/translations/nymead-da.ts
@@ -45,6 +45,15 @@
Forbindelse ændret
+
+ DeviceManagerImplementation
+
+
+
+ The plugin for this device or service is not loaded.
+
+
+
nymea
diff --git a/translations/nymead-de_DE.ts b/translations/nymead-de_DE.ts
index 875c1ccb..02d358ae 100644
--- a/translations/nymead-de_DE.ts
+++ b/translations/nymead-de_DE.ts
@@ -45,6 +45,15 @@
Verbunden geändert
+
+ DeviceManagerImplementation
+
+
+
+ The plugin for this device or service is not loaded.
+
+
+
nymea
diff --git a/translations/nymead-en_US.ts b/translations/nymead-en_US.ts
index 8c78b49a..34380138 100644
--- a/translations/nymead-en_US.ts
+++ b/translations/nymead-en_US.ts
@@ -45,6 +45,15 @@
+
+ DeviceManagerImplementation
+
+
+
+ The plugin for this device or service is not loaded.
+
+
+
nymea
diff --git a/translations/nymead-es.ts b/translations/nymead-es.ts
index 9a642c17..663541af 100644
--- a/translations/nymead-es.ts
+++ b/translations/nymead-es.ts
@@ -45,6 +45,15 @@
Conexión modificada
+
+ DeviceManagerImplementation
+
+
+
+ The plugin for this device or service is not loaded.
+
+
+
nymea
diff --git a/translations/nymead-fi.ts b/translations/nymead-fi.ts
index 51ad42e0..94499fe3 100644
--- a/translations/nymead-fi.ts
+++ b/translations/nymead-fi.ts
@@ -45,6 +45,15 @@
Yhdistetty muutettiin
+
+ DeviceManagerImplementation
+
+
+
+ The plugin for this device or service is not loaded.
+
+
+
nymea
diff --git a/translations/nymead-fr.ts b/translations/nymead-fr.ts
index 7b09e486..56317d34 100644
--- a/translations/nymead-fr.ts
+++ b/translations/nymead-fr.ts
@@ -45,6 +45,15 @@
Connecté modifié
+
+ DeviceManagerImplementation
+
+
+
+ The plugin for this device or service is not loaded.
+
+
+
nymea
diff --git a/translations/nymead-it.ts b/translations/nymead-it.ts
index f907cb95..5f94f310 100644
--- a/translations/nymead-it.ts
+++ b/translations/nymead-it.ts
@@ -45,6 +45,15 @@
Connesso modificato
+
+ DeviceManagerImplementation
+
+
+
+ The plugin for this device or service is not loaded.
+
+
+
nymea
diff --git a/translations/nymead-nl.ts b/translations/nymead-nl.ts
index ad8f6b7f..bf79068e 100644
--- a/translations/nymead-nl.ts
+++ b/translations/nymead-nl.ts
@@ -45,6 +45,15 @@
Verbonden gewijzigd
+
+ DeviceManagerImplementation
+
+
+
+ The plugin for this device or service is not loaded.
+
+
+
nymea
diff --git a/translations/nymead-pt.ts b/translations/nymead-pt.ts
index d94beac0..131f5a13 100644
--- a/translations/nymead-pt.ts
+++ b/translations/nymead-pt.ts
@@ -45,6 +45,15 @@
Ligado alterado
+
+ DeviceManagerImplementation
+
+
+
+ The plugin for this device or service is not loaded.
+
+
+
nymea