From 3c42c4837db6aabef3fd410a95be2db7095d28a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 25 Apr 2023 21:13:42 +0200 Subject: [PATCH] SMA: add speedwire battery handling --- sma/integrationpluginsma.cpp | 83 ++++++- sma/integrationpluginsma.h | 1 + sma/integrationpluginsma.json | 89 +++++++- sma/speedwire/speedwirediscovery.cpp | 22 +- sma/speedwire/speedwireinverter.cpp | 324 ++++++++++++++++++--------- sma/speedwire/speedwireinverter.h | 19 ++ 6 files changed, 412 insertions(+), 126 deletions(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 8131d05..e0f8050 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -116,7 +116,13 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info) if (result.serialNumber == 0) continue; - ThingDescriptor descriptor(speedwireMeterThingClassId, "SMA Energy Meter (" + QString::number(result.serialNumber) + ")" , result.address.toString()); + QString thingName = "SMA Energy Meter (" + QString::number(result.serialNumber) + ")"; + + // Note: the SMA Homemanager 2 identifies it self as inverter / data provider...we filter it out here. + if (result.modelId == 372) + thingName = "SMA Home Manager 2.0 (" + QString::number(result.serialNumber) + ")"; + + ThingDescriptor descriptor(speedwireMeterThingClassId, thingName, result.address.toString()); // We found an energy meter, let's check if we already added this one foreach (Thing *existingThing, myThings()) { if (existingThing->paramValue(speedwireMeterThingSerialNumberParamTypeId).toUInt() == result.serialNumber) { @@ -165,6 +171,11 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info) if (result.serialNumber == 0) continue; + // Note: the SMA Homemanager 2 identifies him self as inverter / data provider... + // we filter it out here since it is a meter and also should identify as one. + if (result.modelId == 372) + continue; + ThingDescriptor descriptor(speedwireInverterThingClassId, "SMA inverter (" + QString::number(result.serialNumber) + ")", result.address.toString()); // We found an inverter, let's check if we already added this one foreach (Thing *existingThing, myThings()) { @@ -327,6 +338,7 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) }); connect(meter, &SpeedwireMeter::valuesUpdated, thing, [=](){ + qCDebug(dcSma()) << "Meter values updated for" << thing->name() << meter->currentPower() << "W"; thing->setStateValue(speedwireMeterConnectedStateTypeId, true); thing->setStateValue(speedwireMeterCurrentPowerStateTypeId, meter->currentPower()); thing->setStateValue(speedwireMeterCurrentPowerPhaseAStateTypeId, meter->currentPowerPhaseA()); @@ -406,9 +418,18 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) if (!reachable) { markSpeedwireInverterAsDisconnected(thing); } + + foreach (Thing *batteryThing, myThings().filterByParentId(thing->id()).filterByThingClassId(speedwireBatteryThingClassId)) { + if (reachable) { + thing->setStateValue(speedwireBatteryConnectedStateTypeId, true); + } else { + markSpeedwireBatteryAsDisconnected(batteryThing); + } + } }); connect(inverter, &SpeedwireInverter::valuesUpdated, thing, [=](){ + qCDebug(dcSma()) << "Inverter values updated for" << thing->name() << -inverter->totalAcPower() << "W" << inverter->totalEnergyProduced() << "kWh"; thing->setStateValue(speedwireInverterConnectedStateTypeId, true); thing->setStateValue(speedwireInverterTotalEnergyProducedStateTypeId, inverter->totalEnergyProduced()); thing->setStateValue(speedwireInverterEnergyProducedTodayStateTypeId, inverter->todayEnergyProduced()); @@ -427,9 +448,46 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) thing->setStateValue(speedwireInverterCurrentPowerMpp2StateTypeId, inverter->powerDcMpp2()); }); + connect(inverter, &SpeedwireInverter::batteryValuesUpdated, thing, [=](){ + if (!thing->setupComplete() || !inverter->batteryAvailable()) + return; + + // First check if we already set up a battery for this inverter + Things childThings = myThings().filterByParentId(thing->id()).filterByThingClassId(speedwireBatteryThingClassId); + if (childThings.isEmpty()) { + // Autocreate battery + emit autoThingsAppeared(ThingDescriptors() << ThingDescriptor(speedwireBatteryThingClassId, "SMA Battery", QString(), thing->id())); + } else { + // We can only have one battery as a child + Thing *batteryThing = childThings.first(); + batteryThing->setStateValue(speedwireBatteryConnectedStateTypeId, true); + batteryThing->setStateValue(speedwireBatteryBatteryLevelStateTypeId, inverter->batteryCharge()); + batteryThing->setStateValue(speedwireBatteryBatteryCriticalStateTypeId, inverter->batteryCharge() < 10); + batteryThing->setStateValue(speedwireBatteryTemperatureStateTypeId, inverter->batteryTemperature()); + batteryThing->setStateValue(speedwireBatteryVoltageStateTypeId, inverter->batteryVoltage()); + batteryThing->setStateValue(speedwireBatteryCurrentStateTypeId, inverter->batteryCurrent()); + + double batteryPower = inverter->batteryVoltage() * inverter->batteryCurrent(); // P = U * I + qCDebug(dcSma()) << "Battery values updated for" << batteryThing->name() << batteryPower << "W"; + batteryThing->setStateValue(speedwireBatteryCurrentPowerStateTypeId, batteryPower); + if (batteryPower == 0) { + batteryThing->setStateValue(speedwireBatteryChargingStateStateTypeId, "idle"); + } else if (batteryPower < 0) { + batteryThing->setStateValue(speedwireBatteryChargingStateStateTypeId, "discharging"); + } else if (batteryPower > 0) { + batteryThing->setStateValue(speedwireBatteryChargingStateStateTypeId, "charging"); + } + } + }); + qCDebug(dcSma()) << "Inverter: Start connecting using password" << password; inverter->startConnecting(password); + } else if (thing->thingClassId() == speedwireBatteryThingClassId) { + + qCDebug(dcSma()) << "Battery: Setup SMA battery" << thing; + info->finish(Thing::ThingErrorNoError); + } else if (thing->thingClassId() == modbusInverterThingClassId) { // Handle reconfigure @@ -514,6 +572,20 @@ void IntegrationPluginSma::postSetupThing(Thing *thing) setupRefreshTimer(); + } else if (thing->thingClassId() == speedwireBatteryThingClassId) { + SpeedwireInverter *inverter = m_speedwireInverters.value(myThings().findById(thing->parentId())); + if (inverter) { + if (inverter->reachable()) { + thing->setStateValue(speedwireBatteryConnectedStateTypeId, true); + } else { + markSpeedwireBatteryAsDisconnected(thing); + } + } else { + markSpeedwireBatteryAsDisconnected(thing); + } + + setupRefreshTimer(); + } else if (thing->thingClassId() == modbusInverterThingClassId) { SmaInverterModbusTcpConnection *connection = m_modbusInverters.value(thing); if (connection) { @@ -787,6 +859,15 @@ void IntegrationPluginSma::markSpeedwireInverterAsDisconnected(Thing *thing) thing->setStateValue(speedwireInverterCurrentPowerStateTypeId, 0); } +void IntegrationPluginSma::markSpeedwireBatteryAsDisconnected(Thing *thing) +{ + thing->setStateValue(speedwireBatteryConnectedStateTypeId, false); + thing->setStateValue(speedwireBatteryVoltageStateTypeId, 0); + thing->setStateValue(speedwireBatteryCurrentStateTypeId, 0); + thing->setStateValue(speedwireBatteryCurrentPowerStateTypeId, 0); + thing->setStateValue(speedwireBatteryChargingStateStateTypeId, "idle"); +} + void IntegrationPluginSma::markModbusInverterAsDisconnected(Thing *thing) { thing->setStateValue(modbusInverterVoltagePhaseAStateTypeId, 0); diff --git a/sma/integrationpluginsma.h b/sma/integrationpluginsma.h index 627e33f..d0cb9fd 100644 --- a/sma/integrationpluginsma.h +++ b/sma/integrationpluginsma.h @@ -87,6 +87,7 @@ private: void markSpeedwireMeterAsDisconnected(Thing *thing); void markSpeedwireInverterAsDisconnected(Thing *thing); + void markSpeedwireBatteryAsDisconnected(Thing *thing); void markModbusInverterAsDisconnected(Thing *thing); quint64 getLocalSerialNumber(); diff --git a/sma/integrationpluginsma.json b/sma/integrationpluginsma.json index 2527ce9..02f9f4d 100644 --- a/sma/integrationpluginsma.json +++ b/sma/integrationpluginsma.json @@ -88,7 +88,7 @@ "name": "speedwireMeter", "displayName": "SMA Energy Meter", "createMethods": ["discovery", "user"], - "interfaces": [ "energymeter" ], + "interfaces": [ "energymeter", "connectable"], "paramTypes": [ { "id": "7c81a0c5-9bc6-43bb-a01a-4de5fe656bba", @@ -286,7 +286,7 @@ "displayName": "SMA Inverter", "createMethods": ["discovery", "user"], "setupMethod": "EnterPin", - "interfaces": [ "solarinverter" ], + "interfaces": [ "solarinverter", "connectable"], "paramTypes": [ { "id": "c8098d53-69eb-4d0b-9f07-e43c4a0ea9a9", @@ -447,6 +447,91 @@ } ] }, + { + "id": "b459dad2-f78b-4a87-a7f3-22f3147b83d8", + "name": "speedwireBattery", + "displayName": "SMA Battery", + "createMethods": ["auto"], + "setupMethod": "JustAdd", + "interfaces": [ "energystorage", "connectable"], + "paramTypes": [ ], + "stateTypes": [ + { + "id": "7f242169-c01a-4c9a-ac71-4f9fa5409875", + "name": "connected", + "displayName": "Connected", + "type": "bool", + "defaultValue": false + }, + { + "id": "d2144cad-e507-433b-a9d3-2ab9cf0c1014", + "name": "voltage", + "displayName": "Voltage", + "type": "double", + "unit": "Volt", + "defaultValue": 0, + "cached": false + }, + { + "id": "541c110d-2f56-44bb-8f7e-de55759b942d", + "name": "current", + "displayName": "Current", + "type": "double", + "unit": "Ampere", + "defaultValue": 0, + "cached": false + }, + { + "id": "6a146a40-84da-4392-8466-4176b21280d2", + "name": "temperature", + "displayName": "Temperature", + "type": "double", + "unit": "DegreeCelsius", + "defaultValue": 0.00 + }, + { + "id": "f0f69109-83a4-4b2a-9e16-66aa33c2e169", + "name": "currentPower", + "displayName": "Current power", + "type": "double", + "unit": "Watt", + "defaultValue": 0.00 + }, + { + "id": "38a413cd-3d09-482d-8d25-b602db3b6540", + "name": "capacity", + "displayName": "Available energy", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0.00 + }, + { + "id": "d815aedf-e836-4274-9b51-2f0128420c46", + "name": "batteryCritical", + "displayName": "Battery critical", + "type": "bool", + "defaultValue": false + }, + { + "id": "ec534954-8ee4-46f4-94b6-b48b375b1d7d", + "name": "batteryLevel", + "displayName": "Battery level", + "type": "int", + "unit": "Percentage", + "minValue": 0, + "maxValue": 100, + "defaultValue": 0 + }, + { + "id": "93310fa3-8237-423b-9062-62e0626e8c70", + "name": "chargingState", + "displayName": "Charging state", + "type": "QString", + "possibleValues": ["idle", "charging", "discharging"], + "defaultValue": "idle" + } + ] + }, { "id": "12e0429e-e8ce-48bd-a11c-faaf0bd71856", "name": "modbusInverter", diff --git a/sma/speedwire/speedwirediscovery.cpp b/sma/speedwire/speedwirediscovery.cpp index 24132e6..1392009 100644 --- a/sma/speedwire/speedwirediscovery.cpp +++ b/sma/speedwire/speedwirediscovery.cpp @@ -101,18 +101,8 @@ bool SpeedwireDiscovery::startDiscovery() m_multicastRunning = false; m_unicastRunning = false; - switch(m_deviceType) { - case SpeedwireInterface::DeviceTypeMeter: - startMulticastDiscovery(); - break; - case SpeedwireInterface::DeviceTypeInverter: - startUnicastDiscovery(); - break; - default: - startUnicastDiscovery(); - startMulticastDiscovery(); - break; - } + startUnicastDiscovery(); + startMulticastDiscovery(); return true; } @@ -376,17 +366,15 @@ void SpeedwireDiscovery::processDatagram(const QHostAddress &senderAddress, quin SpeedwireInverterReply *reply = inverter->sendIdentifyRequest(); qCDebug(dcSma()) << "SpeedwireDiscovery: send identify request to" << senderAddress.toString(); connect(reply, &SpeedwireInverterReply::finished, this, [this, inverter, senderAddress, reply](){ - qCDebug(dcSma()) << "SpeedwireDiscovery: ############### identify request finished from" << senderAddress.toString() << reply->error(); + qCDebug(dcSma()) << "SpeedwireDiscovery: identify request finished from" << senderAddress.toString() << reply->error(); SpeedwireInverterReply *loginReply = inverter->sendLoginRequest(); qCDebug(dcSma()) << "SpeedwireDiscovery: make login attempt using the default password."; connect(loginReply, &SpeedwireInverterReply::finished, this, [loginReply, senderAddress](){ - qCDebug(dcSma()) << "SpeedwireDiscovery: ########################## login attempt finished" << senderAddress.toString() << loginReply->error(); + qCDebug(dcSma()) << "SpeedwireDiscovery: login attempt finished" << senderAddress.toString() << loginReply->error(); }); }); - - } else { qCWarning(dcSma()) << "SpeedwireDiscovery: Unhandled data received" << datagram.toHex(); return; @@ -418,7 +406,7 @@ void SpeedwireDiscovery::finishDiscovery() m_multicastSearchRequestTimer.stop(); if (m_multicastSocket) { - if (!m_multicastSocket->leaveMultic astGroup(Speedwire::multicastAddress())) { + if (!m_multicastSocket->leaveMulticastGroup(Speedwire::multicastAddress())) { qCWarning(dcSma()) << "SpeedwireDiscovery: Failed to leave multicast group" << Speedwire::multicastAddress().toString(); } diff --git a/sma/speedwire/speedwireinverter.cpp b/sma/speedwire/speedwireinverter.cpp index 6557c92..0c0e566 100644 --- a/sma/speedwire/speedwireinverter.cpp +++ b/sma/speedwire/speedwireinverter.cpp @@ -144,6 +144,36 @@ double SpeedwireInverter::powerDcMpp2() const return m_powerDcMpp2; } +bool SpeedwireInverter::batteryAvailable() const +{ + return m_batteryAvailable; +} + +double SpeedwireInverter::batteryCycles() const +{ + return m_batteryCycles; +} + +double SpeedwireInverter::batteryCharge() const +{ + return m_batteryCharge; +} + +double SpeedwireInverter::batteryTemperature() const +{ + return m_batteryTemperature; +} + +double SpeedwireInverter::batteryCurrent() const +{ + return m_batteryCurrent; +} + +double SpeedwireInverter::batteryVoltage() const +{ + return m_batteryVoltage; +} + SpeedwireInverterReply *SpeedwireInverter::sendIdentifyRequest() { // Request 534d4100000402a000000001002600106065 09 a0 ffff ffffffff 0000 7d00 52be283a 0000 0000 0000 0180 00020000 000000000000000000000000 @@ -968,42 +998,122 @@ void SpeedwireInverter::processGridFrequencyResponse(const QByteArray &response) void SpeedwireInverter::processBatteryInfoResponse(const QByteArray &response) { - // 10000000 10000000 - // 01574600 c20cbb61 89130000 89130000 89130000 89130000 010000000 - // 0000000 - qCDebug(dcSma()) << "Inverter: ################ Process battery info response" << response.toHex(); - // QDataStream stream(response); - // stream.setByteOrder(QDataStream::LittleEndian); - // quint32 firstWord, secondWord; - // stream >> firstWord >> secondWord; + // Charging + // 32000000 34000000 + // 095b4940 95ed5064 d2000000 d2000000 d2000000 d2000000 01000000 + // 095c4900 95ed5064 98530000 98530000 98530000 98530000 01000000 + // 095d4940 95ed5064 e1010000 e1010000 e1010000 e1010000 01000000 + // 00000000 - //// // Each line has 7 words - //// quint32 measurementId; - //// quint32 measurementType; // ? + // Disacharging + // 32000000 34000000 + // 095b4940 b74e5364 dc000000 dc000000 dc000000 dc000000 01000000 + // 095c4900 b74e5364 e87b0000 e87b0000 e87b0000 e87b0000 01000000 + // 095d4940 b74e5364 b6f8ffff b6f8ffff b6f8ffff b6f8ffff 01000000 + // 00000000 + qCDebug(dcSma()) << "Inverter: Process battery info response" << response.toHex(); - ////// while (!stream.atEnd()) { - ////// // First row - ////// stream >> measurementId; + QDataStream stream(response); + stream.setByteOrder(QDataStream::LittleEndian); + quint32 firstWord, secondWord; + stream >> firstWord >> secondWord; - ////// // End of data, we are done - ////// if (measurementId == 0) - ////// return; + // Each line has 7 words + quint32 measurementId; + quint32 measurementType; // ? - ////// // Unknown - ////// stream >> measurementType; + while (!stream.atEnd()) { + // First row + stream >> measurementId; - ////// quint8 measurmentNumber = static_cast(measurementId & 0xff); - ////// measurementId = measurementId & 0x00ffff00; + // End of data, we are done + if (measurementId == 0) + return; - ////// // Read measurent lines - ////// if (measurementId == 0x465700 && measurmentNumber == 0x01) { - ////// quint32 frequency; - ////// stream >> frequency; - ////// m_gridFrequency = readValue(frequency, 100.0); - ////// qCDebug(dcSma()) << "Inverter: Grid frequency" << m_gridFrequency << "Hz"; - ////// readUntilEndOfMeasurement(stream); - ////// } - ////// } + // Unknown + stream >> measurementType; + + quint8 measurmentNumber = static_cast(measurementId & 0xff); + measurementId = measurementId & 0x00ffff00; + + // Read measurent lines + if (measurementId == 0x495a00) { + quint32 batteryCycles; + stream >> batteryCycles; + m_batteryCycles = readValue(batteryCycles); + qCDebug(dcSma()) << "Battery: Cycle count" << m_batteryCycles; + readUntilEndOfMeasurement(stream); + } else if (measurementId == 0x495b00) { + qint32 batteryTemperature; + stream >> batteryTemperature; + m_batteryTemperature = readValue(batteryTemperature, 10.0); + qCDebug(dcSma()) << "Battery: Temperature" << m_batteryTemperature << "°C"; + readUntilEndOfMeasurement(stream); + } else if (measurementId == 0x495c00) { + quint32 batteryVoltage; + stream >> batteryVoltage; + m_batteryVoltage = readValue(batteryVoltage, 100.0); + qCDebug(dcSma()) << "Battery: Voltage" << m_batteryVoltage << "V"; + readUntilEndOfMeasurement(stream); + } else if (measurementId == 0x495d00) { + qint32 batteryCurrent; + stream >> batteryCurrent; + m_batteryCurrent = readValue(batteryCurrent, 1000.0); + qCDebug(dcSma()) << "Battery: Current" << m_batteryCurrent << "A"; + readUntilEndOfMeasurement(stream); + } else { + quint32 unknwonValue; + stream >> unknwonValue; + qCDebug(dcSma()) << "Battery: Measurement ID:" << QString("0x%1").arg(measurementId , 0, 16) << "Measurement number:" << QString("0x%1").arg(measurmentNumber, 0, 16); + qCDebug(dcSma()) << "Battery: Unknown value:" << QString("0x%1").arg(unknwonValue , 0, 16) << unknwonValue; + readUntilEndOfMeasurement(stream); + } + } +} + +void SpeedwireInverter::processBatteryChargeResponse(const QByteArray &response) +{ + qCDebug(dcSma()) << "Inverter: Process battery charge response" << response.toHex(); + + QDataStream stream(response); + stream.setByteOrder(QDataStream::LittleEndian); + quint32 firstWord, secondWord; + stream >> firstWord >> secondWord; + + // Each line has 7 words + quint32 measurementId; + quint32 measurementType; // ? + + while (!stream.atEnd()) { + // First row + stream >> measurementId; + + // End of data, we are done + if (measurementId == 0) + return; + + // Unknown + stream >> measurementType; + + quint8 measurmentNumber = static_cast(measurementId & 0xff); + measurementId = measurementId & 0x00ffff00; + + // Read measurent lines + + if (measurementId == 0x295a00) { + quint32 batteryCharge; + stream >> batteryCharge; + m_batteryCharge = readValue(batteryCharge); + qCDebug(dcSma()) << "Battery: Level" << m_batteryCharge << "%"; + readUntilEndOfMeasurement(stream); + } else { + quint32 unknwonValue; + stream >> unknwonValue; + qCDebug(dcSma()) << "Battery: Measurement ID: " << QString("0x%1").arg(measurementId , 0, 16) << "Measurement number:" << QString("0x%1").arg(measurmentNumber, 0, 16); + qCDebug(dcSma()) << "Battery: Unknown value:" << QString("0x%1").arg(unknwonValue , 0, 16) << unknwonValue; + readUntilEndOfMeasurement(stream); + } + } } void SpeedwireInverter::processInverterStatusResponse(const QByteArray &response) @@ -1015,6 +1125,7 @@ void SpeedwireInverter::processInverterStatusResponse(const QByteArray &response // TODO: } + void SpeedwireInverter::readUntilEndOfMeasurement(QDataStream &stream) { // Read until end of line (0x01000000) @@ -1044,6 +1155,15 @@ void SpeedwireInverter::setReachable(bool reachable) emit reachableChanged(m_reachable); } +void SpeedwireInverter::setBatteryAvailable(bool available) +{ + if (m_batteryAvailable == available) + return; + + m_batteryAvailable = available; + emit batteryAvailableChanged(m_batteryAvailable); +} + void SpeedwireInverter::processData(const QHostAddress &senderAddress, quint16 senderPort, const QByteArray &data) { // Note: the interface is already filtering out data from other hosts m_address @@ -1151,88 +1271,43 @@ void SpeedwireInverter::setState(State state) setReachable(false); break; case StateInitializing: { - - if (m_modelId == 372) { - // Home manager 2.0, no login, just fetch...testing - - // ############# TESTING ########## - - // Query battery info - qCDebug(dcSma()) << "Inverter: Request battery info..."; - SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryAc, 0x00491e00, 0x00495dff); // Battery infos - connect(reply, &SpeedwireInverterReply::finished, this, [=](){ - if (reply->error() != SpeedwireInverterReply::ErrorNoError) { + // Try to fetch ac power + qCDebug(dcSma()) << "Inverter: Request AC power..."; + SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryAc, 0x00464000, 0x004642ff); + connect(reply, &SpeedwireInverterReply::finished, this, [=](){ + if (reply->error() != SpeedwireInverterReply::ErrorNoError) { + if (reply->error() == SpeedwireInverterReply::ErrorTimeout) { qCWarning(dcSma()) << "Inverter: Failed to query data from inverter:" << reply->request().command() << reply->error(); - // setState(StateDisconnected); - // return; + + // TODO: try to send identify request and retry 3 times before giving up, + // still need to figure out why the inverter stops responding sometimes and how we can + // make it communicative again, a reconfugre always fixes this issue...somehow... + + setState(StateDisconnected); + return; } - qCDebug(dcSma()) << "Inverter: Query request finished successfully" << reply->request().command(); - processBatteryInfoResponse(reply->responsePayload()); - - - qCDebug(dcSma()) << "Inverter: Request battery SoC..."; - SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryAc, 0x00295A00, 0x00295AFF); // SoC - connect(reply, &SpeedwireInverterReply::finished, this, [=](){ - if (reply->error() != SpeedwireInverterReply::ErrorNoError) { - qCWarning(dcSma()) << "Inverter: Failed to query data from inverter:" << reply->request().command() << reply->error(); - // setState(StateDisconnected); - // return; - } else { - processBatteryInfoResponse(reply->responsePayload()); - } - qCDebug(dcSma()) << "Inverter: Request battery termperature..."; - SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryAc, 0x00491E00, 0x00495DFF); // SoC - connect(reply, &SpeedwireInverterReply::finished, this, [=](){ - if (reply->error() != SpeedwireInverterReply::ErrorNoError) { - qCWarning(dcSma()) << "Inverter: Failed to query data from inverter:" << reply->request().command() << reply->error(); - // setState(StateDisconnected); - // return; - } else { - processBatteryInfoResponse(reply->responsePayload()); - } - }); - }); - }); - - } else { - // Try to fetch ac power - qCDebug(dcSma()) << "Inverter: Request AC power..."; - SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryAc, 0x00464000, 0x004642ff); - connect(reply, &SpeedwireInverterReply::finished, this, [=](){ - if (reply->error() != SpeedwireInverterReply::ErrorNoError) { - if (reply->error() == SpeedwireInverterReply::ErrorTimeout) { - qCWarning(dcSma()) << "Inverter: Failed to query data from inverter:" << reply->request().command() << reply->error(); - - // TODO: try to send identify request and retry 3 times before giving up, - // still need to figure out why the inverter stops responding sometimes and how we can - // make it communicative again, a reconfugre always fixes this issue...somehow... - - setState(StateDisconnected); - return; - } - - // Reachable, but received an inverter error, probably not logged - if (reply->error() == SpeedwireInverterReply::ErrorInverterError) { - qCDebug(dcSma()) << "Inverter: Query data request finished with inverter error. Try to login..."; - setState(StateLogin); - return; - } + // Reachable, but received an inverter error, probably not logged + if (reply->error() == SpeedwireInverterReply::ErrorInverterError) { + qCDebug(dcSma()) << "Inverter: Query data request finished with inverter error. Try to login..."; + setState(StateLogin); + return; } + } - // We where able to read data...emit the signal for the setup just incase - emit loginFinished(true); + // We where able to read data...emit the signal for the setup just incase + emit loginFinished(true); - qCDebug(dcSma()) << "Inverter: Query request finished successfully" << reply->request().command(); - processAcPowerResponse(reply->responseData()); + qCDebug(dcSma()) << "Inverter: Query request finished successfully" << reply->request().command(); + processAcPowerResponse(reply->responseData()); - if (m_deviceInformationFetched) { - setState(StateQueryData); - } else { - setState(StateGetInformation); - } - }); - } + + if (m_deviceInformationFetched) { + setState(StateQueryData); + } else { + setState(StateGetInformation); + } + }); break; } case StateLogin: { @@ -1296,6 +1371,7 @@ void SpeedwireInverter::setState(State state) qCDebug(dcSma()) << "Inverter: Get inverter status request finished successfully" << reply->request().command(); processInverterStatusResponse(reply->responsePayload()); + // Query AC voltage / current qCDebug(dcSma()) << "Inverter: Request AC voltage and current..."; SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryAc, 0x00464800, 0x004655ff); @@ -1309,6 +1385,7 @@ void SpeedwireInverter::setState(State state) qCDebug(dcSma()) << "Inverter: Query request finished successfully" << reply->request().command(); processAcVoltageCurrentResponse(reply->responsePayload()); + // Query DC power qCDebug(dcSma()) << "Inverter: Request DC power..."; SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryDc, 0x00251e00, 0x00251eff); @@ -1322,6 +1399,7 @@ void SpeedwireInverter::setState(State state) qCDebug(dcSma()) << "Inverter: Query request finished successfully" << reply->request().command(); processDcPowerResponse(reply->responsePayload()); + // Query DC voltage/current qCDebug(dcSma()) << "Inverter: Request DC voltage and current..."; SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryDc, 0x00451f00, 0x004521ff); @@ -1335,6 +1413,7 @@ void SpeedwireInverter::setState(State state) qCDebug(dcSma()) << "Inverter: Query request finished successfully" << reply->request().command(); processDcVoltageCurrentResponse(reply->responsePayload()); + // Query energy production qCDebug(dcSma()) << "Inverter: Request energy production..."; SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryEnergy, 0x00260100, 0x002622ff); @@ -1348,6 +1427,7 @@ void SpeedwireInverter::setState(State state) qCDebug(dcSma()) << "Inverter: Query request finished successfully" << reply->request().command(); processEnergyProductionResponse(reply->responsePayload()); + // Query total AC power qCDebug(dcSma()) << "Inverter: Request total AC power..."; SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryAc, 0x00263f00, 0x00263fff); @@ -1377,7 +1457,39 @@ void SpeedwireInverter::setState(State state) setReachable(true); emit valuesUpdated(); - setState(StateIdle); + + // ############# Optional ########## + + // Query battery info + qCDebug(dcSma()) << "Inverter: Request battery info..."; + SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryAc, 0x00491e00, 0x00495dff); // Battery infos + connect(reply, &SpeedwireInverterReply::finished, this, [=](){ + if (reply->error() != SpeedwireInverterReply::ErrorNoError) { + qCDebug(dcSma()) << "Inverter: Failed to query battery info from inverter:" << reply->request().command() << reply->error(); + setBatteryAvailable(false); + setState(StateIdle); + } else { + qCDebug(dcSma()) << "Inverter: Process battery info response" << reply->responsePayload().toHex(); + processBatteryInfoResponse(reply->responsePayload()); + } + + qCDebug(dcSma()) << "Inverter: Request battery charge status..."; + SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryAc, 0x00295A00, 0x00295AFF); // Battery SoC + connect(reply, &SpeedwireInverterReply::finished, this, [=](){ + if (reply->error() != SpeedwireInverterReply::ErrorNoError) { + qCWarning(dcSma()) << "Inverter: Failed to query battery charge status from inverter:" << reply->request().command() << reply->error(); + setBatteryAvailable(false); + setState(StateIdle); + } else { + qCDebug(dcSma()) << "Inverter: Process battery charge status response" << reply->responsePayload().toHex(); + processBatteryChargeResponse(reply->responsePayload()); + } + + setBatteryAvailable(true); + emit batteryValuesUpdated(); + setState(StateIdle); + }); + }); }); }); }); diff --git a/sma/speedwire/speedwireinverter.h b/sma/speedwire/speedwireinverter.h index d749eb5..a91a6dd 100644 --- a/sma/speedwire/speedwireinverter.h +++ b/sma/speedwire/speedwireinverter.h @@ -94,6 +94,13 @@ public: double currentDcMpp1() const; double currentDcMpp2() const; + bool batteryAvailable() const; + double batteryCycles() const; + double batteryCharge() const; + double batteryTemperature() const; + double batteryCurrent() const; + double batteryVoltage() const; + // Query methods SpeedwireInverterReply *sendIdentifyRequest(); SpeedwireInverterReply *sendLoginRequest(const QString &password = "0000", bool loginAsUser = true); @@ -113,6 +120,8 @@ signals: void loginFinished(bool success); void stateChanged(State state); void valuesUpdated(); + void batteryAvailableChanged(bool available); + void batteryValuesUpdated(); private: SpeedwireInterface *m_interface = nullptr; @@ -164,6 +173,14 @@ private: double m_currentDcMpp1 = 0; double m_currentDcMpp2 = 0; + bool m_batteryAvailable = false; + + double m_batteryCycles = 0; + double m_batteryVoltage = 0; + double m_batteryCurrent = 0; + double m_batteryCharge = 0; + double m_batteryTemperature = 0; + void setState(State state); void sendNextReply(); @@ -187,12 +204,14 @@ private: void processEnergyProductionResponse(const QByteArray &response); void processGridFrequencyResponse(const QByteArray &response); void processBatteryInfoResponse(const QByteArray &response); + void processBatteryChargeResponse(const QByteArray &response); void processInverterStatusResponse(const QByteArray &response); void readUntilEndOfMeasurement(QDataStream &stream); double readValue(quint32 value, double divisor = 1.0); void setReachable(bool reachable); + void setBatteryAvailable(bool available); private slots: void processData(const QHostAddress &senderAddress, quint16 senderPort, const QByteArray &data);