nymea-plugins/netatmo/integrationpluginnetatmo.cpp

654 lines
29 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 "integrationpluginnetatmo.h"
#include "netatmoconnection.h"
#include "plugininfo.h"
#include <plugintimer.h>
#include <integrations/thing.h>
#include <network/networkaccessmanager.h>
#include <QDebug>
#include <QUrlQuery>
#include <QJsonDocument>
IntegrationPluginNetatmo::IntegrationPluginNetatmo()
{
}
void IntegrationPluginNetatmo::init()
{
}
void IntegrationPluginNetatmo::startPairing(ThingPairingInfo *info)
{
if (!loadClientCredentials()) {
info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("No API key installed."));
return;
}
// Compose url
NetatmoConnection *connection = new NetatmoConnection(hardwareManager()->networkManager(), m_clientId, m_clientSecret, this);
QUrl loginUrl = connection->getLoginUrl();
// Checking the internet connect^ion
QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(QUrl("https://api.netatmo.net")));
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, info, [this, reply, info, connection, loginUrl]() {
//The server replies usually 404 not found on this request
//int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (reply->error() == QNetworkReply::NetworkError::HostNotFoundError) {
qCWarning(dcNetatmo()) << "Netatmo server is not reachable";
info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("The Netatmo server is not reachable."));
return;
}
ThingId thingId = info->thingId();
m_pendingSetups.insert(thingId, connection);
connect(info, &ThingPairingInfo::aborted, connection, [thingId, this]() {
qCWarning(dcNetatmo()) << "ThingPairingInfo aborted, cleaning up pending setup connection.";
m_pendingSetups.take(thingId)->deleteLater();
});
qCDebug(dcNetatmo()) << "Netatmo server is reachable. Start the OAuth pairing process";
info->setOAuthUrl(loginUrl);
info->finish(Thing::ThingErrorNoError);
});
}
void IntegrationPluginNetatmo::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret)
{
Q_UNUSED(username)
qCDebug(dcNetatmo()) << "Confirm pairing" << info->thingName();
if (info->thingClassId() == netatmoConnectionThingClassId) {
QUrl url(secret);
QUrlQuery query(url);
QByteArray authorizationCode = query.queryItemValue("code").toLocal8Bit();
if (authorizationCode.isEmpty()) {
qCWarning(dcNetatmo()) << "Error while pairing to netatmo server. No authorization code received.";
info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Failed to log in to the Netatmo server."));
return;
}
NetatmoConnection *connection = m_pendingSetups.value(info->thingId());
if (!connection) {
qCWarning(dcNetatmo()) << "No NetatmoConnect connection found for device:" << info->thingName();
m_pendingSetups.remove(info->thingId());
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
connect(connection, &NetatmoConnection::receivedRefreshToken, info, [info, this](const QByteArray &refreshToken){
qCDebug(dcNetatmo()) << "Received token:" << NetatmoConnection::censorDebugOutput(refreshToken);
pluginStorage()->beginGroup(info->thingId().toString());
pluginStorage()->setValue("refresh_token", refreshToken);
pluginStorage()->endGroup();
info->finish(Thing::ThingErrorNoError);
});
qCDebug(dcNetatmo()) << "Authorization code" << NetatmoConnection::censorDebugOutput(authorizationCode);
if (!connection->getAccessTokenFromAuthorizationCode(authorizationCode)) {
qCWarning(dcNetatmo()) << "Failed to get token from authorization code.";
info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Failed to log in to the Netatmo server."));
return;
}
}
}
void IntegrationPluginNetatmo::setupThing(ThingSetupInfo *info)
{
Thing *thing = info->thing();
if (thing->thingClassId() == netatmoConnectionThingClassId) {
qCDebug(dcNetatmo) << "Setup Netatmo connection" << thing->name() << thing->params();
if (!loadClientCredentials()) {
info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("No API key installed."));
return;
}
NetatmoConnection *connection = nullptr;
// Handle reconfigure
if (m_connections.contains(thing)) {
qCDebug(dcNetatmo()) << "Setup after reconfiguration, cleaning up";
m_connections.take(thing)->deleteLater();
}
if (m_pendingSetups.keys().contains(thing->id())) {
// This thing setup is after a pairing process
qCDebug(dcNetatmo()) << "Netatmo available after successful OAuth2 setup. Using the existing object";
connection = m_pendingSetups.take(thing->id());
m_connections.insert(thing, connection);
info->finish(Thing::ThingErrorNoError);
thing->setStateValue("connected", true);
thing->setStateValue(netatmoConnectionLoggedInStateTypeId, true);
connect(connection, &NetatmoConnection::authenticatedChanged, thing, [thing](bool authenticated){
thing->setStateValue(netatmoConnectionLoggedInStateTypeId, authenticated);
});
} else {
// The thing should have been already set up before, lets refresh the token if we have a refresh token,
// otherwise the user has to perform a reconfigure and login using OAuth2... (also old username password user end up here)
if (doingLoginMigration(info))
return;
// No migration needed...
setupConnection(info);
return;
}
return;
} else if (thing->thingClassId() == indoorThingClassId) {
qCDebug(dcNetatmo) << "Setup Netatmo indoor base station" << thing->params();
info->finish(Thing::ThingErrorNoError);
return;
} else if (thing->thingClassId() == outdoorThingClassId) {
qCDebug(dcNetatmo) << "Setup netatmo outdoor module" << thing->params();
info->finish(Thing::ThingErrorNoError);
return;
} else if (thing->thingClassId() == windModuleThingClassId) {
qCDebug(dcNetatmo) << "Setup netatmo wind module" << thing->params();
info->finish(Thing::ThingErrorNoError);
return;
} else if (thing->thingClassId() == rainGaugeThingClassId) {
qCDebug(dcNetatmo) << "Setup netatmo wind module" << thing->params();
info->finish(Thing::ThingErrorNoError);
return;
} else if (thing->thingClassId() == indoorModuleThingClassId) {
qCDebug(dcNetatmo) << "Setup netatmo indoor module" << thing->params();
info->finish(Thing::ThingErrorNoError);
return;
} else {
Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(info->thing()->thingClassId().toString()).toUtf8());
}
}
void IntegrationPluginNetatmo::postSetupThing(Thing *thing)
{
if (thing->thingClassId() == netatmoConnectionThingClassId) {
refreshConnection(thing);
} else if (thing->thingClassId() == indoorThingClassId) {
QString stationId = thing->paramValue(indoorThingMacParamTypeId).toString();
if (m_temporaryInitData.contains(stationId)) {
updateModuleStates(thing, m_temporaryInitData.take(stationId));
}
} else if (thing->thingClassId() == outdoorThingClassId) {
QString stationId = thing->paramValue(outdoorThingMacParamTypeId).toString();
if (m_temporaryInitData.contains(stationId)) {
updateModuleStates(thing, m_temporaryInitData.take(stationId));
}
}
if (!m_pluginTimer) {
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(600);
connect(m_pluginTimer, &PluginTimer::timeout, this, [this](){
foreach (Thing *connectionThing, myThings().filterByThingClassId(netatmoConnectionThingClassId)) {
refreshConnection(connectionThing);
}
});
}
}
void IntegrationPluginNetatmo::thingRemoved(Thing *thing)
{
if (thing->thingClassId() == netatmoConnectionThingClassId) {
m_connections.take(thing)->deleteLater();
}
if (myThings().isEmpty() && m_pluginTimer) {
if (m_pluginTimer) {
hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer);
m_pluginTimer = nullptr;
}
}
}
void IntegrationPluginNetatmo::setupConnection(ThingSetupInfo *info)
{
Thing *thing = info->thing();
qCDebug(dcNetatmo()) << "Setup netatmo account" << thing->name();
// Load refresh token
pluginStorage()->beginGroup(thing->id().toString());
QByteArray refreshToken = pluginStorage()->value("refresh_token").toByteArray();
pluginStorage()->endGroup();
if (refreshToken.isEmpty()) {
info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Could not authenticate on the server. Please reconfigure the connection."));
return;
}
// Create new connection
NetatmoConnection *connection = new NetatmoConnection(hardwareManager()->networkManager(), m_clientId, m_clientSecret, thing);
connect(connection, &NetatmoConnection::authenticatedChanged, info, [this, info, thing, connection](bool authenticated){
if (authenticated) {
m_connections.insert(thing, connection);
qCDebug(dcNetatmo()) << "Authenticated successfully the netatmo connection.";
info->finish(Thing::ThingErrorNoError);
thing->setStateValue("connected", true);
} else {
qCDebug(dcNetatmo()) << "Authentication process failed.";
info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Authentication failed. Please reconfigure the connection."));
}
});
connect(info, &ThingSetupInfo::aborted, connection, [this, thing, connection] {
if (m_connections.contains(thing))
m_connections.remove(thing);
connection->deleteLater();
});
connect(connection, &NetatmoConnection::authenticatedChanged, thing, [thing](bool authenticated){
thing->setStateValue(netatmoConnectionLoggedInStateTypeId, authenticated);
});
connection->getAccessTokenFromRefreshToken(refreshToken);
}
void IntegrationPluginNetatmo::refreshConnection(Thing *thing)
{
qCDebug(dcNetatmo()) << "Refresh connection" << thing;
NetatmoConnection *connection = m_connections.value(thing);
if (!connection) {
qCWarning(dcNetatmo()) << "Failed to refresh data. The connection object does not exist";
return;
}
QNetworkReply *reply = connection->getStationData();
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, this, [this, reply, thing]() {
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (reply->error() != QNetworkReply::NoError) {
qCWarning(dcNetatmo()) << "Failed to refresh station data. Network reply returned with error" << status << reply->errorString();
thing->setStateValue("connected", false);
return;
}
thing->setStateValue("connected", true);
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll(), &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(dcNetatmo()) << "OAuth2: Failed to get token. Refresh data reply JSON error:" << error.errorString();
return;
}
QVariantMap responseMap = jsonDoc.toVariant().toMap();
if (responseMap.value("status") != "ok") {
qCWarning(dcNetatmo()) << "Refresh station data returned status not ok:" << responseMap.value("status").toString();
return;
}
qCDebug(dcNetatmo()) << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));
QVariantMap bodyMap = responseMap.value("body").toMap();
thing->setStateValue("username", bodyMap.value("user").toMap().value("mail").toString());
processRefreshData(thing, bodyMap.value("devices").toList());
});
}
void IntegrationPluginNetatmo::processRefreshData(Thing *connectionThing, const QVariantList &devices)
{
foreach (QVariant deviceVariant, devices) {
QVariantMap deviceMap = deviceVariant.toMap();
if (deviceMap.value("type").toString() == "NAMain") {
Thing *existingThing = findIndoorStationThing(deviceMap.value("_id").toString());
// Check if we have to create the thing (auto)
if (!existingThing) {
ThingDescriptor descriptor(indoorThingClassId, deviceMap.value("module_name").toString() + " " + deviceMap.value("station_name").toString(), QString(), connectionThing->id());
ParamList params;
params.append(Param(indoorThingMacParamTypeId, deviceMap.value("_id").toString()));
descriptor.setParams(params);
m_temporaryInitData.insert(deviceMap.value("_id").toString(), deviceMap);
emit autoThingsAppeared({descriptor});
} else {
// Update the indoor station states
updateModuleStates(existingThing, deviceMap);
}
}
/* Check modules of this station...
*
* NAMain: Indoor station ("Temperature", "CO2", "Humidity", "Noise", "Pressure")
* NAModule1: Outdoor module ("Temperature, Humidity")
* NAModule2: Wind module ("Wind")
* NAModule3: Rain gauge module ("Rain")
* NAModule4: Indoor module ("Temperature, Humidity, CO2")
*/
if (deviceMap.contains("modules")) {
QVariantList modulesList = deviceMap.value("modules").toList();
foreach (QVariant moduleVariant, modulesList) {
QVariantMap moduleMap = moduleVariant.toMap();
if (moduleMap.value("type").toString() == "NAModule1") {
Thing *existingThing = findOutdoorModuleThing(moduleMap.value("_id").toString());
if (!existingThing) {
ThingDescriptor descriptor(outdoorThingClassId, moduleMap.value("module_name").toString() + " " + moduleMap.value("station_name").toString(), QString(), connectionThing->id());
ParamList params;
params.append(Param(outdoorThingMacParamTypeId, moduleMap.value("_id").toString()));
params.append(Param(outdoorThingBaseStationParamTypeId, deviceMap.value("_id").toString()));
descriptor.setParams(params);
m_temporaryInitData.insert(moduleMap.value("_id").toString(), moduleMap);
emit autoThingsAppeared({descriptor});
} else {
updateModuleStates(existingThing, moduleMap);
}
} else if (moduleMap.value("type").toString() == "NAModule2") {
Thing *existingThing = findWindModuleThing(moduleMap.value("_id").toString());
if (!existingThing) {
ThingDescriptor descriptor(windModuleThingClassId, moduleMap.value("module_name").toString() + " " + moduleMap.value("station_name").toString(), QString(), connectionThing->id());
ParamList params;
params.append(Param(windModuleThingMacParamTypeId, moduleMap.value("_id").toString()));
params.append(Param(windModuleThingBaseStationParamTypeId, deviceMap.value("_id").toString()));
descriptor.setParams(params);
m_temporaryInitData.insert(moduleMap.value("_id").toString(), moduleMap);
emit autoThingsAppeared({descriptor});
} else {
updateModuleStates(existingThing, moduleMap);
}
} else if (moduleMap.value("type").toString() == "NAModule3") {
Thing *existingThing = findRainGaugeModuleThing(moduleMap.value("_id").toString());
if (!existingThing) {
ThingDescriptor descriptor(rainGaugeThingClassId, moduleMap.value("module_name").toString() + " " + moduleMap.value("station_name").toString(), QString(), connectionThing->id());
ParamList params;
params.append(Param(rainGaugeThingMacParamTypeId, moduleMap.value("_id").toString()));
params.append(Param(rainGaugeThingBaseStationParamTypeId, deviceMap.value("_id").toString()));
descriptor.setParams(params);
m_temporaryInitData.insert(moduleMap.value("_id").toString(), moduleMap);
emit autoThingsAppeared({descriptor});
} else {
updateModuleStates(existingThing, moduleMap);
}
} else if (moduleMap.value("type").toString() == "NAModule4") {
Thing *existingThing = findIndoorModuleThing(moduleMap.value("_id").toString());
if (!existingThing) {
ThingDescriptor descriptor(indoorModuleThingClassId, moduleMap.value("module_name").toString() + " " + moduleMap.value("station_name").toString(), QString(), connectionThing->id());
ParamList params;
params.append(Param(indoorModuleThingMacParamTypeId, moduleMap.value("_id").toString()));
params.append(Param(indoorModuleThingBaseStationParamTypeId, deviceMap.value("_id").toString()));
descriptor.setParams(params);
m_temporaryInitData.insert(moduleMap.value("_id").toString(), moduleMap);
emit autoThingsAppeared({descriptor});
} else {
updateModuleStates(existingThing, moduleMap);
}
}
}
}
}
}
Thing *IntegrationPluginNetatmo::findIndoorStationThing(const QString &macAddress)
{
foreach (Thing *thing, myThings().filterByThingClassId(indoorThingClassId)) {
if (thing->paramValue(indoorThingMacParamTypeId).toString() == macAddress) {
return thing;
}
}
return nullptr;
}
Thing *IntegrationPluginNetatmo::findOutdoorModuleThing(const QString &macAddress)
{
foreach (Thing *thing, myThings().filterByThingClassId(outdoorThingClassId)) {
if (thing->paramValue(outdoorThingMacParamTypeId).toString() == macAddress) {
return thing;
}
}
return nullptr;
}
Thing *IntegrationPluginNetatmo::findIndoorModuleThing(const QString &macAddress)
{
foreach (Thing *thing, myThings().filterByThingClassId(indoorModuleThingClassId)) {
if (thing->paramValue(indoorModuleThingMacParamTypeId).toString() == macAddress) {
return thing;
}
}
return nullptr;
}
Thing *IntegrationPluginNetatmo::findWindModuleThing(const QString &macAddress)
{
foreach (Thing *thing, myThings().filterByThingClassId(windModuleThingClassId)) {
if (thing->paramValue(windModuleThingMacParamTypeId).toString() == macAddress) {
return thing;
}
}
return nullptr;
}
Thing *IntegrationPluginNetatmo::findRainGaugeModuleThing(const QString &macAddress)
{
foreach (Thing *thing, myThings().filterByThingClassId(rainGaugeThingClassId)) {
if (thing->paramValue(rainGaugeThingMacParamTypeId).toString() == macAddress) {
return thing;
}
}
return nullptr;
}
void IntegrationPluginNetatmo::updateModuleStates(Thing *thing, const QVariantMap &data)
{
// check data timestamp
if (data.contains("last_message"))
thing->setStateValue("updateTime", data.value("last_message").toInt());
// update dashboard data
if (data.contains("dashboard_data")) {
QVariantMap measurments = data.value("dashboard_data").toMap();
if (measurments.contains("Temperature"))
thing->setStateValue("temperature", measurments.value("Temperature").toDouble());
if (measurments.contains("min_temp"))
thing->setStateValue("temperatureMin", measurments.value("min_temp").toDouble());
if (measurments.contains("max_temp"))
thing->setStateValue("temperatureMax", measurments.value("max_temp").toDouble());
if (measurments.contains("Humidity"))
thing->setStateValue("humidity", measurments.value("Humidity").toInt());
if (measurments.contains("AbsolutePressure"))
thing->setStateValue("pressure", measurments.value("AbsolutePressure").toDouble());
if (measurments.contains("CO2"))
thing->setStateValue("co2", measurments.value("CO2").toInt());
if (measurments.contains("Noise"))
thing->setStateValue("noise", measurments.value("Noise").toInt());
// Wind speed (given in km/s, we need m/s)
if (measurments.contains("WindStrength"))
thing->setStateValue("windSpeed", measurments.value("WindStrength").toDouble() / 3.6);
if (measurments.contains("WindAngle"))
thing->setStateValue("windDirection", measurments.value("WindAngle").toInt());
// Rain
if (measurments.contains("sum_rain_1"))
thing->setStateValue("rainfallLastHour", measurments.value("sum_rain_1").toInt());
if (measurments.contains("sum_rain_24"))
thing->setStateValue("rainfallLastDay", measurments.value("sum_rain_24").toInt());
}
// Battery
if (thing->thingClass().hasStateType("batteryLevel")) {
if (data.contains("battery_percent")) {
thing->setStateValue("batteryLevel", data.value("battery_percent").toInt());
} else {
// Fallback for older versions, calculate from voltage
if (data.contains("battery_vp")) {
int battery = data.value("battery_vp").toInt();
if (battery >= 6000) {
thing->setStateValue("batteryLevel", 100);
} else if (battery <= 3600) {
thing->setStateValue("batteryLevel", 0);
} else {
int delta = battery - 3600;
thing->setStateValue("batteryLevel", qRound(100.0 * delta / 2400));
}
}
}
thing->setStateValue("batteryCritical", thing->stateValue("batteryLevel").toInt() < 10);
}
// Signal strength (RF, 90 low, 60 highest)
if (data.contains("rf_status")) {
int signalStrength = data.value("rf_status").toInt();
if (signalStrength <= 60) {
thing->setStateValue("signalStrength", 100);
} else if (signalStrength >= 90) {
thing->setStateValue("signalStrength", 0);
} else {
int delta = 30 - (signalStrength - 60);
thing->setStateValue("signalStrength", qRound(100.0 * delta / 30.0));
}
}
// Wifi status (86=bad, 56=good)"
if (data.contains("wifi_status")) {
int signalStrength = data.value("wifi_status").toInt();
if (signalStrength <= 56) {
thing->setStateValue("signalStrength", 100);
} else if (signalStrength >= 86) {
thing->setStateValue("signalStrength", 0);
} else {
int delta = 30 - (signalStrength - 56);
thing->setStateValue("signalStrength", qRound(100.0 * delta / 30.0));
}
}
// update reachable state
if (data.contains("reachable"))
thing->setStateValue("connected", data.value("reachable").toBool());
}
bool IntegrationPluginNetatmo::doingLoginMigration(ThingSetupInfo *info)
{
// Returns true if we need to perform a login migration
// 1. First we stored the username and password in thing params and then moved to the plugin storage
// 2. Username and password login does not work any more since mid 2022 if you change the password, we need to make a propper oauth2 login
Thing *thing = info->thing();
QString username; QString password;
if (pluginStorage()->childGroups().contains(thing->id().toString())) {
pluginStorage()->beginGroup(thing->id().toString());
username = pluginStorage()->value("username").toString();
password = pluginStorage()->value("password").toString();
pluginStorage()->endGroup();
} else {
/* username and password have been stored as thingParams,
* this is to migrate the params to plug-in storage. */
ParamTypeId usernameParamTypeId = ParamTypeId("763c2c10-dee5-41c8-9f7e-ded741945e73");
ParamTypeId passwordParamTypeId = ParamTypeId("c0d892d6-f359-4782-9d7d-8f74a3b53e3e");
username = thing->paramValue(usernameParamTypeId).toString();
password = thing->paramValue(passwordParamTypeId).toString();
// Delete username and password so it wont be visible in the things.conf file.
thing->setParamValue(ParamTypeId("763c2c10-dee5-41c8-9f7e-ded741945e73"), "");
thing->setParamValue(ParamTypeId("c0d892d6-f359-4782-9d7d-8f74a3b53e3e"), "");
}
if (username.isEmpty() || password.isEmpty()) {
// No username and no password found from previouse setting...nothing to migrate here
return false;
}
// We found username and password. If we would have done the migration, they would not be there any more
qCDebug(dcNetatmo()) << "Found deprecated username and password in the settings. Performing migration to plain OAuth2...";
// Create a connection, get the refresh token, delete the connection and continue with normal setup.
NetatmoConnection *connection = new NetatmoConnection(hardwareManager()->networkManager(), m_clientId, m_clientSecret, thing);
connect(info, &ThingSetupInfo::aborted, connection, &NetatmoConnection::deleteLater);
connect(connection, &NetatmoConnection::authenticatedChanged, info, [this, info, thing, connection](bool authenticated){
connection->deleteLater();
if (authenticated) {
pluginStorage()->beginGroup(thing->id().toString());
pluginStorage()->setValue("refresh_token", connection->refreshToken());
// Success, we can delete username and passord once and for all and keep only the refresh token
pluginStorage()->remove("username");
pluginStorage()->remove("password");
pluginStorage()->endGroup();
qCDebug(dcNetatmo()) << "Migration finished successfully. Continue with normal setup";
setupConnection(info);
} else {
qCDebug(dcNetatmo()) << "Authentication process failed.";
info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Authentication failed. Please reconfigure the connection."));
}
});
connection->getAccessTokenFromUsernamePassword(username, password);
return true;
}
bool IntegrationPluginNetatmo::loadClientCredentials()
{
QByteArray clientId = configValue(netatmoPluginCustomClientIdParamTypeId).toByteArray();
QByteArray clientSecret = configValue(netatmoPluginCustomClientSecretParamTypeId).toByteArray();
if (clientId.isEmpty() || clientSecret.isEmpty()) {
clientId = apiKeyStorage()->requestKey("netatmo").data("clientId");
clientSecret = apiKeyStorage()->requestKey("netatmo").data("clientSecret");
} else {
qCDebug(dcNetatmo()) << "Using custom client id and secret from plugin configuration.";
}
if (clientId.isEmpty() || clientSecret.isEmpty()) {
qCWarning(dcNetatmo()) << "No API key installed. Please install a valid api key provider plugin.";
return false;
} else {
qCDebug(dcNetatmo()) << "Using API client secret and key from API key provider";
}
m_clientId = clientId;
m_clientSecret = clientSecret;
qCDebug(dcNetatmo()) << "API client ID" << NetatmoConnection::censorDebugOutput(m_clientId);
qCDebug(dcNetatmo()) << "API client secret" << NetatmoConnection::censorDebugOutput(m_clientSecret);
return true;
}