Merge PR #771: New plugin: Senec: Add support for SENEC.home energy storages

This commit is contained in:
jenkins 2026-10-26 17:12:23 +01:00
commit 6ca74d0bb6
20 changed files with 2512 additions and 0 deletions

View File

@ -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},

View File

@ -0,0 +1,2 @@
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginsenec.so
senec/translations/*qm usr/share/nymea/translations/

View File

@ -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},

View File

@ -0,0 +1,2 @@
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginsenec.so
senec/translations/*qm usr/share/nymea/translations/

View File

@ -56,6 +56,7 @@ PLUGIN_DIRS = \
pushbullet \
pushnotifications \
reversessh \
senec \
senic \
serialportcommander \
sgready \

21
senec/README.md Normal file
View 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

View 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 &paramTypeId, 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);
}
}

View 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

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

View 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>

View 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>