diff --git a/dweetio/deviceplugindweetio.cpp b/dweetio/deviceplugindweetio.cpp new file mode 100644 index 00000000..4f01476d --- /dev/null +++ b/dweetio/deviceplugindweetio.cpp @@ -0,0 +1,265 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2018 Bernhard Trinnes * + * * + * 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 "deviceplugindweetio.h" +#include "plugininfo.h" +#include "hardwaremanager.h" +#include "network/networkaccessmanager.h" + +#include +#include +#include +#include +#include + +DevicePluginDweetio::DevicePluginDweetio() +{ + +} + +DevicePluginDweetio::~DevicePluginDweetio() +{ + hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); +} + +void DevicePluginDweetio::init() +{ + m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(60); + connect(m_pluginTimer, &PluginTimer::timeout, this, &DevicePluginDweetio::onPluginTimer); +} + +DeviceManager::DeviceSetupStatus DevicePluginDweetio::setupDevice(Device *device) +{ + + if (device->deviceClassId() == postDeviceClassId) { + + QString thing = device->paramValue(postThingParamTypeId).toString(); + if (thing.isEmpty()){ + qDebug(dcDweetio) << "No thing name given, creating one"; + thing = QUuid::createUuid().toString(); + } + return DeviceManager::DeviceSetupStatusSuccess; + } else if (device->deviceClassId() == getDeviceClassId) { + + return DeviceManager::DeviceSetupStatusSuccess; + } + return DeviceManager::DeviceSetupStatusFailure; +} + +void DevicePluginDweetio::postSetupDevice(Device *device) +{ + if (device->deviceClassId() == getDeviceClassId){ + getRequest(device); + } +} + +void DevicePluginDweetio::deviceRemoved(Device *device) +{ + if (device->deviceClassId() == getDeviceClassId) { + foreach(QNetworkReply *reply, m_getReplies.keys()){ + if (m_getReplies.value(reply) == device) { + m_getReplies.remove(reply); + } + } + } + + if (device->deviceClassId() == postDeviceClassId) { + foreach(QNetworkReply *reply, m_postReplies.keys()){ + if (m_postReplies.value(reply) == device) { + m_postReplies.remove(reply); + } + } + } +} + +DeviceManager::DeviceError DevicePluginDweetio::executeAction(Device *device, const Action &action) +{ + qCDebug(dcDweetio) << "Execute action" << device->id() << action.id() << action.params(); + + if (device->deviceClassId() == postDeviceClassId) { + + if (action.actionTypeId() == postContentDataActionTypeId) { + + QString content = action.param(postContentDataAreaParamTypeId).value().toString(); + postContent(content, device, action); + + return DeviceManager::DeviceErrorAsync; + } + return DeviceManager::DeviceErrorActionTypeNotFound; + } + return DeviceManager::DeviceErrorDeviceClassNotFound; +} + +void DevicePluginDweetio::postContent(const QString &content, Device *device, const Action &action) +{ + QUrl url = QString("https://dweet.io:443/dweet/for/") + device->paramValue(postThingParamTypeId).toString(); + QNetworkRequest request(url); + request.setRawHeader("Content-Type", QString("application/json").toLocal8Bit()); + request.setRawHeader("Accept", QString("application/json").toLocal8Bit()); + request.setSslConfiguration(QSslConfiguration::defaultConfiguration()); + + QString key = device->paramValue(postKeyParamTypeId).toString(); + if (!(key.isEmpty()) || !(key == "none")) { + QUrlQuery query; + query.addQueryItem("key", key); + url.setQuery(query); + } + + QVariantMap contentMap; + contentMap.insert(device->paramValue(postContentNameParamTypeId).toString(), content); + + QByteArray data = QJsonDocument::fromVariant(contentMap).toJson(QJsonDocument::Compact); + qDebug(dcDweetio) << "Dweet: " << data << "Url: " << url; + + QNetworkReply *reply = hardwareManager()->networkManager()->post(request, data); + m_asyncActions.insert(reply, action.id()); + connect(reply, &QNetworkReply::finished, this, &DevicePluginDweetio::onNetworkReplyFinished); + m_postReplies.insert(reply, device); + +} + +void DevicePluginDweetio::setConnectionStatus(bool status, Device *device) +{ + // Set connection status + device->setStateValue(postConnectedStateTypeId, status); +} + +void DevicePluginDweetio::processPostReply(const QVariantMap &data, Device *device) +{ + QString message = data.value("this").toString(); + qDebug(dcDweetio) << "Access Reply: " << message << "Device: " << device->name(); +} + +void DevicePluginDweetio::processGetReply(const QVariantMap &data, Device *device) +{ + QVariantList withList = data.value("with").toList(); + QVariantMap contentMap = withList.first().toMap().value("content").toMap(); + QString content = contentMap.value(device->paramValue(getContentNameParamTypeId).toString()).toString(); + device->setStateValue(getContentStateTypeId, content); + + qDebug(dcDweetio) << "Data: " << data << "Device: " << device->name(); +} + +void DevicePluginDweetio::onNetworkReplyFinished() +{ + QNetworkReply *reply = static_cast(sender()); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (m_postReplies.contains(reply)) { + Device *device = m_postReplies.value(reply); + m_postReplies.remove(reply); + ActionId actionId = m_asyncActions.value(reply); + + // check HTTP status code + if ((status != 200) && (status != 204)) { + qCWarning(dcDweetio) << "Update reply HTTP error:" << status << reply->errorString() << reply->readAll(); + emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorMissingParameter); + setConnectionStatus(false, device); + reply->deleteLater(); + return; + } + + emit actionExecutionFinished(actionId, DeviceManager::DeviceErrorNoError); + setConnectionStatus(true, device); + if (status == 204){ + reply->deleteLater(); + return; + } + + // check JSON file + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll(), &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcDweetio) << "Update reply JSON error:" << error.errorString(); + + reply->deleteLater(); + return; + } + + processPostReply(jsonDoc.toVariant().toMap(), device); + + } else if (m_getReplies.contains(reply)) { + + Device *device = m_getReplies.value(reply); + m_getReplies.remove(reply); + + // check HTTP status code + if ((status != 200) && (status != 204)) { + qCWarning(dcDweetio) << "Update reply HTTP error:" << status << reply->errorString() << reply->readAll(); + setConnectionStatus(false, device); + reply->deleteLater(); + return; + } + + setConnectionStatus(true, device); + if (status == 204){ + reply->deleteLater(); + return; + } + + // check JSON file + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll(), &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcDweetio) << "Update reply JSON error:" << error.errorString(); + + reply->deleteLater(); + return; + } + + processGetReply(jsonDoc.toVariant().toMap(), device); + } +} + +void DevicePluginDweetio::getRequest(Device *device) +{ + qCDebug(dcDweetio()) << "Refresh data for" << device->name(); + + QUrl url = QString("https://dweet.io:443/get/latest/dweet/for/") + device->paramValue(postThingParamTypeId).toString(); + QNetworkRequest request(url); + request.setRawHeader("Content-Type", QString("application/json").toLocal8Bit()); + request.setRawHeader("Accept", QString("application/json").toLocal8Bit()); + request.setSslConfiguration(QSslConfiguration::defaultConfiguration()); + + QString key = device->paramValue(getKeyParamTypeId).toString(); + if (!(key.isEmpty()) || !(key == "none")) { + QUrlQuery query; + query.addQueryItem("key", key); + url.setQuery(query); + } + + QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(url)); + connect(reply, &QNetworkReply::finished, this, &DevicePluginDweetio::onNetworkReplyFinished); + m_getReplies.insert(reply, device); +} + + +void DevicePluginDweetio::onPluginTimer() +{ + foreach (Device *device, myDevices()) { + if (device->deviceClassId() == getDeviceClassId) { + getRequest(device); + } + } +} diff --git a/dweetio/deviceplugindweetio.h b/dweetio/deviceplugindweetio.h new file mode 100644 index 00000000..21a1f942 --- /dev/null +++ b/dweetio/deviceplugindweetio.h @@ -0,0 +1,72 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2018 Bernhard Trinnes * + * * + * 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 DEVICEPLUGINDWEETIO_H +#define DEVICEPLUGINDWEETIO_H + +#include "devicemanager.h" +#include "plugin/deviceplugin.h" + +#include +#include +#include +#include +#include + +#include "plugintimer.h" + +class DevicePluginDweetio : public DevicePlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "deviceplugindweetio.json") + Q_INTERFACES(DevicePlugin) + +public: + explicit DevicePluginDweetio(); + ~DevicePluginDweetio(); + + void init() override; + DeviceManager::DeviceSetupStatus setupDevice(Device *device) override; + void deviceRemoved(Device *device) override; + DeviceManager::DeviceError executeAction(Device *device, const Action &action) override; + void postSetupDevice(Device *device) override; + +private: + PluginTimer *m_pluginTimer = nullptr; + QHash m_postReplies; + QHash m_getReplies; + + QHash m_asyncActions; + + void postContent(const QString &content, Device *device, const Action &action); + void processPostReply(const QVariantMap &data, Device *device); + void processGetReply(const QVariantMap &data, Device *device); + void setConnectionStatus(bool status, Device *device); + + void getRequest(Device *device); +private slots: + void onNetworkReplyFinished(); + void onPluginTimer(); +}; + +#endif // DEVICEPLUGINDWEETIO_H diff --git a/dweetio/deviceplugindweetio.json b/dweetio/deviceplugindweetio.json new file mode 100644 index 00000000..97ec5535 --- /dev/null +++ b/dweetio/deviceplugindweetio.json @@ -0,0 +1,130 @@ +{ + "displayName": "Dweetio", + "name": "Dweetio", + "id": "9338f22c-df47-414e-a33c-9b6d40c953c9", + "vendors": [ + { + "id": "23ff381d-c580-4ba1-85bd-30598f893a43", + "displayName": "Dweetio", + "name": "dweetio", + "deviceClasses": [ + { + "id": "5036030e-adc7-403b-b9b6-a27a4a5afea8", + "name": "post", + "displayName": "Post", + "createMethods": ["user"], + "criticalStateTypeId": "4d1790bf-28c6-4c1f-8892-ba1a0ef140f5", + "deviceIcon": "Network", + "interfaces": [], + "basicTags": [ + "Service" + ], + "paramTypes": [ + { + "id": "b50b3682-fb2e-4185-b7aa-31741648d9bb", + "name": "thing", + "displayName": "Thing", + "type" : "QString", + "inputType": "TextLine" + }, + { + "id": "892b7323-ee85-4520-9160-f206f11b344e", + "name": "key", + "displayName": "key", + "defaultValue": "none", + "type" : "QString", + "inputType": "TextLine" + }, + { + "id": "f2b2c3ae-a915-4dd9-a9dd-9545d9dd3d4e", + "name": "contentName", + "displayName": "Content Name", + "type": "QString", + "inputType": "TextLine" + } + ], + "stateTypes": [ + { + "id": "4d1790bf-28c6-4c1f-8892-ba1a0ef140f5", + "name": "connected", + "displayName": "connected", + "displayNameEvent": "connected changed", + "defaultValue": false, + "type": "bool" + } + ], + "actionTypes": [ + { + "id": "76472328-dbaa-458f-90c9-0b46968af511", + "name": "contentData", + "displayName": "Send Content", + "paramTypes": [ + { + "id": "13760d36-26eb-40b5-85b3-450ca7937666", + "name": "contentDataArea", + "displayName": "Content", + "type": "QString", + "inputType": "TextArea" + } + ] + } + ] + }, + { + "id": "8318f86c-061a-450f-978d-f42e3513ca8c", + "name": "get", + "displayName": "Get", + "createMethods": ["user"], + "criticalStateTypeId": "4d1790bf-28c6-4c1f-8892-ba1a0ef140f5", + "deviceIcon": "Network", + "interfaces": [], + "basicTags": [ + "Service" + ], + "paramTypes": [ + { + "id": "b50b3682-fb2e-4185-b7aa-31741648d9bb", + "name": "thing", + "displayName": "Thing", + "type" : "QString", + "inputType": "TextLine" + }, + { + "id": "892b7323-ee85-4520-9160-f206f11b344e", + "name": "key", + "displayName": "Key", + "type" : "QString", + "defaultValue": "none", + "inputType": "TextLine" + }, + { + "id": "f2b2c3ae-a915-4dd9-a9dd-9545d9dd3d4e", + "name": "contentName", + "displayName": "Content name", + "type": "QString", + "inputType": "TextLine" + } + ], + "stateTypes": [ + { + "id": "4d1790bf-28c6-4c1f-8892-ba1a0ef140f5", + "name": "connected", + "displayName": "connected", + "displayNameEvent": "connected changed", + "defaultValue": false, + "type": "bool" + }, + { + "id": "53dfe2bc-792d-420e-bc1f-2890ad715b27", + "name": "content", + "displayName": "Content", + "displayNameEvent": "content changed", + "defaultValue": "", + "type": "QString" + } + ] + } + ] + } + ] +} diff --git a/dweetio/dweetio.pro b/dweetio/dweetio.pro new file mode 100644 index 00000000..63a7cd5c --- /dev/null +++ b/dweetio/dweetio.pro @@ -0,0 +1,9 @@ +include(../plugins.pri) + +TARGET = $$qtLibraryTarget(nymea_deviceplugindweetio) + +SOURCES += \ + deviceplugindweetio.cpp \ + +HEADERS += \ + deviceplugindweetio.h \ diff --git a/nymea-plugins.pro b/nymea-plugins.pro index b40ea038..55792e9b 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -36,6 +36,7 @@ PLUGIN_DIRS = \ simulation \ keba \ remotessh \ + dweetio \ CONFIG+=all