Add sunspec model

This commit is contained in:
Simon Stürz 2022-11-14 17:26:41 +01:00
parent c08b38e4cf
commit f647b809c9
4 changed files with 415 additions and 2 deletions

View File

@ -1,6 +1,8 @@
include(../plugins.pri)
QT += network
PKGCONFIG += nymea-sunspec
QT += network serialbus
SOURCES += \
froniusnetworkreply.cpp \

View File

@ -202,6 +202,106 @@ void IntegrationPluginFronius::setupThing(ThingSetupInfo *info)
info->finish(Thing::ThingErrorNoError);
} else if (thing->thingClassId() == sunspecStorageThingClassId) {
QHostAddress address(thing->paramValue(sunspecStorageThingAddressParamTypeId).toString());
uint port(thing->paramValue(sunspecStorageThingPortParamTypeId).toUInt());
SunSpecConnection * connection = new SunSpecConnection(address, port, 1, SunSpecDataPoint::ByteOrderBigEndian, nullptr);
connect(info, &ThingSetupInfo::aborted, connection, &SunSpecConnection::deleteLater);
// Update all child things connected states for this connection
connect(connection, &SunSpecConnection::connectedChanged, thing, [this, connection, thing] (bool connected) {
if (connected) {
qCDebug(dcSunSpec()) << connection << "connected";
} else {
qCWarning(dcSunSpec()) << connection << "disconnected";
}
thing->setStateValue("connected", connected);
});
// Only during setup
connect(connection, &SunSpecConnection::connectedChanged, info, [this, connection, info] (bool connected) {
//qCDebug(dcSunSpec()) << "SunSpec connected changed during setup:" << (connected ? "connected" : "disconnected");
if (connected) {
connect(connection, &SunSpecConnection::discoveryFinished, info, [this, connection, info] (bool success) {
if (success) {
qCDebug(dcSunSpec()) << "Discovery finished successfully during setup of" << connection << ". Found SunSpec data on base register" << connection->baseRegister();
m_sunspecConnections.insert(connection, info->thing());
info->finish(Thing::ThingErrorNoError);
Thing *thing = info->thing();
// Get the battery model
foreach (SunSpecModel *model, connection->models()) {
if (model->modelId() == SunSpecModelFactory::ModelIdStorage) {
SunSpecStorageModel *storage = qobject_cast<SunSpecStorageModel *>(model);
m_sunSpecStorages.insert(thing, storage);
connect(model, &SunSpecModel::blockUpdated, this, [storage, thing, model](){
qCDebug(dcSunSpec()) << thing->name() << "block data updated" << storage;
thing->setStateValue(sunspecStorageConnectedStateTypeId, true);
thing->setStateValue(sunspecStorageVersionStateTypeId, model->commonModelInfo().versionString);
thing->setStateValue(sunspecStorageBatteryCriticalStateTypeId, storage->chaState() < 5);
thing->setStateValue(sunspecStorageBatteryLevelStateTypeId, qRound(storage->chaState()));
thing->setStateValue(sunspecStorageGridChargingStateTypeId, storage->chaGriSet() == SunSpecStorageModel::ChagrisetGrid);
thing->setStateValue(sunspecStorageEnableChargingStateTypeId, storage->storCtlMod().testFlag(SunSpecStorageModel::Storctl_modCharge));
thing->setStateValue(sunspecStorageChargingRateStateTypeId, storage->wChaGra());
thing->setStateValue(sunspecStorageDischargingRateStateTypeId, storage->wDisChaGra());
switch (storage->chaSt()) {
case SunSpecStorageModel::ChastOff:
thing->setStateValue(sunspecStorageBatteryLevelStateTypeId, "Off");
thing->setStateValue(sunspecStorageChargingStateStateTypeId, "idle");
break;
case SunSpecStorageModel::ChastEmpty:
thing->setStateValue(sunspecStorageBatteryLevelStateTypeId, "Empty");
thing->setStateValue(sunspecStorageChargingStateStateTypeId, "idle");
break;
case SunSpecStorageModel::ChastDischarging:
thing->setStateValue(sunspecStorageBatteryLevelStateTypeId, "Discharging");
thing->setStateValue(sunspecStorageChargingStateStateTypeId, "discharging");
break;
case SunSpecStorageModel::ChastCharging:
thing->setStateValue(sunspecStorageBatteryLevelStateTypeId, "Charging");
thing->setStateValue(sunspecStorageChargingStateStateTypeId, "charging");
break;
case SunSpecStorageModel::ChastFull:
thing->setStateValue(sunspecStorageBatteryLevelStateTypeId, "Full");
thing->setStateValue(sunspecStorageChargingStateStateTypeId, "idle");
break;
case SunSpecStorageModel::ChastHolding:
thing->setStateValue(sunspecStorageBatteryLevelStateTypeId, "Holding");
thing->setStateValue(sunspecStorageChargingStateStateTypeId, "idle");
break;
case SunSpecStorageModel::ChastTesting:
thing->setStateValue(sunspecStorageBatteryLevelStateTypeId, "Testing");
thing->setStateValue(sunspecStorageChargingStateStateTypeId, "idle");
break;
}
});
}
}
} else {
qCWarning(dcSunSpec()) << "Discovery finished with errors during setup of" << connection;
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The SunSpec discovery finished with errors. Please make sure this is a SunSpec device."));
}
});
// Perform initial discovery, finish if a valid base register has been found
connection->startDiscovery();
} else {
info->finish(Thing::ThingErrorHardwareNotAvailable);
}
});
connection->connectDevice();
} else {
Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
}
@ -220,6 +320,11 @@ void IntegrationPluginFronius::postSetupThing(Thing *thing)
foreach (FroniusSolarConnection *connection, m_froniusConnections.keys()) {
refreshConnection(connection);
}
foreach (SunSpecStorageModel *model, m_sunSpecStorages.values()) {
model->readBlockData();
}
});
m_connectionRefreshTimer->start();
@ -250,7 +355,122 @@ void IntegrationPluginFronius::thingRemoved(Thing *thing)
void IntegrationPluginFronius::executeAction(ThingActionInfo *info)
{
Q_UNUSED(info)
Thing *thing = info->thing();
Action action = info->action();
if (thing->thingClassId() == sunspecStorageThingClassId) {
SunSpecStorageModel *storage = qobject_cast<SunSpecStorageModel *>(m_sunSpecStorages.value(thing));
if (!storage) {
qWarning(dcSunSpec()) << "Could not find sunspec model instance for thing" << thing;
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
if (!thing->stateValue(sunspecStorageConnectedStateTypeId).toBool()) {
qWarning(dcSunSpec()) << "Could not execute action for" << thing << "because the SunSpec connection is not connected.";
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("The SunSpec connection is not connected."));
return;
}
if (action.actionTypeId() == sunspecStorageGridChargingActionTypeId) {
bool gridCharging = action.param(sunspecStorageGridChargingActionGridChargingParamTypeId).value().toBool();
qCDebug(dcFronius()) << "Set grid charging" << (gridCharging ? SunSpecStorageModel::ChagrisetGrid : SunSpecStorageModel::ChagrisetPv);
QModbusReply *reply = storage->setChaGriSet(gridCharging ? SunSpecStorageModel::ChagrisetGrid : SunSpecStorageModel::ChagrisetPv);
if (!reply) {
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, info, [info, reply]{
if (reply->error() != QModbusDevice::NoError) {
qCDebug(dcFronius()) << "Set grid charging failed";
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
qCDebug(dcFronius()) << "Set grid charging finished successfully";
info->finish(Thing::ThingErrorNoError);
});
} else if (action.actionTypeId() == sunspecStorageEnableChargingActionTypeId || action.actionTypeId() == sunspecStorageEnableDischargingActionTypeId) {
SunSpecStorageModel::Storctl_modFlags controlModeFlags;
if (action.actionTypeId() == sunspecStorageEnableChargingActionTypeId) {
if (action.param(sunspecStorageEnableChargingActionEnableChargingParamTypeId).value().toBool()) {
controlModeFlags.setFlag(SunSpecStorageModel::Storctl_modCharge);
}
if (thing->stateValue(sunspecStorageEnableDischargingStateTypeId).toBool())
controlModeFlags.setFlag(SunSpecStorageModel::Storctl_modDiScharge);
}
if (action.actionTypeId() == sunspecStorageEnableDischargingActionTypeId) {
if (action.param(sunspecStorageEnableDischargingActionEnableDischargingParamTypeId).value().toBool()) {
controlModeFlags.setFlag(SunSpecStorageModel::Storctl_modDiScharge);
}
if (thing->stateValue(sunspecStorageEnableChargingStateTypeId).toBool())
controlModeFlags.setFlag(SunSpecStorageModel::Storctl_modCharge);
}
qCDebug(dcFronius()) << "Set charging control flag" << controlModeFlags;
QModbusReply *reply = storage->setStorCtlMod(controlModeFlags);
if (!reply) {
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, info, [info, reply]{
if (reply->error() != QModbusDevice::NoError) {
info->finish(Thing::ThingErrorHardwareFailure);
qCDebug(dcFronius()) << "Set charging control flag finished with error";
return;
}
if (info->action().actionTypeId() == sunspecStorageEnableChargingActionTypeId) {
info->thing()->setStateValue(sunspecStorageEnableChargingStateTypeId, info->action().param(sunspecStorageEnableChargingActionEnableChargingParamTypeId).value());
}
if (info->action().actionTypeId() == sunspecStorageEnableDischargingActionTypeId) {
info->thing()->setStateValue(sunspecStorageEnableDischargingStateTypeId, info->action().param(sunspecStorageEnableDischargingActionEnableDischargingParamTypeId).value());
}
info->finish(Thing::ThingErrorNoError);
qCDebug(dcFronius()) << "Set charging control flag finished successfully";
});
} else if (action.actionTypeId() == sunspecStorageChargingRateActionTypeId) {
QModbusReply *reply = storage->setInWRte(action.param(sunspecStorageChargingRateActionChargingRateParamTypeId).value().toInt());
if (!reply) {
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, info, [info, reply]{
if (reply->error() != QModbusDevice::NoError) {
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
info->finish(Thing::ThingErrorNoError);
});
} else if (action.actionTypeId() == sunspecStorageDischargingRateActionTypeId) {
QModbusReply *reply = storage->setOutWRte(action.param(sunspecStorageDischargingRateActionDischargingRateParamTypeId).value().toInt());
if (!reply) {
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, info, [info, reply]{
if (reply->error() != QModbusDevice::NoError) {
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
info->finish(Thing::ThingErrorNoError);
});
} else {
Q_ASSERT_X(false, "executeAction", QString("Unhandled action: %1").arg(action.actionTypeId().toString()).toUtf8());
}
}
}
void IntegrationPluginFronius::refreshConnection(FroniusSolarConnection *connection)

View File

@ -34,6 +34,10 @@
#include "integrations/integrationplugin.h"
#include "froniussolarconnection.h"
#include <nymea-sunspec/sunspecconnection.h>
#include <nymea-sunspec/home/jenkins/nymea-plugins-modbus/libnymea-sunspec/models/sunspecstoragemodel.h>
#include <nymea-sunspec/home/jenkins/nymea-plugins-modbus/libnymea-sunspec/models/sunspecmodelfactory.h>
#include <QHash>
#include <QNetworkReply>
#include <QTimer>
@ -60,6 +64,8 @@ private:
PluginTimer *m_connectionRefreshTimer = nullptr;
QHash<FroniusSolarConnection *, Thing *> m_froniusConnections;
QHash<SunSpecConnection *, Thing *> m_sunspecConnections;
QHash<Thing *, SunSpecStorageModel *> m_sunSpecStorages;
void refreshConnection(FroniusSolarConnection *connection);

View File

@ -302,6 +302,7 @@
"createMethods": ["auto"],
"interfaces": [ "energystorage", "connectable"],
"paramTypes": [
{
"id": "49087f31-abf5-4bb8-946b-a3626ee80566",
"name": "id",
@ -387,6 +388,190 @@
"defaultValue": false
}
]
},
{
"name": "sunspecStorage",
"displayName": "Fronius SunSpec Storage",
"id": "5cdf938a-5680-4fdd-96cf-69a06b3b68ae",
"createMethods": [ "user" ],
"interfaces": ["connectable"],
"paramTypes": [
{
"id": "115f1b9c-10f7-40e3-8dd9-7bd886e7d19c",
"name": "address",
"displayName": "Host address",
"type": "QString",
"inputType": "IPv4Address",
"defaultValue": "84.20.187.116"
},
{
"id": "cefe2a6d-1312-4f3f-b1fa-839c1dc22418",
"name":"port",
"displayName": "Port",
"type": "int",
"defaultValue": 21502
},
{
"id": "a63cb6d6-29c7-4961-a341-f5b7289b1145",
"name":"modelId",
"displayName": "Model",
"type": "int",
"readOnly": true,
"defaultValue": 0
},
{
"id": "3f107844-00c5-4f39-86e5-485b3d1f5c1a",
"name":"modbusAddress",
"displayName": "Modbus address",
"type": "uint",
"readOnly": true,
"defaultValue": 0
},
{
"id": "1335ae67-7597-45d3-856f-2a56449b8a5f",
"name":"manufacturer",
"displayName": "Manufacturer",
"type": "QString",
"defaultValue": "Unkown",
"readOnly": true
},
{
"id": "c860d44c-af8c-4e6c-b67e-481c73a1ad68",
"name":"deviceModel",
"displayName": "Device model",
"type": "QString",
"defaultValue": "Unkown",
"readOnly": true
},
{
"id": "fb366e44-86f2-46ad-97d0-5b20d44eb6cb",
"name":"serialNumber",
"displayName": "Serial number",
"type": "QString",
"defaultValue": "Unkown",
"readOnly": true
}
],
"stateTypes":[
{
"id": "47755a1c-b4a7-4d50-b718-dd43ab6ee5cd",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "f57c9418-fd16-4d9d-b7c5-54c734754dd2",
"name": "batteryCritical",
"displayName": "Battery critical",
"displayNameEvent": "Battery critical changed",
"type": "bool",
"defaultValue": false
},
{
"id": "56930aec-93c7-4609-a0d1-0e24fc45e3da",
"name": "batteryLevel",
"displayName": "Battery level",
"displayNameEvent": "Battery level changed",
"type": "int",
"unit": "Percentage",
"minValue": 0,
"maxValue": 100,
"defaultValue": 0
},
{
"id": "1dd36a84-eacd-4bd7-adbd-3ab1ccfaa9ac",
"name": "storageStatus",
"displayName": "Status",
"displayNameEvent": "Status changed",
"type": "QString",
"possibleValues": [
"Off",
"Empty",
"Discharging",
"Charging",
"Full",
"Holding",
"Testing"
],
"defaultValue": "Off"
},
{
"id": "c8f9cd0a-423a-40d0-94cc-8775cdf075fe",
"name": "chargingState",
"displayName": "Charging state",
"displayNameEvent": "Charging state changed",
"type": "QString",
"possibleValues": ["idle", "charging", "discharging"],
"defaultValue": "idle"
},
{
"id": "12435c63-0a73-4b49-95e1-1d8c8f7e4578",
"name": "gridCharging",
"displayName": "Grid charging",
"displayNameEvent": "Grid charging changed",
"type": "bool",
"defaultValue": false,
"writable": true,
"displayNameAction": "Set grid charging"
},
{
"id": "b80df52f-47aa-44cf-8199-54dc96eba2ad",
"name": "enableCharging",
"displayName": "Charging",
"displayNameEvent": "Charging changed",
"displayNameAction": "Enable charging",
"type": "bool",
"defaultValue": false,
"writable": true
},
{
"id": "80924163-d2a7-409a-a5c2-e30dc2cd30a3",
"name": "enableDischarging",
"displayName": "Discharging",
"displayNameEvent": "Discharging changed",
"displayNameAction": "Enable discharging",
"type": "bool",
"defaultValue": false,
"writable": true
},
{
"id": "a8954186-4264-4edf-873e-1507d45e40e4",
"name": "chargingRate",
"displayName": "Charging rate",
"displayNameEvent": "Charging rate changed",
"type": "int",
"minValue": 0,
"maxValue": 100,
"unit": "Percentage",
"defaultValue": false,
"writable": true,
"displayNameAction": "Set charging rate"
},
{
"id": "67bc1367-453f-4f24-8942-978d9ffd43f5",
"name": "dischargingRate",
"displayName": "Discharging rate",
"displayNameEvent": "Discharging rate changed",
"type": "int",
"minValue": 0,
"maxValue": 100,
"unit": "Percentage",
"defaultValue": false,
"writable": true,
"displayNameAction": "Set discharging rate"
},
{
"id": "6364e277-b168-4acf-b0f6-fe32ced05f0e",
"name": "version",
"displayName": "Version",
"displayNameEvent": "Version changed",
"type": "QString",
"defaultValue": ""
}
]
}
]
}