renamed block or map to model

This commit is contained in:
Boernsman 2021-01-22 09:10:29 +01:00
parent 4a9064c647
commit b536b1b16e
13 changed files with 384 additions and 383 deletions

View File

@ -5,12 +5,19 @@ Connect to SunSpec devices over Modbus TCP
## Supported Things
* SunSpec Inverter
* 101 & 111 Single phase inverter
* 102 & 112 Split phase inverter
* 103 & 113 Three phase inverter
* SunSpec Meter
* SunSpec Storage
* Single phase meter
* Split phase meter
* 203 Three phase meter
* SunSpec Storage [124]
## Requirements
* The package 'nymea-plugin-sunspec' must be installed.
* The SunSpec device must support SunSpec over modbus TCP
## More
https://sunspec.org

View File

@ -49,13 +49,13 @@ void IntegrationPluginSunSpec::init()
m_connectedStateTypeIds.insert(sunspecSplitPhaseMeterThingClassId, sunspecSplitPhaseMeterConnectedStateTypeId);
m_connectedStateTypeIds.insert(sunspecThreePhaseMeterThingClassId, sunspecThreePhaseMeterConnectedStateTypeId);
m_mapIdParamTypeIds.insert(sunspecSinglePhaseInverterThingClassId, sunspecSinglePhaseInverterThingMapIdParamTypeId);
m_mapIdParamTypeIds.insert(sunspecSplitPhaseInverterThingClassId, sunspecSplitPhaseInverterThingMapIdParamTypeId);
m_mapIdParamTypeIds.insert(sunspecThreePhaseInverterThingClassId, sunspecThreePhaseInverterThingMapIdParamTypeId);
m_mapIdParamTypeIds.insert(sunspecStorageThingClassId, sunspecStorageThingMapIdParamTypeId);
m_mapIdParamTypeIds.insert(sunspecSinglePhaseMeterThingClassId, sunspecSinglePhaseMeterThingMapIdParamTypeId);
m_mapIdParamTypeIds.insert(sunspecSplitPhaseMeterThingClassId, sunspecSplitPhaseMeterThingMapIdParamTypeId);
m_mapIdParamTypeIds.insert(sunspecThreePhaseMeterThingClassId, sunspecThreePhaseMeterThingMapIdParamTypeId);
m_modelIdParamTypeIds.insert(sunspecSinglePhaseInverterThingClassId, sunspecSinglePhaseInverterThingModelIdParamTypeId);
m_modelIdParamTypeIds.insert(sunspecSplitPhaseInverterThingClassId, sunspecSplitPhaseInverterThingModelIdParamTypeId);
m_modelIdParamTypeIds.insert(sunspecThreePhaseInverterThingClassId, sunspecThreePhaseInverterThingModelIdParamTypeId);
m_modelIdParamTypeIds.insert(sunspecStorageThingClassId, sunspecStorageThingModelIdParamTypeId);
m_modelIdParamTypeIds.insert(sunspecSinglePhaseMeterThingClassId, sunspecSinglePhaseMeterThingModelIdParamTypeId);
m_modelIdParamTypeIds.insert(sunspecSplitPhaseMeterThingClassId, sunspecSplitPhaseMeterThingModelIdParamTypeId);
m_modelIdParamTypeIds.insert(sunspecThreePhaseMeterThingClassId, sunspecThreePhaseMeterThingModelIdParamTypeId);
m_modbusAddressParamTypeIds.insert(sunspecSinglePhaseInverterThingClassId, sunspecSinglePhaseInverterThingModbusAddressParamTypeId);
m_modbusAddressParamTypeIds.insert(sunspecSplitPhaseInverterThingClassId, sunspecSplitPhaseInverterThingModbusAddressParamTypeId);
@ -98,17 +98,17 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info)
QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecConnectionThingIpAddressParamTypeId).toString());
int port = info->thing()->paramValue(sunspecConnectionThingPortParamTypeId).toInt();
//int slaveId = info->thing()->paramValue(sunspecConnectionThingSlaveIdParamTypeId).toInt();
int slaveId = info->thing()->paramValue(sunspecConnectionThingSlaveIdParamTypeId).toInt();
SunSpec *sunSpec;
if (m_sunSpecConnections.contains(thing->id())) {
qCDebug(dcSunSpec()) << "Reconfigure SunSpec connection with new address" << address;
sunSpec = m_sunSpecConnections.value(thing->id());
sunSpec->setHostAddress(address);
} else {
sunSpec = new SunSpec(address, port, this);
m_sunSpecConnections.insert(thing->id(), sunSpec);
m_sunSpecConnections.take(thing->id())->deleteLater();
}
SunSpec *sunSpec;
sunSpec = new SunSpec(address, port, slaveId, this);
sunSpec->setTimeout(configValue(sunSpecPluginTimeoutParamTypeId).toUInt());
sunSpec->setNumberOfRetries(configValue(sunSpecPluginNumberOfRetriesParamTypeId).toUInt());
m_sunSpecConnections.insert(thing->id(), sunSpec);
if (!sunSpec->connectModbus()) {
qCWarning(dcSunSpec()) << "Error connecting to SunSpec device";
@ -127,9 +127,9 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info)
connect(sunSpec, &SunSpec::destroyed, [this, thing] {
m_sunSpecConnections.remove(thing->id());
});
connect(sunSpec, &SunSpec::foundModbusMap, this, &IntegrationPluginSunSpec::onFoundModbusMap);
connect(sunSpec, &SunSpec::modbusMapSearchFinished, this, &IntegrationPluginSunSpec::onModbusMapSearchFinished);
connect(sunSpec, &SunSpec::commonMapReceived, thing, [thing] (const QString &manufacturer, const QString &deviceModel, const QString &serielNumber) {
connect(sunSpec, &SunSpec::foundSunSpecModel, this, &IntegrationPluginSunSpec::onFoundSunSpecModel);
connect(sunSpec, &SunSpec::sunspecModelSearchFinished, this, &IntegrationPluginSunSpec::onSunSpecModelSearchFinished);
connect(sunSpec, &SunSpec::commonModelReceived, thing, [thing] (const QString &manufacturer, const QString &deviceModel, const QString &serielNumber) {
thing->setStateValue(sunspecConnectionConnectedStateTypeId, true);
thing->setStateValue(sunspecConnectionManufacturerStateTypeId, manufacturer);
thing->setStateValue(sunspecConnectionDeviceModelStateTypeId, deviceModel);
@ -140,14 +140,14 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info)
thing->thingClassId() == sunspecSplitPhaseInverterThingClassId ||
thing->thingClassId() == sunspecSinglePhaseInverterThingClassId ) {
uint mapId = thing->paramValue(m_mapIdParamTypeIds.value(thing->thingClassId())).toInt();
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::BlockId(mapId), modbusAddress);
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;
@ -167,14 +167,14 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info)
thing->thingClassId() == sunspecSplitPhaseMeterThingClassId ||
thing->thingClassId() == sunspecThreePhaseMeterThingClassId) {
uint mapId = thing->paramValue(m_mapIdParamTypeIds.value(thing->thingClassId())).toInt();
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::BlockId(mapId), modbusAddress);
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;
@ -192,6 +192,28 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info)
} 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);
}
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());
@ -215,8 +237,8 @@ void IntegrationPluginSunSpec::postSetupThing(Thing *thing)
qCDebug(dcSunSpec()) << "SunSpecConnection not found";
return;
}
connection->readCommonMap();
connection->findModbusMap(QList<SunSpec::BlockId>()); // Discover all maps, without filter
connection->readCommonModel();
connection->findSunSpecModels(QList<SunSpec::ModelId>()); // Discover all models, without filter
} else if (thing->thingClassId() == sunspecSinglePhaseInverterThingClassId ||
thing->thingClassId() == sunspecSplitPhaseInverterThingClassId ||
@ -227,7 +249,7 @@ void IntegrationPluginSunSpec::postSetupThing(Thing *thing)
qCDebug(dcSunSpec()) << "SunSpecInverter not found";
return;
}
sunSpecInverter->getInverterMap();
sunSpecInverter->getInverterModelDataBlock();
} else if (thing->thingClassId() == sunspecSinglePhaseMeterThingClassId ||
thing->thingClassId() == sunspecSplitPhaseMeterThingClassId ||
@ -238,7 +260,7 @@ void IntegrationPluginSunSpec::postSetupThing(Thing *thing)
qCDebug(dcSunSpec()) << "SunSpecMeter not found";
return;
}
sunSpecMeter->getMeterMap();
sunSpecMeter->getMeterModelDataBlock();
} else if (thing->thingClassId() == sunspecStorageThingClassId) {
SunSpecStorage *sunSpecStorage = m_sunSpecStorages.value(thing);
@ -246,7 +268,7 @@ void IntegrationPluginSunSpec::postSetupThing(Thing *thing)
qCDebug(dcSunSpec()) << "SunSpecStorage not found";
return;
}
sunSpecStorage->getStorageMap();
sunSpecStorage->getStorageModelDataBlock();
} else {
Q_ASSERT_X(false, "postSetupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
@ -373,11 +395,11 @@ void IntegrationPluginSunSpec::executeAction(ThingActionInfo *info)
}
}
bool IntegrationPluginSunSpec::checkIfThingExists(uint mapId, uint modbusAddress)
bool IntegrationPluginSunSpec::checkIfThingExists(uint modelId, uint modbusAddress)
{
Q_FOREACH(Thing *thing, myThings()) {
if (thing->paramValue(m_mapIdParamTypeIds.value(thing->thingClassId())).toUInt() == mapId &&
thing->paramValue(m_modbusAddressParamTypeIds.value(thing->thingClassId())).toUInt() == modbusAddress) {
if (thing->paramValue(m_modelIdParamTypeIds.value(thing->thingClassId())).toUInt() == modelId &&
thing->paramValue(m_modbusAddressParamTypeIds.value(thing->thingClassId())).toUInt() == modbusAddress) {
return true;
}
}
@ -387,16 +409,16 @@ bool IntegrationPluginSunSpec::checkIfThingExists(uint mapId, uint modbusAddress
void IntegrationPluginSunSpec::onRefreshTimer()
{
foreach (SunSpec *connection, m_sunSpecConnections) {
connection->readCommonMap(); //check connection
connection->readCommonModel(); //check connection
}
foreach (SunSpecInverter *inverter, m_sunSpecInverters) {
inverter->getInverterMap();
inverter->getInverterModelDataBlock();
}
foreach (SunSpecMeter *meter, m_sunSpecMeters) {
meter->getMeterMap();
meter->getMeterModelDataBlock();
}
foreach (SunSpecStorage *storage, m_sunSpecStorages) {
storage->getStorageMap();
storage->getStorageModelDataBlock();
}
}
@ -410,6 +432,18 @@ void IntegrationPluginSunSpec::onPluginConfigurationChanged(const ParamTypeId &p
m_refreshTimer->stop();
m_refreshTimer->startTimer(refreshTime);
}
} else if (paramTypeId == sunSpecPluginNumberOfRetriesParamTypeId) {
qCDebug(dcSunSpec()) << "Updating number of retires" << value.toUInt();
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;
}
@ -431,23 +465,7 @@ void IntegrationPluginSunSpec::onConnectionStateChanged(bool status)
}
}
void IntegrationPluginSunSpec::onMapHeaderReceived(uint modbusAddress, SunSpec::BlockId mapId, uint mapLength)
{
qCDebug(dcSunSpec()) << "On map header received" << modbusAddress << mapId << mapLength;
}
void IntegrationPluginSunSpec::onMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector<quint16> data)
{
Q_UNUSED(data)
SunSpec *connection = static_cast<SunSpec *>(sender());
Thing *thing = myThings().findById(m_sunSpecConnections.key(connection));
if (!thing)
return;
qCDebug(dcSunSpec()) << "On map received" << mapId << "Map length" << mapLength << "Thing:" << thing->name();
}
void IntegrationPluginSunSpec::onFoundModbusMap(SunSpec::BlockId mapId, int modbusStartRegister)
void IntegrationPluginSunSpec::onFoundSunSpecModel(SunSpec::ModelId modelId, int modbusStartRegister)
{
SunSpec *connection = static_cast<SunSpec *>(sender());
Thing *thing = myThings().findById(m_sunSpecConnections.key(connection));
@ -456,71 +474,71 @@ void IntegrationPluginSunSpec::onFoundModbusMap(SunSpec::BlockId mapId, int modb
return;
}
qCDebug(dcSunSpec()) << "On map received" << mapId << "Map length" << modbusStartRegister << "Thing:" << thing->name();
if (checkIfThingExists(mapId, modbusStartRegister)) {
qCDebug(dcSunSpec()) << "On model received" << modelId << "length" << modbusStartRegister << "Thing:" << thing->name();
if (checkIfThingExists(modelId, modbusStartRegister)) {
return;
}
QString model = thing->stateValue(sunspecConnectionDeviceModelStateTypeId).toString();
switch (mapId) {
case SunSpec::BlockId::BlockIdInverterSinglePhase:
case SunSpec::BlockId::BlockIdInverterSinglePhaseFloat: {
switch (modelId) {
case SunSpec::ModelId::ModelIdInverterSinglePhase:
case SunSpec::ModelId::ModelIdInverterSinglePhaseFloat: {
ThingDescriptor descriptor(sunspecSinglePhaseInverterThingClassId, model+tr(" single phase inverter"), "", thing->id());
ParamList params;
params.append(Param(sunspecSinglePhaseInverterThingMapIdParamTypeId, mapId));
params.append(Param(sunspecSinglePhaseInverterThingModelIdParamTypeId, modelId));
params.append(Param(sunspecSinglePhaseInverterThingModbusAddressParamTypeId, modbusStartRegister));
descriptor.setParams(params);
emit autoThingsAppeared({descriptor});
} break;
case SunSpec::BlockId::BlockIdInverterSplitPhase:
case SunSpec::BlockId::BlockIdInverterSplitPhaseFloat: {
case SunSpec::ModelId::ModelIdInverterSplitPhase:
case SunSpec::ModelId::ModelIdInverterSplitPhaseFloat: {
ThingDescriptor descriptor(sunspecSplitPhaseInverterThingClassId, model+tr(" split phase inverter"), "", thing->id());
ParamList params;
params.append(Param(sunspecSplitPhaseInverterThingMapIdParamTypeId, mapId));
params.append(Param(sunspecSplitPhaseInverterThingModelIdParamTypeId, modelId));
params.append(Param(sunspecSplitPhaseInverterThingModbusAddressParamTypeId, modbusStartRegister));
descriptor.setParams(params);
emit autoThingsAppeared({descriptor});
} break;
case SunSpec::BlockId::BlockIdInverterThreePhase:
case SunSpec::BlockId::BlockIdInverterThreePhaseFloat: {
case SunSpec::ModelId::ModelIdInverterThreePhase:
case SunSpec::ModelId::ModelIdInverterThreePhaseFloat: {
ThingDescriptor descriptor(sunspecThreePhaseInverterThingClassId, model+tr(" three phase inverter"), "", thing->id());
ParamList params;
params.append(Param(sunspecThreePhaseInverterThingMapIdParamTypeId, mapId));
params.append(Param(sunspecThreePhaseInverterThingModelIdParamTypeId, modelId));
params.append(Param(sunspecThreePhaseInverterThingModbusAddressParamTypeId, modbusStartRegister));
descriptor.setParams(params);
emit autoThingsAppeared({descriptor});
} break;
case SunSpec::BlockIdSinglePhaseMeter:
case SunSpec::BlockIdSinglePhaseMeterFloat: {
case SunSpec::ModelIdSinglePhaseMeter:
case SunSpec::ModelIdSinglePhaseMeterFloat: {
ThingDescriptor descriptor(sunspecSinglePhaseMeterThingClassId, model+tr(" Meter"), "", thing->id());
ParamList params;
params.append(Param(sunspecSinglePhaseMeterThingMapIdParamTypeId, mapId));
params.append(Param(sunspecSinglePhaseMeterThingModelIdParamTypeId, modelId));
params.append(Param(sunspecSinglePhaseMeterThingModbusAddressParamTypeId, modbusStartRegister));
descriptor.setParams(params);
emit autoThingsAppeared({descriptor});
} break;
case SunSpec::BlockIdSplitSinglePhaseMeter:
case SunSpec::BlockIdSplitSinglePhaseMeterFloat: {
case SunSpec::ModelIdSplitSinglePhaseMeter:
case SunSpec::ModelIdSplitSinglePhaseMeterFloat: {
ThingDescriptor descriptor(sunspecSplitPhaseMeterThingClassId, model+tr(" Meter"), "", thing->id());
ParamList params;
params.append(Param(sunspecSplitPhaseMeterThingMapIdParamTypeId, mapId));
params.append(Param(sunspecSplitPhaseMeterThingModelIdParamTypeId, modelId));
params.append(Param(sunspecSplitPhaseMeterThingModbusAddressParamTypeId, modbusStartRegister));
descriptor.setParams(params);
emit autoThingsAppeared({descriptor});
} break;
case SunSpec::BlockIdWyeConnectThreePhaseMeterFloat:
case SunSpec::BlockIdDeltaConnectThreePhaseMeterFloat: {
case SunSpec::ModelIdWyeConnectThreePhaseMeterFloat:
case SunSpec::ModelIdDeltaConnectThreePhaseMeterFloat: {
ThingDescriptor descriptor(sunspecThreePhaseMeterThingClassId, model+" Meter", "", thing->id());
ParamList params;
params.append(Param(sunspecThreePhaseMeterThingMapIdParamTypeId, mapId));
params.append(Param(sunspecThreePhaseMeterThingModelIdParamTypeId, modelId));
params.append(Param(sunspecThreePhaseMeterThingModbusAddressParamTypeId, modbusStartRegister));
descriptor.setParams(params);
emit autoThingsAppeared({descriptor});
} break;
case SunSpec::BlockIdStorage: {
case SunSpec::ModelIdStorage: {
ThingDescriptor descriptor(sunspecStorageThingClassId, model+" Storage", "", thing->id());
ParamList params;
params.append(Param(sunspecStorageThingMapIdParamTypeId, mapId));
params.append(Param(sunspecStorageThingModelIdParamTypeId, modelId));
params.append(Param(sunspecThreePhaseInverterThingModbusAddressParamTypeId, modbusStartRegister));
descriptor.setParams(params);
emit autoThingsAppeared({descriptor});
@ -530,7 +548,7 @@ void IntegrationPluginSunSpec::onFoundModbusMap(SunSpec::BlockId mapId, int modb
}
}
void IntegrationPluginSunSpec::onModbusMapSearchFinished(const QHash<SunSpec::BlockId, int> &mapIds)
void IntegrationPluginSunSpec::onSunSpecModelSearchFinished(const QHash<SunSpec::ModelId, int> &modelIds)
{
SunSpec *connection = static_cast<SunSpec *>(sender());
Thing *thing = myThings().findById(m_sunSpecConnections.key(connection));
@ -539,7 +557,7 @@ void IntegrationPluginSunSpec::onModbusMapSearchFinished(const QHash<SunSpec::Bl
return;
}
qCDebug(dcSunSpec()) << "On modbus map search finished, maps:" << mapIds.count();
qCDebug(dcSunSpec()) << "On sunspec model search finished, models:" << modelIds.count();
}
@ -679,6 +697,7 @@ void IntegrationPluginSunSpec::onStorageDataReceived(const SunSpecStorage::Stora
if(!thing) {
return;
}
thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), true);
thing->setStateValue(sunspecStorageStorageStateStateTypeId, storageData.chargingState);
}
@ -688,8 +707,8 @@ void IntegrationPluginSunSpec::onMeterDataReceived(const SunSpecMeter::MeterData
SunSpecMeter *meter = static_cast<SunSpecMeter *>(sender());
Thing *thing = m_sunSpecMeters.key(meter);
if(!thing) {
if (!thing) {
return;
}
//thing->setStateValue(sunspecMeterStorageStateStateTypeId, meterData.event);
thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), true);
}

View File

@ -59,7 +59,7 @@ public:
void executeAction(ThingActionInfo *info) override;
private:
QHash<ThingClassId, ParamTypeId> m_mapIdParamTypeIds;
QHash<ThingClassId, ParamTypeId> m_modelIdParamTypeIds;
QHash<ThingClassId, ParamTypeId> m_modbusAddressParamTypeIds;
QHash<ThingClassId, StateTypeId> m_connectedStateTypeIds;
@ -77,7 +77,7 @@ private:
QHash<Thing *, SunSpecStorage *> m_sunSpecStorages;
QHash<Thing *, SunSpecMeter *> m_sunSpecMeters;
bool checkIfThingExists(uint mapId, uint modbusAddress);
bool checkIfThingExists(uint modelId, uint modbusAddress);
private slots:
void onRefreshTimer();
@ -86,11 +86,8 @@ private slots:
void onConnectionStateChanged(bool status);
void onMapHeaderReceived(uint modbusAddress, SunSpec::BlockId mapId, uint mapLength);
void onMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector<quint16> data);
void onFoundModbusMap(SunSpec::BlockId mapId, int modbusStartRegister);
void onModbusMapSearchFinished(const QHash<SunSpec::BlockId, int> &mapIds);
void onFoundSunSpecModel(SunSpec::ModelId modelId, int modbusStartRegister);
void onSunSpecModelSearchFinished(const QHash<SunSpec::ModelId, int> &modelIds);
void onWriteRequestExecuted(QUuid requestId, bool success);
void onWriteRequestError(QUuid requestId, const QString &error);
@ -99,6 +96,5 @@ private slots:
void onStorageDataReceived(const SunSpecStorage::StorageData &storageData);
void onMeterDataReceived(const SunSpecMeter::MeterData &meterData);
};
#endif // INTEGRATIONPLUGINSUNSPEC_H

View File

@ -37,7 +37,7 @@
"thingClasses": [
{
"name": "sunspecConnection",
"displayName": "SunSpec Device",
"displayName": "SunSpec connection",
"id": "f51853f3-8815-4cf3-b337-45cda1f3e6d5",
"createMethods": [ "User" ],
"interfaces": ["connectable"],
@ -109,8 +109,8 @@
"paramTypes": [
{
"id": "41715d00-a947-4f43-a475-cea05790e01d",
"name":"mapId",
"displayName": "Map id",
"name":"modelId",
"displayName": "Model",
"type": "int",
"readOnly": true
},
@ -243,8 +243,8 @@
"paramTypes": [
{
"id": "c42fb50e-210f-4b53-88eb-fa216e15f88f",
"name":"mapId",
"displayName": "Map id",
"name":"modelId",
"displayName": "Model",
"type": "int",
"readOnly": true
},
@ -404,8 +404,8 @@
"paramTypes": [
{
"id": "8d5b2b58-ce46-406d-844e-f53136afcf09",
"name":"mapId",
"displayName": "Map id",
"name":"modelId",
"displayName": "Model",
"type": "int",
"readOnly": true
},
@ -583,8 +583,8 @@
"paramTypes": [
{
"id": "7d6fcafb-c62e-4a21-aae2-f4041c487149",
"name":"mapId",
"displayName": "Map id",
"name":"modelId",
"displayName": "Model",
"type": "int",
"readOnly": true,
"defaultValue": 0
@ -682,8 +682,8 @@
"paramTypes": [
{
"id": "89aeec6d-abeb-48b5-9594-214ad5db2d03",
"name":"mapId",
"displayName": "Map id",
"name":"modelId",
"displayName": "Model",
"type": "int",
"readOnly": true,
"defaultValue": 0
@ -808,8 +808,8 @@
"paramTypes": [
{
"id": "a1960821-155c-4176-86fa-974429039182",
"name":"mapId",
"displayName": "Map id",
"name":"modelId",
"displayName": "Model",
"type": "int",
"readOnly": true,
"defaultValue": 0
@ -952,8 +952,8 @@
"paramTypes": [
{
"id": "219beb96-b9fe-4dd2-a386-ecfbbab8786d",
"name":"mapId",
"displayName": "Map id",
"name":"modelId",
"displayName": "Model",
"type": "int",
"readOnly": true,
"defaultValue": 0

View File

@ -32,10 +32,11 @@
#include "extern-plugininfo.h"
#include <QtEndian>
SunSpec::SunSpec(const QHostAddress &hostAddress, uint port, QObject *parent) :
SunSpec::SunSpec(const QHostAddress &hostAddress, uint port, uint slaveId, QObject *parent) :
QObject(parent),
m_hostAddress(hostAddress),
m_port(port)
m_port(port),
m_slaveId(slaveId)
{
qCDebug(dcSunSpec()) << "SunSpec: Creating SunSpec connection";
m_modbusTcpClient = new QModbusTcpClient(this);
@ -143,8 +144,8 @@ void SunSpec::findBaseRegister()
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
if ((unit.value(0) << 16 | unit.value(1)) == 0x53756e53) {
//Well-known value. Uniquely identifies this as a SunSpec Modbus Map
qCDebug(dcSunSpec()) << "SunSpec: Found start of modbus map" << baseRegister;
//Well-known value. Uniquely identifies this as a SunSpec Modbus model
qCDebug(dcSunSpec()) << "SunSpec: Found start of modbus model" << baseRegister;
m_baseRegister = baseRegister;
emit foundBaseRegister(baseRegister);
} else {
@ -162,9 +163,9 @@ void SunSpec::findBaseRegister()
}
}
void SunSpec::findModbusMap(const QList<BlockId> &ids, uint modbusAddressOffset)
void SunSpec::findSunSpecModels(const QList<ModelId> &ids, uint modbusAddressOffset)
{
qCDebug(dcSunSpec()) << "SunSpec: Find modbus map. Start register" << m_baseRegister+modbusAddressOffset;
qCDebug(dcSunSpec()) << "SunSpec: Find modbus model. Start register" << m_baseRegister+modbusAddressOffset;
QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_baseRegister+modbusAddressOffset, 2);
if (QModbusReply *reply = m_modbusTcpClient->sendReadRequest(request, m_slaveId)) {
@ -175,23 +176,23 @@ void SunSpec::findModbusMap(const QList<BlockId> &ids, uint modbusAddressOffset)
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
uint modbusAddress = unit.startAddress();
BlockId blockId = BlockId(unit.value(0));
int blockLength = unit.value(1);
if (blockId == BlockIdEnd) {
qCDebug(dcSunSpec()) << "SunSpec: Block Id End";
modbusMapSearchFinished(m_mapList);
ModelId modelId = ModelId(unit.value(0));
int modelLength = unit.value(1);
if (modelId == ModelIdEnd) {
qCDebug(dcSunSpec()) << "SunSpec: Model Id End";
sunspecModelSearchFinished(m_modelList);
return;
}
if (ids.isEmpty() || ids.contains(blockId)) {
// If ids is empty then emit all blocks
qCDebug(dcSunSpec()) << "SunSpec: Found block" << BlockId(blockId) << "with block length" << blockLength;
m_mapList.insert(BlockId(blockId), modbusAddress);
foundModbusMap(BlockId(blockId), modbusAddress);
if (ids.isEmpty() || ids.contains(modelId)) {
// If ids is empty then emit all models
qCDebug(dcSunSpec()) << "SunSpec: Found model" << ModelId(modelId) << "with length" << modelLength;
m_modelList.insert(ModelId(modelId), modbusAddress);
foundSunSpecModel(ModelId(modelId), modbusAddress);
}
findModbusMap(ids, modbusAddress+2+blockLength-m_baseRegister); //read next block
findSunSpecModels(ids, modbusAddress+2+modelLength-m_baseRegister); //read next model
} else {
qCWarning(dcSunSpec()) << "SunSpec: Find modbus map, read response error:" << reply->error();
qCWarning(dcSunSpec()) << "SunSpec: Find modbus model, read response error:" << reply->error();
}
});
} else {
@ -204,9 +205,9 @@ void SunSpec::findModbusMap(const QList<BlockId> &ids, uint modbusAddressOffset)
}
}
void SunSpec::readMapHeader(uint modbusAddress)
void SunSpec::readModelHeader(uint modbusAddress)
{
qCDebug(dcSunSpec()) << "SunSpec: Read map header, modbus address:" << modbusAddress << "Slave ID" << m_slaveId;
qCDebug(dcSunSpec()) << "SunSpec: Read model header, modbus address:" << modbusAddress << "Slave ID" << m_slaveId;
if (modbusAddress == 0 || modbusAddress == 40000 || modbusAddress == 50000)
modbusAddress += 2;
@ -220,34 +221,34 @@ void SunSpec::readMapHeader(uint modbusAddress)
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
uint modbusAddress = unit.startAddress();
BlockId mapId = BlockId(unit.value(0));
int mapLength = unit.value(1);
qCDebug(dcSunSpec()) << "Received block header response. Map ID:" << mapId << "Map length" << mapLength;
mapHeaderReceived(modbusAddress, mapId, mapLength);
ModelId modelId = ModelId(unit.value(0));
int length = unit.value(1);
qCDebug(dcSunSpec()) << "SunSpec: Received model header response. Model ID:" << modelId << "length" << length;
modelHeaderReceived(modbusAddress, modelId, length);
} else {
qCWarning(dcSunSpec()) << "Read response error:" << reply->error();
qCWarning(dcSunSpec()) << "SunSpec: Read response error:" << reply->error();
}
});
connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error) {
qCWarning(dcSunSpec()) << "Modbus reply error:" << error;
qCWarning(dcSunSpec()) << "SunSpec: Modbus reply error:" << error;
reply->finished(); // To make sure it will be deleted
});
} else {
qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString();
qCWarning(dcSunSpec()) << "SunSpec: Read error: " << m_modbusTcpClient->errorString();
delete reply; // broadcast replies return immediately
return;
}
} else {
qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString();
qCWarning(dcSunSpec()) << "SunSpec: Read error: " << m_modbusTcpClient->errorString();
return;
}
}
void SunSpec::readMap(uint modbusAddress, uint modelLength)
void SunSpec::readModelDataBlock(uint modbusAddress, uint length)
{
qCDebug(dcSunSpec()) << "SunSpec: Read map, modbus address" << modbusAddress << "model length" << modelLength << ", Slave ID" << m_slaveId;
qCDebug(dcSunSpec()) << "SunSpec: Read model, modbus address" << modbusAddress << "length" << length << ", Slave ID" << m_slaveId;
QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, modbusAddress, modelLength+2);
QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, modbusAddress, length+2);
if (QModbusReply *reply = m_modbusTcpClient->sendReadRequest(request, m_slaveId)) {
if (!reply->isFinished()) {
@ -257,32 +258,32 @@ void SunSpec::readMap(uint modbusAddress, uint modelLength)
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
uint modbusAddress = unit.startAddress();
BlockId mapId = BlockId(unit.value(0));
uint mapLength = unit.value(1);
qCDebug(dcSunSpec()) << "Received map. Modbus address" << modbusAddress << "Map ID" << mapId << "Map Length" << mapLength;
emit mapReceived(mapId, mapLength, unit.values().mid(2));
ModelId modelId = ModelId(unit.value(0));
uint length = unit.value(1);
qCDebug(dcSunSpec()) << "SunSpec: Received model. Modbus address" << modbusAddress << "model ID" << modelId << "length" << length;
emit modelDataBlockReceived(modelId, length, unit.values().mid(2));
} else {
qCWarning(dcSunSpec()) << "Read response error:" << reply->error();
qCWarning(dcSunSpec()) << "SunSpec: Read response error:" << reply->error();
}
});
connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error) {
qCWarning(dcSunSpec()) << "Modbus reply error:" << error;
qCWarning(dcSunSpec()) << "SunSpec: Modbus reply error:" << error;
reply->finished(); // To make sure it will be deleted
});
} else {
qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString();
qCWarning(dcSunSpec()) << "SunSpec: Read error: " << m_modbusTcpClient->errorString();
delete reply; // broadcast replies return immediately
return;
}
} else {
qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString();
qCWarning(dcSunSpec()) << "SunSpec: Read error: " << m_modbusTcpClient->errorString();
return;
}
}
void SunSpec::readCommonMap()
void SunSpec::readCommonModel()
{
qCDebug(dcSunSpec()) << "SunSpec: Read common block header. Modbus Address" << m_baseRegister+2 << ", Slave ID" << m_slaveId;
qCDebug(dcSunSpec()) << "SunSpec: Read common model header. Modbus Address" << m_baseRegister+2 << ", Slave ID" << m_slaveId;
QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_baseRegister+2, 66);
@ -293,9 +294,6 @@ void SunSpec::readCommonMap()
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
//uint modbusAddress = unit.startAddress();
//BlockId mapId = BlockId(unit.value(0));
//int mapLength = unit.value(1);
m_manufacturer = convertModbusRegisters(unit.values(), MandatoryRegistersModel1::Manufacturer, 16);
m_manufacturer.remove('\x00');
m_deviceModel = convertModbusRegisters(unit.values(), MandatoryRegistersModel1::Model, 16);
@ -303,60 +301,22 @@ void SunSpec::readCommonMap()
m_serialNumber = convertModbusRegisters(unit.values(), MandatoryRegistersModel1::SerialNumber, 16);
m_serialNumber.remove('\x00');
qCDebug(dcSunSpec()) << "SunSpec: Received common block response. Manufacturer" << m_manufacturer << "Model" << m_deviceModel << "Serial number" << m_serialNumber;
commonMapReceived(m_manufacturer, m_deviceModel, m_serialNumber);
commonModelReceived(m_manufacturer, m_deviceModel, m_serialNumber);
} else {
qCWarning(dcSunSpec()) << "SunSpec: Read common map, read response error:" << reply->error();
qCWarning(dcSunSpec()) << "SunSpec: Read common model, read response error:" << reply->error();
}
});
} else {
qCWarning(dcSunSpec()) << "Sunspec: Read common map read error: " << m_modbusTcpClient->errorString();
qCWarning(dcSunSpec()) << "Sunspec: Read common model read error: " << m_modbusTcpClient->errorString();
delete reply; // broadcast replies return immediately
return;
}
} else {
qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString();
qCWarning(dcSunSpec()) << "SunSpec: Read error: " << m_modbusTcpClient->errorString();
return;
}
}
void SunSpec::onReceivedHoldingRegister(quint32 slaveAddress, quint32 modbusRegister, const QVector<quint16> &data)
{
if (modbusRegister == 00000 || modbusRegister == 40000 || modbusRegister == 50000) {
// Common block, 66 registers long + 2 header registers
qCDebug(dcSunSpec()) << "Sunspec Identification:" << convertModbusRegisters(data, 0, 2);
if (convertModbusRegisters(data, 0, 2) != "SunS") {
qCWarning(dcSunSpec()) << "Could not find SunS value at" << modbusRegister << "at Slave" << slaveAddress;
return;
}
m_baseRegister = modbusRegister;
//Mandatory SunSpec Registers
// ID: 40003
qCDebug(dcSunSpec()) << "Id:" << data[MandatoryRegistersModel1::Manufacturer];
// Manufacturer: 40005 | 16
qCDebug(dcSunSpec()) << "Manufacturer:" << QString::fromLatin1(convertModbusRegisters(data, MandatoryRegistersModel1::Manufacturer, 16));
// Thing model: 40021 | 16
qCDebug(dcSunSpec()) << "Thing model:" << QString::fromLatin1(convertModbusRegisters(data, MandatoryRegistersModel1::Model, 16));
// Data manager version: 40037 | 8
qCDebug(dcSunSpec()) << "Data manager version:" << QString::fromLatin1(convertModbusRegisters(data, 36, 8));
// Inverter Version: 40045 | 8
qCDebug(dcSunSpec()) << "Inverter version:" << QString::fromLatin1(convertModbusRegisters(data, 44, 8));
// Serial Number: 40053 | 16
qCDebug(dcSunSpec()) << "Serial number:" << QString::fromLatin1(convertModbusRegisters(data, MandatoryRegistersModel1::SerialNumber, 16));
// Modbus thing address : 40069 | 1
qCDebug(dcSunSpec()) << "Thing modbus address:" << data[67];
};
}
QByteArray SunSpec::convertModbusRegister(const uint16_t &modbusData)
{
uint8_t data[2];
@ -438,22 +398,18 @@ QUuid SunSpec::writeHoldingRegisters(uint registerAddress, const QVector<quint16
connect(reply, &QModbusReply::finished, this, [reply, requestId, this] {
if (reply->error() != QModbusDevice::NoError) {
qCWarning(dcSunSpec()) << "Read response error:" << reply->error();
qCWarning(dcSunSpec()) << "SunSpec: Read response error:" << reply->error();
emit requestExecuted(requestId, false);
return;
}
reply->deleteLater();
emit requestExecuted(requestId, true);
});
connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){
qCWarning(dcSunSpec()) << "Modbus reply error:" << error;
reply->finished(); // To make sure it will be deleted
});
QTimer::singleShot(200, reply, &QModbusReply::deleteLater);
} else {
delete reply; // broadcast replies return immediately
return "";
}
} else {
qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString();
qCWarning(dcSunSpec()) << "SunSpec: Read error: " << m_modbusTcpClient->errorString();
return "";
}
return requestId;

View File

@ -87,89 +87,89 @@ public:
};
Q_ENUM(SunSpecEvent1)
enum BlockId {
BlockIdCommon = 1,
BlockIdBasicAggregator = 2,
BlockIdSecureDatasetReadRequest = 3,
BlockIdSecureDatasetReadResponse = 4,
BlockIdSecureWriteRequest = 5,
BlockIdSecureWriteSequentialRequest = 6,
BlockIdSecureWriteResponseModel = 7,
BlockIdGetDeviceSecurityCertificate = 8,
BlockIdSetOperatorSecurityCertificate = 9,
BlockIdCommunicationInterfaceHeader = 10,
BlockIdEthernetLinkLayer = 11,
BlockIdIPv4 = 12,
BlockIdIPv6 = 13,
BlockIdProxyServer = 14,
BlockIdInterfaceCountersModel = 15,
BlockIdSimpleIpNetwork = 16,
BlockIdSerialInterface = 17,
BlockIdCellularLink = 18,
BlockIdPPPLink = 19,
BlockIdInverterSinglePhase = 101,
BlockIdInverterSplitPhase = 102,
BlockIdInverterThreePhase = 103,
BlockIdInverterSinglePhaseFloat = 111,
BlockIdInverterSplitPhaseFloat = 112,
BlockIdInverterThreePhaseFloat = 113,
BlockIdNameplate = 120,
BlockIdBasicSettings = 121,
BlockIdMeasurementsStatus = 122,
BlockIdImmediateControls = 123,
BlockIdStorage = 124,
BlockIdPricing = 125,
BlockIdStaticVoltVAR = 126,
BlockIdFreqWattParam = 127,
BlockIdDynamicReactiveCurrent = 128,
BlockIdLVRTD = 129,
BlockIdHVRTD = 130,
BlockIdWattPF = 131,
BlockIdVoltWatt = 132,
BlockIdBasicScheduling = 133,
BlockIdFreqWattCrv = 134,
BlockIdLFRT = 135,
BlockIdHFRT = 136,
BlockIdLVRTC = 137,
BlockIdHVRTC = 138,
BlockIdMultipleMPPTInverterExtensionModel = 160,
BlockIdSinglePhaseMeter = 201,
BlockIdSplitSinglePhaseMeter = 202,
BlockIdWyeConnectThreePhaseMeter = 203,
BlockIdDeltaConnectThreePhaseMeter = 204,
BlockIdSinglePhaseMeterFloat = 211,
BlockIdSplitSinglePhaseMeterFloat = 212,
BlockIdWyeConnectThreePhaseMeterFloat = 213,
BlockIdDeltaConnectThreePhaseMeterFloat = 214,
BlockIdSecureACMeterSelectedReadings = 220,
BlockIdIrradianceModel = 302,
BlockIdBackOfModuleTemperatureModel = 303,
BlockIdInclinometerModel = 304,
BlockIdGPS = 305,
BlockIdReferencePointModel = 306,
BlockIdBaseMet = 307,
BlockIdMiniMetModel = 308,
BlockIdStringCombiner = 401,
BlockIdStringCombinerAdvanced = 402,
BlockIdStringCombinerCurrent = 403,
BlockIdStringCombinerCurrentAdvanced = 404,
BlockIdSolarModuleFloat = 501,
BlockIdSolarModule = 502,
BlockIdTrackerController = 601,
BlockIdEnergyStorageBaseModel = 801,
BlockIdBatteryBaseModel = 802,
BlockIdLithiumIonBatteryModel = 803,
BlockIdVerisStatusConfiguration = 64001,
BlockIdMersenGreenString = 64020,
BlockIdEltekInverterExtension = 64101,
BlockIdOutBackAXSDevice = 64110,
BlockIdBasicChargeController = 64111,
BlockIdOutBackFMChargeController = 64112,
BlockIdEnd = 65535
enum ModelId {
ModelIdCommon = 1,
ModelIdBasicAggregator = 2,
ModelIdSecureDatasetReadRequest = 3,
ModelIdSecureDatasetReadResponse = 4,
ModelIdSecureWriteRequest = 5,
ModelIdSecureWriteSequentialRequest = 6,
ModelIdSecureWriteResponseModel = 7,
ModelIdGetDeviceSecurityCertificate = 8,
ModelIdSetOperatorSecurityCertificate = 9,
ModelIdCommunicationInterfaceHeader = 10,
ModelIdEthernetLinkLayer = 11,
ModelIdIPv4 = 12,
ModelIdIPv6 = 13,
ModelIdProxyServer = 14,
ModelIdInterfaceCountersModel = 15,
ModelIdSimpleIpNetwork = 16,
ModelIdSerialInterface = 17,
ModelIdCellularLink = 18,
ModelIdPPPLink = 19,
ModelIdInverterSinglePhase = 101,
ModelIdInverterSplitPhase = 102,
ModelIdInverterThreePhase = 103,
ModelIdInverterSinglePhaseFloat = 111,
ModelIdInverterSplitPhaseFloat = 112,
ModelIdInverterThreePhaseFloat = 113,
ModelIdNameplate = 120,
ModelIdBasicSettings = 121,
ModelIdMeasurementsStatus = 122,
ModelIdImmediateControls = 123,
ModelIdStorage = 124,
ModelIdPricing = 125,
ModelIdStaticVoltVAR = 126,
ModelIdFreqWattParam = 127,
ModelIdDynamicReactiveCurrent = 128,
ModelIdLVRTD = 129,
ModelIdHVRTD = 130,
ModelIdWattPF = 131,
ModelIdVoltWatt = 132,
ModelIdBasicScheduling = 133,
ModelIdFreqWattCrv = 134,
ModelIdLFRT = 135,
ModelIdHFRT = 136,
ModelIdLVRTC = 137,
ModelIdHVRTC = 138,
ModelIdMultipleMPPTInverterExtensionModel = 160,
ModelIdSinglePhaseMeter = 201,
ModelIdSplitSinglePhaseMeter = 202,
ModelIdWyeConnectThreePhaseMeter = 203,
ModelIdDeltaConnectThreePhaseMeter = 204,
ModelIdSinglePhaseMeterFloat = 211,
ModelIdSplitSinglePhaseMeterFloat = 212,
ModelIdWyeConnectThreePhaseMeterFloat = 213,
ModelIdDeltaConnectThreePhaseMeterFloat = 214,
ModelIdSecureACMeterSelectedReadings = 220,
ModelIdIrradianceModel = 302,
ModelIdBackOfModuleTemperatureModel = 303,
ModelIdInclinometerModel = 304,
ModelIdGPS = 305,
ModelIdReferencePointModel = 306,
ModelIdBaseMet = 307,
ModelIdMiniMetModel = 308,
ModelIdStringCombiner = 401,
ModelIdStringCombinerAdvanced = 402,
ModelIdStringCombinerCurrent = 403,
ModelIdStringCombinerCurrentAdvanced = 404,
ModelIdSolarModuleFloat = 501,
ModelIdSolarModule = 502,
ModelIdTrackerController = 601,
ModelIdEnergyStorageBaseModel = 801,
ModelIdBatteryBaseModel = 802,
ModelIdLithiumIonBatteryModel = 803,
ModelIdVerisStatusConfiguration = 64001,
ModelIdMersenGreenString = 64020,
ModelIdEltekInverterExtension = 64101,
ModelIdOutBackAXSDevice = 64110,
ModelIdBasicChargeController = 64111,
ModelIdOutBackFMChargeController = 64112,
ModelIdEnd = 65535
};
Q_ENUM(BlockId)
Q_ENUM(ModelId)
explicit SunSpec(const QHostAddress &hostAddress, uint port = 502, QObject *parent = 0);
explicit SunSpec(const QHostAddress &hostAddress, uint port = 502, uint slaveId = 1, QObject *parent = 0);
~SunSpec();
bool connectModbus();
void setHostAddress(const QHostAddress &hostAddress);
@ -194,14 +194,14 @@ public:
QString m_manufacturer = "Unknown";
QString m_deviceModel = "Unknown";
QString m_serialNumber = "Unknown";
QHash<BlockId, int> m_mapList;
QHash<ModelId, int> m_modelList;
void findBaseRegister();
void findModbusMap(const QList<BlockId> &mapIds, uint modbusAddressOffset = 2);
void findSunSpecModels(const QList<ModelId> &modelIds, uint modbusAddressOffset = 2);
void readCommonMap();
void readMapHeader(uint modbusAddress);
void readMap(uint modbusAddress, uint modelLength); //modbusAddress = model start address, model length is without header
void readCommonModel();
void readModelHeader(uint modbusAddress);
void readModelDataBlock(uint modbusAddress, uint modelLength); //modbusAddress = model start address, model length is without header
float convertValueWithSSF(quint32 rawValue, quint16 sunssf);
float convertFloatValues(quint16 rawValue0, quint16 rawValue1);
@ -214,20 +214,19 @@ public:
signals:
void connectionStateChanged(bool status);
void requestExecuted(QUuid requetId, bool success);
void requestExecuted(const QUuid &requestId, bool success);
void foundBaseRegister(int modbusAddress);
void commonMapReceived(const QString &manufacturer, const QString &deviceModel, const QString &serialNumber);
void commonModelReceived(const QString &manufacturer, const QString &deviceModel, const QString &serialNumber);
void foundModbusMap(BlockId mapId, int modbusStartRegister);
void modbusMapSearchFinished(const QHash<BlockId, int> &mapIds);
void foundSunSpecModel(ModelId modelId, int modbusStartRegister);
void sunspecModelSearchFinished(const QHash<ModelId, int> &mapIds);
void mapHeaderReceived(uint modbusAddress, BlockId mapId, uint mapLength);
void mapReceived(BlockId mapId, uint mapLength, QVector<quint16> data);
void modelHeaderReceived(uint modbusAddress, ModelId mapId, uint mapLength);
void modelDataBlockReceived(ModelId id, uint ength, QVector<quint16> data);
private slots:
void onModbusStateChanged(QModbusDevice::State state);
void onReceivedHoldingRegister(quint32 slaveAddress, quint32 modbusRegister, const QVector<quint16> &values);
};
#endif // SUNSPEC_H

View File

@ -33,17 +33,17 @@
#include <QTimer>
SunSpecInverter::SunSpecInverter(SunSpec *sunspec, SunSpec::BlockId mapId, int modbusAddress) :
SunSpecInverter::SunSpecInverter(SunSpec *sunspec, SunSpec::ModelId modelId, int modbusAddress) :
QObject(sunspec),
m_connection(sunspec),
m_id(mapId),
m_mapModbusStartRegister(modbusAddress)
m_id(modelId),
m_modelModbusStartRegister(modbusAddress)
{
qCDebug(dcSunSpec()) << "SunSpecInverter: Setting up inverter";
connect(m_connection, &SunSpec::mapReceived, this, &SunSpecInverter::onModbusMapReceived);
connect(m_connection, &SunSpec::modelDataBlockReceived, this, &SunSpecInverter::onModelDataBlockReceived);
}
SunSpec::BlockId SunSpecInverter::blockId()
SunSpec::ModelId SunSpecInverter::modelId()
{
return m_id;
}
@ -51,10 +51,10 @@ SunSpec::BlockId SunSpecInverter::blockId()
void SunSpecInverter::init()
{
qCDebug(dcSunSpec()) << "SunSpecInverter: Init";
m_connection->readMapHeader(m_mapModbusStartRegister);
connect(m_connection, &SunSpec::mapHeaderReceived, this, [this] (uint modbusAddress, SunSpec::BlockId mapId, uint mapLength) {
qCDebug(dcSunSpec()) << "SunSpecInverter: Map Header received, modbus address:" << modbusAddress << "map Id:" << mapId << "map length:" << mapLength;
m_mapLength = mapLength;
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;
});
@ -65,33 +65,33 @@ void SunSpecInverter::init()
});
}
void SunSpecInverter::getInverterMap()
void SunSpecInverter::getInverterModelDataBlock()
{
// TODO check map length to modbus max value
m_connection->readMap(m_mapModbusStartRegister, m_mapLength);
m_connection->readModelDataBlock(m_modelModbusStartRegister, m_modelLength);
}
void SunSpecInverter::readInverterBlockHeader()
void SunSpecInverter::getInverterModelHeader()
{
m_connection->readMapHeader(m_mapModbusStartRegister);
m_connection->readModelHeader(m_modelModbusStartRegister);
}
void SunSpecInverter::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector<quint16> data)
void SunSpecInverter::onModelDataBlockReceived(SunSpec::ModelId mapId, uint mapLength, QVector<quint16> data)
{
Q_UNUSED(mapLength)
if (mapId != m_id) {
return;
}
if (mapLength < m_mapLength) {
if (mapLength < m_modelLength) {
qCDebug(dcSunSpec()) << "SunSpecInverter: on modbus map received, map length ist too short" << mapLength;
//return;
}
InverterData inverterData;
switch (mapId) {
case SunSpec::BlockIdInverterSinglePhase:
case SunSpec::BlockIdInverterSplitPhase:
case SunSpec::BlockIdInverterThreePhase: {
case SunSpec::ModelIdInverterSinglePhase:
case SunSpec::ModelIdInverterSplitPhase:
case SunSpec::ModelIdInverterThreePhase: {
inverterData.acCurrent= m_connection->convertValueWithSSF(data[Model10X::Model10XAcCurrent], data[Model10X::Model10XAmpereScaleFactor]);
inverterData.acPower = m_connection->convertValueWithSSF(data[Model10X::Model10XACPower], data[Model10X::Model10XWattScaleFactor]);
@ -105,14 +105,7 @@ void SunSpecInverter::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength
inverterData.phaseVoltageBN = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseVoltageBN], data[Model10X::Model10XVoltageScaleFactor]);
inverterData.phaseVoltageCN = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseVoltageCN], data[Model10X::Model10XVoltageScaleFactor]);
qCDebug(dcSunSpec()) << "Energy with SSF values:";
qCDebug(dcSunSpec()) << " - AC Energy 1:" << data[Model10X::Model10XAcEnergy];
qCDebug(dcSunSpec()) << " - AC Energy 2" << data[Model10X::Model10XAcEnergy+1];
quint32 acEnergy = ((static_cast<quint32>(data.value(Model10X::Model10XAcEnergy))<<16)|static_cast<quint32>(data.value(Model10X::Model10XAcEnergy+1)));
qCDebug(dcSunSpec()) << " - AC Energy combined" << acEnergy;
qCDebug(dcSunSpec()) << " - Scale factor:" << data[Model10X::Model10XWattHoursScaleFactor];
inverterData.acEnergy = m_connection->convertValueWithSSF(acEnergy, data[Model10X::Model10XWattHoursScaleFactor]);
inverterData.cabinetTemperature = m_connection->convertValueWithSSF(data[Model10X::Model10XCabinetTemperature], data[Model10X::Model10XTemperatureScaleFactor]);
@ -121,9 +114,9 @@ void SunSpecInverter::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength
emit inverterDataReceived(inverterData);
} break;
case SunSpec::BlockIdInverterThreePhaseFloat:
case SunSpec::BlockIdInverterSplitPhaseFloat:
case SunSpec::BlockIdInverterSinglePhaseFloat: {
case SunSpec::ModelIdInverterThreePhaseFloat:
case SunSpec::ModelIdInverterSplitPhaseFloat:
case SunSpec::ModelIdInverterSinglePhaseFloat: {
inverterData.acCurrent = m_connection->convertFloatValues(data[Model11X::Model11XAcCurrent], data[Model11X::Model11XAcCurrent+1]);

View File

@ -96,22 +96,22 @@ public:
SunSpec::SunSpecOperatingState operatingState;
};
SunSpecInverter(SunSpec *sunspec, SunSpec::BlockId blockId, int modbusAddress);
SunSpec::BlockId blockId();
SunSpecInverter(SunSpec *sunspec, SunSpec::ModelId modelId, int modbusAddress);
SunSpec::ModelId modelId();
void init();
void getInverterMap();
void getInverterModelDataBlock();
private:
SunSpec *m_connection = nullptr;
SunSpec::BlockId m_id; //e.g. 103 for three phase inverter, 113 for three phase inverter with floating point representation
uint m_mapLength = 0;
uint m_mapModbusStartRegister = 40000;
SunSpec::ModelId m_id; //e.g. 103 for three phase inverter, 113 for three phase inverter with floating point representation
uint m_modelLength = 0;
uint m_modelModbusStartRegister = 40000;
bool m_initFinishedSuccess = false;
void readInverterBlockHeader();
void getInverterModelHeader();
private slots:
void onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector<quint16> data);
void onModelDataBlockReceived(SunSpec::ModelId mapId, uint mapLength, QVector<quint16> data);
signals:
void initFinished(bool success);

View File

@ -31,28 +31,29 @@
#include "sunspecmeter.h"
#include "extern-plugininfo.h"
SunSpecMeter::SunSpecMeter(SunSpec *sunspec, SunSpec::BlockId blockId, int modbusAddress) :
SunSpecMeter::SunSpecMeter(SunSpec *sunspec, SunSpec::ModelId modelId, int modbusAddress) :
QObject(sunspec),
m_connection(sunspec),
m_id(blockId),
m_mapModbusStartRegister(modbusAddress)
m_id(modelId),
m_modelModbusStartRegister(modbusAddress)
{
qCDebug(dcSunSpec()) << "SunSpecMeter: Setting up meter";
connect(m_connection, &SunSpec::mapReceived, this, &SunSpecMeter::onModbusMapReceived);
connect(m_connection, &SunSpec::modelDataBlockReceived, this, &SunSpecMeter::onModelDataBlockReceived);
}
SunSpec::BlockId SunSpecMeter::blockId()
SunSpec::ModelId SunSpecMeter::modelId()
{
return m_id;
}
void SunSpecMeter::init()
{
qCDebug(dcSunSpec()) << "SunSpecInverter: Init";
m_connection->readMapHeader(m_mapModbusStartRegister);
connect(m_connection, &SunSpec::mapHeaderReceived, this, [this] (uint modbusAddress, SunSpec::BlockId mapId, uint mapLength) {
qCDebug(dcSunSpec()) << "SunSpecInverter: Map Header received, modbus address:" << modbusAddress << "map Id:" << mapId << "map length:" << mapLength;
m_mapLength = mapLength;
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;
});
@ -63,21 +64,37 @@ void SunSpecMeter::init()
});
}
void SunSpecMeter::getMeterMap()
void SunSpecMeter::getMeterModelDataBlock()
{
m_connection->readMap(m_mapModbusStartRegister, m_mapLength);
m_connection->readModelDataBlock(m_modelModbusStartRegister, m_modelLength);
}
void SunSpecMeter::readMeterBlockHeader()
void SunSpecMeter::getMeterModelHeader()
{
}
void SunSpecMeter::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector<quint16> data)
void SunSpecMeter::onModelDataBlockReceived(SunSpec::ModelId modelId, uint length, QVector<quint16> data)
{
Q_UNUSED(mapLength)
if (modelId != m_id) {
return;
}
Q_UNUSED(length)
Q_UNUSED(data)
switch (mapId) {
switch (modelId) {
case SunSpec::ModelIdSinglePhaseMeter:
case SunSpec::ModelIdSinglePhaseMeterFloat: {
} break;
case SunSpec::ModelIdSplitSinglePhaseMeter:
case SunSpec::ModelIdSplitSinglePhaseMeterFloat: {
} break;
case SunSpec::ModelIdWyeConnectThreePhaseMeterFloat:
case SunSpec::ModelIdDeltaConnectThreePhaseMeterFloat: {
} break;
default:
break;
}

View File

@ -68,22 +68,21 @@ public:
SunSpec::SunSpecOperatingState operatingState;
};
SunSpecMeter(SunSpec *sunspec, SunSpec::BlockId blockId, int modbusAddress);
SunSpec::BlockId blockId();
SunSpecMeter(SunSpec *sunspec, SunSpec::ModelId modelId, int modbusAddress);
SunSpec::ModelId modelId();
void init();
void getMeterMap();
void getMeterModelHeader();
void getMeterModelDataBlock();
private:
SunSpec *m_connection = nullptr;
SunSpec::BlockId m_id = SunSpec::BlockIdDeltaConnectThreePhaseMeter;
uint m_mapLength = 0;
uint m_mapModbusStartRegister = 40000;
SunSpec::ModelId m_id = SunSpec::ModelIdDeltaConnectThreePhaseMeter;
uint m_modelLength = 0;
uint m_modelModbusStartRegister = 40000;
bool m_initFinishedSuccess = false;
void readMeterBlockHeader();
private slots:
void onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector<quint16> data);
void onModelDataBlockReceived(SunSpec::ModelId modelId, uint length, QVector<quint16> data);
signals:
void initFinished(bool success);

View File

@ -31,29 +31,28 @@
#include "sunspecstorage.h"
#include "extern-plugininfo.h"
SunSpecStorage::SunSpecStorage(SunSpec *sunspec, SunSpec::BlockId blockId, int modbusAddress) :
SunSpecStorage::SunSpecStorage(SunSpec *sunspec, SunSpec::ModelId modelId, int modbusAddress) :
QObject(sunspec),
m_connection(sunspec),
m_id(blockId),
m_mapModbusStartRegister(modbusAddress)
m_id(modelId),
m_modelModbusStartRegister(modbusAddress)
{
qCDebug(dcSunSpec()) << "SunSpecStorage: Setting up storage";
connect(m_connection, &SunSpec::mapReceived, this, &SunSpecStorage::onModbusMapReceived);
connect(m_connection, &SunSpec::modelDataBlockReceived, this, &SunSpecStorage::onModelDataBlockReceived);
}
SunSpec::BlockId SunSpecStorage::blockId()
SunSpec::ModelId SunSpecStorage::modelId()
{
return m_id;
}
void SunSpecStorage::init()
{
qCDebug(dcSunSpec()) << "SunSpecInverter: Init";
m_connection->readMapHeader(m_mapModbusStartRegister);
connect(m_connection, &SunSpec::mapHeaderReceived, this, [this] (uint modbusAddress, SunSpec::BlockId mapId, uint mapLength) {
qCDebug(dcSunSpec()) << "SunSpecInverter: Map Header received, modbus address:" << modbusAddress << "map Id:" << mapId << "map length:" << mapLength;
m_mapLength = mapLength;
qCDebug(dcSunSpec()) << "SunSpecStorage: Init";
m_connection->readModelHeader(m_modelModbusStartRegister);
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;
});
@ -64,14 +63,14 @@ void SunSpecStorage::init()
});
}
void SunSpecStorage::getStorageMap()
void SunSpecStorage::getStorageModelDataBlock()
{
m_connection->readMap(m_mapModbusStartRegister, m_mapLength);
m_connection->readModelDataBlock(m_modelModbusStartRegister, m_modelLength);
}
void SunSpecStorage::readStorageBlockHeader()
void SunSpecStorage::getStorageModelHeader()
{
m_connection->readMapHeader(m_mapModbusStartRegister);
m_connection->readModelHeader(m_modelModbusStartRegister);
}
QUuid SunSpecStorage::setGridCharging(bool enabled)
@ -82,7 +81,7 @@ QUuid SunSpecStorage::setGridCharging(bool enabled)
PV (charging from grid 0 disabled)
GRID (charging from 1 grid enabled*/
uint registerAddress = m_mapModbusStartRegister + Model124::Model124ChaGriSet;
uint registerAddress = m_modelModbusStartRegister + Model124::Model124ChaGriSet;
quint16 value = enabled;
return m_connection->writeHoldingRegister(registerAddress, value);
}
@ -93,7 +92,7 @@ QUuid SunSpecStorage::setStorageControlMode(bool chargingEnabled, bool dischargi
quint16 value = ((static_cast<quint16>(chargingEnabled) << StorageControlBitFieldCharge) |
(static_cast<quint16>(dischargingEnabled) << StorageControlBitFieldDischarge)) ;
uint modbusRegister = m_mapModbusStartRegister + Model124::Model124ActivateStorageControlMode;
uint modbusRegister = m_modelModbusStartRegister + Model124::Model124ActivateStorageControlMode;
return m_connection->writeHoldingRegister(modbusRegister, value);
}
@ -102,7 +101,7 @@ QUuid SunSpecStorage::setChargingRate(int rate)
//Register Name InWRte
/* Defines the maximum charge rate (charge limit). Default is 100% */
uint modbusRegister = m_mapModbusStartRegister + Model124::Model124SetpointMaximumChargingRate;
uint modbusRegister = m_modelModbusStartRegister + Model124::Model124SetpointMaximumChargingRate;
int16_t value = rate * 100;
return m_connection->writeHoldingRegister(modbusRegister, value);
}
@ -111,20 +110,37 @@ QUuid SunSpecStorage::setDischargingRate(int charging)
{
//Register Name OutWRte
/* Defines the maximum discharge rate (discharge limit). Default is 100% */
uint modbusRegister = m_mapModbusStartRegister + Model124::Model124SetpointMaximumDischargeRate;
uint modbusRegister = m_modelModbusStartRegister + Model124::Model124SetpointMaximumDischargeRate;
quint16 value = charging * 100;
return m_connection->writeHoldingRegister(modbusRegister, value);
}
void SunSpecStorage::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, const QVector<quint16> &data)
void SunSpecStorage::onModelDataBlockReceived(SunSpec::ModelId modelId, uint length, const QVector<quint16> &data)
{
Q_UNUSED(mapLength)
switch (mapId) {
case SunSpec::BlockIdStorage: {
Q_UNUSED(length)
if (modelId != m_id) {
return;
}
switch (modelId) {
case SunSpec::ModelIdStorage: {
StorageData storageData;
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);
} break;
case SunSpec::ModelIdBatteryBaseModel:
case SunSpec::ModelIdLithiumIonBatteryModel: {
}
default:
break;
}

View File

@ -38,11 +38,12 @@ class SunSpecStorage : public QObject
{
Q_OBJECT
public:
SunSpecStorage(SunSpec *sunspec, SunSpec::BlockId blockId, int modbusAddress);
SunSpecStorage(SunSpec *sunspec, SunSpec::ModelId modelId, int modbusAddress);
SunSpec::BlockId blockId();
SunSpec::ModelId modelId();
void init();
void getStorageMap();
void getStorageModelHeader();
void getStorageModelDataBlock();
QUuid setGridCharging(bool enabled);
QUuid setDischargingRate(int rate);
@ -93,15 +94,13 @@ public:
private:
SunSpec *m_connection = nullptr;
SunSpec::BlockId m_id = SunSpec::BlockIdEnergyStorageBaseModel;
uint m_mapLength = 0;
uint m_mapModbusStartRegister = 40000;
SunSpec::ModelId m_id = SunSpec::ModelIdEnergyStorageBaseModel;
uint m_modelLength = 0;
uint m_modelModbusStartRegister = 40000;
bool m_initFinishedSuccess = false;
void readStorageBlockHeader();
private slots:
void onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, const QVector<quint16> &data);
void onModelDataBlockReceived(SunSpec::ModelId modelId, uint length, const QVector<quint16> &data);
signals:
void initFinished(bool success);

View File

@ -60,7 +60,7 @@ public:
void getTrackerMap();
private:
BlockId m_id = BlockIdTrackerController;
ModelId m_id = ModelIdTrackerController;
uint m_mapLength = 0;
uint m_mapModbusStartRegister = 40000;