From 7ef43575d57de3c5d3942ec4bc32bae709842139 Mon Sep 17 00:00:00 2001 From: nymea Date: Wed, 11 Sep 2019 21:33:11 +0200 Subject: [PATCH] Added home connect plug-in --- homeconnect/README.md | 0 homeconnect/devicepluginhomeconnect.cpp | 253 +++++++++++++++++++++++ homeconnect/devicepluginhomeconnect.h | 67 ++++++ homeconnect/devicepluginhomeconnect.json | 49 +++++ homeconnect/homeconnect.cpp | 191 +++++++++++++++++ homeconnect/homeconnect.h | 65 ++++++ homeconnect/homeconnect.pro | 13 ++ nymea-plugins.pro | 2 + 8 files changed, 640 insertions(+) create mode 100644 homeconnect/README.md create mode 100644 homeconnect/devicepluginhomeconnect.cpp create mode 100644 homeconnect/devicepluginhomeconnect.h create mode 100644 homeconnect/devicepluginhomeconnect.json create mode 100644 homeconnect/homeconnect.cpp create mode 100644 homeconnect/homeconnect.h create mode 100644 homeconnect/homeconnect.pro diff --git a/homeconnect/README.md b/homeconnect/README.md new file mode 100644 index 00000000..e69de29b diff --git a/homeconnect/devicepluginhomeconnect.cpp b/homeconnect/devicepluginhomeconnect.cpp new file mode 100644 index 00000000..f76b3d0b --- /dev/null +++ b/homeconnect/devicepluginhomeconnect.cpp @@ -0,0 +1,253 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Bernhard Trinnes . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "devicepluginhomeconnect.h" +#include "devices/device.h" +#include "network/networkaccessmanager.h" +#include "plugininfo.h" + +#include +#include +#include +#include + +DevicePluginHomeConnect::DevicePluginHomeConnect() +{ +} + + +DevicePluginHomeConnect::~DevicePluginHomeConnect() +{ + if (m_pluginTimer5sec) + hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer5sec); + if (m_pluginTimer60sec) + hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer60sec); +} + + +Device::DeviceSetupStatus DevicePluginHomeConnect::setupDevice(Device *device) +{ + if (!m_pluginTimer5sec) { + m_pluginTimer5sec = hardwareManager()->pluginTimerManager()->registerTimer(5); + connect(m_pluginTimer5sec, &PluginTimer::timeout, this, [this]() { + + foreach (Device *connectionDevice, myDevices().filterByDeviceClassId(homeConnectConnectionDeviceClassId)) { + HomeConnect *HomeConnect = m_homeConnectConnections.value(connectionDevice); + if (!HomeConnect) { + qWarning(dcHomeConnect()) << "No HomeConnect connection found to device" << connectionDevice->name(); + continue; + } + } + }); + } + + if (!m_pluginTimer60sec) { + m_pluginTimer60sec = hardwareManager()->pluginTimerManager()->registerTimer(60); + connect(m_pluginTimer60sec, &PluginTimer::timeout, this, [this]() { + foreach (Device *device, myDevices().filterByDeviceClassId(homeConnectConnectionDeviceClassId)) { + HomeConnect *homeConnect = m_homeConnectConnections.value(device); + if (!homeConnect) { + qWarning(dcHomeConnect()) << "No HomeConnect connection found to device" << device->name(); + continue; + } + + } + }); + } + + if (device->deviceClassId() == homeConnectConnectionDeviceClassId) { + HomeConnect *homeConnect; + if (m_setupHomeConnectConnections.keys().contains(device->id())) { + //Fresh device setup, has already a fresh access token + qCDebug(dcHomeConnect()) << "HomeConnect OAuth setup complete"; + HomeConnect = m_setupHomeConnectConnections.take(device->id()); + connect(homeConnect, &HomeConnect::connectionChanged, this, &DevicePluginHomeConnect::onConnectionChanged); + connect(homeConnect, &HomeConnect::actionExecuted, this, &DevicePluginHomeConnect::onActionExecuted); + connect(homeConnect, &HomeConnect::authenticationStatusChanged, this, &DevicePluginHomeConnect::onAuthenticationStatusChanged); + m_homeConnectConnections.insert(device, homeConnect); + return Device::DeviceSetupStatusSuccess; + } else { + //device loaded from the device database, needs a new access token; + pluginStorage()->beginGroup(device->id().toString()); + QByteArray refreshToken = pluginStorage()->value("refresh_token").toByteArray(); + pluginStorage()->endGroup(); + + homeConnect = new HomeConnect(hardwareManager()->networkManager(), "TODO", "TODO", this); + connect(homeConnect, &HomeConnect::connectionChanged, this, &DevicePluginHomeConnect::onConnectionChanged); + connect(homeConnect, &HomeConnect::actionExecuted, this, &DevicePluginHomeConnect::onActionExecuted); + connect(homeConnect, &HomeConnect::authenticationStatusChanged, this, &DevicePluginHomeConnect::onAuthenticationStatusChanged); + HomeConnect->getAccessTokenFromRefreshToken(refreshToken); + m_homeConnectConnections.insert(device, homeConnect); + return Device::DeviceSetupStatusAsync; + } + } + return Device::DeviceSetupStatusFailure; +} + +DevicePairingInfo DevicePluginHomeConnect::pairDevice(DevicePairingInfo &devicePairingInfo) +{ + if (devicePairingInfo.deviceClassId() == homeConnectConnectionDeviceClassId) { + + HomeConnect *HomeConnect = new HomeConnect(hardwareManager()->networkManager(), "TODO", "TODO", this); + QUrl url = homeConnect->getLoginUrl(QUrl("https://127.0.0.1:8888"), "TODO Scope"); + qCDebug(dcHomeConnect()) << "HomeConnect url:" << url; + devicePairingInfo.setOAuthUrl(url); + devicePairingInfo.setStatus(Device::DeviceErrorNoError); + m_setupHomeConnectConnections.insert(devicePairingInfo.deviceId(), HomeConnect); + return devicePairingInfo; + } + + qCWarning(dcHomeConnect()) << "Unhandled pairing metod!"; + devicePairingInfo.setStatus(Device::DeviceErrorCreationMethodNotSupported); + return devicePairingInfo; +} + +DevicePairingInfo DevicePluginHomeConnect::confirmPairing(DevicePairingInfo &devicePairingInfo, const QString &username, const QString &secret) +{ + Q_UNUSED(username); + + if (devicePairingInfo.deviceClassId() == homeConnectConnectionDeviceClassId) { + qCDebug(dcHomeConnect()) << "Redirect url is" << secret; + QUrl url(secret); + QUrlQuery query(url); + QByteArray authorizationCode = query.queryItemValue("code").toLocal8Bit(); + QByteArray state = query.queryItemValue("state").toLocal8Bit(); + //TODO evaluate state if it equals the given state + + HomeConnect *HomeConnect = m_setupHomeConnectConnections.value(devicePairingInfo.deviceId()); + + if (!HomeConnect) { + qWarning(dcHomeConnect()) << "No HomeConnect connection found for device:" << devicePairingInfo.deviceName(); + m_setupHomeConnectConnections.remove(devicePairingInfo.deviceId()); + HomeConnect->deleteLater(); + devicePairingInfo.setStatus(Device::DeviceErrorHardwareFailure); + return devicePairingInfo; + } + HomeConnect->getAccessTokenFromAuthorizationCode(authorizationCode); + connect(HomeConnect, &HomeConnect::authenticationStatusChanged, this, [devicePairingInfo, this](bool authenticated){ + HomeConnect *homeConnect = static_cast(sender()); + DevicePairingInfo info(devicePairingInfo); + if(!authenticated) { + qWarning(dcHomeConnect()) << "Authentication process failed" << devicePairingInfo.deviceName(); + m_setupHomeConnectConnections.remove(info.deviceId()); + homeConnect->deleteLater(); + info.setStatus(Device::DeviceErrorSetupFailed); + emit pairingFinished(info); + return; + } + QByteArray accessToken = homeConnect->accessToken(); + QByteArray refreshToken = homeConnect->refreshToken(); + qCDebug(dcHomeConnect()) << "Token:" << accessToken << refreshToken; + + pluginStorage()->beginGroup(info.deviceId().toString()); + pluginStorage()->setValue("refresh_token", refreshToken); + pluginStorage()->endGroup(); + + info.setStatus(Device::DeviceErrorNoError); + emit pairingFinished(info); + }); + devicePairingInfo.setStatus(Device::DeviceErrorAsync); + return devicePairingInfo; + } + qCWarning(dcHomeConnect()) << "Invalid deviceclassId -> no pairing possible with this device"; + devicePairingInfo.setStatus(Device::DeviceErrorHardwareFailure); + return devicePairingInfo; +} + +void DevicePluginHomeConnect::postSetupDevice(Device *device) +{ + if (device->deviceClassId() == homeConnectConnectionDeviceClassId) { + HomeConnect *homeConnect = m_HomeConnectConnections.value(device); + Q_UNUSED(homeConnect) + } +} + + +void DevicePluginHomeConnect::startMonitoringAutoDevices() +{ + +} + +void DevicePluginHomeConnect::deviceRemoved(Device *device) +{ + qCDebug(dcHomeConnect) << "Delete " << device->name(); + if (myDevices().empty()) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer5sec); + hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer60sec); + m_pluginTimer5sec = nullptr; + m_pluginTimer60sec = nullptr; + } +} + + +Device::DeviceError DevicePluginHomeConnect::executeAction(Device *device, const Action &action) +{ + Q_UNUSED(device) + Q_UNUSED(action) + return Device::DeviceErrorDeviceClassNotFound; +} + +void DevicePluginHomeConnect::onConnectionChanged(bool connected) +{ + HomeConnect *HomeConnect = static_cast(sender()); + Device *device = m_homeConnectConnections.key(HomeConnect); + if (!device) + return; + device->setStateValue(homeConnectConnectionConnectedStateTypeId, connected); +} + +void DevicePluginHomeConnect::onAuthenticationStatusChanged(bool authenticated) +{ + HomeConnect *HomeConnectConnection = static_cast(sender()); + Device *device = m_homeConnectConnections.key(HomeConnectConnection); + if (!device) + return; + + if (!device->setupComplete()) { + if (authenticated) { + emit deviceSetupFinished(device, Device::DeviceSetupStatusSuccess); + } else { + emit deviceSetupFinished(device, Device::DeviceSetupStatusFailure); + } + } else { + device->setStateValue(homeConnectConnectionLoggedInStateTypeId, authenticated); + if (!authenticated) { + //refresh access token needs to be refreshed + pluginStorage()->beginGroup(device->id().toString()); + QByteArray refreshToken = pluginStorage()->value("refresh_token").toByteArray(); + pluginStorage()->endGroup(); + HomeConnectConnection->getAccessTokenFromRefreshToken(refreshToken); + } + } +} + +void DevicePluginHomeConnect::onActionExecuted(QUuid HomeConnectActionId, bool success) +{ + if (m_pendingActions.contains(HomeConnectActionId)) { + ActionId nymeaActionId = m_pendingActions.value(HomeConnectActionId); + if (success) { + emit actionExecutionFinished(nymeaActionId, Device::DeviceErrorNoError); + } else { + emit actionExecutionFinished(nymeaActionId, Device::DeviceErrorHardwareFailure); + } + } +} diff --git a/homeconnect/devicepluginhomeconnect.h b/homeconnect/devicepluginhomeconnect.h new file mode 100644 index 00000000..6d9f6cce --- /dev/null +++ b/homeconnect/devicepluginhomeconnect.h @@ -0,0 +1,67 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Bernhard Trinnes . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef DEVICEPLUGINHOMECONNECT_H +#define DEVICEPLUGINHOMECONNECt_H + +#include "devices/deviceplugin.h" +#include "plugintimer.h" +#include "homeconnect.h" + +#include +#include + +class DevicePluginHomeConnect : public DevicePlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "devicepluginHomeConnect.json") + Q_INTERFACES(DevicePlugin) + +public: + explicit DevicePluginHomeConnect(); + ~DevicePluginHomeConnect() override; + + Device::DeviceSetupStatus setupDevice(Device *device) override; + DevicePairingInfo pairDevice(DevicePairingInfo &devicePairingInfo) override; + DevicePairingInfo confirmPairing(DevicePairingInfo &devicePairingInfo, const QString &username, const QString &secret) override; + + void postSetupDevice(Device *device) override; + void startMonitoringAutoDevices() override; + void deviceRemoved(Device *device) override; + Device::DeviceError executeAction(Device *device, const Action &action) override; + +private: + PluginTimer *m_pluginTimer5sec = nullptr; + PluginTimer *m_pluginTimer60sec = nullptr; + + QHash m_setupHomeConnectConnections; + QHash m_homeConnectConnections; + + QHash m_pendingActions; + +private slots: + void onConnectionChanged(bool connected); + void onAuthenticationStatusChanged(bool authenticated); + void onActionExecuted(QUuid actionId, bool success); +}; + +#endif // DEVICEPLUGINHOMECONNECT_H diff --git a/homeconnect/devicepluginhomeconnect.json b/homeconnect/devicepluginhomeconnect.json new file mode 100644 index 00000000..abefdd53 --- /dev/null +++ b/homeconnect/devicepluginhomeconnect.json @@ -0,0 +1,49 @@ +{ + "id": "109abdc7-5d53-4f63-a4b2-851e97cea8ea", + "name": "homeConnect", + "displayName": "Home Connect", + "vendors": [ + { + "id": "43cfb7a4-402f-4315-86b5-ce095697fd13", + "name": "homeConnect", + "displayName": "Home Connect", + "deviceClasses": [ + { + "id": "babc1a39-730a-4516-95bf-ff51a8ce887a", + "name": "homeConnectConnection", + "displayName": "Home Connect connection", + "interfaces": ["gateway"], + "createMethods": ["user"], + "setupMethod": "oauth", + "paramTypes": [ + ], + "stateTypes": [ + { + "id": "1180576a-1de2-4815-b442-877b572ce586", + "name": "connected", + "displayName": "connected", + "displayNameEvent": "connected changed", + "defaultValue": true, + "type": "bool" + }, + { + "id": "ff40d5c7-3095-4b3e-9e10-4c0774336764", + "name": "loggedIn", + "displayName": "Logged in", + "displayNameEvent": "Logged in changed", + "defaultValue": true, + "type": "bool" + }, + { + "id": "5d3b2396-6528-47c2-b5a4-f751531bccea", + "name": "userDisplayName", + "displayName": "User name", + "displayNameEvent": "User name changed", + "type": "QString" + } + ] + } + ] + } + ] +} diff --git a/homeconnect/homeconnect.cpp b/homeconnect/homeconnect.cpp new file mode 100644 index 00000000..2e43f051 --- /dev/null +++ b/homeconnect/homeconnect.cpp @@ -0,0 +1,191 @@ +#include "homeconnect.h" +#include "extern-plugininfo.h" + +#include +#include +#include +#include + +HomeConnect::HomeConnect(NetworkAccessManager *networkmanager, const QByteArray &clientKey, const QByteArray &clientSecret, QObject *parent) : + QObject(parent), + m_clientKey(clientKey), + m_clientSecret(clientSecret), + m_networkManager(networkmanager) +{ + if(!m_tokenRefreshTimer) { + m_tokenRefreshTimer = new QTimer(this); + m_tokenRefreshTimer->setSingleShot(true); + connect(m_tokenRefreshTimer, &QTimer::timeout, this, &HomeConnect::onRefreshTimeout); + } +} + +QUrl HomeConnect::getLoginUrl(const QUrl &redirectUrl, const QString &scope) +{ + if (m_clientKey.isEmpty()) { + qWarning(dcHomeConnect) << "Client key not defined!"; + return QUrl(""); + } + + if (redirectUrl.isEmpty()){ + qWarning(dcHomeConnect) << "No redirect uri defined!"; + } + m_redirectUri = QUrl::toPercentEncoding(redirectUrl.toString()); + + QUrl url(m_baseAuthorizationUrl); + QUrlQuery queryParams; + queryParams.addQueryItem("client_id", m_clientKey); + queryParams.addQueryItem("redirect_uri", m_redirectUri); + queryParams.addQueryItem("response_type", "code"); + queryParams.addQueryItem("scope", "TODO"); + queryParams.addQueryItem("state", QUuid::createUuid().toString()); + queryParams.addQueryItem("nonce", QUuid::createUuid().toString()); + //queryParams.addQueryItem("code_challenge", QUuid::createUuid().toString()); + //queryParams.addQueryItem("code_challenge_method", QUuid::createUuid().toString()); + url.setQuery(queryParams); + + return url; +} + +void HomeConnect::onRefreshTimeout() +{ + qCDebug(dcHomeConnect) << "Refresh authentication token"; + getAccessTokenFromRefreshToken(m_refreshToken); +} + +void HomeConnect::checkStatusCode(int status, const QByteArray &payload) +{ + QJsonDocument jsonDoc = QJsonDocument::fromJson(payload); + + switch (status){ + case 400: + if(!jsonDoc.toVariant().toMap().contains("error")) { + if(jsonDoc.toVariant().toMap().value("error").toString() == "invalid_client") { + qWarning(dcHomeConnect()) << "Client token provided doesn’t correspond to client that generated auth code."; + } + if(jsonDoc.toVariant().toMap().value("error").toString() == "invalid_redirect_uri") { + qWarning(dcHomeConnect()) << "Missing redirect_uri parameter."; + } + if(jsonDoc.toVariant().toMap().value("error").toString() == "invalid_code") { + qWarning(dcHomeConnect()) << "Expired authorization code."; + } + } + return; + case 401: + qWarning(dcHomeConnect()) << "Client does not have permission to use this API."; + return; + case 405: + qWarning(dcHomeConnect()) << "Wrong HTTP method used."; + return; + default: + break; + } +} + +void HomeConnect::getAccessTokenFromRefreshToken(const QByteArray &refreshToken) +{ + if (refreshToken.isEmpty()) { + qWarning(dcHomeConnect) << "No refresh token given!"; + emit authenticationStatusChanged(false); + return; + } + + QUrl url(m_baseAuthorizationUrl); + QUrlQuery query; + query.clear(); + query.addQueryItem("grant_type", "refresh_token"); + query.addQueryItem("refresh_token", refreshToken); + url.setQuery(query); + + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded; charset=UTF-8"); + + QByteArray auth = QByteArray(m_clientKey + ':' + m_clientSecret).toBase64(QByteArray::Base64Encoding | QByteArray::KeepTrailingEquals); + request.setRawHeader("Authorization", QString("Basic %1").arg(QString(auth)).toUtf8()); + + QNetworkReply *reply = m_networkManager->post(request, QByteArray()); + connect(reply, &QNetworkReply::finished, this, [this, reply](){ + reply->deleteLater(); + + QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll()); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (status != 200 || reply->error() != QNetworkReply::NoError) { + if(jsonDoc.toVariant().toMap().contains("error_description")) { + qWarning(dcHomeConnect()) << "Access token error:" << jsonDoc.toVariant().toMap().value("error_description").toString(); + } + emit authenticationStatusChanged(false); + return; + } + if(!jsonDoc.toVariant().toMap().contains("access_token")) { + emit authenticationStatusChanged(false); + return; + } + m_accessToken = jsonDoc.toVariant().toMap().value("access_token").toByteArray(); + + if (jsonDoc.toVariant().toMap().contains("expires_in")) { + int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt(); + qCDebug(dcHomeConnect) << "Access token expires at" << QDateTime::currentDateTime().addSecs(expireTime).toString(); + if (!m_tokenRefreshTimer) { + qWarning(dcHomeConnect()) << "Access token refresh timer not initialized"; + return; + } + m_tokenRefreshTimer->start((expireTime - 20) * 1000); + } + emit authenticationStatusChanged(true);; + }); +} + +void HomeConnect::getAccessTokenFromAuthorizationCode(const QByteArray &authorizationCode) +{ + // Obtaining access token + if(authorizationCode.isEmpty()) + qWarning(dcHomeConnect) << "No auhtorization code given!"; + if(m_clientKey.isEmpty()) + qWarning(dcHomeConnect) << "Client key not set!"; + if(m_clientSecret.isEmpty()) + qWarning(dcHomeConnect) << "Client secret not set!"; + + QUrl url = QUrl(m_baseAuthorizationUrl); + QUrlQuery query; + query.clear(); + query.addQueryItem("grant_type", "authorization_code"); + query.addQueryItem("code", authorizationCode); + query.addQueryItem("redirect_uri", m_redirectUri); + url.setQuery(query); + + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded;charset=utf-8"); + + QByteArray auth = QByteArray(m_clientKey + ':' + m_clientSecret).toBase64(QByteArray::Base64Encoding | QByteArray::KeepTrailingEquals); + request.setRawHeader("Authorization", QString("Basic %1").arg(QString(auth)).toUtf8()); + + QNetworkReply *reply = m_networkManager->post(request, QByteArray()); + connect(reply, &QNetworkReply::finished, this, [this, reply](){ + reply->deleteLater(); + QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll()); + + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + qCDebug(dcHomeConnect()) << "HomeConnect accessToken reply:" << this << reply->error() << reply->errorString() << jsonDoc.toJson(); + if(!jsonDoc.toVariant().toMap().contains("access_token") || !jsonDoc.toVariant().toMap().contains("refresh_token") ) { + emit authenticationStatusChanged(false); + return; + } + qCDebug(dcHomeConnect()) << "Access token:" << jsonDoc.toVariant().toMap().value("access_token").toString(); + m_accessToken = jsonDoc.toVariant().toMap().value("access_token").toByteArray(); + + qCDebug(dcHomeConnect()) << "Refresh token:" << jsonDoc.toVariant().toMap().value("refresh_token").toString(); + m_refreshToken = jsonDoc.toVariant().toMap().value("refresh_token").toByteArray(); + + if (jsonDoc.toVariant().toMap().contains("expires_in")) { + int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt(); + qCDebug(dcHomeConnect()) << "expires at" << QDateTime::currentDateTime().addSecs(expireTime).toString(); + if (!m_tokenRefreshTimer) { + qWarning(dcHomeConnect()) << "Token refresh timer not initialized"; + emit authenticationStatusChanged(false); + return; + } + m_tokenRefreshTimer->start((expireTime - 20) * 1000); + } + emit authenticationStatusChanged(true); + }); +} diff --git a/homeconnect/homeconnect.h b/homeconnect/homeconnect.h new file mode 100644 index 00000000..195f797b --- /dev/null +++ b/homeconnect/homeconnect.h @@ -0,0 +1,65 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 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 HOMECONNECT_H +#define HOMECONNECT_H + +#include +#include + +#include "network/networkaccessmanager.h" +#include "devices/device.h" + +class HomeConnect : public QObject +{ + Q_OBJECT +public: + HomeConnect(NetworkAccessManager *networkmanager, const QByteArray &clientKey, const QByteArray &clientSecret, QObject *parent = nullptr); + + QUrl getLoginUrl(const QUrl &redirectUrl, const QString &scope); + void checkStatusCode(int status, const QByteArray &payload); + void getAccessTokenFromRefreshToken(const QByteArray &refreshToken); + void getAccessTokenFromAuthorizationCode(const QByteArray &authorizationCode); + +private: + QByteArray m_baseAuthorizationUrl = "https://api.home-connect.com/security/oauth/authorize"; + QByteArray m_baseTokenUrl = "https://api.home-connect.com/security/oauth/token"; + QByteArray m_baseControlUrl = "https://api.home-connect.com"; + QByteArray m_clientKey; + QByteArray m_clientSecret; + + QByteArray m_accessToken; + QByteArray m_refreshToken; + QByteArray m_redirectUri; + + NetworkAccessManager *m_networkManager = nullptr; + QTimer *m_tokenRefreshTimer = nullptr; + +private slots: + void onRefreshTimeout(); + +signals: + void connectionChanged(bool connected); + void authenticationStatusChanged(bool authenticated); + void actionExecuted(QUuid actionId,bool success); +}; +#endif // HOMECONNECT_H diff --git a/homeconnect/homeconnect.pro b/homeconnect/homeconnect.pro new file mode 100644 index 00000000..6af5c583 --- /dev/null +++ b/homeconnect/homeconnect.pro @@ -0,0 +1,13 @@ +include(../plugins.pri) + +QT += network + +TARGET = $$qtLibraryTarget(nymea_devicepluginhomeconnect) + +SOURCES += \ + devicepluginhomeconnect.cpp \ + homeconnect.cpp \ + +HEADERS += \ + devicepluginhomeconnect.h \ + homeconnect.h \ diff --git a/nymea-plugins.pro b/nymea-plugins.pro index 5605b2b5..9743b9d3 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -26,6 +26,8 @@ PLUGIN_DIRS = \ gpio \ i2cdevices \ httpcommander \ + homeconnect \ + intertechno \ keba \ kodi \ lgsmarttv \