Add senec meter option and add capacity setting

This commit is contained in:
Simon Stürz 2025-07-14 16:56:57 +02:00
parent e9a0fe1f08
commit 1f0733db97
5 changed files with 294 additions and 55 deletions

View File

@ -126,6 +126,32 @@ void IntegrationPluginSenec::setupThing(ThingSetupInfo *info)
thing->setStateValue(senecAccountUserDisplayNameStateTypeId, username);
} else if (thing->thingClassId() == senecStorageThingClassId) {
connect(thing, &Thing::settingChanged, this, [this, thing](const ParamTypeId &paramTypeId, const QVariant &value){
if (paramTypeId == senecStorageSettingsCapacityParamTypeId) {
thing->setStateValue(senecStorageCapacityStateTypeId, value.toDouble());
} else if (paramTypeId == senecStorageSettingsAddMeterParamTypeId) {
if (value.toBool()) {
// Check if we have to add the meter
if (myThings().filterByThingClassId(senecMeterThingClassId).filterByParentId(thing->id()).isEmpty()) {
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
Things existingMeters = myThings().filterByThingClassId(senecMeterThingClassId).filterByParentId(thing->id());
if (!existingMeters.isEmpty()) {
qCDebug(dcSenec()) << "Remove meter thing for" << thing->name();
emit autoThingDisappeared(existingMeters.takeFirst()->id());
}
}
}
});
info->finish(Thing::ThingErrorNoError);
} else if (thing->thingClassId() == senecMeterThingClassId) {
info->finish(Thing::ThingErrorNoError);
}
}
@ -201,8 +227,11 @@ void IntegrationPluginSenec::postSetupThing(Thing *thing)
});
} else if (thing->thingClassId() == senecStorageThingClassId) {
thing->setStateValue(senecStorageCapacityStateTypeId, thing->setting(senecStorageSettingsCapacityParamTypeId).toDouble());
SenecAccount *account = m_accounts.value(myThings().findById(thing->parentId()));
QString id = thing->paramValue(senecStorageThingIdParamTypeId).toString();
QNetworkReply *reply = account->getTechnicalData(id);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, account, [reply, thing, this] {
@ -269,60 +298,106 @@ void IntegrationPluginSenec::executeAction(ThingActionInfo *info)
void IntegrationPluginSenec::refresh(Thing *thing)
{
if (thing) {
SenecAccount *account = m_accounts.value(myThings().findById(thing->parentId()));
QString id = thing->paramValue(senecStorageThingIdParamTypeId).toString();
QNetworkReply *reply = account->getDashboard(id);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, account, [reply, thing] {
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
qCWarning(dcSenec()) << "Dashboard request finished with error. Status:" << status << "Error:" << reply->errorString();
thing->setStateValue(senecStorageConnectedStateTypeId, 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()) << "Dashboard request finished successfully, but the response contains invalid JSON object:" << responseData;
return;
}
qCDebug(dcSenec()) << "Dashboard request finished successfully" << qUtf8Printable(jsonDoc.toJson());
thing->setStateValue(senecStorageConnectedStateTypeId, true);
QVariantMap currentDataMap = responseMap.value("currently").toMap();
float batteryCharge = qRound(currentDataMap.value("batteryChargeInW").toFloat() * 100) / 100.0;
float batteryDischarge = qRound(currentDataMap.value("batteryDischargeInW").toFloat() * 100) / 100.0;
int batteryLevel = currentDataMap.value("batteryLevelInPercent").toInt();
// qCDebug(dcSenec()) << "charge:" << batteryCharge << "W" << "discharge:" << batteryDischarge << "W" << "level" << batteryLevel << "%";
float currentPower = 0;
if (batteryCharge != 0) {
currentPower = batteryCharge;
thing->setStateValue(senecStorageChargingStateStateTypeId, "charging");
} else if (batteryDischarge != 0) {
currentPower = -batteryDischarge;
thing->setStateValue(senecStorageChargingStateStateTypeId, "discharging");
} else {
thing->setStateValue(senecStorageChargingStateStateTypeId, "idle");
}
thing->setStateValue(senecStorageCurrentPowerStateTypeId, currentPower);
thing->setStateValue(senecStorageBatteryLevelStateTypeId, batteryLevel);
thing->setStateValue(senecStorageBatteryCriticalStateTypeId, batteryLevel < 10);
});
} else {
// If no thing given, refresh all storages recursive
if (!thing) {
foreach (Thing *storageThing, myThings().filterByThingClassId(senecStorageThingClassId)) {
refresh(storageThing);
}
return;
}
Thing *parentThing = myThings().findById(thing->parentId());
SenecAccount *account = m_accounts.value(parentThing);
QString id = thing->paramValue(senecStorageThingIdParamTypeId).toString();
QNetworkReply *reply = account->getDashboard(id);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, account, [reply, thing, this] {
// Check if we have a meter
Thing *meterThing = nullptr;
Things meterThings = myThings().filterByThingClassId(senecMeterThingClassId).filterByParentId(thing->id());
if (!meterThings.isEmpty()) {
meterThing = meterThings.first();
}
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
qCWarning(dcSenec()) << "Dashboard request finished with error. Status:" << status << "Error:" << reply->errorString();
thing->setStateValue(senecStorageConnectedStateTypeId, false);
if (meterThing) {
meterThing->setStateValue(senecMeterConnectedStateTypeId, 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()) << "Dashboard request finished successfully, but the response contains invalid JSON object:" << responseData;
return;
}
qCDebug(dcSenec()) << "Dashboard request finished successfully" << qUtf8Printable(jsonDoc.toJson());
thing->setStateValue(senecStorageConnectedStateTypeId, true);
QVariantMap currentDataMap = responseMap.value("currently").toMap();
float batteryCharge = qRound(currentDataMap.value("batteryChargeInW").toFloat() * 100) / 100.0;
float batteryDischarge = qRound(currentDataMap.value("batteryDischargeInW").toFloat() * 100) / 100.0;
int batteryLevel = currentDataMap.value("batteryLevelInPercent").toInt();
// qCDebug(dcSenec()) << "charge:" << batteryCharge << "W" << "discharge:" << batteryDischarge << "W" << "level" << batteryLevel << "%";
// Note: there are some situations where the battery is charging and discharging at the same time.
// In that case we use the bigger power. Maybe we cloudl als sum them up, that should be tested...
float currentPower = 0;
if (batteryCharge != 0 && batteryCharge != 0) {
if (batteryCharge > batteryDischarge) {
currentPower = batteryCharge;
} else {
currentPower = -batteryDischarge;
}
} else if (batteryCharge != 0) {
currentPower = batteryCharge;
} else if (batteryDischarge != 0) {
currentPower = -batteryDischarge;
}
if (currentPower > 0) {
thing->setStateValue(senecStorageChargingStateStateTypeId, "charging");
} else if (currentPower < 0) {
thing->setStateValue(senecStorageChargingStateStateTypeId, "discharging");
} else {
thing->setStateValue(senecStorageChargingStateStateTypeId, "idle");
}
thing->setStateValue(senecStorageCurrentPowerStateTypeId, currentPower);
thing->setStateValue(senecStorageBatteryLevelStateTypeId, batteryLevel);
thing->setStateValue(senecStorageBatteryCriticalStateTypeId, batteryLevel < 10);
// Check if we have a meter
if (meterThing) {
float gridConsume = qRound(currentDataMap.value("gridDrawInW").toFloat() * 100) / 100.0;
float gridReturn = qRound(currentDataMap.value("gridFeedInInW").toFloat() * 100) / 100.0;
qCDebug(dcSenec()) << "Grid power: consume" << gridConsume << "W" << "return:" << gridReturn << "W";
double currentPower = 0;
if (gridConsume != 0) {
currentPower = gridConsume;
} else if (gridReturn != 0) {
currentPower = -gridReturn;
}
meterThing->setStateValue(senecMeterCurrentPowerStateTypeId, currentPower);
meterThing->setStateValue(senecMeterConnectedStateTypeId, true);
}
});
}

View File

@ -46,6 +46,25 @@
"id": "a983c5ee-1c58-4cad-87fb-1bc612cbe6e4",
"createMethods": [ "Auto" ],
"interfaces": ["energystorage", "connectable"],
"settingsTypes": [
{
"id": "44836ff6-4d69-42b9-a5a8-b37bcc0b24fe",
"name": "capacity",
"displayName": "Capacity",
"type": "double",
"minValue": 7.1,
"maxValue": 17.75,
"unit": "KiloWattHour",
"defaultValue": 0.0
},
{
"id": "0a16f7c8-2e55-4869-aeb3-574f22b40527",
"name":"addMeter",
"displayName": "Add meter",
"type": "bool",
"defaultValue": false
}
],
"paramTypes": [
{
"id": "9a0fa7b1-bc35-44d4-b987-5ecb87fd8b00",
@ -56,7 +75,7 @@
"defaultValue": ""
}
],
"stateTypes":[
"stateTypes": [
{
"id": "320cb3f0-d2c5-4a30-817f-474f0cc87253",
"name": "connected",
@ -108,6 +127,129 @@
"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
}
]
}
]
}

View File

@ -4,9 +4,11 @@ QT+= network
SOURCES += \
integrationpluginsenec.cpp \
senecaccount.cpp
senecaccount.cpp \
seneclanstorage.cpp
HEADERS += \
integrationpluginsenec.h \
senecaccount.h
senecaccount.h \
seneclanstorage.h

View File

@ -0,0 +1,5 @@
#include "seneclanstorage.h"
SenecLanStorage::SenecLanStorage(QObject *parent)
: QObject{parent}
{}

15
senec/seneclanstorage.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef SENECLANSTORAGE_H
#define SENECLANSTORAGE_H
#include <QObject>
class SenecLanStorage : public QObject
{
Q_OBJECT
public:
explicit SenecLanStorage(QObject *parent = nullptr);
signals:
};
#endif // SENECLANSTORAGE_H