From cf9a65138f1ce779b3c3e7f851f6c7c245144b75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Fri, 11 Mar 2022 11:06:07 +0100 Subject: [PATCH 1/3] Fix SolarEdge battery detection and phase voltage values for 3 phase meters --- sunspec/integrationpluginsunspec.cpp | 99 ++++++++++------------------ sunspec/solaredgebattery.cpp | 30 ++++++++- 2 files changed, 63 insertions(+), 66 deletions(-) diff --git a/sunspec/integrationpluginsunspec.cpp b/sunspec/integrationpluginsunspec.cpp index 9eaea1d..df38525 100644 --- a/sunspec/integrationpluginsunspec.cpp +++ b/sunspec/integrationpluginsunspec.cpp @@ -689,70 +689,41 @@ void IntegrationPluginSunSpec::searchSolarEdgeBatteries(SunSpecConnection *conne void IntegrationPluginSunSpec::searchSolarEdgeBattery(SunSpecConnection *connection, const ThingId &parentThingId, quint16 startRegister) { - // Read the battery device id to verify if the battery is connected. - // Example: start register 0xE100, device id register 0xE140 + // Read the battery data, if init failed there is no battery connected, + // otherwise there is a battery and we should check if we need to setup the battery thing - QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, startRegister + 0x40, 1); - if (QModbusReply *reply = connection->modbusTcpClient()->sendReadRequest(request, connection->slaveId())) { - if (!reply->isFinished()) { - connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); - connect(reply, &QModbusReply::finished, this, [=]() { + // Create a temporary battery object without thing for init + qCDebug(dcSunSpec()) << "Checking presence of SolarEdge battery on modbus register" << startRegister; + SolarEdgeBattery *battery = new SolarEdgeBattery(nullptr, connection, startRegister, connection); + connect(battery, &SolarEdgeBattery::initFinished, connection, [=](bool success) { + // Delete this object since we used it only for set up + battery->deleteLater(); - if (reply->error() != QModbusDevice::NoError) { - qCDebug(dcSunSpec()) << "SolarEdge battery seems not to be connected on" << startRegister; - return; - } - - const QModbusDataUnit unit = reply->result(); - if (unit.values().isEmpty()) { - return; - } - - quint16 batteryDeviceId = unit.value(0); - if (batteryDeviceId == 255) { - qCDebug(dcSunSpec()) << "No SolarEdge battery connected on" << startRegister; - return; - } - - // Create a temporary battery object without thing - qCDebug(dcSunSpec()) << "Found SolarEdge battery on modbus register" << startRegister; - SolarEdgeBattery *battery = new SolarEdgeBattery(nullptr, connection, startRegister, connection); - connect(battery, &SolarEdgeBattery::initFinished, connection, [=](bool success) { - - // Delete this object since we used it only for set up - battery->deleteLater(); - - if (!success) { - qCWarning(dcSunSpec()) << "Failed to initialize SolarEdge battery on register" << battery->modbusStartRegister(); - return; - } - - qCDebug(dcSunSpec()) << "Battery initialized successfully." << battery->batteryData().manufacturerName << battery->batteryData().model; - // Check if we already created this battery - if (!myThings().filterByParam(solarEdgeBatteryThingSerialNumberParamTypeId, battery->batteryData().serialNumber).isEmpty()) { - qCDebug(dcSunSpec()) << "Battery already set up" << battery->batteryData().serialNumber; - } else { - // Create new battery device in the system - ThingDescriptor descriptor(solarEdgeBatteryThingClassId, battery->batteryData().manufacturerName + " - " + battery->batteryData().model, QString(), parentThingId); - ParamList params; - params.append(Param(solarEdgeBatteryThingModbusAddressParamTypeId, startRegister)); - params.append(Param(solarEdgeBatteryThingManufacturerParamTypeId, battery->batteryData().manufacturerName)); - params.append(Param(solarEdgeBatteryThingDeviceModelParamTypeId, battery->batteryData().model)); - params.append(Param(solarEdgeBatteryThingSerialNumberParamTypeId, battery->batteryData().serialNumber)); - descriptor.setParams(params); - emit autoThingsAppeared({descriptor}); - } - - }); - - // Start initializing battery data - battery->init(); - }); - } else { - delete reply; // broadcast replies return immediately + // If init failed, no battery connected + if (!success) { + qCWarning(dcSunSpec()) << "No SolarEdge battery connected on register" << startRegister << ". Not creating battery device."; return; } - } + + qCDebug(dcSunSpec()) << "Battery initialized successfully." << battery->batteryData().manufacturerName << battery->batteryData().model; + // Check if we already created this battery + if (!myThings().filterByParam(solarEdgeBatteryThingSerialNumberParamTypeId, battery->batteryData().serialNumber).isEmpty()) { + qCDebug(dcSunSpec()) << "Battery already set up" << battery->batteryData().serialNumber; + } else { + // Create new battery device in the system + ThingDescriptor descriptor(solarEdgeBatteryThingClassId, battery->batteryData().manufacturerName + " - " + battery->batteryData().model, QString(), parentThingId); + ParamList params; + params.append(Param(solarEdgeBatteryThingModbusAddressParamTypeId, startRegister)); + params.append(Param(solarEdgeBatteryThingManufacturerParamTypeId, battery->batteryData().manufacturerName)); + params.append(Param(solarEdgeBatteryThingDeviceModelParamTypeId, battery->batteryData().model)); + params.append(Param(solarEdgeBatteryThingSerialNumberParamTypeId, battery->batteryData().serialNumber)); + descriptor.setParams(params); + emit autoThingsAppeared({descriptor}); + } + }); + + // Try to initialize battery data + battery->init(); } double IntegrationPluginSunSpec::calculateSolarEdgePvProduction(Thing *thing, double acPower, double dcPower) @@ -1273,7 +1244,7 @@ void IntegrationPluginSunSpec::onMeterBlockUpdated() thing->setStateValue(sunspecThreePhaseMeterCurrentPhaseCStateTypeId, meter->ampsPhaseC()); thing->setStateValue(sunspecThreePhaseMeterVoltagePhaseAStateTypeId, meter->phaseVoltageAn()); thing->setStateValue(sunspecThreePhaseMeterVoltagePhaseBStateTypeId, meter->phaseVoltageBn()); - thing->setStateValue(sunspecThreePhaseMeterVoltagePhaseBStateTypeId, meter->phaseVoltageCn()); + thing->setStateValue(sunspecThreePhaseMeterVoltagePhaseCStateTypeId, meter->phaseVoltageCn()); thing->setStateValue(sunspecThreePhaseMeterFrequencyStateTypeId, meter->hz()); thing->setStateValue(sunspecThreePhaseMeterVersionStateTypeId, model->commonModelInfo().versionString); break; @@ -1299,7 +1270,7 @@ void IntegrationPluginSunSpec::onMeterBlockUpdated() thing->setStateValue(sunspecThreePhaseMeterCurrentPhaseCStateTypeId, meter->ampsPhaseC()); thing->setStateValue(sunspecThreePhaseMeterVoltagePhaseAStateTypeId, meter->phaseVoltageAn()); thing->setStateValue(sunspecThreePhaseMeterVoltagePhaseBStateTypeId, meter->phaseVoltageBn()); - thing->setStateValue(sunspecThreePhaseMeterVoltagePhaseBStateTypeId, meter->phaseVoltageCn()); + thing->setStateValue(sunspecThreePhaseMeterVoltagePhaseCStateTypeId, meter->phaseVoltageCn()); thing->setStateValue(sunspecThreePhaseMeterFrequencyStateTypeId, meter->hz()); thing->setStateValue(sunspecThreePhaseMeterVersionStateTypeId, model->commonModelInfo().versionString); break; @@ -1325,7 +1296,7 @@ void IntegrationPluginSunSpec::onMeterBlockUpdated() thing->setStateValue(sunspecThreePhaseMeterCurrentPhaseCStateTypeId, meter->ampsPhaseC()); thing->setStateValue(sunspecThreePhaseMeterVoltagePhaseAStateTypeId, meter->phaseVoltageAn()); thing->setStateValue(sunspecThreePhaseMeterVoltagePhaseBStateTypeId, meter->phaseVoltageBn()); - thing->setStateValue(sunspecThreePhaseMeterVoltagePhaseBStateTypeId, meter->phaseVoltageCn()); + thing->setStateValue(sunspecThreePhaseMeterVoltagePhaseCStateTypeId, meter->phaseVoltageCn()); thing->setStateValue(sunspecThreePhaseMeterFrequencyStateTypeId, meter->hz()); thing->setStateValue(sunspecThreePhaseMeterVersionStateTypeId, model->commonModelInfo().versionString); break; @@ -1351,7 +1322,7 @@ void IntegrationPluginSunSpec::onMeterBlockUpdated() thing->setStateValue(sunspecThreePhaseMeterCurrentPhaseCStateTypeId, meter->ampsPhaseC()); thing->setStateValue(sunspecThreePhaseMeterVoltagePhaseAStateTypeId, meter->phaseVoltageAn()); thing->setStateValue(sunspecThreePhaseMeterVoltagePhaseBStateTypeId, meter->phaseVoltageBn()); - thing->setStateValue(sunspecThreePhaseMeterVoltagePhaseBStateTypeId, meter->phaseVoltageCn()); + thing->setStateValue(sunspecThreePhaseMeterVoltagePhaseCStateTypeId, meter->phaseVoltageCn()); thing->setStateValue(sunspecThreePhaseMeterFrequencyStateTypeId, meter->hz()); thing->setStateValue(sunspecThreePhaseMeterVersionStateTypeId, model->commonModelInfo().versionString); break; diff --git a/sunspec/solaredgebattery.cpp b/sunspec/solaredgebattery.cpp index 154a173..c1c5481 100644 --- a/sunspec/solaredgebattery.cpp +++ b/sunspec/solaredgebattery.cpp @@ -84,9 +84,12 @@ void SolarEdgeBattery::readBlockData() if (!reply->isFinished()) { connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); connect(reply, &QModbusReply::finished, this, [=]() { - if (reply->error() != QModbusDevice::NoError) { qCWarning(dcSunSpec()) << "SolarEdgeBattery: Read response error:" << reply->error(); + if (!m_initFinishedSuccess) { + m_timer.stop(); + emit initFinished(false); + } return; } @@ -101,10 +104,21 @@ void SolarEdgeBattery::readBlockData() m_batteryData.serialNumber = SunSpecDataPoint::convertToString(values.mid(SerialNumber, 16)); m_batteryData.batteryDeviceId = values[BatteryDeviceId]; + // Check if there is a battery connected, if so, one of the string must contain vaild data... + if (m_batteryData.manufacturerName.isEmpty() && m_batteryData.model.isEmpty() && m_batteryData.serialNumber.isEmpty() && m_batteryData.firmwareVersion.isEmpty()) { + qCWarning(dcSunSpec()) << "SolarEdgeBattery: No valid information detected about the battery. Probably no battery connected at register" << m_modbusStartRegister; + if (!m_initFinishedSuccess) { + m_timer.stop(); + emit initFinished(false); + } + return; + } + + // 8192 17945 536888857 536888857 1.08652e-19 // 0x2000 0x4619 - qCDebug(dcSunSpec()) << "SolarEdgeBattery: " << m_batteryData.batteryDeviceId << m_batteryData.manufacturerName << m_batteryData.model << m_batteryData.firmwareVersion << m_batteryData.serialNumber; + qCDebug(dcSunSpec()) << "SolarEdgeBattery:" << m_batteryData.batteryDeviceId << m_batteryData.manufacturerName << m_batteryData.model << m_batteryData.firmwareVersion << m_batteryData.serialNumber; m_batteryData.ratedEnergy = SunSpecDataPoint::convertToFloat32(values.mid(RatedEnergy, 2)); m_batteryData.maxChargeContinuesPower = SunSpecDataPoint::convertToFloat32(values.mid(MaxChargeContinuesPower, 2)); m_batteryData.maxDischargeContinuesPower = SunSpecDataPoint::convertToFloat32(values.mid(MaxDischargeContinuesPower, 2)); @@ -120,6 +134,10 @@ void SolarEdgeBattery::readBlockData() connect(reply, &QModbusReply::finished, this, [=]() { if (reply->error() != QModbusDevice::NoError) { qCWarning(dcSunSpec()) << "SolarEdgeBattery: Read response error:" << reply->error(); + if (!m_initFinishedSuccess) { + m_timer.stop(); + emit initFinished(false); + } return; } @@ -164,6 +182,10 @@ void SolarEdgeBattery::readBlockData() } else { qCWarning(dcSunSpec()) << "SolarEdgeBattery: Read error: " << m_connection->modbusTcpClient()->errorString(); delete reply; // broadcast replies return immediately + if (!m_initFinishedSuccess) { + m_timer.stop(); + emit initFinished(false); + } return; } } else { @@ -179,6 +201,10 @@ void SolarEdgeBattery::readBlockData() } else { qCWarning(dcSunSpec()) << "SolarEdgeBattery: Read error: " << m_connection->modbusTcpClient()->errorString(); delete reply; // broadcast replies return immediately + if (!m_initFinishedSuccess) { + m_timer.stop(); + emit initFinished(false); + } return; } } else { From 7c180199d799c1999f77d1b44a5a71f97dd3f521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 21 Mar 2022 08:07:07 +0100 Subject: [PATCH 2/3] Improve debug output for solar edge battery data --- sunspec/solaredgebattery.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sunspec/solaredgebattery.cpp b/sunspec/solaredgebattery.cpp index c1c5481..8dad86c 100644 --- a/sunspec/solaredgebattery.cpp +++ b/sunspec/solaredgebattery.cpp @@ -96,7 +96,7 @@ void SolarEdgeBattery::readBlockData() const QModbusDataUnit unit = reply->result(); QVector values = unit.values(); qCDebug(dcSunSpec()) << "SolarEdgeBattery: Received first block data" << m_modbusStartRegister << values.count(); - qCDebug(dcSunSpec()) << SunSpecDataPoint::registersToString(values); + qCDebug(dcSunSpec()) << "SolarEdgeBattery:" << SunSpecDataPoint::registersToString(values); m_batteryData.manufacturerName = SunSpecDataPoint::convertToString(values.mid(ManufacturerName, 16)); m_batteryData.model = SunSpecDataPoint::convertToString(values.mid(Model, 16)); @@ -145,7 +145,7 @@ void SolarEdgeBattery::readBlockData() QVector values = unit.values(); qCDebug(dcSunSpec()) << "SolarEdgeBattery: Received second block data" << m_modbusStartRegister + offset << values.count(); - qCDebug(dcSunSpec()) << SunSpecDataPoint::registersToString(values); + qCDebug(dcSunSpec()) << "SolarEdgeBattery:" << SunSpecDataPoint::registersToString(values); QVector valueRegisters; valueRegisters = values.mid(BatteryAverageTemperature - offset, 2); From 5b8e71422c2d4e46f3205b92236a0e22985dcb95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 23 Mar 2022 11:40:48 +0100 Subject: [PATCH 3/3] Fix battery detection for solar edge and inital connected state of the battery --- sunspec/integrationpluginsunspec.cpp | 4 ++-- sunspec/solaredgebattery.cpp | 29 +++++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/sunspec/integrationpluginsunspec.cpp b/sunspec/integrationpluginsunspec.cpp index df38525..16b14b5 100644 --- a/sunspec/integrationpluginsunspec.cpp +++ b/sunspec/integrationpluginsunspec.cpp @@ -663,7 +663,7 @@ void IntegrationPluginSunSpec::setupSolarEdgeBattery(ThingSetupInfo *info) connect(battery, &SolarEdgeBattery::blockDataUpdated, this, &IntegrationPluginSunSpec::onSolarEdgeBatteryBlockUpdated); info->finish(Thing::ThingErrorNoError); // Set up successfully, init done, we are connected for sure - thing->setSettingValue(solarEdgeBatteryConnectedStateTypeId, true); + thing->setStateValue(solarEdgeBatteryConnectedStateTypeId, true); }); // Start initializing battery data @@ -752,7 +752,7 @@ double IntegrationPluginSunSpec::calculateSolarEdgePvProduction(Thing *thing, do } } - // This is a solar edge, let's see if we have a batter for this connection + // This is a solar edge, let's see if we have a battery for this connection if (battery) { double meterCurrentPower = meterThing ? meterThing->stateValue("currentPower").toDouble() : 0; qCDebug(dcSunSpec()) << "SolarEdge: found battery for inverter: calculate actual PV power from battery DC power and inverter DC power..."; diff --git a/sunspec/solaredgebattery.cpp b/sunspec/solaredgebattery.cpp index 8dad86c..6920586 100644 --- a/sunspec/solaredgebattery.cpp +++ b/sunspec/solaredgebattery.cpp @@ -93,6 +93,13 @@ void SolarEdgeBattery::readBlockData() return; } + // Example data: + // "(0x3438, 0x565f, 0x4c47, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x4c47, 0x4320, 0x5245, 0x5355, 0x2031, 0x3000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x00ff, 0x0000, 0xffff, 0xff7f, 0xffff, 0xff7f, 0xffff, 0xff7f, 0xffff, 0xff7f, 0xffff, 0xff7f)" + // 255 "48V_LG" "LGC RESU 10" "" "" + // "(0x3438, 0x565f, 0x4c47, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x4c47, 0x4320, 0x5245, 0x5355, 0x2031, 0x3000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x3438, 0x5620, 0x4443, 0x4443, 0x2032, 0x2e32, 0x2e39, 0x3120, 0x424d, 0x5320, 0x302e, 0x302e, 0x3000, 0x0000, 0x0000, 0x0000, 0x3745, 0x3034, 0x3432, 0x4543, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0070, 0x0000, 0x2000, 0x4619, 0x4000, 0x459c, 0x4000, 0x459c, 0x4000, 0x44ce, 0x4000, 0x459c)" + // 112 "48V_LG" "LGC RESU 10" "48V DCDC 2.2.91 BMS 0.0.0" "7E0442EC" + + const QModbusDataUnit unit = reply->result(); QVector values = unit.values(); qCDebug(dcSunSpec()) << "SolarEdgeBattery: Received first block data" << m_modbusStartRegister << values.count(); @@ -103,6 +110,7 @@ void SolarEdgeBattery::readBlockData() m_batteryData.firmwareVersion = SunSpecDataPoint::convertToString(values.mid(FirmwareVersion, 16)); m_batteryData.serialNumber = SunSpecDataPoint::convertToString(values.mid(SerialNumber, 16)); m_batteryData.batteryDeviceId = values[BatteryDeviceId]; + qCDebug(dcSunSpec()) << "SolarEdgeBattery:" << m_batteryData.batteryDeviceId << m_batteryData.manufacturerName << m_batteryData.model << m_batteryData.firmwareVersion << m_batteryData.serialNumber; // Check if there is a battery connected, if so, one of the string must contain vaild data... if (m_batteryData.manufacturerName.isEmpty() && m_batteryData.model.isEmpty() && m_batteryData.serialNumber.isEmpty() && m_batteryData.firmwareVersion.isEmpty()) { @@ -114,17 +122,32 @@ void SolarEdgeBattery::readBlockData() return; } + // For some reason, there might be even data in there but no battery connected, let's check if there are invalid registers - // 8192 17945 536888857 536888857 1.08652e-19 - // 0x2000 0x4619 + // Check if there is a battery connected, if so, one of the string must contain vaild data... + const QVector invalidRegisters = { 0xffff, 0xff7f }; + if (values.mid(RatedEnergy, 2) == invalidRegisters && values.mid(MaxChargeContinuesPower, 2) == invalidRegisters && + values.mid(MaxDischargeContinuesPower, 2) == invalidRegisters && values.mid(MaxChargePeakPower, 2) == invalidRegisters && + values.mid(MaxDischargePeakPower, 2) == invalidRegisters) { + qCWarning(dcSunSpec()) << "SolarEdgeBattery: No valid information detected about the battery. Probably no battery connected at register" << m_modbusStartRegister; + if (!m_initFinishedSuccess) { + m_timer.stop(); + emit initFinished(false); + } + return; + } - qCDebug(dcSunSpec()) << "SolarEdgeBattery:" << m_batteryData.batteryDeviceId << m_batteryData.manufacturerName << m_batteryData.model << m_batteryData.firmwareVersion << m_batteryData.serialNumber; m_batteryData.ratedEnergy = SunSpecDataPoint::convertToFloat32(values.mid(RatedEnergy, 2)); m_batteryData.maxChargeContinuesPower = SunSpecDataPoint::convertToFloat32(values.mid(MaxChargeContinuesPower, 2)); m_batteryData.maxDischargeContinuesPower = SunSpecDataPoint::convertToFloat32(values.mid(MaxDischargeContinuesPower, 2)); m_batteryData.maxChargePeakPower = SunSpecDataPoint::convertToFloat32(values.mid(MaxChargePeakPower, 2)); m_batteryData.maxDischargePeakPower = SunSpecDataPoint::convertToFloat32(values.mid(MaxDischargePeakPower, 2)); + // First block looks good, continue with second block + + // 8192 17945 536888857 536888857 1.08652e-19 + // 0x2000 0x4619 + // Read from 0x6c to 0x86 int offset = 0x6c; QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_modbusStartRegister + offset, 28);