diff --git a/debian-qt5/control b/debian-qt5/control
index 49515d2f..99eb27e1 100644
--- a/debian-qt5/control
+++ b/debian-qt5/control
@@ -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},
diff --git a/debian-qt5/nymea-plugin-senec.install.in b/debian-qt5/nymea-plugin-senec.install.in
new file mode 100644
index 00000000..ef2a33b4
--- /dev/null
+++ b/debian-qt5/nymea-plugin-senec.install.in
@@ -0,0 +1,2 @@
+usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginsenec.so
+senec/translations/*qm usr/share/nymea/translations/
diff --git a/debian-qt6/nymea-plugin-senec.install.in b/debian-qt6/nymea-plugin-senec.install.in
new file mode 100644
index 00000000..ef2a33b4
--- /dev/null
+++ b/debian-qt6/nymea-plugin-senec.install.in
@@ -0,0 +1,2 @@
+usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginsenec.so
+senec/translations/*qm usr/share/nymea/translations/
diff --git a/nymea-plugins.pro b/nymea-plugins.pro
index 459ef528..0d32c124 100644
--- a/nymea-plugins.pro
+++ b/nymea-plugins.pro
@@ -56,6 +56,7 @@ PLUGIN_DIRS = \
pushbullet \
pushnotifications \
reversessh \
+ senec \
senic \
serialportcommander \
sgready \
diff --git a/senec/README.md b/senec/README.md
new file mode 100644
index 00000000..eba44c7e
--- /dev/null
+++ b/senec/README.md
@@ -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
diff --git a/senec/integrationpluginsenec.cpp b/senec/integrationpluginsenec.cpp
new file mode 100644
index 00000000..bdfa0b7d
--- /dev/null
+++ b/senec/integrationpluginsenec.cpp
@@ -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 .
+*
+* 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
+
+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 ¶mTypeId, 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();
+ }
+ }
+}
diff --git a/senec/integrationpluginsenec.h b/senec/integrationpluginsenec.h
new file mode 100644
index 00000000..6a45f905
--- /dev/null
+++ b/senec/integrationpluginsenec.h
@@ -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 .
+*
+* 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
+#include
+
+#include
+#include
+
+#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 m_monitors;
+ QHash m_storages;
+
+ Thing *meterThing(Thing *storageThing) const;
+ void setConnected(Thing *storageThing, bool connected);
+
+private slots:
+ void refresh();
+
+};
+
+#endif // INTEGRATIONPLUGINSENEC_H
diff --git a/senec/integrationpluginsenec.json b/senec/integrationpluginsenec.json
new file mode 100644
index 00000000..0bf83dbe
--- /dev/null
+++ b/senec/integrationpluginsenec.json
@@ -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
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/senec/meta.json b/senec/meta.json
new file mode 100644
index 00000000..46766d5a
--- /dev/null
+++ b/senec/meta.json
@@ -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"
+ ]
+}
diff --git a/senec/senec.pro b/senec/senec.pro
new file mode 100644
index 00000000..90c5d150
--- /dev/null
+++ b/senec/senec.pro
@@ -0,0 +1,14 @@
+include(../plugins.pri)
+
+QT+= network
+
+SOURCES += \
+ integrationpluginsenec.cpp \
+ senecdiscovery.cpp \
+ senecstoragelan.cpp
+
+HEADERS += \
+ integrationpluginsenec.h \
+ senecdiscovery.h \
+ senecstoragelan.h
+
diff --git a/senec/senec.svg b/senec/senec.svg
new file mode 100644
index 00000000..f179ab31
--- /dev/null
+++ b/senec/senec.svg
@@ -0,0 +1,40 @@
+
+
diff --git a/senec/senecdiscovery.cpp b/senec/senecdiscovery.cpp
new file mode 100644
index 00000000..9166653a
--- /dev/null
+++ b/senec/senecdiscovery.cpp
@@ -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 .
+*
+* 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::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();
+}
diff --git a/senec/senecdiscovery.h b/senec/senecdiscovery.h
new file mode 100644
index 00000000..6bfd99d6
--- /dev/null
+++ b/senec/senecdiscovery.h
@@ -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 .
+*
+* 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
+#include
+
+#include
+#include
+
+#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 results() const;
+
+signals:
+ void discoveryFinished();
+
+private:
+ NetworkAccessManager *m_networkManager = nullptr;
+ NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr;
+
+ QTimer m_gracePeriodTimer;
+ QDateTime m_startDateTime;
+
+ NetworkDeviceInfos m_networkDeviceInfos;
+ QList m_storages;
+ QList m_results;
+
+ void checkNetworkDevice(const QHostAddress &address);
+ void cleanupStorage(SenecStorageLan *storage);
+
+ void finishDiscovery();
+
+};
+
+#endif // SENECDISCOVERY_H
diff --git a/senec/senecstoragelan.cpp b/senec/senecstoragelan.cpp
new file mode 100644
index 00000000..6c914ed0
--- /dev/null
+++ b/senec/senecstoragelan.cpp
@@ -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 .
+*
+* 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
+#include
+#include
+
+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 &errors)
+{
+ qCDebug(dcSenec()) << "SSL errors occurred" << errors << "but we are ignoring them in the LAN environment...";
+ QNetworkReply *reply = qobject_cast(sender());
+ reply->ignoreSslErrors();
+}
diff --git a/senec/senecstoragelan.h b/senec/senecstoragelan.h
new file mode 100644
index 00000000..e594d047
--- /dev/null
+++ b/senec/senecstoragelan.h
@@ -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 .
+*
+* 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
+#include
+#include
+
+#include
+
+// 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 &errors);
+
+};
+
+#endif // SENECSTORAGELAN_H
diff --git a/senec/translations/3f055ea5-a883-445d-9556-675f5eda6c9a-de.ts b/senec/translations/3f055ea5-a883-445d-9556-675f5eda6c9a-de.ts
new file mode 100644
index 00000000..f8061430
--- /dev/null
+++ b/senec/translations/3f055ea5-a883-445d-9556-675f5eda6c9a-de.ts
@@ -0,0 +1,189 @@
+
+
+
+
+ IntegrationPluginSenec
+
+
+ Unable to discover devices in your network.
+ Geräte im Netzwerk können nicht gesucht werden.
+
+
+
+ Senec
+
+
+ Add meter
+ The name of the ParamType (ThingClass: senecStorageLan, Type: settings, ID: {239d388c-f4a2-4055-88c3-decc6dace2b8})
+ Zähler hinzufügen
+
+
+
+ Battery critical
+ The name of the StateType ({a9aa13fb-3f51-40e3-9d29-ab882513a87a}) of ThingClass senecStorageLan
+ Batterieladung kritisch
+
+
+
+ Battery level
+ The name of the StateType ({66a3a7d2-1210-4891-a8f2-6fe4f9b8c6cc}) of ThingClass senecStorageLan
+ Ladestand
+
+
+
+ Capacity
+ The name of the StateType ({d927e6cf-29f0-412f-b74d-7d803fd3e191}) of ThingClass senecStorageLan
+ Kapazität
+
+
+
+ Charging state
+ The name of the StateType ({339f602f-c9f1-446d-bc96-1c7f9a7db922}) of ThingClass senecStorageLan
+ Ladezustand
+
+
+
+
+ Connected
+ 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
+ Verbunden
+
+
+
+ Current phase A
+ The name of the StateType ({1923f7b0-5b54-46d8-a7e9-9364b7c9eeb8}) of ThingClass senecMeter
+ Strom Phase A
+
+
+
+ Current phase B
+ The name of the StateType ({4908abf8-f655-4e14-a007-3eefeda7088e}) of ThingClass senecMeter
+ Strom Phase B
+
+
+
+ Current phase C
+ The name of the StateType ({e8415152-0fac-4cd9-98ac-b0e7c006d637}) of ThingClass senecMeter
+ Strom Phase C
+
+
+
+ Current power
+ The name of the StateType ({3b00470d-11bc-4352-a885-52ab9443cee5}) of ThingClass senecStorageLan
+ Aktuelle Leistung
+
+
+
+ Current power phase A
+ The name of the StateType ({385df15e-4f1d-4842-a10a-597d087bdac0}) of ThingClass senecMeter
+ Leistung Phase A
+
+
+
+ Current power phase B
+ The name of the StateType ({3bab2b05-99d7-4b3a-b903-054d269a3527}) of ThingClass senecMeter
+ Leistung Phase B
+
+
+
+ Current power phase C
+ The name of the StateType ({01a63c94-b635-4e98-8037-d5d69fe0dd7c}) of ThingClass senecMeter
+ Leistung Phase C
+
+
+
+ Current power usage
+ The name of the StateType ({56e58d03-782d-40bb-8d32-fad396ffefdc}) of ThingClass senecMeter
+ Aktuelle Leistung
+
+
+
+ Energy consumed
+ The name of the StateType ({21728bf0-a566-4eb0-a163-5f108f34bb9b}) of ThingClass senecMeter
+ Bezogene Energie
+
+
+
+ Energy returned
+ The name of the StateType ({f9920cfa-26a0-4247-ad50-b4fca91596f0}) of ThingClass senecMeter
+ Eingespeiste Energie
+
+
+
+ Host address
+ The name of the ParamType (ThingClass: senecStorageLan, Type: thing, ID: {a45cabe9-9f76-405e-b47c-0a3d00bdd44b})
+ IP-Adresse
+
+
+
+ Host name
+ The name of the ParamType (ThingClass: senecStorageLan, Type: thing, ID: {51a1bbc0-28a5-43ac-9021-896acbf9413f})
+ Hostname
+
+
+
+ MAC address
+ The name of the ParamType (ThingClass: senecStorageLan, Type: thing, ID: {0790e1b1-c8c9-488f-9cda-2a0bbf14b4af})
+ MAC-Adresse
+
+
+
+ SENEC Meter
+ The name of the ThingClass ({303f454d-93b7-4f8f-b295-a4c22e09be6d})
+ SENEC Zähler
+
+
+
+ Voltage phase A
+ The name of the StateType ({db469573-bb11-48d2-84ba-2b8ff4b13413}) of ThingClass senecMeter
+ Spannung Phase A
+
+
+
+ Voltage phase B
+ The name of the StateType ({176d5a01-220d-44da-b875-9a40aaa9c85a}) of ThingClass senecMeter
+ Spannung Phase B
+
+
+
+ Voltage phase C
+ The name of the StateType ({e7ab02d3-016a-44ca-8da1-5d7ac9acbc6d}) of ThingClass senecMeter
+ Spannung Phase C
+
+
+
+
+ SENEC
+ The name of the vendor ({1be922b9-4439-42cf-9caa-67f2ac7cc425})
+----------
+The name of the plugin Senec ({3f055ea5-a883-445d-9556-675f5eda6c9a})
+ SENEC
+
+
+
+ SENEC.Home storage
+ The name of the ThingClass ({345df3b1-d411-4db5-bbb2-3b14eb86c1ba})
+ SENEC.Home Speicher
+
+
+
+ charging
+ The name of a possible value of StateType {339f602f-c9f1-446d-bc96-1c7f9a7db922} of ThingClass senecStorageLan
+ lade
+
+
+
+ discharging
+ The name of a possible value of StateType {339f602f-c9f1-446d-bc96-1c7f9a7db922} of ThingClass senecStorageLan
+ entlade
+
+
+
+ idle
+ The name of a possible value of StateType {339f602f-c9f1-446d-bc96-1c7f9a7db922} of ThingClass senecStorageLan
+ leerlauf
+
+
+
diff --git a/senec/translations/3f055ea5-a883-445d-9556-675f5eda6c9a-en_US.ts b/senec/translations/3f055ea5-a883-445d-9556-675f5eda6c9a-en_US.ts
new file mode 100644
index 00000000..56f9b800
--- /dev/null
+++ b/senec/translations/3f055ea5-a883-445d-9556-675f5eda6c9a-en_US.ts
@@ -0,0 +1,189 @@
+
+
+
+
+ IntegrationPluginSenec
+
+
+ Unable to discover devices in your network.
+
+
+
+
+ Senec
+
+
+ Add meter
+ The name of the ParamType (ThingClass: senecStorageLan, Type: settings, ID: {239d388c-f4a2-4055-88c3-decc6dace2b8})
+
+
+
+
+ Battery critical
+ The name of the StateType ({a9aa13fb-3f51-40e3-9d29-ab882513a87a}) of ThingClass senecStorageLan
+
+
+
+
+ Battery level
+ The name of the StateType ({66a3a7d2-1210-4891-a8f2-6fe4f9b8c6cc}) of ThingClass senecStorageLan
+
+
+
+
+ Capacity
+ The name of the StateType ({d927e6cf-29f0-412f-b74d-7d803fd3e191}) of ThingClass senecStorageLan
+
+
+
+
+ Charging state
+ The name of the StateType ({339f602f-c9f1-446d-bc96-1c7f9a7db922}) of ThingClass senecStorageLan
+
+
+
+
+
+ Connected
+ 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
+
+
+
+
+ Current phase A
+ The name of the StateType ({1923f7b0-5b54-46d8-a7e9-9364b7c9eeb8}) of ThingClass senecMeter
+
+
+
+
+ Current phase B
+ The name of the StateType ({4908abf8-f655-4e14-a007-3eefeda7088e}) of ThingClass senecMeter
+
+
+
+
+ Current phase C
+ The name of the StateType ({e8415152-0fac-4cd9-98ac-b0e7c006d637}) of ThingClass senecMeter
+
+
+
+
+ Current power
+ The name of the StateType ({3b00470d-11bc-4352-a885-52ab9443cee5}) of ThingClass senecStorageLan
+
+
+
+
+ Current power phase A
+ The name of the StateType ({385df15e-4f1d-4842-a10a-597d087bdac0}) of ThingClass senecMeter
+
+
+
+
+ Current power phase B
+ The name of the StateType ({3bab2b05-99d7-4b3a-b903-054d269a3527}) of ThingClass senecMeter
+
+
+
+
+ Current power phase C
+ The name of the StateType ({01a63c94-b635-4e98-8037-d5d69fe0dd7c}) of ThingClass senecMeter
+
+
+
+
+ Current power usage
+ The name of the StateType ({56e58d03-782d-40bb-8d32-fad396ffefdc}) of ThingClass senecMeter
+
+
+
+
+ Energy consumed
+ The name of the StateType ({21728bf0-a566-4eb0-a163-5f108f34bb9b}) of ThingClass senecMeter
+
+
+
+
+ Energy returned
+ The name of the StateType ({f9920cfa-26a0-4247-ad50-b4fca91596f0}) of ThingClass senecMeter
+
+
+
+
+ Host address
+ The name of the ParamType (ThingClass: senecStorageLan, Type: thing, ID: {a45cabe9-9f76-405e-b47c-0a3d00bdd44b})
+
+
+
+
+ Host name
+ The name of the ParamType (ThingClass: senecStorageLan, Type: thing, ID: {51a1bbc0-28a5-43ac-9021-896acbf9413f})
+
+
+
+
+ MAC address
+ The name of the ParamType (ThingClass: senecStorageLan, Type: thing, ID: {0790e1b1-c8c9-488f-9cda-2a0bbf14b4af})
+
+
+
+
+ SENEC Meter
+ The name of the ThingClass ({303f454d-93b7-4f8f-b295-a4c22e09be6d})
+
+
+
+
+ Voltage phase A
+ The name of the StateType ({db469573-bb11-48d2-84ba-2b8ff4b13413}) of ThingClass senecMeter
+
+
+
+
+ Voltage phase B
+ The name of the StateType ({176d5a01-220d-44da-b875-9a40aaa9c85a}) of ThingClass senecMeter
+
+
+
+
+ Voltage phase C
+ The name of the StateType ({e7ab02d3-016a-44ca-8da1-5d7ac9acbc6d}) of ThingClass senecMeter
+
+
+
+
+
+ SENEC
+ The name of the vendor ({1be922b9-4439-42cf-9caa-67f2ac7cc425})
+----------
+The name of the plugin Senec ({3f055ea5-a883-445d-9556-675f5eda6c9a})
+
+
+
+
+ SENEC.Home storage
+ The name of the ThingClass ({345df3b1-d411-4db5-bbb2-3b14eb86c1ba})
+
+
+
+
+ charging
+ The name of a possible value of StateType {339f602f-c9f1-446d-bc96-1c7f9a7db922} of ThingClass senecStorageLan
+
+
+
+
+ discharging
+ The name of a possible value of StateType {339f602f-c9f1-446d-bc96-1c7f9a7db922} of ThingClass senecStorageLan
+
+
+
+
+ idle
+ The name of a possible value of StateType {339f602f-c9f1-446d-bc96-1c7f9a7db922} of ThingClass senecStorageLan
+
+
+
+