Add Logilink plug-in

pull/733/head
trinnes 2024-04-30 08:23:41 +02:00
parent d4d9ad698d
commit b9a46d21bb
11 changed files with 644 additions and 0 deletions

10
debian/control vendored
View File

@ -352,6 +352,16 @@ Conflicts: nymea-plugins-translations (< 1.0.1)
Description: nymea integration plugin for lifx
This package contains the nymea integration plugin for lifx devices
Package: nymea-plugin-logilink
Architecture: any
Section: libs
Depends: ${shlibs:Depends},
${misc:Depends},
Conflicts: nymea-plugins-translations (< 1.0.1)
Description: nymea integration plugin for Logilink power sockets
This package contains the nymea integration plugin for Logilink PDU
network controlled power sockets.
Package: nymea-plugin-mecelectronics
Architecture: any
Depends: ${shlibs:Depends},

View File

@ -0,0 +1,2 @@
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginlogilink.so
logilink/translations/*qm usr/share/nymea/translations/

22
logilink/README.md Normal file
View File

@ -0,0 +1,22 @@
# Logilink
This integration allows nymea to control Logilink power sockets.
## Supported things
* PDU8P01
* Secure connection with username and password
* Get and set the state of each socket
* No internet or cloud connection required
## Requirements
* The Logilink device must be in the same local area network as nymea.
* TCP Sockets on port 80 must not be blocked by the router.
* Access to the device login credentials.
* The package “nymea-plugin-logilink” must be installed
## More
See https://logilink.de for a detailed description of the devices.

View File

@ -0,0 +1,288 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, 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 Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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 "integrationpluginlogilink.h"
#include "plugininfo.h"
#include "plugintimer.h"
#include <network/networkaccessmanager.h>
#include <QNetworkReply>
#include <QAuthenticator>
#include <QUrlQuery>
#include <QXmlStreamReader>
IntegrationPluginLogilink::IntegrationPluginLogilink()
{
}
IntegrationPluginLogilink::~IntegrationPluginLogilink()
{
m_pollTimer->deleteLater();
}
void IntegrationPluginLogilink::startPairing(ThingPairingInfo *info)
{
info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Please enter the login credentials for your device."));
}
void IntegrationPluginLogilink::init()
{
m_pollTimer = hardwareManager()->pluginTimerManager()->registerTimer(m_pollInterval);
connect(m_pollTimer, &PluginTimer::timeout, this, &IntegrationPluginLogilink::refreshStates);
}
void IntegrationPluginLogilink::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &password)
{
if (info->thingClassId() == pdu8p01ThingClassId) {
QString ipAddress = info->params().paramValue(pdu8p01ThingIpv4AddressParamTypeId).toString();
QNetworkRequest request;
request.setUrl(QUrl(QString("http://%1/status.xml").arg(ipAddress)));
request.setRawHeader("Authorization", "Basic " + QString("%1:%2").arg(username).arg(password).toUtf8().toBase64());
qCDebug(dcLogilink()) << "ConfirmPairing fetching:" << request.url() << request.rawHeader("Authorization");
QNetworkReply *reply = hardwareManager()->networkManager()->get(request);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, info, [=](){
if (reply->error() == QNetworkReply::NoError) {
pluginStorage()->beginGroup(info->thingId().toString());
pluginStorage()->setValue("username", username);
pluginStorage()->setValue("password", password);
pluginStorage()->endGroup();
info->finish(Thing::ThingErrorNoError);
} else {
info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Wrong username or password."));
}
});
} else {
qCWarning(dcLogilink()) << "Unhandled ThingClass in confirmPairing" << info->thingClassId();
info->finish(Thing::ThingErrorThingClassNotFound);
}
}
void IntegrationPluginLogilink::setupThing(ThingSetupInfo *info)
{
Thing *thing = info->thing();
qCDebug(dcLogilink()) << "Setup" << thing->name();
if (thing->thingClassId() == pdu8p01ThingClassId) {
if (!m_pollTimer->running()) {
m_pollTimer->start();
}
QString ipAddress = thing->paramValue(pdu8p01ThingIpv4AddressParamTypeId).toString();
QNetworkRequest request;
pluginStorage()->beginGroup(thing->id().toString());
const QString username = pluginStorage()->value("username").toString();
const QString password = pluginStorage()->value("password").toString();
pluginStorage()->endGroup();
request.setUrl(QUrl(QString("http://%1/status.xml").arg(ipAddress)));
request.setRawHeader("Authorization", "Basic " + QString("%1:%2").arg(username).arg(password).toUtf8().toBase64());
QNetworkReply *reply = hardwareManager()->networkManager()->get(request);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, info, [=](){
if (reply->error() == QNetworkReply::NoError) {
info->finish(Thing::ThingErrorNoError);
if (myThings().filterByParentId(thing->id()).isEmpty()) {
// Creating sockets as child 'things'
ThingDescriptors descriptorList;
for (int i = 0; i < 8; i++) {
QString deviceName = thing->name() + " socket " + QString::number(i);
ThingDescriptor d(socketThingClassId, deviceName, thing->name(), thing->id());
d.setParams(ParamList() << Param(socketThingNumberParamTypeId, i));
descriptorList << d;
}
emit autoThingsAppeared(descriptorList);
}
} else if (reply->error() == QNetworkReply::AuthenticationRequiredError) {
info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Wrong username or password"));
} else {
info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("Device not found"));
}
});
return;
}
if (thing->thingClassId() == socketThingClassId) {
info->finish(Thing::ThingErrorNoError);
return;
}
qCWarning(dcLogilink()) << "Unhandled ThingClass in setupDevice" << thing->thingClassId();
info->finish(Thing::ThingErrorThingClassNotFound);
}
void IntegrationPluginLogilink::postSetupThing(Thing *thing)
{
qCDebug(dcLogilink()) << "Post setup" << thing->name();
if (thing->thingClassId() == pdu8p01ThingClassId) {
getStates(thing);
}
}
void IntegrationPluginLogilink::thingRemoved(Thing *thing)
{
qCDebug(dcLogilink()) << "Thing removed" << thing->name();
if (thing->thingClassId() == pdu8p01ThingClassId) {
pluginStorage()->remove(thing->id().toString());
}
if (myThings().filterByThingClassId(pdu8p01ThingClassId).isEmpty()) {
m_pollTimer->stop();
}
}
void IntegrationPluginLogilink::executeAction(ThingActionInfo *info)
{
Thing *thing = info->thing();
Action action = info->action();
if (thing->thingClassId() == socketThingClassId) {
if (action.actionTypeId() == socketPowerActionTypeId) {
Thing *parentDevice = myThings().findById(thing->parentId());
auto ipAddress = parentDevice->paramValue(pdu8p01ThingIpv4AddressParamTypeId).toString();
pluginStorage()->beginGroup(parentDevice->id().toString());
QString username = pluginStorage()->value("username").toString();
QString password = pluginStorage()->value("password").toString();
pluginStorage()->endGroup();
QUrl url(QString("http://%1/control_outlet.htm").arg(ipAddress));
QUrlQuery query;
query.addQueryItem("outlet" + thing->paramValue(socketThingNumberParamTypeId).toString(), "1");
query.addQueryItem("op", action.param(socketPowerActionPowerParamTypeId).value().toBool() ? "0" : "1"); // op code 0 is on, 1 is off
query.addQueryItem("submit", "Apply");
url.setQuery(query);
QNetworkRequest request(url);
request.setRawHeader("Authorization", "Basic " + QString("%1:%2").arg(username, password).toUtf8().toBase64());
QNetworkReply *reply = hardwareManager()->networkManager()->get(request);
qCDebug(dcLogilink()) << "Requesting:" << url.toString() << request.rawHeader("Authorization");
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, info, [reply, info](){
if (reply->error() != QNetworkReply::NoError) {
qCWarning(dcLogilink()) << "Execute action failed:" << reply->error() << reply->errorString();
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
info->finish(Thing::ThingErrorNoError);
});
return;
}
info->finish(Thing::ThingErrorActionTypeNotFound);
}
info->finish(Thing::ThingErrorThingClassNotFound);
}
void IntegrationPluginLogilink::refreshStates()
{
foreach (Thing *thing, myThings().filterByThingClassId(pdu8p01ThingClassId)) {
getStates(thing);
}
}
void IntegrationPluginLogilink::getStates(Thing *thing)
{
auto ipAddress = thing->paramValue(pdu8p01ThingIpv4AddressParamTypeId).toString();
pluginStorage()->beginGroup(thing->id().toString());
QString username = pluginStorage()->value("username").toString();
QString password = pluginStorage()->value("password").toString();
pluginStorage()->endGroup();
QUrl url(QString("http://%1/status.xml").arg(ipAddress));
QNetworkRequest request;
request.setUrl(url);
request.setRawHeader("Authorization", "Basic " + QString("%1:%2").arg(username, password).toUtf8().toBase64());
QNetworkReply *reply = hardwareManager()->networkManager()->get(request);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, thing, [this, thing, reply](){
if (reply->error() != QNetworkReply::NoError) {
qCWarning(dcLogilink()) << "Error fetching stats for" << thing->name() << reply->errorString();
thing->setStateValue(pdu8p01ConnectedStateTypeId, false);
foreach (auto child, myThings().filterByParentId(thing->id())) {
child->setStateValue(socketConnectedStateTypeId, false);
}
return;
}
QXmlStreamReader xml;
xml.addData(reply->readAll());
if (xml.hasError()) {
qCDebug(dcLogilink()) << "XML Error:" << xml.errorString();
return;
}
thing->setStateValue(pdu8p01ConnectedStateTypeId, true);
if (xml.readNextStartElement()) {
if (xml.name() == "response") {
qCDebug(dcLogilink()) << "XML contains response";
} else {
qCWarning(dcLogilink()) << "xml name" << xml.name();
}
while(xml.readNextStartElement()) {
qCDebug(dcLogilink()) << "XML name" << xml.name();
if (xml.name() == "curBan") {
auto current = xml.readElementText().toDouble();
qCDebug(dcLogilink()) << "Current" << current;
thing->setStateValue(pdu8p01TotalLoadStateTypeId, current);
} else if (xml.name() == "statBan") {
auto status = xml.readElementText();
qCDebug(dcLogilink()) << "Status" << status;
thing->setStateValue(pdu8p01StatusStateTypeId, status);
} else if (xml.name() == "tempBan") {
auto temperature = xml.readElementText().toDouble();
qCDebug(dcLogilink()) << "Temperature" << temperature;
thing->setStateValue(pdu8p01TemperatureStateTypeId, temperature);
} else if (xml.name() == "humBan") {
auto humidity = xml.readElementText().toDouble();
qCDebug(dcLogilink()) << "hummidity" << humidity;
thing->setStateValue(pdu8p01HumidityStateTypeId, humidity);
} else if (xml.name().startsWith("outletStat")){
int socketNumber = xml.name().right(1).toInt();
bool socketValue = xml.readElementText().startsWith("on");
auto socketThing = myThings().filterByParentId(thing->id())
.filterByParam(socketThingNumberParamTypeId, socketNumber)
.first();
if (!socketThing) {
// Socket not yet setup
continue;
}
qCDebug(dcLogilink()) << "Socket" << socketNumber << socketValue;
socketThing->setStateValue(socketPowerStateTypeId, socketValue);
socketThing->setStateValue(socketConnectedStateTypeId, true);
} else {
xml.skipCurrentElement();
}
}
}
});
}

View File

@ -0,0 +1,69 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, 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 Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef INTEGRATIONPLUGINLOGILINK_H
#define INTEGRATIONPLUGINLOGILINK_H
#include "integrations/integrationplugin.h"
#include <QNetworkAccessManager>
class PluginTimer;
class IntegrationPluginLogilink: public IntegrationPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginlogilink.json")
Q_INTERFACES(IntegrationPlugin)
public:
explicit IntegrationPluginLogilink();
~IntegrationPluginLogilink();
void startPairing(ThingPairingInfo *info) override;
void init() override;
void confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret) override;
void setupThing(ThingSetupInfo *info) override;
void postSetupThing(Thing *thing) override;
void thingRemoved(Thing *thing) override;
void executeAction(ThingActionInfo *info) override;
private slots:
void refreshStates();
private:
PluginTimer *m_pollTimer = nullptr;
const int m_pollInterval = 1; // seconds
void getStates(Thing *thing);
};
#endif // INTEGRATIONPLUGINLOGILINK_H

View File

@ -0,0 +1,116 @@
{
"name": "logilink",
"displayName": "Logilink",
"id": "3e80496e-06b5-11ef-8f86-2be8f966eb8d",
"vendors": [
{
"name": "logilink",
"displayName": "Logilink",
"id": "4aed2c3a-06b5-11ef-8a6b-ff770ac74f51",
"thingClasses": [
{
"id": "d8162d6e-06b5-11ef-888f-13a1e6eca98c",
"name": "pdu8p01",
"displayName": "PDU8P01",
"createMethods": ["user"],
"setupMethod": "userandpassword",
"interfaces": [ "gateway", "temperaturesensor", "humiditysensor"],
"paramTypes": [
{
"id": "d2e1a63e-06b5-11ef-bf2d-5f0d67f222af",
"name":"ipv4Address",
"displayName": "IPv4 address",
"type": "QString",
"inputType": "IPv4Address",
"defaultValue": "192.168.0.100"
}
],
"stateTypes": [
{
"id": "c5c7f1ba-06b5-11ef-ad08-03562210396e",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "413af8c0-0796-11ef-bd2b-4fde50f95418",
"name": "totalLoad",
"displayName": "Total load",
"displayNameEvent": "Total load changed",
"type": "double",
"defaultValue": 0,
"unit": "Ampere"
},
{
"id": "c0ce082a-06b5-11ef-b71a-7f0e8b9a48c0",
"name": "temperature",
"displayName": "Temperature",
"displayNameEvent": "Temperature changed",
"type": "double",
"defaultValue": 0,
"unit": "DegreeCelsius"
},
{
"id": "1aaaf3ca-0793-11ef-8b3e-f7eff48006c2",
"name": "humidity",
"displayName": "Humidity",
"displayNameEvent": "Humidity changed",
"type": "double",
"defaultValue": 0,
"minValue": 0,
"maxValue": 100,
"unit": "Percentage"
},
{
"id": "6ec71e40-0796-11ef-a8f8-a33a504a66bb",
"name": "status",
"displayName": "Status",
"displayNameEvent": "Status changed",
"type": "QString",
"defaultValue": "Unknown"
}
]
},
{
"id": "a320bfb6-06b5-11ef-81e4-834aeaf5fe45",
"name": "socket",
"displayName": "Logilink Socket",
"createMethods": ["auto"],
"interfaces": ["powersocket", "connectable"],
"paramTypes": [
{
"id": "b329edba-06b5-11ef-b04e-d3622c766dd0",
"name": "number",
"displayName": "Socket number",
"type": "int"
}
],
"stateTypes": [
{
"id": "b8d9679a-06b5-11ef-9812-fb840afffc88",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"type": "bool",
"defaultValue": false
},
{
"id": "47329958-c33f-478f-b2a0-910abd150da8",
"name": "power",
"displayName": "Power",
"displayNameEvent": "Power changed",
"displayNameAction": "Set power",
"writable": true,
"type": "bool",
"defaultValue": false,
"ioType": "digitalOutput"
}
]
}
]
}
]
}

BIN
logilink/logilink.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

11
logilink/logilink.pro Normal file
View File

@ -0,0 +1,11 @@
include(../plugins.pri)
QT += network xml
TARGET = $$qtLibraryTarget(nymea_integrationpluginlogilink)
SOURCES += \
integrationpluginlogilink.cpp \
HEADERS += \
integrationpluginlogilink.h \

14
logilink/meta.json Normal file
View File

@ -0,0 +1,14 @@
{
"title": "Logilink",
"tagline": "Integrates Logilink with nymea.",
"icon": "logilink.png",
"stability": "consumer",
"offline": true,
"technologies": [
"network"
],
"categories": [
"sensor",
"socket"
]
}

View File

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>IntegrationPluginLogilink</name>
<message>
<location filename="../integrationpluginlogilink.cpp" line="52"/>
<source>Please enter the login credentials for your device.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../integrationpluginlogilink.cpp" line="80"/>
<source>Wrong username or password.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../integrationpluginlogilink.cpp" line="127"/>
<source>Wrong username or password</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../integrationpluginlogilink.cpp" line="129"/>
<source>Device not found</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>logilink</name>
<message>
<location filename="../../../build-nymea-plugins-Desktop_Qt5-Debug/logilink/plugininfo.h" line="40"/>
<location filename="../../../build-nymea-plugins-Desktop_Qt5-Debug/logilink/plugininfo.h" line="43"/>
<source>Connected</source>
<extracomment>The name of the StateType ({b8d9679a-06b5-11ef-9812-fb840afffc88}) of ThingClass socket
----------
The name of the StateType ({c5c7f1ba-06b5-11ef-ad08-03562210396e}) of ThingClass pdu8p01</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop_Qt5-Debug/logilink/plugininfo.h" line="46"/>
<source>Humidity</source>
<extracomment>The name of the StateType ({1aaaf3ca-0793-11ef-8b3e-f7eff48006c2}) of ThingClass pdu8p01</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop_Qt5-Debug/logilink/plugininfo.h" line="49"/>
<source>IPv4 address</source>
<extracomment>The name of the ParamType (ThingClass: pdu8p01, Type: thing, ID: {d2e1a63e-06b5-11ef-bf2d-5f0d67f222af})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop_Qt5-Debug/logilink/plugininfo.h" line="52"/>
<location filename="../../../build-nymea-plugins-Desktop_Qt5-Debug/logilink/plugininfo.h" line="55"/>
<source>Logilink</source>
<extracomment>The name of the vendor ({4aed2c3a-06b5-11ef-8a6b-ff770ac74f51})
----------
The name of the plugin logilink ({3e80496e-06b5-11ef-8f86-2be8f966eb8d})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop_Qt5-Debug/logilink/plugininfo.h" line="58"/>
<source>Logilink Socket</source>
<extracomment>The name of the ThingClass ({a320bfb6-06b5-11ef-81e4-834aeaf5fe45})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop_Qt5-Debug/logilink/plugininfo.h" line="61"/>
<source>PDU8P01</source>
<extracomment>The name of the ThingClass ({d8162d6e-06b5-11ef-888f-13a1e6eca98c})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop_Qt5-Debug/logilink/plugininfo.h" line="64"/>
<location filename="../../../build-nymea-plugins-Desktop_Qt5-Debug/logilink/plugininfo.h" line="67"/>
<source>Power</source>
<extracomment>The name of the ParamType (ThingClass: socket, ActionType: power, ID: {47329958-c33f-478f-b2a0-910abd150da8})
----------
The name of the StateType ({47329958-c33f-478f-b2a0-910abd150da8}) of ThingClass socket</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop_Qt5-Debug/logilink/plugininfo.h" line="70"/>
<source>Set power</source>
<extracomment>The name of the ActionType ({47329958-c33f-478f-b2a0-910abd150da8}) of ThingClass socket</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop_Qt5-Debug/logilink/plugininfo.h" line="73"/>
<source>Socket number</source>
<extracomment>The name of the ParamType (ThingClass: socket, Type: thing, ID: {b329edba-06b5-11ef-b04e-d3622c766dd0})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop_Qt5-Debug/logilink/plugininfo.h" line="76"/>
<source>Status</source>
<extracomment>The name of the StateType ({6ec71e40-0796-11ef-a8f8-a33a504a66bb}) of ThingClass pdu8p01</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop_Qt5-Debug/logilink/plugininfo.h" line="79"/>
<source>Temperature</source>
<extracomment>The name of the StateType ({c0ce082a-06b5-11ef-b71a-7f0e8b9a48c0}) of ThingClass pdu8p01</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop_Qt5-Debug/logilink/plugininfo.h" line="82"/>
<source>Total load</source>
<extracomment>The name of the StateType ({413af8c0-0796-11ef-bd2b-4fde50f95418}) of ThingClass pdu8p01</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

View File

@ -37,6 +37,7 @@ PLUGIN_DIRS = \
kodi \
lgsmarttv \
lifx \
logilink \
mecelectronics \
meross \
mailnotification \