mirror of
https://github.com/nymea/nymea-plugins.git
synced 2026-08-05 03:33:51 +02:00
added setup
This commit is contained in:
parent
37587e6590
commit
0fc16e66f8
@ -1,5 +1,32 @@
|
|||||||
# Miele
|
# Miele
|
||||||
|
|
||||||
|
Connects your Miele home appliances to nymea.
|
||||||
|
|
||||||
|
## Supported Things
|
||||||
|
|
||||||
|
Miele appliances, like:
|
||||||
|
* Oven
|
||||||
|
* Dishwasher
|
||||||
|
* Coffee maker
|
||||||
|
* Dryer
|
||||||
|
* Fridge
|
||||||
|
* Washer
|
||||||
|
* Cook Top
|
||||||
|
* Hood
|
||||||
|
|
||||||
## Requirements
|
## 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
|
## More
|
||||||
|
|
||||||
|
https://www.miele.com/developer/index.html
|
||||||
|
|
||||||
|
|||||||
@ -29,34 +29,45 @@
|
|||||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
#include "integrationpluginmiele.h"
|
#include "integrationpluginmiele.h"
|
||||||
|
#include "extern-plugininfo.h"
|
||||||
|
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QUrlQuery>
|
||||||
|
|
||||||
IntegrationPluginMiele::IntegrationPluginMiele()
|
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)
|
void IntegrationPluginMiele::startPairing(ThingPairingInfo *info)
|
||||||
{
|
{
|
||||||
if (info->thingClassId() == mieleAccountThingClassId) {
|
if (info->thingClassId() == mieleAccountThingClassId) {
|
||||||
|
|
||||||
QByteArray clientKey = configValue(mielePluginCustomClientKeyParamTypeId).toByteArray();
|
QByteArray clientId = configValue(mielePluginCustomClientIdParamTypeId).toByteArray();
|
||||||
QByteArray clientSecret = configValue(mielePluginCustomClientSecretParamTypeId).toByteArray();
|
if (clientId.isEmpty()) {
|
||||||
if (clientKey.isEmpty() || clientSecret.isEmpty()) {
|
clientId = apiKeyStorage()->requestKey("miele").data("clientKey");
|
||||||
clientKey = apiKeyStorage()->requestKey("miele").data("clientKey");
|
|
||||||
clientSecret = apiKeyStorage()->requestKey("miele").data("clientSecret");
|
|
||||||
}
|
}
|
||||||
if (clientKey.isEmpty() || clientSecret.isEmpty()) {
|
if (clientId.isEmpty()) {
|
||||||
info->finish(Thing::ThingErrorAuthenticationFailure, tr("Client key and/or seceret is not available."));
|
info->finish(Thing::ThingErrorAuthenticationFailure, tr("Client key and/or seceret is not available."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Miele *miele = new Miele(hardwareManager()->networkManager(), clientKey, clientSecret, this);
|
Miele *miele = new Miele(hardwareManager()->networkManager(), clientId, this);
|
||||||
QUrl url = miele->getLoginUrl(QUrl("https://127.0.0.1:8888"), scope);
|
QUrl url = miele->getLoginUrl(QUrl("https://127.0.0.1:8888"));
|
||||||
qCDebug(dcMiele()) << "HomeConnect url:" << url;
|
qCDebug(dcMiele()) << "HomeConnect url:" << url;
|
||||||
m_setupHomeConnectConnections.insert(info->thingId(), miele);
|
m_setupMieleConnections.insert(info->thingId(), miele);
|
||||||
info->setOAuthUrl(url);
|
info->setOAuthUrl(url);
|
||||||
info->finish(Thing::ThingErrorNoError);
|
info->finish(Thing::ThingErrorNoError);
|
||||||
} else {
|
} else {
|
||||||
qCWarning(dMiele()) << "Unhandled pairing metod!";
|
qCWarning(dcMiele()) << "Unhandled pairing metod!";
|
||||||
info->finish(Thing::ThingErrorCreationMethodNotSupported);
|
info->finish(Thing::ThingErrorCreationMethodNotSupported);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,6 +107,51 @@ void IntegrationPluginMiele::confirmPairing(ThingPairingInfo *info, const QStrin
|
|||||||
|
|
||||||
void IntegrationPluginMiele::setupThing(ThingSetupInfo *info)
|
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)
|
void IntegrationPluginMiele::executeAction(ThingActionInfo *info)
|
||||||
|
|||||||
@ -5,15 +5,8 @@
|
|||||||
"paramTypes": [
|
"paramTypes": [
|
||||||
{
|
{
|
||||||
"id": "6ebf0bdb-012d-4277-9603-2a2e68e23c33",
|
"id": "6ebf0bdb-012d-4277-9603-2a2e68e23c33",
|
||||||
"name": "customClientKey",
|
"name": "customClientId",
|
||||||
"displayName": "Custom client key",
|
"displayName": "Custom client id",
|
||||||
"defaultValue": "",
|
|
||||||
"type": "QString"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "37230edb-22ed-4f1d-8849-7fd72f192d89",
|
|
||||||
"name": "customClientSecret",
|
|
||||||
"displayName": "Custom client secret",
|
|
||||||
"defaultValue": "",
|
"defaultValue": "",
|
||||||
"type": "QString"
|
"type": "QString"
|
||||||
}
|
}
|
||||||
@ -27,7 +20,7 @@
|
|||||||
"thingClasses": [
|
"thingClasses": [
|
||||||
{
|
{
|
||||||
"id": "bea89711-7f41-4add-ba27-32dd69515c1c",
|
"id": "bea89711-7f41-4add-ba27-32dd69515c1c",
|
||||||
"name": "miele",
|
"name": "mieleAccount",
|
||||||
"displayName": "Miele",
|
"displayName": "Miele",
|
||||||
"interfaces": ["account"],
|
"interfaces": ["account"],
|
||||||
"createMethods": ["user"],
|
"createMethods": ["user"],
|
||||||
|
|||||||
@ -37,11 +37,59 @@
|
|||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QUrlQuery>
|
#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()
|
void Miele::getDevices()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QUuid Miele::processAction(const QString &deviceId, Miele::ProcessAction action)
|
QUuid Miele::processAction(const QString &deviceId, Miele::ProcessAction action)
|
||||||
{
|
{
|
||||||
QJsonDocument doc;
|
QJsonDocument doc;
|
||||||
@ -107,7 +155,7 @@ QUuid Miele::putAction(const QString &deviceId, const QJsonDocument &action)
|
|||||||
|
|
||||||
QNetworkRequest request(url);
|
QNetworkRequest request(url);
|
||||||
request.setRawHeader("Authorization", "Bearer "+m_accessToken);
|
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.setRawHeader("accept", "application/json; charset=utf-8");
|
||||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
|
|||||||
@ -89,12 +89,12 @@ public:
|
|||||||
StatusNotConnected = 255
|
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 accessToken();
|
||||||
QByteArray refreshToken();
|
QByteArray refreshToken();
|
||||||
void setSimulationMode(bool simulation);
|
void setSimulationMode(bool simulation);
|
||||||
|
|
||||||
QUrl getLoginUrl(const QUrl &redirectUrl, const QString &scope);
|
QUrl getLoginUrl(const QUrl &redirectUrl);
|
||||||
|
|
||||||
void getAccessTokenFromRefreshToken(const QByteArray &refreshToken);
|
void getAccessTokenFromRefreshToken(const QByteArray &refreshToken);
|
||||||
void getAccessTokenFromAuthorizationCode(const QByteArray &authorizationCode);
|
void getAccessTokenFromAuthorizationCode(const QByteArray &authorizationCode);
|
||||||
@ -127,6 +127,20 @@ private:
|
|||||||
QUrl m_authorizationUrl = QUrl("https://api.mcs3.miele.com/thirdparty/login/");
|
QUrl m_authorizationUrl = QUrl("https://api.mcs3.miele.com/thirdparty/login/");
|
||||||
QUrl m_tokenUrl = QUrl("https://api.mcs3.miele.com/thirdparty/token/");
|
QUrl m_tokenUrl = QUrl("https://api.mcs3.miele.com/thirdparty/token/");
|
||||||
QUrl m_apiUrl = QUrl("https://api.mcs3.miele.com/");
|
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);
|
bool checkStatusCode(QNetworkReply *reply, const QByteArray &rawData);
|
||||||
signals:
|
signals:
|
||||||
@ -135,5 +149,8 @@ signals:
|
|||||||
void receivedRefreshToken(const QByteArray &refreshToken);
|
void receivedRefreshToken(const QByteArray &refreshToken);
|
||||||
void receivedAccessToken(const QByteArray &accessToken);
|
void receivedAccessToken(const QByteArray &accessToken);
|
||||||
void commandExecuted(const QUuid &commandId, bool success);
|
void commandExecuted(const QUuid &commandId, bool success);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onRefreshTimeout();
|
||||||
};
|
};
|
||||||
#endif // MIELE
|
#endif // MIELE
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user