642 lines
25 KiB
C++
642 lines
25 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
*
|
|
* Copyright (C) 2013 - 2024, nymea GmbH
|
|
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
|
|
*
|
|
* This file is part of nymea-plugins.
|
|
*
|
|
* nymea-plugins is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* nymea-plugins 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
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with nymea-plugins. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
#include "tado.h"
|
|
#include "extern-plugininfo.h"
|
|
|
|
#include <QJsonArray>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QUrlQuery>
|
|
|
|
Tado::Tado(NetworkAccessManager *networkManager, QObject *parent)
|
|
: QObject(parent)
|
|
, m_networkManager(networkManager)
|
|
{
|
|
m_baseControlUrl = "https://my.tado.com/api/v2";
|
|
m_baseAuthorizationUrl = "https://login.tado.com/oauth2";
|
|
|
|
m_clientId = "1bb50063-6b0c-4d11-bd99-387f4a91cc46";
|
|
|
|
m_refreshTimer.setSingleShot(true);
|
|
connect(&m_refreshTimer, &QTimer::timeout, this, [this]() {
|
|
qCDebug(dcTado()) << "Refresh token...";
|
|
getAccessToken();
|
|
});
|
|
|
|
m_pollAuthenticationTimer.setSingleShot(true);
|
|
m_pollAuthenticationTimer.setInterval(2000);
|
|
connect(&m_pollAuthenticationTimer, &QTimer::timeout, this, [this]() {
|
|
qCDebug(dcTado()) << "Checking authentication status...";
|
|
requestAuthenticationToken();
|
|
});
|
|
}
|
|
|
|
bool Tado::apiAvailable()
|
|
{
|
|
return m_apiAvailable;
|
|
}
|
|
|
|
bool Tado::authenticated()
|
|
{
|
|
return m_authenticationStatus;
|
|
}
|
|
|
|
bool Tado::connected()
|
|
{
|
|
return m_connectionStatus;
|
|
}
|
|
|
|
QString Tado::loginUrl() const
|
|
{
|
|
return m_loginUrl;
|
|
}
|
|
|
|
QString Tado::username() const
|
|
{
|
|
return m_username;
|
|
}
|
|
|
|
QString Tado::refreshToken() const
|
|
{
|
|
return m_refreshToken;
|
|
}
|
|
|
|
void Tado::setRefreshToken(const QString &refreshToken)
|
|
{
|
|
m_refreshToken = refreshToken;
|
|
}
|
|
|
|
void Tado::startAuthentication()
|
|
{
|
|
qCDebug(dcTado()) << "Start authentication process...";
|
|
m_pollAuthenticationCount = 0;
|
|
requestAuthenticationToken();
|
|
}
|
|
|
|
void Tado::getLoginUrl()
|
|
{
|
|
QNetworkRequest request = QNetworkRequest(QUrl(m_baseAuthorizationUrl + "/device_authorize"));
|
|
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/x-www-form-urlencoded");
|
|
|
|
QUrlQuery query;
|
|
query.addQueryItem("client_id", m_clientId);
|
|
query.addQueryItem("scope", "offline_access");
|
|
|
|
QByteArray payload = query.toString(QUrl::FullyEncoded).toUtf8();
|
|
|
|
qCDebug(dcTado()) << "Get login url request" << request.url() << payload;
|
|
|
|
QNetworkReply *reply = m_networkManager->post(request, payload);
|
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
|
connect(reply, &QNetworkReply::finished, this, [reply, this] {
|
|
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
// Check HTTP status code
|
|
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
|
emit getLoginUrlFinished(false);
|
|
|
|
emit connectionError(reply->error());
|
|
|
|
if (reply->error() == QNetworkReply::HostNotFoundError)
|
|
setConnectionStatus(false);
|
|
|
|
if (status == 401 || status == 400)
|
|
setAuthenticationStatus(false);
|
|
|
|
qCWarning(dcTado()) << "Request error:" << status << reply->errorString();
|
|
return;
|
|
}
|
|
|
|
m_apiAvailable = true;
|
|
setConnectionStatus(true);
|
|
|
|
QByteArray data = reply->readAll();
|
|
QJsonParseError error;
|
|
QJsonDocument responseJsonDoc = QJsonDocument::fromJson(data, &error);
|
|
if (error.error != QJsonParseError::NoError) {
|
|
qCDebug(dcTado()) << "Get Token: Received invalid JSON object:" << data;
|
|
emit getLoginUrlFinished(false);
|
|
return;
|
|
}
|
|
|
|
qCDebug(dcTado()) << "Get login url response" << qUtf8Printable(responseJsonDoc.toJson());
|
|
QVariantMap responseMap = responseJsonDoc.toVariant().toMap();
|
|
m_deviceCode = responseMap.value("device_code").toString();
|
|
m_loginUrl = responseMap.value("verification_uri_complete").toString();
|
|
uint pollInterval = responseMap.value("interval").toUInt();
|
|
|
|
qCDebug(dcTado()) << "Login url:" << m_loginUrl;
|
|
qCDebug(dcTado()) << "Device code:" << m_deviceCode;
|
|
qCDebug(dcTado()) << "Poll interval:" << pollInterval;
|
|
m_pollAuthenticationTimer.setInterval(pollInterval * 1000);
|
|
|
|
emit getLoginUrlFinished(true);
|
|
});
|
|
}
|
|
|
|
void Tado::getAccessToken()
|
|
{
|
|
QNetworkRequest request = QNetworkRequest(QUrl(m_baseAuthorizationUrl + "/token"));
|
|
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/x-www-form-urlencoded");
|
|
|
|
QUrlQuery query;
|
|
query.addQueryItem("grant_type", "refresh_token");
|
|
query.addQueryItem("refresh_token", m_refreshToken);
|
|
query.addQueryItem("client_id", m_clientId);
|
|
|
|
QByteArray payload = query.toString(QUrl::FullyEncoded).toUtf8();
|
|
|
|
qCDebug(dcTado()) << "Get access token request" << request.url() << payload;
|
|
|
|
QNetworkReply *reply = m_networkManager->post(request, payload);
|
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
|
connect(reply, &QNetworkReply::finished, this, [reply, this] {
|
|
QByteArray data = reply->readAll();
|
|
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
// Check HTTP status code
|
|
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
|
emit connectionError(reply->error());
|
|
|
|
if (reply->error() == QNetworkReply::HostNotFoundError)
|
|
setConnectionStatus(false);
|
|
|
|
if (status == 401 || status == 400)
|
|
setAuthenticationStatus(false);
|
|
|
|
qCWarning(dcTado()) << "Request error:" << status << reply->errorString() << qUtf8Printable(data);
|
|
return;
|
|
}
|
|
|
|
m_apiAvailable = true;
|
|
setConnectionStatus(true);
|
|
|
|
QJsonParseError error;
|
|
QJsonDocument responseJsonDoc = QJsonDocument::fromJson(data, &error);
|
|
if (error.error != QJsonParseError::NoError) {
|
|
qCDebug(dcTado()) << "Get access token received invalid JSON object:" << data;
|
|
emit getLoginUrlFinished(false);
|
|
return;
|
|
}
|
|
|
|
qCDebug(dcTado()) << "Get access token response" << qUtf8Printable(responseJsonDoc.toJson());
|
|
QVariantMap responseMap = responseJsonDoc.toVariant().toMap();
|
|
|
|
m_accessToken = responseMap.value("access_token").toString();
|
|
emit accessTokenReceived();
|
|
|
|
QString refreshToken = responseMap.value("refresh_token").toString();
|
|
if (m_refreshToken != refreshToken) {
|
|
m_refreshToken = refreshToken;
|
|
emit refreshTokenReceived(m_refreshToken);
|
|
}
|
|
|
|
setAuthenticationStatus(true);
|
|
|
|
// Refresh 10 sekonds before expiration
|
|
m_refreshTimer.setInterval((responseMap.value("expires_in").toUInt() - 10) * 1000);
|
|
m_refreshTimer.start();
|
|
});
|
|
}
|
|
|
|
void Tado::getHomes()
|
|
{
|
|
if (!m_apiAvailable) {
|
|
qCWarning(dcTado()) << "Not sending request, get API credentials first";
|
|
return;
|
|
}
|
|
|
|
if (m_accessToken.isEmpty()) {
|
|
qCWarning(dcTado()) << "Not sending request, get the access token first";
|
|
return;
|
|
}
|
|
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);
|
|
//qCDebug(dcTado()) << "Sending request" << request.url() << request.rawHeaderList();
|
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
|
connect(reply, &QNetworkReply::finished, this, [reply, this] {
|
|
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
|
|
// Check HTTP status code
|
|
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
|
emit connectionError(reply->error());
|
|
if (reply->error() == QNetworkReply::HostNotFoundError) {
|
|
setConnectionStatus(false);
|
|
}
|
|
if (status == 401 || status == 400) {
|
|
setAuthenticationStatus(false);
|
|
}
|
|
qCWarning(dcTado()) << "Request error:" << status << reply->errorString();
|
|
return;
|
|
}
|
|
setConnectionStatus(true);
|
|
setAuthenticationStatus(true);
|
|
|
|
QJsonParseError error;
|
|
QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error);
|
|
if (error.error != QJsonParseError::NoError) {
|
|
qCDebug(dcTado()) << "Get Homes: Recieved invalid JSON object";
|
|
return;
|
|
}
|
|
|
|
qCDebug(dcTado()) << "Get homes response" << qUtf8Printable(data.toJson());
|
|
|
|
QVariantMap responseMap = data.toVariant().toMap();
|
|
QString username = responseMap.value("username").toString();
|
|
if (m_username != username) {
|
|
m_username = username;
|
|
emit usernameChanged(m_username);
|
|
}
|
|
|
|
QList<Home> homes;
|
|
QVariantList homeList = responseMap.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)
|
|
{
|
|
if (!m_apiAvailable) {
|
|
qCWarning(dcTado()) << "Not sending request, get API credentials first";
|
|
return;
|
|
}
|
|
|
|
if (m_accessToken.isEmpty()) {
|
|
qCWarning(dcTado()) << "Not sending request, get the access token first";
|
|
return;
|
|
}
|
|
|
|
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);
|
|
//qCDebug(dcTado()) << "Sending request" << request.url() << request.rawHeaderList();
|
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
|
connect(reply, &QNetworkReply::finished, this, [reply, homeId, this] {
|
|
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
|
|
// Check HTTP status code
|
|
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
|
if (reply->error() == QNetworkReply::HostNotFoundError) {
|
|
setConnectionStatus(false);
|
|
}
|
|
if (status == 401 || status == 400) {
|
|
setAuthenticationStatus(false);
|
|
}
|
|
qCWarning(dcTado()) << "Request error:" << status << reply->errorString();
|
|
return;
|
|
}
|
|
setConnectionStatus(true);
|
|
setAuthenticationStatus(true);
|
|
|
|
QJsonParseError error;
|
|
QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error);
|
|
if (error.error != QJsonParseError::NoError) {
|
|
qCDebug(dcTado()) << "Get Token: Recieved invalid JSON object";
|
|
return;
|
|
}
|
|
QList<Zone> 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)
|
|
{
|
|
if (!m_apiAvailable) {
|
|
qCWarning(dcTado()) << "Not sending request, get API credentials first";
|
|
return;
|
|
}
|
|
|
|
if (m_accessToken.isEmpty()) {
|
|
qCWarning(dcTado()) << "Not sending request, get the access token first";
|
|
return;
|
|
}
|
|
|
|
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);
|
|
//qCDebug(dcTado()) << "Sending request" << request.url() << request.rawHeaderList();
|
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
|
connect(reply, &QNetworkReply::finished, this, [reply, homeId, zoneId, this] {
|
|
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
|
|
// Check HTTP status code
|
|
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
|
emit connectionError(reply->error());
|
|
|
|
if (reply->error() == QNetworkReply::HostNotFoundError) {
|
|
setConnectionStatus(false);
|
|
}
|
|
if (status == 401 || status == 400) {
|
|
setAuthenticationStatus(false);
|
|
}
|
|
qCWarning(dcTado()) << "Request error:" << status << reply->errorString();
|
|
return;
|
|
}
|
|
|
|
setConnectionStatus(true);
|
|
setAuthenticationStatus(true);
|
|
|
|
QJsonParseError error;
|
|
QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error);
|
|
if (error.error != QJsonParseError::NoError) {
|
|
qCDebug(dcTado()) << "Get Token: Recieved invalid JSON object";
|
|
return;
|
|
}
|
|
qCDebug(dcTado()) << "Zone status received:" << qUtf8Printable(data.toJson(QJsonDocument::Indented));
|
|
ZoneState state;
|
|
QVariantMap map = data.toVariant().toMap();
|
|
state.tadoMode = map["tadoMode"].toString();
|
|
state.windowOpenDetected = map["openWindowDetected"].toBool();
|
|
|
|
QVariantMap settingsMap = map["setting"].toMap();
|
|
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["setting"].toMap().value("power").toString() == "ON");
|
|
state.overlaySettingTemperature = overlayMap["setting"].toMap().value("temperature").toDouble();
|
|
} else {
|
|
state.overlayIsSet = false;
|
|
}
|
|
emit zoneStateReceived(homeId, zoneId, state);
|
|
});
|
|
}
|
|
|
|
QUuid Tado::setOverlay(const QString &homeId, const QString &zoneId, bool power, double targetTemperature)
|
|
{
|
|
if (!m_apiAvailable) {
|
|
qCWarning(dcTado()) << "Not sending request, get API credentials first";
|
|
return QUuid();
|
|
}
|
|
|
|
if (m_accessToken.isEmpty()) {
|
|
qCWarning(dcTado()) << "Not sending request, get the access token first";
|
|
return QUuid();
|
|
}
|
|
|
|
QUuid requestId = QUuid::createUuid();
|
|
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());
|
|
|
|
QByteArray body;
|
|
QByteArray powerString;
|
|
if (power)
|
|
powerString = "ON";
|
|
else
|
|
powerString = "OFF";
|
|
|
|
body.append("{\"setting\":{\"type\":\"HEATING\",\"power\":\"" + powerString + "\",\"temperature\":{\"celsius\":" + QVariant(targetTemperature).toByteArray()
|
|
+ "}},\"termination\":{\"type\":\"MANUAL\"}}");
|
|
|
|
//qCDebug(dcTado()) << "Sending request" << body;
|
|
QNetworkReply *reply = m_networkManager->put(request, body);
|
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
|
connect(reply, &QNetworkReply::finished, this, [homeId, zoneId, requestId, reply, this] {
|
|
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
|
emit requestExecuted(requestId, false);
|
|
emit connectionError(reply->error());
|
|
|
|
if (reply->error() == QNetworkReply::HostNotFoundError) {
|
|
setConnectionStatus(false);
|
|
}
|
|
|
|
if (status == 401 || status == 400) { //Unauthorized
|
|
setAuthenticationStatus(false);
|
|
} else if (status == 422) { //Unprocessable Entity
|
|
qCWarning(dcTado()) << "Unprocessable Entity, probably a value out of range";
|
|
} else {
|
|
qCWarning(dcTado()) << "Request error:" << status << reply->errorString();
|
|
}
|
|
return;
|
|
}
|
|
|
|
setAuthenticationStatus(true);
|
|
setConnectionStatus(true);
|
|
emit requestExecuted(requestId, true);
|
|
|
|
QJsonParseError error;
|
|
QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error);
|
|
if (error.error != QJsonParseError::NoError) {
|
|
qCDebug(dcTado()) << "Get Token: Recieved invalid JSON object";
|
|
return;
|
|
}
|
|
QVariantMap map = data.toVariant().toMap();
|
|
|
|
Overlay overlay;
|
|
QVariantMap settingsMap = map["setting"].toMap();
|
|
overlay.zoneType = settingsMap["type"].toString();
|
|
overlay.power = (settingsMap["power"].toString() == "ON");
|
|
overlay.temperature = settingsMap["temperature"].toMap().value("celsius").toDouble();
|
|
QVariantMap terminationMap = map["termination"].toMap();
|
|
overlay.terminationType = terminationMap["type"].toString();
|
|
|
|
overlay.tadoMode = map["type"].toString();
|
|
emit overlayReceived(homeId, zoneId, overlay);
|
|
});
|
|
return requestId;
|
|
}
|
|
|
|
QUuid Tado::deleteOverlay(const QString &homeId, const QString &zoneId)
|
|
{
|
|
if (!m_apiAvailable) {
|
|
qCWarning(dcTado()) << "Not sending request, get API credentials first";
|
|
return QUuid();
|
|
}
|
|
|
|
if (m_accessToken.isEmpty()) {
|
|
qCWarning(dcTado()) << "Not sending request, get the access token first";
|
|
return QUuid();
|
|
}
|
|
|
|
QUuid requestId = QUuid::createUuid();
|
|
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, reply, &QNetworkReply::deleteLater);
|
|
connect(reply, &QNetworkReply::finished, this, [homeId, zoneId, requestId, reply, this] {
|
|
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
if (status < 200 || status > 210 || reply->error() != QNetworkReply::NoError) {
|
|
emit requestExecuted(requestId, false);
|
|
emit connectionError(reply->error());
|
|
|
|
if (reply->error() == QNetworkReply::HostNotFoundError) {
|
|
setConnectionStatus(false);
|
|
}
|
|
|
|
if (status == 401 || status == 400) { //Unauthorized
|
|
setAuthenticationStatus(false);
|
|
} else if (status == 422) { //Unprocessable Entity
|
|
qCWarning(dcTado()) << "Unprocessable Entity, probably a value out of range";
|
|
} else {
|
|
qCWarning(dcTado()) << "Request error:" << status << reply->errorString();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
setAuthenticationStatus(true);
|
|
setConnectionStatus(true);
|
|
emit requestExecuted(requestId, true);
|
|
|
|
QJsonParseError error;
|
|
QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error);
|
|
if (error.error != QJsonParseError::NoError) {
|
|
qCDebug(dcTado()) << "Get Token: Recieved invalid JSON object";
|
|
return;
|
|
}
|
|
|
|
QVariantMap map = data.toVariant().toMap();
|
|
|
|
Overlay overlay;
|
|
QVariantMap settingsMap = map["setting"].toMap();
|
|
overlay.zoneType = settingsMap["type"].toString();
|
|
overlay.power = (settingsMap["power"].toString() == "ON");
|
|
overlay.temperature = settingsMap["temperature"].toMap().value("celsius").toDouble();
|
|
QVariantMap terminationMap = map["termination"].toMap();
|
|
overlay.terminationType = terminationMap["type"].toString();
|
|
|
|
overlay.tadoMode = map["type"].toString();
|
|
emit overlayReceived(homeId, zoneId, overlay);
|
|
});
|
|
return requestId;
|
|
}
|
|
|
|
void Tado::requestAuthenticationToken()
|
|
{
|
|
QNetworkRequest request = QNetworkRequest(QUrl(m_baseAuthorizationUrl + "/token"));
|
|
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/x-www-form-urlencoded");
|
|
|
|
QUrlQuery query;
|
|
query.addQueryItem("client_id", m_clientId);
|
|
query.addQueryItem("device_code", m_deviceCode);
|
|
query.addQueryItem("grant_type", "urn:ietf:params:oauth:grant-type:device_code");
|
|
|
|
QByteArray payload = query.toString(QUrl::FullyEncoded).toUtf8();
|
|
qCDebug(dcTado()) << "Request authentication token" << request.url() << payload;
|
|
|
|
QNetworkReply *reply = m_networkManager->post(request, payload);
|
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
|
connect(reply, &QNetworkReply::finished, this, [reply, this] {
|
|
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
|
qCDebug(dcTado()) << "Request error:" << status << "Retrying:" << m_pollAuthenticationCount << "/" << m_pollAuthenticationLimit;
|
|
|
|
if (m_pollAuthenticationCount >= m_pollAuthenticationLimit) {
|
|
qCWarning(dcTado()) << "Authentication request failed" << m_pollAuthenticationCount << "times. Giving up.";
|
|
emit startAuthenticationFinished(false);
|
|
setAuthenticationStatus(false);
|
|
return;
|
|
}
|
|
|
|
// We poll until the user finished the login or until we reached the limit
|
|
m_pollAuthenticationTimer.start();
|
|
m_pollAuthenticationCount++;
|
|
return;
|
|
}
|
|
|
|
QByteArray data = reply->readAll();
|
|
QJsonParseError error;
|
|
QJsonDocument responseJsonDoc = QJsonDocument::fromJson(data, &error);
|
|
if (error.error != QJsonParseError::NoError) {
|
|
qCDebug(dcTado()) << "Authentication received invalid JSON object:" << data;
|
|
emit startAuthenticationFinished(false);
|
|
setAuthenticationStatus(false);
|
|
return;
|
|
}
|
|
|
|
qCDebug(dcTado()) << "Authentication finished successfully:" << qUtf8Printable(responseJsonDoc.toJson());
|
|
QVariantMap responseMap = responseJsonDoc.toVariant().toMap();
|
|
|
|
m_accessToken = responseMap.value("access_token").toString();
|
|
QString refreshToken = responseMap.value("refresh_token").toString();
|
|
|
|
if (m_refreshToken != refreshToken) {
|
|
m_refreshToken = refreshToken;
|
|
emit refreshTokenReceived(m_refreshToken);
|
|
}
|
|
|
|
emit startAuthenticationFinished(true);
|
|
setAuthenticationStatus(true);
|
|
});
|
|
}
|
|
|
|
void Tado::setAuthenticationStatus(bool status)
|
|
{
|
|
if (m_authenticationStatus != status) {
|
|
m_authenticationStatus = status;
|
|
emit authenticationStatusChanged(status);
|
|
}
|
|
|
|
if (!status)
|
|
m_refreshTimer.stop();
|
|
}
|
|
|
|
void Tado::setConnectionStatus(bool status)
|
|
{
|
|
if (m_connectionStatus != status) {
|
|
m_connectionStatus = status;
|
|
emit connectionChanged(status);
|
|
}
|
|
}
|