mirror of
https://github.com/nymea/nymea-plugins.git
synced 2026-07-19 00:54:53 +02:00
added authentication
This commit is contained in:
parent
0fc16e66f8
commit
c0e53c6669
@ -29,7 +29,7 @@
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "integrationpluginmiele.h"
|
||||
#include "extern-plugininfo.h"
|
||||
#include "plugininfo.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QUrl>
|
||||
@ -46,21 +46,23 @@ IntegrationPluginMiele::IntegrationPluginMiele()
|
||||
m_idParamTypeIds.insert(cookTopThingClassId, cookTopThingIdParamTypeId);
|
||||
m_idParamTypeIds.insert(hoodThingClassId, hoodThingIdParamTypeId);
|
||||
m_idParamTypeIds.insert(cleaningRobotThingClassId, cleaningRobotThingIdParamTypeId);
|
||||
|
||||
m_connectedStateTypeIds.insert(ovenThingClassId, ovenConnectedStateTypeId);
|
||||
m_connectedStateTypeIds.insert(fridgeThingClassId, fridgeConnectedStateTypeId);
|
||||
m_connectedStateTypeIds.insert(dryerThingClassId, dryerConnectedStateTypeId);
|
||||
m_connectedStateTypeIds.insert(coffeeMakerThingClassId, coffeeMakerConnectedStateTypeId);
|
||||
m_connectedStateTypeIds.insert(dishwasherThingClassId, dishwasherConnectedStateTypeId);
|
||||
m_connectedStateTypeIds.insert(washerThingClassId, washerConnectedStateTypeId);
|
||||
m_connectedStateTypeIds.insert(cookTopThingClassId, cookTopConnectedStateTypeId);
|
||||
m_connectedStateTypeIds.insert(cleaningRobotThingClassId, cleaningRobotConnectedStateTypeId);
|
||||
m_connectedStateTypeIds.insert(hoodThingClassId, hoodConnectedStateTypeId);
|
||||
}
|
||||
|
||||
void IntegrationPluginMiele::startPairing(ThingPairingInfo *info)
|
||||
{
|
||||
if (info->thingClassId() == mieleAccountThingClassId) {
|
||||
|
||||
QByteArray clientId = configValue(mielePluginCustomClientIdParamTypeId).toByteArray();
|
||||
if (clientId.isEmpty()) {
|
||||
clientId = apiKeyStorage()->requestKey("miele").data("clientKey");
|
||||
}
|
||||
if (clientId.isEmpty()) {
|
||||
info->finish(Thing::ThingErrorAuthenticationFailure, tr("Client key and/or seceret is not available."));
|
||||
return;
|
||||
}
|
||||
Miele *miele = new Miele(hardwareManager()->networkManager(), clientId, this);
|
||||
Miele *miele = createMieleConnection();
|
||||
QUrl url = miele->getLoginUrl(QUrl("https://127.0.0.1:8888"));
|
||||
qCDebug(dcMiele()) << "HomeConnect url:" << url;
|
||||
m_setupMieleConnections.insert(info->thingId(), miele);
|
||||
@ -84,7 +86,7 @@ void IntegrationPluginMiele::confirmPairing(ThingPairingInfo *info, const QStrin
|
||||
|
||||
Miele *miele = m_setupMieleConnections.value(info->thingId());
|
||||
if (!miele) {
|
||||
qWarning(dcMiele()) << "No Miele connection found for device:" << info->thingName();
|
||||
qWarning(dcMiele()) << "No Miele connection found for thing:" << info->thingName();
|
||||
m_setupMieleConnections.remove(info->thingId());
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
return;
|
||||
@ -118,7 +120,7 @@ void IntegrationPluginMiele::setupThing(ThingSetupInfo *info)
|
||||
m_mieleConnections.insert(thing, miele);
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
} else {
|
||||
//device loaded from the device database, needs a new access token;
|
||||
// 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();
|
||||
@ -126,15 +128,10 @@ void IntegrationPluginMiele::setupThing(ThingSetupInfo *info)
|
||||
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");
|
||||
miele = createMieleConnection();
|
||||
if (!miele) {
|
||||
return info->finish(Thing::ThingErrorSetupFailed);
|
||||
}
|
||||
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);
|
||||
}
|
||||
@ -176,26 +173,26 @@ 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());
|
||||
}
|
||||
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)
|
||||
@ -233,4 +230,76 @@ void IntegrationPluginMiele::thingRemoved(Thing *thing)
|
||||
}
|
||||
}
|
||||
|
||||
Miele *IntegrationPluginMiele::createMieleConnection()
|
||||
{
|
||||
QByteArray clientId = configValue(mielePluginCustomClientIdParamTypeId).toByteArray();
|
||||
QByteArray clientSecret = configValue(mielePluginCustomClientSecretParamTypeId).toByteArray();
|
||||
if (clientId.isEmpty() || clientSecret.isEmpty()) {
|
||||
clientId = apiKeyStorage()->requestKey("miele").data("clientId");
|
||||
clientSecret = apiKeyStorage()->requestKey("miele").data("clientSecret");
|
||||
} else {
|
||||
qCDebug(dcMiele()) << "Using custom api credentials";
|
||||
}
|
||||
if (clientId.isEmpty() || clientSecret.isEmpty()) {
|
||||
return nullptr;
|
||||
} else {
|
||||
qCDebug(dcMiele()) << "Using api credentials from API key provider";
|
||||
}
|
||||
return new Miele(hardwareManager()->networkManager(), clientId, clientSecret, "en", this);
|
||||
}
|
||||
|
||||
void IntegrationPluginMiele::onConnectionChanged(bool connected)
|
||||
{
|
||||
Miele *miele = static_cast<Miele *>(sender());
|
||||
Thing *thing = m_mieleConnections.key(miele);
|
||||
if (!thing)
|
||||
return;
|
||||
thing->setStateValue(mieleAccountConnectedStateTypeId, connected);
|
||||
if (!connected) {
|
||||
Q_FOREACH(Thing *child, myThings().filterByParentId(thing->id())) {
|
||||
child->setStateValue(m_connectedStateTypeIds.value(child->thingClassId()), connected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginMiele::onAuthenticationStatusChanged(bool authenticated)
|
||||
{
|
||||
Miele *mieleConnection = static_cast<Miele *>(sender());
|
||||
if (m_asyncSetup.contains(mieleConnection)) {
|
||||
ThingSetupInfo *info = m_asyncSetup.take(mieleConnection);
|
||||
if (authenticated) {
|
||||
m_mieleConnections.insert(info->thing(), mieleConnection);
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
} else {
|
||||
mieleConnection->deleteLater();
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
}
|
||||
} else {
|
||||
Thing *thing = m_mieleConnections.key(mieleConnection);
|
||||
if (!thing)
|
||||
return;
|
||||
|
||||
thing->setStateValue(mieleAccountLoggedInStateTypeId, authenticated);
|
||||
if (!authenticated) {
|
||||
//refresh access token needs to be refreshed
|
||||
pluginStorage()->beginGroup(thing->id().toString());
|
||||
QByteArray refreshToken = pluginStorage()->value("refresh_token").toByteArray();
|
||||
pluginStorage()->endGroup();
|
||||
mieleConnection->getAccessTokenFromRefreshToken(refreshToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginMiele::onRequestExecuted(QUuid requestId, bool success)
|
||||
{
|
||||
if (m_pendingActions.contains(requestId)) {
|
||||
ThingActionInfo *info = m_pendingActions.value(requestId);
|
||||
if (success) {
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
} else {
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -55,10 +55,6 @@ public:
|
||||
void executeAction(ThingActionInfo *info) override;
|
||||
void thingRemoved(Thing *thing) override;
|
||||
|
||||
void browseThing(BrowseResult *result) override;
|
||||
void browserItem(BrowserItemResult *result) override;
|
||||
void executeBrowserItem(BrowserActionInfo *info) override;
|
||||
|
||||
private:
|
||||
PluginTimer *m_pluginTimer15min = nullptr;
|
||||
|
||||
@ -68,31 +64,11 @@ private:
|
||||
QHash<Thing *, Miele *> m_mieleConnections;
|
||||
|
||||
QHash<QUuid, ThingActionInfo *> m_pendingActions;
|
||||
QHash<Thing *, QString> m_selectedProgram;
|
||||
|
||||
QHash<ThingClassId, ParamTypeId> m_idParamTypeIds;
|
||||
|
||||
QHash<ThingClassId, StateTypeId> m_connectedStateTypeIds;
|
||||
QHash<ThingClassId, StateTypeId> m_doorStateStateTypeIds;
|
||||
QHash<ThingClassId, StateTypeId> m_localControlStateTypeIds;
|
||||
QHash<ThingClassId, StateTypeId> m_remoteControlActivationStateTypeIds;
|
||||
QHash<ThingClassId, StateTypeId> m_remoteStartAllowanceStateTypeIds;
|
||||
QHash<ThingClassId, StateTypeId> m_operationStateTypeIds;
|
||||
QHash<ThingClassId, StateTypeId> m_doorStateTypeIds;
|
||||
QHash<ThingClassId, StateTypeId> m_selectedProgramStateTypeIds;
|
||||
QHash<ThingClassId, StateTypeId> m_progressStateTypeIds;
|
||||
QHash<ThingClassId, StateTypeId> m_endTimerStateTypeIds;
|
||||
|
||||
QHash<ThingClassId, ActionTypeId> m_startActionTypeIds;
|
||||
QHash<ThingClassId, ActionTypeId> m_stopActionTypeIds;
|
||||
|
||||
QHash<ThingClassId, EventTypeId> m_programFinishedEventTypeIds;
|
||||
|
||||
QHash<QString, QString> m_coffeeStrengthTypes;
|
||||
|
||||
void parseKey(Thing *thing, const QString &key, const QVariant &value);
|
||||
void parseSettingKey(Thing *thing, const QString &key, const QVariant &value);
|
||||
bool checkIfActionIsPossible(ThingActionInfo *info);
|
||||
Miele *createMieleConnection();
|
||||
|
||||
private slots:
|
||||
void onConnectionChanged(bool connected);
|
||||
|
||||
@ -9,6 +9,13 @@
|
||||
"displayName": "Custom client id",
|
||||
"defaultValue": "",
|
||||
"type": "QString"
|
||||
},
|
||||
{
|
||||
"id": "cd4709fb-adc2-4403-99b5-93dc9cc7f0d8",
|
||||
"name": "customClientSecret",
|
||||
"displayName": "Custom client secret",
|
||||
"defaultValue": "",
|
||||
"type": "QString"
|
||||
}
|
||||
],
|
||||
"apiKeys": ["miele"],
|
||||
@ -56,7 +63,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cf542b5e-2903-497c-95a0-042df6535450",
|
||||
"id": "98491bd0-9ba7-4060-924d-153900f470b5",
|
||||
"name": "oven",
|
||||
"displayName": "Oven",
|
||||
"interfaces": ["connectable"],
|
||||
@ -64,7 +71,7 @@
|
||||
"browsable": true,
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "1e287843-41dd-49c8-a250-a21ed0e4215c",
|
||||
"id": "5be2f375-8068-40b8-9a88-fc5a07508d82",
|
||||
"name": "id",
|
||||
"displayName": "ID",
|
||||
"defaultValue": "-",
|
||||
@ -73,7 +80,7 @@
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "9c2c9360-0a4a-43ff-8d15-9cefa80380ec",
|
||||
"id": "eba13efe-cc1c-4954-bcaf-beed1d490f6d",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"displayNameEvent": "Connected changed",
|
||||
|
||||
341
miele/miele.cpp
341
miele/miele.cpp
@ -36,11 +36,13 @@
|
||||
#include <QUrl>
|
||||
#include <QJsonObject>
|
||||
#include <QUrlQuery>
|
||||
#include <QMetaEnum>
|
||||
|
||||
|
||||
Miele::Miele(NetworkAccessManager *networkmanager, const QByteArray &clientId, QObject *parent) :
|
||||
Miele::Miele(NetworkAccessManager *networkmanager, const QByteArray &clientId, const QByteArray &clientSecret, const QString &language, QObject *parent) :
|
||||
QObject(parent),
|
||||
m_language(language),
|
||||
m_clientId(clientId),
|
||||
m_clientSecret(clientSecret),
|
||||
m_networkManager(networkmanager)
|
||||
{
|
||||
m_tokenRefreshTimer = new QTimer(this);
|
||||
@ -58,15 +60,15 @@ QByteArray Miele::refreshToken()
|
||||
return m_refreshToken;
|
||||
}
|
||||
|
||||
QUrl Miele::getLoginUrl(const QUrl &redirectUrl)
|
||||
QUrl Miele::getLoginUrl(const QUrl &redirectUrl, const QString &state)
|
||||
{
|
||||
if (m_clientId.isEmpty()) {
|
||||
qWarning(dcMiele) << "Client key not defined!";
|
||||
qCWarning(dcMiele) << "Client key not defined!";
|
||||
return QUrl("");
|
||||
}
|
||||
|
||||
if (redirectUrl.isEmpty()){
|
||||
qWarning(dcMiele()) << "No redirect uri defined!";
|
||||
qCWarning(dcMiele()) << "No redirect uri defined!";
|
||||
}
|
||||
m_redirectUri = QUrl::toPercentEncoding(redirectUrl.toString());
|
||||
|
||||
@ -75,18 +77,178 @@ QUrl Miele::getLoginUrl(const QUrl &redirectUrl)
|
||||
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);
|
||||
if (!state.isEmpty())
|
||||
queryParams.addQueryItem("state", state);
|
||||
url.setQuery(queryParams);
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
void Miele::getAccessTokenFromRefreshToken(const QByteArray &refreshToken)
|
||||
{
|
||||
if (refreshToken.isEmpty()) {
|
||||
qCWarning(dcMiele()) << "No refresh token given!";
|
||||
setAuthenticated(false);
|
||||
return;
|
||||
}
|
||||
|
||||
QUrl url(m_tokenUrl);
|
||||
QUrlQuery query;
|
||||
query.clear();
|
||||
query.addQueryItem("grant_type", "refresh_token");
|
||||
query.addQueryItem("refresh_token", refreshToken);
|
||||
query.addQueryItem("client_id", m_clientId);
|
||||
query.addQueryItem("client_secret", m_clientSecret);
|
||||
|
||||
QNetworkRequest request(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
||||
QNetworkReply *reply = m_networkManager->post(request, query.toString().toUtf8());
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply](){
|
||||
|
||||
QByteArray rawData = reply->readAll();
|
||||
if (!checkStatusCode(reply, rawData)) {
|
||||
return;
|
||||
}
|
||||
QJsonDocument data = QJsonDocument::fromJson(rawData);
|
||||
|
||||
if(!data.toVariant().toMap().contains("access_token")) {
|
||||
setAuthenticated(false);
|
||||
return;
|
||||
}
|
||||
m_accessToken = data.toVariant().toMap().value("access_token").toByteArray();
|
||||
emit receivedAccessToken(m_accessToken);
|
||||
|
||||
if (data.toVariant().toMap().contains("expires_in")) {
|
||||
int expireTime = data.toVariant().toMap().value("expires_in").toInt();
|
||||
qCDebug(dcMiele) << "Access token expires int" << expireTime << "s, at" << QDateTime::currentDateTime().addSecs(expireTime).toString();
|
||||
if (!m_tokenRefreshTimer) {
|
||||
qWarning(dcMiele()) << "Access token refresh timer not initialized";
|
||||
return;
|
||||
}
|
||||
if (expireTime < 20) {
|
||||
qCWarning(dcMiele()) << "Expire time too short";
|
||||
return;
|
||||
}
|
||||
m_tokenRefreshTimer->start((expireTime - 20) * 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Miele::getAccessTokenFromAuthorizationCode(const QByteArray &authorizationCode)
|
||||
{
|
||||
if(authorizationCode.isEmpty())
|
||||
qCWarning(dcMiele) << "No authorization code given!";
|
||||
|
||||
QUrl url = QUrl(m_tokenUrl);
|
||||
QUrlQuery query;
|
||||
query.clear();
|
||||
query.addQueryItem("client_id", m_clientId);
|
||||
query.addQueryItem("client_secret", m_clientSecret);
|
||||
query.addQueryItem("vg", "de-DE");
|
||||
query.addQueryItem("redirect_uri", m_redirectUri);
|
||||
query.addQueryItem("grant_type", "authorization_code");
|
||||
query.addQueryItem("code", authorizationCode);
|
||||
url.setQuery(query);
|
||||
|
||||
QNetworkRequest request(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
||||
QNetworkReply *reply = m_networkManager->post(request, query.toString().toUtf8());
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply](){
|
||||
|
||||
QByteArray rawData = reply->readAll();
|
||||
if (!checkStatusCode(reply, rawData)) {
|
||||
return;
|
||||
}
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(rawData);
|
||||
if(!jsonDoc.toVariant().toMap().contains("access_token") || !jsonDoc.toVariant().toMap().contains("refresh_token") ) {
|
||||
setAuthenticated(false);
|
||||
return;
|
||||
}
|
||||
m_accessToken = jsonDoc.toVariant().toMap().value("access_token").toByteArray();
|
||||
receivedAccessToken(m_accessToken);
|
||||
m_refreshToken = jsonDoc.toVariant().toMap().value("refresh_token").toByteArray();
|
||||
receivedRefreshToken(m_refreshToken);
|
||||
|
||||
if (jsonDoc.toVariant().toMap().contains("expires_in")) {
|
||||
int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt();
|
||||
qCDebug(dcMiele()) << "Token expires in" << expireTime << "s, at" << QDateTime::currentDateTime().addSecs(expireTime).toString();
|
||||
if (!m_tokenRefreshTimer) {
|
||||
qWarning(dcMiele()) << "Token refresh timer not initialized";
|
||||
setAuthenticated(false);
|
||||
return;
|
||||
}
|
||||
if (expireTime < 20) {
|
||||
qCWarning(dcMiele()) << "Expire time too short";
|
||||
return;
|
||||
}
|
||||
m_tokenRefreshTimer->start((expireTime - 20) * 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Miele::getDevices()
|
||||
{
|
||||
QUrl url = m_apiUrl;
|
||||
url.setPath("/v1/devices");
|
||||
url.setQuery("language=en");
|
||||
|
||||
QNetworkRequest request(url);
|
||||
request.setRawHeader("access_token", m_accessToken);
|
||||
request.setRawHeader("language", "en");
|
||||
request.setRawHeader("accept", "application/json; charset=utf-8");
|
||||
|
||||
QNetworkReply *reply = m_networkManager->get(request);
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]{
|
||||
|
||||
QByteArray rawData = reply->readAll();
|
||||
if (!checkStatusCode(reply, rawData)) {
|
||||
return;
|
||||
}
|
||||
QVariantMap map = QJsonDocument::fromJson(rawData).toVariant().toMap();
|
||||
qCDebug(dcMiele()) << "Get devices" << map;
|
||||
if (map.contains("data")) {
|
||||
QVariantMap dataMap = map.value("data").toMap();
|
||||
qCDebug(dcMiele()) << "key" << dataMap.value("key").toString() << "value" << dataMap.value("value").toString() << dataMap.value("unit").toString();
|
||||
} else if (map.contains("error")) {
|
||||
qCWarning(dcMiele()) << "Send command" << map.value("error").toMap().value("description").toString();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Miele::getDevice(const QString &deviceId)
|
||||
{
|
||||
QUrl url = m_apiUrl;
|
||||
url.setPath("/v1/devices/"+deviceId);
|
||||
url.setQuery("language=en");
|
||||
|
||||
QNetworkRequest request(url);
|
||||
request.setRawHeader("access_token", m_accessToken);
|
||||
request.setRawHeader("language", "en");
|
||||
request.setRawHeader("accept", "application/json; charset=utf-8");
|
||||
|
||||
QNetworkReply *reply = m_networkManager->get(request);
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]{
|
||||
|
||||
QByteArray rawData = reply->readAll();
|
||||
if (!checkStatusCode(reply, rawData)) {
|
||||
return;
|
||||
}
|
||||
QVariantMap map = QJsonDocument::fromJson(rawData).toVariant().toMap();
|
||||
qCDebug(dcMiele()) << "Get device" << map;
|
||||
});
|
||||
}
|
||||
|
||||
void Miele::getActions(const QString &deviceId)
|
||||
{
|
||||
Q_UNUSED(deviceId)
|
||||
}
|
||||
|
||||
|
||||
@ -146,6 +308,65 @@ QUuid Miele::setTargetTemperature(const QString &deviceId, int zone, int targetT
|
||||
return putAction(deviceId, doc);
|
||||
}
|
||||
|
||||
QUuid Miele::setColors(const QString &deviceId, Miele::Color color)
|
||||
{
|
||||
QJsonDocument doc;
|
||||
QJsonObject object;
|
||||
QString colorString = QMetaEnum::fromType<Miele::Color>().valueToKey(color);
|
||||
object.insert("color", colorString.remove("Color").toLower());
|
||||
doc.setObject(object);
|
||||
return putAction(deviceId, doc);
|
||||
}
|
||||
|
||||
QUuid Miele::setModes(const QString &deviceId, Miele::Mode mode)
|
||||
{
|
||||
QJsonDocument doc;
|
||||
QJsonObject object;
|
||||
object.insert("modes", mode);
|
||||
doc.setObject(object);
|
||||
return putAction(deviceId, doc);
|
||||
}
|
||||
|
||||
QUuid Miele::setVentilationStep(const QString &deviceId, int step)
|
||||
{
|
||||
QJsonDocument doc;
|
||||
QJsonObject object;
|
||||
object.insert("ventilationStep", step);
|
||||
doc.setObject(object);
|
||||
return putAction(deviceId, doc);
|
||||
}
|
||||
|
||||
QUuid Miele::setStartTime(const QString &deviceId, int seconds)
|
||||
{
|
||||
QJsonDocument doc;
|
||||
QJsonObject object;
|
||||
object.insert("startTime", seconds);
|
||||
doc.setObject(object);
|
||||
return putAction(deviceId, doc);
|
||||
}
|
||||
|
||||
void Miele::getAllEvents()
|
||||
{
|
||||
QUrl url = m_apiUrl;
|
||||
url.setPath("/v1/devices/all/events");
|
||||
|
||||
QNetworkRequest request(url);
|
||||
request.setRawHeader("authorization", m_accessToken);
|
||||
request.setRawHeader("accept", "*/*");
|
||||
|
||||
QNetworkReply *reply = m_networkManager->get(request);
|
||||
connect(reply, &QNetworkReply::finished, [reply, this] {
|
||||
reply->deleteLater();
|
||||
QTimer::singleShot(5000, this, [this] {getAllEvents();}); //try to reconnect every 5 seconds
|
||||
});
|
||||
connect(reply, &QNetworkReply::readyRead, this, [this, reply]{
|
||||
|
||||
while (reply->canReadLine()) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
QUuid Miele::putAction(const QString &deviceId, const QJsonDocument &action)
|
||||
{
|
||||
QUuid commandId = QUuid::createUuid();
|
||||
@ -179,3 +400,109 @@ QUuid Miele::putAction(const QString &deviceId, const QJsonDocument &action)
|
||||
});
|
||||
return commandId;
|
||||
}
|
||||
|
||||
void Miele::setAuthenticated(bool state)
|
||||
{
|
||||
if (state != m_authenticated) {
|
||||
m_authenticated = state;
|
||||
emit authenticationStatusChanged(state);
|
||||
}
|
||||
}
|
||||
|
||||
void Miele::setConnected(bool state)
|
||||
{
|
||||
if (state != m_connected) {
|
||||
m_connected = state;
|
||||
emit connectionChanged(state);
|
||||
}
|
||||
}
|
||||
|
||||
bool Miele::checkStatusCode(QNetworkReply *reply, const QByteArray &rawData)
|
||||
{
|
||||
// Check for the internet connection
|
||||
if (reply->error() == QNetworkReply::NetworkError::HostNotFoundError ||
|
||||
reply->error() == QNetworkReply::NetworkError::UnknownNetworkError ||
|
||||
reply->error() == QNetworkReply::NetworkError::TemporaryNetworkFailureError) {
|
||||
qCWarning(dcMiele()) << "Connection error" << reply->errorString();
|
||||
setConnected(false);
|
||||
setAuthenticated(false);
|
||||
return false;
|
||||
} else {
|
||||
setConnected(true);
|
||||
}
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
QJsonParseError error;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(rawData, &error);
|
||||
|
||||
switch (status){
|
||||
case 200: //The request was successful. Typically returned for successful GET requests.
|
||||
case 204: //The request was successful. Typically returned for successful PUT/DELETE requests with no payload.
|
||||
break;
|
||||
case 400: //Error occurred (e.g. validation error - value is out of range)
|
||||
if(!jsonDoc.toVariant().toMap().contains("error")) {
|
||||
if(jsonDoc.toVariant().toMap().value("error").toString() == "invalid_client") {
|
||||
qWarning(dcMiele()) << "Client token provided doesn’t correspond to client that generated auth code.";
|
||||
}
|
||||
if(jsonDoc.toVariant().toMap().value("error").toString() == "invalid_redirect_uri") {
|
||||
qWarning(dcMiele()) << "Missing redirect_uri parameter.";
|
||||
}
|
||||
if(jsonDoc.toVariant().toMap().value("error").toString() == "invalid_code") {
|
||||
qWarning(dcMiele()) << "Expired authorization code.";
|
||||
}
|
||||
}
|
||||
setAuthenticated(false);
|
||||
return false;
|
||||
case 401:
|
||||
qWarning(dcMiele()) << "Client does not have permission to use this API.";
|
||||
setAuthenticated(false);
|
||||
return false;
|
||||
case 403:
|
||||
qCWarning(dcMiele()) << "Forbidden, Scope has not been granted or home appliance is not assigned to HC account";
|
||||
setAuthenticated(false);
|
||||
return false;
|
||||
case 404:
|
||||
qCWarning(dcMiele()) << "Not Found. This resource is not available (e.g. no images on washing machine)";
|
||||
return false;
|
||||
case 405:
|
||||
qWarning(dcMiele()) << "Wrong HTTP method used.";
|
||||
setAuthenticated(false);
|
||||
return false;
|
||||
case 408:
|
||||
qCWarning(dcMiele())<< "Request Timeout, API Server failed to produce an answer or has no connection to backend service";
|
||||
return false;
|
||||
case 409:
|
||||
qCWarning(dcMiele()) << "Conflict - Command/Query cannot be executed for the home appliance, the error response contains the error details";
|
||||
qCWarning(dcMiele()) << "Error" << jsonDoc.toVariant().toMap().value("error").toString();
|
||||
return false;
|
||||
case 415:
|
||||
qCWarning(dcMiele())<< "Unsupported Media Type. The request's Content-Type is not supported";
|
||||
return false;
|
||||
case 429:
|
||||
qCWarning(dcMiele())<< "Too Many Requests, the number of requests for a specific endpoint exceeded the quota of the client";
|
||||
return false;
|
||||
case 500:
|
||||
qCWarning(dcMiele())<< "Internal Server Error, in case of a server configuration error or any errors in resource files";
|
||||
return false;
|
||||
case 503:
|
||||
qCWarning(dcMiele())<< "Service Unavailable,if a required backend service is not available";
|
||||
return false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qCWarning(dcMiele()) << "Received invalide JSON object" << rawData;
|
||||
qCWarning(dcMiele()) << "Status" << status;
|
||||
setAuthenticated(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
setAuthenticated(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Miele::onRefreshTimeout()
|
||||
{
|
||||
qCDebug(dcMiele()) << "Refresh authentication token";
|
||||
getAccessTokenFromRefreshToken(m_refreshToken);
|
||||
}
|
||||
|
||||
@ -68,6 +68,7 @@ public:
|
||||
ColorPurple,
|
||||
ColorTurquoise
|
||||
};
|
||||
Q_ENUM(Color)
|
||||
|
||||
enum Status {
|
||||
StatusOff = 1,
|
||||
@ -89,12 +90,11 @@ public:
|
||||
StatusNotConnected = 255
|
||||
};
|
||||
|
||||
Miele(NetworkAccessManager *networkmanager, const QByteArray &clientId, QObject *parent = nullptr);
|
||||
Miele(NetworkAccessManager *networkmanager, const QByteArray &clientId, const QByteArray &clientSecret, const QString &language = "en", QObject *parent = nullptr);
|
||||
QByteArray accessToken();
|
||||
QByteArray refreshToken();
|
||||
void setSimulationMode(bool simulation);
|
||||
|
||||
QUrl getLoginUrl(const QUrl &redirectUrl);
|
||||
QUrl getLoginUrl(const QUrl &redirectUrl, const QString &state = "");
|
||||
|
||||
void getAccessTokenFromRefreshToken(const QByteArray &refreshToken);
|
||||
void getAccessTokenFromAuthorizationCode(const QByteArray &authorizationCode);
|
||||
@ -114,12 +114,14 @@ public:
|
||||
QUuid setColors(const QString &deviceId, Color color);
|
||||
QUuid setModes(const QString &deviceId, Mode mode);
|
||||
QUuid setVentilationStep(const QString &deviceId, int step);
|
||||
QUuid setStartTime();
|
||||
QUuid setStartTime(const QString &deviceId, int seconds);
|
||||
|
||||
// EVENTS
|
||||
void getAllEvents();
|
||||
private:
|
||||
|
||||
QString m_language;
|
||||
QByteArray m_clientId;
|
||||
QByteArray m_clientSecret;
|
||||
NetworkAccessManager *m_networkManager = nullptr;
|
||||
|
||||
QUuid putAction(const QString &deviceId, const QJsonDocument &action); //
|
||||
@ -127,21 +129,18 @@ 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;
|
||||
|
||||
void setAuthenticated(bool state);
|
||||
void setConnected(bool state);
|
||||
bool checkStatusCode(QNetworkReply *reply, const QByteArray &rawData);
|
||||
signals:
|
||||
void connectionChanged(bool connected);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user