diff --git a/debian/control b/debian/control
index 49ef4ffd..3e08f32e 100644
--- a/debian/control
+++ b/debian/control
@@ -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},
diff --git a/debian/nymea-plugin-logilink.install.in b/debian/nymea-plugin-logilink.install.in
new file mode 100644
index 00000000..fcf460cb
--- /dev/null
+++ b/debian/nymea-plugin-logilink.install.in
@@ -0,0 +1,2 @@
+usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginlogilink.so
+logilink/translations/*qm usr/share/nymea/translations/
diff --git a/logilink/README.md b/logilink/README.md
new file mode 100644
index 00000000..00531636
--- /dev/null
+++ b/logilink/README.md
@@ -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.
diff --git a/logilink/integrationpluginlogilink.cpp b/logilink/integrationpluginlogilink.cpp
new file mode 100644
index 00000000..c5058ada
--- /dev/null
+++ b/logilink/integrationpluginlogilink.cpp
@@ -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 .
+*
+* 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
+#include
+#include
+#include
+#include
+
+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();
+ }
+ }
+ }
+ });
+}
diff --git a/logilink/integrationpluginlogilink.h b/logilink/integrationpluginlogilink.h
new file mode 100644
index 00000000..5d77ad79
--- /dev/null
+++ b/logilink/integrationpluginlogilink.h
@@ -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 .
+*
+* 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
+
+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
diff --git a/logilink/integrationpluginlogilink.json b/logilink/integrationpluginlogilink.json
new file mode 100644
index 00000000..1a611e3a
--- /dev/null
+++ b/logilink/integrationpluginlogilink.json
@@ -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"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/logilink/logilink.png b/logilink/logilink.png
new file mode 100644
index 00000000..24d65dd6
Binary files /dev/null and b/logilink/logilink.png differ
diff --git a/logilink/logilink.pro b/logilink/logilink.pro
new file mode 100644
index 00000000..41fb471d
--- /dev/null
+++ b/logilink/logilink.pro
@@ -0,0 +1,11 @@
+include(../plugins.pri)
+
+QT += network xml
+
+TARGET = $$qtLibraryTarget(nymea_integrationpluginlogilink)
+
+SOURCES += \
+ integrationpluginlogilink.cpp \
+
+HEADERS += \
+ integrationpluginlogilink.h \
diff --git a/logilink/meta.json b/logilink/meta.json
new file mode 100644
index 00000000..0d1be361
--- /dev/null
+++ b/logilink/meta.json
@@ -0,0 +1,14 @@
+{
+ "title": "Logilink",
+ "tagline": "Integrates Logilink with nymea.",
+ "icon": "logilink.png",
+ "stability": "consumer",
+ "offline": true,
+ "technologies": [
+ "network"
+ ],
+ "categories": [
+ "sensor",
+ "socket"
+ ]
+}
diff --git a/logilink/translations/3e80496e-06b5-11ef-8f86-2be8f966eb8d-en_US.ts b/logilink/translations/3e80496e-06b5-11ef-8f86-2be8f966eb8d-en_US.ts
new file mode 100644
index 00000000..c077f8ca
--- /dev/null
+++ b/logilink/translations/3e80496e-06b5-11ef-8f86-2be8f966eb8d-en_US.ts
@@ -0,0 +1,111 @@
+
+
+
+
+ IntegrationPluginLogilink
+
+
+ Please enter the login credentials for your device.
+
+
+
+
+ Wrong username or password.
+
+
+
+
+ Wrong username or password
+
+
+
+
+ Device not found
+
+
+
+
+ logilink
+
+
+
+ Connected
+ 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
+
+
+
+
+ Humidity
+ The name of the StateType ({1aaaf3ca-0793-11ef-8b3e-f7eff48006c2}) of ThingClass pdu8p01
+
+
+
+
+ IPv4 address
+ The name of the ParamType (ThingClass: pdu8p01, Type: thing, ID: {d2e1a63e-06b5-11ef-bf2d-5f0d67f222af})
+
+
+
+
+
+ Logilink
+ The name of the vendor ({4aed2c3a-06b5-11ef-8a6b-ff770ac74f51})
+----------
+The name of the plugin logilink ({3e80496e-06b5-11ef-8f86-2be8f966eb8d})
+
+
+
+
+ Logilink Socket
+ The name of the ThingClass ({a320bfb6-06b5-11ef-81e4-834aeaf5fe45})
+
+
+
+
+ PDU8P01
+ The name of the ThingClass ({d8162d6e-06b5-11ef-888f-13a1e6eca98c})
+
+
+
+
+
+ Power
+ 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
+
+
+
+
+ Set power
+ The name of the ActionType ({47329958-c33f-478f-b2a0-910abd150da8}) of ThingClass socket
+
+
+
+
+ Socket number
+ The name of the ParamType (ThingClass: socket, Type: thing, ID: {b329edba-06b5-11ef-b04e-d3622c766dd0})
+
+
+
+
+ Status
+ The name of the StateType ({6ec71e40-0796-11ef-a8f8-a33a504a66bb}) of ThingClass pdu8p01
+
+
+
+
+ Temperature
+ The name of the StateType ({c0ce082a-06b5-11ef-b71a-7f0e8b9a48c0}) of ThingClass pdu8p01
+
+
+
+
+ Total load
+ The name of the StateType ({413af8c0-0796-11ef-bd2b-4fde50f95418}) of ThingClass pdu8p01
+
+
+
+
diff --git a/nymea-plugins.pro b/nymea-plugins.pro
index 6e9da803..9f4b53b0 100644
--- a/nymea-plugins.pro
+++ b/nymea-plugins.pro
@@ -37,6 +37,7 @@ PLUGIN_DIRS = \
kodi \
lgsmarttv \
lifx \
+ logilink \
mecelectronics \
meross \
mailnotification \