improved sunspec storage

This commit is contained in:
Boernsman 2021-01-22 14:21:23 +01:00
parent b536b1b16e
commit c112e1e6a0
8 changed files with 305 additions and 168 deletions

View File

@ -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 QHash<SunSpec:
qCWarning(dcSunSpec()) << "Thing not found for SunSpec connection" << connection->deviceModel() << 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<SunSpecStorage *>(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)

View File

@ -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

View File

@ -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,

View File

@ -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)) {

View File

@ -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;

View File

@ -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<quint16> data)

View File

@ -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<quint16>(chargingEnabled) << StorageControlBitFieldCharge) |
(static_cast<quint16>(dischargingEnabled) << StorageControlBitFieldDischarge)) ;
quint16 value = ((static_cast<quint16>(chargingEnabled)) |
(static_cast<quint16>(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;

View File

@ -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