From c112e1e6a01352c507094a3ae7f18140395aae5d Mon Sep 17 00:00:00 2001 From: Boernsman Date: Fri, 22 Jan 2021 14:21:23 +0100 Subject: [PATCH] improved sunspec storage --- sunspec/integrationpluginsunspec.cpp | 238 ++++++++++++++++---------- sunspec/integrationpluginsunspec.h | 6 +- sunspec/integrationpluginsunspec.json | 40 ++--- sunspec/sunspec.cpp | 13 +- sunspec/sunspecinverter.cpp | 15 +- sunspec/sunspecmeter.cpp | 22 ++- sunspec/sunspecstorage.cpp | 62 ++++--- sunspec/sunspecstorage.h | 77 ++++++--- 8 files changed, 305 insertions(+), 168 deletions(-) diff --git a/sunspec/integrationpluginsunspec.cpp b/sunspec/integrationpluginsunspec.cpp index cabaa94..4fbfa66 100644 --- a/sunspec/integrationpluginsunspec.cpp +++ b/sunspec/integrationpluginsunspec.cpp @@ -140,81 +140,37 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) thing->thingClassId() == sunspecSplitPhaseInverterThingClassId || thing->thingClassId() == sunspecSinglePhaseInverterThingClassId ) { - uint modelId = thing->paramValue(m_modelIdParamTypeIds.value(thing->thingClassId())).toInt(); - int modbusAddress = thing->paramValue(m_modbusAddressParamTypeIds.value(thing->thingClassId())).toInt(); - SunSpec *connection = m_sunSpecConnections.value(thing->parentId()); - if (!connection) { - qCWarning(dcSunSpec()) << "Could not find SunSpec connection"; - return info->finish(Thing::ThingErrorHardwareNotAvailable); + Thing *parent = myThings().findById(thing->parentId()); + if (parent->setupStatus() == Thing::ThingSetupStatusComplete) { + setupInverter(info); + } else { + connect(parent, &Thing::setupStatusChanged, info, [this, info] { + setupInverter(info); + }); } - SunSpecInverter *sunSpecInverter = new SunSpecInverter(connection, SunSpec::ModelId(modelId), modbusAddress); - sunSpecInverter->init(); - connect(sunSpecInverter, &SunSpecInverter::initFinished, info, [this, sunSpecInverter, info] (bool success){ - qCDebug(dcSunSpec()) << "Modbus Inverter init finished, success:" << success; - if (success) { - m_sunSpecInverters.insert(info->thing(), sunSpecInverter); - info->finish(Thing::ThingErrorNoError); - } else { - info->finish(Thing::ThingErrorHardwareNotAvailable); - } - }); - - connect(info, &ThingSetupInfo::aborted, sunSpecInverter, &SunSpecInverter::deleteLater); - connect(sunSpecInverter, &SunSpecInverter::destroyed, thing, [thing, this] {m_sunSpecInverters.remove(thing);}); - connect(sunSpecInverter, &SunSpecInverter::inverterDataReceived, this, &IntegrationPluginSunSpec::onInverterDataReceived); - } else if (thing->thingClassId() == sunspecSinglePhaseMeterThingClassId || thing->thingClassId() == sunspecSplitPhaseMeterThingClassId || thing->thingClassId() == sunspecThreePhaseMeterThingClassId) { - uint modelId = thing->paramValue(m_modelIdParamTypeIds.value(thing->thingClassId())).toInt(); - int modbusAddress = thing->paramValue(m_modbusAddressParamTypeIds.value(thing->thingClassId())).toInt(); - SunSpec *connection = m_sunSpecConnections.value(thing->parentId()); - if (!connection) { - qCWarning(dcSunSpec()) << "Could not find SunSpec connection"; - return info->finish(Thing::ThingErrorHardwareNotAvailable); + Thing *parent = myThings().findById(thing->parentId()); + if (parent->setupStatus() == Thing::ThingSetupStatusComplete) { + setupMeter(info); + } else { + connect(parent, &Thing::setupStatusChanged, info, [this, info] { + setupMeter(info); + }); } - SunSpecMeter *sunSpecMeter = new SunSpecMeter(connection, SunSpec::ModelId(modelId), modbusAddress); - sunSpecMeter->init(); - connect(sunSpecMeter, &SunSpecMeter::initFinished, info, [this, sunSpecMeter, info] (bool success){ - qCDebug(dcSunSpec()) << "Modbus meter init finished, success:" << success; - if (success) { - m_sunSpecMeters.insert(info->thing(), sunSpecMeter); - info->finish(Thing::ThingErrorNoError); - } else { - info->finish(Thing::ThingErrorHardwareNotAvailable); - } - }); - - connect(info, &ThingSetupInfo::aborted, sunSpecMeter, &SunSpecMeter::deleteLater); - connect(sunSpecMeter, &SunSpecMeter::destroyed, thing, [thing, this] {m_sunSpecMeters.remove(thing);}); - connect(sunSpecMeter, &SunSpecMeter::meterDataReceived, this, &IntegrationPluginSunSpec::onMeterDataReceived); } else if (info->thing()->thingClassId() == sunspecStorageThingClassId) { - uint modelId = thing->paramValue(m_modelIdParamTypeIds.value(thing->thingClassId())).toInt(); - int modbusAddress = thing->paramValue(m_modbusAddressParamTypeIds.value(thing->thingClassId())).toInt(); - SunSpec *connection = m_sunSpecConnections.value(thing->parentId()); - if (!connection) { - qCWarning(dcSunSpec()) << "Could not find SunSpec connection"; - return info->finish(Thing::ThingErrorHardwareNotAvailable); + Thing *parent = myThings().findById(thing->parentId()); + if (parent->setupStatus() == Thing::ThingSetupStatusComplete) { + setupStorage(info); + } else { + connect(parent, &Thing::setupStatusChanged, info, [this, info] { + setupStorage(info); + }); } - SunSpecStorage *sunSpecStorage = new SunSpecStorage(connection, SunSpec::ModelId(modelId), modbusAddress); - sunSpecStorage->init(); - connect(sunSpecStorage, &SunSpecStorage::initFinished, info, [this, sunSpecStorage, info] (bool success){ - qCDebug(dcSunSpec()) << "Modbus storage init finished, success:" << success; - if (success) { - m_sunSpecStorages.insert(info->thing(), sunSpecStorage); - info->finish(Thing::ThingErrorNoError); - } else { - info->finish(Thing::ThingErrorHardwareNotAvailable); - } - }); - - connect(info, &ThingSetupInfo::aborted, sunSpecStorage, &SunSpecStorage::deleteLater); - connect(sunSpecStorage, &SunSpecStorage::destroyed, thing, [thing, this] {m_sunSpecStorages.remove(thing);}); - connect(sunSpecStorage, &SunSpecStorage::storageDataReceived, this, &IntegrationPluginSunSpec::onStorageDataReceived); - } else { Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(info->thing()->thingClassId().toString()).toUtf8()); } @@ -353,15 +309,16 @@ void IntegrationPluginSunSpec::executeAction(ThingActionInfo *info) m_asyncActions.insert(requestId, info); connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); } - } else if (action.actionTypeId() == sunspecStorageEnableChargingLimitActionTypeId) { - /*int value = (action.param(sunspecStorageEnableChargingLimitActionEnableChargingLimitParamTypeId).value().toBool() << 1) | thing->stateValue(sunspecStorageEnableDischargingLimitStateTypeId).toBool(); - QUuid requestId = sunSpecStorage->setStorageControlMode(value); + } else if (action.actionTypeId() == sunspecStorageEnableChargingActionTypeId) { + bool charging = action.param(sunspecStorageEnableChargingActionEnableChargingParamTypeId).value().toBool(); + bool discharging = thing->stateValue(sunspecStorageEnableDischargingStateTypeId).toBool(); + QUuid requestId = sunSpecStorage->setStorageControlMode(charging, discharging); if (requestId.isNull()) { info->finish(Thing::ThingErrorHardwareFailure); } else { m_asyncActions.insert(requestId, info); connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); - }*/ + } } else if (action.actionTypeId() == sunspecStorageChargingRateActionTypeId) { QUuid requestId = sunSpecStorage->setChargingRate(action.param(sunspecStorageChargingRateActionChargingRateParamTypeId).value().toInt()); if (requestId.isNull()) { @@ -370,15 +327,16 @@ void IntegrationPluginSunSpec::executeAction(ThingActionInfo *info) m_asyncActions.insert(requestId, info); connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); } - } else if (action.actionTypeId() == sunspecStorageEnableDischargingLimitActionTypeId) { - /*int value = (action.param(sunspecStorageEnableDischargingLimitActionEnableDischargingLimitParamTypeId).value().toBool() << 1) | thing->stateValue(sunspecStorageEnableChargingLimitStateTypeId).toBool(); - QUuid requestId = sunSpecStorage->setStorageControlMode(value); + } else if (action.actionTypeId() == sunspecStorageEnableDischargingActionTypeId) { + bool discharging = action.param(sunspecStorageEnableDischargingActionEnableDischargingParamTypeId).value().toBool(); + bool charging = thing->stateValue(sunspecStorageEnableChargingStateTypeId).toBool(); + QUuid requestId = sunSpecStorage->setStorageControlMode(charging, discharging); if (requestId.isNull()) { info->finish(Thing::ThingErrorHardwareFailure); } else { m_asyncActions.insert(requestId, info); connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); - }*/ + } } else if (action.actionTypeId() == sunspecStorageDischargingRateActionTypeId) { QUuid requestId = sunSpecStorage->setDischargingRate(action.param(sunspecStorageDischargingRateActionDischargingRateParamTypeId).value().toInt()); if (requestId.isNull()) { @@ -406,6 +364,88 @@ bool IntegrationPluginSunSpec::checkIfThingExists(uint modelId, uint modbusAddre return false; } +void IntegrationPluginSunSpec::setupInverter(ThingSetupInfo *info) +{ + Thing *thing = info->thing(); + uint modelId = thing->paramValue(m_modelIdParamTypeIds.value(thing->thingClassId())).toInt(); + int modbusAddress = thing->paramValue(m_modbusAddressParamTypeIds.value(thing->thingClassId())).toInt(); + SunSpec *connection = m_sunSpecConnections.value(thing->parentId()); + if (!connection) { + qCWarning(dcSunSpec()) << "Could not find SunSpec connection"; + return info->finish(Thing::ThingErrorHardwareNotAvailable); + } + SunSpecInverter *sunSpecInverter = new SunSpecInverter(connection, SunSpec::ModelId(modelId), modbusAddress); + sunSpecInverter->init(); + connect(sunSpecInverter, &SunSpecInverter::initFinished, info, [this, sunSpecInverter, info] (bool success){ + qCDebug(dcSunSpec()) << "Modbus Inverter init finished, success:" << success; + if (success) { + m_sunSpecInverters.insert(info->thing(), sunSpecInverter); + info->finish(Thing::ThingErrorNoError); + } else { + info->finish(Thing::ThingErrorHardwareNotAvailable); + } + }); + + connect(info, &ThingSetupInfo::aborted, sunSpecInverter, &SunSpecInverter::deleteLater); + connect(sunSpecInverter, &SunSpecInverter::destroyed, thing, [thing, this] {m_sunSpecInverters.remove(thing);}); + connect(sunSpecInverter, &SunSpecInverter::inverterDataReceived, this, &IntegrationPluginSunSpec::onInverterDataReceived); +} + +void IntegrationPluginSunSpec::setupMeter(ThingSetupInfo *info) +{ + Thing *thing = info->thing(); + + uint modelId = thing->paramValue(m_modelIdParamTypeIds.value(thing->thingClassId())).toInt(); + int modbusAddress = thing->paramValue(m_modbusAddressParamTypeIds.value(thing->thingClassId())).toInt(); + SunSpec *connection = m_sunSpecConnections.value(thing->parentId()); + if (!connection) { + qCWarning(dcSunSpec()) << "Could not find SunSpec connection"; + return info->finish(Thing::ThingErrorHardwareNotAvailable); + } + SunSpecMeter *sunSpecMeter = new SunSpecMeter(connection, SunSpec::ModelId(modelId), modbusAddress); + sunSpecMeter->init(); + connect(sunSpecMeter, &SunSpecMeter::initFinished, info, [this, sunSpecMeter, info] (bool success){ + qCDebug(dcSunSpec()) << "Modbus meter init finished, success:" << success; + if (success) { + m_sunSpecMeters.insert(info->thing(), sunSpecMeter); + info->finish(Thing::ThingErrorNoError); + } else { + info->finish(Thing::ThingErrorHardwareNotAvailable); + } + }); + + connect(info, &ThingSetupInfo::aborted, sunSpecMeter, &SunSpecMeter::deleteLater); + connect(sunSpecMeter, &SunSpecMeter::destroyed, thing, [thing, this] {m_sunSpecMeters.remove(thing);}); + connect(sunSpecMeter, &SunSpecMeter::meterDataReceived, this, &IntegrationPluginSunSpec::onMeterDataReceived); +} + +void IntegrationPluginSunSpec::setupStorage(ThingSetupInfo *info) +{ + Thing *thing = info->thing(); + uint modelId = thing->paramValue(m_modelIdParamTypeIds.value(thing->thingClassId())).toInt(); + int modbusAddress = thing->paramValue(m_modbusAddressParamTypeIds.value(thing->thingClassId())).toInt(); + SunSpec *connection = m_sunSpecConnections.value(thing->parentId()); + if (!connection) { + qCWarning(dcSunSpec()) << "Could not find SunSpec connection"; + return info->finish(Thing::ThingErrorHardwareNotAvailable); + } + SunSpecStorage *sunSpecStorage = new SunSpecStorage(connection, SunSpec::ModelId(modelId), modbusAddress); + sunSpecStorage->init(); + connect(sunSpecStorage, &SunSpecStorage::initFinished, info, [this, sunSpecStorage, info] (bool success){ + qCDebug(dcSunSpec()) << "Modbus storage init finished, success:" << success; + if (success) { + m_sunSpecStorages.insert(info->thing(), sunSpecStorage); + info->finish(Thing::ThingErrorNoError); + } else { + info->finish(Thing::ThingErrorHardwareNotAvailable); + } + }); + + connect(info, &ThingSetupInfo::aborted, sunSpecStorage, &SunSpecStorage::deleteLater); + connect(sunSpecStorage, &SunSpecStorage::destroyed, thing, [thing, this] {m_sunSpecStorages.remove(thing);}); + connect(sunSpecStorage, &SunSpecStorage::storageDataReceived, this, &IntegrationPluginSunSpec::onStorageDataReceived); +} + void IntegrationPluginSunSpec::onRefreshTimer() { foreach (SunSpec *connection, m_sunSpecConnections) { @@ -437,13 +477,11 @@ void IntegrationPluginSunSpec::onPluginConfigurationChanged(const ParamTypeId &p Q_FOREACH(SunSpec *connection, m_sunSpecConnections) { connection->setNumberOfRetries(value.toUInt()); } - } else if (paramTypeId == sunSpecPluginTimeoutParamTypeId) { qCDebug(dcSunSpec()) << "Updating timeout" << value.toUInt() << "[ms]"; Q_FOREACH(SunSpec *connection, m_sunSpecConnections) { connection->setTimeout(value.toUInt()); } - } else { qCWarning(dcSunSpec()) << "Unknown plugin configuration" << paramTypeId << "Value" << value; } @@ -474,7 +512,7 @@ void IntegrationPluginSunSpec::onFoundSunSpecModel(SunSpec::ModelId modelId, int return; } - qCDebug(dcSunSpec()) << "On model received" << modelId << "length" << modbusStartRegister << "Thing:" << thing->name(); + qCDebug(dcSunSpec()) << "On model received" << modelId << "start register" << modbusStartRegister << "Thing:" << thing->name(); if (checkIfThingExists(modelId, modbusStartRegister)) { return; } @@ -510,7 +548,7 @@ void IntegrationPluginSunSpec::onFoundSunSpecModel(SunSpec::ModelId modelId, int case SunSpec::ModelIdSinglePhaseMeter: case SunSpec::ModelIdSinglePhaseMeterFloat: { - ThingDescriptor descriptor(sunspecSinglePhaseMeterThingClassId, model+tr(" Meter"), "", thing->id()); + ThingDescriptor descriptor(sunspecSinglePhaseMeterThingClassId, model+tr(" meter"), "", thing->id()); ParamList params; params.append(Param(sunspecSinglePhaseMeterThingModelIdParamTypeId, modelId)); params.append(Param(sunspecSinglePhaseMeterThingModbusAddressParamTypeId, modbusStartRegister)); @@ -519,7 +557,7 @@ void IntegrationPluginSunSpec::onFoundSunSpecModel(SunSpec::ModelId modelId, int } break; case SunSpec::ModelIdSplitSinglePhaseMeter: case SunSpec::ModelIdSplitSinglePhaseMeterFloat: { - ThingDescriptor descriptor(sunspecSplitPhaseMeterThingClassId, model+tr(" Meter"), "", thing->id()); + ThingDescriptor descriptor(sunspecSplitPhaseMeterThingClassId, model+tr(" meter"), "", thing->id()); ParamList params; params.append(Param(sunspecSplitPhaseMeterThingModelIdParamTypeId, modelId)); params.append(Param(sunspecSplitPhaseMeterThingModbusAddressParamTypeId, modbusStartRegister)); @@ -528,7 +566,7 @@ void IntegrationPluginSunSpec::onFoundSunSpecModel(SunSpec::ModelId modelId, int } break; case SunSpec::ModelIdWyeConnectThreePhaseMeterFloat: case SunSpec::ModelIdDeltaConnectThreePhaseMeterFloat: { - ThingDescriptor descriptor(sunspecThreePhaseMeterThingClassId, model+" Meter", "", thing->id()); + ThingDescriptor descriptor(sunspecThreePhaseMeterThingClassId, model+" meter", "", thing->id()); ParamList params; params.append(Param(sunspecThreePhaseMeterThingModelIdParamTypeId, modelId)); params.append(Param(sunspecThreePhaseMeterThingModbusAddressParamTypeId, modbusStartRegister)); @@ -536,10 +574,10 @@ void IntegrationPluginSunSpec::onFoundSunSpecModel(SunSpec::ModelId modelId, int emit autoThingsAppeared({descriptor}); } break; case SunSpec::ModelIdStorage: { - ThingDescriptor descriptor(sunspecStorageThingClassId, model+" Storage", "", thing->id()); + ThingDescriptor descriptor(sunspecStorageThingClassId, model+" storage", "", thing->id()); ParamList params; params.append(Param(sunspecStorageThingModelIdParamTypeId, modelId)); - params.append(Param(sunspecThreePhaseInverterThingModbusAddressParamTypeId, modbusStartRegister)); + params.append(Param(sunspecStorageThingModbusAddressParamTypeId, modbusStartRegister)); descriptor.setParams(params); emit autoThingsAppeared({descriptor}); } break; @@ -556,9 +594,7 @@ void IntegrationPluginSunSpec::onSunSpecModelSearchFinished(const QHashdeviceModel() << connection->serialNumber(); return; } - qCDebug(dcSunSpec()) << "On sunspec model search finished, models:" << modelIds.count(); - } void IntegrationPluginSunSpec::onWriteRequestExecuted(QUuid requestId, bool success) @@ -589,7 +625,6 @@ void IntegrationPluginSunSpec::onInverterDataReceived(const SunSpecInverter::Inv thing->setStateValue(sunspecThreePhaseInverterTotalCurrentStateTypeId, inverterData.acCurrent); thing->setStateValue(sunspecThreePhaseInverterCabinetTemperatureStateTypeId, inverterData.cabinetTemperature); - if (thing->thingClassId() == sunspecSplitPhaseMeterThingClassId) { thing->setStateValue(sunspecSplitPhaseInverterPhaseANVoltageStateTypeId, inverterData.phaseVoltageAN); @@ -609,7 +644,6 @@ void IntegrationPluginSunSpec::onInverterDataReceived(const SunSpecInverter::Inv thing->setStateValue(sunspecThreePhaseInverterPhaseCCurrentStateTypeId, inverterData.phaseCCurrent); } - switch(inverterData.operatingState) { case SunSpec::SunSpecOperatingState::Off: thing->setStateValue(sunspecThreePhaseInverterOperatingStateStateTypeId, "Off"); @@ -689,7 +723,7 @@ void IntegrationPluginSunSpec::onInverterDataReceived(const SunSpecInverter::Inv } } -void IntegrationPluginSunSpec::onStorageDataReceived(const SunSpecStorage::StorageData &storageData) +void IntegrationPluginSunSpec::onStorageDataReceived(const SunSpecStorage::StorageData &mandatory, const SunSpecStorage::StorageDataOptional &optional) { SunSpecStorage *storage = static_cast(sender()); Thing *thing = m_sunSpecStorages.key(storage); @@ -698,7 +732,37 @@ void IntegrationPluginSunSpec::onStorageDataReceived(const SunSpecStorage::Stora return; } thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), true); - thing->setStateValue(sunspecStorageStorageStateStateTypeId, storageData.chargingState); + + thing->setStateValue(sunspecStorageChargingRateStateTypeId, mandatory.maxChargeRate); + thing->setStateValue(sunspecStorageDischargingRateStateTypeId, mandatory.maxDischargeRate); + + bool charging = false; + switch (optional.chargeSatus) { + case SunSpecStorage::ChargingStatusOff: + thing->setStateValue(sunspecStorageStorageStatusStateTypeId, "Off"); + break; + case SunSpecStorage::ChargingStatusFull: + thing->setStateValue(sunspecStorageStorageStatusStateTypeId, "Full"); + break; + case SunSpecStorage::ChargingStatusEmpty: + thing->setStateValue(sunspecStorageStorageStatusStateTypeId, "Empty"); + break; + case SunSpecStorage::ChargingStatusHolding: + thing->setStateValue(sunspecStorageStorageStatusStateTypeId, "Holding"); + break; + case SunSpecStorage::ChargingStatusTesting: + thing->setStateValue(sunspecStorageStorageStatusStateTypeId, "Testing"); + break; + case SunSpecStorage::ChargingStatusCharging: + thing->setStateValue(sunspecStorageStorageStatusStateTypeId, "Charging"); + break; + case SunSpecStorage::ChargingStatusDischarging: + thing->setStateValue(sunspecStorageStorageStatusStateTypeId, "Discharging"); + break; + }; + double batteryLevel = optional.currentlyAvailableEnergy; + thing->setStateValue(sunspecStorageBatteryLevelStateTypeId, batteryLevel); + thing->setStateValue(sunspecStorageBatteryCriticalStateTypeId, (batteryLevel < 5 && !charging)); } void IntegrationPluginSunSpec::onMeterDataReceived(const SunSpecMeter::MeterData &meterData) diff --git a/sunspec/integrationpluginsunspec.h b/sunspec/integrationpluginsunspec.h index fedbb21..5a1f9aa 100644 --- a/sunspec/integrationpluginsunspec.h +++ b/sunspec/integrationpluginsunspec.h @@ -79,6 +79,10 @@ private: bool checkIfThingExists(uint modelId, uint modbusAddress); + void setupInverter(ThingSetupInfo *info); + void setupMeter(ThingSetupInfo *info); + void setupStorage(ThingSetupInfo *info); + private slots: void onRefreshTimer(); @@ -93,7 +97,7 @@ private slots: void onWriteRequestError(QUuid requestId, const QString &error); void onInverterDataReceived(const SunSpecInverter::InverterData &inverterData); - void onStorageDataReceived(const SunSpecStorage::StorageData &storageData); + void onStorageDataReceived(const SunSpecStorage::StorageData &mandatory, const SunSpecStorage::StorageDataOptional &optional); void onMeterDataReceived(const SunSpecMeter::MeterData &meterData); }; #endif // INTEGRATIONPLUGINSUNSPEC_H diff --git a/sunspec/integrationpluginsunspec.json b/sunspec/integrationpluginsunspec.json index bb081cc..b28d92d 100644 --- a/sunspec/integrationpluginsunspec.json +++ b/sunspec/integrationpluginsunspec.json @@ -998,9 +998,9 @@ }, { "id": "da2b19c5-0f48-49d1-93f0-abdc0051407d", - "name": "storageState", - "displayName": "State", - "displayNameEvent": "State changed", + "name": "storageStatus", + "displayName": "Status", + "displayNameEvent": "Status changed", "type": "QString", "possibleValues": [ "Off", @@ -1025,13 +1025,23 @@ }, { "id": "1f530f79-c0d2-466b-90e1-79149e34d92f", - "name": "enableChargingLimit", - "displayName": "Charging limit", - "displayNameEvent": "Charging limit changed", + "name": "enableCharging", + "displayName": "Charging", + "displayNameEvent": "Charging changed", + "displayNameAction": "Enable charging", "type": "bool", "defaultValue": false, - "writable": true, - "displayNameAction": "Enable Charging Limit" + "writable": true + }, + { + "id": "bc99a159-815a-40ab-a6e8-b46f315305f7", + "name": "enableDischarging", + "displayName": "Discharging", + "displayNameEvent": "Discharging changed", + "displayNameAction": "Enable discharging", + "type": "bool", + "defaultValue": false, + "writable": true }, { "id": "7f469bbc-64a5-4045-8d5f-9a1a85039851", @@ -1039,30 +1049,20 @@ "displayName": "Charging rate", "displayNameEvent": "Charging rate changed", "type": "int", - "minValue": -100, + "minValue": 0, "maxValue": 100, "unit": "Percentage", "defaultValue": false, "writable": true, "displayNameAction": "Set charging rate" }, - { - "id": "bc99a159-815a-40ab-a6e8-b46f315305f7", - "name": "enableDischargingLimit", - "displayName": "Discharging limit", - "displayNameEvent": "Discharging limit changed", - "displayNameAction": "Enable Discharging Limit", - "type": "bool", - "defaultValue": false, - "writable": true - }, { "id": "6068f030-acce-44a2-b95f-bd00dd5ca760", "name": "dischargingRate", "displayName": "Discharging rate", "displayNameEvent": "Discharging rate changed", "type": "int", - "minValue": -100, + "minValue": 0, "maxValue": 100, "unit": "Percentage", "defaultValue": false, diff --git a/sunspec/sunspec.cpp b/sunspec/sunspec.cpp index 0dd3401..071666c 100644 --- a/sunspec/sunspec.cpp +++ b/sunspec/sunspec.cpp @@ -226,20 +226,20 @@ void SunSpec::readModelHeader(uint modbusAddress) qCDebug(dcSunSpec()) << "SunSpec: Received model header response. Model ID:" << modelId << "length" << length; modelHeaderReceived(modbusAddress, modelId, length); } else { - qCWarning(dcSunSpec()) << "SunSpec: Read response error:" << reply->error(); + qCWarning(dcSunSpec()) << "SunSpec: Read model header response error:" << reply->error(); } }); connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error) { - qCWarning(dcSunSpec()) << "SunSpec: Modbus reply error:" << error; + qCWarning(dcSunSpec()) << "SunSpec: Read model header, modbus reply error:" << error; reply->finished(); // To make sure it will be deleted }); } else { - qCWarning(dcSunSpec()) << "SunSpec: Read error: " << m_modbusTcpClient->errorString(); + qCWarning(dcSunSpec()) << "SunSpec: Read model header error: " << m_modbusTcpClient->errorString(); delete reply; // broadcast replies return immediately return; } } else { - qCWarning(dcSunSpec()) << "SunSpec: Read error: " << m_modbusTcpClient->errorString(); + qCWarning(dcSunSpec()) << "SunSpec: Read model header error: " << m_modbusTcpClient->errorString(); return; } } @@ -248,6 +248,11 @@ void SunSpec::readModelDataBlock(uint modbusAddress, uint length) { qCDebug(dcSunSpec()) << "SunSpec: Read model, modbus address" << modbusAddress << "length" << length << ", Slave ID" << m_slaveId; + if (length > 125) { //Modbus register limit is 125 + qCWarning(dcSunSpec()) << "SunSpec: Data block length is too long, max 125 register"; + return; + } + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, modbusAddress, length+2); if (QModbusReply *reply = m_modbusTcpClient->sendReadRequest(request, m_slaveId)) { diff --git a/sunspec/sunspecinverter.cpp b/sunspec/sunspecinverter.cpp index bee5c8d..7cc97f1 100644 --- a/sunspec/sunspecinverter.cpp +++ b/sunspec/sunspecinverter.cpp @@ -53,10 +53,12 @@ void SunSpecInverter::init() qCDebug(dcSunSpec()) << "SunSpecInverter: Init"; m_connection->readModelHeader(m_modelModbusStartRegister); connect(m_connection, &SunSpec::modelHeaderReceived, this, [this] (uint modbusAddress, SunSpec::ModelId modelId, uint length) { - qCDebug(dcSunSpec()) << "SunSpecInverter: Model Header received, modbus address:" << modbusAddress << "model Id:" << modelId << "length:" << length; - m_modelLength = length; - emit initFinished(true); - m_initFinishedSuccess = true; + if (modelId == m_id) { + qCDebug(dcSunSpec()) << "SunSpecInverter: Model Header received, modbus address:" << modbusAddress << "model Id:" << modelId << "length:" << length; + m_modelLength = length; + emit initFinished(true); + m_initFinishedSuccess = true; + } }); QTimer::singleShot(10000, this,[this] { if (!m_initFinishedSuccess) { @@ -67,12 +69,13 @@ void SunSpecInverter::init() void SunSpecInverter::getInverterModelDataBlock() { - // TODO check map length to modbus max value + qCDebug(dcSunSpec()) << "SunSpecInverter: get inverter model data block, modbus register" << m_modelModbusStartRegister << "length" << m_modelLength; m_connection->readModelDataBlock(m_modelModbusStartRegister, m_modelLength); } void SunSpecInverter::getInverterModelHeader() { + qCDebug(dcSunSpec()) << "SunSpecInverter: get inverter model header, modbus register" << m_modelModbusStartRegister; m_connection->readModelHeader(m_modelModbusStartRegister); } @@ -83,7 +86,7 @@ void SunSpecInverter::onModelDataBlockReceived(SunSpec::ModelId mapId, uint mapL return; } if (mapLength < m_modelLength) { - qCDebug(dcSunSpec()) << "SunSpecInverter: on modbus map received, map length ist too short" << mapLength; + qCDebug(dcSunSpec()) << "SunSpecInverter: on modbus map received, map length is too short" << mapLength; //return; } InverterData inverterData; diff --git a/sunspec/sunspecmeter.cpp b/sunspec/sunspecmeter.cpp index e154d1b..9e7c488 100644 --- a/sunspec/sunspecmeter.cpp +++ b/sunspec/sunspecmeter.cpp @@ -52,26 +52,30 @@ void SunSpecMeter::init() qCDebug(dcSunSpec()) << "SunSpecMeter: Init"; m_connection->readModelHeader(m_modelModbusStartRegister); connect(m_connection, &SunSpec::modelHeaderReceived, this, [this] (uint modbusAddress, SunSpec::ModelId modelId, uint length) { - qCDebug(dcSunSpec()) << "SunSpecMeter: Model Header received, modbus address:" << modbusAddress << "model Id:" << modelId << "length:" << length; - m_modelLength = length; - emit initFinished(true); - m_initFinishedSuccess = true; + if (modelId == m_id) { + qCDebug(dcSunSpec()) << "SunSpecMeter: Model Header received, modbus address:" << modbusAddress << "model Id:" << modelId << "length:" << length; + m_modelLength = length; + emit initFinished(true); + m_initFinishedSuccess = true; + } }); QTimer::singleShot(10000, this,[this] { - if (!m_initFinishedSuccess) { - emit initFinished(false); - } + if (!m_initFinishedSuccess) { + emit initFinished(false); + } }); } void SunSpecMeter::getMeterModelDataBlock() { - m_connection->readModelDataBlock(m_modelModbusStartRegister, m_modelLength); + qCDebug(dcSunSpec()) << "SunSpecMeter: get meter model data block, modbus register" << m_modelModbusStartRegister << "length" << m_modelLength; + m_connection->readModelDataBlock(m_modelModbusStartRegister, m_modelLength); } void SunSpecMeter::getMeterModelHeader() { - + qCDebug(dcSunSpec()) << "SunSpecMeter: get meter model header, modbus register" << m_modelModbusStartRegister << "length" << m_modelLength; + m_connection->readModelHeader(m_modelModbusStartRegister); } void SunSpecMeter::onModelDataBlockReceived(SunSpec::ModelId modelId, uint length, QVector data) diff --git a/sunspec/sunspecstorage.cpp b/sunspec/sunspecstorage.cpp index 28600cc..5648ca5 100644 --- a/sunspec/sunspecstorage.cpp +++ b/sunspec/sunspecstorage.cpp @@ -49,27 +49,31 @@ SunSpec::ModelId SunSpecStorage::modelId() void SunSpecStorage::init() { qCDebug(dcSunSpec()) << "SunSpecStorage: Init"; - m_connection->readModelHeader(m_modelModbusStartRegister); + getStorageModelHeader(); connect(m_connection, &SunSpec::modelHeaderReceived, this, [this] (uint modbusAddress, SunSpec::ModelId modelId, uint length) { - qCDebug(dcSunSpec()) << "SunSpecStorager: Model header received, modbus address:" << modbusAddress << "model Id:" << modelId << "length:" << length; - m_modelLength = length; - emit initFinished(true); - m_initFinishedSuccess = true; + if (modelId == m_id) { + qCDebug(dcSunSpec()) << "SunSpecStorager: Model header received, modbus address:" << modbusAddress << "model Id:" << modelId << "length:" << length; + m_modelLength = length; + emit initFinished(true); + m_initFinishedSuccess = true; + } }); - QTimer::singleShot(10000, this,[this] { - if (!m_initFinishedSuccess) { - emit initFinished(false); - } + QTimer::singleShot(10000, this, [this] { + if (!m_initFinishedSuccess) { + emit initFinished(false); + } }); } void SunSpecStorage::getStorageModelDataBlock() { + qCDebug(dcSunSpec()) << "SunSpecStorage: get storage model data block, modbus register" << m_modelModbusStartRegister << "length" << m_modelLength; m_connection->readModelDataBlock(m_modelModbusStartRegister, m_modelLength); } void SunSpecStorage::getStorageModelHeader() { + qCDebug(dcSunSpec()) << "SunSpecStorage: get storage model header, modbus register" << m_modelModbusStartRegister << "length" << m_modelLength; m_connection->readModelHeader(m_modelModbusStartRegister); } @@ -81,7 +85,7 @@ QUuid SunSpecStorage::setGridCharging(bool enabled) PV (charging from grid 0 disabled) GRID (charging from 1 grid enabled*/ - uint registerAddress = m_modelModbusStartRegister + Model124::Model124ChaGriSet; + uint registerAddress = m_modelModbusStartRegister + Model124Optional::Model124ChargeGridSet; quint16 value = enabled; return m_connection->writeHoldingRegister(registerAddress, value); } @@ -89,8 +93,8 @@ QUuid SunSpecStorage::setGridCharging(bool enabled) QUuid SunSpecStorage::setStorageControlMode(bool chargingEnabled, bool dischargingEnabled) { // Set charge bit to enable charge limit, set discharge bit to enable discharge limit, set both bits to enable both limits - quint16 value = ((static_cast(chargingEnabled) << StorageControlBitFieldCharge) | - (static_cast(dischargingEnabled) << StorageControlBitFieldDischarge)) ; + quint16 value = ((static_cast(chargingEnabled)) | + (static_cast(dischargingEnabled) << 1)) ; uint modbusRegister = m_modelModbusStartRegister + Model124::Model124ActivateStorageControlMode; return m_connection->writeHoldingRegister(modbusRegister, value); @@ -124,22 +128,38 @@ void SunSpecStorage::onModelDataBlockReceived(SunSpec::ModelId modelId, uint len switch (modelId) { case SunSpec::ModelIdStorage: { - StorageData storageData; + StorageData mandatory; qCDebug(dcSunSpec()) << "SunSpecStorage: Storage model received:"; qCDebug(dcSunSpec()) << " - Setpoint maximum charge" << data[Model124SetpointMaximumCharge]; qCDebug(dcSunSpec()) << " - Setpoint maximum charging rate" << data[Model124SetpointMaximumChargingRate]; qCDebug(dcSunSpec()) << " - Setpoint maximum discharge rate" << data[Model124SetpointMaximumDischargeRate]; qCDebug(dcSunSpec()) << " - Active storage control mode" << data[Model124ActivateStorageControlMode]; - qCDebug(dcSunSpec()) << " - Currently available energy" << data[Model124CurrentlyAvailableEnergy]; - storageData.chargingState = ChargingState(data[Model124::Model124ChargeStatus]); - qCDebug(dcSunSpec()) << " - Setpoint maximum charging rate" << data[Model124ChaGriSet]; - qCDebug(dcSunSpec()) << " - Setpoint maximum charging rate" << data[Model124ScaleFactorMaximumCharge]; - qCDebug(dcSunSpec()) << " - Setpoint maximum charging rate" << data[Model124ScaleFactorMaximumChargeDischargeRate]; - qCDebug(dcSunSpec()) << " - Setpoint maximum charging rate" << data[Model124ScaleFactorAvailableEnergyPercent]; - emit storageDataReceived(storageData); + qCDebug(dcSunSpec()) << " - ChaGriSet" << data[Model124ChargeGridSet]; + qCDebug(dcSunSpec()) << " - Scale factor max charge" << data[Model124ScaleFactorMaximumCharge]; + qCDebug(dcSunSpec()) << " - Scale factor max charge/discharge rate" << data[Model124ScaleFactorMaximumChargeDischargeRate]; + qCDebug(dcSunSpec()) << " - Scale factor" << data[Model124ScaleFactorAvailableEnergyPercent]; + mandatory.maxCharge = m_connection->convertValueWithSSF(data[Model124SetpointMaximumCharge], data[Model124ScaleFactorMaximumChargeDischargeRate]); + mandatory.maxChargeRate = m_connection->convertValueWithSSF(data[Model124SetpointMaximumChargingRate], data[Model124ScaleFactorPercentChargeDischargeRate]); + mandatory.chargingEnabled = data[Model124ActivateStorageControlMode]&0x01; + mandatory.dischargingEnabled = data[Model124ActivateStorageControlMode]&0x02; + mandatory.maxDischargeRate = m_connection->convertValueWithSSF(data[Model124SetpointMaximumDischargeRate], data[Model124ScaleFactorPercentChargeDischargeRate]); + + StorageDataOptional optional; + optional.chargeSatus = ChargingStatus(data[Model124ChargeStatus]); + optional.batteryVoltage = m_connection->convertValueWithSSF(data[Model124InternalBatteryVoltage], data[Model124ScaleFactorBatteryVoltage]); + optional.storageAvailable = m_connection->convertValueWithSSF(data[Model124StorageAvailableAH], data[Model124ScaleFactorMaximumChargingVA]); + optional.gridChargingEnabled = (data[Model124ChargeGridSet] == 1); + optional.currentlyAvailableEnergy = m_connection->convertValueWithSSF(data[Model124CurrentlyAvailableEnergyPercent], data[Model124ScaleFactorAvailableEnergyPercent]); + //qCDebug(dcSunSpec()) << " - Currently available energy" << data[Model124CurrentlyAvailableEnergy]; + //mandatory.chargingState = ChargingStatus(data[Model124ChargeStatus]); + //qCDebug(dcSunSpec()) << " - Charging state" << mandatory.chargingState; + + emit storageDataReceived(mandatory, optional); + } break; - case SunSpec::ModelIdBatteryBaseModel: + case SunSpec::ModelIdBatteryBaseModel: case SunSpec::ModelIdLithiumIonBatteryModel: { + qCDebug(dcSunSpec()) << "Model not yet supported"; } default: break; diff --git a/sunspec/sunspecstorage.h b/sunspec/sunspecstorage.h index 1cb7f1c..87e316c 100644 --- a/sunspec/sunspecstorage.h +++ b/sunspec/sunspecstorage.h @@ -50,11 +50,12 @@ public: QUuid setChargingRate(int rate); QUuid setStorageControlMode(bool chargingEnabled, bool dischargingEnabled); - enum StorageControlBitField { - StorageControlBitFieldCharge = 0, - StorageControlBitFieldDischarge = 1 + enum StorageControl { + StorageControlHold = 0, + StorageControlCharge = 1, + StorageControlDischarge = 2, }; - Q_ENUM(StorageControlBitField) + Q_ENUM(StorageControl) enum GridCharge { PV = 0, @@ -62,33 +63,69 @@ public: }; Q_ENUM(GridCharge) - enum ChargingState { - ChargingStateOff, - ChargingStateEmpty, - ChargingStateDischarging, - ChargingStateCharging, - ChargingStateFull, - ChargingStateHolding, - ChargingStateTesting + enum ChargingStatus { + ChargingStatusOff, + ChargingStatusEmpty, + ChargingStatusDischarging, + ChargingStatusCharging, + ChargingStatusFull, + ChargingStatusHolding, + ChargingStatusTesting }; - Q_ENUM(ChargingState) + Q_ENUM(ChargingStatus) - enum Model124 { // Mandatory register + enum Model124 { // Mandatory registers Model124SetpointMaximumCharge = 0, Model124SetpointMaximumChargingRate = 1, Model124SetpointMaximumDischargeRate = 2, Model124ActivateStorageControlMode = 3, - Model124CurrentlyAvailableEnergy = 6, - Model124ChargeStatus = 9, - Model124ChaGriSet = 15, Model124ScaleFactorMaximumCharge = 16, Model124ScaleFactorMaximumChargeDischargeRate = 17, - Model124ScaleFactorAvailableEnergyPercent = 20 }; Q_ENUM(Model124) + enum Model124Optional { // Optional registers + Model124MaximumChargingVA = 4, // VAChaMax + Model124MinimumReserveStoragePercent = 5, // MinRsvPct + Model124CurrentlyAvailableEnergyPercent = 6, // ChaState + Model124StorageAvailableAH = 7, // StorAval + Model124InternalBatteryVoltage = 8, // InBatV + Model124ChargeStatus = 9, // ChaSt + Model124MaxDischargingRatePercent = 10, // OutWRte + Model124MaxChargingRatePercent = 11, + Model124ChargeDischargeTimeWindow = 12, + Model124ChargeDischargeTimeout = 13, + Model124RampTime = 14, // InOutWRte_RmpTms + Model124ChargeGridSet = 15, // ChGriSet + Model124ScaleFactorMaximumChargingVA = 18, + Model124ScaleFactorMinimumReservePercentage = 19, + Model124ScaleFactorAvailableEnergyPercent = 20, + Model124ScaleFactorStateCharge = 21, + Model124ScaleFactorBatteryVoltage = 22, + Model124ScaleFactorPercentChargeDischargeRate = 23 + }; + Q_ENUM(Model124Optional) + struct StorageData { - ChargingState chargingState; + double maxCharge; // [W] Setpoint for maximum charge. + double maxChargeRate; // [%] Setpoint for maximum charging rate. Default is MaxChaRte. + double maxDischargeRate; // [%] Setpoint for maximum discharge rate. Default is MaxDisChaRte. + bool chargingEnabled; + bool dischargingEnabled; + }; + + struct StorageDataOptional { + // [VA] Setpoint for maximum charging VA. + // [& ]Setpoint for minimum reserve for storage as a percentage of the nominal maximum storage. + double currentlyAvailableEnergy; // [%] Currently available energy as a percent of the capacity rating. + double storageAvailable; // [Ah] State of charge (ChaState) minus storage reserve (MinRsvPct) times capacity rating (AhrRtg). + double batteryVoltage; // [V] Internal battery voltage. + ChargingStatus chargeSatus; // Charge status of storage device. Enumerated value. + // [%] Percent of max discharge rate. + // [%] Percent of max charging rate. + // [s] Time window for charge/discharge rate change. + // [s] Timeout period for charge/discharge rate. + // [s] Ramp time for moving from current setpoint to new setpoint. bool gridChargingEnabled; }; @@ -104,7 +141,7 @@ private slots: signals: void initFinished(bool success); - void storageDataReceived(const StorageData &data); + void storageDataReceived(const StorageData &mandatory, const StorageDataOptional &optional); }; #endif // SUNSPECSTORAGE_H