From 0a8e8ef2804522846fbc75ccb18357b8ac677007 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Wed, 18 Dec 2019 22:41:32 +0100 Subject: [PATCH] added token refresh and overlay deletion --- tado/deviceplugintado.cpp | 61 ++++++++++++--- tado/deviceplugintado.h | 1 - tado/deviceplugintado.json | 35 ++++++--- tado/tado.cpp | 155 +++++++++++++++++++++++++++++++++++-- tado/tado.h | 18 ++++- 5 files changed, 238 insertions(+), 32 deletions(-) diff --git a/tado/deviceplugintado.cpp b/tado/deviceplugintado.cpp index 1a9528e7..8e6528c5 100644 --- a/tado/deviceplugintado.cpp +++ b/tado/deviceplugintado.cpp @@ -39,11 +39,6 @@ DevicePluginTado::~DevicePluginTado() } -void DevicePluginTado::init() -{ - -} - void DevicePluginTado::startPairing(DevicePairingInfo *info) { info->finish(Device::DeviceErrorNoError, QT_TR_NOOP("Please enter the login credentials.")); @@ -125,7 +120,7 @@ void DevicePluginTado::deviceRemoved(Device *device) void DevicePluginTado::postSetupDevice(Device *device) { if (!m_pluginTimer) { - m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(600); + m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(10); connect(m_pluginTimer, &PluginTimer::timeout, this, &DevicePluginTado::onPluginTimer); } @@ -148,16 +143,42 @@ void DevicePluginTado::postSetupDevice(Device *device) void DevicePluginTado::executeAction(DeviceActionInfo *info) { Device *device = info->device(); + Action action = info->action(); if (device->deviceClassId() == zoneDeviceClassId) { + Tado *tado = m_tadoAccounts.value(device->parentId()); + if (!tado) + return; + QString homeId = device->paramValue(zoneDeviceHomeIdParamTypeId).toString(); + QString zoneId = device->paramValue(zoneDeviceZoneIdParamTypeId).toString(); + if (action.actionTypeId() == zoneModeActionTypeId) { + if (action.param(zoneModeActionModeParamTypeId).value().toString() == "Home") { + tado->deleteOverlay(homeId, zoneId); + } else { - info->finish(Device::DeviceErrorNoError); + } + info->finish(Device::DeviceErrorNoError); + } else if (action.actionTypeId() == zoneTargetTemperatureActionTypeId) { + + double temperature = action.param(zoneTargetTemperatureActionTargetTemperatureParamTypeId).value().toDouble(); + tado->setOverlay(homeId, zoneId, "MANUAL", temperature); + info->finish(Device::DeviceErrorNoError); + } } } void DevicePluginTado::onPluginTimer() { + foreach (Device *device, myDevices().filterByDeviceClassId(zoneDeviceClassId)) { + Tado *tado = m_tadoAccounts.value(device->parentId()); + if (!tado) + continue; + + QString homeId = device->paramValue(zoneDeviceHomeIdParamTypeId).toString(); + QString zoneId = device->paramValue(zoneDeviceZoneIdParamTypeId).toString(); + tado->getZoneState(homeId, zoneId); + } } void DevicePluginTado::onConnectionChanged(bool connected) @@ -168,7 +189,9 @@ void DevicePluginTado::onConnectionChanged(bool connected) Device *device = myDevices().findById(m_tadoAccounts.key(tado)); device->setStateValue(tadoConnectionConnectedStateTypeId, connected); - //TODO set connected state in child devices + foreach(Device *zoneDevice, myDevices().filterByParentDeviceId(device->id())) { + zoneDevice->setStateValue(zoneConnectedStateTypeId, connected); + } } } @@ -231,10 +254,15 @@ void DevicePluginTado::onZonesReceived(const QString &homeId, QList DeviceDescriptors descriptors; foreach (Tado::Zone zone, zones) { + DeviceDescriptor descriptor(zoneDeviceClassId, zone.name, "Type:" + zone.type, parentDevice->id()); ParamList params; params.append(Param(zoneDeviceHomeIdParamTypeId, homeId)); params.append(Param(zoneDeviceZoneIdParamTypeId, zone.id)); + if (myDevices().findByParams(params)) + continue; + + params.append(Param(zoneDeviceTypeParamTypeId, zone.type)); descriptor.setParams(params); descriptors.append(descriptor); } @@ -246,18 +274,29 @@ void DevicePluginTado::onZonesReceived(const QString &homeId, QList void DevicePluginTado::onZoneStateReceived(const QString &homeId, const QString &zoneId, Tado::ZoneState state) { - qCDebug(dcTado()) << "Zone state received:"; Tado *tado = static_cast(sender()); DeviceId parentId = m_tadoAccounts.key(tado); ParamList params; params.append(Param(zoneDeviceHomeIdParamTypeId, homeId)); params.append(Param(zoneDeviceZoneIdParamTypeId, zoneId)); Device *device = myDevices().filterByParentDeviceId(parentId).findByParams(params); + if (!device) + return; + + if (state.overlayIsSet) { + if (state.overlaySettingPower) { + device->setStateValue(zoneModeStateTypeId, "Manual"); + } else { + device->setStateValue(zoneModeStateTypeId, "Off"); + } + } else { + device->setStateValue(zoneModeStateTypeId, "Home"); + } - device->setStateValue(zoneModeStateTypeId, state.tadoMode); device->setStateValue(zonePowerStateTypeId, state.power); device->setStateValue(zoneConnectedStateTypeId, state.connected); - device->setStateValue(zoneTargetTemperatureStateTypeId, state.targetTemperature); + device->setStateValue(zoneTargetTemperatureStateTypeId, state.settingTemperature); device->setStateValue(zoneTemperatureStateTypeId, state.temperature); device->setStateValue(zoneHumidityStateTypeId, state.humidity); + device->setStateValue(zoneWindowOpenStateTypeId, state.windowOpen); } diff --git a/tado/deviceplugintado.h b/tado/deviceplugintado.h index 61950459..46bdfa83 100644 --- a/tado/deviceplugintado.h +++ b/tado/deviceplugintado.h @@ -41,7 +41,6 @@ public: explicit DevicePluginTado(); ~DevicePluginTado(); - void init() override; void startPairing(DevicePairingInfo *info) override; void confirmPairing(DevicePairingInfo *info, const QString &username, const QString &secret) override; void setupDevice(DeviceSetupInfo *info) override; diff --git a/tado/deviceplugintado.json b/tado/deviceplugintado.json index d342c17f..865987bf 100644 --- a/tado/deviceplugintado.json +++ b/tado/deviceplugintado.json @@ -19,8 +19,8 @@ { "id": "2f79bc1d-27ed-480a-b583-728363c83ea6", "name": "connected", - "displayName": "available", - "displayNameEvent": "available changed", + "displayName": "Available", + "displayNameEvent": "Available changed", "type": "bool", "defaultValue": false }, @@ -64,14 +64,21 @@ "type": "QString", "inputType": "TextLine", "readOnly": true + }, + { + "id": "8e86797e-5333-4428-9dba-9ed5ac243b44", + "name": "type", + "displayName": "Type", + "type": "bool", + "defaultValue": false } ], "stateTypes": [ { "id": "9f45a703-6a15-447c-a77a-0df731cda48e", "name": "connected", - "displayName": "available", - "displayNameEvent": "available changed", + "displayName": "Available", + "displayNameEvent": "Available changed", "type": "bool", "defaultValue": false }, @@ -82,10 +89,10 @@ "displayNameEvent": "Mode changed", "displayNameAction": "Set mode", "type": "QString", - "defaultValue": "Auto", + "defaultValue": "Home", "possibleValues": [ "Manual", - "Auto", + "Home", "Off" ], "writable": true @@ -98,11 +105,19 @@ "type": "bool", "defaultValue": false }, + { + "id": "c7a04e26-bb22-406e-b117-262bdb8b9c0e", + "name": "windowOpen", + "displayName": "Window open", + "displayNameEvent": "Window open changed", + "type": "bool", + "defaultValue": false + }, { "id": "80098178-7d92-43dd-a216-23704cc0eaa2", "name": "temperature", - "displayName": "temperature", - "displayNameEvent": "temperature changed", + "displayName": "Temperature", + "displayNameEvent": "Temperature changed", "unit": "DegreeCelsius", "type": "double", "defaultValue": 0 @@ -121,8 +136,8 @@ { "id": "0faaaff1-2a33-44ec-b68d-d8855f584b02", "name": "humidity", - "displayName": "humidity", - "displayNameEvent": "humidity changed", + "displayName": "Humidity", + "displayNameEvent": "Humidity changed", "unit": "Percentage", "type": "double", "defaultValue": 0, diff --git a/tado/tado.cpp b/tado/tado.cpp index b983e93f..554fc08d 100644 --- a/tado/tado.cpp +++ b/tado/tado.cpp @@ -33,7 +33,9 @@ Tado::Tado(NetworkAccessManager *networkManager, const QString &username, QObjec m_networkManager(networkManager), m_username(username) { - + m_refreshTimer = new QTimer(this); + m_refreshTimer->setSingleShot(true); + connect(m_refreshTimer, &QTimer::timeout, this, &Tado::onRefreshTimer); } void Tado::setUsername(const QString &username) @@ -91,7 +93,9 @@ void Tado::getToken(const QString &password) m_accessToken = token.accesToken; token.tokenType = obj["token_type"].toString(); token.refreshToken = obj["refresh_token"].toString(); + m_refreshToken = token.refreshToken; token.expires = obj["expires_in"].toInt(); + m_refreshTimer->start((token.expires - 10)*1000); token.scope = obj["scope"].toString(); token.jti = obj["jti"].toString(); emit tokenReceived(token); @@ -216,30 +220,165 @@ void Tado::getZoneState(const QString &homeId, const QString &zoneId) qDebug(dcTado()) << "Get Token: Recieved invalide JSON object"; return; } - ZoneState state; QVariantMap map = data.toVariant().toMap(); - state.tadoMode = map["tado_mode"].toString(); + state.tadoMode = map["tadoMode"].toString(); + state.windowOpen = map["openWindow"].toBool(); QVariantMap settingsMap = map["setting"].toMap(); - state.type = settingsMap["type"].toString(); - state.power = (settingsMap["power"].toString() == "ON"); - state.targetTemperature = settingsMap["temperature"].toMap().value("celsius").toDouble(); + state.settingType = settingsMap["type"].toString(); + state.settingPower = (settingsMap["power"].toString() == "ON"); + state.settingTemperature = settingsMap["temperature"].toMap().value("celsius").toDouble(); state.connected = (map["link"].toMap().value("state").toString() == "ONLINE"); + QVariantMap activityDataMap = map["activityDataPoints"].toMap(); + state.heatingPowerPercentage = activityDataMap["heatingPower"].toMap().value("percentage").toDouble(); + state.heatingPowerType = activityDataMap["heatingPower"].toMap().value("type").toString(); + QVariantMap dataMap = map["sensorDataPoints"].toMap(); state.temperature = dataMap["insideTemperature"].toMap().value("celsius").toDouble(); state.humidity = dataMap["humidity"].toMap().value("percentage").toDouble(); + if (!map["overlay"].toMap().isEmpty()){ + state.overlayIsSet = true; + QVariantMap overlayMap = map["overlay"].toMap(); + state.overlayType = map["overlayType"].toString(); + state.overlaySettingPower = (overlayMap["settings"].toMap().value("power").toString() == "ON"); + state.overlaySettingTemperature = overlayMap["settings"].toMap().value("temperature").toDouble(); + } else { + state.overlayIsSet = false; + } emit zoneStateReceived(homeId, zoneId, state); }); } void Tado::setOverlay(const QString &homeId, const QString &zoneId, const QString &mode, double targetTemperature) { - Q_UNUSED(zoneId); - Q_UNUSED(homeId); Q_UNUSED(mode); Q_UNUSED(targetTemperature); + + QNetworkRequest request; + request.setUrl(QUrl(m_baseControlUrl+"/homes/"+homeId+"/zones/"+zoneId+"/overlay")); + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json;charset=utf-8"); + request.setRawHeader("Authorization", "Bearer " + m_accessToken.toLocal8Bit()); + QJsonDocument doc; + QJsonObject obj; + QJsonObject setting; + setting.insert("type", "HEATING"); + setting.insert("power", "ON"); + QJsonObject temperature; + temperature.insert("celsius", targetTemperature); + temperature.insert("fahrenheit", (targetTemperature * (9.0/5.0)) + 32.0); + setting.insert("temperature", temperature); + obj.insert("setting", setting); + QJsonObject termination; + termination.insert("type", "MANUAL"); + obj.insert("termination", termination); + doc.setObject(obj); + + QNetworkReply *reply = m_networkManager->put(request, doc.toJson()); + qCDebug(dcTado()) << "Sending request" << request.url() << doc.toJson(); + connect(reply, &QNetworkReply::finished, this, [reply, this] { + reply->deleteLater(); + + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // Check HTTP status code + if (status != 200 || reply->error() != QNetworkReply::NoError) { + if (reply->error() == QNetworkReply::HostNotFoundError) { + emit connectionChanged(false); + } + if (status == 400 || status == 401) { + emit authenticationStatusChanged(false); + } + qCWarning(dcTado()) << "Request error:" << status << reply->errorString(); + return; + } + + QJsonParseError error; + QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error); + if (error.error != QJsonParseError::NoError) { + qDebug(dcTado()) << "Get Token: Recieved invalide JSON object"; + return; + } + QVariantMap map = data.toVariant().toMap(); + qCDebug(dcTado()) << map["type"].toString(); + }); +} + +void Tado::deleteOverlay(const QString &homeId, const QString &zoneId) +{ + QNetworkRequest request; + request.setUrl(QUrl(m_baseControlUrl+"/homes/"+homeId+"/zones/"+zoneId+"/overlay")); + request.setRawHeader("Authorization", "Bearer " + m_accessToken.toLocal8Bit()); + QNetworkReply *reply = m_networkManager->deleteResource(request); + connect(reply, &QNetworkReply::finished, this, [reply, this] { + reply->deleteLater(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // Check HTTP status code + if (status < 200 || status > 210 || reply->error() != QNetworkReply::NoError) { + if (reply->error() == QNetworkReply::HostNotFoundError) { + emit connectionChanged(false); + } + if (status == 400 || status == 401) { + emit authenticationStatusChanged(false); + } + qCWarning(dcTado()) << "Request error:" << status << reply->errorString(); + return; + } + }); +} + +void Tado::onRefreshTimer() +{ + QNetworkRequest request; + request.setUrl(QUrl(m_baseAuthorizationUrl)); + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/x-www-form-urlencoded"); + QByteArray body; + body.append("client_id=" + m_clientId); + body.append("&client_secret=" + m_clientSecret); + body.append("&grant_type=refresh_token"); + body.append("&refresh_token=" + m_refreshToken); + body.append("&scope=home.user"); + + QNetworkReply *reply = m_networkManager->post(request, body); + connect(reply, &QNetworkReply::finished, this, [reply, this] { + reply->deleteLater(); + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // Check HTTP status code + if (status != 200 || reply->error() != QNetworkReply::NoError) { + if (reply->error() == QNetworkReply::HostNotFoundError) { + emit connectionChanged(false); + } + if (status == 400 || status == 401) { + emit authenticationStatusChanged(false); + } + qCWarning(dcTado()) << "Request error:" << status << reply->errorString(); + return; + } + emit connectionChanged(true); + emit authenticationStatusChanged(true); + + QJsonParseError error; + QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error); + if (error.error != QJsonParseError::NoError) { + qDebug(dcTado()) << "Get Token: Recieved invalide JSON object"; + return; + } + Token token; + QVariantMap obj = data.toVariant().toMap(); + token.accesToken = obj["access_token"].toString(); + m_accessToken = token.accesToken; + token.tokenType = obj["token_type"].toString(); + token.refreshToken = obj["refresh_token"].toString(); + m_refreshToken = token.refreshToken; + token.expires = obj["expires_in"].toInt(); + m_refreshTimer->start((token.expires - 10)*1000); + token.scope = obj["scope"].toString(); + token.jti = obj["jti"].toString(); + emit tokenReceived(token); + }); } diff --git a/tado/tado.h b/tado/tado.h index 77ff6d3a..1c8b4467 100644 --- a/tado/tado.h +++ b/tado/tado.h @@ -27,6 +27,7 @@ #include "devices/device.h" #include +#include class Tado : public QObject { @@ -50,11 +51,19 @@ public: struct ZoneState { bool connected; bool power; - double targetTemperature; QString tadoMode; - QString type; + QString settingType; + double settingTemperature; + bool settingPower; double temperature; double humidity; + bool windowOpen; + double heatingPowerPercentage; + QString heatingPowerType; + bool overlayIsSet; + bool overlaySettingPower; + double overlaySettingTemperature; + QString overlayType; }; struct Home { @@ -73,6 +82,7 @@ public: void getZoneState(const QString &homeId, const QString &zoneId); void setOverlay(const QString &homeId, const QString &zoneId, const QString &mode, double targetTemperature); + void deleteOverlay(const QString &homeId, const QString &zoneId); private: QByteArray m_baseAuthorizationUrl = "https://auth.tado.com/oauth/token"; @@ -83,6 +93,8 @@ private: NetworkAccessManager *m_networkManager = nullptr; QString m_username; QString m_accessToken; + QString m_refreshToken; + QTimer *m_refreshTimer = nullptr; signals: void connectionChanged(bool connected); @@ -93,6 +105,8 @@ signals: void homesReceived(QList homes); void zonesReceived(const QString &homeId, QList zones); void zoneStateReceived(const QString &homeId,const QString &zoneId, ZoneState sate); +private slots: + void onRefreshTimer(); };