mirror of
https://github.com/nymea/nymea-plugins.git
synced 2026-10-27 14:22:44 +01:00
Merge PR #771: New plugin: Senec: Add support for SENEC.home energy storages
This commit is contained in:
commit
6ca74d0bb6
@ -714,6 +714,7 @@ Description: nymea integration plugin for TCP commander
|
||||
This package contains the nymea integration plugin for sending arbitrary
|
||||
TCP packets from and to nymea.
|
||||
|
||||
|
||||
Package: nymea-plugin-httpcommander
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends},
|
||||
@ -724,6 +725,16 @@ Description: nymea integration plugin for HTTP commander
|
||||
HTTP requests from and to nymea.
|
||||
|
||||
|
||||
Package: nymea-plugin-senec
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends},
|
||||
${misc:Depends},
|
||||
Conflicts: nymea-plugins-translations (<< 1.0.1)
|
||||
Description: nymea integration plugin for SENEC.home
|
||||
This package contains the nymea integration plugin for the SENEC.home
|
||||
energy storages.
|
||||
|
||||
|
||||
Package: nymea-plugin-senic
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends},
|
||||
|
||||
2
debian-qt5/nymea-plugin-senec.install.in
Normal file
2
debian-qt5/nymea-plugin-senec.install.in
Normal file
@ -0,0 +1,2 @@
|
||||
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginsenec.so
|
||||
senec/translations/*qm usr/share/nymea/translations/
|
||||
@ -678,6 +678,16 @@ Description: nymea integration plugin for HTTP commander
|
||||
HTTP requests from and to nymea.
|
||||
|
||||
|
||||
Package: nymea-plugin-senec
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends},
|
||||
${misc:Depends},
|
||||
Conflicts: nymea-plugins-translations (<< 1.0.1)
|
||||
Description: nymea integration plugin for SENEC.home
|
||||
This package contains the nymea integration plugin for the SENEC.home
|
||||
energy storages.
|
||||
|
||||
|
||||
Package: nymea-plugin-senic
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends},
|
||||
|
||||
2
debian-qt6/nymea-plugin-senec.install.in
Normal file
2
debian-qt6/nymea-plugin-senec.install.in
Normal file
@ -0,0 +1,2 @@
|
||||
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginsenec.so
|
||||
senec/translations/*qm usr/share/nymea/translations/
|
||||
@ -56,6 +56,7 @@ PLUGIN_DIRS = \
|
||||
pushbullet \
|
||||
pushnotifications \
|
||||
reversessh \
|
||||
senec \
|
||||
senic \
|
||||
serialportcommander \
|
||||
sgready \
|
||||
|
||||
21
senec/README.md
Normal file
21
senec/README.md
Normal file
@ -0,0 +1,21 @@
|
||||
# SENEC.Home
|
||||
|
||||
Integrates SENEC.Home energy storages into the system.
|
||||
|
||||
## Local network (SENEC.Home V2 / V3)
|
||||
|
||||
The storage is discovered in the local network and polled directly. Optionally the grid meter of the storage can be added using the "Add meter" setting.
|
||||
|
||||
## SENEC.Connect (SENEC.Home 4, P4, E4)
|
||||
|
||||
V4 systems do not provide a local API and are integrated using the official read-only SENEC.Connect cloud API.
|
||||
|
||||
1. Sign in on https://developer.senec.com with your SENEC account.
|
||||
2. Subscribe to the SENEC.Connect product (SENEC.Data 45000).
|
||||
3. Copy the subscription key from your profile and enter it when adding the SENEC.Connect thing.
|
||||
|
||||
All systems registered to the account will appear automatically, each with a grid meter and a PV inverter. The data is updated once per minute due to the request limit of the API. The energy totals of the meter and the inverter are calculated from the power values.
|
||||
|
||||
## More
|
||||
|
||||
https://senec.com
|
||||
523
senec/integrationpluginsenec.cpp
Normal file
523
senec/integrationpluginsenec.cpp
Normal file
@ -0,0 +1,523 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* 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 "integrationpluginsenec.h"
|
||||
#include "senecdiscovery.h"
|
||||
#include "plugininfo.h"
|
||||
|
||||
#include <network/networkdevicediscovery.h>
|
||||
|
||||
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"; // foobar
|
||||
// QString value = SenecStorageLan::parseString(rawValue);
|
||||
// qCWarning(dcSenec()) << rawValue << value;
|
||||
|
||||
// QString rawValue = "u8_64"; // 100
|
||||
// quint8 value = SenecStorageLan::parseUInt8(rawValue);
|
||||
// qCWarning(dcSenec()) << rawValue << value;
|
||||
}
|
||||
|
||||
IntegrationPluginSenec::~IntegrationPluginSenec()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void IntegrationPluginSenec::discoverThings(ThingDiscoveryInfo *info)
|
||||
{
|
||||
if (info->thingClassId() == senecStorageLanThingClassId) {
|
||||
|
||||
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(dcSenec()) << "Starting network discovery...";
|
||||
SenecDiscovery *discovery = new SenecDiscovery(hardwareManager()->networkManager(), hardwareManager()->networkDeviceDiscovery(), info);
|
||||
connect(discovery, &SenecDiscovery::discoveryFinished, info, [=](){
|
||||
ThingDescriptors descriptors;
|
||||
qCInfo(dcSenec()) << "Discovery finished. Found" << discovery->results().count() << "devices";
|
||||
foreach (const SenecDiscovery::Result &result, discovery->results()) {
|
||||
qCInfo(dcSenec()) << "Discovered device on" << result.networkDeviceInfo;
|
||||
|
||||
QString title = "SENEC connection (" + result.deviceId + ")";
|
||||
QString description = result.networkDeviceInfo.address().toString();
|
||||
|
||||
ThingDescriptor descriptor(senecStorageLanThingClassId, title, description);
|
||||
|
||||
ParamList params;
|
||||
params << Param(senecStorageLanThingMacAddressParamTypeId, result.networkDeviceInfo.thingParamValueMacAddress());
|
||||
params << Param(senecStorageLanThingHostNameParamTypeId, result.networkDeviceInfo.thingParamValueHostName());
|
||||
params << Param(senecStorageLanThingAddressParamTypeId, result.networkDeviceInfo.thingParamValueAddress());
|
||||
descriptor.setParams(params);
|
||||
|
||||
// Check if we already have set up this device
|
||||
Thing *existingThing = myThings().findByParams(params);
|
||||
if (existingThing) {
|
||||
qCDebug(dcSenec()) << "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 sign in on developer.senec.com with your SENEC account, subscribe to the SENEC.Connect product and enter the subscription key from your profile."));
|
||||
}
|
||||
|
||||
void IntegrationPluginSenec::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret)
|
||||
{
|
||||
Q_UNUSED(username)
|
||||
|
||||
QString subscriptionKey = secret.trimmed();
|
||||
if (subscriptionKey.isEmpty()) {
|
||||
info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Please enter a subscription key."));
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(dcSenec()) << "Verifying SENEC.Connect subscription key" << subscriptionKey.left(4) + QString(subscriptionKey.length() - 4, '*');
|
||||
|
||||
SenecConnect *senecConnect = new SenecConnect(hardwareManager()->networkManager(), subscriptionKey, info);
|
||||
QNetworkReply *reply = senecConnect->getDeviceData();
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, info, [this, reply, info, subscriptionKey] {
|
||||
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (status == 401 || status == 403) {
|
||||
qCWarning(dcSenec()) << "SENEC.Connect rejected the subscription key. Status:" << status;
|
||||
info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("The subscription key is not valid."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
||||
qCWarning(dcSenec()) << "SENEC.Connect request finished with error. Status:" << status << "Error:" << reply->errorString();
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Unable to connect to SENEC.Connect. Please try again later."));
|
||||
return;
|
||||
}
|
||||
|
||||
pluginStorage()->beginGroup(info->thingId().toString());
|
||||
pluginStorage()->setValue("subscriptionKey", subscriptionKey);
|
||||
pluginStorage()->endGroup();
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
}
|
||||
|
||||
void IntegrationPluginSenec::setupThing(ThingSetupInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
qCDebug(dcSenec()) << "Setting up" << thing->name() << thing->params();
|
||||
|
||||
if (thing->thingClassId() == senecStorageLanThingClassId) {
|
||||
|
||||
// Handle reconfigure
|
||||
if (m_monitors.contains(thing)) {
|
||||
qCDebug(dcSenec()) << "Unregister existing monitor and recreate a new one...";
|
||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
||||
}
|
||||
|
||||
if (m_storages.contains(thing))
|
||||
m_storages.take(thing)->deleteLater();
|
||||
|
||||
NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(thing);
|
||||
m_monitors.insert(thing, monitor);
|
||||
|
||||
SenecStorageLan *storage = new SenecStorageLan(hardwareManager()->networkManager(), this);
|
||||
m_storages.insert(thing, storage);
|
||||
|
||||
storage->setAddress(monitor->networkDeviceInfo().address());
|
||||
connect(monitor, &NetworkDeviceMonitor::reachableChanged, storage, [monitor, storage](bool reachable){
|
||||
if (reachable) {
|
||||
storage->setAddress(monitor->networkDeviceInfo().address());
|
||||
}
|
||||
});
|
||||
|
||||
connect(storage, &SenecStorageLan::availableChanged, thing, [this, thing](bool available){
|
||||
setConnected(thing, available);
|
||||
});
|
||||
|
||||
connect(storage, &SenecStorageLan::initializeFinished, thing, [storage, thing](bool success){
|
||||
if (success) {
|
||||
thing->setStateValue(senecStorageLanCapacityStateTypeId, storage->capacity());
|
||||
}
|
||||
});
|
||||
|
||||
connect(storage, &SenecStorageLan::updatedFinished, thing, [storage, thing, this](bool success){
|
||||
setConnected(thing, storage->available());
|
||||
|
||||
if (!success)
|
||||
return;
|
||||
|
||||
int batteryLevel = qRound(storage->batteryLevel());
|
||||
thing->setStateValue(senecStorageLanBatteryLevelStateTypeId, batteryLevel);
|
||||
thing->setStateValue(senecStorageLanBatteryCriticalStateTypeId, batteryLevel < 10);
|
||||
thing->setStateValue(senecStorageLanCurrentPowerStateTypeId, storage->batteryPower());
|
||||
if (storage->batteryPower() > 0) {
|
||||
thing->setStateValue(senecStorageLanChargingStateStateTypeId, "charging");
|
||||
} else if (storage->batteryPower() < 0) {
|
||||
thing->setStateValue(senecStorageLanChargingStateStateTypeId, "discharging");
|
||||
} else {
|
||||
thing->setStateValue(senecStorageLanChargingStateStateTypeId, "idle");
|
||||
}
|
||||
|
||||
Thing *meter = childThing(thing, senecMeterThingClassId);
|
||||
if (meter) {
|
||||
meter->setStateValue(senecMeterCurrentPowerStateTypeId, storage->gridPower());
|
||||
}
|
||||
});
|
||||
|
||||
connect(thing, &Thing::settingChanged, this, [this, thing](const ParamTypeId ¶mTypeId, const QVariant &value){
|
||||
if (paramTypeId == senecStorageLanSettingsAddMeterParamTypeId) {
|
||||
|
||||
if (value.toBool()) {
|
||||
// Check if we have to add the meter
|
||||
if (!childThing(thing, senecMeterThingClassId)) {
|
||||
qCDebug(dcSenec()) << "Add meter for" << thing->name();
|
||||
emit autoThingsAppeared(ThingDescriptors() << ThingDescriptor(senecMeterThingClassId, "SENEC Meter", QString(), thing->id()));
|
||||
}
|
||||
} else {
|
||||
// Check if we have to remove the meter
|
||||
Thing *meter = childThing(thing, senecMeterThingClassId);
|
||||
if (meter) {
|
||||
qCDebug(dcSenec()) << "Remove meter thing for" << thing->name();
|
||||
emit autoThingDisappeared(meter->id());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
|
||||
} else if (thing->thingClassId() == senecConnectThingClassId) {
|
||||
|
||||
pluginStorage()->beginGroup(thing->id().toString());
|
||||
QString subscriptionKey = pluginStorage()->value("subscriptionKey").toString();
|
||||
pluginStorage()->endGroup();
|
||||
|
||||
if (subscriptionKey.isEmpty()) {
|
||||
info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("The SENEC.Connect subscription key is missing. Please reconfigure the connection."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle reconfigure
|
||||
if (m_connects.contains(thing))
|
||||
m_connects.take(thing)->deleteLater();
|
||||
|
||||
m_connects.insert(thing, new SenecConnect(hardwareManager()->networkManager(), subscriptionKey, this));
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
|
||||
} else if (thing->thingClassId() == senecStorageCloudThingClassId) {
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
|
||||
} else if (thing->thingClassId() == senecMeterThingClassId) {
|
||||
Thing *parentThing = myThings().findById(thing->parentId());
|
||||
if (parentThing && parentThing->thingClassId() == senecStorageLanThingClassId) {
|
||||
thing->setStateValue(senecMeterConnectedStateTypeId, parentThing->stateValue(senecStorageLanConnectedStateTypeId).toBool());
|
||||
} else if (parentThing && parentThing->thingClassId() == senecStorageCloudThingClassId) {
|
||||
thing->setStateValue(senecMeterConnectedStateTypeId, parentThing->stateValue(senecStorageCloudConnectedStateTypeId).toBool());
|
||||
}
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
|
||||
} else if (thing->thingClassId() == senecInverterThingClassId) {
|
||||
Thing *parentThing = myThings().findById(thing->parentId());
|
||||
if (parentThing) {
|
||||
thing->setStateValue(senecInverterConnectedStateTypeId, parentThing->stateValue(senecStorageCloudConnectedStateTypeId).toBool());
|
||||
}
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginSenec::postSetupThing(Thing *thing)
|
||||
{
|
||||
if (thing->thingClassId() == senecStorageLanThingClassId) {
|
||||
m_storages.value(thing)->initialize();
|
||||
|
||||
if (!m_refreshTimer) {
|
||||
qCDebug(dcSenec()) << "Starting LAN refresh timer ...";
|
||||
m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(5);
|
||||
connect(m_refreshTimer, &PluginTimer::timeout, this, &IntegrationPluginSenec::refresh);
|
||||
m_refreshTimer->start();
|
||||
}
|
||||
|
||||
} else if (thing->thingClassId() == senecConnectThingClassId) {
|
||||
refreshCloud(thing);
|
||||
|
||||
// SENEC.Connect allows 45000 requests per month, which is roughly one request per minute
|
||||
if (!m_cloudRefreshTimer) {
|
||||
qCDebug(dcSenec()) << "Starting SENEC.Connect refresh timer ...";
|
||||
m_cloudRefreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(60);
|
||||
connect(m_cloudRefreshTimer, &PluginTimer::timeout, this, &IntegrationPluginSenec::refreshClouds);
|
||||
m_cloudRefreshTimer->start();
|
||||
}
|
||||
|
||||
} else if (thing->thingClassId() == senecStorageCloudThingClassId) {
|
||||
ThingDescriptors descriptors;
|
||||
if (!childThing(thing, senecMeterThingClassId))
|
||||
descriptors.append(ThingDescriptor(senecMeterThingClassId, "SENEC Meter", thing->name(), thing->id()));
|
||||
|
||||
if (!childThing(thing, senecInverterThingClassId))
|
||||
descriptors.append(ThingDescriptor(senecInverterThingClassId, "SENEC PV inverter", thing->name(), thing->id()));
|
||||
|
||||
if (!descriptors.isEmpty()) {
|
||||
qCDebug(dcSenec()) << "Adding" << descriptors.count() << "child things for" << thing->name();
|
||||
emit autoThingsAppeared(descriptors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginSenec::thingRemoved(Thing *thing)
|
||||
{
|
||||
if (thing->thingClassId() == senecStorageLanThingClassId) {
|
||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
||||
m_storages.take(thing)->deleteLater();
|
||||
}
|
||||
|
||||
if (thing->thingClassId() == senecConnectThingClassId) {
|
||||
if (m_connects.contains(thing))
|
||||
m_connects.take(thing)->deleteLater();
|
||||
|
||||
pluginStorage()->beginGroup(thing->id().toString());
|
||||
pluginStorage()->remove("");
|
||||
pluginStorage()->endGroup();
|
||||
}
|
||||
|
||||
m_lastEnergyUpdates.remove(thing);
|
||||
|
||||
if (m_refreshTimer && m_storages.isEmpty()) {
|
||||
qCDebug(dcSenec()) << "Stopping LAN refresh timer";
|
||||
hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer);
|
||||
m_refreshTimer = nullptr;
|
||||
}
|
||||
|
||||
if (m_cloudRefreshTimer && m_connects.isEmpty()) {
|
||||
qCDebug(dcSenec()) << "Stopping SENEC.Connect refresh timer";
|
||||
hardwareManager()->pluginTimerManager()->unregisterTimer(m_cloudRefreshTimer);
|
||||
m_cloudRefreshTimer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Thing *IntegrationPluginSenec::childThing(Thing *parentThing, const ThingClassId &thingClassId) const
|
||||
{
|
||||
Things childThings = myThings().filterByThingClassId(thingClassId).filterByParentId(parentThing->id());
|
||||
if (childThings.isEmpty())
|
||||
return nullptr;
|
||||
|
||||
return childThings.first();
|
||||
}
|
||||
|
||||
void IntegrationPluginSenec::setConnected(Thing *storageThing, bool connected)
|
||||
{
|
||||
storageThing->setStateValue(senecStorageLanConnectedStateTypeId, connected);
|
||||
|
||||
Thing *meter = childThing(storageThing, senecMeterThingClassId);
|
||||
if (meter) {
|
||||
meter->setStateValue(senecMeterConnectedStateTypeId, connected);
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginSenec::refresh()
|
||||
{
|
||||
foreach (Thing *storageThing, myThings().filterByThingClassId(senecStorageLanThingClassId)) {
|
||||
SenecStorageLan *storage = m_storages.value(storageThing);
|
||||
// Retry fetching the static information until the storage could be initialized once
|
||||
if (storage->initialized()) {
|
||||
storage->update();
|
||||
} else {
|
||||
storage->initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginSenec::refreshClouds()
|
||||
{
|
||||
foreach (Thing *connectThing, myThings().filterByThingClassId(senecConnectThingClassId)) {
|
||||
refreshCloud(connectThing);
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginSenec::refreshCloud(Thing *connectThing)
|
||||
{
|
||||
SenecConnect *senecConnect = m_connects.value(connectThing);
|
||||
if (!senecConnect)
|
||||
return;
|
||||
|
||||
QNetworkReply *reply = senecConnect->getDeviceData();
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, connectThing, [this, reply, connectThing] {
|
||||
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (status == 401 || status == 403) {
|
||||
qCWarning(dcSenec()) << "SENEC.Connect request has been rejected. The subscription key seems to be invalid. Status:" << status;
|
||||
connectThing->setStateValue(senecConnectLoggedInStateTypeId, false);
|
||||
setCloudConnected(connectThing, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (status != 200 || reply->error() != QNetworkReply::NoError) {
|
||||
qCWarning(dcSenec()) << "SENEC.Connect request finished with error. Status:" << status << "Error:" << reply->errorString();
|
||||
setCloudConnected(connectThing, false);
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray responseData = reply->readAll();
|
||||
qCDebug(dcSenec()) << "SENEC.Connect request finished successfully" << qUtf8Printable(responseData);
|
||||
|
||||
bool ok = false;
|
||||
QList<SenecConnect::SystemData> systems = SenecConnect::parseDeviceData(responseData, &ok);
|
||||
if (!ok) {
|
||||
qCWarning(dcSenec()) << "SENEC.Connect request finished successfully, but the response contains invalid data:" << responseData;
|
||||
setCloudConnected(connectThing, false);
|
||||
return;
|
||||
}
|
||||
|
||||
connectThing->setStateValue(senecConnectLoggedInStateTypeId, true);
|
||||
connectThing->setStateValue(senecConnectConnectedStateTypeId, true);
|
||||
processCloudData(connectThing, systems);
|
||||
});
|
||||
}
|
||||
|
||||
void IntegrationPluginSenec::processCloudData(Thing *connectThing, const QList<SenecConnect::SystemData> &systems)
|
||||
{
|
||||
ThingDescriptors descriptors;
|
||||
Things storageThings = myThings().filterByThingClassId(senecStorageCloudThingClassId).filterByParentId(connectThing->id());
|
||||
|
||||
foreach (const SenecConnect::SystemData &system, systems) {
|
||||
Things existingThings = storageThings.filterByParam(senecStorageCloudThingSystemIdParamTypeId, system.systemId);
|
||||
if (existingThings.isEmpty()) {
|
||||
qCDebug(dcSenec()) << "Creating new storage for" << system.systemId << system.model;
|
||||
ThingDescriptor descriptor(senecStorageCloudThingClassId, system.model.isEmpty() ? "SENEC.Home storage" : system.model, system.systemId, connectThing->id());
|
||||
descriptor.setParams(ParamList() << Param(senecStorageCloudThingSystemIdParamTypeId, system.systemId)
|
||||
<< Param(senecStorageCloudThingSerialNumberParamTypeId, system.serialNumber));
|
||||
descriptors.append(descriptor);
|
||||
continue;
|
||||
}
|
||||
|
||||
Thing *storageThing = existingThings.first();
|
||||
Thing *meter = childThing(storageThing, senecMeterThingClassId);
|
||||
Thing *inverter = childThing(storageThing, senecInverterThingClassId);
|
||||
|
||||
storageThing->setStateValue(senecStorageCloudConnectedStateTypeId, system.active);
|
||||
if (meter)
|
||||
meter->setStateValue(senecMeterConnectedStateTypeId, system.active);
|
||||
|
||||
if (inverter)
|
||||
inverter->setStateValue(senecInverterConnectedStateTypeId, system.active);
|
||||
|
||||
if (!system.active) {
|
||||
qCDebug(dcSenec()) << "System" << system.systemId << "does not provide live data. Marking as disconnected.";
|
||||
continue;
|
||||
}
|
||||
|
||||
int batteryLevel = qRound(system.batteryLevel);
|
||||
storageThing->setStateValue(senecStorageCloudCapacityStateTypeId, system.capacity);
|
||||
storageThing->setStateValue(senecStorageCloudBatteryLevelStateTypeId, batteryLevel);
|
||||
storageThing->setStateValue(senecStorageCloudBatteryCriticalStateTypeId, batteryLevel < 10);
|
||||
storageThing->setStateValue(senecStorageCloudCurrentPowerStateTypeId, system.batteryPower);
|
||||
if (system.batteryPower > 0) {
|
||||
storageThing->setStateValue(senecStorageCloudChargingStateStateTypeId, "charging");
|
||||
} else if (system.batteryPower < 0) {
|
||||
storageThing->setStateValue(senecStorageCloudChargingStateStateTypeId, "discharging");
|
||||
} else {
|
||||
storageThing->setStateValue(senecStorageCloudChargingStateStateTypeId, "idle");
|
||||
}
|
||||
|
||||
if (meter) {
|
||||
updateEnergy(meter, system.gridPower, senecMeterTotalEnergyConsumedStateTypeId, senecMeterTotalEnergyProducedStateTypeId);
|
||||
meter->setStateValue(senecMeterCurrentPowerStateTypeId, system.gridPower);
|
||||
}
|
||||
|
||||
if (inverter) {
|
||||
updateEnergy(inverter, -system.production, StateTypeId(), senecInverterTotalEnergyProducedStateTypeId);
|
||||
inverter->setStateValue(senecInverterCurrentPowerStateTypeId, -system.production);
|
||||
}
|
||||
}
|
||||
|
||||
if (!descriptors.isEmpty()) {
|
||||
qCDebug(dcSenec()) << "Adding" << descriptors.count() << "new SENEC.Home" << (descriptors.count() > 1 ? "storages" : "storage");
|
||||
emit autoThingsAppeared(descriptors);
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginSenec::setCloudConnected(Thing *connectThing, bool connected)
|
||||
{
|
||||
connectThing->setStateValue(senecConnectConnectedStateTypeId, connected);
|
||||
|
||||
foreach (Thing *storageThing, myThings().filterByThingClassId(senecStorageCloudThingClassId).filterByParentId(connectThing->id())) {
|
||||
storageThing->setStateValue(senecStorageCloudConnectedStateTypeId, connected);
|
||||
|
||||
Thing *meter = childThing(storageThing, senecMeterThingClassId);
|
||||
if (meter)
|
||||
meter->setStateValue(senecMeterConnectedStateTypeId, connected);
|
||||
|
||||
Thing *inverter = childThing(storageThing, senecInverterThingClassId);
|
||||
if (inverter)
|
||||
inverter->setStateValue(senecInverterConnectedStateTypeId, connected);
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginSenec::updateEnergy(Thing *thing, double currentPower, const StateTypeId &consumedStateTypeId, const StateTypeId &producedStateTypeId)
|
||||
{
|
||||
// The cloud API only provides power values, so the energy totals are integrated between two samples
|
||||
QDateTime now = QDateTime::currentDateTimeUtc();
|
||||
QDateTime lastUpdate = m_lastEnergyUpdates.value(thing);
|
||||
m_lastEnergyUpdates.insert(thing, now);
|
||||
|
||||
// Skip the first sample and gaps (restart, connection loss) instead of extrapolating
|
||||
if (!lastUpdate.isValid() || lastUpdate.secsTo(now) > 300)
|
||||
return;
|
||||
|
||||
// Trapezoidal integration using the previous power value still stored in the state
|
||||
double hoursPassed = lastUpdate.msecsTo(now) / 1000.0 / 3600.0;
|
||||
double averagePower = (thing->stateValue("currentPower").toDouble() + currentPower) / 2.0;
|
||||
double energy = qAbs(averagePower) / 1000.0 * hoursPassed;
|
||||
|
||||
if (averagePower > 0 && !consumedStateTypeId.isNull()) {
|
||||
thing->setStateValue(consumedStateTypeId, thing->stateValue(consumedStateTypeId).toDouble() + energy);
|
||||
} else if (averagePower < 0 && !producedStateTypeId.isNull()) {
|
||||
thing->setStateValue(producedStateTypeId, thing->stateValue(producedStateTypeId).toDouble() + energy);
|
||||
}
|
||||
}
|
||||
90
senec/integrationpluginsenec.h
Normal file
90
senec/integrationpluginsenec.h
Normal file
@ -0,0 +1,90 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* 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 INTEGRATIONPLUGINSENEC_H
|
||||
#define INTEGRATIONPLUGINSENEC_H
|
||||
|
||||
#include <plugintimer.h>
|
||||
#include <integrations/integrationplugin.h>
|
||||
|
||||
#include <network/networkaccessmanager.h>
|
||||
#include <network/networkdevicemonitor.h>
|
||||
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
#include "senecconnect.h"
|
||||
#include "senecstoragelan.h"
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
class IntegrationPluginSenec : public IntegrationPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginsenec.json")
|
||||
Q_INTERFACES(IntegrationPlugin)
|
||||
|
||||
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;
|
||||
|
||||
void setupThing(ThingSetupInfo *info) override;
|
||||
void postSetupThing(Thing *thing) override;
|
||||
void thingRemoved(Thing *thing) override;
|
||||
|
||||
private:
|
||||
PluginTimer *m_refreshTimer = nullptr;
|
||||
PluginTimer *m_cloudRefreshTimer = nullptr;
|
||||
|
||||
QHash<Thing *, NetworkDeviceMonitor *> m_monitors;
|
||||
QHash<Thing *, SenecStorageLan *> m_storages;
|
||||
QHash<Thing *, SenecConnect *> m_connects;
|
||||
QHash<Thing *, QDateTime> m_lastEnergyUpdates;
|
||||
|
||||
Thing *childThing(Thing *parentThing, const ThingClassId &thingClassId) const;
|
||||
void setConnected(Thing *storageThing, bool connected);
|
||||
|
||||
void refreshCloud(Thing *connectThing);
|
||||
void processCloudData(Thing *connectThing, const QList<SenecConnect::SystemData> &systems);
|
||||
void setCloudConnected(Thing *connectThing, bool connected);
|
||||
void updateEnergy(Thing *thing, double currentPower, const StateTypeId &consumedStateTypeId, const StateTypeId &producedStateTypeId);
|
||||
|
||||
private slots:
|
||||
void refresh();
|
||||
void refreshClouds();
|
||||
|
||||
};
|
||||
|
||||
#endif // INTEGRATIONPLUGINSENEC_H
|
||||
369
senec/integrationpluginsenec.json
Normal file
369
senec/integrationpluginsenec.json
Normal file
@ -0,0 +1,369 @@
|
||||
{
|
||||
"displayName": "SENEC",
|
||||
"name": "Senec",
|
||||
"id": "3f055ea5-a883-445d-9556-675f5eda6c9a",
|
||||
"vendors": [
|
||||
{
|
||||
"displayName": "SENEC",
|
||||
"name": "senec",
|
||||
"id": "1be922b9-4439-42cf-9caa-67f2ac7cc425",
|
||||
"thingClasses": [
|
||||
{
|
||||
"id": "d064f111-1332-4096-b5de-529d44f72963",
|
||||
"name": "senecConnect",
|
||||
"displayName": "SENEC.Connect",
|
||||
"interfaces": ["account"],
|
||||
"createMethods": ["User"],
|
||||
"setupMethod": "EnterPin",
|
||||
"providedInterfaces": ["energystorage", "energymeter", "solarinverter"],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "ed8ef7d7-bf20-4780-870d-cf7171440952",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "73c4da2a-2167-4b68-8fbf-d03f5b7920a5",
|
||||
"name": "loggedIn",
|
||||
"displayName": "Logged in",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "345df3b1-d411-4db5-bbb2-3b14eb86c1ba",
|
||||
"name": "senecStorageLan",
|
||||
"displayName": "SENEC.Home storage",
|
||||
"interfaces": ["energystorage", "networkdevice"],
|
||||
"createMethods": ["Discovery", "User"],
|
||||
"settingsTypes": [
|
||||
{
|
||||
"id": "239d388c-f4a2-4055-88c3-decc6dace2b8",
|
||||
"name": "addMeter",
|
||||
"displayName": "Add meter",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
}
|
||||
],
|
||||
"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": "d3bdc64b-67fb-4d84-97ed-c5142e5db55d",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "3b00470d-11bc-4352-a885-52ab9443cee5",
|
||||
"name": "currentPower",
|
||||
"displayName": "Current power",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0.00,
|
||||
"cached": true
|
||||
},
|
||||
{
|
||||
"id": "66a3a7d2-1210-4891-a8f2-6fe4f9b8c6cc",
|
||||
"name": "batteryLevel",
|
||||
"displayName": "Battery level",
|
||||
"type": "int",
|
||||
"unit": "Percentage",
|
||||
"minValue": 0,
|
||||
"maxValue": 100,
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "a9aa13fb-3f51-40e3-9d29-ab882513a87a",
|
||||
"name": "batteryCritical",
|
||||
"displayName": "Battery critical",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "339f602f-c9f1-446d-bc96-1c7f9a7db922",
|
||||
"name": "chargingState",
|
||||
"displayName": "Charging state",
|
||||
"type": "QString",
|
||||
"possibleValues": ["idle", "charging", "discharging"],
|
||||
"defaultValue": "idle"
|
||||
},
|
||||
{
|
||||
"id": "d927e6cf-29f0-412f-b74d-7d803fd3e191",
|
||||
"name": "capacity",
|
||||
"displayName": "Capacity",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "8de0eeca-a698-4aee-bc67-a586c8f10652",
|
||||
"name": "senecStorageCloud",
|
||||
"displayName": "SENEC.Home storage (SENEC.Connect)",
|
||||
"interfaces": ["energystorage", "connectable"],
|
||||
"createMethods": ["Auto"],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "9172116e-e513-429b-b9a0-c73d7587bfa8",
|
||||
"name": "systemId",
|
||||
"displayName": "System ID",
|
||||
"type": "QString",
|
||||
"readOnly": true,
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "5d1d61dd-a419-4ba5-992a-91e9a3f34d31",
|
||||
"name": "serialNumber",
|
||||
"displayName": "Serial number",
|
||||
"type": "QString",
|
||||
"readOnly": true,
|
||||
"defaultValue": ""
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "cd7d8a3c-44b4-4517-8aa0-25288d03f19a",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "eccdfa19-b033-445c-b196-ab41bb2e449f",
|
||||
"name": "currentPower",
|
||||
"displayName": "Current power",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0.00,
|
||||
"cached": true
|
||||
},
|
||||
{
|
||||
"id": "500b0df2-61d0-4895-91c7-c8f2616dd93d",
|
||||
"name": "batteryLevel",
|
||||
"displayName": "Battery level",
|
||||
"type": "int",
|
||||
"unit": "Percentage",
|
||||
"minValue": 0,
|
||||
"maxValue": 100,
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "8805be49-b88b-48b6-a0e3-7b6b251d3299",
|
||||
"name": "batteryCritical",
|
||||
"displayName": "Battery critical",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "8db60ec4-06cd-4fc3-a66e-ced915bf5c11",
|
||||
"name": "chargingState",
|
||||
"displayName": "Charging state",
|
||||
"type": "QString",
|
||||
"possibleValues": ["idle", "charging", "discharging"],
|
||||
"defaultValue": "idle"
|
||||
},
|
||||
{
|
||||
"id": "f282b051-a68c-4739-88c7-e5819d8424f9",
|
||||
"name": "capacity",
|
||||
"displayName": "Capacity",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "senecMeter",
|
||||
"displayName": "SENEC Meter",
|
||||
"id": "303f454d-93b7-4f8f-b295-a4c22e09be6d",
|
||||
"createMethods": [ "auto" ],
|
||||
"interfaces": ["energymeter", "connectable"],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "9aa4dbfb-255b-408b-b148-8fb145ba8c98",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "56e58d03-782d-40bb-8d32-fad396ffefdc",
|
||||
"name": "currentPower",
|
||||
"displayName": "Current power usage",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "f9920cfa-26a0-4247-ad50-b4fca91596f0",
|
||||
"name": "totalEnergyProduced",
|
||||
"displayName": "Energy returned",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "21728bf0-a566-4eb0-a163-5f108f34bb9b",
|
||||
"name": "totalEnergyConsumed",
|
||||
"displayName": "Energy consumed",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "385df15e-4f1d-4842-a10a-597d087bdac0",
|
||||
"name": "currentPowerPhaseA",
|
||||
"displayName": "Current power phase A",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "3bab2b05-99d7-4b3a-b903-054d269a3527",
|
||||
"name": "currentPowerPhaseB",
|
||||
"displayName": "Current power phase B",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "01a63c94-b635-4e98-8037-d5d69fe0dd7c",
|
||||
"name": "currentPowerPhaseC",
|
||||
"displayName": "Current power phase C",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "db469573-bb11-48d2-84ba-2b8ff4b13413",
|
||||
"name": "voltagePhaseA",
|
||||
"displayName": "Voltage phase A",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "176d5a01-220d-44da-b875-9a40aaa9c85a",
|
||||
"name": "voltagePhaseB",
|
||||
"displayName": "Voltage phase B",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "e7ab02d3-016a-44ca-8da1-5d7ac9acbc6d",
|
||||
"name": "voltagePhaseC",
|
||||
"displayName": "Voltage phase C",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "1923f7b0-5b54-46d8-a7e9-9364b7c9eeb8",
|
||||
"name": "currentPhaseA",
|
||||
"displayName": "Current phase A",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "4908abf8-f655-4e14-a007-3eefeda7088e",
|
||||
"name": "currentPhaseB",
|
||||
"displayName": "Current phase B",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "e8415152-0fac-4cd9-98ac-b0e7c006d637",
|
||||
"name": "currentPhaseC",
|
||||
"displayName": "Current phase C",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "12a613fb-f33c-48b1-a9a4-caaee1041d25",
|
||||
"name": "senecInverter",
|
||||
"displayName": "SENEC PV inverter",
|
||||
"interfaces": ["solarinverter", "connectable"],
|
||||
"createMethods": ["Auto"],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "67c15e22-2503-41e3-8318-2ccb973760c4",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "11e72bac-a531-4622-bbd0-da5b638e3257",
|
||||
"name": "currentPower",
|
||||
"displayName": "Current power",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "e5a22d6c-573a-46df-ba92-3d33bd0ad754",
|
||||
"name": "totalEnergyProduced",
|
||||
"displayName": "Total energy produced",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
14
senec/meta.json
Normal file
14
senec/meta.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"title": "SENEC",
|
||||
"tagline": "Integrate SENEC.Home energy storages from the local network or via SENEC.Connect.",
|
||||
"icon": "senec.svg",
|
||||
"stability": "community",
|
||||
"offline": false,
|
||||
"technologies": [
|
||||
"network",
|
||||
"cloud"
|
||||
],
|
||||
"categories": [
|
||||
"energy"
|
||||
]
|
||||
}
|
||||
16
senec/senec.pro
Normal file
16
senec/senec.pro
Normal file
@ -0,0 +1,16 @@
|
||||
include(../plugins.pri)
|
||||
|
||||
QT+= network
|
||||
|
||||
SOURCES += \
|
||||
integrationpluginsenec.cpp \
|
||||
senecconnect.cpp \
|
||||
senecdiscovery.cpp \
|
||||
senecstoragelan.cpp
|
||||
|
||||
HEADERS += \
|
||||
integrationpluginsenec.h \
|
||||
senecconnect.h \
|
||||
senecdiscovery.h \
|
||||
senecstoragelan.h
|
||||
|
||||
40
senec/senec.svg
Normal file
40
senec/senec.svg
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
data-name="Ebene 1"
|
||||
viewBox="0 0 100 23"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="senec_logo_de_clean_resize_1.svg"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
width="100"
|
||||
height="23"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
id="namedview8"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="3.0458796"
|
||||
inkscape:cx="5.9096229"
|
||||
inkscape:cy="65.170008"
|
||||
inkscape:window-width="1728"
|
||||
inkscape:window-height="1006"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg6" />
|
||||
<path
|
||||
d="m 16.483695,17.845517 c 0,2.942408 -2.713129,5.216087 -6.9738891,5.216087 H 1.045605 c -0.55408995,0 -1.01264754,-0.458557 -1.01264754,-1.012647 v -2.445638 c 0,-0.55409 0.43945112,-1.012647 0.99354104,-1.031754 h 7.5661923 c 0.6687298,0 2.7322372,0 2.7322372,-1.757802 V 15.973075 A 1.6259672,1.6259672 0 0 0 9.8537235,14.215273 C 9.2614206,14.06242 4.6376363,13.030667 4.1981852,12.95424 A 5.1797851,5.1797851 0 0 1 0.01385098,7.4897667 V 5.7892839 C 0.01385098,2.789556 2.6505554,0 7.1596997,0 h 8.2158163 c 0.592303,0 0.802475,0.43945066 0.515876,0.84068815 L 14.381975,3.592031 C 14.076271,4.1079077 13.522181,4.432719 12.910771,4.432719 H 7.2552332 c -1.1463929,0 -2.0444004,0.9553274 -2.0444004,2.1017203 v 0.5731964 c 0.057314,0.7642619 0.6305158,1.4138846 1.394777,1.5476304 0,0 4.0314822,0.8406881 5.5791122,1.10818 3.229007,0.6687289 4.337187,2.9424099 4.337187,5.0059169 v 3.076154 z M 20.66803,1.0317536 C 20.66803,0.47766372 21.107481,0.01910658 21.661571,0 h 14.501868 c 0.592304,0 0.840688,0.36302443 0.515877,0.84068815 L 35.131686,3.592031 C 34.787769,4.0696947 34.252785,4.3753994 33.660482,4.432719 H 21.623357 c -0.554089,0 -1.012647,-0.420344 -1.031753,-0.9744339 V 3.4200721 Z M 55.499266,0 c -0.55409,0 -1.012647,0.42034408 -1.031753,0.97443391 V 13.56565 c 0,0.592303 -0.229279,0.668729 -0.592303,0.152852 L 45.277263,0.84068815 C 44.914238,0.34391785 44.341042,0.03821306 43.729633,0 H 41.455954 C 40.901864,0 40.443306,0.42034408 40.4242,0.97443391 V 22.02985 c 0,0.55409 0.420344,1.012648 0.974434,1.031754 h 3.114367 c 0.55409,0 1.012647,-0.420344 1.031753,-0.974434 V 9.9162979 c 0,-0.5923027 0.229279,-0.6687289 0.592303,-0.1528518 l 8.521521,12.4956829 c 0.363025,0.496771 0.936221,0.802475 1.547631,0.840688 h 2.426531 c 0.55409,0 1.012647,-0.420343 1.031754,-0.974433 V 1.0317536 A 1.0317536,1.0317536 0 0 0 58.670953,0 H 55.480159 Z M 21.699784,18.590672 c -0.55409,0 -1.012648,0.420344 -1.031754,0.974434 v 0.03821 l -0.07643,2.407425 c 0,0.55409 0.420343,1.012647 0.974433,1.031754 h 12.094445 c 0.592303,-0.05732 1.127287,-0.363024 1.471204,-0.840688 l 1.54763,-2.732237 c 0.286599,-0.515877 0.07643,-0.840688 -0.515877,-0.840688 L 21.699784,18.571565 Z M 20.66803,10.069151 c 0,-0.5540902 0.439451,-1.0126474 0.993541,-1.031754 h 11.998911 c 0.592303,0 0.840689,0.3630245 0.515876,0.840688 l -1.54763,2.713131 c -0.343917,0.477664 -0.878901,0.783369 -1.471204,0.840688 H 21.60425 c -0.55409,0 -1.012646,-0.420344 -1.031753,-0.974434 v -0.05732 l 0.07643,-2.350106 z M 65.855015,0 c -0.55409,0 -1.012647,0.42034408 -1.031754,0.97443391 v 0.0573194 l 0.07643,2.4265316 c 0,0.5540899 0.439451,1.0126471 0.993541,1.0317535 h 11.903379 c 0.592304,-0.057319 1.127286,-0.3630243 1.471205,-0.840688 L 80.853655,0.85979441 C 81.159359,0.34391785 80.93008,0.01910658 80.337778,0.01910658 H 65.874122 Z m -1.031754,19.603319 c 0,-0.55409 0.420344,-1.012647 0.974434,-1.031754 h 14.520976 c 0.592303,0 0.840688,0.363024 0.515877,0.840688 l -1.54763,2.713131 c -0.343919,0.477663 -0.878901,0.783367 -1.471204,0.840688 H 65.950548 c -0.55409,0.03821 -1.031754,-0.382132 -1.069967,-0.936222 v -0.03821 l -0.07643,-2.407424 z M 65.855015,9.037397 c -0.55409,0 -1.012647,0.4203441 -1.031754,0.974434 v 0.05731 l 0.07643,2.426533 c -0.03821,0.55409 0.382132,1.031753 0.955328,1.069967 h 9.495954 c 0.592303,-0.05733 1.127287,-0.363025 1.471204,-0.840688 l 1.54763,-2.713132 c 0.305702,-0.515867 0.07642,-0.8406783 -0.51588,-0.8406783 L 65.874122,9.0756101 Z m 25.62188,14.024207 h 5.36894 c 0.611409,0 1.165502,-0.324811 1.471206,-0.840688 l 1.54763,-2.732237 c 0.305699,-0.43945 0.07643,-0.840688 -0.51588,-0.840688 H 93.043632 C 90.024797,18.495139 89.012151,17.253214 89.012151,14.406339 V 8.0820696 a 3.5347113,3.5347113 0 0 1 3.439178,-3.6111374 h 4.337186 c 0.61141,0 1.165498,-0.3248114 1.471201,-0.8406882 l 1.54763,-2.78955585 C 100.11305,0.40123749 99.883772,0 99.291477,0 H 91.610641 C 86.834004,0 83.81517,3.6684572 83.81517,8.3113482 v 6.5344408 c 0.09554,4.26076 2.311892,8.234922 7.661725,8.234922 z"
|
||||
style="fill:#000099;fill-rule:evenodd;stroke-width:1"
|
||||
id="path4" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
104
senec/senecconnect.cpp
Normal file
104
senec/senecconnect.cpp
Normal file
@ -0,0 +1,104 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* 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 "senecconnect.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
#include <QUrlQuery>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonParseError>
|
||||
|
||||
SenecConnect::SenecConnect(NetworkAccessManager *networkManager, const QString &subscriptionKey, QObject *parent)
|
||||
: QObject{parent},
|
||||
m_networkManager{networkManager},
|
||||
m_subscriptionKey{subscriptionKey}
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QUrl SenecConnect::deviceDataUrl()
|
||||
{
|
||||
QUrl url("https://apim-eds-gwc-prod.azure-api.net/senec-connect/v1/systems/device-data/general");
|
||||
QUrlQuery query;
|
||||
query.addQueryItem("include", "battery,meter,bessNameplate");
|
||||
url.setQuery(query);
|
||||
return url;
|
||||
}
|
||||
|
||||
QList<SenecConnect::SystemData> SenecConnect::parseDeviceData(const QByteArray &data, bool *ok)
|
||||
{
|
||||
QList<SystemData> systems;
|
||||
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
|
||||
if (jsonError.error != QJsonParseError::NoError || !jsonDoc.isArray()) {
|
||||
if (ok)
|
||||
*ok = false;
|
||||
|
||||
return systems;
|
||||
}
|
||||
|
||||
foreach (const QVariant &systemVariant, jsonDoc.toVariant().toList()) {
|
||||
QVariantMap systemMap = systemVariant.toMap();
|
||||
QVariantMap batteryMap = systemMap.value("battery").toMap();
|
||||
QVariantMap meterMap = systemMap.value("meter").toMap();
|
||||
QVariantMap nameplateMap = systemMap.value("bessNameplate").toMap();
|
||||
|
||||
SystemData system;
|
||||
system.systemId = nameplateMap.value("system_id").toString();
|
||||
if (system.systemId.isEmpty())
|
||||
continue;
|
||||
|
||||
system.serialNumber = nameplateMap.value("serial_number").toString();
|
||||
system.model = nameplateMap.value("model").toString();
|
||||
system.capacity = nameplateMap.value("design_capacity").toFloat() / 1000.0;
|
||||
system.batteryPower = batteryMap.value("power").toFloat();
|
||||
system.batteryLevel = batteryMap.value("state_of_charge").toFloat();
|
||||
system.gridPower = meterMap.value("grid_power").toFloat();
|
||||
system.production = meterMap.value("production").toFloat();
|
||||
// Stale systems are still listed, but without live battery measurements
|
||||
system.active = batteryMap.contains("voltage") || batteryMap.contains("current");
|
||||
systems.append(system);
|
||||
}
|
||||
|
||||
if (ok)
|
||||
*ok = true;
|
||||
|
||||
return systems;
|
||||
}
|
||||
|
||||
QNetworkReply *SenecConnect::getDeviceData()
|
||||
{
|
||||
QNetworkRequest request(deviceDataUrl());
|
||||
request.setRawHeader("Ocp-Apim-Subscription-Key", m_subscriptionKey.toUtf8());
|
||||
request.setRawHeader("Accept", "application/json");
|
||||
qCDebug(dcSenec()) << "Get" << request.url().toString();
|
||||
return m_networkManager->get(request);
|
||||
}
|
||||
71
senec/senecconnect.h
Normal file
71
senec/senecconnect.h
Normal file
@ -0,0 +1,71 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* 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 SENECCONNECT_H
|
||||
#define SENECCONNECT_H
|
||||
|
||||
#include <QUrl>
|
||||
#include <QObject>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include <network/networkaccessmanager.h>
|
||||
|
||||
// Official read-only SENEC cloud API (V4 systems only): https://developer.senec.com
|
||||
|
||||
class SenecConnect : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
struct SystemData {
|
||||
QString systemId;
|
||||
QString serialNumber;
|
||||
QString model;
|
||||
float capacity = 0; // kWh
|
||||
float batteryPower = 0; // W, positive charging
|
||||
float batteryLevel = 0; // %
|
||||
float gridPower = 0; // W, positive import
|
||||
float production = 0; // W
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
explicit SenecConnect(NetworkAccessManager *networkManager, const QString &subscriptionKey, QObject *parent = nullptr);
|
||||
|
||||
static QUrl deviceDataUrl();
|
||||
static QList<SystemData> parseDeviceData(const QByteArray &data, bool *ok = nullptr);
|
||||
|
||||
QNetworkReply *getDeviceData();
|
||||
|
||||
private:
|
||||
NetworkAccessManager *m_networkManager = nullptr;
|
||||
QString m_subscriptionKey;
|
||||
|
||||
};
|
||||
|
||||
#endif // SENECCONNECT_H
|
||||
112
senec/senecdiscovery.cpp
Normal file
112
senec/senecdiscovery.cpp
Normal file
@ -0,0 +1,112 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* 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()
|
||||
{
|
||||
qCInfo(dcSenec()) << "Discovery: Searching SENEC energy storages in the local 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) {
|
||||
qCDebug(dcSenec()) << "Discovery: Failed to initialize host" << address.toString() << "Continue ...";
|
||||
cleanupStorage(storage);
|
||||
return;
|
||||
}
|
||||
|
||||
// Successfully initialized
|
||||
Result result;
|
||||
result.deviceId = storage->deviceId();
|
||||
result.address = address;
|
||||
m_results.append(result);
|
||||
|
||||
qCInfo(dcSenec()) << "Discovery: Found SENEC storage on" << address.toString() << storage->deviceId();
|
||||
cleanupStorage(storage);
|
||||
});
|
||||
|
||||
storage->initialize();
|
||||
}
|
||||
|
||||
void SenecDiscovery::cleanupStorage(SenecStorageLan *storage)
|
||||
{
|
||||
m_storages.removeAll(storage);
|
||||
storage->deleteLater();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
qCInfo(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
|
||||
351
senec/senecstoragelan.cpp
Normal file
351
senec/senecstoragelan.cpp
Normal file
@ -0,0 +1,351 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* 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;
|
||||
}
|
||||
|
||||
bool SenecStorageLan::available() const
|
||||
{
|
||||
return m_available;
|
||||
}
|
||||
|
||||
bool SenecStorageLan::initialized() const
|
||||
{
|
||||
return m_initialized;
|
||||
}
|
||||
|
||||
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::batteryLevel() const
|
||||
{
|
||||
return m_batteryLevel;
|
||||
}
|
||||
|
||||
float SenecStorageLan::batteryPower() const
|
||||
{
|
||||
return m_batteryPower;
|
||||
}
|
||||
|
||||
float SenecStorageLan::gridPower() const
|
||||
{
|
||||
return m_gridPower;
|
||||
}
|
||||
|
||||
float SenecStorageLan::inverterPower() const
|
||||
{
|
||||
return m_inverterPower;
|
||||
}
|
||||
|
||||
float SenecStorageLan::parseFloat(const QString &value)
|
||||
{
|
||||
if (!value.startsWith("fl_")) {
|
||||
qCWarning(dcSenec()) << "The given value does not seem to be a float, it is not starting with fl_" << value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
QByteArray rawData = QByteArray::fromHex(value.mid(3).toUtf8());
|
||||
if (rawData.size() != 4) {
|
||||
qCWarning(dcSenec()) << "Invalid raw data size for converting value to float32" << value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
float result = 0;
|
||||
quint32 dataValue = 0;
|
||||
QDataStream stream(rawData);
|
||||
stream >> dataValue;
|
||||
memcpy(&result, &dataValue, sizeof(quint32));
|
||||
return result;
|
||||
}
|
||||
|
||||
QString SenecStorageLan::parseString(const QString &value)
|
||||
{
|
||||
if (!value.startsWith("st_")) {
|
||||
qCWarning(dcSenec()) << "The given value does not seem to be a string, it is not starting with st_" << value;
|
||||
return QString();
|
||||
}
|
||||
|
||||
return value.mid(3);
|
||||
}
|
||||
|
||||
// quint8 SenecStorageLan::parseUInt8(const QString &value)
|
||||
// {
|
||||
// Q_ASSERT_X(value.left(3) == "u8_", "SenecStorageLan", "The given value does not seem to be a uint8, it is not starting with u8_");
|
||||
|
||||
// }
|
||||
|
||||
|
||||
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());
|
||||
|
||||
m_initialized = true;
|
||||
emit initializeFinished(true);
|
||||
setAvailable(true);
|
||||
|
||||
qCDebug(dcSenec()) << "Initialized successfully";
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void SenecStorageLan::update()
|
||||
{
|
||||
if (!m_url.isValid()) {
|
||||
qCDebug(dcSenec()) << "Cannot update the storage. The request URL is not valid. Maybe the IP is not known yet or invalid.";
|
||||
emit updatedFinished(false);
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantMap request;
|
||||
|
||||
QVariantMap energyMap;
|
||||
energyMap.insert("GUI_BAT_DATA_POWER", QString());
|
||||
energyMap.insert("GUI_INVERTER_POWER", QString());
|
||||
energyMap.insert("GUI_BAT_DATA_FUEL_CHARGE", QString());
|
||||
energyMap.insert("GUI_HOUSE_POW", QString());
|
||||
energyMap.insert("GUI_GRID_POW", QString());
|
||||
|
||||
request.insert("ENERGY", energyMap);
|
||||
|
||||
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()) << "Update request finished with error. Status:" << status << "Error:" << reply->errorString();
|
||||
setAvailable(false);
|
||||
emit updatedFinished(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()) << "Update request finished successfully, but the response contains invalid JSON object:" << responseData;
|
||||
setAvailable(false);
|
||||
emit updatedFinished(false);
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(dcSenec()) << "Update request finished successfully" << qUtf8Printable(jsonDoc.toJson());
|
||||
|
||||
QVariantMap energyResponseMap = responseMap.value("ENERGY").toMap();
|
||||
m_batteryPower = parseFloat(energyResponseMap.value("GUI_BAT_DATA_POWER").toString());
|
||||
m_batteryLevel = parseFloat(energyResponseMap.value("GUI_BAT_DATA_FUEL_CHARGE").toString());
|
||||
m_gridPower = parseFloat(energyResponseMap.value("GUI_GRID_POW").toString());
|
||||
m_inverterPower = parseFloat(energyResponseMap.value("GUI_INVERTER_POWER").toString());
|
||||
|
||||
setAvailable(true);
|
||||
|
||||
qCDebug(dcSenec()).nospace().noquote() << "Update values: Battery power: " << m_batteryPower
|
||||
<< "W, Battery level: " << m_batteryLevel
|
||||
<< "%, Grid power: " << m_gridPower
|
||||
<< "W, Inverter power: " << m_inverterPower << "W";
|
||||
|
||||
emit updatedFinished(true);
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
112
senec/senecstoragelan.h
Normal file
112
senec/senecstoragelan.h
Normal file
@ -0,0 +1,112 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* 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>
|
||||
|
||||
// https://blog.odenthal.cc/query-the-hidden-api-of-your-senec-photovoltaik-appliance/
|
||||
|
||||
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;
|
||||
bool initialized() const;
|
||||
|
||||
// Init properties
|
||||
QString deviceId() const;
|
||||
float capacity() const;
|
||||
float maxChargePower() const;
|
||||
float maxDischargePower() const;
|
||||
|
||||
// Update properties
|
||||
float batteryLevel() const;
|
||||
float batteryPower() const;
|
||||
float gridPower() const;
|
||||
float inverterPower() const;
|
||||
|
||||
|
||||
static float parseFloat(const QString &value); // fl_
|
||||
static QString parseString(const QString &value); // st_
|
||||
//static quint8 parseUInt8(const QString &value); // u8_
|
||||
|
||||
|
||||
public slots:
|
||||
void initialize();
|
||||
void update();
|
||||
|
||||
signals:
|
||||
void initializeFinished(bool success);
|
||||
void availableChanged(bool available);
|
||||
void updatedFinished(bool success);
|
||||
|
||||
private:
|
||||
NetworkAccessManager *m_networkManager = nullptr;
|
||||
|
||||
QUrl m_url;
|
||||
QHostAddress m_address;
|
||||
|
||||
bool m_available = false;
|
||||
bool m_initialized = false;
|
||||
|
||||
QString m_deviceId;
|
||||
float m_capacity = 0;
|
||||
float m_maxChargePower = 0;
|
||||
float m_maxDischargePower = 0;
|
||||
|
||||
float m_batteryLevel = 0;
|
||||
float m_batteryPower = 0;
|
||||
float m_gridPower = 0;
|
||||
float m_inverterPower = 0;
|
||||
|
||||
void updateUrl();
|
||||
void setAvailable(bool available);
|
||||
|
||||
private slots:
|
||||
void ignoreSslErrors(const QList<QSslError> &errors);
|
||||
|
||||
};
|
||||
|
||||
#endif // SENECSTORAGELAN_H
|
||||
292
senec/translations/3f055ea5-a883-445d-9556-675f5eda6c9a-de.ts
Normal file
292
senec/translations/3f055ea5-a883-445d-9556-675f5eda6c9a-de.ts
Normal file
@ -0,0 +1,292 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="de">
|
||||
<context>
|
||||
<name>IntegrationPluginSenec</name>
|
||||
<message>
|
||||
<location filename="../integrationpluginsenec.cpp" line="65"/>
|
||||
<source>Unable to discover devices in your network.</source>
|
||||
<translation>Geräte im Netzwerk können nicht gesucht werden.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../integrationpluginsenec.cpp" line="107"/>
|
||||
<source>Please sign in on developer.senec.com with your SENEC account, subscribe to the SENEC.Connect product and enter the subscription key from your profile.</source>
|
||||
<translation>Bitte melden Sie sich auf developer.senec.com mit Ihrem SENEC-Konto an, abonnieren Sie das Produkt SENEC.Connect und geben Sie den Abonnementschlüssel aus Ihrem Profil ein.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../integrationpluginsenec.cpp" line="116"/>
|
||||
<source>Please enter a subscription key.</source>
|
||||
<translation>Bitte geben Sie einen Abonnementschlüssel ein.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../integrationpluginsenec.cpp" line="130"/>
|
||||
<source>The subscription key is not valid.</source>
|
||||
<translation>Der Abonnementschlüssel ist ungültig.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../integrationpluginsenec.cpp" line="136"/>
|
||||
<source>Unable to connect to SENEC.Connect. Please try again later.</source>
|
||||
<translation>Verbindung zu SENEC.Connect nicht möglich. Bitte versuchen Sie es später erneut.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../integrationpluginsenec.cpp" line="240"/>
|
||||
<source>The SENEC.Connect subscription key is missing. Please reconfigure the connection.</source>
|
||||
<translation>Der SENEC.Connect-Abonnementschlüssel fehlt. Bitte konfigurieren Sie die Verbindung neu.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Senec</name>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="68"/>
|
||||
<source>Add meter</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: senecStorageLan, Type: settings, ID: {239d388c-f4a2-4055-88c3-decc6dace2b8})</extracomment>
|
||||
<translation>Zähler hinzufügen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="71"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="74"/>
|
||||
<source>Battery critical</source>
|
||||
<extracomment>The name of the StateType ({8805be49-b88b-48b6-a0e3-7b6b251d3299}) of ThingClass senecStorageCloud
|
||||
----------
|
||||
The name of the StateType ({a9aa13fb-3f51-40e3-9d29-ab882513a87a}) of ThingClass senecStorageLan</extracomment>
|
||||
<translation>Batterieladung kritisch</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="77"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="80"/>
|
||||
<source>Battery level</source>
|
||||
<extracomment>The name of the StateType ({500b0df2-61d0-4895-91c7-c8f2616dd93d}) of ThingClass senecStorageCloud
|
||||
----------
|
||||
The name of the StateType ({66a3a7d2-1210-4891-a8f2-6fe4f9b8c6cc}) of ThingClass senecStorageLan</extracomment>
|
||||
<translation>Ladestand</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="83"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="86"/>
|
||||
<source>Capacity</source>
|
||||
<extracomment>The name of the StateType ({f282b051-a68c-4739-88c7-e5819d8424f9}) of ThingClass senecStorageCloud
|
||||
----------
|
||||
The name of the StateType ({d927e6cf-29f0-412f-b74d-7d803fd3e191}) of ThingClass senecStorageLan</extracomment>
|
||||
<translation>Kapazität</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="89"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="92"/>
|
||||
<source>Charging state</source>
|
||||
<extracomment>The name of the StateType ({8db60ec4-06cd-4fc3-a66e-ced915bf5c11}) of ThingClass senecStorageCloud
|
||||
----------
|
||||
The name of the StateType ({339f602f-c9f1-446d-bc96-1c7f9a7db922}) of ThingClass senecStorageLan</extracomment>
|
||||
<translation>Ladezustand</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="95"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="98"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="101"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="104"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="107"/>
|
||||
<source>Connected</source>
|
||||
<extracomment>The name of the StateType ({67c15e22-2503-41e3-8318-2ccb973760c4}) of ThingClass senecInverter
|
||||
----------
|
||||
The name of the StateType ({9aa4dbfb-255b-408b-b148-8fb145ba8c98}) of ThingClass senecMeter
|
||||
----------
|
||||
The name of the StateType ({cd7d8a3c-44b4-4517-8aa0-25288d03f19a}) of ThingClass senecStorageCloud
|
||||
----------
|
||||
The name of the StateType ({d3bdc64b-67fb-4d84-97ed-c5142e5db55d}) of ThingClass senecStorageLan
|
||||
----------
|
||||
The name of the StateType ({ed8ef7d7-bf20-4780-870d-cf7171440952}) of ThingClass senecConnect</extracomment>
|
||||
<translation>Verbunden</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="110"/>
|
||||
<source>Current phase A</source>
|
||||
<extracomment>The name of the StateType ({1923f7b0-5b54-46d8-a7e9-9364b7c9eeb8}) of ThingClass senecMeter</extracomment>
|
||||
<translation>Strom Phase A</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="113"/>
|
||||
<source>Current phase B</source>
|
||||
<extracomment>The name of the StateType ({4908abf8-f655-4e14-a007-3eefeda7088e}) of ThingClass senecMeter</extracomment>
|
||||
<translation>Strom Phase B</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="116"/>
|
||||
<source>Current phase C</source>
|
||||
<extracomment>The name of the StateType ({e8415152-0fac-4cd9-98ac-b0e7c006d637}) of ThingClass senecMeter</extracomment>
|
||||
<translation>Strom Phase C</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="119"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="122"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="125"/>
|
||||
<source>Current power</source>
|
||||
<extracomment>The name of the StateType ({11e72bac-a531-4622-bbd0-da5b638e3257}) of ThingClass senecInverter
|
||||
----------
|
||||
The name of the StateType ({eccdfa19-b033-445c-b196-ab41bb2e449f}) of ThingClass senecStorageCloud
|
||||
----------
|
||||
The name of the StateType ({3b00470d-11bc-4352-a885-52ab9443cee5}) of ThingClass senecStorageLan</extracomment>
|
||||
<translation>Aktuelle Leistung</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="128"/>
|
||||
<source>Current power phase A</source>
|
||||
<extracomment>The name of the StateType ({385df15e-4f1d-4842-a10a-597d087bdac0}) of ThingClass senecMeter</extracomment>
|
||||
<translation>Leistung Phase A</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="131"/>
|
||||
<source>Current power phase B</source>
|
||||
<extracomment>The name of the StateType ({3bab2b05-99d7-4b3a-b903-054d269a3527}) of ThingClass senecMeter</extracomment>
|
||||
<translation>Leistung Phase B</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="134"/>
|
||||
<source>Current power phase C</source>
|
||||
<extracomment>The name of the StateType ({01a63c94-b635-4e98-8037-d5d69fe0dd7c}) of ThingClass senecMeter</extracomment>
|
||||
<translation>Leistung Phase C</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="137"/>
|
||||
<source>Current power usage</source>
|
||||
<extracomment>The name of the StateType ({56e58d03-782d-40bb-8d32-fad396ffefdc}) of ThingClass senecMeter</extracomment>
|
||||
<translation>Aktuelle Leistung</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="140"/>
|
||||
<source>Energy consumed</source>
|
||||
<extracomment>The name of the StateType ({21728bf0-a566-4eb0-a163-5f108f34bb9b}) of ThingClass senecMeter</extracomment>
|
||||
<translation>Bezogene Energie</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="143"/>
|
||||
<source>Energy returned</source>
|
||||
<extracomment>The name of the StateType ({f9920cfa-26a0-4247-ad50-b4fca91596f0}) of ThingClass senecMeter</extracomment>
|
||||
<translation>Eingespeiste Energie</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="146"/>
|
||||
<source>Host address</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: senecStorageLan, Type: thing, ID: {a45cabe9-9f76-405e-b47c-0a3d00bdd44b})</extracomment>
|
||||
<translation>IP-Adresse</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="149"/>
|
||||
<source>Host name</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: senecStorageLan, Type: thing, ID: {51a1bbc0-28a5-43ac-9021-896acbf9413f})</extracomment>
|
||||
<translation>Hostname</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="152"/>
|
||||
<source>Logged in</source>
|
||||
<extracomment>The name of the StateType ({73c4da2a-2167-4b68-8fbf-d03f5b7920a5}) of ThingClass senecConnect</extracomment>
|
||||
<translation>Angemeldet</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="155"/>
|
||||
<source>MAC address</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: senecStorageLan, Type: thing, ID: {0790e1b1-c8c9-488f-9cda-2a0bbf14b4af})</extracomment>
|
||||
<translation>MAC-Adresse</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="164"/>
|
||||
<source>SENEC Meter</source>
|
||||
<extracomment>The name of the ThingClass ({303f454d-93b7-4f8f-b295-a4c22e09be6d})</extracomment>
|
||||
<translation>SENEC Zähler</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="167"/>
|
||||
<source>SENEC PV inverter</source>
|
||||
<extracomment>The name of the ThingClass ({12a613fb-f33c-48b1-a9a4-caaee1041d25})</extracomment>
|
||||
<translation>SENEC PV-Wechselrichter</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="170"/>
|
||||
<source>SENEC.Connect</source>
|
||||
<extracomment>The name of the ThingClass ({d064f111-1332-4096-b5de-529d44f72963})</extracomment>
|
||||
<translation>SENEC.Connect</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="176"/>
|
||||
<source>SENEC.Home storage (SENEC.Connect)</source>
|
||||
<extracomment>The name of the ThingClass ({8de0eeca-a698-4aee-bc67-a586c8f10652})</extracomment>
|
||||
<translation>SENEC.Home Speicher (SENEC.Connect)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="179"/>
|
||||
<source>Serial number</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: senecStorageCloud, Type: thing, ID: {5d1d61dd-a419-4ba5-992a-91e9a3f34d31})</extracomment>
|
||||
<translation>Seriennummer</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="182"/>
|
||||
<source>System ID</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: senecStorageCloud, Type: thing, ID: {9172116e-e513-429b-b9a0-c73d7587bfa8})</extracomment>
|
||||
<translation>System-ID</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="185"/>
|
||||
<source>Total energy produced</source>
|
||||
<extracomment>The name of the StateType ({e5a22d6c-573a-46df-ba92-3d33bd0ad754}) of ThingClass senecInverter</extracomment>
|
||||
<translation>Gesamte erzeugte Energie</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="188"/>
|
||||
<source>Voltage phase A</source>
|
||||
<extracomment>The name of the StateType ({db469573-bb11-48d2-84ba-2b8ff4b13413}) of ThingClass senecMeter</extracomment>
|
||||
<translation>Spannung Phase A</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="191"/>
|
||||
<source>Voltage phase B</source>
|
||||
<extracomment>The name of the StateType ({176d5a01-220d-44da-b875-9a40aaa9c85a}) of ThingClass senecMeter</extracomment>
|
||||
<translation>Spannung Phase B</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="194"/>
|
||||
<source>Voltage phase C</source>
|
||||
<extracomment>The name of the StateType ({e7ab02d3-016a-44ca-8da1-5d7ac9acbc6d}) of ThingClass senecMeter</extracomment>
|
||||
<translation>Spannung Phase C</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="158"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="161"/>
|
||||
<source>SENEC</source>
|
||||
<extracomment>The name of the vendor ({1be922b9-4439-42cf-9caa-67f2ac7cc425})
|
||||
----------
|
||||
The name of the plugin Senec ({3f055ea5-a883-445d-9556-675f5eda6c9a})</extracomment>
|
||||
<translation>SENEC</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="173"/>
|
||||
<source>SENEC.Home storage</source>
|
||||
<extracomment>The name of the ThingClass ({345df3b1-d411-4db5-bbb2-3b14eb86c1ba})</extracomment>
|
||||
<translation>SENEC.Home Speicher</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="197"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="200"/>
|
||||
<source>charging</source>
|
||||
<extracomment>The name of a possible value of StateType {8db60ec4-06cd-4fc3-a66e-ced915bf5c11} of ThingClass senecStorageCloud
|
||||
----------
|
||||
The name of a possible value of StateType {339f602f-c9f1-446d-bc96-1c7f9a7db922} of ThingClass senecStorageLan</extracomment>
|
||||
<translation>lade</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="203"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="206"/>
|
||||
<source>discharging</source>
|
||||
<extracomment>The name of a possible value of StateType {8db60ec4-06cd-4fc3-a66e-ced915bf5c11} of ThingClass senecStorageCloud
|
||||
----------
|
||||
The name of a possible value of StateType {339f602f-c9f1-446d-bc96-1c7f9a7db922} of ThingClass senecStorageLan</extracomment>
|
||||
<translation>entlade</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="209"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="212"/>
|
||||
<source>idle</source>
|
||||
<extracomment>The name of a possible value of StateType {8db60ec4-06cd-4fc3-a66e-ced915bf5c11} of ThingClass senecStorageCloud
|
||||
----------
|
||||
The name of a possible value of StateType {339f602f-c9f1-446d-bc96-1c7f9a7db922} of ThingClass senecStorageLan</extracomment>
|
||||
<translation>leerlauf</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
292
senec/translations/3f055ea5-a883-445d-9556-675f5eda6c9a-en_US.ts
Normal file
292
senec/translations/3f055ea5-a883-445d-9556-675f5eda6c9a-en_US.ts
Normal file
@ -0,0 +1,292 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1">
|
||||
<context>
|
||||
<name>IntegrationPluginSenec</name>
|
||||
<message>
|
||||
<location filename="../integrationpluginsenec.cpp" line="65"/>
|
||||
<source>Unable to discover devices in your network.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../integrationpluginsenec.cpp" line="107"/>
|
||||
<source>Please sign in on developer.senec.com with your SENEC account, subscribe to the SENEC.Connect product and enter the subscription key from your profile.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../integrationpluginsenec.cpp" line="116"/>
|
||||
<source>Please enter a subscription key.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../integrationpluginsenec.cpp" line="130"/>
|
||||
<source>The subscription key is not valid.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../integrationpluginsenec.cpp" line="136"/>
|
||||
<source>Unable to connect to SENEC.Connect. Please try again later.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../integrationpluginsenec.cpp" line="240"/>
|
||||
<source>The SENEC.Connect subscription key is missing. Please reconfigure the connection.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Senec</name>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="68"/>
|
||||
<source>Add meter</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: senecStorageLan, Type: settings, ID: {239d388c-f4a2-4055-88c3-decc6dace2b8})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="71"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="74"/>
|
||||
<source>Battery critical</source>
|
||||
<extracomment>The name of the StateType ({8805be49-b88b-48b6-a0e3-7b6b251d3299}) of ThingClass senecStorageCloud
|
||||
----------
|
||||
The name of the StateType ({a9aa13fb-3f51-40e3-9d29-ab882513a87a}) of ThingClass senecStorageLan</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="77"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="80"/>
|
||||
<source>Battery level</source>
|
||||
<extracomment>The name of the StateType ({500b0df2-61d0-4895-91c7-c8f2616dd93d}) of ThingClass senecStorageCloud
|
||||
----------
|
||||
The name of the StateType ({66a3a7d2-1210-4891-a8f2-6fe4f9b8c6cc}) of ThingClass senecStorageLan</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="83"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="86"/>
|
||||
<source>Capacity</source>
|
||||
<extracomment>The name of the StateType ({f282b051-a68c-4739-88c7-e5819d8424f9}) of ThingClass senecStorageCloud
|
||||
----------
|
||||
The name of the StateType ({d927e6cf-29f0-412f-b74d-7d803fd3e191}) of ThingClass senecStorageLan</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="89"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="92"/>
|
||||
<source>Charging state</source>
|
||||
<extracomment>The name of the StateType ({8db60ec4-06cd-4fc3-a66e-ced915bf5c11}) of ThingClass senecStorageCloud
|
||||
----------
|
||||
The name of the StateType ({339f602f-c9f1-446d-bc96-1c7f9a7db922}) of ThingClass senecStorageLan</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="95"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="98"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="101"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="104"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="107"/>
|
||||
<source>Connected</source>
|
||||
<extracomment>The name of the StateType ({67c15e22-2503-41e3-8318-2ccb973760c4}) of ThingClass senecInverter
|
||||
----------
|
||||
The name of the StateType ({9aa4dbfb-255b-408b-b148-8fb145ba8c98}) of ThingClass senecMeter
|
||||
----------
|
||||
The name of the StateType ({cd7d8a3c-44b4-4517-8aa0-25288d03f19a}) of ThingClass senecStorageCloud
|
||||
----------
|
||||
The name of the StateType ({d3bdc64b-67fb-4d84-97ed-c5142e5db55d}) of ThingClass senecStorageLan
|
||||
----------
|
||||
The name of the StateType ({ed8ef7d7-bf20-4780-870d-cf7171440952}) of ThingClass senecConnect</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="110"/>
|
||||
<source>Current phase A</source>
|
||||
<extracomment>The name of the StateType ({1923f7b0-5b54-46d8-a7e9-9364b7c9eeb8}) of ThingClass senecMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="113"/>
|
||||
<source>Current phase B</source>
|
||||
<extracomment>The name of the StateType ({4908abf8-f655-4e14-a007-3eefeda7088e}) of ThingClass senecMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="116"/>
|
||||
<source>Current phase C</source>
|
||||
<extracomment>The name of the StateType ({e8415152-0fac-4cd9-98ac-b0e7c006d637}) of ThingClass senecMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="119"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="122"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="125"/>
|
||||
<source>Current power</source>
|
||||
<extracomment>The name of the StateType ({11e72bac-a531-4622-bbd0-da5b638e3257}) of ThingClass senecInverter
|
||||
----------
|
||||
The name of the StateType ({eccdfa19-b033-445c-b196-ab41bb2e449f}) of ThingClass senecStorageCloud
|
||||
----------
|
||||
The name of the StateType ({3b00470d-11bc-4352-a885-52ab9443cee5}) of ThingClass senecStorageLan</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="128"/>
|
||||
<source>Current power phase A</source>
|
||||
<extracomment>The name of the StateType ({385df15e-4f1d-4842-a10a-597d087bdac0}) of ThingClass senecMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="131"/>
|
||||
<source>Current power phase B</source>
|
||||
<extracomment>The name of the StateType ({3bab2b05-99d7-4b3a-b903-054d269a3527}) of ThingClass senecMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="134"/>
|
||||
<source>Current power phase C</source>
|
||||
<extracomment>The name of the StateType ({01a63c94-b635-4e98-8037-d5d69fe0dd7c}) of ThingClass senecMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="137"/>
|
||||
<source>Current power usage</source>
|
||||
<extracomment>The name of the StateType ({56e58d03-782d-40bb-8d32-fad396ffefdc}) of ThingClass senecMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="140"/>
|
||||
<source>Energy consumed</source>
|
||||
<extracomment>The name of the StateType ({21728bf0-a566-4eb0-a163-5f108f34bb9b}) of ThingClass senecMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="143"/>
|
||||
<source>Energy returned</source>
|
||||
<extracomment>The name of the StateType ({f9920cfa-26a0-4247-ad50-b4fca91596f0}) of ThingClass senecMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="146"/>
|
||||
<source>Host address</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: senecStorageLan, Type: thing, ID: {a45cabe9-9f76-405e-b47c-0a3d00bdd44b})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="149"/>
|
||||
<source>Host name</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: senecStorageLan, Type: thing, ID: {51a1bbc0-28a5-43ac-9021-896acbf9413f})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="152"/>
|
||||
<source>Logged in</source>
|
||||
<extracomment>The name of the StateType ({73c4da2a-2167-4b68-8fbf-d03f5b7920a5}) of ThingClass senecConnect</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="155"/>
|
||||
<source>MAC address</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: senecStorageLan, Type: thing, ID: {0790e1b1-c8c9-488f-9cda-2a0bbf14b4af})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="164"/>
|
||||
<source>SENEC Meter</source>
|
||||
<extracomment>The name of the ThingClass ({303f454d-93b7-4f8f-b295-a4c22e09be6d})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="167"/>
|
||||
<source>SENEC PV inverter</source>
|
||||
<extracomment>The name of the ThingClass ({12a613fb-f33c-48b1-a9a4-caaee1041d25})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="170"/>
|
||||
<source>SENEC.Connect</source>
|
||||
<extracomment>The name of the ThingClass ({d064f111-1332-4096-b5de-529d44f72963})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="176"/>
|
||||
<source>SENEC.Home storage (SENEC.Connect)</source>
|
||||
<extracomment>The name of the ThingClass ({8de0eeca-a698-4aee-bc67-a586c8f10652})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="179"/>
|
||||
<source>Serial number</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: senecStorageCloud, Type: thing, ID: {5d1d61dd-a419-4ba5-992a-91e9a3f34d31})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="182"/>
|
||||
<source>System ID</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: senecStorageCloud, Type: thing, ID: {9172116e-e513-429b-b9a0-c73d7587bfa8})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="185"/>
|
||||
<source>Total energy produced</source>
|
||||
<extracomment>The name of the StateType ({e5a22d6c-573a-46df-ba92-3d33bd0ad754}) of ThingClass senecInverter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="188"/>
|
||||
<source>Voltage phase A</source>
|
||||
<extracomment>The name of the StateType ({db469573-bb11-48d2-84ba-2b8ff4b13413}) of ThingClass senecMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="191"/>
|
||||
<source>Voltage phase B</source>
|
||||
<extracomment>The name of the StateType ({176d5a01-220d-44da-b875-9a40aaa9c85a}) of ThingClass senecMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="194"/>
|
||||
<source>Voltage phase C</source>
|
||||
<extracomment>The name of the StateType ({e7ab02d3-016a-44ca-8da1-5d7ac9acbc6d}) of ThingClass senecMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="158"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="161"/>
|
||||
<source>SENEC</source>
|
||||
<extracomment>The name of the vendor ({1be922b9-4439-42cf-9caa-67f2ac7cc425})
|
||||
----------
|
||||
The name of the plugin Senec ({3f055ea5-a883-445d-9556-675f5eda6c9a})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="173"/>
|
||||
<source>SENEC.Home storage</source>
|
||||
<extracomment>The name of the ThingClass ({345df3b1-d411-4db5-bbb2-3b14eb86c1ba})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="197"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="200"/>
|
||||
<source>charging</source>
|
||||
<extracomment>The name of a possible value of StateType {8db60ec4-06cd-4fc3-a66e-ced915bf5c11} of ThingClass senecStorageCloud
|
||||
----------
|
||||
The name of a possible value of StateType {339f602f-c9f1-446d-bc96-1c7f9a7db922} of ThingClass senecStorageLan</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="203"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="206"/>
|
||||
<source>discharging</source>
|
||||
<extracomment>The name of a possible value of StateType {8db60ec4-06cd-4fc3-a66e-ced915bf5c11} of ThingClass senecStorageCloud
|
||||
----------
|
||||
The name of a possible value of StateType {339f602f-c9f1-446d-bc96-1c7f9a7db922} of ThingClass senecStorageLan</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="209"/>
|
||||
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="212"/>
|
||||
<source>idle</source>
|
||||
<extracomment>The name of a possible value of StateType {8db60ec4-06cd-4fc3-a66e-ced915bf5c11} of ThingClass senecStorageCloud
|
||||
----------
|
||||
The name of a possible value of StateType {339f602f-c9f1-446d-bc96-1c7f9a7db922} of ThingClass senecStorageLan</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
Loading…
x
Reference in New Issue
Block a user