add a generic MQTT client plugin

master
Michael Zanetti 2018-11-27 23:30:09 +01:00
parent 3ca44f103f
commit 7ee77fbc92
6 changed files with 470 additions and 0 deletions

195
mqtt/devicepluginmqtt.cpp Normal file
View 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
View 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
View 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
View File

@ -0,0 +1,13 @@
include(../plugins.pri)
QT += network
TARGET = $$qtLibraryTarget(nymea_devicepluginmqtt)
SOURCES += \
devicepluginmqtt.cpp
HEADERS += \
devicepluginmqtt.h

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
</TS>

View File

@ -21,6 +21,7 @@ PLUGIN_DIRS = \
leynew \
lgsmarttv \
mailnotification \
mqtt \
netatmo \
networkdetector \
openweathermap \