diff --git a/debian/control b/debian/control index d181a514..76c4b5a4 100644 --- a/debian/control +++ b/debian/control @@ -506,7 +506,21 @@ Description: nymea.io plugin for TCP commander With the powerful rule engine you are able to connect any device available in the system and create individual scenes and behaviors for your environment. . - This package will install the nymea.io plugin for TCP commander + This package will install the nymea.io plugin for the TCP commander + +Package: nymea-plugin-httpcommander +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends}, + nymea-plugins-translations, +Description: nymea.io plugin for HTTP commander + The nymea daemon is a plugin based IoT (Internet of Things) server. The + server works like a translator for devices, things and services and + allows them to interact. + With the powerful rule engine you are able to connect any device available + in the system and create individual scenes and behaviors for your environment. + . + This package will install the nymea.io plugin for the HTPP commander Package: nymea-plugin-simulation @@ -589,7 +603,6 @@ Description: nymea.io plugin for keba This package will install the nymea.io plugin for keba - Package: nymea-plugins-translations Section: misc Architecture: all @@ -605,7 +618,6 @@ Description: Translation files for nymea plugins - translations This package provides the translation files for all nymea plugins. - Package: nymea-plugins Section: libs Architecture: all @@ -644,6 +656,7 @@ Architecture: all Depends: nymea-plugin-commandlauncher, nymea-plugin-udpcommander, nymea-plugin-tcpcommander, + nymea-plugin-httpcommander, nymea-plugin-genericelements, nymea-plugin-avahimonitor, nymea-plugin-gpio, diff --git a/debian/nymea-plugin-httpcommander.install.in b/debian/nymea-plugin-httpcommander.install.in new file mode 100644 index 00000000..74e4374a --- /dev/null +++ b/debian/nymea-plugin-httpcommander.install.in @@ -0,0 +1 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_devicepluginhttpcommander.so diff --git a/httpcommander/devicepluginhttpcommander.cpp b/httpcommander/devicepluginhttpcommander.cpp new file mode 100644 index 00000000..cd5327a9 --- /dev/null +++ b/httpcommander/devicepluginhttpcommander.cpp @@ -0,0 +1,255 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2017 Bernhard Trinnes * + * Copyright (C) 2018 Simon Stürz * + * * + * 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 * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "devicepluginhttpcommander.h" +#include "network/networkaccessmanager.h" +#include "plugininfo.h" + + +DevicePluginHttpCommander::DevicePluginHttpCommander() +{ +} + +DevicePluginHttpCommander::~DevicePluginHttpCommander() +{ + hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); +} + +void DevicePluginHttpCommander::init() +{ + m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(10); + connect(m_pluginTimer, &PluginTimer::timeout, this, &DevicePluginHttpCommander::onPluginTimer); +} + +DeviceManager::DeviceSetupStatus DevicePluginHttpCommander::setupDevice(Device *device) +{ + qDebug(dcHttpCommander()) << "Setup device" << device->name() << device->params(); + + // Get + if (device->deviceClassId() == httpGetCommanderDeviceClassId) { + QUrl url = device->paramValue(httpGetCommanderUrlParamTypeId).toUrl(); + if (!url.isValid()) { + qDebug(dcHttpCommander()) << "Given URL is not valid"; + return DeviceManager::DeviceSetupStatusFailure; + } + + return DeviceManager::DeviceSetupStatusSuccess; + } + + // Put + if (device->deviceClassId() == httpPutCommanderDeviceClassId) { + QUrl url = device->paramValue(httpPutCommanderUrlParamTypeId).toUrl(); + if (!url.isValid()) { + qDebug(dcHttpCommander()) << "Given URL is not valid"; + return DeviceManager::DeviceSetupStatusFailure; + } + + return DeviceManager::DeviceSetupStatusSuccess; + } + + // Post + if (device->deviceClassId() == httpPostCommanderDeviceClassId) { + QUrl url = device->paramValue(httpPostCommanderUrlParamTypeId).toUrl(); + if (!url.isValid()) { + qDebug(dcHttpCommander()) << "Given URL is not valid"; + return DeviceManager::DeviceSetupStatusFailure; + } + + return DeviceManager::DeviceSetupStatusSuccess; + } + + return DeviceManager::DeviceSetupStatusFailure; +} + + +void DevicePluginHttpCommander::postSetupDevice(Device *device) +{ + if (device->deviceClassId() == httpGetCommanderDeviceClassId) { + makeGetCall(device); + } + + if (device->deviceClassId() == httpPostCommanderDeviceClassId) { + //TODO find a way to check it the URL is reachable + device->setStateValue(httpPostCommanderConnectedStateTypeId, true); + } + + if (device->deviceClassId() == httpPutCommanderDeviceClassId) { + //TODO find a way to check it the URL is reachable + device->setStateValue(httpPutCommanderConnectedStateTypeId, true); + } +} + + +DeviceManager::DeviceError DevicePluginHttpCommander::executeAction(Device *device, const Action &action) +{ + if (device->deviceClassId() == httpPostCommanderDeviceClassId) { + + if (action.actionTypeId() == httpPostCommanderPostActionTypeId) { + QUrl url = device->paramValue(httpPostCommanderUrlParamTypeId).toUrl(); + url.setPort(device->paramValue(httpPostCommanderPortParamTypeId).toInt()); + QByteArray payload = action.param(httpPostCommanderDataParamTypeId).value().toByteArray(); + + QNetworkReply *reply = hardwareManager()->networkManager()->post(QNetworkRequest(url), payload); + connect(reply, &QNetworkReply::finished, this, &DevicePluginHttpCommander::onPostRequestFinished); + + m_httpRequests.insert(reply, device); + + return DeviceManager::DeviceErrorNoError; + } + return DeviceManager::DeviceErrorActionTypeNotFound; + + } + + if (device->deviceClassId() == httpPutCommanderDeviceClassId) { + + // check if this is the "press" action + if (action.actionTypeId() == httpPutCommanderPutActionTypeId) { + + QUrl url = device->paramValue(httpPutCommanderUrlParamTypeId).toUrl(); + url.setPort(device->paramValue(httpPutCommanderPortParamTypeId).toInt()); + QByteArray payload = action.param(httpPutCommanderDataParamTypeId).value().toByteArray(); + QNetworkReply *reply = hardwareManager()->networkManager()->put(QNetworkRequest(url), payload); + connect(reply, &QNetworkReply::finished, this, &DevicePluginHttpCommander::onPutRequestFinished); + + m_httpRequests.insert(reply, device); + + return DeviceManager::DeviceErrorNoError; + } + } + return DeviceManager::DeviceErrorDeviceClassNotFound; +} + +void DevicePluginHttpCommander::makeGetCall(Device *device) +{ + QUrl url = device->paramValue(httpGetCommanderUrlParamTypeId).toUrl(); + url.setPort(device->paramValue(httpGetCommanderPortParamTypeId).toInt()); + QNetworkRequest request; + request.setUrl(url); + request.setRawHeader("User-Agent", "guhIO 1.0"); + + QNetworkReply *reply = hardwareManager()->networkManager()->get(request); + connect(reply, &QNetworkReply::finished, this, &DevicePluginHttpCommander::onGetRequestFinished); + + m_httpRequests.insert(reply, device); +} + +void DevicePluginHttpCommander::onPluginTimer() +{ + foreach (Device *device, myDevices()) { + if (device->deviceClassId() == httpGetCommanderDeviceClassId) { + makeGetCall(device); + } + } +} + +void DevicePluginHttpCommander::onGetRequestFinished() +{ + QNetworkReply *reply = static_cast(sender()); + qDebug(dcHttpCommander()) << "GET reply finished"; + QByteArray data = reply->readAll(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (!m_httpRequests.contains(reply)) { + reply->deleteLater(); + return; + } + + Device *device = m_httpRequests.take(reply); + device->setStateValue(httpGetCommanderResponseStateTypeId, data); + + // Check HTTP status code + if (status != 200 || reply->error() != QNetworkReply::NoError) { + qCWarning(dcHttpCommander()) << "Request error:" << status << reply->errorString(); + device->setStateValue(httpGetCommanderConnectedStateTypeId, false); + reply->deleteLater(); + return; + } + + device->setStateValue(httpGetCommanderConnectedStateTypeId, true); + reply->deleteLater(); +} + +void DevicePluginHttpCommander::onPostRequestFinished() +{ + QNetworkReply *reply = static_cast(sender()); + qDebug(dcHttpCommander()) << "POST reply finished"; + QByteArray data = reply->readAll(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (!m_httpRequests.contains(reply)) { + reply->deleteLater(); + return; + } + + Device *device = m_httpRequests.take(reply); + device->setStateValue(httpPostCommanderResponseStateTypeId, data); + + // Check HTTP status code + if (status != 200 || reply->error() != QNetworkReply::NoError) { + qCWarning(dcHttpCommander()) << "Request error:" << status << reply->errorString(); + device->setStateValue(httpPostCommanderConnectedStateTypeId, false); + reply->deleteLater(); + return; + } + + device->setStateValue(httpPostCommanderConnectedStateTypeId, true); + reply->deleteLater(); +} + +void DevicePluginHttpCommander::onPutRequestFinished() +{ + QNetworkReply *reply = static_cast(sender()); + qDebug(dcHttpCommander()) << "PUT reply finished"; + QByteArray data = reply->readAll(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (!m_httpRequests.contains(reply)) { + reply->deleteLater(); + return; + } + + Device *device = m_httpRequests.take(reply); + device->setStateValue(httpPutCommanderResponseStateTypeId, data); + + // Check HTTP status code + if (status != 200 || reply->error() != QNetworkReply::NoError) { + qCWarning(dcHttpCommander()) << "Request error:" << status << reply->errorString(); + device->setStateValue(httpPutCommanderConnectedStateTypeId, false); + reply->deleteLater(); + return; + } + + device->setStateValue(httpPutCommanderConnectedStateTypeId, true); + reply->deleteLater(); +} + + +void DevicePluginHttpCommander::deviceRemoved(Device *device) +{ + if (m_httpRequests.values().contains(device)) { + QNetworkReply *reply = m_httpRequests.key(device); + m_httpRequests.remove(reply); + // Note: will be deleted once finished + } +} + diff --git a/httpcommander/devicepluginhttpcommander.h b/httpcommander/devicepluginhttpcommander.h new file mode 100644 index 00000000..26023ed9 --- /dev/null +++ b/httpcommander/devicepluginhttpcommander.h @@ -0,0 +1,64 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2017 Bernhard Trinnes * + * Copyright (C) 2018 Simon Stürz * + * * + * 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 * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef DEVICEPLUGINHTTPCOMMANDER_H +#define DEVICEPLUGINHTTPCOMMANDER_H + +#include "plugin/deviceplugin.h" +#include "devicemanager.h" +#include "plugintimer.h" + +#include + +class DevicePluginHttpCommander : public DevicePlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "devicepluginhttpcommander.json") + Q_INTERFACES(DevicePlugin) + +public: + explicit DevicePluginHttpCommander(); + ~DevicePluginHttpCommander(); + + void init() override; + DeviceManager::DeviceSetupStatus setupDevice(Device *device) override; + void postSetupDevice(Device *device) override; + void deviceRemoved(Device *device) override; + DeviceManager::DeviceError executeAction(Device *device, const Action &action) override; + +private: + PluginTimer *m_pluginTimer = nullptr; + QHash m_httpRequests; + + void makeGetCall(Device *device); + +private slots: + void onPluginTimer(); + + void onGetRequestFinished(); + void onPostRequestFinished(); + void onPutRequestFinished(); +}; + +#endif // DEVICEPLUGINHTTPCOMMANDER_H diff --git a/httpcommander/devicepluginhttpcommander.json b/httpcommander/devicepluginhttpcommander.json new file mode 100644 index 00000000..121f70d3 --- /dev/null +++ b/httpcommander/devicepluginhttpcommander.json @@ -0,0 +1,185 @@ +{ + "name": "HttpCommander", + "displayName": "Http Commander", + "id": "4e62670c-6268-4487-8dff-cccca498731a", + "vendors": [ + { + "name": "httpCommander", + "displayName": "HTTP commander", + "id": "45d7c941-7690-43c9-92fc-fab36e1cebd0", + "deviceClasses": [ + { + "id": "b101abdf-86fd-4d2e-a657-ee76044235bd", + "name": "httpPostCommander", + "displayName": "HTTP post commander", + "deviceIcon": "Network", + "createMethods": ["user"], + "basicTags": [ + "Service" + ], + "interfaces": ["connectable"], + "paramTypes": [ + { + "id": "020f672e-cc9a-4b74-92dd-a92a93ab1d23", + "name": "url", + "displayName": "Address", + "type": "QString", + "inputType": "None", + "defaultValue": "https://nymea.io" + }, + { + "id": "37830ea8-2249-46e6-aaca-12164928a81a", + "name": "port", + "displayName": "Port", + "type": "int", + "defaultValue": "443" + } + ], + "stateTypes": [ + { + "id": "8daac0e7-4c2f-4cdf-b528-02cfe04c6b39", + "name": "connected", + "displayName": "Reachable", + "displayNameEvent": "Reachability changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "69f32ec8-114d-43f4-9241-1f6a57261f32", + "name": "response", + "displayName": "response", + "displayNameEvent": "Response received", + "type": "QString", + "defaultValue": "" + } + ], + "actionTypes": [ + { + "id": "5a97ca56-b334-411b-adba-116496ffe83d", + "name": "post", + "displayName": "Post data", + "paramTypes": [ + { + "id": "363119a3-c02c-4ed5-a915-11706198f3eb", + "name": "data", + "displayName": "Data", + "type": "QString", + "defaultValue": "" + } + ] + } + ] + }, + { + "id": "05bf65f5-ff13-43e3-b6ae-77019e79d8a1", + "name": "httpPutCommander", + "displayName": "HTTP put commander", + "deviceIcon": "Network", + "createMethods": ["user"], + "interfaces": ["connectable"], + "basicTags": [ + "Service" + ], + "paramTypes": [ + { + "id": "1a3fcb23-931b-4ba1-b134-c49b656c76f7", + "name": "url", + "displayName": "Address", + "type": "QString", + "inputType": "None", + "defaultValue": "https://nymea.io" + }, + { + "id": "db994349-1105-4ce5-b6fe-6fd38fbc436a", + "name": "port", + "displayName": "Port", + "type": "int", + "defaultValue": "443" + } + ], + "stateTypes": [ + { + "id": "d102ff86-b773-48e3-a7a5-e138cb541f49", + "name": "connected", + "displayName": "Reachable", + "displayNameEvent": "Reachability changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "69f32ec8-114d-43f4-9241-1f6a57261f32", + "name": "response", + "displayName": "Response", + "displayNameEvent": "Response received", + "type": "QString", + "defaultValue": "" + } + ], + "actionTypes": [ + { + "id": "a9f165dc-cdf1-48f0-b4b6-7c24373cb77c", + "name": "put", + "displayName": "put", + "paramTypes": [ + { + "id": "7742d445-8fc1-4b20-87f2-1bb35929fce1", + "name": "data", + "displayName": "Data", + "type": "QString", + "defaultValue": "" + } + ] + } + ] + }, + { + "id": "8f3f6dde-9db3-4237-800b-bb7f804098c9", + "name": "httpGetCommander", + "displayName": "HTTP get", + "deviceIcon": "Network", + "createMethods": ["user"], + "basicTags": [ + "Service" + ], + "interfaces": ["connectable"], + "paramTypes": [ + { + "id": "477b544b-b631-4526-a4ef-c712ff5f955d", + "name": "url", + "displayName": "URL or IPv4 Address", + "type": "QString", + "inputType": "None", + "defaultValue": "https://nymea.io" + }, + { + "id": "bee8b151-815a-4159-9d8a-42b76e99b42c", + "name": "port", + "displayName": "Port", + "type": "int", + "defaultValue": "443" + } + ], + "stateTypes":[ + { + "id": "0d63f815-efd1-488a-9bfa-e9f6bda540d2", + "name": "connected", + "displayName": "Reachable", + "displayNameEvent": "Reachability changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "d81f0644-b94e-48ed-ae48-1b8ff6cebc0c", + "name": "response", + "displayName": "Response", + "type": "QString", + "defaultValue": "", + "displayNameEvent": "Response data received" + } + ] + } + ] + } + ] +} + diff --git a/httpcommander/httpcommander.pro b/httpcommander/httpcommander.pro new file mode 100644 index 00000000..890aea91 --- /dev/null +++ b/httpcommander/httpcommander.pro @@ -0,0 +1,11 @@ +include(../plugins.pri) + +TARGET = $$qtLibraryTarget(nymea_devicepluginhttpcommander) + +SOURCES += \ + devicepluginhttpcommander.cpp \ + +HEADERS += \ + devicepluginhttpcommander.h \ + + diff --git a/httpcommander/translations/4e62670c-6268-4487-8dff-cccca498731a-en_US.ts b/httpcommander/translations/4e62670c-6268-4487-8dff-cccca498731a-en_US.ts new file mode 100644 index 00000000..f7f66d85 --- /dev/null +++ b/httpcommander/translations/4e62670c-6268-4487-8dff-cccca498731a-en_US.ts @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/nymea-plugins.pro b/nymea-plugins.pro index fcb08542..6fbd70a7 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -19,6 +19,7 @@ PLUGIN_DIRS = \ leynew \ udpcommander \ tcpcommander \ + httpcommander \ kodi \ elgato \ senic \