add a generic MQTT client plugin
This commit is contained in:
parent
3ca44f103f
commit
7ee77fbc92
195
mqtt/devicepluginmqtt.cpp
Normal file
195
mqtt/devicepluginmqtt.cpp
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2018 Michael Zanetti <michael.zanetti@nymea.io> *
|
||||||
|
* *
|
||||||
|
* This file is part of nymea. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2.1 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library 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 library; If not, see *
|
||||||
|
* <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\page mqtt.html
|
||||||
|
\title Generic MQTT
|
||||||
|
\brief Plugin for catching UDP commands from the network.
|
||||||
|
|
||||||
|
\ingroup plugins
|
||||||
|
\ingroup nymea-plugins-maker
|
||||||
|
|
||||||
|
This plugin allows to receive UDP packages over a certain UDP port and generates an \l{Event} if the message content matches
|
||||||
|
the \l{Param} command.
|
||||||
|
|
||||||
|
\note This plugin is ment to be combined with a \l{nymeaserver::Rule}.
|
||||||
|
|
||||||
|
\section3 Example
|
||||||
|
|
||||||
|
If you create an UDP Commander on port 2323 and with the command \c{"Light 1 ON"}, following command will trigger an \l{Event} in nymea
|
||||||
|
and allows you to connect this \l{Event} with a \l{nymeaserver::Rule}.
|
||||||
|
|
||||||
|
\note In this example nymea is running on \c localhost
|
||||||
|
|
||||||
|
\code
|
||||||
|
$ echo "Light 1 ON" | nc -u localhost 2323
|
||||||
|
OK
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
This allows you to execute \l{Action}{Actions} in your home automation system when a certain UDP message will be sent to nymea.
|
||||||
|
|
||||||
|
If the command will be recognized from nymea, the sender will receive as answere a \c{"OK"} string.
|
||||||
|
|
||||||
|
\chapter Plugin properties
|
||||||
|
Following JSON file contains the definition and the description of all available \l{DeviceClass}{DeviceClasses}
|
||||||
|
and \l{Vendor}{Vendors} of this \l{DevicePlugin}.
|
||||||
|
|
||||||
|
For more details how to read this JSON file please check out the documentation for \l{The plugin JSON File}.
|
||||||
|
|
||||||
|
\quotefile plugins/deviceplugins/udpcommander/devicepluginudpcommander.json
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "devicepluginmqtt.h"
|
||||||
|
#include "plugin/device.h"
|
||||||
|
#include "plugininfo.h"
|
||||||
|
#include "network/mqtt/mqttprovider.h"
|
||||||
|
|
||||||
|
#include "nymea-mqtt/mqttclient.h"
|
||||||
|
|
||||||
|
DevicePluginMqtt::DevicePluginMqtt()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceManager::DeviceSetupStatus DevicePluginMqtt::setupDevice(Device *device)
|
||||||
|
{
|
||||||
|
MqttClient *client = nullptr;
|
||||||
|
if (device->deviceClassId() == internalMqttClientDeviceClassId) {
|
||||||
|
client = hardwareManager()->mqttProvider()->createInternalClient(device->id());
|
||||||
|
} else if (device->deviceClassId() == mqttClientDeviceClassId){
|
||||||
|
client = new MqttClient("nymea-" + device->id().toString().remove(QRegExp("[{}]")).left(8), this);
|
||||||
|
client->setUsername(device->paramValue(mqttClientDeviceUsernameParamTypeId).toString());
|
||||||
|
client->setPassword(device->paramValue(mqttClientDevicePasswordParamTypeId).toString());
|
||||||
|
client->connectToHost(device->paramValue(mqttClientDeviceServerAddressParamTypeId).toString(), device->paramValue(mqttClientDeviceServerPortParamTypeId).toInt());
|
||||||
|
}
|
||||||
|
m_clients.insert(device, client);
|
||||||
|
|
||||||
|
connect(client, &MqttClient::connected, this, [this, device](){
|
||||||
|
subscribe(device);
|
||||||
|
});
|
||||||
|
connect(client, &MqttClient::subscribed, this, [this, device](quint16 packetId, const Mqtt::SubscribeReturnCodes returnCodes){
|
||||||
|
Q_UNUSED(packetId)
|
||||||
|
emit deviceSetupFinished(device, returnCodes.first() == Mqtt::SubscribeReturnCodeFailure ? DeviceManager::DeviceSetupStatusFailure : DeviceManager::DeviceSetupStatusSuccess);
|
||||||
|
});
|
||||||
|
connect(client, &MqttClient::publishReceived, this, &DevicePluginMqtt::publishReceived);
|
||||||
|
connect(client, &MqttClient::published, this, &DevicePluginMqtt::published);
|
||||||
|
// In case we're already connected, manually call subscribe now
|
||||||
|
if (client->isConnected()) {
|
||||||
|
subscribe(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
return DeviceManager::DeviceSetupStatusAsync;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DeviceManager::DeviceError DevicePluginMqtt::executeAction(Device *device, const Action &action)
|
||||||
|
{
|
||||||
|
ParamTypeId topicParamTypeId = internalMqttClientTriggerActionTopicParamTypeId;
|
||||||
|
ParamTypeId payloadParamTypeId = internalMqttClientTriggerActionDataParamTypeId;
|
||||||
|
ParamTypeId qosParamTypeId = internalMqttClientTriggerActionQosParamTypeId;
|
||||||
|
ParamTypeId retainParamTypeId = internalMqttClientTriggerActionRetainParamTypeId;
|
||||||
|
|
||||||
|
if (device->deviceClassId() == mqttClientDeviceClassId) {
|
||||||
|
topicParamTypeId = mqttClientTriggerActionTopicParamTypeId;
|
||||||
|
payloadParamTypeId = mqttClientTriggerActionDataParamTypeId;
|
||||||
|
qosParamTypeId = mqttClientTriggerActionQosParamTypeId;
|
||||||
|
retainParamTypeId = mqttClientTriggerActionRetainParamTypeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
MqttClient *client = m_clients.value(device);
|
||||||
|
if (!client) {
|
||||||
|
qCWarning(dcMqttclient) << "No valid MQTT client for device" << device->name();
|
||||||
|
return DeviceManager::DeviceErrorDeviceNotFound;
|
||||||
|
}
|
||||||
|
Mqtt::QoS qos = Mqtt::QoS0;
|
||||||
|
switch (action.param(qosParamTypeId).value().toInt()) {
|
||||||
|
case 0:
|
||||||
|
qos = Mqtt::QoS0;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
qos = Mqtt::QoS1;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
qos = Mqtt::QoS2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
quint16 packetId = client->publish(action.param(topicParamTypeId).value().toString(),
|
||||||
|
action.param(payloadParamTypeId).value().toByteArray(),
|
||||||
|
qos,
|
||||||
|
action.param(retainParamTypeId).value().toBool());
|
||||||
|
m_pendingPublishes.insert(packetId, action);
|
||||||
|
|
||||||
|
return DeviceManager::DeviceErrorAsync;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginMqtt::subscribe(Device *device)
|
||||||
|
{
|
||||||
|
MqttClient *client = m_clients.value(device);
|
||||||
|
if (!client) {
|
||||||
|
// Device might have been removed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (device->deviceClassId() == internalMqttClientDeviceClassId) {
|
||||||
|
client->subscribe(device->paramValue(internalMqttClientDeviceTopicFilterParamTypeId).toString());
|
||||||
|
} else {
|
||||||
|
client->subscribe(device->paramValue(mqttClientDeviceTopicFilterParamTypeId).toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginMqtt::publishReceived(const QString &topic, const QByteArray &payload, bool retained)
|
||||||
|
{
|
||||||
|
qCDebug(dcMqttclient()) << "Publish received" << topic << payload << retained;
|
||||||
|
|
||||||
|
MqttClient* client = static_cast<MqttClient*>(sender());
|
||||||
|
Device *device = m_clients.key(client);
|
||||||
|
if (!device) {
|
||||||
|
qCWarning(dcMqttclient) << "Received a publish message from a client where de don't have a matching device";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EventTypeId eventTypeId = internalMqttClientTriggeredEventTypeId;
|
||||||
|
ParamTypeId topicParamTypeId = internalMqttClientTriggeredEventTopicParamTypeId;
|
||||||
|
ParamTypeId payloadParamTypeId = internalMqttClientTriggeredEventDataParamTypeId;
|
||||||
|
|
||||||
|
if (device->deviceClassId() == mqttClientDeviceClassId) {
|
||||||
|
eventTypeId = mqttClientTriggeredEventTypeId;
|
||||||
|
topicParamTypeId = mqttClientTriggeredEventTopicParamTypeId;
|
||||||
|
payloadParamTypeId = mqttClientTriggeredEventDataParamTypeId;
|
||||||
|
}
|
||||||
|
emitEvent(Event(eventTypeId, device->id(), ParamList() << Param(topicParamTypeId, topic) << Param(payloadParamTypeId, payload)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginMqtt::published(quint16 packetId)
|
||||||
|
{
|
||||||
|
if (!m_pendingPublishes.contains(packetId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit actionExecutionFinished(m_pendingPublishes.take(packetId).id(), DeviceManager::DeviceErrorNoError);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginMqtt::deviceRemoved(Device *device)
|
||||||
|
{
|
||||||
|
qCDebug(dcMqttclient) << device;
|
||||||
|
m_clients.take(device)->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
61
mqtt/devicepluginmqtt.h
Normal file
61
mqtt/devicepluginmqtt.h
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2018 Michael Zanetti <michael.zanetti@nymea.io> *
|
||||||
|
* *
|
||||||
|
* This file is part of nymea. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2.1 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library 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 library; If not, see *
|
||||||
|
* <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#ifndef DEVICEPLUGINMQTT_H
|
||||||
|
#define DEVICEPLUGINMQTT_H
|
||||||
|
|
||||||
|
#include "plugin/deviceplugin.h"
|
||||||
|
|
||||||
|
#include <QHash>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QUdpSocket>
|
||||||
|
|
||||||
|
class MqttClient;
|
||||||
|
|
||||||
|
class DevicePluginMqtt: public DevicePlugin
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "devicepluginmqtt.json")
|
||||||
|
Q_INTERFACES(DevicePlugin)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit DevicePluginMqtt();
|
||||||
|
|
||||||
|
DeviceManager::DeviceSetupStatus setupDevice(Device *device) override;
|
||||||
|
void deviceRemoved(Device *device) override;
|
||||||
|
|
||||||
|
DeviceManager::DeviceError executeAction(Device *device, const Action &action) override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void subscribe(Device *device);
|
||||||
|
|
||||||
|
void publishReceived(const QString &topic, const QByteArray &payload, bool retained);
|
||||||
|
void published(quint16 packetId);
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
QHash<Device*, MqttClient*> m_clients;
|
||||||
|
|
||||||
|
QHash<quint16, Action> m_pendingPublishes;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DEVICEPLUGINMQTT_H
|
||||||
196
mqtt/devicepluginmqtt.json
Normal file
196
mqtt/devicepluginmqtt.json
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
{
|
||||||
|
"name": "mqttclient",
|
||||||
|
"displayName": "MQTT client",
|
||||||
|
"id": "27c58205-07c8-4482-85ad-b435387803a5",
|
||||||
|
"vendors": [
|
||||||
|
{
|
||||||
|
"name": "guh",
|
||||||
|
"displayName": "guh GmbH",
|
||||||
|
"id": "2062d64d-3232-433c-88bc-0d33c0ba2ba6",
|
||||||
|
"deviceClasses": [
|
||||||
|
{
|
||||||
|
"id": "19117099-a5ef-44a1-b2bb-2efafe00f197",
|
||||||
|
"name": "internalMqttClient",
|
||||||
|
"displayName": "Internal MQTT client",
|
||||||
|
"interfaces": ["inputtrigger", "outputtrigger"],
|
||||||
|
"createMethods": ["user"],
|
||||||
|
"paramTypes": [
|
||||||
|
{
|
||||||
|
"id": "4e91772a-82d8-498f-8b62-bba90a682e76",
|
||||||
|
"name": "topicFilter",
|
||||||
|
"displayName": "Subscription topic filter",
|
||||||
|
"type": "QString",
|
||||||
|
"defaultValue": "#"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"eventTypes": [
|
||||||
|
{
|
||||||
|
"id": "d4ea2a70-da5a-49e0-9f30-aac1334b6a02",
|
||||||
|
"name": "triggered",
|
||||||
|
"displayName": "Publish received",
|
||||||
|
"paramTypes": [
|
||||||
|
{
|
||||||
|
"id": "27ec8baf-0c13-4d0a-aaee-313582592695",
|
||||||
|
"name": "topic",
|
||||||
|
"displayName": "Topic",
|
||||||
|
"type": "QString"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8af98566-79d9-4e65-b1dc-9067e4f93af1",
|
||||||
|
"name": "data",
|
||||||
|
"displayName": "Playload",
|
||||||
|
"type": "QString"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"actionTypes": [
|
||||||
|
{
|
||||||
|
"id": "2f90ff12-dd67-4ddf-815d-330b4e2d56bf",
|
||||||
|
"name": "trigger",
|
||||||
|
"displayName": "Publish",
|
||||||
|
"paramTypes": [
|
||||||
|
{
|
||||||
|
"id": "bed321c2-a8c4-4420-b831-c4faa8501115",
|
||||||
|
"name": "topic",
|
||||||
|
"displayName": "Topic",
|
||||||
|
"type": "QString",
|
||||||
|
"defaultValue": "/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5bff6492-e6c7-4e50-a1c1-69881250561d",
|
||||||
|
"name": "data",
|
||||||
|
"displayName": "Payload",
|
||||||
|
"type": "QString",
|
||||||
|
"defaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b019b678-aaf1-46d0-a0f8-af2131f14e55",
|
||||||
|
"name": "qos",
|
||||||
|
"displayName": "QoS",
|
||||||
|
"type": "int",
|
||||||
|
"minValue": 0,
|
||||||
|
"maxValue": 2,
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c2c6386e-5b7d-4a2a-a8e8-e9c259ba926b",
|
||||||
|
"name": "retain",
|
||||||
|
"displayName": "Retain message",
|
||||||
|
"type": "bool",
|
||||||
|
"defaultValue": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e325b581-8d7f-446e-b761-67554c5aacd4",
|
||||||
|
"name": "mqttClient",
|
||||||
|
"displayName": "MQTT client",
|
||||||
|
"interfaces": ["inputtrigger", "outputtrigger"],
|
||||||
|
"createMethods": ["user"],
|
||||||
|
"paramTypes": [
|
||||||
|
{
|
||||||
|
"id": "a9a97dd6-9f80-43eb-a956-f5f3e4c6e3e2",
|
||||||
|
"name": "serverAddress",
|
||||||
|
"displayName": "Address",
|
||||||
|
"type": "QString",
|
||||||
|
"defaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "91973ede-b64e-4cae-ae67-6087df79eeb4",
|
||||||
|
"name": "serverPort",
|
||||||
|
"displayName": "Port",
|
||||||
|
"type": "int",
|
||||||
|
"minValue": 0,
|
||||||
|
"maxValue": 65535,
|
||||||
|
"defaultValue": 1883
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ae19fcc2-80ae-4d3f-8bac-4cf0db98d9e7",
|
||||||
|
"name": "username",
|
||||||
|
"displayName": "Username",
|
||||||
|
"type": "QString",
|
||||||
|
"defaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d8211599-52f7-46f6-a741-a7204b987309",
|
||||||
|
"name": "password",
|
||||||
|
"displayName": "Password",
|
||||||
|
"type": "QString",
|
||||||
|
"defaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "53e2715a-e72f-445a-ae6b-2ac4e6031114",
|
||||||
|
"name": "topicFilter",
|
||||||
|
"displayName": "Subscription topic filter",
|
||||||
|
"type": "QString",
|
||||||
|
"defaultValue": "#"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"eventTypes": [
|
||||||
|
{
|
||||||
|
"id": "243ec6ee-a72e-47e0-91dd-b9b918c43072",
|
||||||
|
"name": "triggered",
|
||||||
|
"displayName": "Publish received",
|
||||||
|
"paramTypes": [
|
||||||
|
{
|
||||||
|
"id": "bd83c7ec-3a14-46c6-a064-25757ceb0207",
|
||||||
|
"name": "topic",
|
||||||
|
"displayName": "Topic",
|
||||||
|
"type": "QString"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a947a277-a17a-4cb2-addb-f8ecec1cc63c",
|
||||||
|
"name": "data",
|
||||||
|
"displayName": "Playload",
|
||||||
|
"type": "QString"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"actionTypes": [
|
||||||
|
{
|
||||||
|
"id": "39df4723-c888-4a3f-a151-9408699a9d25",
|
||||||
|
"name": "trigger",
|
||||||
|
"displayName": "Publish",
|
||||||
|
"paramTypes": [
|
||||||
|
{
|
||||||
|
"id": "193655ec-1714-4ea0-b8ee-f1dc312f15d3",
|
||||||
|
"name": "topic",
|
||||||
|
"displayName": "Topic",
|
||||||
|
"type": "QString",
|
||||||
|
"defaultValue": "/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a0e8989b-2797-4447-8d67-408382bfebae",
|
||||||
|
"name": "data",
|
||||||
|
"displayName": "Payload",
|
||||||
|
"type": "QString",
|
||||||
|
"defaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4d2130be-8123-4103-b0bb-43ba876e147f",
|
||||||
|
"name": "qos",
|
||||||
|
"displayName": "QoS",
|
||||||
|
"type": "int",
|
||||||
|
"minValue": 0,
|
||||||
|
"maxValue": 2,
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "097774cc-7947-4eb1-bd30-ec4566afa628",
|
||||||
|
"name": "retain",
|
||||||
|
"displayName": "Retain message",
|
||||||
|
"type": "bool",
|
||||||
|
"defaultValue": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
mqtt/mqtt.pro
Normal file
13
mqtt/mqtt.pro
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
include(../plugins.pri)
|
||||||
|
|
||||||
|
QT += network
|
||||||
|
|
||||||
|
TARGET = $$qtLibraryTarget(nymea_devicepluginmqtt)
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
devicepluginmqtt.cpp
|
||||||
|
|
||||||
|
HEADERS += \
|
||||||
|
devicepluginmqtt.h
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1">
|
||||||
|
</TS>
|
||||||
@ -21,6 +21,7 @@ PLUGIN_DIRS = \
|
|||||||
leynew \
|
leynew \
|
||||||
lgsmarttv \
|
lgsmarttv \
|
||||||
mailnotification \
|
mailnotification \
|
||||||
|
mqtt \
|
||||||
netatmo \
|
netatmo \
|
||||||
networkdetector \
|
networkdetector \
|
||||||
openweathermap \
|
openweathermap \
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user