added setup

This commit is contained in:
Boernsman 2020-11-30 10:04:47 +01:00
parent 37587e6590
commit 0fc16e66f8
5 changed files with 187 additions and 24 deletions

View File

@ -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

View File

@ -29,34 +29,45 @@
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "integrationpluginmiele.h"
#include "extern-plugininfo.h"
#include <QJsonDocument>
#include <QUrl>
#include <QUrlQuery>
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)

View File

@ -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"],

View File

@ -37,11 +37,59 @@
#include <QJsonObject>
#include <QUrlQuery>
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");

View File

@ -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