This commit is contained in:
Simon Stürz 2025-08-22 14:17:09 +02:00
parent f5bdc2e8c2
commit 453179ab53
6 changed files with 325 additions and 2 deletions

View File

@ -37,6 +37,8 @@
#include <network/networkdevicediscovery.h>
#include "senecconnection.h"
IntegrationPluginSenec::IntegrationPluginSenec()
{
// Testing the convert methods
@ -52,6 +54,8 @@ IntegrationPluginSenec::IntegrationPluginSenec()
// QString rawValue = "u8_64"; // 100
// quint8 value = SenecStorageLan::parseUInt8(rawValue);
// qCWarning(dcSenec()) << rawValue << value;
}
IntegrationPluginSenec::~IntegrationPluginSenec()
@ -107,7 +111,31 @@ void IntegrationPluginSenec::discoverThings(ThingDiscoveryInfo *info)
void IntegrationPluginSenec::startPairing(ThingPairingInfo *info)
{
info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Please enter username and password for your SENEC.home account."));
if (info->thingClassId() == senecConnectionThingClassId) {
qCDebug(dcSenec()) << "Start pairing process ...";
SenecConnection *connection = new SenecConnection(hardwareManager()->networkManager(), this);
//m_unfinishedTadoAccounts.insert(info->thingId(), tado);
connect(info, &ThingPairingInfo::aborted, this, [connection]() {
qCWarning(dcSenec()) << "Thing pairing has been aborted, cleaning up...";
//m_unfinishedTadoAccounts.remove(info->thingId());
connection->deleteLater();
});
connect(connection, &SenecConnection::initFinished, info, [info, connection] (bool success) {
if (!success) {
info->finish(Thing::ThingErrorAuthenticationFailure);
return;
}
qCDebug(dcSenec()) << "Senec server is reachable. Starting the OpenID auth process" << connection->authEndpoint().toString(QUrl::FullyEncodedy);
info->setOAuthUrl(connection->authEndpoint());
info->finish(Thing::ThingErrorNoError);
});
connection->initialize();
}
}
void IntegrationPluginSenec::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret)

View File

@ -40,6 +40,43 @@
}
]
},
{
"id": "290e4abb-d000-43a4-9e23-e298e5c36707",
"name": "senecConnection",
"displayName": "SENEC connection",
"interfaces": ["account"],
"createMethods": ["user"],
"setupMethod": "oauth",
"paramTypes": [
],
"stateTypes": [
{
"id": "1180576a-1de2-4815-b442-877b572ce586",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"defaultValue": true,
"cached": false,
"type": "bool"
},
{
"id": "ff40d5c7-3095-4b3e-9e10-4c0774336764",
"name": "loggedIn",
"displayName": "Logged in",
"displayNameEvent": "Logged in changed",
"defaultValue": true,
"type": "bool"
},
{
"id": "5d3b2396-6528-47c2-b5a4-f751531bccea",
"name": "userDisplayName",
"displayName": "User name",
"displayNameEvent": "User name changed",
"defaultValue": "",
"type": "QString"
}
]
},
{
"id": "345df3b1-d411-4db5-bbb2-3b14eb86c1ba",
"name": "senecStorageLan",

View File

@ -5,12 +5,14 @@ QT+= network
SOURCES += \
integrationpluginsenec.cpp \
senecaccount.cpp \
senecconnection.cpp \
senecdiscovery.cpp \
senecstoragelan.cpp
HEADERS += \
integrationpluginsenec.h \
senecaccount.h \
senecconnection.h \
senecdiscovery.h \
senecstoragelan.h

184
senec/senecconnection.cpp Normal file
View File

@ -0,0 +1,184 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2025, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; version 3. This project 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "senecconnection.h"
#include "extern-plugininfo.h"
#include <QUrlQuery>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QRegularExpression>
SenecConnection::SenecConnection(NetworkAccessManager *networkManager, QObject *parent)
: QObject{parent},
m_networkManager{networkManager}
{
m_configUrl = QUrl("https://sso.senec.com/realms/senec/.well-known/openid-configuration");
m_systemsUrl = QUrl("https://senec-app-systems-proxy.prod.senec.dev");
m_measurementsUrl = QUrl("https://senec-app-measurements-proxy.prod.senec.dev");
m_clientId = QString("endcustomer-app-frontend");
m_redirectUrl =/* QString("http://127.0.0.1:8888");*/ QUrl("senec-app-auth://keycloak.prod");
m_scopes << "roles" << "meinsenec" << "openid";
m_userAgent = QString("nymea (+https://github.com/nymea/nymea)");
}
void SenecConnection::initialize()
{
qCDebug(dcSenec()) << "OpenID: Initialize and get configuration";
QNetworkRequest request(m_configUrl);
request.setHeader(QNetworkRequest::UserAgentHeader, m_userAgent.toUtf8());
QNetworkReply *reply = m_networkManager->get(request);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, this, [this, reply, request](){
QByteArray data = reply->readAll();
if (reply->error() != QNetworkReply::NoError) {
qCWarning(dcSenec()) << "OpenID: Failed to get configuration" << request.url().toString() << reply->errorString();
if (!data.isEmpty())
qCWarning(dcSenec()) << "OpenID: Response data:" << qUtf8Printable(data);
emit initFinished(false);
return;
}
//qCDebug(dcPushNotifications()) << "OAuth2: Request access token response" << qUtf8Printable(data);
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(dcSenec()) << "OpenID: Failed to get configuration. Response data JSON error:" << error.errorString();
emit initFinished(false);
return;
}
QVariantMap configMap = jsonDoc.toVariant().toMap();
m_authEndpoint = QUrl(configMap.value("authorization_endpoint").toString());
QUrlQuery queryParams;
queryParams.addQueryItem("client_id", m_clientId);
queryParams.addQueryItem("redirect_uri", m_redirectUrl.toString());
queryParams.addQueryItem("response_type", "code");
queryParams.addQueryItem("scope", m_scopes.join("+"));
// queryParams.addQueryItem("nonce", QUuid::createUuid().toString());
// m_codeChallenge = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
// queryParams.addQueryItem("code_challenge", m_codeChallenge);
// queryParams.addQueryItem("code_challenge_method", "S256");
m_authEndpoint.setQuery(queryParams);
emit initFinished(true);
});
}
QUrl SenecConnection::authEndpoint() const
{
return m_authEndpoint;
}
// void SenecConnection::login(const QString &username, const QString &password)
// {
// Q_UNUSED(username, password)
// QNetworkRequest request(m_configUrl);
// request.setHeader(QNetworkRequest::UserAgentHeader, m_userAgent.toUtf8());
// QNetworkReply *reply = m_networkManager->get(request);
// connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
// connect(reply, &QNetworkReply::finished, this, [this, reply, request](){
// QByteArray data = reply->readAll();
// if (reply->error() != QNetworkReply::NoError) {
// qCWarning(dcSenec()) << "OpenID: Failed to get configuration" << request.url().toString() << reply->errorString();
// if (!data.isEmpty())
// qCWarning(dcSenec()) << "OpenID: Response data:" << qUtf8Printable(data);
// // setAuthenticated(false);
// // // Retry in 30 seconds, maybe we are not online yet
// // m_refreshTimer.start(60 * 1000);
// return;
// }
// //qCDebug(dcPushNotifications()) << "OAuth2: Request access token response" << qUtf8Printable(data);
// QJsonParseError error;
// QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
// if (error.error != QJsonParseError::NoError) {
// qCWarning(dcSenec()) << "OpenID: Failed to get configuration. Response data JSON error:" << error.errorString();
// //setAuthenticated(false);
// return;
// }
// QVariantMap configMap = jsonDoc.toVariant().toMap();
// m_authEndpoint = QUrl(configMap.value("authorization_endpoint").toString());
// // QUrl url(m_baseTokenUrl);
// // QUrlQuery query;
// // query.clear();
// // query.addQueryItem("grant_type", "refresh_token");
// // query.addQueryItem("refresh_token", refreshToken);
// // query.addQueryItem("client_secret", m_clientSecret);
// // QNetworkRequest request(m_authEndpoint);
// // request.setHeader(QNetworkRequest::UserAgentHeader, m_userAgent.toUtf8());
// // request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
// // // QUrlQuery queryParams;
// // // queryParams.addQueryItem("client_id", m_clientId);
// // // queryParams.addQueryItem("redirect_uri", m_redirectUrl);
// // // queryParams.addQueryItem("response_type", "code");
// // // queryParams.addQueryItem("scope", m_scopes.join(" "));
// // // queryParams.addQueryItem("nonce", QUuid::createUuid().toString());
// // // m_codeChallenge = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
// // // queryParams.addQueryItem("code_challenge", m_codeChallenge);
// // // queryParams.addQueryItem("code_challenge_method", "S256");
// // // url.setQuery(queryParams);
// // // QNetworkReply *reply = m_networkManager->post(request, query.toString().toUtf8());
// // // connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
// // // connect(reply, &QNetworkReply::finished, this, [this, reply](){
// // redirect_uri: REDIRECT_URI,
// // scope: SCOPE,
// // code_challenge:,
// // code_challenge_method: 'S256',
// });
// });
// }

72
senec/senecconnection.h Normal file
View File

@ -0,0 +1,72 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2025, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; version 3. This project 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef SENECCONNECTION_H
#define SENECCONNECTION_H
#include <QUrl>
#include <QObject>
#include <network/networkaccessmanager.h>
class SenecConnection : public QObject
{
Q_OBJECT
public:
explicit SenecConnection(NetworkAccessManager *networkManager, QObject *parent = nullptr);
void initialize();
QUrl authEndpoint() const;
// void login(const QString &username, const QString &password);
signals:
void initFinished(bool success);
private:
NetworkAccessManager *m_networkManager = nullptr;
QUrl m_configUrl;
QUrl m_systemsUrl;
QUrl m_measurementsUrl;
QString m_clientId;
QUrl m_redirectUrl;
QStringList m_scopes;
QString m_userAgent;
// Fetch from config url
QUrl m_authEndpoint;
QString m_codeChallenge;
};
#endif // SENECCONNECTION_H

View File

@ -1,6 +1,6 @@
include(../plugins.pri)
QT *= serialport
QT += serialport
PKGCONFIG += libudev