diff --git a/homeconnect/README.md b/homeconnect/README.md index 0029311d..a868445a 100644 --- a/homeconnect/README.md +++ b/homeconnect/README.md @@ -1,6 +1,6 @@ # Home Connect -Connects your Home Connect home appliences to nymea. +Connects your Home Connect home appliances to nymea. ## Supported Things diff --git a/homeconnect/homeconnect.cpp b/homeconnect/homeconnect.cpp index a75aab13..17cd1d3b 100644 --- a/homeconnect/homeconnect.cpp +++ b/homeconnect/homeconnect.cpp @@ -93,7 +93,7 @@ QUrl HomeConnect::getLoginUrl(const QUrl &redirectUrl, const QString &scope) queryParams.addQueryItem("scope", scope); queryParams.addQueryItem("state", QUuid::createUuid().toString()); queryParams.addQueryItem("nonce", QUuid::createUuid().toString()); - m_codeChallenge = QUuid::createUuid().toString().remove('{').remove('}').remove("-"); + m_codeChallenge = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); queryParams.addQueryItem("code_challenge", m_codeChallenge); queryParams.addQueryItem("code_challenge_method", "plain"); url.setQuery(queryParams); @@ -107,19 +107,28 @@ void HomeConnect::onRefreshTimeout() getAccessTokenFromRefreshToken(m_refreshToken); } -bool HomeConnect::checkStatusCode(int status, const QByteArray &payload) +bool HomeConnect::checkStatusCode(QNetworkReply *reply, const QByteArray &rawData) { - //TODO emit (dis)connected, (un)authenticated, - QJsonParseError error; - QJsonDocument jsonDoc = QJsonDocument::fromJson(payload, &error); - if (error.error != QJsonParseError::NoError) { - qCWarning(dcHomeConnect()) << "Received invalide JSON object" << payload; - qCWarning(dcHomeConnect()) << "Status" << status; + // Check for the internet connection + if (reply->error() == QNetworkReply::NetworkError::HostNotFoundError || + reply->error() == QNetworkReply::NetworkError::UnknownNetworkError || + reply->error() == QNetworkReply::NetworkError::TemporaryNetworkFailureError) { + qCWarning(dcHomeConnect()) << "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 400: + 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(dcHomeConnect()) << "Client token provided doesn’t correspond to client that generated auth code."; @@ -131,16 +140,54 @@ bool HomeConnect::checkStatusCode(int status, const QByteArray &payload) qWarning(dcHomeConnect()) << "Expired authorization code."; } } + setAuthenticated(false); return false; case 401: qWarning(dcHomeConnect()) << "Client does not have permission to use this API."; + setAuthenticated(false); + return false; + case 403: + qCWarning(dcHomeConnect()) << "Forbidden, Scope has not been granted or home appliance is not assigned to HC account"; + setAuthenticated(false); + return false; + case 404: + qCWarning(dcHomeConnect()) << "Not Found. This resource is not available (e.g. no images on washing machine)"; return false; case 405: qWarning(dcHomeConnect()) << "Wrong HTTP method used."; + setAuthenticated(false); + return false; + case 408: + qCWarning(dcHomeConnect())<< "Request Timeout, API Server failed to produce an answer or has no connection to backend service"; + return false; + case 409: + qCWarning(dcHomeConnect()) << "Conflict - Command/Query cannot be executed for the home appliance, the error response contains the error details"; + qCWarning(dcHomeConnect()) << "Error" << jsonDoc.toVariant().toMap().value("error").toString(); + return false; + case 415: + qCWarning(dcHomeConnect())<< "Unsupported Media Type. The request's Content-Type is not supported"; + return false; + case 429: + qCWarning(dcHomeConnect())<< "Too Many Requests, the number of requests for a specific endpoint exceeded the quota of the client"; + return false; + case 500: + qCWarning(dcHomeConnect())<< "Internal Server Error, in case of a server configuration error or any errors in resource files"; + return false; + case 503: + qCWarning(dcHomeConnect())<< "Service Unavailable,if a required backend service is not available"; return false; default: break; } + + if (error.error != QJsonParseError::NoError) { + qCWarning(dcHomeConnect()) << "Received invalide JSON object" << rawData; + qCWarning(dcHomeConnect()) << "Status" << status; + setAuthenticated(false); + return false; + } + + setAuthenticated(true); return true; } @@ -148,7 +195,7 @@ void HomeConnect::getAccessTokenFromRefreshToken(const QByteArray &refreshToken) { if (refreshToken.isEmpty()) { qWarning(dcHomeConnect) << "No refresh token given!"; - emit authenticationStatusChanged(false); + setAuthenticated(false); return; } @@ -166,37 +213,31 @@ void HomeConnect::getAccessTokenFromRefreshToken(const QByteArray &refreshToken) connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, this, [this, reply](){ - - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (status != 200 || reply->error() != QNetworkReply::NoError) { - qWarning(dcHomeConnect()) << "Access token error:" << reply->errorString() << reply->readAll(); - emit authenticationStatusChanged(false); - return; - } - QJsonParseError error; - QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error); - if (error.error != QJsonParseError::NoError) { - emit authenticationStatusChanged(false); - qCDebug(dcHomeConnect()) << "Received invalide JSON object" << data.toJson(); + QByteArray rawData = reply->readAll(); + if (!checkStatusCode(reply, rawData)) { return; } + QJsonDocument data = QJsonDocument::fromJson(rawData); if(!data.toVariant().toMap().contains("access_token")) { - emit authenticationStatusChanged(false); + setAuthenticated(false); return; } m_accessToken = data.toVariant().toMap().value("access_token").toByteArray(); if (data.toVariant().toMap().contains("expires_in")) { int expireTime = data.toVariant().toMap().value("expires_in").toInt(); - qCDebug(dcHomeConnect) << "Access token expires at" << QDateTime::currentDateTime().addSecs(expireTime).toString(); + qCDebug(dcHomeConnect) << "Access token expires int" << expireTime << "s, at" << QDateTime::currentDateTime().addSecs(expireTime).toString(); if (!m_tokenRefreshTimer) { qWarning(dcHomeConnect()) << "Access token refresh timer not initialized"; return; } + if (expireTime < 20) { + qCWarning(dcHomeConnect()) << "Expire time too short"; + return; + } m_tokenRefreshTimer->start((expireTime - 20) * 1000); } - emit authenticationStatusChanged(true);; }); } @@ -227,15 +268,14 @@ void HomeConnect::getAccessTokenFromAuthorizationCode(const QByteArray &authoriz QNetworkReply *reply = m_networkManager->post(request, query.toString().toUtf8()); connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, this, [this, reply](){ - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (status != 200 || reply->error() != QNetworkReply::NoError) { - qWarning(dcHomeConnect()) << reply->errorString() << status << reply->readAll(); - emit authenticationStatusChanged(false); + + QByteArray rawData = reply->readAll(); + if (!checkStatusCode(reply, rawData)) { return; } - QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll()); + QJsonDocument jsonDoc = QJsonDocument::fromJson(rawData); if(!jsonDoc.toVariant().toMap().contains("access_token") || !jsonDoc.toVariant().toMap().contains("refresh_token") ) { - emit authenticationStatusChanged(false); + setAuthenticated(false); return; } m_accessToken = jsonDoc.toVariant().toMap().value("access_token").toByteArray(); @@ -243,15 +283,18 @@ void HomeConnect::getAccessTokenFromAuthorizationCode(const QByteArray &authoriz if (jsonDoc.toVariant().toMap().contains("expires_in")) { int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt(); - qCDebug(dcHomeConnect()) << "Token expires at" << QDateTime::currentDateTime().addSecs(expireTime).toString(); + qCDebug(dcHomeConnect()) << "Token expires in" << expireTime << "s, at" << QDateTime::currentDateTime().addSecs(expireTime).toString(); if (!m_tokenRefreshTimer) { qWarning(dcHomeConnect()) << "Token refresh timer not initialized"; - emit authenticationStatusChanged(false); + setAuthenticated(false); + return; + } + if (expireTime < 20) { + qCWarning(dcHomeConnect()) << "Expire time too short"; return; } m_tokenRefreshTimer->start((expireTime - 20) * 1000); } - emit authenticationStatusChanged(true); }); } @@ -267,9 +310,10 @@ void HomeConnect::getHomeAppliances() connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, this, [this, reply](){ - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); QByteArray rawData = reply->readAll(); - checkStatusCode(status, rawData); + if (!checkStatusCode(reply, rawData)) { + return; + } QVariantMap dataMap = QJsonDocument::fromJson(rawData).toVariant().toMap().value("data").toMap(); QList appliances; @@ -304,9 +348,10 @@ void HomeConnect::getPrograms(const QString &haId) connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, this, [this, haId, reply]{ - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); QByteArray rawData = reply->readAll(); - checkStatusCode(status, rawData); + if (!checkStatusCode(reply, rawData)) { + return; + } QVariantMap dataMap = QJsonDocument::fromJson(rawData).toVariant().toMap().value("data").toMap(); QVariantList programList = dataMap.value("programs").toList(); @@ -334,9 +379,10 @@ void HomeConnect::getProgramsAvailable(const QString &haId) connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, this, [this, haId, reply]{ - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); QByteArray rawData = reply->readAll(); - checkStatusCode(status, rawData); + if (!checkStatusCode(reply, rawData)) { + return; + } QVariantMap dataMap = QJsonDocument::fromJson(rawData).toVariant().toMap().value("data").toMap(); QVariantList programList = dataMap.value("programs").toList(); @@ -364,9 +410,10 @@ void HomeConnect::getProgramsActive(const QString &haId) connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, this, [this, haId, reply]{ - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); QByteArray rawData = reply->readAll(); - checkStatusCode(status, rawData); + if (!checkStatusCode(reply, rawData)) { + return; + } QVariantMap map = QJsonDocument::fromJson(rawData).toVariant().toMap(); QHash options; @@ -396,9 +443,10 @@ void HomeConnect::getProgramsSelected(const QString &haId) connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, this, [this, haId, reply]{ - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); QByteArray rawData = reply->readAll(); - checkStatusCode(status, rawData); + if (!checkStatusCode(reply, rawData)) { + return; + } QVariantMap map = QJsonDocument::fromJson(rawData).toVariant().toMap(); QHash options; @@ -428,10 +476,10 @@ void HomeConnect::getProgramsActiveOption(const QString &haId, const QString &op connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, this, [this, reply]{ - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); QByteArray rawData = reply->readAll(); - checkStatusCode(status, rawData); - + if (!checkStatusCode(reply, rawData)) { + return; + } QVariantMap dataMap = QJsonDocument::fromJson(rawData).toVariant().toMap().value("data").toMap(); qCDebug(dcHomeConnect()) << "key" << dataMap.value("key").toString() << "value" << dataMap.value("value").toString() << dataMap.value("unit").toString(); }); @@ -638,9 +686,10 @@ void HomeConnect::getStatus(const QString &haid) connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, this, [this, haid, reply]{ - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); QByteArray rawData = reply->readAll(); - checkStatusCode(status, rawData); + if (!checkStatusCode(reply, rawData)) { + return; + } QVariantMap dataMap = QJsonDocument::fromJson(rawData).toVariant().toMap().value("data").toMap(); QHash statusList; @@ -676,9 +725,10 @@ void HomeConnect::getSettings(const QString &haid) connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, this, [this, haid, reply]{ - int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); QByteArray rawData = reply->readAll(); - checkStatusCode(status, rawData); + if (!checkStatusCode(reply, rawData)) { + return; + } QVariantMap dataMap = QJsonDocument::fromJson(rawData).toVariant().toMap().value("data").toMap(); QVariantList settingsList = dataMap.value("settings").toList(); QHash settings; @@ -800,21 +850,35 @@ QUuid HomeConnect::sendCommand(const QString &haid, const QString &command) connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, this, [this, commandId, reply]{ - //TODO check status - QJsonParseError error; - QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error); - if (error.error != QJsonParseError::NoError) { - qCDebug(dcHomeConnect()) << "Send command: Received invalide JSON object"; + QByteArray rawData = reply->readAll(); + if (!checkStatusCode(reply, rawData)) { return; } - qCDebug(dcHomeConnect()) << "Send command" << data.toJson(); - if (data.toVariant().toMap().contains("data")) { - QVariantMap dataMap = data.toVariant().toMap().value("data").toMap(); + QVariantMap map = QJsonDocument::fromJson(rawData).toVariant().toMap(); + qCDebug(dcHomeConnect()) << "Send command" << map; + if (map.contains("data")) { + QVariantMap dataMap = map.value("data").toMap(); qCDebug(dcHomeConnect()) << "key" << dataMap.value("key").toString() << "value" << dataMap.value("value").toString() << dataMap.value("unit").toString(); - } else if (data.toVariant().toMap().contains("error")) { - qCWarning(dcHomeConnect()) << "Send command" << data.toVariant().toMap().value("error").toMap().value("description").toString(); + } else if (map.contains("error")) { + qCWarning(dcHomeConnect()) << "Send command" << map.value("error").toMap().value("description").toString(); } emit commandExecuted(commandId, true); }); return commandId; } + +void HomeConnect::setAuthenticated(bool state) +{ + if (state != m_authenticated) { + m_authenticated = state; + emit authenticationStatusChanged(state); + } +} + +void HomeConnect::setConnected(bool state) +{ + if (state != m_connected) { + m_connected = state; + emit connectionChanged(state); + } +} diff --git a/homeconnect/homeconnect.h b/homeconnect/homeconnect.h index bda4cec0..1a8cf9db 100644 --- a/homeconnect/homeconnect.h +++ b/homeconnect/homeconnect.h @@ -159,7 +159,13 @@ private: NetworkAccessManager *m_networkManager = nullptr; QTimer *m_tokenRefreshTimer = nullptr; - bool checkStatusCode(int status, const QByteArray &payload); + void setAuthenticated(bool state); + void setConnected(bool state); + + bool m_authenticated = false; + bool m_connected = false; + + bool checkStatusCode(QNetworkReply *reply, const QByteArray &rawData); private slots: void onRefreshTimeout(); diff --git a/homeconnect/integrationpluginhomeconnect.cpp b/homeconnect/integrationpluginhomeconnect.cpp index ed9b9670..696fb1a3 100644 --- a/homeconnect/integrationpluginhomeconnect.cpp +++ b/homeconnect/integrationpluginhomeconnect.cpp @@ -77,11 +77,11 @@ IntegrationPluginHomeConnect::IntegrationPluginHomeConnect() m_remoteControlActivationStateTypeIds.insert(dishwasherThingClassId, dishwasherRemoteControlActivationStateStateTypeId); m_remoteControlActivationStateTypeIds.insert(washerThingClassId, washerRemoteControlActivationStateStateTypeId); - m_doorStateTypeIds.insert(dishwasherThingClassId, dishwasherDoorStateStateTypeId); - m_doorStateTypeIds.insert(washerThingClassId, washerDoorStateStateTypeId); - m_doorStateTypeIds.insert(dryerThingClassId, dryerDoorStateStateTypeId); - m_doorStateTypeIds.insert(ovenThingClassId, ovenDoorStateStateTypeId); - m_doorStateTypeIds.insert(coffeeMakerThingClassId, coffeeMakerDoorStateStateTypeId); + m_doorStateTypeIds.insert(dishwasherThingClassId, dishwasherClosedStateTypeId); + m_doorStateTypeIds.insert(washerThingClassId, washerClosedStateTypeId); + m_doorStateTypeIds.insert(dryerThingClassId, dryerClosedStateTypeId); + m_doorStateTypeIds.insert(ovenThingClassId, ovenClosedStateTypeId); + m_doorStateTypeIds.insert(coffeeMakerThingClassId, coffeeMakerClosedStateTypeId); m_operationStateTypeIds.insert(ovenThingClassId, ovenOperationStateStateTypeId); m_operationStateTypeIds.insert(dryerThingClassId, dryerOperationStateStateTypeId); @@ -140,21 +140,29 @@ IntegrationPluginHomeConnect::IntegrationPluginHomeConnect() void IntegrationPluginHomeConnect::startPairing(ThingPairingInfo *info) { - if (info->thingClassId() == homeConnectConnectionThingClassId) { + if (info->thingClassId() == homeConnectAccountThingClassId) { bool simulationMode = configValue(homeConnectPluginSimulationModeParamTypeId).toBool(); bool controlEnabled = configValue(homeConnectPluginControlEnabledParamTypeId).toBool(); - QByteArray clientKey = apiKeyStorage()->requestKey("homeconnect").data("clientKey"); - QByteArray clientSecret = apiKeyStorage()->requestKey("homeconnect").data("clientSecret"); + QByteArray clientKey = configValue(homeConnectPluginCustomClientKeyParamTypeId).toByteArray(); + QByteArray clientSecret = configValue(homeConnectPluginCustomClientSecretParamTypeId).toByteArray(); + if (clientKey.isEmpty() || clientSecret.isEmpty()) { + clientKey = apiKeyStorage()->requestKey("homeconnect").data("clientKey"); + clientSecret = apiKeyStorage()->requestKey("homeconnect").data("clientSecret"); + } + if (clientKey.isEmpty() || clientSecret.isEmpty()) { + info->finish(Thing::ThingErrorAuthenticationFailure, tr("Client key and/or seceret is not available.")); + return; + } HomeConnect *homeConnect = new HomeConnect(hardwareManager()->networkManager(), clientKey, clientSecret, simulationMode, this); QString scope = "IdentifyAppliance Monitor Settings Dishwasher Washer Dryer WasherDryer Refrigerator Freezer WineCooler CoffeeMaker Hood CookProcessor"; if (controlEnabled) scope.append(" Control"); QUrl url = homeConnect->getLoginUrl(QUrl("https://127.0.0.1:8888"), scope); qCDebug(dcHomeConnect()) << "HomeConnect url:" << url; + m_setupHomeConnectConnections.insert(info->thingId(), homeConnect); info->setOAuthUrl(url); info->finish(Thing::ThingErrorNoError); - m_setupHomeConnectConnections.insert(info->thingId(), homeConnect); } else { qCWarning(dcHomeConnect()) << "Unhandled pairing metod!"; info->finish(Thing::ThingErrorCreationMethodNotSupported); @@ -165,7 +173,7 @@ void IntegrationPluginHomeConnect::confirmPairing(ThingPairingInfo *info, const { Q_UNUSED(username); - if (info->thingClassId() == homeConnectConnectionThingClassId) { + if (info->thingClassId() == homeConnectAccountThingClassId) { qCDebug(dcHomeConnect()) << "Redirect url is" << secret; QUrl url(secret); QUrlQuery query(url); @@ -175,7 +183,6 @@ void IntegrationPluginHomeConnect::confirmPairing(ThingPairingInfo *info, const if (!homeConnect) { qWarning(dcHomeConnect()) << "No HomeConnect connection found for device:" << info->thingName(); m_setupHomeConnectConnections.remove(info->thingId()); - homeConnect->deleteLater(); info->finish(Thing::ThingErrorHardwareFailure); return; } @@ -211,7 +218,7 @@ void IntegrationPluginHomeConnect::setupThing(ThingSetupInfo *info) { Thing *thing = info->thing(); - if (thing->thingClassId() == homeConnectConnectionThingClassId) { + if (thing->thingClassId() == homeConnectAccountThingClassId) { bool simulationMode = configValue(homeConnectPluginSimulationModeParamTypeId).toBool(); HomeConnect *homeConnect; if (m_setupHomeConnectConnections.keys().contains(thing->id())) { @@ -225,8 +232,20 @@ void IntegrationPluginHomeConnect::setupThing(ThingSetupInfo *info) pluginStorage()->beginGroup(thing->id().toString()); QByteArray refreshToken = pluginStorage()->value("refresh_token").toByteArray(); pluginStorage()->endGroup(); - QByteArray clientKey = apiKeyStorage()->requestKey("homeconnect").data("clientKey"); - QByteArray clientSecret = apiKeyStorage()->requestKey("homeconnect").data("clientSecret"); + if (refreshToken.isEmpty()) { + info->finish(Thing::ThingErrorAuthenticationFailure, tr("Refresh token is not available.")); + return; + } + QByteArray clientKey = configValue(homeConnectPluginCustomClientKeyParamTypeId).toByteArray(); + QByteArray clientSecret = configValue(homeConnectPluginCustomClientSecretParamTypeId).toByteArray(); + if (clientKey.isEmpty() || clientSecret.isEmpty()) { + clientKey = apiKeyStorage()->requestKey("homeconnect").data("clientKey"); + clientSecret = apiKeyStorage()->requestKey("homeconnect").data("clientSecret"); + } + if (clientKey.isEmpty() || clientSecret.isEmpty()) { + info->finish(Thing::ThingErrorAuthenticationFailure, tr("Client key and/or seceret is not available.")); + return; + } homeConnect = new HomeConnect(hardwareManager()->networkManager(), clientKey, clientSecret, simulationMode, this); homeConnect->getAccessTokenFromRefreshToken(refreshToken); m_asyncSetup.insert(homeConnect, info); @@ -269,14 +288,14 @@ void IntegrationPluginHomeConnect::postSetupThing(Thing *thing) if (!m_pluginTimer60sec) { m_pluginTimer60sec = hardwareManager()->pluginTimerManager()->registerTimer(60); connect(m_pluginTimer60sec, &PluginTimer::timeout, this, [this]() { - foreach (Thing *thing, myThings().filterByThingClassId(homeConnectConnectionThingClassId)) { + Q_FOREACH (Thing *thing, myThings().filterByThingClassId(homeConnectAccountThingClassId)) { HomeConnect *homeConnect = m_homeConnectConnections.value(thing); if (!homeConnect) { qWarning(dcHomeConnect()) << "No HomeConnect account found for" << thing->name(); continue; } homeConnect->getHomeAppliances(); - Q_FOREACH(Thing *childThing, myThings().filterByParentId(thing->id())) { + Q_FOREACH (Thing *childThing, myThings().filterByParentId(thing->id())) { QString haId = childThing->paramValue(m_idParamTypeIds.value(childThing->thingClassId())).toString(); homeConnect->getStatus(haId); homeConnect->getSettings(haId); @@ -286,12 +305,12 @@ void IntegrationPluginHomeConnect::postSetupThing(Thing *thing) }); } - if (thing->thingClassId() == homeConnectConnectionThingClassId) { + if (thing->thingClassId() == homeConnectAccountThingClassId) { HomeConnect *homeConnect = m_homeConnectConnections.value(thing); homeConnect->getHomeAppliances(); homeConnect->connectEventStream(); - thing->setStateValue(homeConnectConnectionConnectedStateTypeId, true); - thing->setStateValue(homeConnectConnectionLoggedInStateTypeId, true); + thing->setStateValue(homeConnectAccountConnectedStateTypeId, true); + thing->setStateValue(homeConnectAccountLoggedInStateTypeId, true); //TBD Set user name } else if ((thing->thingClassId() == dryerThingClassId) || (thing->thingClassId() == fridgeThingClassId) || @@ -481,7 +500,7 @@ void IntegrationPluginHomeConnect::executeAction(ThingActionInfo *info) void IntegrationPluginHomeConnect::thingRemoved(Thing *thing) { qCDebug(dcHomeConnect) << "Delete " << thing->name(); - if (thing->thingClassId() == homeConnectConnectionThingClassId) { + if (thing->thingClassId() == homeConnectAccountThingClassId) { m_homeConnectConnections.take(thing)->deleteLater(); } else { m_selectedProgram.remove(thing); @@ -546,7 +565,6 @@ void IntegrationPluginHomeConnect::executeBrowserItem(BrowserActionInfo *info) if (!homeConnect) return; QString haid = thing->paramValue(m_idParamTypeIds.value(thing->thingClassId())).toString(); - QUuid requestId = homeConnect->selectProgram(haid, info->browserAction().itemId(), QList ()); m_selectedProgram.insert(thing, info->browserAction().itemId()); @@ -581,8 +599,8 @@ void IntegrationPluginHomeConnect::parseKey(Thing *thing, const QString &key, co thing->setStateValue(ovenTargetTemperatureStateTypeId, value); } else if (key == "BSH.Common.Option.Duration") { thing->setStateValue(ovenDurationStateTypeId, value); - } else if (key == "Cooking.Oven.Option.FastPreHeat") { - } else if (key == "BSH.Common.Option.StartInRelative") { + //} else if (key == "Cooking.Oven.Option.FastPreHeat") { + //} else if (key == "BSH.Common.Option.StartInRelative") { } else if (key == "LaundryCare.Washer.Option.Temperature") { thing->setStateValue(washerTemperatureStateTypeId, value.toString().split('.').last()); // Cold, 20, 40, 60°C } else if (key == "LaundryCare.Washer.Option.SpinSpeed") { @@ -620,14 +638,13 @@ void IntegrationPluginHomeConnect::parseKey(Thing *thing, const QString &key, co qCDebug(dcHomeConnect()) << "Unkown Coffee temperature string" << temperature; } } else if (key == "Cooking.Common.Option.Hood.VentingLevel") { - //TODO - } else if (key == "Cooking.Common.Option.Hood.IntensiveLevel") { - //TODO - } else if (key == "ConsumerProducts.CleaningRobot.Option.ReferenceMapId") { - } else if (key == "ConsumerProducts.CleaningRobot.Option.CleaningMode") { + thing->setStateValue(hoodVentingLevelStateTypeId, value); + //} else if (key == "Cooking.Common.Option.Hood.IntensiveLevel") { + //} else if (key == "ConsumerProducts.CleaningRobot.Option.ReferenceMapId") { + //} else if (key == "ConsumerProducts.CleaningRobot.Option.CleaningMode") { // Program Progress Changes - } else if (key == "BSH.Common.Option.ElapsedProgramTime") { + //} else if (key == "BSH.Common.Option.ElapsedProgramTime") { } else if (key == "BSH.Common.Option.RemainingProgramTime") { QString time = QDateTime::fromMSecsSinceEpoch(QDateTime::currentMSecsSinceEpoch()+(value.toInt()*1000)).time().toString(); thing->setStateValue(m_endTimerStateTypeIds.value(thing->thingClassId()), time); @@ -635,7 +652,7 @@ void IntegrationPluginHomeConnect::parseKey(Thing *thing, const QString &key, co if (m_progressStateTypeIds.contains(thing->thingClassId())) { thing->setStateValue(m_progressStateTypeIds.value(thing->thingClassId()), value); } - } else if (key == "ConsumerProducts.CleaningRobot.Option.ProcessPhase") { + //} else if (key == "ConsumerProducts.CleaningRobot.Option.ProcessPhase") { } else if (key == "BSH.Common.Status.OperationState") { if (m_operationStateTypeIds.contains(thing->thingClassId())) { QString operationState = value.toString(); @@ -683,12 +700,11 @@ void IntegrationPluginHomeConnect::parseKey(Thing *thing, const QString &key, co if (m_progressStateTypeIds.contains(thing->thingClassId())) { thing->setStateValue(m_progressStateTypeIds.value(thing->thingClassId()), 0); } - } else if (key == "BSH.Common.Event.AlarmClockElapsed") { + //} else if (key == "BSH.Common.Event.AlarmClockElapsed") { } else if (key == "Cooking.Oven.Event.PreheatFinished") { emitEvent(Event(ovenPreheatFinishedEventTypeId, thing->id())); // Home Appliance State Changes - } else if (key == "BSH.Common.Setting.PowerState") { - //Ignore + //} else if (key == "BSH.Common.Setting.PowerState") { } else if (key == "BSH.Common.Status.RemoteControlActive") { if (m_remoteControlActivationStateTypeIds.contains(thing->thingClassId())) { thing->setStateValue(m_remoteControlActivationStateTypeIds.value(thing->thingClassId()), value); @@ -703,7 +719,7 @@ void IntegrationPluginHomeConnect::parseKey(Thing *thing, const QString &key, co } } else if (key == "BSH.Common.Status.DoorState") { if (m_doorStateTypeIds.contains(thing->thingClassId())) { - thing->setStateValue(m_doorStateTypeIds.value(thing->thingClassId()), value.toString().split('.').last()); + thing->setStateValue(m_doorStateTypeIds.value(thing->thingClassId()), value.toString().split('.').last() != "Open"); } // Home Appliance Events @@ -736,32 +752,33 @@ void IntegrationPluginHomeConnect::parseKey(Thing *thing, const QString &key, co void IntegrationPluginHomeConnect::parseSettingKey(Thing *thing, const QString &key, const QVariant &value) { - if (key.contains("BSH.Common.Setting.PowerState")) { - } else if (key.contains("BSH.Common.Setting.TemperatureUnit")) { - } else if (key.contains("BSH.Common.Setting.LiquidVolumeUnit")) { - } else if (key.contains("Refrigeration.FridgeFreezer.Setting.SetpointTemperatureRefrigerator")) { + if (key.contains("Refrigeration.FridgeFreezer.Setting.SetpointTemperatureRefrigerator")) { thing->setStateValue(fridgeFridgeTemperatureSettingStateTypeId, value); } else if (key.contains("Refrigeration.FridgeFreezer.Setting.SetpointTemperatureFreezer")) { thing->setStateValue(fridgeFreezerTemperatureStateTypeId, value); - } else if (key.contains("Refrigeration.Common.Setting.BottleCooler.SetpointTemperature")) { - } else if (key.contains("Refrigeration.Common.Setting.ChillerLeft.SetpointTemperature")) { - } else if (key.contains("Refrigeration.Common.Setting.ChillerCommon.SetpointTemperature")) { - } else if (key.contains("Refrigeration.Common.Setting.ChillerRight.SetpointTemperature")) { - } else if (key.contains("Refrigeration.Common.Setting.WineCompartment.SetpointTemperature")) { - } else if (key.contains("Refrigeration.Common.Setting.WineCompartment2.SetpointTemperature")) { - } else if (key.contains("Refrigeration.Common.Setting.WineCompartment3.SetpointTemperature")) { - } else if (key.contains("Refrigeration.FridgeFreezer.Setting.SuperModeRefrigerator")) { - } else if (key.contains("Refrigeration.FridgeFreezer.Setting.SuperModeFreezer")) { - } else if (key.contains("Refrigeration.Common.Setting.EcoMode")) { - } else if (key.contains("Refrigeration.Common.Setting.SabbathMode")) { - } else if (key.contains("Refrigeration.Common.Setting.VacationMode")) { - } else if (key.contains("Refrigeration.Common.Setting.FreshMode")) { - } else if (key.contains("Cooking.Common.Setting.Lighting")) { - } else if (key.contains("Cooking.Common.Setting.LightingBrightness")) { - } else if (key.contains("BSH.Common.Setting.AmbientLightEnabled")) { - } else if (key.contains("BSH.Common.Setting.AmbientLightBrightness")) { - } else if (key.contains("BSH.Common.Setting.AmbientLightColor")) { - } else if (key.contains("BSH.Common.Setting.AmbientLightCustomColor")) { + // For future improvements + //} else if (key.contains("BSH.Common.Setting.PowerState")) { + //} else if (key.contains("BSH.Common.Setting.TemperatureUnit")) { + //} else if (key.contains("BSH.Common.Setting.LiquidVolumeUnit")) { + //} else if (key.contains("Refrigeration.Common.Setting.BottleCooler.SetpointTemperature")) { + //} else if (key.contains("Refrigeration.Common.Setting.ChillerLeft.SetpointTemperature")) { + //} else if (key.contains("Refrigeration.Common.Setting.ChillerCommon.SetpointTemperature")) { + //} else if (key.contains("Refrigeration.Common.Setting.ChillerRight.SetpointTemperature")) { + //} else if (key.contains("Refrigeration.Common.Setting.WineCompartment.SetpointTemperature")) { + //} else if (key.contains("Refrigeration.Common.Setting.WineCompartment2.SetpointTemperature")) { + //} else if (key.contains("Refrigeration.Common.Setting.WineCompartment3.SetpointTemperature")) { + //} else if (key.contains("Refrigeration.FridgeFreezer.Setting.SuperModeRefrigerator")) { + //} else if (key.contains("Refrigeration.FridgeFreezer.Setting.SuperModeFreezer")) { + //} else if (key.contains("Refrigeration.Common.Setting.EcoMode")) { + //} else if (key.contains("Refrigeration.Common.Setting.SabbathMode")) { + //} else if (key.contains("Refrigeration.Common.Setting.VacationMode")) { + //} else if (key.contains("Refrigeration.Common.Setting.FreshMode")) { + //} else if (key.contains("Cooking.Common.Setting.Lighting")) { + //} else if (key.contains("Cooking.Common.Setting.LightingBrightness")) { + //} else if (key.contains("BSH.Common.Setting.AmbientLightEnabled")) { + //} else if (key.contains("BSH.Common.Setting.AmbientLightBrightness")) { + //} else if (key.contains("BSH.Common.Setting.AmbientLightColor")) { + //} else if (key.contains("BSH.Common.Setting.AmbientLightCustomColor")) { } } @@ -771,7 +788,7 @@ void IntegrationPluginHomeConnect::onConnectionChanged(bool connected) Thing *thing = m_homeConnectConnections.key(homeConnect); if (!thing) return; - thing->setStateValue(homeConnectConnectionConnectedStateTypeId, connected); + thing->setStateValue(homeConnectAccountConnectedStateTypeId, connected); if (!connected) { Q_FOREACH(Thing *child, myThings().filterByParentId(thing->id())) { child->setStateValue(m_connectedStateTypeIds.value(child->thingClassId()), connected); @@ -796,7 +813,7 @@ void IntegrationPluginHomeConnect::onAuthenticationStatusChanged(bool authentica if (!thing) return; - thing->setStateValue(homeConnectConnectionLoggedInStateTypeId, authenticated); + thing->setStateValue(homeConnectAccountLoggedInStateTypeId, authenticated); if (!authenticated) { //refresh access token needs to be refreshed pluginStorage()->beginGroup(thing->id().toString()); diff --git a/homeconnect/integrationpluginhomeconnect.json b/homeconnect/integrationpluginhomeconnect.json index a2f22445..f0d668de 100644 --- a/homeconnect/integrationpluginhomeconnect.json +++ b/homeconnect/integrationpluginhomeconnect.json @@ -16,6 +16,20 @@ "displayName": "Control enabled", "defaultValue": false, "type": "bool" + }, + { + "id": "19cedaab-b8a0-4cab-80c7-0cae5fce124d", + "name": "customClientKey", + "displayName": "Custom client key", + "defaultValue": "", + "type": "QString" + }, + { + "id": "49b828ab-f495-4332-900b-99a9d37565b1", + "name": "customClientSecret", + "displayName": "Custom client secret", + "defaultValue": "", + "type": "QString" } ], "apiKeys": ["homeconnect"], @@ -27,8 +41,8 @@ "thingClasses": [ { "id": "babc1a39-730a-4516-95bf-ff51a8ce887a", - "name": "homeConnectConnection", - "displayName": "Home Connect connection", + "name": "homeConnectAccount", + "displayName": "Home Connect account", "interfaces": ["account"], "createMethods": ["user"], "setupMethod": "oauth", @@ -66,7 +80,7 @@ "id": "96845b7d-4c20-43a0-a810-ec505df3ee88", "name": "oven", "displayName": "Oven", - "interfaces": ["connectable"], + "interfaces": ["connectable", "closablesensor"], "createMethods": ["auto"], "browsable": true, "paramTypes": [ @@ -90,15 +104,11 @@ }, { "id": "e892ca9e-5b31-41f5-a568-44474091f0f6", - "name": "doorState", - "displayName": "Door state", - "displayNameEvent": "Door state changed", - "defaultValue": "Open", - "type": "QString", - "possibleValues": [ - "Open", - "Closed" - ] + "name": "closed", + "displayName": "Door closed", + "displayNameEvent": "Door closed changed", + "defaultValue": false, + "type": "bool" }, { "id": "c9f9bd33-513f-4834-a504-c2c1611fb4be", @@ -241,15 +251,11 @@ }, { "id": "8cbb3746-7e04-4fc8-93eb-b774b606a057", - "name": "doorState", - "displayName": "Door state", - "displayNameEvent": "Door state changed", - "defaultValue": "Open", - "type": "QString", - "possibleValues": [ - "Open", - "Closed" - ] + "name": "closed", + "displayName": "Door closed", + "displayNameEvent": "Door closed changed", + "defaultValue": false, + "type": "bool" }, { "id": "2b45fbfc-d3d7-4dd4-91f8-4a789405246e", @@ -369,15 +375,11 @@ }, { "id": "5011efa7-9915-4ecc-b717-6ced369abcb7", - "name": "doorState", - "displayName": "Door state", - "displayNameEvent": "Door state changed", - "defaultValue": "Open", - "type": "QString", - "possibleValues": [ - "Open", - "Closed" - ] + "name": "closed", + "displayName": "Door closed", + "displayNameEvent": "Door closed changed", + "defaultValue": false, + "type": "bool" }, { "id": "693fc8d2-d9a9-4109-b8c5-f3d22091500c", @@ -540,16 +542,11 @@ }, { "id": "81aa609e-69c0-4d5c-98f0-46e6f14c7eaa", - "name": "doorState", - "displayName": "Door state", - "displayNameEvent": "Door state changed", - "defaultValue": "Open", - "type": "QString", - "possibleValues": [ - "Open", - "Closed", - "Locked" - ] + "name": "closed", + "displayName": "Door closed", + "displayNameEvent": "Door closed changed", + "defaultValue": false, + "type": "bool" }, { "id": "52fde732-ed14-437e-8fbf-461d2ed19654", @@ -682,15 +679,11 @@ }, { "id": "99bba70c-5ead-4076-8b71-720a931668a4", - "name": "doorState", - "displayName": "Door state", - "displayNameEvent": "Door state changed", - "defaultValue": "Open", - "type": "QString", - "possibleValues": [ - "Open", - "Closed" - ] + "name": "closed", + "displayName": "Door closed", + "displayNameEvent": "Door closed changed", + "defaultValue": false, + "type": "bool" }, { "id": "bc13977e-0ea1-4804-af00-311ae62c6c06", @@ -785,16 +778,11 @@ }, { "id": "99296e86-09bf-4b74-b122-ee82b6bfdb62", - "name": "doorState", - "displayName": "Door state", - "displayNameEvent": "Door state changed", - "defaultValue": "Open", - "type": "QString", - "possibleValues": [ - "Open", - "Closed", - "Locked" - ] + "name": "closed", + "displayName": "Door closed", + "displayNameEvent": "Door closed changed", + "defaultValue": false, + "type": "bool" }, { "id": "2ad3e2f8-c955-4e1d-b394-1e71a16f03bb",