Merge PR #766: Tado: Update account authentication to device code grant
This commit is contained in:
commit
19cb1c497d
@ -1,6 +1,6 @@
|
|||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
*
|
*
|
||||||
* Copyright 2013 - 2020, nymea GmbH
|
* Copyright 2013 - 2025, nymea GmbH
|
||||||
* Contact: contact@nymea.io
|
* Contact: contact@nymea.io
|
||||||
*
|
*
|
||||||
* This file is part of nymea.
|
* This file is part of nymea.
|
||||||
@ -43,67 +43,74 @@ IntegrationPluginTado::IntegrationPluginTado()
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void IntegrationPluginTado::startPairing(ThingPairingInfo *info)
|
void IntegrationPluginTado::init()
|
||||||
{
|
{
|
||||||
qCDebug(dcTado()) << "Start pairing process, checking the internet connection ...";
|
|
||||||
NetworkAccessManager *network = hardwareManager()->networkManager();
|
|
||||||
QNetworkReply *reply = network->get(QNetworkRequest(QUrl("https://my.tado.com/api/v2")));
|
|
||||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
|
||||||
connect(reply, &QNetworkReply::finished, info, [reply, info] {
|
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::NetworkError::HostNotFoundError) {
|
|
||||||
qCWarning(dcTado()) << "Tado server is not reachable, likely because of a missing internet connection.";
|
|
||||||
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Tado server is not reachable."));
|
|
||||||
} else {
|
|
||||||
qCDebug(dcTado()) << "Internet connection available";
|
|
||||||
info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Please enter the login credentials for your Tado account."));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void IntegrationPluginTado::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &password)
|
void IntegrationPluginTado::startPairing(ThingPairingInfo *info)
|
||||||
{
|
{
|
||||||
qCDebug(dcTado()) << "Confirm pairing" << username << "Network manager available" << hardwareManager()->networkManager()->available();
|
qCDebug(dcTado()) << "Start pairing process ...";
|
||||||
Tado *tado = new Tado(hardwareManager()->networkManager(), username, this);
|
|
||||||
|
Tado *tado = new Tado(hardwareManager()->networkManager(), this);
|
||||||
m_unfinishedTadoAccounts.insert(info->thingId(), tado);
|
m_unfinishedTadoAccounts.insert(info->thingId(), tado);
|
||||||
|
|
||||||
connect(info, &ThingPairingInfo::aborted, this, [info, tado, this]() {
|
connect(info, &ThingPairingInfo::aborted, this, [info, tado, this]() {
|
||||||
qCWarning(dcTado()) << "Thing pairing has been aborted, going to clean-up";
|
qCWarning(dcTado()) << "Thing pairing has been aborted, cleaning up...";
|
||||||
m_unfinishedTadoAccounts.remove(info->thingId());
|
m_unfinishedTadoAccounts.remove(info->thingId());
|
||||||
tado->deleteLater();
|
tado->deleteLater();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
connect(tado, &Tado::getLoginUrlFinished, info, [info, tado, this] (bool success) {
|
||||||
|
if (!success) {
|
||||||
|
info->finish(Thing::ThingErrorAuthenticationFailure);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(info, &ThingPairingInfo::aborted, tado, [info, this]() {
|
||||||
|
qCWarning(dcTado()) << "ThingPairingInfo aborted, cleaning up pending setup connection.";
|
||||||
|
m_unfinishedTadoAccounts.take(info->thingId())->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
qCDebug(dcTado()) << "Tado server is reachable. Starting the OAuth pairing process using" << tado->loginUrl();
|
||||||
|
info->setOAuthUrl(QUrl(tado->loginUrl()));
|
||||||
|
info->finish(Thing::ThingErrorNoError);
|
||||||
|
});
|
||||||
|
|
||||||
|
tado->getLoginUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IntegrationPluginTado::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &password)
|
||||||
|
{
|
||||||
|
Q_UNUSED(username)
|
||||||
|
|
||||||
|
qCDebug(dcTado()) << "Confirm pairing" << password;
|
||||||
|
Tado *tado = m_unfinishedTadoAccounts.value(info->thingId());
|
||||||
|
|
||||||
connect(tado, &Tado::connectionError, info, [info] (QNetworkReply::NetworkError error){
|
connect(tado, &Tado::connectionError, info, [info] (QNetworkReply::NetworkError error){
|
||||||
|
if (error != QNetworkReply::NetworkError::NoError){
|
||||||
if (error == QNetworkReply::NetworkError::ProtocolInvalidOperationError) {
|
|
||||||
qCWarning(dcTado()) << "Confirm pairing failed, wrong username or password";
|
|
||||||
info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("Wrong username or password."));
|
|
||||||
} else if (error != QNetworkReply::NetworkError::NoError){
|
|
||||||
qCWarning(dcTado()) << "Confirm pairing failed" << error;
|
qCWarning(dcTado()) << "Confirm pairing failed" << error;
|
||||||
info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("Connection error"));
|
info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("A connection error occurred."));
|
||||||
}
|
|
||||||
// info->finish(success) will be called after the token has been received
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(tado, &Tado::apiCredentialsReceived, info, [info, password, tado] (bool success) {
|
|
||||||
if (success) {
|
|
||||||
tado->getToken(password);
|
|
||||||
} else {
|
|
||||||
info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Client credentials not found, the plug-in version might be outdated."));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(tado, &Tado::tokenReceived, info, [this, info, username, password](Tado::Token token) {
|
connect(tado, &Tado::startAuthenticationFinished, info, [info, tado, this](bool success) {
|
||||||
Q_UNUSED(token)
|
if (!success) {
|
||||||
|
info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Failed to authenticate with tado server."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(dcTado()) << "Authentication finished successfully.";
|
||||||
pluginStorage()->beginGroup(info->thingId().toString());
|
pluginStorage()->beginGroup(info->thingId().toString());
|
||||||
pluginStorage()->setValue("username", username);
|
pluginStorage()->setValue("refreshToken", tado->refreshToken());
|
||||||
pluginStorage()->setValue("password", password);
|
|
||||||
pluginStorage()->endGroup();
|
pluginStorage()->endGroup();
|
||||||
|
|
||||||
info->finish(Thing::ThingErrorNoError);
|
info->finish(Thing::ThingErrorNoError);
|
||||||
});
|
});
|
||||||
tado->getApiCredentials();
|
|
||||||
|
tado->startAuthentication();
|
||||||
}
|
}
|
||||||
|
|
||||||
void IntegrationPluginTado::setupThing(ThingSetupInfo *info)
|
void IntegrationPluginTado::setupThing(ThingSetupInfo *info)
|
||||||
@ -112,8 +119,9 @@ void IntegrationPluginTado::setupThing(ThingSetupInfo *info)
|
|||||||
|
|
||||||
if (thing->thingClassId() == tadoAccountThingClassId) {
|
if (thing->thingClassId() == tadoAccountThingClassId) {
|
||||||
|
|
||||||
qCDebug(dcTado) << "Setup Tado account" << thing->name() << thing->params();
|
qCDebug(dcTado) << "Setting up Tado account" << thing->name() << thing->params();
|
||||||
Tado *tado;
|
|
||||||
|
Tado *tado = nullptr;
|
||||||
|
|
||||||
if (m_tadoAccounts.contains(thing->id())) {
|
if (m_tadoAccounts.contains(thing->id())) {
|
||||||
qCDebug(dcTado()) << "Setup after reconfigure, cleaning up";
|
qCDebug(dcTado()) << "Setup after reconfigure, cleaning up";
|
||||||
@ -125,51 +133,65 @@ void IntegrationPluginTado::setupThing(ThingSetupInfo *info)
|
|||||||
tado = m_unfinishedTadoAccounts.take(thing->id());
|
tado = m_unfinishedTadoAccounts.take(thing->id());
|
||||||
m_tadoAccounts.insert(thing->id(), tado);
|
m_tadoAccounts.insert(thing->id(), tado);
|
||||||
info->finish(Thing::ThingErrorNoError);
|
info->finish(Thing::ThingErrorNoError);
|
||||||
} else {
|
|
||||||
pluginStorage()->beginGroup(thing->id().toString());
|
pluginStorage()->beginGroup(thing->id().toString());
|
||||||
QString username = pluginStorage()->value("username").toString();
|
pluginStorage()->setValue("refreshToken", tado->refreshToken());
|
||||||
QString password = pluginStorage()->value("password").toString();
|
pluginStorage()->endGroup();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Load refresh token
|
||||||
|
pluginStorage()->beginGroup(thing->id().toString());
|
||||||
|
QString refreshToken = pluginStorage()->value("refreshToken").toString();
|
||||||
|
qCDebug(dcTado()) << "Loaded refresh token" << refreshToken;
|
||||||
pluginStorage()->endGroup();
|
pluginStorage()->endGroup();
|
||||||
|
|
||||||
tado = new Tado(hardwareManager()->networkManager(), username, this);
|
if (refreshToken.isEmpty()) {
|
||||||
|
info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Could not authenticate on the server. Please reconfigure the connection."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tado = new Tado(hardwareManager()->networkManager(), this);
|
||||||
m_tadoAccounts.insert(thing->id(), tado);
|
m_tadoAccounts.insert(thing->id(), tado);
|
||||||
connect(info, &ThingSetupInfo::aborted, [info, this] {
|
tado->setRefreshToken(refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete any leftover username password from deprecated password grant flow,
|
||||||
|
// storing passwords in plaintext does not correspond to good manners
|
||||||
|
pluginStorage()->beginGroup(thing->id().toString());
|
||||||
|
pluginStorage()->remove("username");
|
||||||
|
pluginStorage()->remove("password");
|
||||||
|
pluginStorage()->endGroup();
|
||||||
|
|
||||||
|
connect(info, &ThingSetupInfo::aborted, this, [info, this] {
|
||||||
|
if (m_tadoAccounts.contains(info->thing()->id())) {
|
||||||
|
m_tadoAccounts.take(info->thing()->id())->deleteLater();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(tado, &Tado::refreshTokenReceived, this, [thing, this](const QString &refreshToken){
|
||||||
|
pluginStorage()->beginGroup(thing->id().toString());
|
||||||
|
pluginStorage()->setValue("refreshToken", refreshToken);
|
||||||
|
pluginStorage()->endGroup();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(tado, &Tado::accessTokenReceived, info, [info]() {
|
||||||
|
qCDebug(dcTado()) << "Token received, account setup successfull";
|
||||||
|
info->finish(Thing::ThingErrorNoError);
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(tado, &Tado::connectionError, info, [this, info] (QNetworkReply::NetworkError error) {
|
||||||
|
if (error != QNetworkReply::NetworkError::NoError){
|
||||||
if (m_tadoAccounts.contains(info->thing()->id())) {
|
if (m_tadoAccounts.contains(info->thing()->id())) {
|
||||||
Tado *tado = m_tadoAccounts.take(info->thing()->id());
|
Tado *tado = m_tadoAccounts.take(info->thing()->id());
|
||||||
tado->deleteLater();
|
tado->deleteLater();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
connect(tado, &Tado::apiCredentialsReceived, info, [password, tado] {
|
qCWarning(dcTado()) << "Connection error during setup:" << error;
|
||||||
tado->getToken(password);
|
info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("Connection error"));
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
connect(tado, &Tado::tokenReceived, info, [ info](Tado::Token token) {
|
connect(tado, &Tado::usernameChanged, this, &IntegrationPluginTado::onUsernameChanged);
|
||||||
Q_UNUSED(token)
|
|
||||||
|
|
||||||
qCDebug(dcTado()) << "Token received, account setup successfull";
|
|
||||||
info->finish(Thing::ThingErrorNoError);
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(tado, &Tado::connectionError, info, [this, info] (QNetworkReply::NetworkError error) {
|
|
||||||
|
|
||||||
if (error != QNetworkReply::NetworkError::NoError){
|
|
||||||
|
|
||||||
if (m_tadoAccounts.contains(info->thing()->id())) {
|
|
||||||
Tado *tado = m_tadoAccounts.take(info->thing()->id());
|
|
||||||
tado->deleteLater();
|
|
||||||
}
|
|
||||||
if (error == QNetworkReply::NetworkError::ProtocolInvalidOperationError) {
|
|
||||||
qCWarning(dcTado()) << "Confirm pairing failed, wrong username or password";
|
|
||||||
info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("Wrong username or password."));
|
|
||||||
} else {
|
|
||||||
qCWarning(dcTado()) << "Confirm pairing failed" << error;
|
|
||||||
info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("Connection error"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
tado->getApiCredentials();
|
|
||||||
}
|
|
||||||
connect(tado, &Tado::authenticationStatusChanged, this, &IntegrationPluginTado::onAuthenticationStatusChanged);
|
connect(tado, &Tado::authenticationStatusChanged, this, &IntegrationPluginTado::onAuthenticationStatusChanged);
|
||||||
connect(tado, &Tado::requestExecuted, this, &IntegrationPluginTado::onRequestExecuted);
|
connect(tado, &Tado::requestExecuted, this, &IntegrationPluginTado::onRequestExecuted);
|
||||||
connect(tado, &Tado::connectionChanged, this, &IntegrationPluginTado::onConnectionChanged);
|
connect(tado, &Tado::connectionChanged, this, &IntegrationPluginTado::onConnectionChanged);
|
||||||
@ -177,6 +199,9 @@ void IntegrationPluginTado::setupThing(ThingSetupInfo *info)
|
|||||||
connect(tado, &Tado::zonesReceived, this, &IntegrationPluginTado::onZonesReceived);
|
connect(tado, &Tado::zonesReceived, this, &IntegrationPluginTado::onZonesReceived);
|
||||||
connect(tado, &Tado::zoneStateReceived, this, &IntegrationPluginTado::onZoneStateReceived);
|
connect(tado, &Tado::zoneStateReceived, this, &IntegrationPluginTado::onZoneStateReceived);
|
||||||
connect(tado, &Tado::overlayReceived, this, &IntegrationPluginTado::onOverlayReceived);
|
connect(tado, &Tado::overlayReceived, this, &IntegrationPluginTado::onOverlayReceived);
|
||||||
|
|
||||||
|
tado->getAccessToken();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
} else if (thing->thingClassId() == zoneThingClassId) {
|
} else if (thing->thingClassId() == zoneThingClassId) {
|
||||||
@ -205,6 +230,9 @@ void IntegrationPluginTado::thingRemoved(Thing *thing)
|
|||||||
tado->deleteLater();
|
tado->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up storage
|
||||||
|
pluginStorage()->remove(thing->id().toString());
|
||||||
|
|
||||||
if (myThings().isEmpty() && m_pluginTimer) {
|
if (myThings().isEmpty() && m_pluginTimer) {
|
||||||
m_pluginTimer->deleteLater();
|
m_pluginTimer->deleteLater();
|
||||||
m_pluginTimer = nullptr;
|
m_pluginTimer = nullptr;
|
||||||
@ -220,7 +248,7 @@ void IntegrationPluginTado::postSetupThing(Thing *thing)
|
|||||||
|
|
||||||
if (thing->thingClassId() == tadoAccountThingClassId) {
|
if (thing->thingClassId() == tadoAccountThingClassId) {
|
||||||
Tado *tado = m_tadoAccounts.value(thing->id());
|
Tado *tado = m_tadoAccounts.value(thing->id());
|
||||||
thing->setStateValue(tadoAccountUserDisplayNameStateTypeId, tado->username());
|
//thing->setStateValue(tadoAccountUserDisplayNameStateTypeId, tado->username());
|
||||||
thing->setStateValue(tadoAccountLoggedInStateTypeId, true);
|
thing->setStateValue(tadoAccountLoggedInStateTypeId, true);
|
||||||
thing->setStateValue(tadoAccountConnectedStateTypeId, true);
|
thing->setStateValue(tadoAccountConnectedStateTypeId, true);
|
||||||
tado->getHomes();
|
tado->getHomes();
|
||||||
@ -260,7 +288,7 @@ void IntegrationPluginTado::executeAction(ThingActionInfo *info)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_asyncActions.insert(requestId, info);
|
m_asyncActions.insert(requestId, info);
|
||||||
connect(info, &ThingActionInfo::aborted, [requestId, this] {m_asyncActions.remove(requestId);});
|
connect(info, &ThingActionInfo::aborted, thing, [requestId, this] {m_asyncActions.remove(requestId);});
|
||||||
} else if (action.actionTypeId() == zoneTargetTemperatureActionTypeId) {
|
} else if (action.actionTypeId() == zoneTargetTemperatureActionTypeId) {
|
||||||
|
|
||||||
double temperature = action.param(zoneTargetTemperatureActionTargetTemperatureParamTypeId).value().toDouble();
|
double temperature = action.param(zoneTargetTemperatureActionTargetTemperatureParamTypeId).value().toDouble();
|
||||||
@ -271,7 +299,7 @@ void IntegrationPluginTado::executeAction(ThingActionInfo *info)
|
|||||||
requestId = tado->setOverlay(homeId, zoneId, true, temperature);
|
requestId = tado->setOverlay(homeId, zoneId, true, temperature);
|
||||||
}
|
}
|
||||||
m_asyncActions.insert(requestId, info);
|
m_asyncActions.insert(requestId, info);
|
||||||
connect(info, &ThingActionInfo::aborted, [requestId, this] {m_asyncActions.remove(requestId);});
|
connect(info, &ThingActionInfo::aborted, thing, [requestId, this] {m_asyncActions.remove(requestId);});
|
||||||
} else if (action.actionTypeId() == zonePowerActionTypeId) {
|
} else if (action.actionTypeId() == zonePowerActionTypeId) {
|
||||||
bool power = action.param(zonePowerActionPowerParamTypeId).value().toBool();
|
bool power = action.param(zonePowerActionPowerParamTypeId).value().toBool();
|
||||||
thing->setStateValue(zonePowerStateTypeId, power); // the actual power set response might be slow
|
thing->setStateValue(zonePowerStateTypeId, power); // the actual power set response might be slow
|
||||||
@ -283,7 +311,7 @@ void IntegrationPluginTado::executeAction(ThingActionInfo *info)
|
|||||||
requestId = tado->setOverlay(homeId, zoneId, true, temperature);
|
requestId = tado->setOverlay(homeId, zoneId, true, temperature);
|
||||||
}
|
}
|
||||||
m_asyncActions.insert(requestId, info);
|
m_asyncActions.insert(requestId, info);
|
||||||
connect(info, &ThingActionInfo::aborted, [requestId, this] {m_asyncActions.remove(requestId);});
|
connect(info, &ThingActionInfo::aborted, thing, [requestId, this] {m_asyncActions.remove(requestId);});
|
||||||
} else {
|
} else {
|
||||||
qCWarning(dcTado()) << "Execute action, unhandled actionTypeId" << action.actionTypeId();
|
qCWarning(dcTado()) << "Execute action, unhandled actionTypeId" << action.actionTypeId();
|
||||||
info->finish(Thing::ThingErrorActionTypeNotFound);
|
info->finish(Thing::ThingErrorActionTypeNotFound);
|
||||||
@ -299,12 +327,9 @@ void IntegrationPluginTado::onPluginTimer()
|
|||||||
Q_FOREACH(Tado *tado, m_tadoAccounts){
|
Q_FOREACH(Tado *tado, m_tadoAccounts){
|
||||||
ThingId accountThingId = m_tadoAccounts.key(tado);
|
ThingId accountThingId = m_tadoAccounts.key(tado);
|
||||||
if (!tado->authenticated()) {
|
if (!tado->authenticated()) {
|
||||||
pluginStorage()->beginGroup(accountThingId.toString());
|
tado->getAccessToken();
|
||||||
QString password = pluginStorage()->value("password").toString();
|
|
||||||
pluginStorage()->endGroup();
|
|
||||||
tado->getToken(password);
|
|
||||||
} else {
|
} else {
|
||||||
Q_FOREACH(Thing *thing, myThings().filterByParentId(accountThingId)) {
|
foreach (Thing *thing, myThings().filterByParentId(accountThingId)) {
|
||||||
if (thing->thingClassId() == zoneThingClassId) {
|
if (thing->thingClassId() == zoneThingClassId) {
|
||||||
QString homeId = thing->paramValue(zoneThingHomeIdParamTypeId).toString();
|
QString homeId = thing->paramValue(zoneThingHomeIdParamTypeId).toString();
|
||||||
QString zoneId = thing->paramValue(zoneThingZoneIdParamTypeId).toString();
|
QString zoneId = thing->paramValue(zoneThingZoneIdParamTypeId).toString();
|
||||||
@ -326,7 +351,7 @@ void IntegrationPluginTado::onConnectionChanged(bool connected)
|
|||||||
thing->setStateValue(tadoAccountConnectedStateTypeId, connected);
|
thing->setStateValue(tadoAccountConnectedStateTypeId, connected);
|
||||||
|
|
||||||
if (!connected) {
|
if (!connected) {
|
||||||
Q_FOREACH(Thing *child, myThings().filterByParentId(thing->id())) {
|
foreach (Thing *child, myThings().filterByParentId(thing->id())) {
|
||||||
if (child->thingClassId() == zoneThingClassId) {
|
if (child->thingClassId() == zoneThingClassId) {
|
||||||
child->setStateValue(zoneConnectedStateTypeId, connected);
|
child->setStateValue(zoneConnectedStateTypeId, connected);
|
||||||
}
|
}
|
||||||
@ -347,7 +372,7 @@ void IntegrationPluginTado::onAuthenticationStatusChanged(bool authenticated)
|
|||||||
}
|
}
|
||||||
thing->setStateValue(tadoAccountLoggedInStateTypeId, authenticated);
|
thing->setStateValue(tadoAccountLoggedInStateTypeId, authenticated);
|
||||||
if (!authenticated) {
|
if (!authenticated) {
|
||||||
Q_FOREACH(Thing *child, myThings().filterByParentId(thing->id())) {
|
foreach (Thing *child, myThings().filterByParentId(thing->id())) {
|
||||||
if (child->thingClassId() == zoneThingClassId) {
|
if (child->thingClassId() == zoneThingClassId) {
|
||||||
child->setStateValue(zoneConnectedStateTypeId, authenticated);
|
child->setStateValue(zoneConnectedStateTypeId, authenticated);
|
||||||
}
|
}
|
||||||
@ -356,6 +381,16 @@ void IntegrationPluginTado::onAuthenticationStatusChanged(bool authenticated)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IntegrationPluginTado::onUsernameChanged(const QString &username)
|
||||||
|
{
|
||||||
|
Tado *tado = static_cast<Tado*>(sender());
|
||||||
|
|
||||||
|
if (m_tadoAccounts.values().contains(tado)){
|
||||||
|
Thing *thing = myThings().findById(m_tadoAccounts.key(tado));
|
||||||
|
thing->setStateValue(tadoAccountUserDisplayNameStateTypeId, username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void IntegrationPluginTado::onRequestExecuted(QUuid requestId, bool success)
|
void IntegrationPluginTado::onRequestExecuted(QUuid requestId, bool success)
|
||||||
{
|
{
|
||||||
if (m_asyncActions.contains(requestId)) {
|
if (m_asyncActions.contains(requestId)) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
*
|
*
|
||||||
* Copyright 2013 - 2020, nymea GmbH
|
* Copyright 2013 - 2025, nymea GmbH
|
||||||
* Contact: contact@nymea.io
|
* Contact: contact@nymea.io
|
||||||
*
|
*
|
||||||
* This file is part of nymea.
|
* This file is part of nymea.
|
||||||
@ -39,7 +39,6 @@
|
|||||||
#include <network/oauth2.h>
|
#include <network/oauth2.h>
|
||||||
|
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
class IntegrationPluginTado : public IntegrationPlugin
|
class IntegrationPluginTado : public IntegrationPlugin
|
||||||
@ -50,8 +49,12 @@ class IntegrationPluginTado : public IntegrationPlugin
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
explicit IntegrationPluginTado();
|
explicit IntegrationPluginTado();
|
||||||
|
|
||||||
|
void init() override;
|
||||||
|
|
||||||
void startPairing(ThingPairingInfo *info) override;
|
void startPairing(ThingPairingInfo *info) override;
|
||||||
void confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret) override;
|
void confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret) override;
|
||||||
|
|
||||||
void setupThing(ThingSetupInfo *info) override;
|
void setupThing(ThingSetupInfo *info) override;
|
||||||
void thingRemoved(Thing *thing) override;
|
void thingRemoved(Thing *thing) override;
|
||||||
void postSetupThing(Thing *thing) override;
|
void postSetupThing(Thing *thing) override;
|
||||||
@ -59,9 +62,9 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
PluginTimer *m_pluginTimer = nullptr;
|
PluginTimer *m_pluginTimer = nullptr;
|
||||||
QHash<ThingId, Tado*> m_unfinishedTadoAccounts;
|
QHash<ThingId, Tado *> m_unfinishedTadoAccounts;
|
||||||
|
|
||||||
QHash<ThingId, Tado*> m_tadoAccounts;
|
QHash<ThingId, Tado *> m_tadoAccounts;
|
||||||
QHash<QUuid, ThingActionInfo *> m_asyncActions;
|
QHash<QUuid, ThingActionInfo *> m_asyncActions;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
@ -69,6 +72,7 @@ private slots:
|
|||||||
|
|
||||||
void onConnectionChanged(bool connected);
|
void onConnectionChanged(bool connected);
|
||||||
void onAuthenticationStatusChanged(bool authenticated);
|
void onAuthenticationStatusChanged(bool authenticated);
|
||||||
|
void onUsernameChanged(const QString &username);
|
||||||
void onRequestExecuted(QUuid requestId, bool success);
|
void onRequestExecuted(QUuid requestId, bool success);
|
||||||
void onHomesReceived(QList<Tado::Home> homes);
|
void onHomesReceived(QList<Tado::Home> homes);
|
||||||
void onZonesReceived(const QString &homeId, QList<Tado::Zone> zones);
|
void onZonesReceived(const QString &homeId, QList<Tado::Zone> zones);
|
||||||
|
|||||||
@ -14,13 +14,12 @@
|
|||||||
"displayName": "Tado account",
|
"displayName": "Tado account",
|
||||||
"interfaces": ["account"],
|
"interfaces": ["account"],
|
||||||
"createMethods": ["user"],
|
"createMethods": ["user"],
|
||||||
"setupMethod": "userandpassword",
|
"setupMethod": "oauth",
|
||||||
"stateTypes": [
|
"stateTypes": [
|
||||||
{
|
{
|
||||||
"id": "2f79bc1d-27ed-480a-b583-728363c83ea6",
|
"id": "2f79bc1d-27ed-480a-b583-728363c83ea6",
|
||||||
"name": "connected",
|
"name": "connected",
|
||||||
"displayName": "Connected",
|
"displayName": "Connected",
|
||||||
"displayNameEvent": "Connected changed",
|
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
},
|
},
|
||||||
@ -28,7 +27,6 @@
|
|||||||
"id": "2aed240b-8c5c-418b-a9d1-0d75412c1c27",
|
"id": "2aed240b-8c5c-418b-a9d1-0d75412c1c27",
|
||||||
"name": "loggedIn",
|
"name": "loggedIn",
|
||||||
"displayName": "Logged in",
|
"displayName": "Logged in",
|
||||||
"displayNameEvent": "Logged in changed",
|
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
},
|
},
|
||||||
@ -36,7 +34,6 @@
|
|||||||
"id": "33f55afc-a673-47a4-9fb0-75fdac6a66f4",
|
"id": "33f55afc-a673-47a4-9fb0-75fdac6a66f4",
|
||||||
"name": "userDisplayName",
|
"name": "userDisplayName",
|
||||||
"displayName": "Username",
|
"displayName": "Username",
|
||||||
"displayNameEvent": "Username changed",
|
|
||||||
"type": "QString",
|
"type": "QString",
|
||||||
"defaultValue": "-"
|
"defaultValue": "-"
|
||||||
}
|
}
|
||||||
@ -78,7 +75,6 @@
|
|||||||
"id": "9f45a703-6a15-447c-a77a-0df731cda48e",
|
"id": "9f45a703-6a15-447c-a77a-0df731cda48e",
|
||||||
"name": "connected",
|
"name": "connected",
|
||||||
"displayName": "Connected",
|
"displayName": "Connected",
|
||||||
"displayNameEvent": "Connected changed",
|
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
},
|
},
|
||||||
@ -86,7 +82,6 @@
|
|||||||
"id": "4cecf87c-8a5d-4bc4-a4ba-d2ee6103714b",
|
"id": "4cecf87c-8a5d-4bc4-a4ba-d2ee6103714b",
|
||||||
"name": "mode",
|
"name": "mode",
|
||||||
"displayName": "Mode",
|
"displayName": "Mode",
|
||||||
"displayNameEvent": "Mode changed",
|
|
||||||
"displayNameAction": "Set mode",
|
"displayNameAction": "Set mode",
|
||||||
"type": "QString",
|
"type": "QString",
|
||||||
"defaultValue": "Tado",
|
"defaultValue": "Tado",
|
||||||
@ -101,7 +96,6 @@
|
|||||||
"id": "8b800998-5c2d-4940-9d0e-036979cf49ca",
|
"id": "8b800998-5c2d-4940-9d0e-036979cf49ca",
|
||||||
"name": "tadoMode",
|
"name": "tadoMode",
|
||||||
"displayName": "Tado mode",
|
"displayName": "Tado mode",
|
||||||
"displayNameEvent": "Tado mode changed",
|
|
||||||
"type": "QString",
|
"type": "QString",
|
||||||
"defaultValue": "Tado"
|
"defaultValue": "Tado"
|
||||||
},
|
},
|
||||||
@ -109,7 +103,6 @@
|
|||||||
"id": "e886377d-34b7-4908-ad0d-ed463fc6181d",
|
"id": "e886377d-34b7-4908-ad0d-ed463fc6181d",
|
||||||
"name": "power",
|
"name": "power",
|
||||||
"displayName": "Power",
|
"displayName": "Power",
|
||||||
"displayNameEvent": "Power changed",
|
|
||||||
"displayNameAction": "Set power",
|
"displayNameAction": "Set power",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"writable": true,
|
"writable": true,
|
||||||
@ -126,7 +119,6 @@
|
|||||||
"id": "80098178-7d92-43dd-a216-23704cc0eaa2",
|
"id": "80098178-7d92-43dd-a216-23704cc0eaa2",
|
||||||
"name": "temperature",
|
"name": "temperature",
|
||||||
"displayName": "Temperature",
|
"displayName": "Temperature",
|
||||||
"displayNameEvent": "Temperature changed",
|
|
||||||
"unit": "DegreeCelsius",
|
"unit": "DegreeCelsius",
|
||||||
"type": "double",
|
"type": "double",
|
||||||
"defaultValue": 0
|
"defaultValue": 0
|
||||||
@ -135,7 +127,6 @@
|
|||||||
"id": "684fcc62-f12b-4669-988e-4b79f153b0f2",
|
"id": "684fcc62-f12b-4669-988e-4b79f153b0f2",
|
||||||
"name": "targetTemperature",
|
"name": "targetTemperature",
|
||||||
"displayName": "Target temperature",
|
"displayName": "Target temperature",
|
||||||
"displayNameEvent": "Target temperature changed",
|
|
||||||
"displayNameAction": "Set target temperature",
|
"displayNameAction": "Set target temperature",
|
||||||
"unit": "DegreeCelsius",
|
"unit": "DegreeCelsius",
|
||||||
"type": "double",
|
"type": "double",
|
||||||
@ -148,7 +139,6 @@
|
|||||||
"id": "0faaaff1-2a33-44ec-b68d-d8855f584b02",
|
"id": "0faaaff1-2a33-44ec-b68d-d8855f584b02",
|
||||||
"name": "humidity",
|
"name": "humidity",
|
||||||
"displayName": "Humidity",
|
"displayName": "Humidity",
|
||||||
"displayNameEvent": "Humidity changed",
|
|
||||||
"unit": "Percentage",
|
"unit": "Percentage",
|
||||||
"type": "double",
|
"type": "double",
|
||||||
"defaultValue": 0,
|
"defaultValue": 0,
|
||||||
|
|||||||
410
tado/tado.cpp
410
tado/tado.cpp
@ -1,6 +1,6 @@
|
|||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
*
|
*
|
||||||
* Copyright 2013 - 2020, nymea GmbH
|
* Copyright 2013 - 2025, nymea GmbH
|
||||||
* Contact: contact@nymea.io
|
* Contact: contact@nymea.io
|
||||||
*
|
*
|
||||||
* This file is part of nymea.
|
* This file is part of nymea.
|
||||||
@ -36,24 +36,27 @@
|
|||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QUrlQuery>
|
#include <QUrlQuery>
|
||||||
|
|
||||||
Tado::Tado(NetworkAccessManager *networkManager, const QString &username, QObject *parent) :
|
Tado::Tado(NetworkAccessManager *networkManager, QObject *parent) :
|
||||||
QObject(parent),
|
QObject(parent),
|
||||||
m_networkManager(networkManager),
|
m_networkManager(networkManager)
|
||||||
m_username(username)
|
|
||||||
{
|
{
|
||||||
m_refreshTimer = new QTimer(this);
|
m_baseControlUrl = "https://my.tado.com/api/v2";
|
||||||
m_refreshTimer->setSingleShot(true);
|
m_baseAuthorizationUrl = "https://login.tado.com/oauth2";
|
||||||
connect(m_refreshTimer, &QTimer::timeout, this, &Tado::onRefreshTimer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tado::setUsername(const QString &username)
|
m_clientId = "1bb50063-6b0c-4d11-bd99-387f4a91cc46";
|
||||||
{
|
|
||||||
m_username = username;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString Tado::username()
|
m_refreshTimer.setSingleShot(true);
|
||||||
{
|
connect(&m_refreshTimer, &QTimer::timeout, this, [this](){
|
||||||
return m_username;
|
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()
|
bool Tado::apiAvailable()
|
||||||
@ -71,138 +74,160 @@ bool Tado::connected()
|
|||||||
return m_connectionStatus;
|
return m_connectionStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tado::getApiCredentials(const QString &url)
|
|
||||||
|
QString Tado::loginUrl() const
|
||||||
{
|
{
|
||||||
QNetworkRequest request;
|
return m_loginUrl;
|
||||||
request.setUrl(url);
|
}
|
||||||
QNetworkReply *reply = m_networkManager->get(request);
|
|
||||||
qCDebug(dcTado()) << "Sending request" << request.url();
|
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, reply, &QNetworkReply::deleteLater);
|
||||||
connect(reply, &QNetworkReply::finished, this, [reply, this] {
|
connect(reply, &QNetworkReply::finished, this, [reply, this] {
|
||||||
|
|
||||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
// Check HTTP status code
|
// Check HTTP status code
|
||||||
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
||||||
qCWarning(dcTado()) << "Request error:" << status << reply->errorString();
|
emit getLoginUrlFinished(false);
|
||||||
emit apiCredentialsReceived(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QRegExp filter;
|
|
||||||
filter.setPatternSyntax(QRegExp::Wildcard);
|
|
||||||
filter.setPattern("*tgaRestApiV2Endpoint:*");
|
|
||||||
|
|
||||||
QStringList list = QString(reply->readAll()).split('\n');
|
emit connectionError(reply->error());
|
||||||
int index = list.indexOf(filter);
|
|
||||||
if (index == -1) {
|
if (reply->error() == QNetworkReply::HostNotFoundError)
|
||||||
qCWarning(dcTado()) << "GetApiCredenitals: Could not find the API url";
|
setConnectionStatus(false);
|
||||||
emit apiCredentialsReceived(false);
|
|
||||||
|
if (status == 401 || status == 400)
|
||||||
|
setAuthenticationStatus(false);
|
||||||
|
|
||||||
|
qCWarning(dcTado()) << "Request error:" << status << reply->errorString();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_baseControlUrl = list.value(index).split(": ").last().remove(QRegExp("[,']"));;
|
|
||||||
qCDebug(dcTado()) << "Received control url" << m_baseControlUrl;
|
|
||||||
filter.setPattern("*apiEndpoint*");
|
|
||||||
index = list.indexOf(filter);
|
|
||||||
if (index == -1) {
|
|
||||||
qCWarning(dcTado()) << "GetApiCredenitals: Could not find the authorization url";
|
|
||||||
emit apiCredentialsReceived(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_baseAuthorizationUrl = list.value(index).split(": ").last().remove(QRegExp("[,']"))+"/token";
|
|
||||||
qCDebug(dcTado()) << "Received auth url" << m_baseAuthorizationUrl;
|
|
||||||
filter.setPattern("*clientId*");
|
|
||||||
index = list.indexOf(filter);
|
|
||||||
if (index == -1) {
|
|
||||||
emit apiCredentialsReceived(false);
|
|
||||||
qCWarning(dcTado()) << "GetApiCredenitals: Could not find the client Id";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_clientId = list.value(index).split(": ").last().remove(QRegExp("[,']"));
|
|
||||||
qCDebug(dcTado()) << "Received client id" << m_clientId.mid(0, 4)+"*****";
|
|
||||||
filter.setPattern("*clientSecret*");
|
|
||||||
index = list.indexOf(filter);
|
|
||||||
if (index == -1) {
|
|
||||||
qCWarning(dcTado()) << "GetApiCredenitals: Could not find the client secret";
|
|
||||||
emit apiCredentialsReceived(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_clientSecret = list.value(index).split(": ").last().remove(QRegExp("[,']"));
|
|
||||||
qCDebug(dcTado()) << "Received client secret" << m_clientSecret.mid(0, 4)+"*****";
|
|
||||||
m_apiAvailable = true;
|
m_apiAvailable = true;
|
||||||
emit apiCredentialsReceived(true);
|
setConnectionStatus(true);
|
||||||
|
|
||||||
|
QByteArray data = reply->readAll();
|
||||||
|
QJsonParseError error;
|
||||||
|
QJsonDocument responseJsonDoc = QJsonDocument::fromJson(data, &error);
|
||||||
|
if (error.error != QJsonParseError::NoError) {
|
||||||
|
qDebug(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::getToken(const QString &password)
|
|
||||||
|
void Tado::getAccessToken()
|
||||||
{
|
{
|
||||||
if (!m_apiAvailable) {
|
QNetworkRequest request = QNetworkRequest(QUrl(m_baseAuthorizationUrl + "/token"));
|
||||||
qCWarning(dcTado()) << "Not sending request, get API credentials first";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QNetworkRequest request;
|
|
||||||
request.setUrl(QUrl(m_baseAuthorizationUrl));
|
|
||||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/x-www-form-urlencoded");
|
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||||
QUrlQuery query;
|
|
||||||
query.setQueryItems({{"client_id", m_clientId},
|
|
||||||
{"client_secret", m_clientSecret},
|
|
||||||
{"grant_type", "password"},
|
|
||||||
{"scope", "home.user"},
|
|
||||||
{"username", m_username},
|
|
||||||
{"password", password}});
|
|
||||||
|
|
||||||
QNetworkReply *reply = m_networkManager->post(request, query.toString(QUrl::FullyEncoded).toUtf8());
|
QUrlQuery query;
|
||||||
// qCDebug(dcTado()) << "Sending request" << request.url() << query.toString(QUrl::FullyEncoded).toUtf8();
|
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, reply, &QNetworkReply::deleteLater);
|
||||||
connect(reply, &QNetworkReply::finished, this, [reply, this] {
|
connect(reply, &QNetworkReply::finished, this, [reply, this] {
|
||||||
|
|
||||||
|
QByteArray data = reply->readAll();
|
||||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
// Check HTTP status code
|
// Check HTTP status code
|
||||||
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
||||||
|
|
||||||
emit connectionError(reply->error());
|
emit connectionError(reply->error());
|
||||||
if (reply->error() == QNetworkReply::HostNotFoundError) {
|
|
||||||
|
if (reply->error() == QNetworkReply::HostNotFoundError)
|
||||||
setConnectionStatus(false);
|
setConnectionStatus(false);
|
||||||
}
|
|
||||||
if (status == 401 || status == 400) {
|
if (status == 401 || status == 400)
|
||||||
setAuthenticationStatus(false);
|
setAuthenticationStatus(false);
|
||||||
}
|
|
||||||
qCWarning(dcTado()) << "Request error:" << status << reply->errorString();
|
qCWarning(dcTado()) << "Request error:" << status << reply->errorString() << qUtf8Printable(data);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_apiAvailable = true;
|
||||||
setConnectionStatus(true);
|
setConnectionStatus(true);
|
||||||
|
|
||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error);
|
QJsonDocument responseJsonDoc = QJsonDocument::fromJson(data, &error);
|
||||||
if (error.error != QJsonParseError::NoError) {
|
if (error.error != QJsonParseError::NoError) {
|
||||||
qDebug(dcTado()) << "Get Token: Received invalid JSON object:" << data;
|
qDebug(dcTado()) << "Get access token received invalid JSON object:" << data;
|
||||||
|
emit getLoginUrlFinished(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (data.isObject()) {
|
|
||||||
Token token;
|
|
||||||
QVariantMap obj = data.toVariant().toMap();
|
|
||||||
if (obj.contains("access_token")) {
|
|
||||||
token.accesToken = obj["access_token"].toString();
|
|
||||||
m_accessToken = token.accesToken;
|
|
||||||
} else {
|
|
||||||
qCWarning(dcTado()) << "Received response doesnt contain an access token";
|
|
||||||
}
|
|
||||||
|
|
||||||
token.tokenType = obj["token_type"].toString();
|
qCDebug(dcTado()) << "Get access token response" << qUtf8Printable(responseJsonDoc.toJson());
|
||||||
token.refreshToken = obj["refresh_token"].toString();
|
QVariantMap responseMap = responseJsonDoc.toVariant().toMap();
|
||||||
m_refreshToken = token.refreshToken;
|
|
||||||
if (obj.contains("expires_in")) {
|
m_accessToken = responseMap.value("access_token").toString();
|
||||||
token.expires = obj["expires_in"].toInt();
|
emit accessTokenReceived();
|
||||||
m_refreshTimer->start((token.expires - 10)*1000);
|
|
||||||
} else {
|
QString refreshToken = responseMap.value("refresh_token").toString();
|
||||||
qCWarning(dcTado()) << "Received response doesn't contain an expire time";
|
if (m_refreshToken != refreshToken) {
|
||||||
}
|
m_refreshToken = refreshToken;
|
||||||
token.scope = obj["scope"].toString();
|
emit refreshTokenReceived(m_refreshToken);
|
||||||
token.jti = obj["jti"].toString();
|
|
||||||
setAuthenticationStatus(true);
|
|
||||||
emit tokenReceived(token);
|
|
||||||
} else {
|
|
||||||
qCWarning(dcTado()) << "Received response isn't an object" << data.toJson();
|
|
||||||
setAuthenticationStatus(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setAuthenticationStatus(true);
|
||||||
|
|
||||||
|
// Refresh 10 sekonds before expiration
|
||||||
|
m_refreshTimer.setInterval((responseMap.value("expires_in").toUInt() - 10) * 1000);
|
||||||
|
m_refreshTimer.start();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,11 +271,21 @@ void Tado::getHomes()
|
|||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error);
|
QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error);
|
||||||
if (error.error != QJsonParseError::NoError) {
|
if (error.error != QJsonParseError::NoError) {
|
||||||
qDebug(dcTado()) << "Get Token: Recieved invalid JSON object";
|
qDebug(dcTado()) << "Get Homes: Recieved invalid JSON object";
|
||||||
return;
|
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;
|
QList<Home> homes;
|
||||||
QVariantList homeList = data.toVariant().toMap().value("homes").toList();
|
QVariantList homeList = responseMap.value("homes").toList();
|
||||||
foreach (QVariant variant, homeList) {
|
foreach (QVariant variant, homeList) {
|
||||||
QVariantMap obj = variant.toMap();
|
QVariantMap obj = variant.toMap();
|
||||||
Home home;
|
Home home;
|
||||||
@ -258,6 +293,7 @@ void Tado::getHomes()
|
|||||||
home.name = obj["name"].toString();
|
home.name = obj["name"].toString();
|
||||||
homes.append(home);
|
homes.append(home);
|
||||||
}
|
}
|
||||||
|
|
||||||
emit homesReceived(homes);
|
emit homesReceived(homes);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -275,7 +311,7 @@ void Tado::getZones(const QString &homeId)
|
|||||||
}
|
}
|
||||||
|
|
||||||
QNetworkRequest request;
|
QNetworkRequest request;
|
||||||
request.setUrl(QUrl(m_baseControlUrl+"/homes/"+homeId+"/zones"));
|
request.setUrl(QUrl(m_baseControlUrl + "/homes/" + homeId + "/zones"));
|
||||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/x-www-form-urlencoded");
|
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken.toLocal8Bit());
|
request.setRawHeader("Authorization", "Bearer " + m_accessToken.toLocal8Bit());
|
||||||
QNetworkReply *reply = m_networkManager->get(request);
|
QNetworkReply *reply = m_networkManager->get(request);
|
||||||
@ -332,7 +368,7 @@ void Tado::getZoneState(const QString &homeId, const QString &zoneId)
|
|||||||
}
|
}
|
||||||
|
|
||||||
QNetworkRequest request;
|
QNetworkRequest request;
|
||||||
request.setUrl(QUrl(m_baseControlUrl+"/homes/"+homeId+"/zones/"+zoneId+"/state"));
|
request.setUrl(QUrl(m_baseControlUrl + "/homes/" + homeId + "/zones/" + zoneId + "/state"));
|
||||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/x-www-form-urlencoded");
|
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken.toLocal8Bit());
|
request.setRawHeader("Authorization", "Bearer " + m_accessToken.toLocal8Bit());
|
||||||
QNetworkReply *reply = m_networkManager->get(request);
|
QNetworkReply *reply = m_networkManager->get(request);
|
||||||
@ -413,7 +449,7 @@ QUuid Tado::setOverlay(const QString &homeId, const QString &zoneId, bool power,
|
|||||||
|
|
||||||
QUuid requestId = QUuid::createUuid();
|
QUuid requestId = QUuid::createUuid();
|
||||||
QNetworkRequest request;
|
QNetworkRequest request;
|
||||||
request.setUrl(QUrl(m_baseControlUrl+"/homes/"+homeId+"/zones/"+zoneId+"/overlay"));
|
request.setUrl(QUrl(m_baseControlUrl + "/homes/" + homeId + "/zones/" + zoneId + "/overlay"));
|
||||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json;charset=utf-8");
|
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json;charset=utf-8");
|
||||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken.toLocal8Bit());
|
request.setRawHeader("Authorization", "Bearer " + m_accessToken.toLocal8Bit());
|
||||||
|
|
||||||
@ -424,14 +460,14 @@ QUuid Tado::setOverlay(const QString &homeId, const QString &zoneId, bool power,
|
|||||||
else
|
else
|
||||||
powerString = "OFF";
|
powerString = "OFF";
|
||||||
|
|
||||||
body.append("{\"setting\":{\"type\":\"HEATING\",\"power\":\""+ powerString + "\",\"temperature\":{\"celsius\":" + QVariant(targetTemperature).toByteArray() + "}},\"termination\":{\"type\":\"MANUAL\"}}");
|
body.append("{\"setting\":{\"type\":\"HEATING\",\"power\":\"" + powerString + "\",\"temperature\":{\"celsius\":" + QVariant(targetTemperature).toByteArray() + "}},\"termination\":{\"type\":\"MANUAL\"}}");
|
||||||
|
|
||||||
//qCDebug(dcTado()) << "Sending request" << body;
|
//qCDebug(dcTado()) << "Sending request" << body;
|
||||||
QNetworkReply *reply = m_networkManager->put(request, body);
|
QNetworkReply *reply = m_networkManager->put(request, body);
|
||||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||||
connect(reply, &QNetworkReply::finished, this, [homeId, zoneId, requestId, reply, this] {
|
connect(reply, &QNetworkReply::finished, this, [homeId, zoneId, requestId, reply, this] {
|
||||||
|
|
||||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
// Check HTTP status code
|
|
||||||
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
||||||
emit requestExecuted(requestId, false);
|
emit requestExecuted(requestId, false);
|
||||||
emit connectionError(reply->error());
|
emit connectionError(reply->error());
|
||||||
@ -439,6 +475,7 @@ QUuid Tado::setOverlay(const QString &homeId, const QString &zoneId, bool power,
|
|||||||
if (reply->error() == QNetworkReply::HostNotFoundError) {
|
if (reply->error() == QNetworkReply::HostNotFoundError) {
|
||||||
setConnectionStatus(false);
|
setConnectionStatus(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status == 401 || status == 400) { //Unauthorized
|
if (status == 401 || status == 400) { //Unauthorized
|
||||||
setAuthenticationStatus(false);
|
setAuthenticationStatus(false);
|
||||||
} else if (status == 422) { //Unprocessable Entity
|
} else if (status == 422) { //Unprocessable Entity
|
||||||
@ -448,6 +485,7 @@ QUuid Tado::setOverlay(const QString &homeId, const QString &zoneId, bool power,
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setAuthenticationStatus(true);
|
setAuthenticationStatus(true);
|
||||||
setConnectionStatus(true);
|
setConnectionStatus(true);
|
||||||
emit requestExecuted(requestId, true);
|
emit requestExecuted(requestId, true);
|
||||||
@ -488,21 +526,22 @@ QUuid Tado::deleteOverlay(const QString &homeId, const QString &zoneId)
|
|||||||
|
|
||||||
QUuid requestId = QUuid::createUuid();
|
QUuid requestId = QUuid::createUuid();
|
||||||
QNetworkRequest request;
|
QNetworkRequest request;
|
||||||
request.setUrl(QUrl(m_baseControlUrl+"/homes/"+homeId+"/zones/"+zoneId+"/overlay"));
|
request.setUrl(QUrl(m_baseControlUrl + "/homes/" + homeId + "/zones/" + zoneId + "/overlay"));
|
||||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken.toLocal8Bit());
|
request.setRawHeader("Authorization", "Bearer " + m_accessToken.toLocal8Bit());
|
||||||
QNetworkReply *reply = m_networkManager->deleteResource(request);
|
QNetworkReply *reply = m_networkManager->deleteResource(request);
|
||||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||||
connect(reply, &QNetworkReply::finished, this, [homeId, zoneId, requestId, reply, this] {
|
connect(reply, &QNetworkReply::finished, this, [homeId, zoneId, requestId, reply, this] {
|
||||||
|
|
||||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
|
||||||
// Check HTTP status code
|
|
||||||
if (status < 200 || status > 210 || reply->error() != QNetworkReply::NoError) {
|
if (status < 200 || status > 210 || reply->error() != QNetworkReply::NoError) {
|
||||||
|
|
||||||
emit requestExecuted(requestId ,false);
|
emit requestExecuted(requestId ,false);
|
||||||
emit connectionError(reply->error());
|
emit connectionError(reply->error());
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::HostNotFoundError) {
|
if (reply->error() == QNetworkReply::HostNotFoundError) {
|
||||||
setConnectionStatus(false);
|
setConnectionStatus(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status == 401 || status == 400) { //Unauthorized
|
if (status == 401 || status == 400) { //Unauthorized
|
||||||
setAuthenticationStatus(false);
|
setAuthenticationStatus(false);
|
||||||
} else if (status == 422) { //Unprocessable Entity
|
} else if (status == 422) { //Unprocessable Entity
|
||||||
@ -510,9 +549,10 @@ QUuid Tado::deleteOverlay(const QString &homeId, const QString &zoneId)
|
|||||||
} else {
|
} else {
|
||||||
qCWarning(dcTado()) << "Request error:" << status << reply->errorString();
|
qCWarning(dcTado()) << "Request error:" << status << reply->errorString();
|
||||||
}
|
}
|
||||||
qCWarning(dcTado()) << "Request error:" << status << reply->errorString();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setAuthenticationStatus(true);
|
setAuthenticationStatus(true);
|
||||||
setConnectionStatus(true);
|
setConnectionStatus(true);
|
||||||
emit requestExecuted(requestId, true);
|
emit requestExecuted(requestId, true);
|
||||||
@ -523,6 +563,7 @@ QUuid Tado::deleteOverlay(const QString &homeId, const QString &zoneId)
|
|||||||
qDebug(dcTado()) << "Get Token: Recieved invalid JSON object";
|
qDebug(dcTado()) << "Get Token: Recieved invalid JSON object";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantMap map = data.toVariant().toMap();
|
QVariantMap map = data.toVariant().toMap();
|
||||||
|
|
||||||
Overlay overlay;
|
Overlay overlay;
|
||||||
@ -539,6 +580,66 @@ QUuid Tado::deleteOverlay(const QString &homeId, const QString &zoneId)
|
|||||||
return requestId;
|
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) {
|
||||||
|
qDebug(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)
|
void Tado::setAuthenticationStatus(bool status)
|
||||||
{
|
{
|
||||||
if (m_authenticationStatus != status) {
|
if (m_authenticationStatus != status) {
|
||||||
@ -546,9 +647,9 @@ void Tado::setAuthenticationStatus(bool status)
|
|||||||
emit authenticationStatusChanged(status);
|
emit authenticationStatusChanged(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!status) {
|
if (!status)
|
||||||
m_refreshTimer->stop();
|
m_refreshTimer.stop();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tado::setConnectionStatus(bool status)
|
void Tado::setConnectionStatus(bool status)
|
||||||
@ -558,62 +659,3 @@ void Tado::setConnectionStatus(bool status)
|
|||||||
emit connectionChanged(status);
|
emit connectionChanged(status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tado::onRefreshTimer()
|
|
||||||
{
|
|
||||||
if(m_refreshToken.isEmpty()) {
|
|
||||||
qCWarning(dcTado()) << "Not sending request, get the access token first";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QNetworkRequest request;
|
|
||||||
request.setUrl(QUrl(m_baseAuthorizationUrl));
|
|
||||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/x-www-form-urlencoded");
|
|
||||||
QUrlQuery query;
|
|
||||||
query.setQueryItems({{"client_id", m_clientId},
|
|
||||||
{"client_secret", m_clientSecret},
|
|
||||||
{"grant_type", "refresh_token"},
|
|
||||||
{"refresh_token", m_refreshToken},
|
|
||||||
{"scope", "home.user"}});
|
|
||||||
|
|
||||||
QNetworkReply *reply = m_networkManager->post(request, query.toString(QUrl::FullyEncoded).toUtf8());
|
|
||||||
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 == 400 || status == 401) {
|
|
||||||
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) {
|
|
||||||
qDebug(dcTado()) << "Get Token: Recieved invalid 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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
68
tado/tado.h
68
tado/tado.h
@ -1,6 +1,6 @@
|
|||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
*
|
*
|
||||||
* Copyright 2013 - 2020, nymea GmbH
|
* Copyright 2013 - 2025, nymea GmbH
|
||||||
* Contact: contact@nymea.io
|
* Contact: contact@nymea.io
|
||||||
*
|
*
|
||||||
* This file is part of nymea.
|
* This file is part of nymea.
|
||||||
@ -31,8 +31,8 @@
|
|||||||
#ifndef TADO_H
|
#ifndef TADO_H
|
||||||
#define TADO_H
|
#define TADO_H
|
||||||
|
|
||||||
#include "network/networkaccessmanager.h"
|
#include <network/networkaccessmanager.h>
|
||||||
#include "integrations/thing.h"
|
#include <integrations/thing.h>
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
@ -42,16 +42,6 @@ class Tado : public QObject
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
|
||||||
struct Token {
|
|
||||||
QString accesToken;
|
|
||||||
QString tokenType;
|
|
||||||
QString refreshToken;
|
|
||||||
int expires;
|
|
||||||
QString scope;
|
|
||||||
QString jti;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Zone {
|
struct Zone {
|
||||||
QString id;
|
QString id;
|
||||||
QString name;
|
QString name;
|
||||||
@ -89,16 +79,24 @@ public:
|
|||||||
QString name;
|
QString name;
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit Tado(NetworkAccessManager *networkManager, const QString &username, QObject *parent = nullptr);
|
explicit Tado(NetworkAccessManager *networkManager, QObject *parent = nullptr);
|
||||||
|
|
||||||
void setUsername(const QString &username);
|
|
||||||
QString username();
|
|
||||||
bool apiAvailable();
|
bool apiAvailable();
|
||||||
bool authenticated();
|
bool authenticated();
|
||||||
bool connected();
|
bool connected();
|
||||||
|
|
||||||
void getApiCredentials(const QString &url = "https://app.tado.com/env.js");
|
QString loginUrl() const;
|
||||||
void getToken(const QString &password);
|
|
||||||
|
QString username() const;
|
||||||
|
|
||||||
|
QString refreshToken() const;
|
||||||
|
void setRefreshToken(const QString &refreshToken);
|
||||||
|
|
||||||
|
// Login process
|
||||||
|
void getLoginUrl();
|
||||||
|
void startAuthentication();
|
||||||
|
void getAccessToken();
|
||||||
|
|
||||||
void getHomes();
|
void getHomes();
|
||||||
void getZones(const QString &homeId);
|
void getZones(const QString &homeId);
|
||||||
void getZoneState(const QString &homeId, const QString &zoneId);
|
void getZoneState(const QString &homeId, const QString &zoneId);
|
||||||
@ -110,36 +108,46 @@ private:
|
|||||||
bool m_apiAvailable = false;
|
bool m_apiAvailable = false;
|
||||||
QString m_baseAuthorizationUrl;
|
QString m_baseAuthorizationUrl;
|
||||||
QString m_baseControlUrl;
|
QString m_baseControlUrl;
|
||||||
QString m_clientSecret;
|
|
||||||
QString m_clientId;
|
QString m_clientId;
|
||||||
|
QString m_deviceCode;
|
||||||
|
|
||||||
NetworkAccessManager *m_networkManager = nullptr;
|
NetworkAccessManager *m_networkManager = nullptr;
|
||||||
QString m_username;
|
QString m_loginUrl;
|
||||||
QString m_accessToken;
|
QString m_accessToken;
|
||||||
QString m_refreshToken;
|
QString m_refreshToken;
|
||||||
QTimer *m_refreshTimer = nullptr;
|
QString m_username;
|
||||||
|
|
||||||
|
QTimer m_refreshTimer;
|
||||||
|
QTimer m_pollAuthenticationTimer;
|
||||||
|
uint m_pollAuthenticationCount = 0;
|
||||||
|
uint m_pollAuthenticationLimit = 5;
|
||||||
|
|
||||||
bool m_authenticationStatus = false;
|
bool m_authenticationStatus = false;
|
||||||
bool m_connectionStatus = false;
|
bool m_connectionStatus = false;
|
||||||
|
|
||||||
|
void requestAuthenticationToken();
|
||||||
|
|
||||||
void setAuthenticationStatus(bool status);
|
void setAuthenticationStatus(bool status);
|
||||||
void setConnectionStatus(bool status);
|
void setConnectionStatus(bool status);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void connectionChanged(bool connected);
|
void connectionChanged(bool connected);
|
||||||
void apiCredentialsReceived(bool success);
|
void getLoginUrlFinished(bool success);
|
||||||
|
void startAuthenticationFinished(bool success);
|
||||||
|
void accessTokenReceived();
|
||||||
|
void usernameChanged(const QString &username);
|
||||||
|
void refreshTokenReceived(const QString &refreshToken);
|
||||||
|
|
||||||
void authenticationStatusChanged(bool authenticated);
|
void authenticationStatusChanged(bool authenticated);
|
||||||
|
|
||||||
void requestExecuted(QUuid requestId, bool success);
|
void requestExecuted(QUuid requestId, bool success);
|
||||||
|
|
||||||
void tokenReceived(Token token);
|
void homesReceived(QList<Tado::Home> homes);
|
||||||
void homesReceived(QList<Home> homes);
|
void zonesReceived(const QString &homeId, QList<Tado::Zone> zones);
|
||||||
void zonesReceived(const QString &homeId, QList<Zone> zones);
|
void zoneStateReceived(const QString &homeId,const QString &zoneId, Tado::ZoneState sate);
|
||||||
void zoneStateReceived(const QString &homeId,const QString &zoneId, ZoneState sate);
|
void overlayReceived(const QString &homeId, const QString &zoneId, const Tado::Overlay &overlay);
|
||||||
void overlayReceived(const QString &homeId, const QString &zoneId, const Overlay &overlay);
|
|
||||||
void connectionError(QNetworkReply::NetworkError error);
|
void connectionError(QNetworkReply::NetworkError error);
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onRefreshTimer();
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // TADO_H
|
#endif // TADO_H
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user