diff --git a/senec/integrationpluginsenec.cpp b/senec/integrationpluginsenec.cpp index 566c6a44..7a4af96f 100644 --- a/senec/integrationpluginsenec.cpp +++ b/senec/integrationpluginsenec.cpp @@ -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 ¶mTypeId, 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); + } + }); } diff --git a/senec/integrationpluginsenec.json b/senec/integrationpluginsenec.json index 1ed328b7..65948091 100644 --- a/senec/integrationpluginsenec.json +++ b/senec/integrationpluginsenec.json @@ -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 + } + ] } ] } diff --git a/senec/senec.pro b/senec/senec.pro index 2a1e45fe..42a9c622 100644 --- a/senec/senec.pro +++ b/senec/senec.pro @@ -4,9 +4,11 @@ QT+= network SOURCES += \ integrationpluginsenec.cpp \ - senecaccount.cpp + senecaccount.cpp \ + seneclanstorage.cpp HEADERS += \ integrationpluginsenec.h \ - senecaccount.h + senecaccount.h \ + seneclanstorage.h diff --git a/senec/seneclanstorage.cpp b/senec/seneclanstorage.cpp new file mode 100644 index 00000000..2f2c08eb --- /dev/null +++ b/senec/seneclanstorage.cpp @@ -0,0 +1,5 @@ +#include "seneclanstorage.h" + +SenecLanStorage::SenecLanStorage(QObject *parent) + : QObject{parent} +{} diff --git a/senec/seneclanstorage.h b/senec/seneclanstorage.h new file mode 100644 index 00000000..2f94fbdb --- /dev/null +++ b/senec/seneclanstorage.h @@ -0,0 +1,15 @@ +#ifndef SENECLANSTORAGE_H +#define SENECLANSTORAGE_H + +#include + +class SenecLanStorage : public QObject +{ + Q_OBJECT +public: + explicit SenecLanStorage(QObject *parent = nullptr); + +signals: +}; + +#endif // SENECLANSTORAGE_H