From 2693b1c8bdcde0f0651cf639409c10ed0f20ce29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 13 Oct 2015 18:41:35 +0200 Subject: [PATCH] add netatmo plugin add OAuth2 --- debian/guh-plugins.install | 1 + doc/typeutils.qdoc | 8 +- guh.pri | 2 +- libguh/libguh.pro | 22 +- libguh/loggingcategories.cpp | 5 +- libguh/loggingcategories.h | 5 +- libguh/network/oauth2.cpp | 265 ++++++++++++++++++ libguh/network/oauth2.h | 99 +++++++ libguh/plugin/deviceplugin.cpp | 2 + libguh/typeutils.h | 1 + .../awattar/devicepluginawattar.cpp | 2 +- plugins/deviceplugins/deviceplugins.pro | 1 + .../elgato/devicepluginelgato.json | 1 + .../netatmo/devicepluginnetatmo.cpp | 218 ++++++++++++++ .../netatmo/devicepluginnetatmo.h | 67 +++++ .../netatmo/devicepluginnetatmo.json | 141 ++++++++++ plugins/deviceplugins/netatmo/netatmo.pro | 13 + .../netatmo/netatmobasestation.cpp | 94 +++++++ .../netatmo/netatmobasestation.h | 72 +++++ server/main.cpp | 5 +- 20 files changed, 1005 insertions(+), 19 deletions(-) create mode 100644 libguh/network/oauth2.cpp create mode 100644 libguh/network/oauth2.h create mode 100644 plugins/deviceplugins/netatmo/devicepluginnetatmo.cpp create mode 100644 plugins/deviceplugins/netatmo/devicepluginnetatmo.h create mode 100644 plugins/deviceplugins/netatmo/devicepluginnetatmo.json create mode 100644 plugins/deviceplugins/netatmo/netatmo.pro create mode 100644 plugins/deviceplugins/netatmo/netatmobasestation.cpp create mode 100644 plugins/deviceplugins/netatmo/netatmobasestation.h diff --git a/debian/guh-plugins.install b/debian/guh-plugins.install index 48751053..fc65aec2 100644 --- a/debian/guh-plugins.install +++ b/debian/guh-plugins.install @@ -20,3 +20,4 @@ usr/lib/guh/plugins/libguh_devicepluginudpcommander.so usr/lib/guh/plugins/libguh_devicepluginkodi.so usr/lib/guh/plugins/libguh_devicepluginelgato.so usr/lib/guh/plugins/libguh_devicepluginawattar.so +usr/lib/guh/plugins/libguh_devicepluginnetatmo.so diff --git a/doc/typeutils.qdoc b/doc/typeutils.qdoc index 9e395d90..c68295f3 100644 --- a/doc/typeutils.qdoc +++ b/doc/typeutils.qdoc @@ -257,12 +257,16 @@ \li 36 \li The value of the \l{Param} has unit \tt {[\%]} \unicode{0x2192} percentage. \row - \li Types::UnitEuro + \li Types::UnitPartsPerMillion \li 37 + \li The value of the \l{Param} has unit \tt {[ppm]} \unicode{0x2192} parts per million. + \row + \li Types::UnitEuro + \li 38 \li The value of the \l{Param} has unit \tt {[€]} \unicode{0x2192} euro. \row \li Types::UnitDollar - \li 38 + \li 39 \li The value of the \l{Param} has unit \tt {[\$]} \unicode{0x2192} dollar. \endtable diff --git a/guh.pri b/guh.pri index 7624c8ae..0d968c11 100644 --- a/guh.pri +++ b/guh.pri @@ -2,7 +2,7 @@ GUH_VERSION_STRING=$$system('dpkg-parsechangelog | sed -n -e "s/^Version: //p"') # define protocol versions -JSON_PROTOCOL_VERSION=31 +JSON_PROTOCOL_VERSION=32 REST_API_VERSION=1 DEFINES += GUH_VERSION_STRING=\\\"$${GUH_VERSION_STRING}\\\" \ diff --git a/libguh/libguh.pro b/libguh/libguh.pro index 7fbec106..0acd8f18 100644 --- a/libguh/libguh.pro +++ b/libguh/libguh.pro @@ -16,11 +16,13 @@ contains(DEFINES, BLUETOOTH_LE) { bluetooth/bluetoothlowenergydevice.h \ } -SOURCES += plugin/device.cpp \ +SOURCES += devicemanager.cpp \ + loggingcategories.cpp \ + guhsettings.cpp \ + plugin/device.cpp \ plugin/deviceclass.cpp \ plugin/deviceplugin.cpp \ plugin/devicedescriptor.cpp \ - devicemanager.cpp \ hardware/gpio.cpp \ hardware/gpiomonitor.cpp \ hardware/radio433/radio433.cpp \ @@ -31,6 +33,8 @@ SOURCES += plugin/device.cpp \ network/upnpdiscovery/upnpdevice.cpp \ network/upnpdiscovery/upnpdevicedescriptor.cpp \ network/upnpdiscovery/upnpdiscoveryrequest.cpp \ + network/networkmanager.cpp \ + network/oauth2.cpp \ types/action.cpp \ types/actiontype.cpp \ types/state.cpp \ @@ -45,14 +49,15 @@ SOURCES += plugin/device.cpp \ types/ruleaction.cpp \ types/ruleactionparam.cpp \ types/statedescriptor.cpp \ - loggingcategories.cpp \ - guhsettings.cpp \ -HEADERS += plugin/device.h \ +HEADERS += devicemanager.h \ + typeutils.h \ + loggingcategories.h \ + guhsettings.h \ + plugin/device.h \ plugin/deviceclass.h \ plugin/deviceplugin.h \ plugin/devicedescriptor.h \ - devicemanager.h \ hardware/gpio.h \ hardware/gpiomonitor.h \ hardware/radio433/radio433.h \ @@ -63,6 +68,8 @@ HEADERS += plugin/device.h \ network/upnpdiscovery/upnpdevice.h \ network/upnpdiscovery/upnpdevicedescriptor.h \ network/upnpdiscovery/upnpdiscoveryrequest.h \ + network/networkmanager.h \ + network/oauth2.h \ types/action.h \ types/actiontype.h \ types/state.h \ @@ -77,9 +84,6 @@ HEADERS += plugin/device.h \ types/ruleaction.h \ types/ruleactionparam.h \ types/statedescriptor.h \ - typeutils.h \ - loggingcategories.h \ - guhsettings.h \ # install files for libguh-dev diff --git a/libguh/loggingcategories.cpp b/libguh/loggingcategories.cpp index f7cc8dcf..781fb941 100644 --- a/libguh/loggingcategories.cpp +++ b/libguh/loggingcategories.cpp @@ -25,9 +25,10 @@ Q_LOGGING_CATEGORY(dcDeviceManager, "DeviceManager") Q_LOGGING_CATEGORY(dcRuleEngine, "RuleEngine") Q_LOGGING_CATEGORY(dcHardware, "Hardware") Q_LOGGING_CATEGORY(dcConnection, "Connection") +Q_LOGGING_CATEGORY(dcLogEngine, "LogEngine") Q_LOGGING_CATEGORY(dcTcpServer, "TcpServer") Q_LOGGING_CATEGORY(dcWebServer, "WebServer") +Q_LOGGING_CATEGORY(dcWebSocketServer, "WebSocketServer") Q_LOGGING_CATEGORY(dcJsonRpc, "JsonRpc") Q_LOGGING_CATEGORY(dcRest, "Rest") -Q_LOGGING_CATEGORY(dcLogEngine, "LogEngine") -Q_LOGGING_CATEGORY(dcWebSocketServer, "WebSocketServer") +Q_LOGGING_CATEGORY(dcOAuth2, "OAuth2") diff --git a/libguh/loggingcategories.h b/libguh/loggingcategories.h index cd740357..5b174b38 100644 --- a/libguh/loggingcategories.h +++ b/libguh/loggingcategories.h @@ -29,11 +29,12 @@ Q_DECLARE_LOGGING_CATEGORY(dcDeviceManager) Q_DECLARE_LOGGING_CATEGORY(dcRuleEngine) Q_DECLARE_LOGGING_CATEGORY(dcHardware) Q_DECLARE_LOGGING_CATEGORY(dcConnection) -Q_DECLARE_LOGGING_CATEGORY(dcJsonRpc) -Q_DECLARE_LOGGING_CATEGORY(dcRest) Q_DECLARE_LOGGING_CATEGORY(dcLogEngine) Q_DECLARE_LOGGING_CATEGORY(dcTcpServer) Q_DECLARE_LOGGING_CATEGORY(dcWebServer) Q_DECLARE_LOGGING_CATEGORY(dcWebSocketServer) +Q_DECLARE_LOGGING_CATEGORY(dcJsonRpc) +Q_DECLARE_LOGGING_CATEGORY(dcRest) +Q_DECLARE_LOGGING_CATEGORY(dcOAuth2) #endif // LOGGINGCATEGORYS_H diff --git a/libguh/network/oauth2.cpp b/libguh/network/oauth2.cpp new file mode 100644 index 00000000..dca3a241 --- /dev/null +++ b/libguh/network/oauth2.cpp @@ -0,0 +1,265 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of guh. * + * * + * Guh is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 2 of the License. * + * * + * Guh 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with guh. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "oauth2.h" +#include "loggingcategories.h" + +#include +#include +#include + +OAuth2::OAuth2(QString clientId, QString clientSecret, QObject *parent) : + QObject(parent), + m_clientId(clientId), + m_clientSecret(clientSecret), + m_authenticated(false) +{ + m_networkManager = new QNetworkAccessManager(this); + connect(m_networkManager, &QNetworkAccessManager::finished, this, &OAuth2::replyFinished); + + m_timer = new QTimer(this); + m_timer->setSingleShot(false); + + connect(m_timer, &QTimer::timeout, this, &OAuth2::refreshTimeout); +} + +QUrl OAuth2::url() const +{ + return m_url; +} + +void OAuth2::setUrl(const QUrl &url) +{ + m_url = url; +} + +QUrlQuery OAuth2::query() const +{ + return m_query; +} + +void OAuth2::setQuery(const QUrlQuery &query) +{ + m_query = query; +} + +QString OAuth2::username() const +{ + return m_username; +} + +void OAuth2::setUsername(const QString &username) +{ + m_username = username; +} + +QString OAuth2::password() const +{ + return m_password; +} + +void OAuth2::setPassword(const QString &password) +{ + m_password = password; +} + +QString OAuth2::clientId() const +{ + return m_clientId; +} + +void OAuth2::setClientId(const QString &clientId) +{ + m_clientId = clientId; +} + +QString OAuth2::clientSecret() const +{ + return m_clientSecret; +} + +void OAuth2::setClientSecret(const QString clientSecret) +{ + m_clientSecret = clientSecret; +} + +QString OAuth2::scope() const +{ + return m_scope; +} + +void OAuth2::setScope(const QString &scope) +{ + m_scope = scope; +} + +QString OAuth2::token() const +{ + return m_token; +} + +bool OAuth2::authenticated() const +{ + return m_authenticated; +} + +void OAuth2::startAuthentication() +{ + qCDebug(dcOAuth2) << "Start authentication" << m_username; + + QUrlQuery query; + query.addQueryItem("grant_type", "password"); + query.addQueryItem("client_id", m_clientId); + query.addQueryItem("client_secret", m_clientSecret); + query.addQueryItem("username", m_username); + query.addQueryItem("password", m_password); + query.addQueryItem("scope", m_scope); + setQuery(query); + + QNetworkRequest request(m_url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded; charset=UTF-8"); + m_tokenRequests.append(m_networkManager->post(request, m_query.toString().toUtf8())); +} + +void OAuth2::setAuthenticated(const bool &authenticated) +{ + if (authenticated) { + qCDebug(dcOAuth2) << "Authenticated successfully" << m_username; + } else { + qCWarning(dcOAuth2) << "Authentication failed" << m_username; + } + m_authenticated = authenticated; + emit authenticationChanged(); +} + +void OAuth2::setToken(const QString &token) +{ + m_token = token; + emit tokenChanged(); +} + +void OAuth2::replyFinished(QNetworkReply *reply) +{ + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // token request + if (m_tokenRequests.contains(reply)) { + + QByteArray data = reply->readAll(); + m_tokenRequests.removeAll(reply); + + // check HTTP status code + if (status != 200) { + qCWarning(dcOAuth2) << "Request token reply HTTP error:" << status << reply->errorString(); + qCWarning(dcOAuth2) << data; + setAuthenticated(false); + reply->deleteLater(); + return; + } + + // check JSON + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcOAuth2) << "Request token reply JSON error:" << error.errorString(); + setAuthenticated(false); + reply->deleteLater(); + return; + } + + if (!jsonDoc.toVariant().toMap().contains("access_token")) { + qCWarning(dcOAuth2) << "Could not get access token" << jsonDoc.toJson(); + setAuthenticated(false); + reply->deleteLater(); + return; + } + + setToken(jsonDoc.toVariant().toMap().value("access_token").toString()); + setAuthenticated(true); + + if (jsonDoc.toVariant().toMap().contains("expires_in") && jsonDoc.toVariant().toMap().contains("refresh_token")) { + int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt(); + m_refreshToken = jsonDoc.toVariant().toMap().value("refresh_token").toString(); + qCDebug(dcOAuth2) << "Token will be refreshed in" << expireTime << "[s]"; + m_timer->start((expireTime - 20) * 1000); + } + + } else if (m_refreshTokenRequests.contains(reply)) { + + QByteArray data = reply->readAll(); + m_refreshTokenRequests.removeAll(reply); + + // check HTTP status code + if (status != 200) { + qCWarning(dcOAuth2) << "Refresh token reply HTTP error:" << status << reply->errorString(); + qCWarning(dcOAuth2) << data; + setAuthenticated(false); + reply->deleteLater(); + return; + } + + // check JSON + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcOAuth2) << "Refresh token reply JSON error:" << error.errorString(); + setAuthenticated(false); + reply->deleteLater(); + return; + } + + if (!jsonDoc.toVariant().toMap().contains("access_token")) { + qCWarning(dcOAuth2) << "Could not get access token after refresh" << jsonDoc.toJson(); + setAuthenticated(false); + reply->deleteLater(); + return; + } + + setToken(jsonDoc.toVariant().toMap().value("access_token").toString()); + qCDebug(dcOAuth2) << "Token refreshed successfully"; + + if (jsonDoc.toVariant().toMap().contains("expires_in") && jsonDoc.toVariant().toMap().contains("refresh_token")) { + int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt(); + m_refreshToken = jsonDoc.toVariant().toMap().value("refresh_token").toString(); + qCDebug(dcOAuth2) << "Token will be refreshed in" << expireTime << "[s]"; + m_timer->start((expireTime - 20) * 1000); + } + + if (!authenticated()) + setAuthenticated(true); + } + + reply->deleteLater(); +} + +void OAuth2::refreshTimeout() +{ + qCDebug(dcOAuth2) << "Refresh authentication token for" << m_username; + + QUrlQuery query; + query.addQueryItem("grant_type", "refresh_token"); + query.addQueryItem("refresh_token", m_refreshToken); + query.addQueryItem("client_id", m_clientId); + query.addQueryItem("client_secret", m_clientSecret); + + QNetworkRequest request(m_url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded; charset=UTF-8"); + m_refreshTokenRequests.append(m_networkManager->post(request, query.toString().toUtf8())); +} diff --git a/libguh/network/oauth2.h b/libguh/network/oauth2.h new file mode 100644 index 00000000..0e6bc064 --- /dev/null +++ b/libguh/network/oauth2.h @@ -0,0 +1,99 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of guh. * + * * + * Guh is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 2 of the License. * + * * + * Guh 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with guh. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef OAUTH2_H +#define OAUTH2_H + +#include +#include +#include +#include +#include +#include +#include + +// OAuth 2.0 - Resource Owner Password Credentials Grant: http://tools.ietf.org/html/rfc6749#section-4.3 + +class OAuth2 : public QObject +{ + Q_OBJECT +public: + explicit OAuth2(QString clientId, QString clientSecret, QObject *parent = 0); + + QUrl url() const; + void setUrl(const QUrl &url); + + QUrlQuery query() const; + void setQuery(const QUrlQuery &query); + + QString username() const; + void setUsername(const QString &username); + + QString password() const; + void setPassword(const QString &password); + + QString clientId() const; + void setClientId(const QString &clientId); + + QString clientSecret() const; + void setClientSecret(const QString clientSecret); + + QString scope() const; + void setScope(const QString &scope); + + QString token() const; + + bool authenticated() const; + + void startAuthentication(); + +private: + QNetworkAccessManager *m_networkManager; + QTimer *m_timer; + QList m_tokenRequests; + QList m_refreshTokenRequests; + + QUrl m_url; + QUrlQuery m_query; + QString m_username; + QString m_password; + QString m_clientId; + QString m_clientSecret; + QString m_scope; + + QString m_token; + QString m_refreshToken; + + bool m_authenticated; + + void setAuthenticated(const bool &authenticated); + void setToken(const QString &token); + +private slots: + void replyFinished(QNetworkReply *reply); + void refreshTimeout(); + +signals: + void authenticationChanged(); + void tokenChanged(); + +}; + +#endif // OAUTH2_H diff --git a/libguh/plugin/deviceplugin.cpp b/libguh/plugin/deviceplugin.cpp index 90e04df0..229f9219 100644 --- a/libguh/plugin/deviceplugin.cpp +++ b/libguh/plugin/deviceplugin.cpp @@ -760,6 +760,8 @@ Types::Unit DevicePlugin::unitStringToUnit(const QString &unitString) const return Types::UnitEuroPerMegaWattHour; } else if (unitString == "Percentage") { return Types::UnitPercentage; + } else if (unitString == "PartsPerMillion") { + return Types::UnitPartsPerMillion; } else if (unitString == "Euro") { return Types::UnitEuro; } else if (unitString == "Dollar") { diff --git a/libguh/typeutils.h b/libguh/typeutils.h index 81a2b989..175f5fc5 100644 --- a/libguh/typeutils.h +++ b/libguh/typeutils.h @@ -115,6 +115,7 @@ public: UnitKiloWattHour, UnitEuroPerMegaWattHour, UnitPercentage, + UnitPartsPerMillion, UnitEuro, UnitDollar }; diff --git a/plugins/deviceplugins/awattar/devicepluginawattar.cpp b/plugins/deviceplugins/awattar/devicepluginawattar.cpp index 96e3d2b5..6e0df105 100644 --- a/plugins/deviceplugins/awattar/devicepluginawattar.cpp +++ b/plugins/deviceplugins/awattar/devicepluginawattar.cpp @@ -44,7 +44,7 @@ \note If a \l{StateType} has the parameter \tt{"writable": {...}}, an \l{ActionType} with the same uuid and \l{ParamType}{ParamTypes} will be created automatically. - \quotefile plugins/deviceplugins/udpcommander/devicepluginawattar.json + \quotefile plugins/deviceplugins/awattar/devicepluginawattar.json */ #include "devicepluginawattar.h" diff --git a/plugins/deviceplugins/deviceplugins.pro b/plugins/deviceplugins/deviceplugins.pro index 54956118..e1cae99e 100644 --- a/plugins/deviceplugins/deviceplugins.pro +++ b/plugins/deviceplugins/deviceplugins.pro @@ -20,6 +20,7 @@ SUBDIRS += elro \ kodi \ elgato \ awattar \ + netatmo \ diff --git a/plugins/deviceplugins/elgato/devicepluginelgato.json b/plugins/deviceplugins/elgato/devicepluginelgato.json index 11a57278..b85546cc 100644 --- a/plugins/deviceplugins/elgato/devicepluginelgato.json +++ b/plugins/deviceplugins/elgato/devicepluginelgato.json @@ -1,6 +1,7 @@ { "name": "Elgato", "id": "c5c03ad4-bfdb-444a-8eca-2c234c46cc27", + "idName": "Elgato", "vendors": [ { "name": "Elgato", diff --git a/plugins/deviceplugins/netatmo/devicepluginnetatmo.cpp b/plugins/deviceplugins/netatmo/devicepluginnetatmo.cpp new file mode 100644 index 00000000..a4ff7ddc --- /dev/null +++ b/plugins/deviceplugins/netatmo/devicepluginnetatmo.cpp @@ -0,0 +1,218 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of guh. * + * * + * Guh is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 2 of the License. * + * * + * Guh 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with guh. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/*! + \page netatmo.html + \title netatmo + + \ingroup plugins + \ingroup network + + This plugin allows to receive data from you netatmo weather station. + + \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}. + + Each \l{DeviceClass} has a list of \l{ParamType}{paramTypes}, \l{ActionType}{actionTypes}, \l{StateType}{stateTypes} + and \l{EventType}{eventTypes}. The \l{DeviceClass::CreateMethod}{createMethods} parameter describes how the \l{Device} + will be created in the system. A device can have more than one \l{DeviceClass::CreateMethod}{CreateMethod}. + The \l{DeviceClass::SetupMethod}{setupMethod} describes the setup method of the \l{Device}. + The detailed implementation of each \l{DeviceClass} can be found in the source code. + + \note If a \l{StateType} has the parameter \tt{"writable": {...}}, an \l{ActionType} with the same uuid and \l{ParamType}{ParamTypes} + will be created automatically. + + \quotefile plugins/deviceplugins/netatmo/devicepluginnetatmo.json +*/ + +#include "devicepluginnetatmo.h" +#include "plugin/device.h" +#include "plugininfo.h" + +#include +#include + +DevicePluginNetatmo::DevicePluginNetatmo() +{ +} + +DeviceManager::HardwareResources DevicePluginNetatmo::requiredHardware() const +{ + return DeviceManager::HardwareResourceNetworkManager | DeviceManager::HardwareResourceTimer; +} + +DeviceManager::DeviceSetupStatus DevicePluginNetatmo::setupDevice(Device *device) +{ + if (device->deviceClassId() == connectionDeviceClassId) { + qCDebug(dcNetatmo) << "Setup netatmo connection"; + + OAuth2 *authentication = new OAuth2("561c015d49c75f0d1cce6e13", "GuvKkdtu7JQlPD47qTTepRR9hQ0CUPAj4Tae3Ohcq", this); + authentication->setUrl(QUrl("https://api.netatmo.net/oauth2/token")); + authentication->setUsername(device->paramValue("username").toString()); + authentication->setPassword(device->paramValue("password").toString()); + authentication->setScope("read_station read_thermostat write_thermostat"); + + m_authentications.insert(authentication, device); + m_asyncSetups.append(device); + connect(authentication, &OAuth2::authenticationChanged, this, &DevicePluginNetatmo::onAuthenticationChanged); + + authentication->startAuthentication(); + return DeviceManager::DeviceSetupStatusAsync; + + } else if (device->deviceClassId() == indoorDeviceClassId) { + qCDebug(dcNetatmo) << "Setup netatmo indoor base station" << device->params(); + NetatmoBaseStation *indoor = new NetatmoBaseStation(device->paramValue("name").toString(), + device->paramValue("mac address").toString(), + device->paramValue("connection id").toString(), this); + + connect(indoor, &NetatmoBaseStation::statesChanged, this, &DevicePluginNetatmo::onIndoorStatesChanged); + + return DeviceManager::DeviceSetupStatusSuccess; + } + + return DeviceManager::DeviceSetupStatusFailure; +} + +void DevicePluginNetatmo::deviceRemoved(Device *device) +{ + OAuth2 * authentication = m_authentications.key(device); + m_authentications.remove(authentication); + authentication->deleteLater(); +} + +void DevicePluginNetatmo::networkManagerReplyReady(QNetworkReply *reply) +{ + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // update values request + if (m_refreshRequest.keys().contains(reply)) { + Device *device = m_refreshRequest.take(reply); + + // check HTTP status code + if (status != 200) { + qCWarning(dcNetatmo) << "Device list reply HTTP error:" << status << reply->errorString(); + emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusFailure); + reply->deleteLater(); + return; + } + + // check JSON file + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll(), &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcNetatmo) << "Device list reply JSON error:" << error.errorString(); + emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusFailure); + reply->deleteLater(); + return; + } + + qCDebug(dcNetatmo) << jsonDoc.toJson(); + processRefreshData(jsonDoc.toVariant().toMap(), device->id().toString()); + } + + reply->deleteLater(); +} + +void DevicePluginNetatmo::guhTimer() +{ + foreach (Device *device, myDevices()) { + if (device->deviceClassId() == connectionDeviceClassId) { + OAuth2 *authentication = m_authentications.key(device); + // TODO: check if authenticated + refreshData(device, authentication->token()); + } + } +} + +void DevicePluginNetatmo::refreshData(Device *device, const QString &token) +{ + QUrlQuery query; + query.addQueryItem("access_token", token); + + QUrl url("https://api.netatmo.com/api/devicelist"); + url.setQuery(query); + + QNetworkReply *reply = networkManagerGet(QNetworkRequest(url)); + m_refreshRequest.insert(reply, device); +} + +void DevicePluginNetatmo::processRefreshData(const QVariantMap &data, const QString &connectionId) +{ + if (data.contains("body")) { + if (data.value("body").toMap().contains("devices")) { + QVariantList deviceList = data.value("body").toMap().value("devices").toList(); + //QVariantList modulesList = data.value("body").toMap().value("modules").toList(); + + // check devices + foreach (QVariant deviceVariant, deviceList) { + QVariantMap deviceMap = deviceVariant.toMap(); + // we support currently only NAMain devices + if (deviceMap.value("type").toString() == "NAMain") { + NetatmoBaseStation *indoor = findIndoorDevice(deviceMap.value("_id").toString()); + // check if we have to create the device (auto) + if (!indoor) { + DeviceDescriptor descriptor(indoorDeviceClassId, "Indoor Station", deviceMap.value("station_name").toString()); + ParamList params; + params.append(Param("name", deviceMap.value("station_name").toString())); + params.append(Param("mac address", deviceMap.value("_id").toString())); + params.append(Param("connection id", connectionId)); + descriptor.setParams(params); + emit autoDevicesAppeared(indoorDeviceClassId, QList() << descriptor); + } else { + indoor->updateStates(deviceMap); + } + } + } + } + } +} + + +NetatmoBaseStation *DevicePluginNetatmo::findIndoorDevice(const QString &macAddress) +{ + foreach (NetatmoBaseStation *bs, m_indoorDevices.keys()) { + if (bs->macAddress() == macAddress) { + return bs; + } + } + return 0; +} + +void DevicePluginNetatmo::onAuthenticationChanged() +{ + OAuth2 *authentication = static_cast(sender()); + Device *device = m_authentications.value(authentication); + + // check if this is was a setup athentication + if (m_asyncSetups.contains(device)) { + m_asyncSetups.removeAll(device); + if (authentication->authenticated()) { + emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusSuccess); + refreshData(device, authentication->token()); + } else { + emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusFailure); + m_authentications.remove(authentication); + authentication->deleteLater(); + } + } +} + + diff --git a/plugins/deviceplugins/netatmo/devicepluginnetatmo.h b/plugins/deviceplugins/netatmo/devicepluginnetatmo.h new file mode 100644 index 00000000..170969a0 --- /dev/null +++ b/plugins/deviceplugins/netatmo/devicepluginnetatmo.h @@ -0,0 +1,67 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of guh. * + * * + * Guh is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 2 of the License. * + * * + * Guh 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with guh. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef DEVICEPLUGINNETATMO_H +#define DEVICEPLUGINNETATMO_H + +#include +#include +#include + +#include "plugin/deviceplugin.h" +#include "network/oauth2.h" +#include "netatmobasestation.h" + +class DevicePluginNetatmo : public DevicePlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "guru.guh.DevicePlugin" FILE "devicepluginnetatmo.json") + Q_INTERFACES(DevicePlugin) + +public: + explicit DevicePluginNetatmo(); + + DeviceManager::HardwareResources requiredHardware() const override; + DeviceManager::DeviceSetupStatus setupDevice(Device *device) override; + void deviceRemoved(Device *device) override; + void networkManagerReplyReady(QNetworkReply *reply) override; + + void guhTimer() override; + +private: + QList m_asyncSetups; + + QHash m_authentications; + QHash m_indoorDevices; + + QHash m_refreshRequest; + + void refreshData(Device *device, const QString &token); + void processRefreshData(const QVariantMap &data, const QString &connectionId); + + NetatmoBaseStation *findIndoorDevice(const QString &macAddress); + +private slots: + void onAuthenticationChanged(); + void onIndoorStatesChanged(); + +}; + +#endif // DEVICEPLUGINNETATMO_H diff --git a/plugins/deviceplugins/netatmo/devicepluginnetatmo.json b/plugins/deviceplugins/netatmo/devicepluginnetatmo.json new file mode 100644 index 00000000..6d7c15c3 --- /dev/null +++ b/plugins/deviceplugins/netatmo/devicepluginnetatmo.json @@ -0,0 +1,141 @@ +{ + "name": "Netatmo", + "idName": "Netatmo", + "id": "3af70774-4b43-46fe-96a4-22108d4efb1b", + "vendors": [ + { + "name": "Netatmo", + "idName": "netatmo", + "id": "31828f6f-c63d-4d44-b2b1-d0f70b3ce933", + "deviceClasses": [ + { + "deviceClassId": "728d5a67-27a3-400e-b83c-2765f5196f69", + "name": "Netatmo Connection", + "idName": "connection", + "createMethods": ["user"], + "paramTypes": [ + { + "name": "username", + "type": "QString", + "inputType": "TextLine" + }, + { + "name": "password", + "type": "QString", + "inputType": "Password" + } + ], + "stateType": [ + { + "id": "2f79bc1d-27ed-480a-b583-728363c83ea6", + "idName": "available", + "name": "available", + "type": "bool", + "defaultValue": false + } + ] + }, + { + "deviceClassId": "d79d373c-c992-4d75-bd01-a0f8ab346ac5", + "name": "Indoor Station", + "idName": "indoor", + "createMethods": ["auto"], + "paramTypes": [ + { + "name": "name", + "type": "QString", + "inputType": "TextLine", + "readOnly": true + }, + { + "name": "mac address", + "type": "QString", + "inputType": "TextLine", + "readOnly": true + }, + { + "name": "connection id", + "type": "QString", + "inputType": "TextLine", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "50da9f6b-c350-401c-a72e-2e4036f3975d", + "idName": "updateTime", + "name": "last update", + "unit": "UnixTime", + "type": "int", + "defaultValue": 0 + }, + { + "id": "3cb25538-e463-40ae-92f9-8f34f0c06b92", + "idName": "temperature", + "name": "temperature", + "unit": "DegreeCelsius", + "type": "double", + "defaultValue": 0 + }, + { + "id": "ae8bb713-8805-4efd-89a1-bca44a1f1690", + "idName": "temperatureMin", + "name": "temperature minimum", + "unit": "DegreeCelsius", + "type": "double", + "defaultValue": 0 + }, + { + "id": "dd30507e-037b-4c74-bcca-e04b94c7c5fe", + "idName": "temperatureMax", + "name": "temperature maximum", + "unit": "DegreeCelsius", + "type": "double", + "defaultValue": 0 + }, + { + "id": "e2db5f01-196a-48d1-8874-6b8cbfe0d8c9", + "idName": "humidity", + "name": "humidity", + "unit": "Percentage", + "type": "int", + "defaultValue": 0 + }, + { + "id": "03b0a7b7-987d-4d3b-b3f0-21d9f92ad326", + "idName": "pressure", + "name": "pressure", + "unit": "MilliBar", + "type": "double", + "defaultValue": 0 + }, + { + "id": "906cea9d-1daf-4e9c-90b9-e40f43052a34", + "idName": "noise", + "name": "noise", + "unit": "Dezibel", + "type": "int", + "defaultValue": 0 + }, + { + "id": "e5710bd1-79fa-4bd4-9052-8416aae909b9", + "idName": "co2", + "name": "co2", + "unit": "PartsPerMillion", + "type": "int", + "defaultValue": 0 + }, + { + "id": "6ea906d4-5740-454d-a730-6fdb9fa0d624", + "idName": "wifiStrength", + "name": "wifi signal strength", + "unit": "Percentage", + "type": "int", + "defaultValue": 0 + } + ] + } + ] + } + ] +} diff --git a/plugins/deviceplugins/netatmo/netatmo.pro b/plugins/deviceplugins/netatmo/netatmo.pro new file mode 100644 index 00000000..38fa38f4 --- /dev/null +++ b/plugins/deviceplugins/netatmo/netatmo.pro @@ -0,0 +1,13 @@ +include(../../plugins.pri) + +TARGET = $$qtLibraryTarget(guh_devicepluginnetatmo) + +SOURCES += \ + devicepluginnetatmo.cpp \ + netatmobasestation.cpp + +HEADERS += \ + devicepluginnetatmo.h \ + netatmobasestation.h + + diff --git a/plugins/deviceplugins/netatmo/netatmobasestation.cpp b/plugins/deviceplugins/netatmo/netatmobasestation.cpp new file mode 100644 index 00000000..a6dffc5d --- /dev/null +++ b/plugins/deviceplugins/netatmo/netatmobasestation.cpp @@ -0,0 +1,94 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of guh. * + * * + * Guh is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 2 of the License. * + * * + * Guh 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with guh. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "netatmobasestation.h" + +NetatmoBaseStation::NetatmoBaseStation(const QString &name, const QString &macAddress, const QString &connectionId, QObject *parent) : + QObject(parent), + m_name(name), + m_macAddress(macAddress), + m_connectionId(connectionId) +{ +} + +QString NetatmoBaseStation::name() const +{ + return m_name; +} + +QString NetatmoBaseStation::macAddress() const +{ + return m_macAddress; +} + +QString NetatmoBaseStation::connectionId() const +{ + return m_connectionId; +} + +int NetatmoBaseStation::lastUpdate() const +{ + return m_lastUpdate; +} + +double NetatmoBaseStation::temperature() const +{ + return m_temperature; +} + +double NetatmoBaseStation::minTemperature() const +{ + return m_minTemperature; +} + +double NetatmoBaseStation::maxTemperature() const +{ + return m_maxTemperature; +} + +double NetatmoBaseStation::pressure() const +{ + return m_pressure; +} + +int NetatmoBaseStation::humidity() const +{ + return m_humidity; +} + +int NetatmoBaseStation::noise() const +{ + return m_noise; +} + +int NetatmoBaseStation::co2() const +{ + return m_co2; +} + +int NetatmoBaseStation::wifiStrength() const +{ + return m_wifiStrength; +} + +void NetatmoBaseStation::updateStates(const QVariantMap &data) +{ + Q_UNUSED(data) +} diff --git a/plugins/deviceplugins/netatmo/netatmobasestation.h b/plugins/deviceplugins/netatmo/netatmobasestation.h new file mode 100644 index 00000000..6be01157 --- /dev/null +++ b/plugins/deviceplugins/netatmo/netatmobasestation.h @@ -0,0 +1,72 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of guh. * + * * + * Guh is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 2 of the License. * + * * + * Guh 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with guh. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef NETATMOBASESTATION_H +#define NETATMOBASESTATION_H + +#include +#include + +class NetatmoBaseStation : public QObject +{ + Q_OBJECT +public: + explicit NetatmoBaseStation(const QString &name, const QString &macAddress, const QString &connectionId, QObject *parent = 0); + + // Params + QString name() const; + QString macAddress() const; + QString connectionId() const; + + // States + int lastUpdate() const; + double temperature() const; + double minTemperature() const; + double maxTemperature() const; + double pressure() const; + int humidity() const; + int noise() const; + int co2() const; + int wifiStrength() const; + + void updateStates(const QVariantMap &data); + +private: + // Params + QString m_name; + QString m_macAddress; + QString m_connectionId; + + // States + int m_lastUpdate; + double m_temperature; + double m_minTemperature; + double m_maxTemperature; + double m_pressure; + int m_humidity; + int m_noise; + int m_co2; + int m_wifiStrength; + +signals: + void statesChanged(); +}; + +#endif // NETATMOBASESTATION_H diff --git a/server/main.cpp b/server/main.cpp index 61819961..75ed308f 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -61,14 +61,15 @@ int main(int argc, char *argv[]) s_loggingFilters.insert("Warnings", true); s_loggingFilters.insert("DeviceManager", true); s_loggingFilters.insert("RuleEngine", true); + s_loggingFilters.insert("Hardware", false); s_loggingFilters.insert("Connection", true); + s_loggingFilters.insert("LogEngine", false); s_loggingFilters.insert("TcpServer", false); s_loggingFilters.insert("WebServer", true); s_loggingFilters.insert("WebSocketServer", false); s_loggingFilters.insert("JsonRpc", false); s_loggingFilters.insert("Rest", true); - s_loggingFilters.insert("Hardware", false); - s_loggingFilters.insert("LogEngine", false); + s_loggingFilters.insert("OAuth2", false); QHash loggingFiltersPlugins; foreach (const QJsonObject &pluginMetadata, DeviceManager::pluginsMetadata()) {