New plugin: Senec: Add support for SENEC.home V2 and V3 energy storages

This commit is contained in:
Simon Stürz 2025-07-08 17:08:11 +02:00
parent 64b64f26d3
commit 8f72d83369
17 changed files with 1692 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

@ -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 \

11
senec/README.md Normal file
View File

@ -0,0 +1,11 @@
# SENEC.Home
Connects to SENEC.Home energy storages in the local network and integrates them into the system. Optionally the grid meter of the storage can be added.
Currently supported and tested models:
* SENEC.Home P4
## More
https://senec.com

View File

@ -0,0 +1,259 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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::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 = meterThing(thing);
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 (!meterThing(thing)) {
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 = meterThing(thing);
if (meter) {
qCDebug(dcSenec()) << "Remove meter thing for" << thing->name();
emit autoThingDisappeared(meter->id());
}
}
}
});
info->finish(Thing::ThingErrorNoError);
} else if (thing->thingClassId() == senecMeterThingClassId) {
Thing *parentThing = myThings().findById(thing->parentId());
if (parentThing) {
thing->setStateValue(senecMeterConnectedStateTypeId, parentThing->stateValue(senecStorageLanConnectedStateTypeId).toBool());
}
info->finish(Thing::ThingErrorNoError);
}
}
void IntegrationPluginSenec::postSetupThing(Thing *thing)
{
if (thing->thingClassId() == senecStorageLanThingClassId) {
m_storages.value(thing)->initialize();
}
// Create the refresh timer if not already set up
if (!m_refreshTimer) {
qCDebug(dcSenec()) << "Starting refresh timer ...";
m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(5);
connect(m_refreshTimer, &PluginTimer::timeout, this, &IntegrationPluginSenec::refresh);
m_refreshTimer->start();
}
}
void IntegrationPluginSenec::thingRemoved(Thing *thing)
{
if (thing->thingClassId() == senecStorageLanThingClassId) {
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
m_storages.take(thing)->deleteLater();
}
if (myThings().isEmpty()) {
qCDebug(dcSenec()) << "Stopping refresh timer";
hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer);
m_refreshTimer = nullptr;
}
}
Thing *IntegrationPluginSenec::meterThing(Thing *storageThing) const
{
Things meterThings = myThings().filterByThingClassId(senecMeterThingClassId).filterByParentId(storageThing->id());
if (meterThings.isEmpty())
return nullptr;
return meterThings.first();
}
void IntegrationPluginSenec::setConnected(Thing *storageThing, bool connected)
{
storageThing->setStateValue(senecStorageLanConnectedStateTypeId, connected);
Thing *meter = meterThing(storageThing);
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();
}
}
}

View File

@ -0,0 +1,75 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 "senecstoragelan.h"
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 setupThing(ThingSetupInfo *info) override;
void postSetupThing(Thing *thing) override;
void thingRemoved(Thing *thing) override;
private:
PluginTimer *m_refreshTimer = nullptr;
QHash<Thing *, NetworkDeviceMonitor *> m_monitors;
QHash<Thing *, SenecStorageLan *> m_storages;
Thing *meterThing(Thing *storageThing) const;
void setConnected(Thing *storageThing, bool connected);
private slots:
void refresh();
};
#endif // INTEGRATIONPLUGINSENEC_H

View File

@ -0,0 +1,232 @@
{
"displayName": "SENEC",
"name": "Senec",
"id": "3f055ea5-a883-445d-9556-675f5eda6c9a",
"vendors": [
{
"displayName": "SENEC",
"name": "senec",
"id": "1be922b9-4439-42cf-9caa-67f2ac7cc425",
"thingClasses": [
{
"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
}
]
},
{
"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
}
]
}
]
}
]
}

13
senec/meta.json Normal file
View File

@ -0,0 +1,13 @@
{
"title": "SENEC",
"tagline": "Integrate SENEC.Home energy storages from the local network.",
"icon": "senec.svg",
"stability": "community",
"offline": true,
"technologies": [
"network"
],
"categories": [
"energy"
]
}

14
senec/senec.pro Normal file
View File

@ -0,0 +1,14 @@
include(../plugins.pri)
QT+= network
SOURCES += \
integrationpluginsenec.cpp \
senecdiscovery.cpp \
senecstoragelan.cpp
HEADERS += \
integrationpluginsenec.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

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,189 @@
<?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>
</context>
<context>
<name>Senec</name>
<message>
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="52"/>
<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="55"/>
<source>Battery critical</source>
<extracomment>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="58"/>
<source>Battery level</source>
<extracomment>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="61"/>
<source>Capacity</source>
<extracomment>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="64"/>
<source>Charging state</source>
<extracomment>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="67"/>
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="70"/>
<source>Connected</source>
<extracomment>The name of the StateType ({9aa4dbfb-255b-408b-b148-8fb145ba8c98}) of ThingClass senecMeter
----------
The name of the StateType ({d3bdc64b-67fb-4d84-97ed-c5142e5db55d}) of ThingClass senecStorageLan</extracomment>
<translation>Verbunden</translation>
</message>
<message>
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="73"/>
<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="76"/>
<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="79"/>
<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="82"/>
<source>Current power</source>
<extracomment>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="85"/>
<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="88"/>
<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="91"/>
<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="94"/>
<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="97"/>
<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="100"/>
<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="103"/>
<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="106"/>
<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="109"/>
<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="118"/>
<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="124"/>
<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="127"/>
<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="130"/>
<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="112"/>
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="115"/>
<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="121"/>
<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="133"/>
<source>charging</source>
<extracomment>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="136"/>
<source>discharging</source>
<extracomment>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="139"/>
<source>idle</source>
<extracomment>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,189 @@
<?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>
</context>
<context>
<name>Senec</name>
<message>
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="52"/>
<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="55"/>
<source>Battery critical</source>
<extracomment>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="58"/>
<source>Battery level</source>
<extracomment>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="61"/>
<source>Capacity</source>
<extracomment>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="64"/>
<source>Charging state</source>
<extracomment>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="67"/>
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="70"/>
<source>Connected</source>
<extracomment>The name of the StateType ({9aa4dbfb-255b-408b-b148-8fb145ba8c98}) of ThingClass senecMeter
----------
The name of the StateType ({d3bdc64b-67fb-4d84-97ed-c5142e5db55d}) of ThingClass senecStorageLan</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="73"/>
<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="76"/>
<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="79"/>
<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="82"/>
<source>Current power</source>
<extracomment>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="85"/>
<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="88"/>
<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="91"/>
<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="94"/>
<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="97"/>
<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="100"/>
<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="103"/>
<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="106"/>
<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="109"/>
<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="118"/>
<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="124"/>
<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="127"/>
<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="130"/>
<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="112"/>
<location filename="../../build/Desktop_Qt_6_11_1_Debug/senec/plugininfo.h" line="115"/>
<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="121"/>
<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="133"/>
<source>charging</source>
<extracomment>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="136"/>
<source>discharging</source>
<extracomment>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="139"/>
<source>idle</source>
<extracomment>The name of a possible value of StateType {339f602f-c9f1-446d-bc96-1c7f9a7db922} of ThingClass senecStorageLan</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
</TS>