diff --git a/debian/control b/debian/control index a3c0e990..acecfdc0 100644 --- a/debian/control +++ b/debian/control @@ -494,6 +494,21 @@ Description: nymea.io plugin for a generic MQTT client This package will install a generic MQTT client plugin for nymea.io +Package: nymea-plugin-miele +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends}, + nymea-plugins-translations, +Description: nymea.io plugin for Miele appliances + The nymea daemon is a plugin based IoT (Internet of Things) server. The + server works like a translator for devices, things and services and + allows them to interact. + With the powerful rule engine you are able to connect any device available + in the system and create individual scenes and behaviors for your environment. + . + This package will install an integration plugin for Miele appliances. + + Package: nymea-plugin-netatmo Architecture: any Depends: ${shlibs:Depends}, diff --git a/debian/nymea-plugin-miele.install.in b/debian/nymea-plugin-miele.install.in new file mode 100644 index 00000000..1275f706 --- /dev/null +++ b/debian/nymea-plugin-miele.install.in @@ -0,0 +1 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginmiele.so diff --git a/miele/README.md b/miele/README.md new file mode 100644 index 00000000..6bd4b562 --- /dev/null +++ b/miele/README.md @@ -0,0 +1,5 @@ +# Miele + +## Requirements + +## More diff --git a/miele/integrationpluginmiele.cpp b/miele/integrationpluginmiele.cpp new file mode 100644 index 00000000..f5803218 --- /dev/null +++ b/miele/integrationpluginmiele.cpp @@ -0,0 +1,158 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "integrationpluginmiele.h" + +IntegrationPluginMiele::IntegrationPluginMiele() +{ + +} + +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"); + } + if (clientKey.isEmpty() || clientSecret.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); + qCDebug(dcMiele()) << "HomeConnect url:" << url; + m_setupHomeConnectConnections.insert(info->thingId(), miele); + info->setOAuthUrl(url); + info->finish(Thing::ThingErrorNoError); + } else { + qCWarning(dMiele()) << "Unhandled pairing metod!"; + info->finish(Thing::ThingErrorCreationMethodNotSupported); + } +} + +void IntegrationPluginMiele::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret) +{ + Q_UNUSED(username); + + if (info->thingClassId() == mieleAccountThingClassId) { + qCDebug(dcMiele()) << "Redirect url is" << secret; + QUrl url(secret); + QUrlQuery query(url); + QByteArray authorizationCode = query.queryItemValue("code").toLocal8Bit(); + + Miele *miele = m_setupMieleConnections.value(info->thingId()); + if (!miele) { + qWarning(dcMiele()) << "No Miele connection found for device:" << info->thingName(); + m_setupMieleConnections.remove(info->thingId()); + info->finish(Thing::ThingErrorHardwareFailure); + return; + } + qCDebug(dcMiele()) << "Authorization code" << authorizationCode; + miele->getAccessTokenFromAuthorizationCode(authorizationCode); + connect(miele, &Miele::receivedRefreshToken, info, [info, this](const QByteArray &refreshToken){ + qCDebug(dcMiele()) << "Token:" << refreshToken; + + pluginStorage()->beginGroup(info->thingId().toString()); + pluginStorage()->setValue("refresh_token", refreshToken); + pluginStorage()->endGroup(); + + info->finish(Thing::ThingErrorNoError); + }); + } else { + Q_ASSERT_X(false, "confirmPairing", QString("Unhandled thingClassId: %1").arg(info->thingClassId().toString()).toUtf8()); + } +} + +void IntegrationPluginMiele::setupThing(ThingSetupInfo *info) +{ + +} + +void IntegrationPluginMiele::postSetupThing(Thing *thing) +{ + if (!m_pluginTimer15min) { + m_pluginTimer15min = hardwareManager()->pluginTimerManager()->registerTimer(60*15); + connect(m_pluginTimer15min, &PluginTimer::timeout, this, [this]() { + Q_FOREACH (Thing *thing, myThings().filterByThingClassId(mieleAccountThingClassId)) { + Miele *miele = m_mieleConnections.value(thing); + if (!miele) { + qWarning(dcMiele()) << "No Miele account found for" << thing->name(); + continue; + } + miele->getDevices(); + Q_FOREACH (Thing *childThing, myThings().filterByParentId(thing->id())) { + QString deviceId = childThing->paramValue(m_idParamTypeIds.value(childThing->thingClassId())).toString(); + miele->getDevice(deviceId); + } + } + }); + } +} + +void IntegrationPluginMiele::executeAction(ThingActionInfo *info) +{ + Thing *thing = info->thing(); + Action action = info->action(); + Miele *miele = m_mieleConnections.value(myThings().findById(thing->parentId())); + if (!miele) { + return info->finish(Thing::ThingErrorHardwareNotAvailable); + } + QString haid = thing->paramValue(m_idParamTypeIds.value(thing->thingClassId())).toString(); + + if (thing->thingClassId() == ovenThingClassId) { + + } else if (thing->thingClassId() == ovenThingClassId) { + + } else { + + } +} + +void IntegrationPluginMiele::thingRemoved(Thing *thing) +{ + qCDebug(dcMiele) << "Delete " << thing->name(); + + if (thing->thingClassId() == mieleAccountThingClassId) { + m_mieleConnections.take(thing)->deleteLater(); + } + + if (myThings().empty()) { + if (m_pluginTimer15min) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer15min); + m_pluginTimer15min = nullptr; + } + } +} + + diff --git a/miele/integrationpluginmiele.h b/miele/integrationpluginmiele.h new file mode 100644 index 00000000..b49a715a --- /dev/null +++ b/miele/integrationpluginmiele.h @@ -0,0 +1,103 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef INTEGRATIONPLUGINMIELE_H +#define INTEGRATIONPLUGINMIELE_H + +#include "integrations/integrationplugin.h" +#include "plugintimer.h" +#include "miele.h" + +#include +#include + +class IntegrationPluginMiele : public IntegrationPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginmiele.json") + Q_INTERFACES(IntegrationPlugin) + +public: + explicit IntegrationPluginMiele(); + + void startPairing(ThingPairingInfo *info) override; + void confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret) override; + + void setupThing(ThingSetupInfo *info) override; + void postSetupThing(Thing *thing) override; + 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; + + QHash m_asyncSetup; + + QHash m_setupMieleConnections; + QHash m_mieleConnections; + + QHash m_pendingActions; + QHash m_selectedProgram; + + QHash m_idParamTypeIds; + + QHash m_connectedStateTypeIds; + QHash m_doorStateStateTypeIds; + QHash m_localControlStateTypeIds; + QHash m_remoteControlActivationStateTypeIds; + QHash m_remoteStartAllowanceStateTypeIds; + QHash m_operationStateTypeIds; + QHash m_doorStateTypeIds; + QHash m_selectedProgramStateTypeIds; + QHash m_progressStateTypeIds; + QHash m_endTimerStateTypeIds; + + QHash m_startActionTypeIds; + QHash m_stopActionTypeIds; + + QHash m_programFinishedEventTypeIds; + + QHash 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); + +private slots: + void onConnectionChanged(bool connected); + void onAuthenticationStatusChanged(bool authenticated); + void onRequestExecuted(QUuid requestId, bool success); +}; + +#endif // INTEGRATIONPLUGINMIELE_H diff --git a/miele/integrationpluginmiele.json b/miele/integrationpluginmiele.json new file mode 100644 index 00000000..8437f7ee --- /dev/null +++ b/miele/integrationpluginmiele.json @@ -0,0 +1,343 @@ +{ + "id": "d44fad27-668a-4540-9eee-505d0ff13c37", + "name": "miele", + "displayName": "Miele", + "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", + "defaultValue": "", + "type": "QString" + } + ], + "apiKeys": ["miele"], + "vendors": [ + { + "id": "ec1e94c4-d649-4af5-90c2-6c50b1a48e9e", + "name": "miele", + "displayName": "Miele", + "thingClasses": [ + { + "id": "bea89711-7f41-4add-ba27-32dd69515c1c", + "name": "miele", + "displayName": "Miele", + "interfaces": ["account"], + "createMethods": ["user"], + "setupMethod": "oauth", + "paramTypes": [ + ], + "stateTypes": [ + { + "id": "89f4554b-fad2-402e-a0f0-6a76b26d10ea", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "defaultValue": true, + "cached": false, + "type": "bool" + }, + { + "id": "b38f3d86-6d54-442c-a0f3-78ece8887d93", + "name": "loggedIn", + "displayName": "Logged in", + "displayNameEvent": "Logged in changed", + "defaultValue": true, + "type": "bool" + }, + { + "id": "a5b85688-d319-4f61-8907-7da8a2b16d64", + "name": "userDisplayName", + "displayName": "User name", + "displayNameEvent": "User name changed", + "defaultValue": "", + "type": "QString" + } + ] + }, + { + "id": "cf542b5e-2903-497c-95a0-042df6535450", + "name": "oven", + "displayName": "Oven", + "interfaces": ["connectable"], + "createMethods": ["auto"], + "browsable": true, + "paramTypes": [ + { + "id": "1e287843-41dd-49c8-a250-a21ed0e4215c", + "name": "id", + "displayName": "ID", + "defaultValue": "-", + "type": "QString" + } + ], + "stateTypes": [ + { + "id": "9c2c9360-0a4a-43ff-8d15-9cefa80380ec", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "defaultValue": true, + "cached": false, + "type": "bool" + } + ] + }, + { + "id": "82c7cba0-63fc-400c-8c47-dbeee47a3459", + "name": "dishwasher", + "displayName": "Dishwasher", + "interfaces": ["connectable"], + "createMethods": ["auto"], + "browsable": true, + "paramTypes": [ + { + "id": "f61d0fef-b588-4547-a5b6-03dc1622d0c5", + "name": "id", + "displayName": "ID", + "defaultValue": "-", + "type": "QString" + } + ], + "stateTypes": [ + { + "id": "170a1bb2-7c8f-448e-acdc-61a4537f10ab", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "defaultValue": true, + "cached": false, + "type": "bool" + } + ] + }, + { + "id": "8af89a1f-c752-4452-a568-d3b987e4667d", + "name": "coffeeMaker", + "displayName": "Coffee Maker", + "interfaces": ["connectable"], + "createMethods": ["auto"], + "browsable": true, + "paramTypes": [ + { + "id": "438cb528-91f4-481f-9b51-16658fd0a7c8", + "name": "id", + "displayName": "ID", + "defaultValue": "-", + "type": "QString" + } + ], + "stateTypes": [ + { + "id": "4b999b92-def7-4192-9814-38adf9af14e3", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "defaultValue": true, + "cached": false, + "type": "bool" + } + ] + }, + { + "id": "ad9bc908-9f61-4cb3-a8df-db003c6bf171", + "name": "dryer", + "displayName": "Dryer", + "interfaces": ["connectable"], + "createMethods": ["auto"], + "browsable": true, + "paramTypes": [ + { + "id": "3de1300c-766f-4a0b-9bc8-e75aa2402a6a", + "name": "id", + "displayName": "ID", + "defaultValue": "-", + "type": "QString" + } + ], + "stateTypes": [ + { + "id": "4dfce9b8-940b-49cb-8961-b2504fc2f3a6", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "defaultValue": true, + "cached": false, + "type": "bool" + } + ] + }, + { + "id": "a03e17fd-8da8-4977-8ac3-c058ada9f547", + "name": "fridge", + "displayName": "Fridge Freezer", + "interfaces": ["connectable"], + "createMethods": ["auto"], + "paramTypes": [ + { + "id": "945c1fa3-c304-4fc5-8c6b-f88f1f08d47a", + "name": "id", + "displayName": "ID", + "defaultValue": "-", + "type": "QString" + } + ], + "stateTypes": [ + { + "id": "6b066033-a290-459f-b6a1-c63857e6b538", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "defaultValue": true, + "cached": false, + "type": "bool" + } + ] + }, + { + "id": "58a6feaf-b2f7-48e9-822e-6f9526671f04", + "name": "washer", + "displayName": "Washer", + "interfaces": ["connectable"], + "createMethods": ["auto"], + "browsable": true, + "paramTypes": [ + { + "id": "2681e31e-cb0a-486f-b5b7-8ae28f7c96f9", + "name": "id", + "displayName": "ID", + "defaultValue": "-", + "type": "QString" + } + ], + "stateTypes": [ + { + "id": "ba271347-04b6-40a6-86f7-9c260a2fb23f", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "defaultValue": true, + "cached": false, + "type": "bool" + } + ] + }, + { + "id": "bbc55426-f649-4d32-81fc-cebd8a2b46d0", + "name": "cookTop", + "displayName": "Cook top", + "interfaces": ["connectable"], + "createMethods": ["auto"], + "paramTypes": [ + { + "id": "39f094ee-b672-4d75-b106-a21eeec41a34", + "name": "id", + "displayName": "ID", + "defaultValue": "-", + "type": "QString" + } + ], + "stateTypes": [ + { + "id": "08b7960c-e244-4956-808f-53ca7e486dd9", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "defaultValue": true, + "cached": false, + "type": "bool" + } + ] + }, + { + "id": "707e46a5-9ca0-4375-8249-2de19ec669ec", + "name": "hood", + "displayName": "Hood", + "interfaces": ["connectable"], + "createMethods": ["auto"], + "paramTypes": [ + { + "id": "15c3cea9-16c2-4517-ae43-3a9b5f388b82", + "name": "id", + "displayName": "ID", + "defaultValue": "-", + "type": "QString" + } + ], + "stateTypes": [ + { + "id": "be91bef9-c957-4bb8-9c92-1d2a548e762f", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "defaultValue": true, + "cached": false, + "type": "bool" + } + ] + }, + { + "id": "5698a1cf-5b3d-4746-a4fe-e4412d2768ab", + "name": "cleaningRobot", + "displayName": "Cleaning robot", + "interfaces": ["connectable"], + "createMethods": ["auto"], + "paramTypes": [ + { + "id": "629ac68f-89a2-41cf-801b-20df9968d204", + "name": "id", + "displayName": "ID", + "defaultValue": "-", + "type": "QString" + } + ], + "stateTypes": [ + { + "id": "8abe70f4-f49f-4b13-8251-216bea230f04", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "defaultValue": true, + "cached": false, + "type": "bool" + } + ] + }, + { + "id": "40d31505-d6ae-4c45-a2b2-d652e1b85c80", + "name": "cookProcessor", + "displayName": "Cook processor", + "interfaces": ["connectable"], + "createMethods": ["auto"], + "paramTypes": [ + { + "id": "51750b9f-4fc3-4556-a651-c8efcf07abe7", + "name": "id", + "displayName": "ID", + "defaultValue": "-", + "type": "QString" + } + ], + "stateTypes": [ + { + "id": "9eee48ba-b2e2-40fe-81b5-3404b655a497", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "defaultValue": true, + "cached": false, + "type": "bool" + } + ] + } + ] + } + ] +} diff --git a/miele/meta.json b/miele/meta.json new file mode 100644 index 00000000..bbe5b9e8 --- /dev/null +++ b/miele/meta.json @@ -0,0 +1,13 @@ +{ + "title": "Miele", + "tagline": "Connect to Miele appliances.", + "icon": "miele.png", + "stability": "consumer", + "offline": false, + "technologies": [ + "network" + ], + "categories": [ + "online-service" + ] +} diff --git a/miele/miele.cpp b/miele/miele.cpp new file mode 100644 index 00000000..40f8c2c8 --- /dev/null +++ b/miele/miele.cpp @@ -0,0 +1,133 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "miele.h" +#include "extern-plugininfo.h" + +#include +#include +#include +#include +#include + +void Miele::getDevices() +{ + +} + +QUuid Miele::processAction(const QString &deviceId, Miele::ProcessAction action) +{ + QJsonDocument doc; + QJsonObject object; + object.insert("processAction", action); + doc.setObject(object); + return putAction(deviceId, doc); +} + +QUuid Miele::setPower(const QString &deviceId, bool power) +{ + QJsonDocument doc; + QJsonObject object; + if (power) { + object.insert("powerOn", true); + } else { + object.insert("powerOff", true); + } + doc.setObject(object); + return putAction(deviceId, doc); +} + +QUuid Miele::setDeviceName(const QString &deviceId, const QString &deviceName) +{ + QJsonDocument doc; + QJsonObject object; + object.insert("description", deviceName); + doc.setObject(object); + return putAction(deviceId, doc); +} + +QUuid Miele::setLight(const QString &deviceId, bool power) +{ + QJsonDocument doc; + QJsonObject object; + if (power) { + object.insert("light", 1); + } else { + object.insert("light", 2); + } + doc.setObject(object); + return putAction(deviceId, doc); +} + +QUuid Miele::setTargetTemperature(const QString &deviceId, int zone, int targetTemperature) +{ + QJsonDocument doc; + QJsonObject object; + QJsonObject temperatureObj; + temperatureObj.insert("zone", zone); + temperatureObj.insert("value", targetTemperature); + object.insert("targetTemperature", temperatureObj); + doc.setObject(object); + return putAction(deviceId, doc); +} + +QUuid Miele::putAction(const QString &deviceId, const QJsonDocument &action) +{ + QUuid commandId = QUuid::createUuid(); + QUrl url = m_apiUrl; + url.setPath("/v1/devices/"+deviceId+"/actions"); + url.setQuery("language=en"); + + QNetworkRequest request(url); + request.setRawHeader("Authorization", "Bearer "+m_accessToken); + // request.setRawHeader("Accept-Language", "en-US"); + request.setRawHeader("accept", "application/json; charset=utf-8"); + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); + + QNetworkReply *reply = m_networkManager->put(request, action.toJson()); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, this, [this, commandId, reply]{ + + QByteArray rawData = reply->readAll(); + if (!checkStatusCode(reply, rawData)) { + return; + } + QVariantMap map = QJsonDocument::fromJson(rawData).toVariant().toMap(); + qCDebug(dcMiele()) << "Send command" << 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(); + } + emit commandExecuted(commandId, true); + }); + return commandId; +} diff --git a/miele/miele.h b/miele/miele.h new file mode 100644 index 00000000..0fb89c24 --- /dev/null +++ b/miele/miele.h @@ -0,0 +1,139 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef MIELE_H +#define MIELE_H + +#include +#include +#include +#include + +#include "network/networkaccessmanager.h" + +class Miele : public QObject +{ + Q_OBJECT +public: + enum ProcessAction { + ProcessActionsStart = 0, + ProcessActionsStop, + ProcessActionsPause, + ProcessActionsStarSuperfreezing, + ProcessActionsStopSuperfreezing, + ProcessActionsStartSupercooling, + ProcessActionsStopSupercooling + }; + + enum Mode { + ModeNormal, + ModeSabbath + }; + + enum Color { + ColorWhite, + ColorBlue, + ColorRed, + ColorYellow, + ColorOrange, + ColorGreen, + ColorPink, + ColorPurple, + ColorTurquoise + }; + + enum Status { + StatusOff = 1, + StatusOn = 2, + StatusProgrammed = 3, + StatusProgrammedWaitingToStart = 4, + StatusRunnin = 5, + StatusPause = 6, + StatusEndProgrammed = 7, + StatusFailure = 8, + StatusProgrammInterrupted = 9, + StatusIdle = 10, + StatusRinseHold = 11, + StatusService = 12, + StatusSuperfreezing = 13, + StatusSupercooling = 14, + StatusSuperheating = 15, + StatusSupercoolingSuperfreezing = 146, + StatusNotConnected = 255 + }; + + Miele(NetworkAccessManager *networkmanager, const QByteArray &clientKey, const QByteArray &clientSecret, bool simulationMode = false, QObject *parent = nullptr); + QByteArray accessToken(); + QByteArray refreshToken(); + void setSimulationMode(bool simulation); + + QUrl getLoginUrl(const QUrl &redirectUrl, const QString &scope); + + void getAccessTokenFromRefreshToken(const QByteArray &refreshToken); + void getAccessTokenFromAuthorizationCode(const QByteArray &authorizationCode); + + // INFORMATION + void getDevices(); + void getDevice(const QString &deviceId); + + // ACTION + void getActions(const QString &deviceId); //The GET action is used to request a device to send the currently supported actions + QUuid processAction(const QString &deviceId, ProcessAction action); + + QUuid setPower(const QString &deviceId, bool power); + QUuid setDeviceName(const QString &deviceId, const QString &deviceName); + QUuid setLight(const QString &deviceId, bool power); + QUuid setTargetTemperature(const QString &deviceId, int zone, int targetTemperature); + QUuid setColors(const QString &deviceId, Color color); + QUuid setModes(const QString &deviceId, Mode mode); + QUuid setVentilationStep(const QString &deviceId, int step); + QUuid setStartTime(); + + // EVENTS + void getAllEvents(); +private: + + NetworkAccessManager *m_networkManager = nullptr; + + QUuid putAction(const QString &deviceId, const QJsonDocument &action); // + + 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/"); + + bool checkStatusCode(QNetworkReply *reply, const QByteArray &rawData); +signals: + void connectionChanged(bool connected); + void authenticationStatusChanged(bool authenticated); + void receivedRefreshToken(const QByteArray &refreshToken); + void receivedAccessToken(const QByteArray &accessToken); + void commandExecuted(const QUuid &commandId, bool success); +}; +#endif // MIELE diff --git a/miele/miele.pro b/miele/miele.pro new file mode 100644 index 00000000..f3e269d3 --- /dev/null +++ b/miele/miele.pro @@ -0,0 +1,11 @@ +include(../plugins.pri) + +QT += network + +SOURCES += \ + integrationpluginmiele.cpp \ + miele.cpp \ + +HEADERS += \ + integrationpluginmiele.h \ + miele.h \ diff --git a/miele/translations/d44fad27-668a-4540-9eee-505d0ff13c37-en_US.ts b/miele/translations/d44fad27-668a-4540-9eee-505d0ff13c37-en_US.ts new file mode 100644 index 00000000..345b9c8e --- /dev/null +++ b/miele/translations/d44fad27-668a-4540-9eee-505d0ff13c37-en_US.ts @@ -0,0 +1,12 @@ + + + + + IntegrationPluginMiele + + + Client key and/or seceret is not available. + + + + diff --git a/nymea-plugins.pro b/nymea-plugins.pro index 30260ca7..ab627f6e 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -33,6 +33,7 @@ PLUGIN_DIRS = \ lifx \ mailnotification \ mqttclient \ + miele \ nanoleaf \ netatmo \ networkdetector \