diff --git a/nymea-plugins.pro b/nymea-plugins.pro index 9e1177a0..6157a086 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -43,6 +43,7 @@ PLUGIN_DIRS = \ simulation \ snapd \ sonos \ + tado \ tasmota \ tcpcommander \ texasinstruments \ diff --git a/tado/deviceplugintado.cpp b/tado/deviceplugintado.cpp new file mode 100644 index 00000000..1a9528e7 --- /dev/null +++ b/tado/deviceplugintado.cpp @@ -0,0 +1,263 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Bernhard Trinnes * + * * + * This file is part of nymea. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "deviceplugintado.h" +#include "devices/device.h" +#include "plugininfo.h" +#include "network/networkaccessmanager.h" + +#include +#include +#include + +DevicePluginTado::DevicePluginTado() +{ + +} + +DevicePluginTado::~DevicePluginTado() +{ + +} + +void DevicePluginTado::init() +{ + +} + +void DevicePluginTado::startPairing(DevicePairingInfo *info) +{ + info->finish(Device::DeviceErrorNoError, QT_TR_NOOP("Please enter the login credentials.")); +} + +void DevicePluginTado::confirmPairing(DevicePairingInfo *info, const QString &username, const QString &password) +{ + Tado *tado = new Tado(hardwareManager()->networkManager(), username, this); + connect(tado, &Tado::tokenReceived, this, &DevicePluginTado::onTokenReceived); + connect(tado, &Tado::authenticationStatusChanged, this, &DevicePluginTado::onAuthenticationStatusChanged); + connect(tado, &Tado::connectionChanged, this, &DevicePluginTado::onConnectionChanged); + connect(tado, &Tado::homesReceived, this, &DevicePluginTado::onHomesReceived); + connect(tado, &Tado::zonesReceived, this, &DevicePluginTado::onZonesReceived); + connect(tado, &Tado::zoneStateReceived, this, &DevicePluginTado::onZoneStateReceived); + m_unfinishedTadoAccounts.insert(info->deviceId(), tado); + m_unfinishedDevicePairings.insert(info->deviceId(), info); + tado->getToken(password); + + pluginStorage()->beginGroup(info->deviceId().toString()); + pluginStorage()->setValue("username", username); + pluginStorage()->setValue("password", password); + pluginStorage()->endGroup(); +} + +void DevicePluginTado::setupDevice(DeviceSetupInfo *info) +{ + Device *device = info->device(); + + if (device->deviceClassId() == tadoConnectionDeviceClassId) { + + qCDebug(dcTado) << "Setup tado connection" << device->name() << device->params(); + pluginStorage()->beginGroup(device->id().toString()); + QString username = pluginStorage()->value("username").toString(); + QString password = pluginStorage()->value("password").toString(); + pluginStorage()->endGroup(); + + Tado *tado; + if (m_unfinishedTadoAccounts.contains(device->id())) { + tado = m_unfinishedTadoAccounts.take(device->id()); + m_tadoAccounts.insert(device->id(), tado); + return info->finish(Device::DeviceErrorNoError); + } else { + tado = new Tado(hardwareManager()->networkManager(), username, this); + connect(tado, &Tado::tokenReceived, this, &DevicePluginTado::onTokenReceived); + connect(tado, &Tado::authenticationStatusChanged, this, &DevicePluginTado::onAuthenticationStatusChanged); + connect(tado, &Tado::connectionChanged, this, &DevicePluginTado::onConnectionChanged); + connect(tado, &Tado::homesReceived, this, &DevicePluginTado::onHomesReceived); + connect(tado, &Tado::zonesReceived, this, &DevicePluginTado::onZonesReceived); + connect(tado, &Tado::zoneStateReceived, this, &DevicePluginTado::onZoneStateReceived); + tado->getToken(password); + m_tadoAccounts.insert(device->id(), tado); + m_asyncDeviceSetup.insert(tado, info); + return; + } + + } else if (device->deviceClassId() == zoneDeviceClassId) { + qCDebug(dcTado) << "Setup tado thermostat" << device->params(); + return info->finish(Device::DeviceErrorNoError); + } + qCWarning(dcTado()) << "Unhandled device class in setupDevice"; +} + +void DevicePluginTado::deviceRemoved(Device *device) +{ + if (device->deviceClassId() == tadoConnectionDeviceClassId) { + Tado *tado = m_tadoAccounts.take(device->id()); + tado->deleteLater(); + + } else if (device->deviceClassId() == zoneDeviceClassId) { + + } + + if (myDevices().isEmpty() && m_pluginTimer) { + m_pluginTimer->deleteLater(); + m_pluginTimer = nullptr; + } +} + +void DevicePluginTado::postSetupDevice(Device *device) +{ + if (!m_pluginTimer) { + m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(600); + connect(m_pluginTimer, &PluginTimer::timeout, this, &DevicePluginTado::onPluginTimer); + } + + if (device->deviceClassId() == tadoConnectionDeviceClassId) { + Tado *tado = m_tadoAccounts.value(device->id()); + device->setStateValue(tadoConnectionUserDisplayNameStateTypeId, tado->username()); + device->setStateValue(tadoConnectionLoggedInStateTypeId, true); + device->setStateValue(tadoConnectionConnectedStateTypeId, true); + + tado->getHomes(); + + } else if (device->deviceClassId() == zoneDeviceClassId) { + if (m_tadoAccounts.contains(device->parentId())) { + Tado *tado = m_tadoAccounts.value(device->parentId()); + tado->getZoneState(device->paramValue(zoneDeviceHomeIdParamTypeId).toString(), device->paramValue(zoneDeviceZoneIdParamTypeId).toString()); + } + } +} + +void DevicePluginTado::executeAction(DeviceActionInfo *info) +{ + Device *device = info->device(); + + if (device->deviceClassId() == zoneDeviceClassId) { + + + info->finish(Device::DeviceErrorNoError); + } +} + +void DevicePluginTado::onPluginTimer() +{ +} + +void DevicePluginTado::onConnectionChanged(bool connected) +{ + Tado *tado = static_cast(sender()); + + if (m_tadoAccounts.values().contains(tado)){ + Device *device = myDevices().findById(m_tadoAccounts.key(tado)); + device->setStateValue(tadoConnectionConnectedStateTypeId, connected); + + //TODO set connected state in child devices + } +} + +void DevicePluginTado::onAuthenticationStatusChanged(bool authenticated) +{ + Tado *tado = static_cast(sender()); + + if (m_unfinishedTadoAccounts.values().contains(tado) && !authenticated){ + DeviceId id = m_tadoAccounts.key(tado); + DevicePairingInfo *info = m_unfinishedDevicePairings.take(id); + info->finish(Device::DeviceErrorSetupFailed); + } + + if (m_tadoAccounts.values().contains(tado)){ + Device *device = myDevices().findById(m_tadoAccounts.key(tado)); + device->setStateValue(tadoConnectionLoggedInStateTypeId, authenticated); + } +} + +void DevicePluginTado::onTokenReceived(Tado::Token token) +{ + Q_UNUSED(token); + + qCDebug(dcTado()) << "Token received"; + Tado *tado = static_cast(sender()); + + if (m_asyncDeviceSetup.contains(tado)) { + DeviceSetupInfo *info = m_asyncDeviceSetup.take(tado); + info->finish(Device::DeviceErrorNoError); + } + + if (m_unfinishedTadoAccounts.values().contains(tado)) { + DeviceId id = m_unfinishedTadoAccounts.key(tado); + DevicePairingInfo *info = m_unfinishedDevicePairings.take(id); + info->finish(Device::DeviceErrorNoError); + } + + if (m_tadoAccounts.values().contains(tado)) { + + } +} + +void DevicePluginTado::onHomesReceived(QList homes) +{ + qCDebug(dcTado()) << "Homes received"; + Tado *tado = static_cast(sender()); + foreach (Tado::Home home, homes) { + tado->getZones(home.id); + } +} + +void DevicePluginTado::onZonesReceived(const QString &homeId, QList zones) +{ + Tado *tado = static_cast(sender()); + + if (m_tadoAccounts.values().contains(tado)) { + + Device *parentDevice = myDevices().findById(m_tadoAccounts.key(tado)); + qCDebug(dcTado()) << "Zones received:" << zones.count() << parentDevice->name(); + + 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)); + descriptor.setParams(params); + descriptors.append(descriptor); + } + emit autoDevicesAppeared(descriptors); + } else { + qCWarning(dcTado()) << "Tado connection not linked to a device Id" << m_tadoAccounts.size() << m_tadoAccounts.key(tado).toString(); + } +} + +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); + + device->setStateValue(zoneModeStateTypeId, state.tadoMode); + device->setStateValue(zonePowerStateTypeId, state.power); + device->setStateValue(zoneConnectedStateTypeId, state.connected); + device->setStateValue(zoneTargetTemperatureStateTypeId, state.targetTemperature); + device->setStateValue(zoneTemperatureStateTypeId, state.temperature); + device->setStateValue(zoneHumidityStateTypeId, state.humidity); +} diff --git a/tado/deviceplugintado.h b/tado/deviceplugintado.h new file mode 100644 index 00000000..61950459 --- /dev/null +++ b/tado/deviceplugintado.h @@ -0,0 +1,71 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2020 Bernhard Trinnes * + * * + * This file is part of nymea. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef DEVICEPLUGINTADO_H +#define DEVICEPLUGINTADO_H + +#include "plugintimer.h" +#include "devices/deviceplugin.h" +#include "network/oauth2.h" +#include "tado.h" + +#include +#include + +class DevicePluginTado : public DevicePlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "deviceplugintado.json") + Q_INTERFACES(DevicePlugin) + +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; + void deviceRemoved(Device *device) override; + void postSetupDevice(Device *device) override; + void executeAction(DeviceActionInfo *info) override; + +private: + PluginTimer *m_pluginTimer = nullptr; + QHash m_unfinishedTadoAccounts; + QHash m_unfinishedDevicePairings; + + QHash m_tadoAccounts; + QHash m_asyncDeviceSetup; + +private slots: + void onPluginTimer(); + + void onConnectionChanged(bool connected); + void onAuthenticationStatusChanged(bool authenticated); + void onTokenReceived(Tado::Token token); + void onHomesReceived(QList homes); + void onZonesReceived(const QString &homeId, QList zones); + void onZoneStateReceived(const QString &homeId,const QString &zoneId, Tado::ZoneState sate); +}; + +#endif // DEVICEPLUGINTADO_H diff --git a/tado/deviceplugintado.json b/tado/deviceplugintado.json new file mode 100644 index 00000000..d342c17f --- /dev/null +++ b/tado/deviceplugintado.json @@ -0,0 +1,137 @@ +{ + "displayName": "Tado", + "name": "Tado", + "id": "b4f2d2ee-50bb-4786-b7f5-261fed204fa5", + "vendors": [ + { + "displayName": "Tado", + "name": "tado", + "id": "23c8a19f-bd6a-4c90-bcc9-2f0c0d9292c5", + "deviceClasses": [ + { + "id": "69be7d15-5658-4442-872e-42abbd8bff81", + "name": "tadoConnection", + "displayName": "Tado Connection", + "interfaces": ["account"], + "createMethods": ["user"], + "setupMethod": "userandpassword", + "stateTypes": [ + { + "id": "2f79bc1d-27ed-480a-b583-728363c83ea6", + "name": "connected", + "displayName": "available", + "displayNameEvent": "available changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "2aed240b-8c5c-418b-a9d1-0d75412c1c27", + "name": "loggedIn", + "displayName": "Logged in", + "displayNameEvent": "Logged in changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "33f55afc-a673-47a4-9fb0-75fdac6a66f4", + "name": "userDisplayName", + "displayName": "Username", + "displayNameEvent": "Username changed", + "type": "QString", + "defaultValue": "-" + } + ] + }, + { + "id": "1a7bb944-fb9c-490a-8a4c-794b27282292", + "name": "zone", + "displayName": "Zone", + "interfaces": ["thermostat", "temperaturesensor", "connectable"], + "createMethods": ["auto"], + "paramTypes": [ + { + "id": "330cad74-6f07-42ad-b226-299927c3c4f0", + "name": "homeId", + "displayName": "Home id", + "type": "QString", + "inputType": "TextLine", + "readOnly": true + }, + { + "id": "cd67476b-978d-4a22-a40e-50cbc941e09e", + "name": "zoneId", + "displayName": "Zone id", + "type": "QString", + "inputType": "TextLine", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "9f45a703-6a15-447c-a77a-0df731cda48e", + "name": "connected", + "displayName": "available", + "displayNameEvent": "available changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "4cecf87c-8a5d-4bc4-a4ba-d2ee6103714b", + "name": "mode", + "displayName": "Mode", + "displayNameEvent": "Mode changed", + "displayNameAction": "Set mode", + "type": "QString", + "defaultValue": "Auto", + "possibleValues": [ + "Manual", + "Auto", + "Off" + ], + "writable": true + }, + { + "id": "e886377d-34b7-4908-ad0d-ed463fc6181d", + "name": "power", + "displayName": "Power", + "displayNameEvent": "Power changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "80098178-7d92-43dd-a216-23704cc0eaa2", + "name": "temperature", + "displayName": "temperature", + "displayNameEvent": "temperature changed", + "unit": "DegreeCelsius", + "type": "double", + "defaultValue": 0 + }, + { + "id": "684fcc62-f12b-4669-988e-4b79f153b0f2", + "name": "targetTemperature", + "displayName": "Target temperature", + "displayNameEvent": "Target temperature changed", + "displayNameAction": "Set target temperature", + "unit": "DegreeCelsius", + "type": "double", + "defaultValue": 0, + "writable": true + }, + { + "id": "0faaaff1-2a33-44ec-b68d-d8855f584b02", + "name": "humidity", + "displayName": "humidity", + "displayNameEvent": "humidity changed", + "unit": "Percentage", + "type": "double", + "defaultValue": 0, + "minValue": 0, + "maxValue": 100 + } + ] + } + ] + } + ] +} diff --git a/tado/tado.cpp b/tado/tado.cpp new file mode 100644 index 00000000..b983e93f --- /dev/null +++ b/tado/tado.cpp @@ -0,0 +1,245 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Bernhard Trinnes * + * * + * This file is part of nymea. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "tado.h" +#include "extern-plugininfo.h" + +#include +#include +#include +#include + +Tado::Tado(NetworkAccessManager *networkManager, const QString &username, QObject *parent) : + QObject(parent), + m_networkManager(networkManager), + m_username(username) +{ + +} + +void Tado::setUsername(const QString &username) +{ + m_username = username; +} + +QString Tado::username() +{ + return m_username; +} + +void Tado::getToken(const QString &password) +{ + 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=password"); + body.append("&scope=home.user"); + body.append("&username=" + m_username); + body.append("&password=" + password); + + QNetworkReply *reply = m_networkManager->post(request, body); + qCDebug(dcTado()) << "Sending request" << request.url() << 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(); + token.expires = obj["expires_in"].toInt(); + token.scope = obj["scope"].toString(); + token.jti = obj["jti"].toString(); + emit tokenReceived(token); + }); +} + +void Tado::getHomes() +{ + QNetworkRequest request; + request.setUrl(QUrl(m_baseControlUrl + "/me")); + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/x-www-form-urlencoded"); + request.setRawHeader("Authorization", "Bearer " + m_accessToken.toLocal8Bit()); + QNetworkReply *reply = m_networkManager->get(request); + qDebug(dcTado()) << "Sending request" << request.url() << request.rawHeaderList(); + 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; + } + QList homes; + QVariantList homeList = data.toVariant().toMap().value("homes").toList(); + foreach (QVariant variant, homeList) { + QVariantMap obj = variant.toMap(); + Home home; + home.id = obj["id"].toString(); + home.name = obj["name"].toString(); + + homes.append(home); + } + emit homesReceived(homes); + }); +} + +void Tado::getZones(const QString &homeId) +{ + QNetworkRequest request; + request.setUrl(QUrl(m_baseControlUrl+"/homes/"+homeId+"/zones")); + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/x-www-form-urlencoded"); + request.setRawHeader("Authorization", "Bearer " + m_accessToken.toLocal8Bit()); + QNetworkReply *reply = m_networkManager->get(request); + qDebug(dcTado()) << "Sending request" << request.url() << request.rawHeaderList(); + connect(reply, &QNetworkReply::finished, this, [reply, homeId, 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; + } + QList zones; + QVariantList list = data.toVariant().toList(); + foreach (QVariant variant, list) { + QVariantMap obj = variant.toMap(); + Zone zone; + zone.id = obj["id"].toString(); + zone.name = obj["name"].toString(); + zone.type = obj["type"].toString(); + zones.append(zone); + } + emit zonesReceived(homeId, zones); + }); +} + +void Tado::getZoneState(const QString &homeId, const QString &zoneId) +{ + QNetworkRequest request; + request.setUrl(QUrl(m_baseControlUrl+"/homes/"+homeId+"/zones/"+zoneId+"/state")); + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/x-www-form-urlencoded"); + request.setRawHeader("Authorization", "Bearer " + m_accessToken.toLocal8Bit()); + QNetworkReply *reply = m_networkManager->get(request); + qDebug(dcTado()) << "Sending request" << request.url() << request.rawHeaderList(); + connect(reply, &QNetworkReply::finished, this, [reply, homeId, zoneId, 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; + } + + ZoneState state; + QVariantMap map = data.toVariant().toMap(); + state.tadoMode = map["tado_mode"].toString(); + + 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.connected = (map["link"].toMap().value("state").toString() == "ONLINE"); + + QVariantMap dataMap = map["sensorDataPoints"].toMap(); + state.temperature = dataMap["insideTemperature"].toMap().value("celsius").toDouble(); + state.humidity = dataMap["humidity"].toMap().value("percentage").toDouble(); + + 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); +} diff --git a/tado/tado.h b/tado/tado.h new file mode 100644 index 00000000..77ff6d3a --- /dev/null +++ b/tado/tado.h @@ -0,0 +1,99 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Bernhard Trinnes * + * * + * This file is part of nymea. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef TADO_H +#define TADO_H + +#include "network/networkaccessmanager.h" +#include "devices/device.h" + +#include + +class Tado : public QObject +{ + Q_OBJECT +public: + struct Token { + QString accesToken; + QString tokenType; + QString refreshToken; + int expires; + QString scope; + QString jti; + }; + + struct Zone { + QString id; + QString name; + QString type; + }; + + struct ZoneState { + bool connected; + bool power; + double targetTemperature; + QString tadoMode; + QString type; + double temperature; + double humidity; + }; + + struct Home { + QString id; + QString name; + }; + + explicit Tado(NetworkAccessManager *networkManager, const QString &username, QObject *parent = nullptr); + + void setUsername(const QString &username); + QString username(); + + void getToken(const QString &password); + void getHomes(); + void getZones(const QString &homeId); + void getZoneState(const QString &homeId, const QString &zoneId); + + void setOverlay(const QString &homeId, const QString &zoneId, const QString &mode, double targetTemperature); + +private: + QByteArray m_baseAuthorizationUrl = "https://auth.tado.com/oauth/token"; + QByteArray m_baseControlUrl = "https://my.tado.com/api/v2"; + QByteArray m_clientSecret = "4HJGRffVR8xb3XdEUQpjgZ1VplJi6Xgw"; + QByteArray m_clientId = "public-api-preview"; + + NetworkAccessManager *m_networkManager = nullptr; + QString m_username; + QString m_accessToken; + +signals: + void connectionChanged(bool connected); + void authenticationStatusChanged(bool authenticated); + + void tokenReceived(Token token); + + void homesReceived(QList homes); + void zonesReceived(const QString &homeId, QList zones); + void zoneStateReceived(const QString &homeId,const QString &zoneId, ZoneState sate); + +}; + +#endif // TADO_H diff --git a/tado/tado.pro b/tado/tado.pro new file mode 100644 index 00000000..6f088926 --- /dev/null +++ b/tado/tado.pro @@ -0,0 +1,14 @@ +include(../plugins.pri) + +QT += network + +TARGET = $$qtLibraryTarget(nymea_deviceplugintado) + +SOURCES += \ + deviceplugintado.cpp \ + tado.cpp + +HEADERS += \ + deviceplugintado.h \ + tado.h +