added token refresh and overlay deletion

master
Boernsman 2019-12-18 22:41:32 +01:00
parent 8d87047bf2
commit 0a8e8ef280
5 changed files with 238 additions and 32 deletions

View File

@ -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<Tado::Zone>
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<Tado::Zone>
void DevicePluginTado::onZoneStateReceived(const QString &homeId, const QString &zoneId, Tado::ZoneState state)
{
qCDebug(dcTado()) << "Zone state received:";
Tado *tado = static_cast<Tado*>(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);
}

View File

@ -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;

View File

@ -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,

View File

@ -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);
});
}

View File

@ -27,6 +27,7 @@
#include "devices/device.h"
#include <QObject>
#include <QTimer>
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<Home> homes);
void zonesReceived(const QString &homeId, QList<Zone> zones);
void zoneStateReceived(const QString &homeId,const QString &zoneId, ZoneState sate);
private slots:
void onRefreshTimer();
};