mirror of
https://github.com/nymea/nymea-plugins.git
synced 2026-07-15 00:22:26 +02:00
Add V2 and V3 basic communication
This commit is contained in:
parent
1f0733db97
commit
37d9e1ae04
@ -8,4 +8,4 @@ Currently supported and tested models:
|
||||
|
||||
## More
|
||||
|
||||
https://senec.com
|
||||
https://senec.com
|
||||
|
||||
@ -36,6 +36,14 @@
|
||||
|
||||
IntegrationPluginSenec::IntegrationPluginSenec()
|
||||
{
|
||||
// Testing the convert methods
|
||||
// QString rawValue = "fl_42A2E5E4"; // 81.449
|
||||
// float value = SenecStorageLan::parseFloat(rawValue);
|
||||
// qCWarning(dcSenec()) << rawValue << value;
|
||||
|
||||
// QString rawValue = "st_foobar";
|
||||
// QString value = SenecStorageLan::parseString(rawValue);
|
||||
// qCWarning(dcSenec()) << rawValue << value;
|
||||
|
||||
}
|
||||
|
||||
@ -44,6 +52,52 @@ IntegrationPluginSenec::~IntegrationPluginSenec()
|
||||
|
||||
}
|
||||
|
||||
void IntegrationPluginSenec::discoverThings(ThingDiscoveryInfo *info)
|
||||
{
|
||||
if (info->thingClassId() == senecConnectionThingClassId) {
|
||||
|
||||
if (!hardwareManager()->networkDeviceDiscovery()->available()) {
|
||||
qCWarning(dcSenec()) << "Failed to discover network devices. The network device discovery is not available.";
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Unable to discover devices in your network."));
|
||||
return;
|
||||
}
|
||||
|
||||
// qCInfo(dcESPSomfyRTS()) << "Starting network discovery...";
|
||||
// EspSomfyRtsDiscovery *discovery = new EspSomfyRtsDiscovery(hardwareManager()->networkManager(), hardwareManager()->networkDeviceDiscovery(), info);
|
||||
// connect(discovery, &EspSomfyRtsDiscovery::discoveryFinished, info, [=](){
|
||||
// ThingDescriptors descriptors;
|
||||
// qCInfo(dcESPSomfyRTS()) << "Discovery finished. Found" << discovery->results().count() << "devices";
|
||||
// foreach (const EspSomfyRtsDiscovery::Result &result, discovery->results()) {
|
||||
// qCInfo(dcESPSomfyRTS()) << "Discovered device on" << result.networkDeviceInfo;
|
||||
|
||||
// QString title = "ESP Somfy RTS (" + result.name + ")";
|
||||
// QString description = result.networkDeviceInfo.address().toString();
|
||||
|
||||
// ThingDescriptor descriptor(espSomfyRtsThingClassId, title, description);
|
||||
|
||||
// ParamList params;
|
||||
// params << Param(espSomfyRtsThingMacAddressParamTypeId, result.networkDeviceInfo.thingParamValueMacAddress());
|
||||
// params << Param(espSomfyRtsThingHostNameParamTypeId, result.networkDeviceInfo.thingParamValueHostName());
|
||||
// params << Param(espSomfyRtsThingAddressParamTypeId, result.networkDeviceInfo.thingParamValueAddress());
|
||||
// descriptor.setParams(params);
|
||||
|
||||
// // Check if we already have set up this device
|
||||
// Thing *existingThing = myThings().findByParams(params);
|
||||
// if (existingThing) {
|
||||
// qCDebug(dcESPSomfyRTS()) << "This thing already exists in the system:" << result.networkDeviceInfo;
|
||||
// descriptor.setThingId(existingThing->id());
|
||||
// }
|
||||
|
||||
// info->addThingDescriptor(descriptor);
|
||||
// }
|
||||
|
||||
// info->finish(Thing::ThingErrorNoError);
|
||||
// });
|
||||
|
||||
// discovery->startDiscovery();
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginSenec::startPairing(ThingPairingInfo *info)
|
||||
{
|
||||
info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Please enter username and password for your SENEC.home account."));
|
||||
@ -51,59 +105,62 @@ void IntegrationPluginSenec::startPairing(ThingPairingInfo *info)
|
||||
|
||||
void IntegrationPluginSenec::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret)
|
||||
{
|
||||
qCDebug(dcSenec()) << "Start logging in" << username << secret.left(2) + QString(secret.length() - 2, '*');
|
||||
if (info->thingClassId() == senecAccountThingClassId) {
|
||||
|
||||
QVariantMap requestMap;
|
||||
requestMap.insert("username", username);
|
||||
requestMap.insert("password", secret);
|
||||
qCDebug(dcSenec()) << "Start logging in" << username << secret.left(2) + QString(secret.length() - 2, '*');
|
||||
|
||||
QNetworkRequest request(SenecAccount::loginUrl());
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
QVariantMap requestMap;
|
||||
requestMap.insert("username", username);
|
||||
requestMap.insert("password", secret);
|
||||
|
||||
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QJsonDocument::fromVariant(requestMap).toJson(QJsonDocument::Indented));
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, this, [reply, info, username, this] {
|
||||
QNetworkRequest request(SenecAccount::loginUrl());
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
// Check HTTP status code
|
||||
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
||||
qCWarning(dcSenec()) << "Login request finished with error. Status:" << status << "Error:" << reply->errorString();
|
||||
info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Username or password is invalid."));
|
||||
return;
|
||||
}
|
||||
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QJsonDocument::fromVariant(requestMap).toJson(QJsonDocument::Indented));
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, this, [reply, info, username, this] {
|
||||
|
||||
// Note: as of now (API 4.4.3) the login seems to return a static token, which does not require any refresh.
|
||||
// Not as bad as saving user and password on the device, but almost ...
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
// Check HTTP status code
|
||||
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
||||
qCWarning(dcSenec()) << "Login request finished with error. Status:" << status << "Error:" << reply->errorString();
|
||||
info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Username or password is invalid."));
|
||||
return;
|
||||
}
|
||||
|
||||
// https://documenter.getpostman.com/view/932140/2s9YXib2td
|
||||
// Note: as of now (API 4.4.3) the login seems to return a static token, which does not require any refresh.
|
||||
// Not as bad as saving user and password on the device, but almost ...
|
||||
|
||||
QByteArray responseData = reply->readAll();
|
||||
// https://documenter.getpostman.com/view/932140/2s9YXib2td
|
||||
|
||||
QJsonParseError jsonError;
|
||||
QVariantMap responseMap = QJsonDocument::fromJson(responseData, &jsonError).toVariant().toMap();
|
||||
if (jsonError.error != QJsonParseError::NoError) {
|
||||
qCWarning(dcSenec()) << "Login request finished successfully, but the response contains invalid JSON object:" << responseData;
|
||||
info->finish(Thing::ThingErrorAuthenticationFailure);
|
||||
return;
|
||||
}
|
||||
QByteArray responseData = reply->readAll();
|
||||
|
||||
if (!responseMap.contains("token") || !responseMap.contains("refreshToken")) {
|
||||
qCWarning(dcSenec()) << "Login request finished successfully, but the response JSON does not contain the expected properties" << qUtf8Printable(responseData);
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
return;
|
||||
}
|
||||
QJsonParseError jsonError;
|
||||
QVariantMap responseMap = QJsonDocument::fromJson(responseData, &jsonError).toVariant().toMap();
|
||||
if (jsonError.error != QJsonParseError::NoError) {
|
||||
qCWarning(dcSenec()) << "Login request finished successfully, but the response contains invalid JSON object:" << responseData;
|
||||
info->finish(Thing::ThingErrorAuthenticationFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
QString token = responseMap.value("token").toString();
|
||||
QString refreshToken = responseMap.value("refreshToken").toString();
|
||||
if (!responseMap.contains("token") || !responseMap.contains("refreshToken")) {
|
||||
qCWarning(dcSenec()) << "Login request finished successfully, but the response JSON does not contain the expected properties" << qUtf8Printable(responseData);
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
pluginStorage()->beginGroup(info->thingId().toString());
|
||||
pluginStorage()->setValue("username", username);
|
||||
pluginStorage()->setValue("token", token);
|
||||
pluginStorage()->setValue("refreshToken", refreshToken);
|
||||
pluginStorage()->endGroup();
|
||||
QString token = responseMap.value("token").toString();
|
||||
QString refreshToken = responseMap.value("refreshToken").toString();
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
pluginStorage()->beginGroup(info->thingId().toString());
|
||||
pluginStorage()->setValue("username", username);
|
||||
pluginStorage()->setValue("token", token);
|
||||
pluginStorage()->setValue("refreshToken", refreshToken);
|
||||
pluginStorage()->endGroup();
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginSenec::setupThing(ThingSetupInfo *info)
|
||||
@ -357,7 +414,7 @@ void IntegrationPluginSenec::refresh(Thing *thing)
|
||||
// In that case we use the bigger power. Maybe we cloudl als sum them up, that should be tested...
|
||||
|
||||
float currentPower = 0;
|
||||
if (batteryCharge != 0 && batteryCharge != 0) {
|
||||
if (batteryCharge != 0 && batteryDischarge != 0) {
|
||||
if (batteryCharge > batteryDischarge) {
|
||||
currentPower = batteryCharge;
|
||||
} else {
|
||||
|
||||
@ -34,9 +34,13 @@
|
||||
#include <plugintimer.h>
|
||||
#include <integrations/integrationplugin.h>
|
||||
|
||||
#include <network/networkaccessmanager.h>
|
||||
#include <network/networkdevicemonitor.h>
|
||||
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
#include "senecaccount.h"
|
||||
#include "senecstoragelan.h"
|
||||
|
||||
class IntegrationPluginSenec : public IntegrationPlugin
|
||||
{
|
||||
@ -49,6 +53,8 @@ public:
|
||||
explicit IntegrationPluginSenec();
|
||||
~IntegrationPluginSenec();
|
||||
|
||||
void discoverThings(ThingDiscoveryInfo *info) override;
|
||||
|
||||
void startPairing(ThingPairingInfo *info) override;
|
||||
void confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret) override;
|
||||
|
||||
|
||||
@ -40,6 +40,50 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ef9e9a4c-2f66-4194-adcc-23f401572399",
|
||||
"name": "senecConnection",
|
||||
"displayName": "SENEC Connection",
|
||||
"interfaces": ["gateway", "networkdevice"],
|
||||
"createMethods": ["Discovery", "User"],
|
||||
"providedInterfaces": ["energystorage"],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "a45cabe9-9f76-405e-b47c-0a3d00bdd44b",
|
||||
"name": "address",
|
||||
"displayName": "Host address",
|
||||
"type": "QString",
|
||||
"inputType": "IPv4Address",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "51a1bbc0-28a5-43ac-9021-896acbf9413f",
|
||||
"name": "hostName",
|
||||
"displayName": "Host name",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "0790e1b1-c8c9-488f-9cda-2a0bbf14b4af",
|
||||
"name": "macAddress",
|
||||
"displayName": "MAC address",
|
||||
"type": "QString",
|
||||
"inputType": "MacAddress",
|
||||
"readOnly": true,
|
||||
"defaultValue": ""
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "a95292ad-08c0-405c-85f4-b47978584604",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "senecStorage",
|
||||
"displayName": "SENEC.Home storage",
|
||||
@ -59,7 +103,7 @@
|
||||
},
|
||||
{
|
||||
"id": "0a16f7c8-2e55-4869-aeb3-574f22b40527",
|
||||
"name":"addMeter",
|
||||
"name": "addMeter",
|
||||
"displayName": "Add meter",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
@ -68,7 +112,7 @@
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "9a0fa7b1-bc35-44d4-b987-5ecb87fd8b00",
|
||||
"name":"id",
|
||||
"name": "id",
|
||||
"displayName": "System ID",
|
||||
"type": "QString",
|
||||
"readOnly": true,
|
||||
|
||||
@ -5,10 +5,12 @@ QT+= network
|
||||
SOURCES += \
|
||||
integrationpluginsenec.cpp \
|
||||
senecaccount.cpp \
|
||||
seneclanstorage.cpp
|
||||
senecdiscovery.cpp \
|
||||
senecstoragelan.cpp
|
||||
|
||||
HEADERS += \
|
||||
integrationpluginsenec.h \
|
||||
senecaccount.h \
|
||||
seneclanstorage.h
|
||||
senecdiscovery.h \
|
||||
senecstoragelan.h
|
||||
|
||||
|
||||
@ -58,11 +58,6 @@ QUrl SenecAccount::systemsUrl()
|
||||
return url;
|
||||
}
|
||||
|
||||
bool SenecAccount::available() const
|
||||
{
|
||||
return m_available;
|
||||
}
|
||||
|
||||
QNetworkReply *SenecAccount::getSystems()
|
||||
{
|
||||
QNetworkRequest request(SenecAccount::systemsUrl());
|
||||
|
||||
@ -47,16 +47,11 @@ public:
|
||||
static QUrl loginUrl();
|
||||
static QUrl systemsUrl();
|
||||
|
||||
bool available() const;
|
||||
|
||||
QNetworkReply *getSystems();
|
||||
QNetworkReply *getDashboard(const QString &id);
|
||||
QNetworkReply *getAbilities(const QString &id);
|
||||
QNetworkReply *getTechnicalData(const QString &id);
|
||||
|
||||
signals:
|
||||
bool availableChanged(bool available);
|
||||
|
||||
private:
|
||||
NetworkAccessManager *m_networkManager = nullptr;
|
||||
|
||||
@ -64,7 +59,6 @@ private:
|
||||
QString m_token;
|
||||
QString m_refreshToken;
|
||||
|
||||
bool m_available = false;
|
||||
};
|
||||
|
||||
#endif // SENECACCOUNT_H
|
||||
|
||||
103
senec/senecdiscovery.cpp
Normal file
103
senec/senecdiscovery.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* 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 "senecdiscovery.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
SenecDiscovery::SenecDiscovery(NetworkAccessManager *networkManager, NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent)
|
||||
: QObject{parent},
|
||||
m_networkManager{networkManager},
|
||||
m_networkDeviceDiscovery{networkDeviceDiscovery}
|
||||
{
|
||||
m_gracePeriodTimer.setSingleShot(true);
|
||||
m_gracePeriodTimer.setInterval(3000);
|
||||
connect(&m_gracePeriodTimer, &QTimer::timeout, this, [this](){
|
||||
finishDiscovery();
|
||||
});
|
||||
}
|
||||
|
||||
void SenecDiscovery::startDiscovery()
|
||||
{
|
||||
qCDebug(dcSenec()) << "Discovery: Searching SENEC energy storages in the network...";
|
||||
m_startDateTime = QDateTime::currentDateTime();
|
||||
|
||||
NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover();
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::hostAddressDiscovered, this, &SenecDiscovery::checkNetworkDevice);
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){
|
||||
qCDebug(dcSenec()) << "Discovery: Network discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "network devices";
|
||||
m_networkDeviceInfos = discoveryReply->networkDeviceInfos();
|
||||
m_gracePeriodTimer.start();
|
||||
discoveryReply->deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
QList<SenecDiscovery::Result> SenecDiscovery::results() const
|
||||
{
|
||||
return m_results;
|
||||
}
|
||||
|
||||
void SenecDiscovery::checkNetworkDevice(const QHostAddress &address)
|
||||
{
|
||||
qCDebug(dcSenec()) << "Discovery: Verifying" << address;
|
||||
|
||||
SenecStorageLan *storage = new SenecStorageLan(m_networkManager, address, this);
|
||||
m_storages.append(storage);
|
||||
|
||||
connect(storage, &SenecStorageLan::initializeFinished, this, [this, storage, address](bool success){
|
||||
if (!success) {
|
||||
cleanupStorage(storage);
|
||||
return;
|
||||
}
|
||||
|
||||
// Successfully initialized
|
||||
Result result;
|
||||
result.deviceId = storage->deviceId();
|
||||
result.address = address;
|
||||
m_results.append(result);
|
||||
qCInfo(dcSenec()) << "Found SENEC storage on" << address.toString() << storage->deviceId();
|
||||
cleanupStorage(storage);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void SenecDiscovery::finishDiscovery()
|
||||
{
|
||||
qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch();
|
||||
|
||||
// Fill in all network device infos we have
|
||||
for (int i = 0; i < m_results.count(); i++)
|
||||
m_results[i].networkDeviceInfo = m_networkDeviceInfos.get(m_results.at(i).address);
|
||||
|
||||
qCDebug(dcSenec()) << "Discovery: Finished the discovery process. Found" << m_results.count()
|
||||
<< "SENEC devices in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz");
|
||||
m_gracePeriodTimer.stop();
|
||||
|
||||
emit discoveryFinished();
|
||||
}
|
||||
79
senec/senecdiscovery.h
Normal file
79
senec/senecdiscovery.h
Normal file
@ -0,0 +1,79 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* 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 SENECDISCOVERY_H
|
||||
#define SENECDISCOVERY_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
#include <network/networkaccessmanager.h>
|
||||
#include <network/networkdevicediscovery.h>
|
||||
|
||||
#include "senecstoragelan.h"
|
||||
|
||||
class SenecDiscovery : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SenecDiscovery(NetworkAccessManager *networkManager, NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent = nullptr);
|
||||
|
||||
typedef struct Result {
|
||||
QString deviceId;
|
||||
QHostAddress address;
|
||||
NetworkDeviceInfo networkDeviceInfo;
|
||||
} Result;
|
||||
|
||||
void startDiscovery();
|
||||
|
||||
QList<SenecDiscovery::Result> results() const;
|
||||
|
||||
signals:
|
||||
void discoveryFinished();
|
||||
|
||||
private:
|
||||
NetworkAccessManager *m_networkManager = nullptr;
|
||||
NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr;
|
||||
|
||||
QTimer m_gracePeriodTimer;
|
||||
QDateTime m_startDateTime;
|
||||
|
||||
NetworkDeviceInfos m_networkDeviceInfos;
|
||||
QList<SenecStorageLan *> m_storages;
|
||||
QList<SenecDiscovery::Result> m_results;
|
||||
|
||||
void checkNetworkDevice(const QHostAddress &address);
|
||||
void cleanupStorage(SenecStorageLan *storage);
|
||||
|
||||
void finishDiscovery();
|
||||
|
||||
};
|
||||
|
||||
#endif // SENECDISCOVERY_H
|
||||
@ -1,5 +0,0 @@
|
||||
#include "seneclanstorage.h"
|
||||
|
||||
SenecLanStorage::SenecLanStorage(QObject *parent)
|
||||
: QObject{parent}
|
||||
{}
|
||||
@ -1,15 +0,0 @@
|
||||
#ifndef SENECLANSTORAGE_H
|
||||
#define SENECLANSTORAGE_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class SenecLanStorage : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SenecLanStorage(QObject *parent = nullptr);
|
||||
|
||||
signals:
|
||||
};
|
||||
|
||||
#endif // SENECLANSTORAGE_H
|
||||
242
senec/senecstoragelan.cpp
Normal file
242
senec/senecstoragelan.cpp
Normal file
@ -0,0 +1,242 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* 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 "senecstoragelan.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonParseError>
|
||||
|
||||
SenecStorageLan::SenecStorageLan(NetworkAccessManager *networkManager, QObject *parent)
|
||||
: QObject{parent},
|
||||
m_networkManager{networkManager}
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
SenecStorageLan::SenecStorageLan(NetworkAccessManager *networkManager, const QHostAddress &address, QObject *parent)
|
||||
: QObject{parent},
|
||||
m_networkManager{networkManager},
|
||||
m_address{address}
|
||||
{
|
||||
updateUrl();
|
||||
}
|
||||
|
||||
QHostAddress SenecStorageLan::address() const
|
||||
{
|
||||
return m_address;
|
||||
}
|
||||
|
||||
void SenecStorageLan::setAddress(const QHostAddress &address)
|
||||
{
|
||||
m_address = address;
|
||||
updateUrl();
|
||||
}
|
||||
|
||||
QUrl SenecStorageLan::url() const
|
||||
{
|
||||
return m_url;
|
||||
}
|
||||
|
||||
QString SenecStorageLan::deviceId() const
|
||||
{
|
||||
return m_deviceId;
|
||||
}
|
||||
|
||||
float SenecStorageLan::capacity() const
|
||||
{
|
||||
return m_capacity;
|
||||
}
|
||||
|
||||
float SenecStorageLan::maxChargePower() const
|
||||
{
|
||||
return m_maxChargePower;
|
||||
}
|
||||
|
||||
float SenecStorageLan::maxDischargePower() const
|
||||
{
|
||||
return m_maxDischargePower;
|
||||
}
|
||||
|
||||
|
||||
float SenecStorageLan::parseFloat(const QString &value)
|
||||
{
|
||||
Q_ASSERT_X(value.left(3) == "fl_", "SenecStorageLan", "The given value does not seem to be a float, it is not starting with fl_");
|
||||
QString hexString = value.right(value.length() - 3);
|
||||
|
||||
float result = 0;
|
||||
quint32 dataValue = 0;
|
||||
|
||||
QByteArray rawData = QByteArray::fromHex(hexString.toUtf8());
|
||||
Q_ASSERT_X(rawData.count() == 4, "ModbusDataUtils", "invalid raw data size for converting value to float32. The rawdate has not the expected size of 4.");
|
||||
QDataStream stream(rawData);
|
||||
stream >> dataValue;
|
||||
memcpy(&result, &dataValue, sizeof(quint32));
|
||||
return result;
|
||||
}
|
||||
|
||||
QString SenecStorageLan::parseString(const QString &value)
|
||||
{
|
||||
Q_ASSERT_X(value.left(3) == "st_", "SenecStorageLan", "The given value does not seem to be a string, it is not starting with st_");
|
||||
return value.right(value.length() - 3);
|
||||
}
|
||||
|
||||
|
||||
void SenecStorageLan::initialize()
|
||||
{
|
||||
if (m_url.isValid()) {
|
||||
qCWarning(dcSenec()) << "Cannot initialize the storage. The request URL is not valid. Maybe the IP is not known yet or invalid.";
|
||||
emit initializeFinished(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify the debug request is working
|
||||
QVariantMap request;
|
||||
request.insert("DEBUG", QVariantMap());
|
||||
|
||||
QNetworkReply *reply = m_networkManager->post(QNetworkRequest(m_url), QJsonDocument::fromVariant(request).toJson());
|
||||
connect(reply, &QNetworkReply::sslErrors, this, &SenecStorageLan::ignoreSslErrors);
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply] {
|
||||
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
// Check HTTP status code
|
||||
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
||||
qCWarning(dcSenec()) << "Debug request finished with error. Status:" << status << "Error:" << reply->errorString();
|
||||
setAvailable(false);
|
||||
emit initializeFinished(false);
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray responseData = reply->readAll();
|
||||
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData, &jsonError);
|
||||
QVariantMap responseMap = jsonDoc.toVariant().toMap();
|
||||
if (jsonError.error != QJsonParseError::NoError) {
|
||||
qCWarning(dcSenec()) << "Debug request finished successfully, but the response contains invalid JSON object:" << responseData;
|
||||
setAvailable(false);
|
||||
emit initializeFinished(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify content, if the content is valid, we assume we have found a SENEC storage
|
||||
if (!responseMap.contains("DEBUG")) {
|
||||
qCWarning(dcSenec()) << "Unexpected reponse data from debug request. Aborting...";
|
||||
setAvailable(false);
|
||||
emit initializeFinished(false);
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(dcSenec()) << "Debug request finished successfully" << qUtf8Printable(jsonDoc.toJson());
|
||||
|
||||
// Request basic information
|
||||
|
||||
QVariantMap request;
|
||||
QVariantMap factoryMap;
|
||||
factoryMap.insert("DEVICE_ID", QString());
|
||||
factoryMap.insert("DESIGN_CAPACITY", QString());
|
||||
factoryMap.insert("MAX_CHARGE_POWER_DC", QString());
|
||||
factoryMap.insert("MAX_DISCHARGE_POWER_DC", QString());
|
||||
request.insert("FACTORY", factoryMap);
|
||||
|
||||
QNetworkReply *reply = m_networkManager->post(QNetworkRequest(m_url), QJsonDocument::fromVariant(request).toJson());
|
||||
connect(reply, &QNetworkReply::sslErrors, this, &SenecStorageLan::ignoreSslErrors);
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply] {
|
||||
|
||||
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
// Check HTTP status code
|
||||
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
||||
qCWarning(dcSenec()) << "Factory request finished with error. Status:" << status << "Error:" << reply->errorString();
|
||||
setAvailable(false);
|
||||
emit initializeFinished(false);
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray responseData = reply->readAll();
|
||||
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData, &jsonError);
|
||||
QVariantMap responseMap = jsonDoc.toVariant().toMap();
|
||||
if (jsonError.error != QJsonParseError::NoError) {
|
||||
qCWarning(dcSenec()) << "Factory request finished successfully, but the response contains invalid JSON object:" << responseData;
|
||||
setAvailable(false);
|
||||
emit initializeFinished(false);
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(dcSenec()) << "Factory request finished successfully" << qUtf8Printable(jsonDoc.toJson());
|
||||
|
||||
QVariantMap factoryResponseMap = responseMap.value("FACTORY").toMap();
|
||||
m_deviceId = parseString(factoryResponseMap.value("DEVICE_ID").toString());
|
||||
m_capacity = parseFloat(factoryResponseMap.value("DESIGN_CAPACITY").toString());
|
||||
m_maxChargePower = parseFloat(factoryResponseMap.value("MAX_CHARGE_POWER_DC").toString());
|
||||
m_maxDischargePower = parseFloat(factoryResponseMap.value("MAX_DISCHARGE_POWER_DC").toString());
|
||||
|
||||
emit initializeFinished(true);
|
||||
setAvailable(true);
|
||||
|
||||
qCDebug(dcSenec()) << "Initialized successfully";
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void SenecStorageLan::updateUrl()
|
||||
{
|
||||
QUrl url;
|
||||
if (m_address.isNull()) {
|
||||
m_url = url;
|
||||
return;
|
||||
}
|
||||
|
||||
url.setScheme("https");
|
||||
url.setHost(m_address.toString());
|
||||
url.setPath("lala.cgi");
|
||||
m_url = url;
|
||||
}
|
||||
|
||||
void SenecStorageLan::setAvailable(bool available)
|
||||
{
|
||||
if (m_available == available)
|
||||
return;
|
||||
|
||||
m_available = available;
|
||||
emit availableChanged(m_available);
|
||||
}
|
||||
|
||||
void SenecStorageLan::ignoreSslErrors(const QList<QSslError> &errors)
|
||||
{
|
||||
qCDebug(dcSenec()) << "SSL errors occurred" << errors << "but we are ignoring them in the LAN environment...";
|
||||
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
|
||||
reply->ignoreSslErrors();
|
||||
}
|
||||
93
senec/senecstoragelan.h
Normal file
93
senec/senecstoragelan.h
Normal file
@ -0,0 +1,93 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* 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 SENECSTORAGELAN_H
|
||||
#define SENECSTORAGELAN_H
|
||||
|
||||
#include <QUrl>
|
||||
#include <QObject>
|
||||
#include <QHostAddress>
|
||||
|
||||
#include <network/networkaccessmanager.h>
|
||||
|
||||
class SenecStorageLan : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SenecStorageLan(NetworkAccessManager *networkManager, QObject *parent = nullptr);
|
||||
explicit SenecStorageLan(NetworkAccessManager *networkManager, const QHostAddress &address, QObject *parent = nullptr);
|
||||
|
||||
QHostAddress address() const;
|
||||
void setAddress(const QHostAddress &address);
|
||||
|
||||
QUrl url() const;
|
||||
|
||||
bool available() const;
|
||||
|
||||
QString deviceId() const;
|
||||
float capacity() const;
|
||||
float maxChargePower() const;
|
||||
float maxDischargePower() const;
|
||||
|
||||
|
||||
static float parseFloat(const QString &value);
|
||||
static QString parseString(const QString &value);
|
||||
|
||||
|
||||
public slots:
|
||||
void initialize();
|
||||
|
||||
signals:
|
||||
void initializeFinished(bool success);
|
||||
void availableChanged(bool available);
|
||||
|
||||
private:
|
||||
NetworkAccessManager *m_networkManager = nullptr;
|
||||
|
||||
QUrl m_url;
|
||||
QHostAddress m_address;
|
||||
|
||||
bool m_available = false;
|
||||
|
||||
QString m_deviceId;
|
||||
float m_capacity = 0;
|
||||
float m_maxChargePower = 0;
|
||||
float m_maxDischargePower = 0;
|
||||
|
||||
void updateUrl();
|
||||
void setAvailable(bool available);
|
||||
|
||||
|
||||
private slots:
|
||||
void ignoreSslErrors(const QList<QSslError> &errors);
|
||||
|
||||
};
|
||||
|
||||
#endif // SENECSTORAGELAN_H
|
||||
Loading…
x
Reference in New Issue
Block a user