homeconnect: Add Qt6 support

master
Simon Stürz 2025-08-08 13:48:23 +02:00
parent d2eacc95a1
commit 233b89d65e
4 changed files with 59 additions and 57 deletions

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Copyright 2013 - 2025, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
@ -35,6 +35,7 @@
#include <QJsonObject>
#include <QJsonArray>
#include <QUrlQuery>
#include <QRegularExpression>
HomeConnect::HomeConnect(NetworkAccessManager *networkmanager, const QByteArray &clientKey, const QByteArray &clientSecret, bool simulationMode, QObject *parent) :
QObject(parent),
@ -76,12 +77,12 @@ void HomeConnect::setSimulationMode(bool simulation)
QUrl HomeConnect::getLoginUrl(const QUrl &redirectUrl, const QString &scope)
{
if (m_clientKey.isEmpty()) {
qWarning(dcHomeConnect) << "Client key not defined!";
qCWarning(dcHomeConnect()) << "Client key not defined!";
return QUrl("");
}
if (redirectUrl.isEmpty()){
qWarning(dcHomeConnect) << "No redirect uri defined!";
qCWarning(dcHomeConnect()) << "No redirect uri defined!";
}
m_redirectUri = QUrl::toPercentEncoding(redirectUrl.toString());
@ -93,7 +94,7 @@ QUrl HomeConnect::getLoginUrl(const QUrl &redirectUrl, const QString &scope)
queryParams.addQueryItem("scope", scope);
queryParams.addQueryItem("state", QUuid::createUuid().toString());
queryParams.addQueryItem("nonce", QUuid::createUuid().toString());
m_codeChallenge = QUuid::createUuid().toString().remove(QRegExp("[{}-]"));
m_codeChallenge = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
queryParams.addQueryItem("code_challenge", m_codeChallenge);
queryParams.addQueryItem("code_challenge_method", "plain");
url.setQuery(queryParams);
@ -103,7 +104,7 @@ QUrl HomeConnect::getLoginUrl(const QUrl &redirectUrl, const QString &scope)
void HomeConnect::onRefreshTimeout()
{
qCDebug(dcHomeConnect) << "Refresh authentication token";
qCDebug(dcHomeConnect()) << "Refresh authentication token";
getAccessTokenFromRefreshToken(m_refreshToken);
}
@ -131,19 +132,19 @@ bool HomeConnect::checkStatusCode(QNetworkReply *reply, const QByteArray &rawDat
case 400: //Error occurred (e.g. validation error - value is out of range)
if(!jsonDoc.toVariant().toMap().contains("error")) {
if(jsonDoc.toVariant().toMap().value("error").toString() == "invalid_client") {
qWarning(dcHomeConnect()) << "Client token provided doesnt correspond to client that generated auth code.";
qCWarning(dcHomeConnect()) << "Client token provided doesnt correspond to client that generated auth code.";
}
if(jsonDoc.toVariant().toMap().value("error").toString() == "invalid_redirect_uri") {
qWarning(dcHomeConnect()) << "Missing redirect_uri parameter.";
qCWarning(dcHomeConnect()) << "Missing redirect_uri parameter.";
}
if(jsonDoc.toVariant().toMap().value("error").toString() == "invalid_code") {
qWarning(dcHomeConnect()) << "Expired authorization code.";
qCWarning(dcHomeConnect()) << "Expired authorization code.";
}
}
setAuthenticated(false);
return false;
case 401:
qWarning(dcHomeConnect()) << "Client does not have permission to use this API.";
qCWarning(dcHomeConnect()) << "Client does not have permission to use this API.";
setAuthenticated(false);
return false;
case 403:
@ -154,7 +155,7 @@ bool HomeConnect::checkStatusCode(QNetworkReply *reply, const QByteArray &rawDat
qCWarning(dcHomeConnect()) << "Not Found. This resource is not available (e.g. no images on washing machine)";
return false;
case 405:
qWarning(dcHomeConnect()) << "Wrong HTTP method used.";
qCWarning(dcHomeConnect()) << "Wrong HTTP method used.";
setAuthenticated(false);
return false;
case 408:
@ -194,7 +195,7 @@ bool HomeConnect::checkStatusCode(QNetworkReply *reply, const QByteArray &rawDat
void HomeConnect::getAccessTokenFromRefreshToken(const QByteArray &refreshToken)
{
if (refreshToken.isEmpty()) {
qWarning(dcHomeConnect) << "No refresh token given!";
qCWarning(dcHomeConnect()) << "No refresh token given!";
setAuthenticated(false);
return;
}
@ -228,9 +229,9 @@ void HomeConnect::getAccessTokenFromRefreshToken(const QByteArray &refreshToken)
if (data.toVariant().toMap().contains("expires_in")) {
int expireTime = data.toVariant().toMap().value("expires_in").toInt();
qCDebug(dcHomeConnect) << "Access token expires int" << expireTime << "s, at" << QDateTime::currentDateTime().addSecs(expireTime).toString();
qCDebug(dcHomeConnect()) << "Access token expires int" << expireTime << "s, at" << QDateTime::currentDateTime().addSecs(expireTime).toString();
if (!m_tokenRefreshTimer) {
qWarning(dcHomeConnect()) << "Access token refresh timer not initialized";
qCWarning(dcHomeConnect()) << "Access token refresh timer not initialized";
return;
}
if (expireTime < 20) {
@ -246,11 +247,11 @@ void HomeConnect::getAccessTokenFromAuthorizationCode(const QByteArray &authoriz
{
// Obtaining access token
if(authorizationCode.isEmpty())
qWarning(dcHomeConnect) << "No authorization code given!";
qCWarning(dcHomeConnect()) << "No authorization code given!";
if(m_clientKey.isEmpty())
qWarning(dcHomeConnect) << "Client key not set!";
qCWarning(dcHomeConnect()) << "Client key not set!";
if(m_clientSecret.isEmpty())
qWarning(dcHomeConnect) << "Client secret not set!";
qCWarning(dcHomeConnect()) << "Client secret not set!";
QUrl url = QUrl(m_baseTokenUrl);
QUrlQuery query; url.setQuery(query);
@ -288,7 +289,7 @@ void HomeConnect::getAccessTokenFromAuthorizationCode(const QByteArray &authoriz
int expireTime = jsonDoc.toVariant().toMap().value("expires_in").toInt();
qCDebug(dcHomeConnect()) << "Token expires in" << expireTime << "s, at" << QDateTime::currentDateTime().addSecs(expireTime).toString();
if (!m_tokenRefreshTimer) {
qWarning(dcHomeConnect()) << "Token refresh timer not initialized";
qCWarning(dcHomeConnect()) << "Token refresh timer not initialized";
setAuthenticated(false);
return;
}
@ -546,7 +547,7 @@ QUuid HomeConnect::selectProgram(const QString &haId, const QString &programKey,
QUuid HomeConnect::setSelectedProgramOptions(const QString &haId, QList<HomeConnect::Option> options)
{
if (options.isEmpty())
return "";
return QUuid();
QUuid commandId = QUuid::createUuid();
QUrl url = QUrl(m_baseControlUrl+"/api/homeappliances/"+haId+"/programs/selected/options");
@ -754,7 +755,7 @@ void HomeConnect::connectEventStream()
QNetworkReply *reply = m_networkManager->get(request);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, [reply, this] {
connect(reply, &QNetworkReply::finished, this, [reply, this] {
int reconnectTime = 5000; // Usual reconnect in 5 s
if (reply->error() != QNetworkReply::NetworkError::NoError) {
qCDebug(dcHomeConnect()) << "Event stream error" << reply->errorString() << reply->readAll();

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Copyright 2013 - 2025, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
@ -35,7 +35,7 @@
#include <QTimer>
#include <QUuid>
#include "network/networkaccessmanager.h"
#include <network/networkaccessmanager.h>
class HomeConnect : public QObject
{
@ -177,13 +177,13 @@ signals:
void receivedAccessToken(const QByteArray &accessToken);
void commandExecuted(const QUuid &commandId, bool success);
void receivedHomeAppliances(const QList<HomeAppliance> &appliances);
void receivedHomeAppliances(const QList<HomeConnect::HomeAppliance> &appliances);
void receivedStatusList(const QString &haId, const QHash<QString, QVariant> &statusList);
void receivedPrograms(const QString &haId, const QStringList &programs);
void receivedAvailablePrograms(const QString &haId, const QStringList &programs);
void receivedSettings(const QString &haId, const QHash<QString, QVariant> &settings);
void receivedActiveProgram(const QString &haId, const QString &key, const QHash<QString, QVariant> &options);
void receivedSelectedProgram(const QString &haId, const QString &key, const QHash<QString, QVariant> &options);
void receivedEvents(EventType eventType, const QString &haId, const QList<Event> &events);
void receivedEvents(HomeConnect::EventType eventType, const QString &haId, const QList<HomeConnect::Event> &events);
};
#endif // HOMECONNECT_H

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Copyright 2013 - 2025, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
@ -29,10 +29,10 @@
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "integrationpluginhomeconnect.h"
#include "integrations/integrationplugin.h"
#include "network/networkaccessmanager.h"
#include "plugininfo.h"
#include <network/networkaccessmanager.h>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrlQuery>
@ -218,7 +218,7 @@ void IntegrationPluginHomeConnect::confirmPairing(ThingPairingInfo *info, const
HomeConnect *homeConnect = m_setupHomeConnectConnections.value(info->thingId());
if (!homeConnect) {
qWarning(dcHomeConnect()) << "No HomeConnect connection found for device:" << info->thingName();
qCWarning(dcHomeConnect()) << "No HomeConnect connection found for device:" << info->thingName();
m_setupHomeConnectConnections.remove(info->thingId());
return info->finish(Thing::ThingErrorHardwareFailure);
}
@ -326,7 +326,7 @@ void IntegrationPluginHomeConnect::postSetupThing(Thing *thing)
Q_FOREACH (Thing *thing, myThings().filterByThingClassId(homeConnectAccountThingClassId)) {
HomeConnect *homeConnect = m_homeConnectConnections.value(thing);
if (!homeConnect) {
qWarning(dcHomeConnect()) << "No HomeConnect account found for" << thing->name();
qCWarning(dcHomeConnect()) << "No HomeConnect account found for" << thing->name();
continue;
}
homeConnect->getHomeAppliances();
@ -411,7 +411,7 @@ void IntegrationPluginHomeConnect::executeAction(ThingActionInfo *info)
options.append(coffeeTemperature);
requestId = homeConnect->setSelectedProgramOptions(haid, options);
m_pendingActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, [requestId, this] {
connect(info, &ThingActionInfo::aborted, this, [requestId, this] {
m_pendingActions.remove(requestId);
});
@ -429,7 +429,7 @@ void IntegrationPluginHomeConnect::executeAction(ThingActionInfo *info)
options.append(beanAmount);
requestId = homeConnect->setSelectedProgramOptions(haid, options);
m_pendingActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, [requestId, this] {
connect(info, &ThingActionInfo::aborted, this, [requestId, this] {
m_pendingActions.remove(requestId);
});
@ -443,7 +443,7 @@ void IntegrationPluginHomeConnect::executeAction(ThingActionInfo *info)
options.append(fillQuantity);
requestId = homeConnect->setSelectedProgramOptions(haid, options);
m_pendingActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, [requestId, this] {
connect(info, &ThingActionInfo::aborted, this, [requestId, this] {
m_pendingActions.remove(requestId);
});
} else if (action.actionTypeId() == coffeeMakerStartActionTypeId) {
@ -454,7 +454,7 @@ void IntegrationPluginHomeConnect::executeAction(ThingActionInfo *info)
QUuid requestId;
requestId = homeConnect->startProgram(haid, m_selectedProgram.value(thing), QList<HomeConnect::Option>());
m_pendingActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, [requestId, this] {
connect(info, &ThingActionInfo::aborted, this, [requestId, this] {
m_pendingActions.remove(requestId);
});
}
@ -473,7 +473,7 @@ void IntegrationPluginHomeConnect::executeAction(ThingActionInfo *info)
startTime.value = action.param(dishwasherStartActionStartTimeParamTypeId).value().toInt() * 60;
requestId = homeConnect->startProgram(haid, m_selectedProgram.value(thing), QList<HomeConnect::Option>() << startTime);
m_pendingActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, [requestId, this] {
connect(info, &ThingActionInfo::aborted, this, [requestId, this] {
m_pendingActions.remove(requestId);
});
} else {
@ -489,7 +489,7 @@ void IntegrationPluginHomeConnect::executeAction(ThingActionInfo *info)
QUuid requestId;
requestId = homeConnect->startProgram(haid, m_selectedProgram.value(thing), QList<HomeConnect::Option>());
m_pendingActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, [requestId, this] {
connect(info, &ThingActionInfo::aborted, this, [requestId, this] {
m_pendingActions.remove(requestId);
});
}
@ -502,7 +502,7 @@ void IntegrationPluginHomeConnect::executeAction(ThingActionInfo *info)
QUuid requestId;
requestId = homeConnect->startProgram(haid, m_selectedProgram.value(thing), QList<HomeConnect::Option>());
m_pendingActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, [requestId, this] {
connect(info, &ThingActionInfo::aborted, this, [requestId, this] {
m_pendingActions.remove(requestId);
});
} else if (action.actionTypeId() == dryerDryingTargetActionTypeId) {
@ -521,7 +521,7 @@ void IntegrationPluginHomeConnect::executeAction(ThingActionInfo *info)
options.append(dryingTarget);
requestId = homeConnect->setSelectedProgramOptions(haid, options);
m_pendingActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, [requestId, this] {
connect(info, &ThingActionInfo::aborted, this, [requestId, this] {
m_pendingActions.remove(requestId);
});
}
@ -538,7 +538,7 @@ void IntegrationPluginHomeConnect::executeAction(ThingActionInfo *info)
void IntegrationPluginHomeConnect::thingRemoved(Thing *thing)
{
qCDebug(dcHomeConnect) << "Delete " << thing->name();
qCDebug(dcHomeConnect()) << "Delete " << thing->name();
if (thing->thingClassId() == homeConnectAccountThingClassId) {
HomeConnect *homeConnect = m_homeConnectConnections.take(thing);
if (homeConnect)
@ -567,7 +567,7 @@ void IntegrationPluginHomeConnect::browseThing(BrowseResult *result)
homeConnect->getProgramsAvailable(haid);
connect(homeConnect, &HomeConnect::receivedAvailablePrograms, result, [result, this] (const QString &haId, const QStringList programs) {
if(result->thing()->paramValue(m_idParamTypeIds.value(result->thing()->thingClassId())).toString() == haId) {
Q_FOREACH(QString program, programs) {
Q_FOREACH(const QString &program, programs) {
BrowserItem item;
item.setExecutable(true);
item.setDisplayName(program.split('.').last());
@ -723,7 +723,7 @@ void IntegrationPluginHomeConnect::parseKey(Thing *thing, const QString &key, co
if (value.toString().split('.').last().contains("Finished")) {
//apparently the finished event is not emitted by HomeConnect so this will hopefully do the trick
if (m_programFinishedEventTypeIds.contains(thing->thingClassId())) {
emitEvent(Event(m_programFinishedEventTypeIds.value(thing->thingClassId()), thing->id()));
emit emitEvent(Event(m_programFinishedEventTypeIds.value(thing->thingClassId()), thing->id()));
}
if (m_progressStateTypeIds.contains(thing->thingClassId())) {
thing->setStateValue(m_progressStateTypeIds.value(thing->thingClassId()), 0);
@ -732,21 +732,21 @@ void IntegrationPluginHomeConnect::parseKey(Thing *thing, const QString &key, co
// Program Progress Events
} else if (key == "BSH.Common.Event.ProgramAborted") {
if (m_programFinishedEventTypeIds.contains(thing->thingClassId())) {
emitEvent(Event(m_programFinishedEventTypeIds.value(thing->thingClassId()), thing->id()));
emit emitEvent(Event(m_programFinishedEventTypeIds.value(thing->thingClassId()), thing->id()));
}
if (m_progressStateTypeIds.contains(thing->thingClassId())) {
thing->setStateValue(m_progressStateTypeIds.value(thing->thingClassId()), 0);
}
} else if (key == "BSH.Common.Event.ProgramFinished") {
if (m_programFinishedEventTypeIds.contains(thing->thingClassId())) {
emitEvent(Event(m_programFinishedEventTypeIds.value(thing->thingClassId()), thing->id()));
emit emitEvent(Event(m_programFinishedEventTypeIds.value(thing->thingClassId()), thing->id()));
}
if (m_progressStateTypeIds.contains(thing->thingClassId())) {
thing->setStateValue(m_progressStateTypeIds.value(thing->thingClassId()), 0);
}
//} else if (key == "BSH.Common.Event.AlarmClockElapsed") {
} else if (key == "Cooking.Oven.Event.PreheatFinished") {
emitEvent(Event(ovenPreheatFinishedEventTypeId, thing->id()));
emit emitEvent(Event(ovenPreheatFinishedEventTypeId, thing->id()));
// Home Appliance State Changes
//} else if (key == "BSH.Common.Setting.PowerState") {
} else if (key == "BSH.Common.Status.RemoteControlActive") {
@ -768,23 +768,23 @@ void IntegrationPluginHomeConnect::parseKey(Thing *thing, const QString &key, co
// Home Appliance Events
} else if (key == "ConsumerProducts.CoffeeMaker.Event.BeanContainerEmpty") {
emitEvent(Event(coffeeMakerBeanContainerEmptyEventTypeId, thing->id()));
emit emitEvent(Event(coffeeMakerBeanContainerEmptyEventTypeId, thing->id()));
} else if (key == "ConsumerProducts.CoffeeMaker.Event.WaterTankEmpty") {
emitEvent(Event(coffeeMakerWaterTankEmptyEventTypeId, thing->id()));
emit emitEvent(Event(coffeeMakerWaterTankEmptyEventTypeId, thing->id()));
} else if (key == "ConsumerProducts.CoffeeMaker.Event.DripTrayFull") {
emitEvent(Event(coffeeMakerDripTrayFullEventTypeId, thing->id()));
emit emitEvent(Event(coffeeMakerDripTrayFullEventTypeId, thing->id()));
} else if (key == "Refrigeration.FridgeFreezer.Event.DoorAlarmFreezer") {;
emitEvent(Event(fridgeDoorAlarmFreezerEventTypeId, thing->id()));
emit emitEvent(Event(fridgeDoorAlarmFreezerEventTypeId, thing->id()));
} else if (key == "Refrigeration.FridgeFreezer.Event.DoorAlarmRefrigerator") {
emitEvent(Event(fridgeDoorAlarmRefrigeratorEventTypeId, thing->id()));
emit emitEvent(Event(fridgeDoorAlarmRefrigeratorEventTypeId, thing->id()));
} else if (key == "Refrigeration.FridgeFreezer.Event.TemperatureAlarmFreezer") {
emitEvent(Event(fridgeTemperatureAlarmFreezerEventTypeId, thing->id()));
emit emitEvent(Event(fridgeTemperatureAlarmFreezerEventTypeId, thing->id()));
} else if (key == "ConsumerProducts.CleaningRobot.Event.EmptyDustBoxAndCleanFilter") {
emitEvent(Event(cleaningRobotEmptyDustBoxAndCleanFilterEventTypeId, thing->id()));
emit emitEvent(Event(cleaningRobotEmptyDustBoxAndCleanFilterEventTypeId, thing->id()));
} else if (key == "ConsumerProducts.CleaningRobot.Event.RobotIsStuck") {
emitEvent(Event(cleaningRobotRobotIsStuckEventTypeId, thing->id()));
emit emitEvent(Event(cleaningRobotRobotIsStuckEventTypeId, thing->id()));
} else if (key == "ConsumerProducts.CleaningRobot.Event.DockingStationNotFound") {
emitEvent(Event(cleaningRobotDockingStationNotFoundEventTypeId, thing->id()));
emit emitEvent(Event(cleaningRobotDockingStationNotFoundEventTypeId, thing->id()));
// UNDOCUMENTED
} else if (key == "Cooking.Oven.Status.CurrentCavityTemperature") {
@ -999,7 +999,7 @@ void IntegrationPluginHomeConnect::onReceivedStatusList(const QString &haId, con
Q_FOREACH(Thing *thing, myThings().filterByParentId(parentThing->id())) {
if (thing->paramValue(m_idParamTypeIds.value(thing->thingClassId())).toString() == haId) {
qCDebug(dcHomeConnect()) << "Received status list device" << thing->name();
Q_FOREACH(QString key, statusList.keys()) {
Q_FOREACH(const QString &key, statusList.keys()) {
parseKey(thing, key, statusList.value(key));
}
break;
@ -1076,7 +1076,7 @@ void IntegrationPluginHomeConnect::onReceivedSettings(const QString &haId, const
Q_FOREACH(Thing *thing, myThings().filterByParentId(parentThing->id())) {
if (thing->paramValue(m_idParamTypeIds.value(thing->thingClassId())).toString() == haId) {
qCDebug(dcHomeConnect()) << "Received setting" << thing->name() << settings;
Q_FOREACH(QString setting, settings.keys()) {
Q_FOREACH(const QString &setting, settings.keys()) {
parseSettingKey(thing, setting, settings.value(setting));
}
break;

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Copyright 2013 - 2025, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
@ -31,13 +31,14 @@
#ifndef INTEGRATIONPLUGINHOMECONNECT_H
#define INTEGRATIONPLUGINHOMECONNECT_H
#include "integrations/integrationplugin.h"
#include "plugintimer.h"
#include "homeconnect.h"
#include <integrations/integrationplugin.h>
#include <plugintimer.h>
#include <QHash>
#include <QDebug>
#include "homeconnect.h"
class IntegrationPluginHomeConnect : public IntegrationPlugin
{
Q_OBJECT