From 0fc16e66f86a891d4d760c0f166279d5cc596737 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Mon, 30 Nov 2020 10:04:47 +0100 Subject: [PATCH] added setup --- miele/README.md | 27 ++++++++ miele/integrationpluginmiele.cpp | 100 ++++++++++++++++++++++++++---- miele/integrationpluginmiele.json | 13 +--- miele/miele.cpp | 50 ++++++++++++++- miele/miele.h | 21 ++++++- 5 files changed, 187 insertions(+), 24 deletions(-) diff --git a/miele/README.md b/miele/README.md index 6bd4b562..f5656e3c 100644 --- a/miele/README.md +++ b/miele/README.md @@ -1,5 +1,32 @@ # Miele +Connects your Miele home appliances to nymea. + +## Supported Things + +Miele appliances, like: +* Oven +* Dishwasher +* Coffee maker +* Dryer +* Fridge +* Washer +* Cook Top +* Hood + ## Requirements +* The package “nymea-plugin-miele” must be installed +* Internet connection +* Miele account + +## Plug-In Settings + +**Custom client key and secret** +You can register as developer on https://miele.com/developer, where you can obtain you own client credentials. +The default client credentials are made available through the nymea community API key provider. + ## More + +https://www.miele.com/developer/index.html + diff --git a/miele/integrationpluginmiele.cpp b/miele/integrationpluginmiele.cpp index f5803218..edba98d6 100644 --- a/miele/integrationpluginmiele.cpp +++ b/miele/integrationpluginmiele.cpp @@ -29,34 +29,45 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "integrationpluginmiele.h" +#include "extern-plugininfo.h" + +#include +#include +#include IntegrationPluginMiele::IntegrationPluginMiele() { - + m_idParamTypeIds.insert(ovenThingClassId, ovenThingIdParamTypeId); + m_idParamTypeIds.insert(fridgeThingClassId, fridgeThingIdParamTypeId); + m_idParamTypeIds.insert(dryerThingClassId, dryerThingIdParamTypeId); + m_idParamTypeIds.insert(coffeeMakerThingClassId, coffeeMakerThingIdParamTypeId); + m_idParamTypeIds.insert(dishwasherThingClassId, dishwasherThingIdParamTypeId); + m_idParamTypeIds.insert(washerThingClassId, washerThingIdParamTypeId); + m_idParamTypeIds.insert(cookTopThingClassId, cookTopThingIdParamTypeId); + m_idParamTypeIds.insert(hoodThingClassId, hoodThingIdParamTypeId); + m_idParamTypeIds.insert(cleaningRobotThingClassId, cleaningRobotThingIdParamTypeId); } void IntegrationPluginMiele::startPairing(ThingPairingInfo *info) { if (info->thingClassId() == mieleAccountThingClassId) { - QByteArray clientKey = configValue(mielePluginCustomClientKeyParamTypeId).toByteArray(); - QByteArray clientSecret = configValue(mielePluginCustomClientSecretParamTypeId).toByteArray(); - if (clientKey.isEmpty() || clientSecret.isEmpty()) { - clientKey = apiKeyStorage()->requestKey("miele").data("clientKey"); - clientSecret = apiKeyStorage()->requestKey("miele").data("clientSecret"); + QByteArray clientId = configValue(mielePluginCustomClientIdParamTypeId).toByteArray(); + if (clientId.isEmpty()) { + clientId = apiKeyStorage()->requestKey("miele").data("clientKey"); } - if (clientKey.isEmpty() || clientSecret.isEmpty()) { + if (clientId.isEmpty()) { info->finish(Thing::ThingErrorAuthenticationFailure, tr("Client key and/or seceret is not available.")); return; } - Miele *miele = new Miele(hardwareManager()->networkManager(), clientKey, clientSecret, this); - QUrl url = miele->getLoginUrl(QUrl("https://127.0.0.1:8888"), scope); + Miele *miele = new Miele(hardwareManager()->networkManager(), clientId, this); + QUrl url = miele->getLoginUrl(QUrl("https://127.0.0.1:8888")); qCDebug(dcMiele()) << "HomeConnect url:" << url; - m_setupHomeConnectConnections.insert(info->thingId(), miele); + m_setupMieleConnections.insert(info->thingId(), miele); info->setOAuthUrl(url); info->finish(Thing::ThingErrorNoError); } else { - qCWarning(dMiele()) << "Unhandled pairing metod!"; + qCWarning(dcMiele()) << "Unhandled pairing metod!"; info->finish(Thing::ThingErrorCreationMethodNotSupported); } } @@ -96,6 +107,51 @@ void IntegrationPluginMiele::confirmPairing(ThingPairingInfo *info, const QStrin void IntegrationPluginMiele::setupThing(ThingSetupInfo *info) { + Thing *thing = info->thing(); + + if (thing->thingClassId() == mieleAccountThingClassId) { + Miele *miele; + if (m_setupMieleConnections.keys().contains(thing->id())) { + //Fresh device setup, has already a fresh access token + qCDebug(dcMiele()) << "Miele OAuth setup complete"; + miele = m_setupMieleConnections.take(thing->id()); + m_mieleConnections.insert(thing, miele); + info->finish(Thing::ThingErrorNoError); + } else { + //device loaded from the device database, needs a new access token; + pluginStorage()->beginGroup(thing->id().toString()); + QByteArray refreshToken = pluginStorage()->value("refresh_token").toByteArray(); + pluginStorage()->endGroup(); + if (refreshToken.isEmpty()) { + info->finish(Thing::ThingErrorAuthenticationFailure, tr("Refresh token is not available.")); + return; + } + QByteArray clientId = configValue(mielePluginCustomClientIdParamTypeId).toByteArray(); + if (clientId.isEmpty()) { + clientId = apiKeyStorage()->requestKey("miele").data("clientId"); + } + if (clientId.isEmpty()) { + info->finish(Thing::ThingErrorAuthenticationFailure, tr("Client id is not available.")); + return; + } + miele = new Miele(hardwareManager()->networkManager(), clientId, this); + miele->getAccessTokenFromRefreshToken(refreshToken); + m_asyncSetup.insert(miele, info); + } + } else if (m_idParamTypeIds.contains(thing->thingClassId())) { + Thing *parentThing = myThings().findById(thing->parentId()); + if (parentThing->setupComplete()) { + info->finish(Thing::ThingErrorNoError); + } else { + connect(parentThing, &Thing::setupStatusChanged, info, [parentThing, info]{ + if (parentThing->setupComplete()) { + info->finish(Thing::ThingErrorNoError); + } + }); + } + } else { + Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); + } } @@ -118,6 +174,28 @@ void IntegrationPluginMiele::postSetupThing(Thing *thing) } }); } + + if (thing->thingClassId() == mieleAccountThingClassId) { + Miele *miele = m_mieleConnections.value(thing); + miele->getDevices(); + //miele->connectEventStream(); + thing->setStateValue(mieleAccountConnectedStateTypeId, true); + thing->setStateValue(mieleAccountLoggedInStateTypeId, true); + //TBD Set user name + } else if (m_idParamTypeIds.contains(thing->thingClassId())) { + Thing *parentThing = myThings().findById(thing->parentId()); + if (!parentThing) + qCWarning(dcMiele()) << "Could not find parent with Id" << thing->parentId().toString(); + Miele *miele = m_mieleConnections.value(parentThing); + QString deviceId = thing->paramValue(m_idParamTypeIds.value(thing->thingClassId())).toString(); + if (!miele) { + qCWarning(dcMiele()) << "Could not find HomeConnect connection for thing" << thing->name(); + } else { + miele->getDevice(deviceId); + } + } else { + Q_ASSERT_X(false, "postSetupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); + } } void IntegrationPluginMiele::executeAction(ThingActionInfo *info) diff --git a/miele/integrationpluginmiele.json b/miele/integrationpluginmiele.json index 8437f7ee..8a1f8a62 100644 --- a/miele/integrationpluginmiele.json +++ b/miele/integrationpluginmiele.json @@ -5,15 +5,8 @@ "paramTypes": [ { "id": "6ebf0bdb-012d-4277-9603-2a2e68e23c33", - "name": "customClientKey", - "displayName": "Custom client key", - "defaultValue": "", - "type": "QString" - }, - { - "id": "37230edb-22ed-4f1d-8849-7fd72f192d89", - "name": "customClientSecret", - "displayName": "Custom client secret", + "name": "customClientId", + "displayName": "Custom client id", "defaultValue": "", "type": "QString" } @@ -27,7 +20,7 @@ "thingClasses": [ { "id": "bea89711-7f41-4add-ba27-32dd69515c1c", - "name": "miele", + "name": "mieleAccount", "displayName": "Miele", "interfaces": ["account"], "createMethods": ["user"], diff --git a/miele/miele.cpp b/miele/miele.cpp index 40f8c2c8..c3f1df69 100644 --- a/miele/miele.cpp +++ b/miele/miele.cpp @@ -37,11 +37,59 @@ #include #include + +Miele::Miele(NetworkAccessManager *networkmanager, const QByteArray &clientId, QObject *parent) : + QObject(parent), + m_clientId(clientId), + m_networkManager(networkmanager) +{ + m_tokenRefreshTimer = new QTimer(this); + m_tokenRefreshTimer->setSingleShot(true); + connect(m_tokenRefreshTimer, &QTimer::timeout, this, &Miele::onRefreshTimeout); +} + +QByteArray Miele::accessToken() +{ + return m_accessToken; +} + +QByteArray Miele::refreshToken() +{ + return m_refreshToken; +} + +QUrl Miele::getLoginUrl(const QUrl &redirectUrl) +{ + if (m_clientId.isEmpty()) { + qWarning(dcMiele) << "Client key not defined!"; + return QUrl(""); + } + + if (redirectUrl.isEmpty()){ + qWarning(dcMiele()) << "No redirect uri defined!"; + } + m_redirectUri = QUrl::toPercentEncoding(redirectUrl.toString()); + + QUrl url(m_authorizationUrl); + QUrlQuery queryParams; + queryParams.addQueryItem("client_id", m_clientId); + queryParams.addQueryItem("redirect_uri", m_redirectUri); + queryParams.addQueryItem("response_type", "code"); + m_state = QUuid::createUuid().toString(); + queryParams.addQueryItem("state", m_state); + url.setQuery(queryParams); + + return url; +} + + + void Miele::getDevices() { } + QUuid Miele::processAction(const QString &deviceId, Miele::ProcessAction action) { QJsonDocument doc; @@ -107,7 +155,7 @@ QUuid Miele::putAction(const QString &deviceId, const QJsonDocument &action) QNetworkRequest request(url); request.setRawHeader("Authorization", "Bearer "+m_accessToken); - // request.setRawHeader("Accept-Language", "en-US"); + // request.setRawHeader("Accept-Language", "en-US"); request.setRawHeader("accept", "application/json; charset=utf-8"); request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); diff --git a/miele/miele.h b/miele/miele.h index 0fb89c24..1996e994 100644 --- a/miele/miele.h +++ b/miele/miele.h @@ -89,12 +89,12 @@ public: StatusNotConnected = 255 }; - Miele(NetworkAccessManager *networkmanager, const QByteArray &clientKey, const QByteArray &clientSecret, bool simulationMode = false, QObject *parent = nullptr); + Miele(NetworkAccessManager *networkmanager, const QByteArray &clientId, QObject *parent = nullptr); QByteArray accessToken(); QByteArray refreshToken(); void setSimulationMode(bool simulation); - QUrl getLoginUrl(const QUrl &redirectUrl, const QString &scope); + QUrl getLoginUrl(const QUrl &redirectUrl); void getAccessTokenFromRefreshToken(const QByteArray &refreshToken); void getAccessTokenFromAuthorizationCode(const QByteArray &authorizationCode); @@ -127,6 +127,20 @@ private: QUrl m_authorizationUrl = QUrl("https://api.mcs3.miele.com/thirdparty/login/"); QUrl m_tokenUrl = QUrl("https://api.mcs3.miele.com/thirdparty/token/"); QUrl m_apiUrl = QUrl("https://api.mcs3.miele.com/"); + QByteArray m_clientId; + + QByteArray m_accessToken; + QByteArray m_refreshToken; + QByteArray m_redirectUri = "https://127.0.0.1:8888"; + QString m_state; + + QTimer *m_tokenRefreshTimer = nullptr; + + void setAuthenticated(bool state); + void setConnected(bool state); + + bool m_authenticated = false; + bool m_connected = false; bool checkStatusCode(QNetworkReply *reply, const QByteArray &rawData); signals: @@ -135,5 +149,8 @@ signals: void receivedRefreshToken(const QByteArray &refreshToken); void receivedAccessToken(const QByteArray &accessToken); void commandExecuted(const QUuid &commandId, bool success); + +private slots: + void onRefreshTimeout(); }; #endif // MIELE