Merge PR #60: SunSpec: Fix solar edge battery detection

master
Jenkins nymea 2022-03-23 13:25:39 +01:00
commit d35b204030
2 changed files with 92 additions and 72 deletions

View File

@ -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
@ -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)
@ -781,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...";
@ -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;

View File

@ -84,33 +84,70 @@ 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;
}
// 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<quint16> 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));
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;
// 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...
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;
}
// For some reason, there might be even data in there but no battery connected, let's check if there are invalid registers
// Check if there is a battery connected, if so, one of the string must contain vaild data...
const QVector<quint16> 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);
@ -120,6 +157,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;
}
@ -127,7 +168,7 @@ void SolarEdgeBattery::readBlockData()
QVector<quint16> 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<quint16> valueRegisters;
valueRegisters = values.mid(BatteryAverageTemperature - offset, 2);
@ -164,6 +205,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 +224,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 {