From 674752e3f4285e47fa867a493a5bf0c3e906a2ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 26 Jul 2022 15:48:15 +0200 Subject: [PATCH 01/11] Adding Huawei modbus RTU functionality and update SmartDongle connection and discovery --- huawei/README.md | 37 +- huawei/huawei-fusion-solar-registers.json | 280 +++++++++ huawei/huawei-registers.json | 131 +++- huawei/huawei.pro | 4 +- huawei/huaweifusionsolar.cpp | 693 ++++++++++++++-------- huawei/huaweifusionsolar.h | 13 +- huawei/huaweifusionsolardiscovery.cpp | 166 ++++++ huawei/huaweifusionsolardiscovery.h | 75 +++ huawei/integrationpluginhuawei.cpp | 408 ++++++++++--- huawei/integrationpluginhuawei.h | 9 +- huawei/integrationpluginhuawei.json | 75 ++- 11 files changed, 1525 insertions(+), 366 deletions(-) create mode 100644 huawei/huawei-fusion-solar-registers.json create mode 100644 huawei/huaweifusionsolardiscovery.cpp create mode 100644 huawei/huaweifusionsolardiscovery.h diff --git a/huawei/README.md b/huawei/README.md index dba2cef..82d7646 100644 --- a/huawei/README.md +++ b/huawei/README.md @@ -1,12 +1,39 @@ # Huawei FusionSolar -Connects to a Huawei FusionSolar using Modbus TCP. +Connects to a Huawei FusionSolar using Modbus RTU or TCP. -## Supported things +## Huawei FusionSolar -* Inverter -* Meter (only current power) -* Battery (on Unit 1 and 2) +In order to communicate with the Huawei FusionSolar inverter, a working communikation must be provided. This can be done with a Huawei SmartDongle or with a direct modbus RTU connection. + +Once nymea has connected successfully to the inverter, following devices will be supported: + +* Huawei FusionSolar Inverter (all model supported by the SmartDongle) +* Huawei Meter (connected internally to the Inverter) +* Luna2000 battery units 1 and 2 if connected + +### Huawei SmartDongle + +The [SmartDongle](https://solar.huawei.com/-/media/Solar/attachment/pdf/apac/datasheet/SmartDongle-WLAN-FE.pdf) can be used to communicate +with the Huawei Solar Inverter. In order to allow nymea to read from the device please make sure following requirements are met: + +* SmartDongle software version must be at least `SP130`. +* Connect the Huawei SmartDongle to the network +* Use the official FusonSolar App to enable Modbus TCP access for the Dongle (full access). You can find more informations [here](https://forum.huawei.com/enterprise/en/modbus-tcp-guide/thread/789585-100027?page=1#comments-area). + + +You can also contact the [official Huawei support](mailto:eu_inverter_support@huawei.com) in order to get the update files and instructions, or get it from [here](https://support.huawei.com/enterprise/en/digital-power/sdongle-pid-23826585/software). + +> The SmartDongle provides only access to the registers specified in the Huawei `openAPI`. Full modbus register access requires a modbus RTU connction. + + +### Direct modbus RTU connection (RS485). + +If you want to communicate directly with the inverter using modbus RTU, you need to wire up the connection correctly and set up the Modbus RTU interface in the nymea settings using the right configuations. Then you can discover the Modbus RTU interface while adding the Huawei Modbus RTU inverter in nymea. + +The Modbus RTU offers a full access to all modbus registers available on the inverter. + +Please contact your installer how to enable a modbus RTU connection for your setup. ## More https://solar.huawei.com/eu/ diff --git a/huawei/huawei-fusion-solar-registers.json b/huawei/huawei-fusion-solar-registers.json new file mode 100644 index 0000000..6386d6c --- /dev/null +++ b/huawei/huawei-fusion-solar-registers.json @@ -0,0 +1,280 @@ +{ + "className": "HuaweiFusion", + "protocol": "TCP", + "endianness": "BigEndian", + "errorLimitUntilNotReachable": 15, + "checkReachableRegister": "inverterActivePower", + "enums": [ + { + "name": "InverterDeviceStatus", + "values": [ + { + "key": "StandbyInitializing", + "value": 0 + }, + { + "key": "StandbyDetectingInsulationResistance", + "value": 1 + }, + { + "key": "StandbyDetectingIrradiation", + "value": 2 + }, + { + "key": "StandbyDridDetecting", + "value": 3 + }, + { + "key": "Starting", + "value": 256 + }, + { + "key": "OnGrid", + "value": 512 + }, + { + "key": "PowerLimited", + "value": 513 + }, + { + "key": "SelfDerating", + "value": 514 + }, + { + "key": "ShutdownFault", + "value": 768 + }, + { + "key": "ShutdownCommand", + "value": 769 + }, + { + "key": "ShutdownOVGR", + "value": 770 + }, + { + "key": "ShutdownCommunicationDisconnected", + "value": 771 + }, + { + "key": "ShutdownPowerLimit", + "value": 772 + }, + { + "key": "ShutdownManualStartupRequired", + "value": 773 + }, + { + "key": "ShutdownInputUnderpower", + "value": 774 + }, + { + "key": "GridSchedulingPCurve", + "value": 1025 + }, + { + "key": "GridSchedulingQUCurve", + "value": 1026 + }, + { + "key": "GridSchedulingPFUCurve", + "value": 1027 + }, + { + "key": "GridSchedulingDryContact", + "value": 1028 + }, + { + "key": "GridSchedulingQPCurve", + "value": 1029 + }, + { + "key": "SpotCheckReady", + "value": 1280 + }, + { + "key": "SpotChecking", + "value": 1281 + }, + { + "key": "Inspecting", + "value": 1536 + }, + { + "key": "AfciSelfCheck", + "value": 1792 + }, + { + "key": "IVScanning", + "value": 2048 + }, + { + "key": "DCInputDetection", + "value": 2304 + }, + { + "key": "RunningOffGridCharging", + "value": 2560 + }, + { + "key": "StandbyNoIrradiation", + "value": 40960 + } + ] + }, + { + "name": "BatteryDeviceStatus", + "values": [ + { + "key": "Offline", + "value": 0 + }, + { + "key": "Standby", + "value": 1 + }, + { + "key": "Running", + "value": 1 + }, + { + "key": "Fault", + "value": 1 + }, + { + "key": "SleepMode", + "value": 1 + } + ] + } + ], + "blocks": [ + ], + "registers": [ + { + "id": "inverterActivePower", + "address": 32080, + "size": 2, + "type": "int32", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Inverter active power", + "unit": "kW", + "staticScaleFactor": -3, + "defaultValue": "0", + "access": "RO" + }, + { + "id": "inverterDeviceStatus", + "address": 32089, + "size": 1, + "type": "uint16", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Inverter device status", + "enum": "InverterDeviceStatus", + "defaultValue": "InverterDeviceStatusStandbyInitializing", + "access": "RO" + }, + { + "id": "inverterEnergyProduced", + "address": 32106, + "size": 2, + "type": "uint32", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Inverter energy produced", + "unit": "kWh", + "staticScaleFactor": -2, + "defaultValue": "0", + "access": "RO" + }, + { + "id": "powerMeterActivePower", + "address": 37113, + "size": 2, + "type": "int32", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Power meter active power", + "unit": "W", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "lunaBattery1Status", + "address": 37000, + "size": 1, + "type": "uint16", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Luna 2000 Battery 1 status", + "enum": "BatteryDeviceStatus", + "defaultValue": "BatteryDeviceStatusOffline", + "access": "RO" + }, + { + "id": "lunaBattery1Power", + "address": 37001, + "size": 2, + "type": "int32", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Luna 2000 Battery 1 power", + "unit": "W", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "lunaBattery1Soc", + "address": 37004, + "size": 1, + "type": "uint16", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Luna 2000 Battery 1 state of charge", + "staticScaleFactor": -1, + "unit": "%", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "lunaBattery2Status", + "address": 37741, + "size": 1, + "type": "uint16", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Luna 2000 Battery 2 status", + "enum": "BatteryDeviceStatus", + "defaultValue": "BatteryDeviceStatusOffline", + "access": "RO" + }, + { + "id": "lunaBattery2Power", + "address": 37743, + "size": 2, + "type": "int32", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Luna 2000 Battery 2 power", + "unit": "W", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "lunaBattery2Soc", + "address": 37738, + "size": 1, + "type": "uint16", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Luna 2000 Battery 2 state of charge", + "staticScaleFactor": -1, + "unit": "%", + "defaultValue": "0", + "access": "RO" + } + ] +} + diff --git a/huawei/huawei-registers.json b/huawei/huawei-registers.json index 25629a4..3ae47e3 100644 --- a/huawei/huawei-registers.json +++ b/huawei/huawei-registers.json @@ -1,6 +1,6 @@ { "className": "Huawei", - "protocol": "TCP", + "protocol": "RTU", "endianness": "BigEndian", "errorLimitUntilNotReachable": 15, "checkReachableRegister": "inverterActivePower", @@ -149,6 +149,135 @@ } ], "blocks": [ + { + "id": "identifyer", + "readSchedule": "init", + "registers": [ + { + "id": "model", + "address": 30000, + "size": 15, + "type": "string", + "registerType": "holdingRegister", + "description": "Model", + "access": "RO" + }, + { + "id": "serialNumber", + "address": 30015, + "size": 10, + "type": "string", + "registerType": "holdingRegister", + "description": "Serial number", + "access": "RO" + }, + { + "id": "productNumber", + "address": 30025, + "size": 10, + "type": "string", + "registerType": "holdingRegister", + "description": "Product number", + "access": "RO" + } + ] + }, + { + "id": "setup", + "readSchedule": "init", + "registers": [ + { + "id": "modelId", + "address": 30070, + "size": 1, + "type": "uint16", + "registerType": "holdingRegister", + "description": "Model ID", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "numberOfPvStrings", + "address": 30071, + "size": 1, + "type": "uint16", + "registerType": "holdingRegister", + "description": "Number of PV strings", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "numberOfMppTracks", + "address": 30072, + "size": 1, + "type": "uint16", + "registerType": "holdingRegister", + "description": "Number of MPP tracks", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "ratedPower", + "address": 30073, + "size": 2, + "type": "uint32", + "registerType": "holdingRegister", + "description": "Rated power (Pn)", + "unit": "kW", + "staticScaleFactor": -3, + "defaultValue": "0", + "access": "RO" + }, + { + "id": "maxActivePower", + "address": 30075, + "size": 2, + "type": "uint32", + "registerType": "holdingRegister", + "description": "Maximum active power (P max)", + "unit": "kW", + "staticScaleFactor": -3, + "defaultValue": "0", + "access": "RO" + }, + { + "id": "maxApparentPower", + "address": 30077, + "size": 2, + "type": "uint32", + "registerType": "holdingRegister", + "description": "Maximum apparant power (S max)", + "unit": "kVA", + "staticScaleFactor": -3, + "defaultValue": "0", + "access": "RO" + }, + { + "id": "maxReactivePowerToGrid", + "address": 30079, + "size": 2, + "type": "int32", + "registerType": "holdingRegister", + "description": "Maximum reactive power (Q max) - fed to grid", + "unit": "kVar", + "staticScaleFactor": -3, + "defaultValue": "0", + "access": "RO" + }, + { + "id": "maxReactivePowerFromGrid", + "address": 30081, + "size": 2, + "type": "int32", + "registerType": "holdingRegister", + "description": "Maximum reactive power (Q max) - absorbed from grid", + "unit": "kVar", + "staticScaleFactor": -3, + "defaultValue": "0", + "access": "RO" + } + ] + } ], "registers": [ { diff --git a/huawei/huawei.pro b/huawei/huawei.pro index d5d9324..fbc8b0d 100644 --- a/huawei/huawei.pro +++ b/huawei/huawei.pro @@ -1,14 +1,16 @@ include(../plugins.pri) # Generate modbus connection -MODBUS_CONNECTIONS += huawei-registers.json #MODBUS_TOOLS_CONFIG += VERBOSE +MODBUS_CONNECTIONS += huawei-fusion-solar-registers.json huawei-registers.json include(../modbus.pri) HEADERS += \ huaweifusionsolar.h \ + huaweifusionsolardiscovery.h \ integrationpluginhuawei.h SOURCES += \ huaweifusionsolar.cpp \ + huaweifusionsolardiscovery.cpp \ integrationpluginhuawei.cpp diff --git a/huawei/huaweifusionsolar.cpp b/huawei/huaweifusionsolar.cpp index 9298378..a57c960 100644 --- a/huawei/huaweifusionsolar.cpp +++ b/huawei/huaweifusionsolar.cpp @@ -1,4 +1,4 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright 2013 - 2022, nymea GmbH * Contact: contact@nymea.io @@ -35,15 +35,57 @@ NYMEA_LOGGING_CATEGORY(dcHuaweiFusionSolar, "HuaweiFusionSolar") HuaweiFusionSolar::HuaweiFusionSolar(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent) : - HuaweiModbusTcpConnection(hostAddress, port, slaveId, parent) + HuaweiFusionModbusTcpConnection(hostAddress, port, slaveId, parent) { - + connect(this, &HuaweiFusionModbusTcpConnection::connectionStateChanged, this, [=](bool connected){ + if (!connected) { + m_registersQueue.clear(); + } + }); } bool HuaweiFusionSolar::initialize() { - // No init registers defined. Nothing to be done and we are finished. - emit initializationFinished(true); + if (!reachable()) { + qCWarning(dcHuaweiFusionSolar()) << "Tried to initialize but the device is not to be reachable."; + return false; + } + + if (m_initReply) { + qCWarning(dcHuaweiFusionSolar()) << "Tried to initialize but the init process is already running."; + return false; + } + + qCDebug(dcHuaweiFusionSolar()) << "Initialize connection by reading \"Inverter active power\" register:" << 32080 << "size:" << 2; + m_initReply = readInverterActivePower(); + if (!m_initReply) { + qCWarning(dcHuaweiFusionSolar()) << "Error occurred while initializing connection and read \"Inverter active power\" register from" << hostAddress().toString() << errorString(); + return false; + } + + if (m_initReply->isFinished()) { + m_initReply->deleteLater(); // Broadcast reply returns immediatly + m_initReply = nullptr; + return false; + } + + connect(m_initReply, &QModbusReply::finished, this, [this](){ + if (m_initReply->error() == QModbusDevice::NoError) { + qCDebug(dcHuaweiFusionSolar()) << "Initialization finished of HuaweiFusionSolar" << hostAddress().toString() << "finished successfully"; + emit initializationFinished(true); + } else { + qCWarning(dcHuaweiFusionSolar()) << "Initialization finished of HuaweiFusionSolar" << hostAddress().toString() << "failed."; + emit initializationFinished(false); + } + + m_initReply->deleteLater(); + m_initReply = nullptr; + }); + + connect(m_initReply, &QModbusReply::errorOccurred, this, [this] (QModbusDevice::Error error){ + qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while initializing connection and read \"Inverter active power\" registers from" << hostAddress().toString() << error << m_initReply->errorString(); + }); + return true; } @@ -54,19 +96,19 @@ bool HuaweiFusionSolar::update() return true; // Add the requests - m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterInverterActivePower); - m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterInverterDeviceStatus); - m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterInverterEnergyProduced); - m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery1Status); + m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterInverterActivePower); + m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterInverterDeviceStatus); + m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterInverterEnergyProduced); + m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery1Status); if (m_battery1Available) { - m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery1Power); - m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery1Soc); + m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery1Power); + m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery1Soc); } - m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterPowerMeterActivePower); - m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery2Status); + m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterPowerMeterActivePower); + m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery2Status); if (m_battery2Available) { - m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery2Power); - m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery2Soc); + m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery2Power); + m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery2Soc); } // Note: since huawei can only process one request at the time, we need to queue the requests @@ -89,316 +131,399 @@ void HuaweiFusionSolar::readNextRegister() m_currentRegisterRequest = m_registersQueue.dequeue(); switch (m_currentRegisterRequest) { - case HuaweiModbusTcpConnection::RegisterInverterActivePower: { + case HuaweiFusionModbusTcpConnection::RegisterInverterActivePower: { + // Update registers from Inverter active power qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Inverter active power\" register:" << 32080 << "size:" << 2; QModbusReply *reply = readInverterActivePower(); - if (reply) { - if (!reply->isFinished()) { - connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); - connect(reply, &QModbusReply::finished, this, [this, reply](){ - if (reply->error() == QModbusDevice::NoError) { - const QModbusDataUnit unit = reply->result(); - const QVector values = unit.values(); - qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Inverter active power\" register" << 32080 << "size:" << 2 << values; - processInverterActivePowerRegisterValues(values); - } - finishRequest(); - }); - - connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ - qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Inverter active power\" registers from" << hostAddress().toString() << error << reply->errorString(); - }); - } else { - reply->deleteLater(); // Broadcast reply returns immediatly - finishRequest(); - } - } else { + if (!reply) { qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Inverter active power\" registers from" << hostAddress().toString() << errorString(); finishRequest(); + return; } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + finishRequest(); + return; + } + + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [this, reply](){ + handleModbusError(reply->error()); + if (reply->error() == QModbusDevice::NoError) { + const QModbusDataUnit unit = reply->result(); + qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Inverter active power\" register" << 32080 << "size:" << 2 << unit.values(); + processInverterActivePowerRegisterValues(unit.values()); + } + + finishRequest(); + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + if (reply->error() == QModbusDevice::ProtocolError) { + QModbusResponse response = reply->rawResult(); + if (response.isException()) { + qCDebug(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Inverter active power\" registers from" << hostAddress().toString() << exceptionToString(response.exceptionCode()); + } + } else { + qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Inverter active power\" registers from" << hostAddress().toString() << error << reply->errorString(); + } + }); + break; } - case HuaweiModbusTcpConnection::RegisterInverterDeviceStatus: { + case HuaweiFusionModbusTcpConnection::RegisterInverterDeviceStatus: { // Update registers from Inverter device status qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Inverter device status\" register:" << 32089 << "size:" << 1; QModbusReply *reply = readInverterDeviceStatus(); - if (reply) { - if (!reply->isFinished()) { - connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); - connect(reply, &QModbusReply::finished, this, [this, reply](){ - if (reply->error() == QModbusDevice::NoError) { - const QModbusDataUnit unit = reply->result(); - const QVector values = unit.values(); - qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Inverter device status\" register" << 32089 << "size:" << 1 << values; - processInverterDeviceStatusRegisterValues(values); - qCDebug(dcHuaweiFusionSolar()) << "Inverter status" << inverterDeviceStatus(); - } - finishRequest(); - }); - - connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ - qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Inverter device status\" registers from" << hostAddress().toString() << error << reply->errorString(); - }); - } else { - reply->deleteLater(); // Broadcast reply returns immediatly - finishRequest(); - } - } else { + if (!reply) { qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Inverter device status\" registers from" << hostAddress().toString() << errorString(); finishRequest(); + return; } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + finishRequest(); + return; + } + + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [this, reply](){ + handleModbusError(reply->error()); + if (reply->error() == QModbusDevice::NoError) { + const QModbusDataUnit unit = reply->result(); + qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Inverter device status\" register" << 32089 << "size:" << 1 << unit.values(); + processInverterDeviceStatusRegisterValues(unit.values()); + } + finishRequest(); + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + if (reply->error() == QModbusDevice::ProtocolError) { + QModbusResponse response = reply->rawResult(); + if (response.isException()) { + qCDebug(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Inverter device status\" registers from" << hostAddress().toString() << exceptionToString(response.exceptionCode()); + } + } else { + qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Inverter device status\" registers from" << hostAddress().toString() << error << reply->errorString(); + } + }); + break; } - case HuaweiModbusTcpConnection::RegisterInverterEnergyProduced: { + case HuaweiFusionModbusTcpConnection::RegisterInverterEnergyProduced: { // Update registers from Inverter energy produced qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Inverter energy produced\" register:" << 32106 << "size:" << 2; QModbusReply *reply = readInverterEnergyProduced(); - if (reply) { - if (!reply->isFinished()) { - connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); - connect(reply, &QModbusReply::finished, this, [this, reply](){ - if (reply->error() == QModbusDevice::NoError) { - const QModbusDataUnit unit = reply->result(); - const QVector values = unit.values(); - qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Inverter energy produced\" register" << 32106 << "size:" << 2 << values; - processInverterEnergyProducedRegisterValues(values); - } - finishRequest(); - }); - - connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ - qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Inverter energy produced\" registers from" << hostAddress().toString() << error << reply->errorString(); - }); - } else { - reply->deleteLater(); // Broadcast reply returns immediatly - finishRequest(); - } - } else { + if (!reply) { qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Inverter energy produced\" registers from" << hostAddress().toString() << errorString(); finishRequest(); + return; } - break; - } - case HuaweiModbusTcpConnection::RegisterLunaBattery1Status: { - // Update registers from Luna 2000 Battery 1 status - qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Luna 2000 Battery 1 status\" register:" << 37000 << "size:" << 1; - QModbusReply *reply = readLunaBattery1Status(); - if (reply) { - if (!reply->isFinished()) { - connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); - connect(reply, &QModbusReply::finished, this, [this, reply](){ - if (reply->error() == QModbusDevice::NoError) { - const QModbusDataUnit unit = reply->result(); - const QVector values = unit.values(); - qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 1 status\" register" << 37000 << "size:" << 1 << values; - processLunaBattery1StatusRegisterValues(values); - qCDebug(dcHuaweiFusionSolar()) << "Battery 1 status" << m_lunaBattery1Status; - if (m_lunaBattery1Status == BatteryDeviceStatusOffline) { - m_battery1Available = false; - } else { - m_battery1Available = true; - } - } - finishRequest(); - }); - connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ - qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 1 status\" registers from" << hostAddress().toString() << error << reply->errorString(); - }); - } else { - reply->deleteLater(); // Broadcast reply returns immediatly - finishRequest(); - } - } else { - qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Luna 2000 Battery 1 status\" registers from" << hostAddress().toString() << errorString(); + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly finishRequest(); + return; } - break; - } - case HuaweiModbusTcpConnection::RegisterLunaBattery1Power: { - // Update registers from Luna 2000 Battery 1 power - qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Luna 2000 Battery 1 power\" register:" << 37001 << "size:" << 2; - QModbusReply *reply = readLunaBattery1Power(); - if (reply) { - if (!reply->isFinished()) { - connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); - connect(reply, &QModbusReply::finished, this, [this, reply](){ - if (reply->error() == QModbusDevice::NoError) { - const QModbusDataUnit unit = reply->result(); - const QVector values = unit.values(); - qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 1 power\" register" << 37001 << "size:" << 2 << values; - processLunaBattery1PowerRegisterValues(values); - } - finishRequest(); - }); - connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ - qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 1 power\" registers from" << hostAddress().toString() << error << reply->errorString(); - }); - } else { - reply->deleteLater(); // Broadcast reply returns immediatly - finishRequest(); + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [this, reply](){ + handleModbusError(reply->error()); + if (reply->error() == QModbusDevice::NoError) { + const QModbusDataUnit unit = reply->result(); + qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Inverter energy produced\" register" << 32106 << "size:" << 2 << unit.values(); + processInverterEnergyProducedRegisterValues(unit.values()); } - } else { - qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Luna 2000 Battery 1 power\" registers from" << hostAddress().toString() << errorString(); finishRequest(); - } - break; - } - case HuaweiModbusTcpConnection::RegisterLunaBattery1Soc: { - // Update registers from Luna 2000 Battery 1 state of charge - qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Luna 2000 Battery 1 state of charge\" register:" << 37004 << "size:" << 1; - QModbusReply *reply = readLunaBattery1Soc(); - if (reply) { - if (!reply->isFinished()) { - connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); - connect(reply, &QModbusReply::finished, this, [this, reply](){ - if (reply->error() == QModbusDevice::NoError) { - const QModbusDataUnit unit = reply->result(); - const QVector values = unit.values(); - qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 1 state of charge\" register" << 37004 << "size:" << 1 << values; - processLunaBattery1SocRegisterValues(values); - } - finishRequest(); - }); + }); - connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ - qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 1 state of charge\" registers from" << hostAddress().toString() << error << reply->errorString(); - }); + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + if (reply->error() == QModbusDevice::ProtocolError) { + QModbusResponse response = reply->rawResult(); + if (response.isException()) { + qCDebug(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Inverter energy produced\" registers from" << hostAddress().toString() << exceptionToString(response.exceptionCode()); + } } else { - reply->deleteLater(); // Broadcast reply returns immediatly - finishRequest(); + qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Inverter energy produced\" registers from" << hostAddress().toString() << error << reply->errorString(); } - } else { - qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Luna 2000 Battery 1 state of charge\" registers from" << hostAddress().toString() << errorString(); - finishRequest(); - } + }); break; } - case HuaweiModbusTcpConnection::RegisterPowerMeterActivePower: { + case HuaweiFusionModbusTcpConnection::RegisterPowerMeterActivePower: { // Update registers from Power meter active power qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Power meter active power\" register:" << 37113 << "size:" << 2; QModbusReply *reply = readPowerMeterActivePower(); - if (reply) { - if (!reply->isFinished()) { - connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); - connect(reply, &QModbusReply::finished, this, [this, reply](){ - if (reply->error() == QModbusDevice::NoError) { - const QModbusDataUnit unit = reply->result(); - const QVector values = unit.values(); - qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Power meter active power\" register" << 37113 << "size:" << 2 << values; - processPowerMeterActivePowerRegisterValues(values); - } - finishRequest(); - }); - - connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ - qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Power meter active power\" registers from" << hostAddress().toString() << error << reply->errorString(); - }); - } else { - reply->deleteLater(); // Broadcast reply returns immediatly - finishRequest(); - } - } else { + if (!reply) { qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Power meter active power\" registers from" << hostAddress().toString() << errorString(); finishRequest(); + return; } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + finishRequest(); + return; + } + + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [this, reply](){ + handleModbusError(reply->error()); + if (reply->error() == QModbusDevice::NoError) { + const QModbusDataUnit unit = reply->result(); + qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Power meter active power\" register" << 37113 << "size:" << 2 << unit.values(); + processPowerMeterActivePowerRegisterValues(unit.values()); + } + finishRequest(); + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + if (reply->error() == QModbusDevice::ProtocolError) { + QModbusResponse response = reply->rawResult(); + if (response.isException()) { + qCDebug(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Power meter active power\" registers from" << hostAddress().toString() << exceptionToString(response.exceptionCode()); + } + } else { + qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Power meter active power\" registers from" << hostAddress().toString() << error << reply->errorString(); + } + }); + break; } - case HuaweiModbusTcpConnection::RegisterLunaBattery2Status: { + case HuaweiFusionModbusTcpConnection::RegisterLunaBattery1Status: { + // Update registers from Luna 2000 Battery 1 status + qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Luna 2000 Battery 1 status\" register:" << 37000 << "size:" << 1; + QModbusReply *reply = readLunaBattery1Status(); + if (!reply) { + qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Luna 2000 Battery 1 status\" registers from" << hostAddress().toString() << errorString(); + finishRequest(); + return; + } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + finishRequest(); + return; + } + + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [this, reply](){ + handleModbusError(reply->error()); + if (reply->error() == QModbusDevice::NoError) { + const QModbusDataUnit unit = reply->result(); + qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 1 status\" register" << 37000 << "size:" << 1 << unit.values(); + processLunaBattery1StatusRegisterValues(unit.values()); + } + finishRequest(); + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + if (reply->error() == QModbusDevice::ProtocolError) { + QModbusResponse response = reply->rawResult(); + if (response.isException()) { + qCDebug(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 1 status\" registers from" << hostAddress().toString() << exceptionToString(response.exceptionCode()); + } + } else { + qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 1 status\" registers from" << hostAddress().toString() << error << reply->errorString(); + } + }); + break; + } + case HuaweiFusionModbusTcpConnection::RegisterLunaBattery1Power: { + // Update registers from Luna 2000 Battery 1 power + qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Luna 2000 Battery 1 power\" register:" << 37001 << "size:" << 2; + QModbusReply *reply = readLunaBattery1Power(); + if (!reply) { + qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Luna 2000 Battery 1 power\" registers from" << hostAddress().toString() << errorString(); + finishRequest(); + return; + } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + finishRequest(); + return; + } + + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [this, reply](){ + handleModbusError(reply->error()); + if (reply->error() == QModbusDevice::NoError) { + const QModbusDataUnit unit = reply->result(); + qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 1 power\" register" << 37001 << "size:" << 2 << unit.values(); + processLunaBattery1PowerRegisterValues(unit.values()); + } + finishRequest(); + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + if (reply->error() == QModbusDevice::ProtocolError) { + QModbusResponse response = reply->rawResult(); + if (response.isException()) { + qCDebug(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 1 power\" registers from" << hostAddress().toString() << exceptionToString(response.exceptionCode()); + } + } else { + qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 1 power\" registers from" << hostAddress().toString() << error << reply->errorString(); + } + }); + break; + } + case HuaweiFusionModbusTcpConnection::RegisterLunaBattery1Soc: { + // Update registers from Luna 2000 Battery 1 state of charge + qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Luna 2000 Battery 1 state of charge\" register:" << 37004 << "size:" << 1; + QModbusReply *reply = readLunaBattery1Soc(); + if (!reply) { + qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Luna 2000 Battery 1 state of charge\" registers from" << hostAddress().toString() << errorString(); + finishRequest(); + return; + } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + finishRequest(); + return; + } + + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [this, reply](){ + handleModbusError(reply->error()); + if (reply->error() == QModbusDevice::NoError) { + const QModbusDataUnit unit = reply->result(); + qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 1 state of charge\" register" << 37004 << "size:" << 1 << unit.values(); + processLunaBattery1SocRegisterValues(unit.values()); + } + finishRequest(); + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + if (reply->error() == QModbusDevice::ProtocolError) { + QModbusResponse response = reply->rawResult(); + if (response.isException()) { + qCDebug(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 1 state of charge\" registers from" << hostAddress().toString() << exceptionToString(response.exceptionCode()); + } + } else { + qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 1 state of charge\" registers from" << hostAddress().toString() << error << reply->errorString(); + } + }); + break; + } + + case HuaweiFusionModbusTcpConnection::RegisterLunaBattery2Status: { // Update registers from Luna 2000 Battery 2 status qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Luna 2000 Battery 2 status\" register:" << 37741 << "size:" << 1; QModbusReply *reply = readLunaBattery2Status(); - if (reply) { - if (!reply->isFinished()) { - connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); - connect(reply, &QModbusReply::finished, this, [this, reply](){ - if (reply->error() == QModbusDevice::NoError) { - const QModbusDataUnit unit = reply->result(); - const QVector values = unit.values(); - qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 2 status\" register" << 37741 << "size:" << 1 << values; - processLunaBattery2StatusRegisterValues(values); - qCDebug(dcHuaweiFusionSolar()) << "Battery 2 status" << m_lunaBattery2Status; - if (m_lunaBattery2Status == BatteryDeviceStatusOffline) { - m_battery2Available = false; - } else { - m_battery2Available = true; - } - } - finishRequest(); - }); - - connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ - qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 2 status\" registers from" << hostAddress().toString() << error << reply->errorString(); - }); - } else { - reply->deleteLater(); // Broadcast reply returns immediatly - finishRequest(); - } - } else { + if (!reply) { qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Luna 2000 Battery 2 status\" registers from" << hostAddress().toString() << errorString(); finishRequest(); + return; } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + finishRequest(); + return; + } + + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [this, reply](){ + handleModbusError(reply->error()); + if (reply->error() == QModbusDevice::NoError) { + const QModbusDataUnit unit = reply->result(); + qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 2 status\" register" << 37741 << "size:" << 1 << unit.values(); + processLunaBattery2StatusRegisterValues(unit.values()); + } + finishRequest(); + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + if (reply->error() == QModbusDevice::ProtocolError) { + QModbusResponse response = reply->rawResult(); + if (response.isException()) { + qCDebug(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 2 status\" registers from" << hostAddress().toString() << exceptionToString(response.exceptionCode()); + } + } else { + qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 2 status\" registers from" << hostAddress().toString() << error << reply->errorString(); + } + }); break; } - case HuaweiModbusTcpConnection::RegisterLunaBattery2Power: { + case HuaweiFusionModbusTcpConnection::RegisterLunaBattery2Power: { // Update registers from Luna 2000 Battery 2 power qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Luna 2000 Battery 2 power\" register:" << 37743 << "size:" << 2; QModbusReply *reply = readLunaBattery2Power(); - if (reply) { - if (!reply->isFinished()) { - connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); - connect(reply, &QModbusReply::finished, this, [this, reply](){ - if (reply->error() == QModbusDevice::NoError) { - const QModbusDataUnit unit = reply->result(); - const QVector values = unit.values(); - qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 2 power\" register" << 37743 << "size:" << 2 << values; - processLunaBattery2PowerRegisterValues(values); - } - finishRequest(); - }); - - connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ - qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 2 power\" registers from" << hostAddress().toString() << error << reply->errorString(); - }); - } else { - reply->deleteLater(); // Broadcast reply returns immediatly - finishRequest(); - } - } else { + if (!reply) { qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Luna 2000 Battery 2 power\" registers from" << hostAddress().toString() << errorString(); finishRequest(); + return; } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + finishRequest(); + return; + } + + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [this, reply](){ + handleModbusError(reply->error()); + if (reply->error() == QModbusDevice::NoError) { + const QModbusDataUnit unit = reply->result(); + qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 2 power\" register" << 37743 << "size:" << 2 << unit.values(); + processLunaBattery2PowerRegisterValues(unit.values()); + } + finishRequest(); + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + if (reply->error() == QModbusDevice::ProtocolError) { + QModbusResponse response = reply->rawResult(); + if (response.isException()) { + qCDebug(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 2 power\" registers from" << hostAddress().toString() << exceptionToString(response.exceptionCode()); + } + } else { + qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 2 power\" registers from" << hostAddress().toString() << error << reply->errorString(); + } + }); break; } - case HuaweiModbusTcpConnection::RegisterLunaBattery2Soc: { + case HuaweiFusionModbusTcpConnection::RegisterLunaBattery2Soc: { // Update registers from Luna 2000 Battery 2 state of charge qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Luna 2000 Battery 2 state of charge\" register:" << 37738 << "size:" << 1; QModbusReply *reply = readLunaBattery2Soc(); - if (reply) { - if (!reply->isFinished()) { - connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); - connect(reply, &QModbusReply::finished, this, [this, reply](){ - if (reply->error() == QModbusDevice::NoError) { - const QModbusDataUnit unit = reply->result(); - const QVector values = unit.values(); - qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 2 state of charge\" register" << 37738 << "size:" << 1 << values; - processLunaBattery2SocRegisterValues(values); - } - finishRequest(); - }); - - connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ - qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 2 state of charge\" registers from" << hostAddress().toString() << error << reply->errorString(); - }); - } else { - reply->deleteLater(); // Broadcast reply returns immediatly - finishRequest(); - } - } else { + if (!reply) { qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Luna 2000 Battery 2 state of charge\" registers from" << hostAddress().toString() << errorString(); finishRequest(); + return; } + + if (reply->isFinished()) { + reply->deleteLater(); // Broadcast reply returns immediatly + finishRequest(); + return; + } + + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [this, reply](){ + handleModbusError(reply->error()); + if (reply->error() == QModbusDevice::NoError) { + const QModbusDataUnit unit = reply->result(); + qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 2 state of charge\" register" << 37738 << "size:" << 1 << unit.values(); + processLunaBattery2SocRegisterValues(unit.values()); + } + finishRequest(); + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + if (reply->error() == QModbusDevice::ProtocolError) { + QModbusResponse response = reply->rawResult(); + if (response.isException()) { + qCDebug(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 2 state of charge\" registers from" << hostAddress().toString() << exceptionToString(response.exceptionCode()); + } + } else { + qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 2 state of charge\" registers from" << hostAddress().toString() << error << reply->errorString(); + } + }); break; } } @@ -409,3 +534,45 @@ void HuaweiFusionSolar::finishRequest() m_currentRegisterRequest = -1; QTimer::singleShot(1000, this, &HuaweiFusionSolar::readNextRegister); } + +QString HuaweiFusionSolar::exceptionToString(QModbusPdu::ExceptionCode exception) +{ + QString exceptionString; + switch (exception) { + case QModbusPdu::IllegalFunction: + exceptionString = "Illegal function"; + break; + case QModbusPdu::IllegalDataAddress: + exceptionString = "Illegal data address"; + break; + case QModbusPdu::IllegalDataValue: + exceptionString = "Illegal data value"; + break; + case QModbusPdu::ServerDeviceFailure: + exceptionString = "Server device failure"; + break; + case QModbusPdu::Acknowledge: + exceptionString = "Acknowledge"; + break; + case QModbusPdu::ServerDeviceBusy: + exceptionString = "Server device busy"; + break; + case QModbusPdu::NegativeAcknowledge: + exceptionString = "Negative acknowledge"; + break; + case QModbusPdu::MemoryParityError: + exceptionString = "Memory parity error"; + break; + case QModbusPdu::GatewayPathUnavailable: + exceptionString = "Gateway path unavailable"; + break; + case QModbusPdu::GatewayTargetDeviceFailedToRespond: + exceptionString = "Gateway target device failed to respond"; + break; + case QModbusPdu::ExtendedException: + exceptionString = "Extended exception"; + break; + } + + return exceptionString; +} diff --git a/huawei/huaweifusionsolar.h b/huawei/huaweifusionsolar.h index 2f9de41..7c56ecf 100644 --- a/huawei/huaweifusionsolar.h +++ b/huawei/huaweifusionsolar.h @@ -34,20 +34,21 @@ #include #include -#include "huaweimodbustcpconnection.h" +#include "huaweifusionmodbustcpconnection.h" -class HuaweiFusionSolar : public HuaweiModbusTcpConnection +class HuaweiFusionSolar : public HuaweiFusionModbusTcpConnection { Q_OBJECT public: explicit HuaweiFusionSolar(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent = nullptr); + ~HuaweiFusionSolar() = default; - virtual bool initialize() override; + bool initialize() override; virtual bool update() override; - private: - QQueue m_registersQueue; + QQueue m_registersQueue; + QModbusReply *m_initReply = nullptr; int m_currentRegisterRequest = -1; void finishRequest(); @@ -55,6 +56,8 @@ private: bool m_battery1Available = true; bool m_battery2Available = true; + QString exceptionToString(QModbusPdu::ExceptionCode exception); + private slots: void readNextRegister(); diff --git a/huawei/huaweifusionsolardiscovery.cpp b/huawei/huaweifusionsolardiscovery.cpp new file mode 100644 index 0000000..10103d8 --- /dev/null +++ b/huawei/huaweifusionsolardiscovery.cpp @@ -0,0 +1,166 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "huaweifusionsolardiscovery.h" +#include "extern-plugininfo.h" + +HuaweiFusionSolarDiscovery::HuaweiFusionSolarDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint16 port, quint16 modbusAddress, QObject *parent) : + QObject(parent), + m_networkDeviceDiscovery(networkDeviceDiscovery), + m_port(port), + m_modbusAddress(modbusAddress) +{ + m_gracePeriodTimer.setSingleShot(true); + m_gracePeriodTimer.setInterval(3000); + connect(&m_gracePeriodTimer, &QTimer::timeout, this, [this](){ + qCDebug(dcHuawei()) << "Discovery: Grace period timer triggered."; + finishDiscovery(); + }); +} + + +void HuaweiFusionSolarDiscovery::startDiscovery() +{ + qCInfo(dcHuawei()) << "Discovery: Start searching for Huawei FusionSolar SmartDongle in the network..."; + NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover(); + + // Check any already discovered infos.. + foreach (const NetworkDeviceInfo &networkDeviceInfo, discoveryReply->networkDeviceInfos()) { + checkNetworkDevice(networkDeviceInfo); + } + + // Imedialty check any new device gets discovered + connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &HuaweiFusionSolarDiscovery::checkNetworkDevice); + + // Check what might be left on finished + connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){ + qCDebug(dcHuawei()) << "Discovery: Network discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "network devices"; + m_networkDeviceInfos = discoveryReply->networkDeviceInfos(); + qCDebug(dcHuawei()) << "Discovery: Network discovery finished. Start finishing discovery..."; + // Send a report request to nework device info not sent already... + foreach (const NetworkDeviceInfo &networkDeviceInfo, m_networkDeviceInfos) { + if (!m_verifiedNetworkDeviceInfos.contains(networkDeviceInfo)) { + checkNetworkDevice(networkDeviceInfo); + } + } + + m_gracePeriodTimer.start(); + }); +} + +NetworkDeviceInfos HuaweiFusionSolarDiscovery::discoveryResults() const +{ + return m_discoveryResults; +} + +void HuaweiFusionSolarDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo) +{ + if (m_verifiedNetworkDeviceInfos.contains(networkDeviceInfo)) + return; + + // The dongle must have a huawei registered mac address + if (!networkDeviceInfo.macAddressManufacturer().toLower().contains("huawei")) + return; + + HuaweiFusionSolar *connection = new HuaweiFusionSolar(networkDeviceInfo.address(), m_port, m_modbusAddress, this); + m_connections.append(connection); + m_verifiedNetworkDeviceInfos.append(networkDeviceInfo); + + connect(connection, &HuaweiFusionSolar::reachableChanged, this, [=](bool reachable){ + if (!reachable) { + // Disconnected ... done with this connection + cleanupConnection(connection); + return; + } + + // Modbus TCP connected...ok, let's try to initialize it! + connect(connection, &HuaweiFusionSolar::initializationFinished, this, [=](bool success){ + if (!success) { + qCDebug(dcHuawei()) << "Discovery: Initialization failed on" << networkDeviceInfo.address().toString() << "Continue...";; + cleanupConnection(connection); + return; + } + + m_discoveryResults.append(networkDeviceInfo); + + qCDebug(dcHuawei()) << "Discovery: --> Found" << networkDeviceInfo; + + // Done with this connection + cleanupConnection(connection); + }); + + if (!connection->initialize()) { + qCDebug(dcHuawei()) << "Discovery: Unable to initialize connection on" << networkDeviceInfo.address().toString() << "Continue...";; + cleanupConnection(connection); + } + + // Initializing... + }); + + // If we get any error...skip this host... + connect(connection, &HuaweiFusionSolar::connectionErrorOccurred, this, [=](QModbusDevice::Error error){ + if (error != QModbusDevice::NoError) { + qCDebug(dcHuawei()) << "Discovery: Connection error on" << networkDeviceInfo.address().toString() << "Continue...";; + cleanupConnection(connection); + } + }); + + // If check reachability failed...skip this host... + connect(connection, &HuaweiFusionSolar::checkReachabilityFailed, this, [=](){ + qCDebug(dcHuawei()) << "Discovery: Check reachability failed on" << networkDeviceInfo.address().toString() << "Continue...";; + cleanupConnection(connection); + }); + + // Try to connect, maybe it works, maybe not... + connection->connectDevice(); + +} + +void HuaweiFusionSolarDiscovery::cleanupConnection(HuaweiFusionSolar *connection) +{ + m_connections.removeAll(connection); + connection->disconnectDevice(); + connection->deleteLater(); +} + +void HuaweiFusionSolarDiscovery::finishDiscovery() +{ + qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch(); + + // Cleanup any leftovers...we don't care any more + foreach (HuaweiFusionSolar *connection, m_connections) + cleanupConnection(connection); + + qCInfo(dcHuawei()) << "Discovery: Finished the discovery process. Found" << m_discoveryResults.count() + << "inverters in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz"); + m_gracePeriodTimer.stop(); + + emit discoveryFinished(); +} diff --git a/huawei/huaweifusionsolardiscovery.h b/huawei/huaweifusionsolardiscovery.h new file mode 100644 index 0000000..9cecdf2 --- /dev/null +++ b/huawei/huaweifusionsolardiscovery.h @@ -0,0 +1,75 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef HUAWEIFUSIONSOLARDISCOVERY_H +#define HUAWEIFUSIONSOLARDISCOVERY_H + +#include + +#include + +#include "huaweifusionsolar.h" + +class HuaweiFusionSolarDiscovery : public QObject +{ + Q_OBJECT +public: + explicit HuaweiFusionSolarDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint16 port = 502, quint16 modbusAddress = 1, QObject *parent = nullptr); + + void startDiscovery(); + + NetworkDeviceInfos discoveryResults() const; + +signals: + void discoveryFinished(); + +private: + NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr; + quint16 m_port; + quint16 m_modbusAddress; + + QTimer m_gracePeriodTimer; + QDateTime m_startDateTime; + + NetworkDeviceInfos m_networkDeviceInfos; + NetworkDeviceInfos m_verifiedNetworkDeviceInfos; + + QList m_connections; + + NetworkDeviceInfos m_discoveryResults; + + void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo); + void cleanupConnection(HuaweiFusionSolar *connection); + + void finishDiscovery(); + +}; + +#endif // HUAWEIFUSIONSOLARDISCOVERY_H diff --git a/huawei/integrationpluginhuawei.cpp b/huawei/integrationpluginhuawei.cpp index 1c83ee2..928fdf0 100644 --- a/huawei/integrationpluginhuawei.cpp +++ b/huawei/integrationpluginhuawei.cpp @@ -28,10 +28,11 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +#include "huaweifusionsolardiscovery.h" #include "integrationpluginhuawei.h" #include "plugininfo.h" -#include +#include #include IntegrationPluginHuawei::IntegrationPluginHuawei() @@ -41,59 +42,69 @@ IntegrationPluginHuawei::IntegrationPluginHuawei() void IntegrationPluginHuawei::discoverThings(ThingDiscoveryInfo *info) { - if (!hardwareManager()->networkDeviceDiscovery()->available()) { - qCWarning(dcHuawei()) << "The network discovery is not available on this platform."; - info->finish(Thing::ThingErrorUnsupportedFeature, QT_TR_NOOP("The network device discovery is not available.")); - return; - } + if (info->thingClassId() == huaweiFusionSolarInverterThingClassId) { + if (!hardwareManager()->networkDeviceDiscovery()->available()) { + qCWarning(dcHuawei()) << "The network discovery is not available on this platform."; + info->finish(Thing::ThingErrorUnsupportedFeature, QT_TR_NOOP("The network device discovery is not available.")); + return; + } - NetworkDeviceDiscoveryReply *discoveryReply = hardwareManager()->networkDeviceDiscovery()->discover(); - connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){ - foreach (const NetworkDeviceInfo &networkDeviceInfo, discoveryReply->networkDeviceInfos()) { + // Create a discovery with the info as parent for auto deleting the object once the discovery info is done + HuaweiFusionSolarDiscovery *discovery = new HuaweiFusionSolarDiscovery(hardwareManager()->networkDeviceDiscovery(), 502, 1, info); + connect(discovery, &HuaweiFusionSolarDiscovery::discoveryFinished, info, [=](){ + foreach (const NetworkDeviceInfo &networkDeviceInfo, discovery->discoveryResults()) { - qCDebug(dcHuawei()) << "Found" << networkDeviceInfo; + ThingDescriptor descriptor(huaweiFusionSolarInverterThingClassId, QT_TR_NOOP("Huawei Solar Inverter"), networkDeviceInfo.macAddress() + " - " + networkDeviceInfo.address().toString()); + qCDebug(dcHuawei()) << "Discovered:" << descriptor.title() << descriptor.description(); - // Filter for mac manufacturer - if (!networkDeviceInfo.macAddressManufacturer().contains("Huawei")) + // Check if we already have set up this device + Things existingThings = myThings().filterByParam(huaweiFusionSolarInverterThingMacAddressParamTypeId, networkDeviceInfo.macAddress()); + if (existingThings.count() == 1) { + qCDebug(dcHuawei()) << "This inverter already exists in the system:" << networkDeviceInfo; + descriptor.setThingId(existingThings.first()->id()); + } + + ParamList params; + params << Param(huaweiFusionSolarInverterThingMacAddressParamTypeId, networkDeviceInfo.macAddress()); + // Note: if we discover also the port and modbusaddress, we must fill them in from the discovery here, for now everywhere the defaults... + descriptor.setParams(params); + info->addThingDescriptor(descriptor); + } + + info->finish(Thing::ThingErrorNoError); + }); + + // Start the discovery process + discovery->startDiscovery(); + + } else if (info->thingClassId() == huaweiRtuInverterThingClassId) { + qCDebug(dcHuawei()) << "Discovering modbus RTU resources..."; + if (hardwareManager()->modbusRtuResource()->modbusRtuMasters().isEmpty()) { + info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("No Modbus RTU interface available. Please set up a Modbus RTU interface first.")); + return; + } + + uint slaveAddress = info->params().paramValue(huaweiRtuInverterThingSlaveAddressParamTypeId).toUInt(); + if (slaveAddress > 254 || slaveAddress == 0) { + info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The Modbus slave address must be a value between 1 and 254.")); + return; + } + + foreach (ModbusRtuMaster *modbusMaster, hardwareManager()->modbusRtuResource()->modbusRtuMasters()) { + qCDebug(dcHuawei()) << "Found RTU master resource" << modbusMaster << "connected" << modbusMaster->connected(); + if (!modbusMaster->connected()) continue; - QString title; - if (networkDeviceInfo.hostName().isEmpty()) { - title = "Huawei FusionSolar"; - } else { - title = networkDeviceInfo.hostName() + " (" + networkDeviceInfo.address().toString() + ")"; - } - - QString description; - if (networkDeviceInfo.macAddressManufacturer().isEmpty()) { - description = networkDeviceInfo.macAddress(); - } else { - description = networkDeviceInfo.macAddress() + " (" + networkDeviceInfo.macAddressManufacturer() + ")"; - } - - ThingDescriptor descriptor(huaweiInverterThingClassId, title, description); + ThingDescriptor descriptor(info->thingClassId(), "Huawei Inverter", QString::number(slaveAddress) + " " + modbusMaster->serialPort()); ParamList params; - params << Param(huaweiInverterThingIpAddressParamTypeId, networkDeviceInfo.address().toString()); - params << Param(huaweiInverterThingMacAddressParamTypeId, networkDeviceInfo.macAddress()); + params << Param(huaweiRtuInverterThingSlaveAddressParamTypeId, slaveAddress); + params << Param(huaweiRtuInverterThingModbusMasterUuidParamTypeId, modbusMaster->modbusUuid()); descriptor.setParams(params); - - // Check if we already have set up this device - Things existingThings = myThings().filterByParam(huaweiInverterThingMacAddressParamTypeId, networkDeviceInfo.macAddress()); - if (existingThings.count() == 1) { - qCDebug(dcHuawei()) << "This connection already exists in the system:" << networkDeviceInfo; - descriptor.setThingId(existingThings.first()->id()); - } - info->addThingDescriptor(descriptor); } info->finish(Thing::ThingErrorNoError); - }); -} - -void IntegrationPluginHuawei::startMonitoringAutoThings() -{ - + } } void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info) @@ -101,50 +112,111 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info) Thing *thing = info->thing(); qCDebug(dcHuawei()) << "Setup" << thing << thing->params(); - if (thing->thingClassId() == huaweiInverterThingClassId) { - QHostAddress hostAddress = QHostAddress(thing->paramValue(huaweiInverterThingIpAddressParamTypeId).toString()); - if (hostAddress.isNull()) { - info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("No IP address given")); + if (thing->thingClassId() == huaweiFusionSolarInverterThingClassId) { + + // Handle reconfigure + if (m_connections.contains(thing)) { + m_connections.take(thing)->deleteLater(); + + if (m_monitors.contains(thing)) { + hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); + } + } + + // Make sure we have a valid mac address, otherwise no monitor and not auto searching is possible + MacAddress macAddress = MacAddress(thing->paramValue(huaweiFusionSolarInverterThingMacAddressParamTypeId).toString()); + if (macAddress.isNull()) { + qCWarning(dcHuawei()) << "Failed to set up Fusion Solar because the MAC address is not valid:" << thing->paramValue(huaweiFusionSolarInverterThingMacAddressParamTypeId).toString() << macAddress.toString(); + info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The MAC address is not vaild. Please reconfigure the device to fix this.")); return; } - uint port = thing->paramValue(huaweiInverterThingPortParamTypeId).toUInt(); - quint16 slaveId = thing->paramValue(huaweiInverterThingSlaveIdParamTypeId).toUInt(); + // Create a monitor so we always get the correct IP in the network and see if the device is reachable without polling on our own + NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(macAddress); + m_monitors.insert(thing, monitor); - HuaweiFusionSolar *connection = new HuaweiFusionSolar(hostAddress, port, slaveId, this); + // Continue with setup only if we know that the network device is reachable + if (monitor->reachable()) { + setupFusionSolar(info); + } else { + // otherwise wait until we reach the networkdevice before setting up the device + qCDebug(dcHuawei()) << "Network device" << thing->name() << "is not reachable yet. Continue with the setup once reachable."; + connect(monitor, &NetworkDeviceMonitor::reachableChanged, info, [=](bool reachable){ + if (reachable) { + qCDebug(dcHuawei()) << "Network device" << thing->name() << "is now reachable. Continue with the setup..."; + setupFusionSolar(info); + } + }); + } - connect(connection, &HuaweiFusionSolar::connectionStateChanged, this, [this, thing, connection](bool status){ - qCDebug(dcHuawei()) << "Connected changed to" << status << "for" << thing; - if (status) { + return; + } + + if (thing->thingClassId() == huaweiRtuInverterThingClassId) { + + uint address = thing->paramValue(huaweiRtuInverterThingSlaveAddressParamTypeId).toUInt(); + if (address > 254 || address == 0) { + qCWarning(dcHuawei()) << "Setup failed, slave address is not valid" << address; + info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("The Modbus address not valid. It must be a value between 1 and 254.")); + return; + } + + QUuid uuid = thing->paramValue(huaweiRtuInverterThingModbusMasterUuidParamTypeId).toUuid(); + if (!hardwareManager()->modbusRtuResource()->hasModbusRtuMaster(uuid)) { + qCWarning(dcHuawei()) << "Setup failed, hardware manager not available"; + info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("The Modbus RTU resource is not available.")); + return; + } + + if (m_rtuConnections.contains(thing)) { + qCDebug(dcHuawei()) << "Already have a Huawei connection for this thing. Cleaning up old connection and initializing new one..."; + delete m_rtuConnections.take(thing); + } + + ModbusRtuMaster *rtuMaster = hardwareManager()->modbusRtuResource()->getModbusRtuMaster(uuid); + HuaweiModbusRtuConnection *connection = new HuaweiModbusRtuConnection(rtuMaster, address, this); + + connect(connection, &HuaweiModbusRtuConnection::reachableChanged, this, [this, thing, connection](bool reachable){ + qCDebug(dcHuawei()) << thing->name() << "reachable changed" << reachable; + if (reachable) { // Connected true will be set after successfull init connection->initialize(); - thing->setStateValue(huaweiInverterConnectedStateTypeId, true); } else { - thing->setStateValue(huaweiInverterConnectedStateTypeId, false); - } - - foreach (Thing *childThing, myThings().filterByParentId(thing->id())) { - childThing->setStateValue("connected", status); + thing->setStateValue("connected", false); + foreach (Thing *childThing, myThings().filterByParentId(thing->id())) { + childThing->setStateValue("connected", false); + } } }); - connect(connection, &HuaweiFusionSolar::inverterActivePowerChanged, this, [thing](float inverterActivePower){ + connect(connection, &HuaweiModbusRtuConnection::initializationFinished, this, [this, thing, connection](bool success){ + if (success) { + thing->setStateValue("connected", true); + foreach (Thing *childThing, myThings().filterByParentId(thing->id())) { + childThing->setStateValue("connected", true); + } + + connection->update(); + } + }); + + connect(connection, &HuaweiModbusRtuConnection::inverterActivePowerChanged, this, [thing](float inverterActivePower){ qCDebug(dcHuawei()) << "Inverter power changed" << inverterActivePower * -1000.0 << "W"; - thing->setStateValue(huaweiInverterCurrentPowerStateTypeId, inverterActivePower * -1000.0); + thing->setStateValue(huaweiRtuInverterCurrentPowerStateTypeId, inverterActivePower * -1000.0); }); - connect(connection, &HuaweiFusionSolar::inverterDeviceStatusChanged, this, [thing](HuaweiFusionSolar::InverterDeviceStatus inverterDeviceStatus){ + connect(connection, &HuaweiModbusRtuConnection::inverterDeviceStatusChanged, this, [thing](HuaweiModbusRtuConnection::InverterDeviceStatus inverterDeviceStatus){ qCDebug(dcHuawei()) << "Inverter device status changed" << inverterDeviceStatus; Q_UNUSED(thing) }); - connect(connection, &HuaweiFusionSolar::inverterEnergyProducedChanged, this, [thing](float inverterEnergyProduced){ + connect(connection, &HuaweiModbusRtuConnection::inverterEnergyProducedChanged, this, [thing](float inverterEnergyProduced){ qCDebug(dcHuawei()) << "Inverter total energy produced changed" << inverterEnergyProduced << "kWh"; - thing->setStateValue(huaweiInverterTotalEnergyProducedStateTypeId, inverterEnergyProduced); + thing->setStateValue(huaweiRtuInverterTotalEnergyProducedStateTypeId, inverterEnergyProduced); }); // Meter - connect(connection, &HuaweiFusionSolar::powerMeterActivePowerChanged, this, [this, thing](qint32 powerMeterActivePower){ + connect(connection, &HuaweiModbusRtuConnection::powerMeterActivePowerChanged, this, [this, thing](qint32 powerMeterActivePower){ Things meterThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiMeterThingClassId); if (!meterThings.isEmpty()) { qCDebug(dcHuawei()) << "Meter power changed" << powerMeterActivePower << "W"; @@ -154,9 +226,9 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info) }); // Battery 1 - connect(connection, &HuaweiFusionSolar::lunaBattery1StatusChanged, this, [this, thing](HuaweiFusionSolar::BatteryDeviceStatus lunaBattery1Status){ + connect(connection, &HuaweiModbusRtuConnection::lunaBattery1StatusChanged, this, [this, thing](HuaweiModbusRtuConnection::BatteryDeviceStatus lunaBattery1Status){ qCDebug(dcHuawei()) << "Battery 1 status changed" << lunaBattery1Status; - if (lunaBattery1Status != HuaweiFusionSolar::BatteryDeviceStatusOffline) { + if (lunaBattery1Status != HuaweiModbusRtuConnection::BatteryDeviceStatusOffline) { // Check if w have to create the energy storage Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId); bool alreadySetUp = false; @@ -177,7 +249,7 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info) } }); - connect(connection, &HuaweiFusionSolar::lunaBattery1PowerChanged, this, [this, thing](qint32 lunaBattery1Power){ + connect(connection, &HuaweiModbusRtuConnection::lunaBattery1PowerChanged, this, [this, thing](qint32 lunaBattery1Power){ qCDebug(dcHuawei()) << "Battery 1 power changed" << lunaBattery1Power << "W"; Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 1); if (!batteryThings.isEmpty()) { @@ -192,7 +264,7 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info) } }); - connect(connection, &HuaweiFusionSolar::lunaBattery1SocChanged, this, [this, thing](float lunaBattery1Soc){ + connect(connection, &HuaweiModbusRtuConnection::lunaBattery1SocChanged, this, [this, thing](float lunaBattery1Soc){ qCDebug(dcHuawei()) << "Battery 1 SOC changed" << lunaBattery1Soc << "%"; Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 1); if (!batteryThings.isEmpty()) { @@ -202,9 +274,9 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info) }); // Battery 2 - connect(connection, &HuaweiFusionSolar::lunaBattery2StatusChanged, this, [this, thing](HuaweiFusionSolar::BatteryDeviceStatus lunaBattery1Status){ + connect(connection, &HuaweiModbusRtuConnection::lunaBattery2StatusChanged, this, [this, thing](HuaweiModbusRtuConnection::BatteryDeviceStatus lunaBattery1Status){ qCDebug(dcHuawei()) << "Battery 2 status changed" << lunaBattery1Status; - if (lunaBattery1Status != HuaweiFusionSolar::BatteryDeviceStatusOffline) { + if (lunaBattery1Status != HuaweiModbusRtuConnection::BatteryDeviceStatusOffline) { // Check if w have to create the energy storage Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId); bool alreadySetUp = false; @@ -225,7 +297,7 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info) } }); - connect(connection, &HuaweiFusionSolar::lunaBattery2PowerChanged, this, [this, thing](qint32 lunaBattery2Power){ + connect(connection, &HuaweiModbusRtuConnection::lunaBattery2PowerChanged, this, [this, thing](qint32 lunaBattery2Power){ qCDebug(dcHuawei()) << "Battery 2 power changed" << lunaBattery2Power << "W"; Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 2); if (!batteryThings.isEmpty()) { @@ -241,7 +313,7 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info) } }); - connect(connection, &HuaweiFusionSolar::lunaBattery2SocChanged, this, [this, thing](float lunaBattery2Soc){ + connect(connection, &HuaweiModbusRtuConnection::lunaBattery2SocChanged, this, [this, thing](float lunaBattery2Soc){ qCDebug(dcHuawei()) << "Battery 2 SOC changed" << lunaBattery2Soc << "%"; Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 2); if (!batteryThings.isEmpty()) { @@ -250,11 +322,12 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info) } }); - m_connections.insert(thing, connection); - connection->connectDevice(); + m_rtuConnections.insert(thing, connection); + connection->initialize(); // FIXME: make async and check if this is really a huawei info->finish(Thing::ThingErrorNoError); + return; } if (thing->thingClassId() == huaweiMeterThingClassId) { @@ -264,7 +337,7 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info) if (parentThing) { thing->setStateValue("connected", parentThing->stateValue("connected").toBool()); } - + return; } if (thing->thingClassId() == huaweiBatteryThingClassId) { @@ -274,12 +347,13 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info) if (parentThing) { thing->setStateValue("connected", parentThing->stateValue("connected").toBool()); } + return; } } void IntegrationPluginHuawei::postSetupThing(Thing *thing) { - if (thing->thingClassId() == huaweiInverterThingClassId) { + if (thing->thingClassId() == huaweiFusionSolarInverterThingClassId || thing->thingClassId() == huaweiRtuInverterThingClassId) { if (!m_pluginTimer) { qCDebug(dcHuawei()) << "Starting plugin timer..."; m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(5); @@ -289,6 +363,10 @@ void IntegrationPluginHuawei::postSetupThing(Thing *thing) connection->update(); } } + + foreach(HuaweiModbusRtuConnection *connection, m_rtuConnections) { + connection->update(); + } }); m_pluginTimer->start(); @@ -304,8 +382,16 @@ void IntegrationPluginHuawei::postSetupThing(Thing *thing) void IntegrationPluginHuawei::thingRemoved(Thing *thing) { - if (thing->thingClassId() == huaweiInverterThingClassId && m_connections.contains(thing)) { + if (thing->thingClassId() == huaweiFusionSolarInverterThingClassId && m_connections.contains(thing)) { m_connections.take(thing)->deleteLater(); + + if (m_monitors.contains(thing)) { + hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); + } + } + + if (thing->thingClassId() == huaweiRtuInverterThingClassId && m_rtuConnections.contains(thing)) { + m_rtuConnections.take(thing)->deleteLater(); } if (myThings().isEmpty() && m_pluginTimer) { @@ -314,9 +400,171 @@ void IntegrationPluginHuawei::thingRemoved(Thing *thing) } } -void IntegrationPluginHuawei::executeAction(ThingActionInfo *info) +void IntegrationPluginHuawei::setupFusionSolar(ThingSetupInfo *info) { + Thing *thing = info->thing(); + NetworkDeviceMonitor *monitor = m_monitors.value(thing); + uint port = thing->paramValue(huaweiFusionSolarInverterThingPortParamTypeId).toUInt(); + quint16 slaveId = thing->paramValue(huaweiFusionSolarInverterThingSlaveIdParamTypeId).toUInt(); + + HuaweiFusionSolar *connection = new HuaweiFusionSolar(monitor->networkDeviceInfo().address(), port, slaveId, this); + + qCDebug(dcHuawei()) << "Finish setup huawei fusion solar dongle" << monitor->networkDeviceInfo().address().toString() << port << slaveId; + + connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){ + if (!thing->setupComplete()) + return; + + qCDebug(dcHuawei()) << "Network device monitor for" << thing->name() << (reachable ? "is now reachable" : "is not reachable any more" ); + if (reachable) { + // Update address and refresh + thing->setStateValue("hostAddress", monitor->networkDeviceInfo().address().toString()); + connection->setHostAddress(monitor->networkDeviceInfo().address()); + connection->update(); + } + }); + + connect(connection, &HuaweiFusionSolar::reachableChanged, this, [this, thing, connection](bool reachable){ + qCDebug(dcHuawei()) << "Reachable changed to" << reachable << "for" << thing; + if (reachable) { + // Connected true will be set after successfull init + connection->initialize(); + thing->setStateValue("connected", true); + } else { + thing->setStateValue("connected", false); + } + + foreach (Thing *childThing, myThings().filterByParentId(thing->id())) { + childThing->setStateValue("connected", reachable); + } + }); + + connect(connection, &HuaweiFusionSolar::inverterActivePowerChanged, this, [thing](float inverterActivePower){ + qCDebug(dcHuawei()) << "Inverter power changed" << inverterActivePower * -1000.0 << "W"; + thing->setStateValue(huaweiFusionSolarInverterCurrentPowerStateTypeId, inverterActivePower * -1000.0); + }); + + connect(connection, &HuaweiFusionSolar::inverterDeviceStatusChanged, this, [thing](HuaweiFusionSolar::InverterDeviceStatus inverterDeviceStatus){ + qCDebug(dcHuawei()) << "Inverter device status changed" << inverterDeviceStatus; + Q_UNUSED(thing) + }); + + connect(connection, &HuaweiFusionSolar::inverterEnergyProducedChanged, this, [thing](float inverterEnergyProduced){ + qCDebug(dcHuawei()) << "Inverter total energy produced changed" << inverterEnergyProduced << "kWh"; + thing->setStateValue(huaweiFusionSolarInverterTotalEnergyProducedStateTypeId, inverterEnergyProduced); + }); + + // Meter + connect(connection, &HuaweiFusionSolar::powerMeterActivePowerChanged, this, [this, thing](qint32 powerMeterActivePower){ + Things meterThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiMeterThingClassId); + if (!meterThings.isEmpty()) { + qCDebug(dcHuawei()) << "Meter power changed" << powerMeterActivePower << "W"; + // Note: > 0 -> return, < 0 consume + meterThings.first()->setStateValue(huaweiMeterCurrentPowerStateTypeId, -powerMeterActivePower); + } + }); + + // Battery 1 + connect(connection, &HuaweiFusionSolar::lunaBattery1StatusChanged, this, [this, thing](HuaweiFusionSolar::BatteryDeviceStatus lunaBattery1Status){ + qCDebug(dcHuawei()) << "Battery 1 status changed" << lunaBattery1Status; + if (lunaBattery1Status != HuaweiFusionSolar::BatteryDeviceStatusOffline) { + // Check if w have to create the energy storage + Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId); + bool alreadySetUp = false; + foreach (Thing *batteryThing, batteryThings) { + if (batteryThing->paramValue(huaweiBatteryThingUnitParamTypeId).toUInt() == 1) { + alreadySetUp = true; + } + } + + if (!alreadySetUp) { + qCDebug(dcHuawei()) << "Set up huawei energy storage 1 for" << thing; + ThingDescriptor descriptor(huaweiBatteryThingClassId, "Luna 2000 Battery", QString(), thing->id()); + ParamList params; + params.append(Param(huaweiBatteryThingUnitParamTypeId, 1)); + descriptor.setParams(params); + emit autoThingsAppeared(ThingDescriptors() << descriptor); + } + } + }); + + connect(connection, &HuaweiFusionSolar::lunaBattery1PowerChanged, this, [this, thing](qint32 lunaBattery1Power){ + qCDebug(dcHuawei()) << "Battery 1 power changed" << lunaBattery1Power << "W"; + Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 1); + if (!batteryThings.isEmpty()) { + batteryThings.first()->setStateValue(huaweiBatteryCurrentPowerStateTypeId, lunaBattery1Power); + if (lunaBattery1Power < 0) { + batteryThings.first()->setStateValue(huaweiBatteryChargingStateStateTypeId, "discharging"); + } else if (lunaBattery1Power > 0) { + batteryThings.first()->setStateValue(huaweiBatteryChargingStateStateTypeId, "charging"); + } else { + batteryThings.first()->setStateValue(huaweiBatteryChargingStateStateTypeId, "idle"); + } + } + }); + + connect(connection, &HuaweiFusionSolar::lunaBattery1SocChanged, this, [this, thing](float lunaBattery1Soc){ + qCDebug(dcHuawei()) << "Battery 1 SOC changed" << lunaBattery1Soc << "%"; + Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 1); + if (!batteryThings.isEmpty()) { + batteryThings.first()->setStateValue(huaweiBatteryBatteryLevelStateTypeId, lunaBattery1Soc); + batteryThings.first()->setStateValue(huaweiBatteryBatteryCriticalStateTypeId, lunaBattery1Soc < 10); + } + }); + + // Battery 2 + connect(connection, &HuaweiFusionSolar::lunaBattery2StatusChanged, this, [this, thing](HuaweiFusionSolar::BatteryDeviceStatus lunaBattery1Status){ + qCDebug(dcHuawei()) << "Battery 2 status changed" << lunaBattery1Status; + if (lunaBattery1Status != HuaweiFusionSolar::BatteryDeviceStatusOffline) { + // Check if w have to create the energy storage + Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId); + bool alreadySetUp = false; + foreach (Thing *batteryThing, batteryThings) { + if (batteryThing->paramValue(huaweiBatteryThingUnitParamTypeId).toUInt() == 2) { + alreadySetUp = true; + } + } + + if (!alreadySetUp) { + qCDebug(dcHuawei()) << "Set up huawei energy storage 2 for" << thing; + ThingDescriptor descriptor(huaweiBatteryThingClassId, "Luna 2000 Battery", QString(), thing->id()); + ParamList params; + params.append(Param(huaweiBatteryThingUnitParamTypeId, 2)); + descriptor.setParams(params); + emit autoThingsAppeared(ThingDescriptors() << descriptor); + } + } + }); + + connect(connection, &HuaweiFusionSolar::lunaBattery2PowerChanged, this, [this, thing](qint32 lunaBattery2Power){ + qCDebug(dcHuawei()) << "Battery 2 power changed" << lunaBattery2Power << "W"; + Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 2); + if (!batteryThings.isEmpty()) { + batteryThings.first()->setStateValue(huaweiBatteryCurrentPowerStateTypeId, lunaBattery2Power); + + if (lunaBattery2Power < 0) { + batteryThings.first()->setStateValue(huaweiBatteryChargingStateStateTypeId, "discharging"); + } else if (lunaBattery2Power > 0) { + batteryThings.first()->setStateValue(huaweiBatteryChargingStateStateTypeId, "charging"); + } else { + batteryThings.first()->setStateValue(huaweiBatteryChargingStateStateTypeId, "idle"); + } + } + }); + + connect(connection, &HuaweiFusionSolar::lunaBattery2SocChanged, this, [this, thing](float lunaBattery2Soc){ + qCDebug(dcHuawei()) << "Battery 2 SOC changed" << lunaBattery2Soc << "%"; + Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 2); + if (!batteryThings.isEmpty()) { + batteryThings.first()->setStateValue(huaweiBatteryBatteryLevelStateTypeId, lunaBattery2Soc); + batteryThings.first()->setStateValue(huaweiBatteryBatteryCriticalStateTypeId, lunaBattery2Soc < 10); + } + }); + + m_connections.insert(thing, connection); + connection->connectDevice(); + + // FIXME: make async and check if this is really a huawei info->finish(Thing::ThingErrorNoError); } - diff --git a/huawei/integrationpluginhuawei.h b/huawei/integrationpluginhuawei.h index 8e5afe6..fd45cbe 100644 --- a/huawei/integrationpluginhuawei.h +++ b/huawei/integrationpluginhuawei.h @@ -33,8 +33,11 @@ #include #include +#include +#include "extern-plugininfo.h" #include "huaweifusionsolar.h" +#include "huaweimodbusrtuconnection.h" class IntegrationPluginHuawei: public IntegrationPlugin { @@ -47,16 +50,18 @@ public: explicit IntegrationPluginHuawei(); void discoverThings(ThingDiscoveryInfo *info) override; - void startMonitoringAutoThings() override; void setupThing(ThingSetupInfo *info) override; void postSetupThing(Thing *thing) override; void thingRemoved(Thing *thing) override; - void executeAction(ThingActionInfo *info) override; private: PluginTimer *m_pluginTimer = nullptr; + QHash m_monitors; QHash m_connections; + QHash m_rtuConnections; + + void setupFusionSolar(ThingSetupInfo *info); }; #endif // INTEGRATIONPLUGINHUAWEI_H diff --git a/huawei/integrationpluginhuawei.json b/huawei/integrationpluginhuawei.json index 0aaeedd..f54d5bc 100644 --- a/huawei/integrationpluginhuawei.json +++ b/huawei/integrationpluginhuawei.json @@ -9,21 +9,13 @@ "id": "f654c99d-a286-4abb-b33e-1a71843d8da0", "thingClasses": [ { - "name": "huaweiInverter", + "name": "huaweiFusionSolarInverter", "displayName": "Huawei FusionSolar Inverter", "id": "87e75ee0-d544-457b-add3-bd4e58160fcd", "createMethods": ["discovery", "user"], "interfaces": ["solarinverter", "connectable"], "providedInterfaces": [ "solarinverter", "energymeter", "energystorage"], "paramTypes": [ - { - "id": "d93371db-0954-4dcd-a1a5-6881b78cb0ea", - "name": "ipAddress", - "displayName": "IP address", - "type": "QString", - "inputType": "IPv4Address", - "defaultValue": "127.0.0.1" - }, { "id": "93517bff-1928-4c4a-8207-5fe596c86eba", "name":"macAddress", @@ -79,6 +71,71 @@ ], "actionTypes": [ ] }, + { + "name": "huaweiRtuInverter", + "displayName": "Huawei Inverter", + "id": "77558007-5076-4ca6-bd46-169f215c3e29", + "createMethods": ["discovery"], + "interfaces": ["solarinverter", "connectable"], + "providedInterfaces": [ "solarinverter", "energymeter", "energystorage"], + "discoveryParamTypes": [ + { + "id": "93a4d3a8-c7d0-470b-b6e3-d8fc43b8e8d0", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "int", + "defaultValue": 1 + } + ], + "paramTypes": [ + { + "id": "5c7b28b1-3691-452e-8f49-d80ae7bcbe2c", + "name": "modbusMasterUuid", + "displayName": "Modbus RTU master", + "type": "QUuid", + "defaultValue": "", + "readOnly": true + }, + { + "id": "de06f027-7940-4c45-9c96-30930ac2796d", + "name": "slaveAddress", + "displayName": "Modbus slave address", + "type": "uint", + "defaultValue": 1 + } + ], + "stateTypes": [ + { + "id": "191ffa22-de6f-4325-8698-56b817f78df5", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "6064d90e-1b6b-40fd-9da0-6ebc713efb7d", + "name": "currentPower", + "displayName": "Active power", + "displayNameEvent": "Active power changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0, + "cached": false + }, + { + "id": "49b92919-301c-4ff7-ae63-0c1a2184e3f4", + "name": "totalEnergyProduced", + "displayName": "AC energy", + "displayNameEvent": "AC energy changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0.00 + } + ], + "actionTypes": [ ] + }, { "name": "huaweiMeter", "displayName": "Huawei Meter", From d995874ddbbf26da3bd0934d7ce33c52951af8e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 3 Aug 2022 15:24:16 +0200 Subject: [PATCH 02/11] Update translations --- ...e4509-47f3-4622-9bc4-0a90fe2b6262-en_US.ts | 378 ++++++------------ 1 file changed, 120 insertions(+), 258 deletions(-) diff --git a/huawei/translations/fc3e4509-47f3-4622-9bc4-0a90fe2b6262-en_US.ts b/huawei/translations/fc3e4509-47f3-4622-9bc4-0a90fe2b6262-en_US.ts index ec98e90..28ede6f 100644 --- a/huawei/translations/fc3e4509-47f3-4622-9bc4-0a90fe2b6262-en_US.ts +++ b/huawei/translations/fc3e4509-47f3-4622-9bc4-0a90fe2b6262-en_US.ts @@ -4,397 +4,259 @@ Huawei - - - - + + + AC energy - The name of the ParamType (ThingClass: huaweiMeter, EventType: totalEnergyProduced, ID: {759554dd-74c5-4836-9792-96e02eb816f0}) + The name of the StateType ({759554dd-74c5-4836-9792-96e02eb816f0}) of ThingClass huaweiMeter ---------- -The name of the StateType ({759554dd-74c5-4836-9792-96e02eb816f0}) of ThingClass huaweiMeter +The name of the StateType ({49b92919-301c-4ff7-ae63-0c1a2184e3f4}) of ThingClass huaweiRtuInverter ---------- -The name of the ParamType (ThingClass: huaweiInverter, EventType: totalEnergyProduced, ID: {e97fe328-6ca4-4fe4-86f7-fee6e9e406a5}) ----------- -The name of the StateType ({e97fe328-6ca4-4fe4-86f7-fee6e9e406a5}) of ThingClass huaweiInverter +The name of the StateType ({e97fe328-6ca4-4fe4-86f7-fee6e9e406a5}) of ThingClass huaweiFusionSolarInverter - - - AC energy changed - The name of the EventType ({759554dd-74c5-4836-9792-96e02eb816f0}) of ThingClass huaweiMeter ----------- -The name of the EventType ({e97fe328-6ca4-4fe4-86f7-fee6e9e406a5}) of ThingClass huaweiInverter - - - - - + + Active power - The name of the ParamType (ThingClass: huaweiInverter, EventType: currentPower, ID: {f463f36e-69f9-4614-b690-664ce22d76e0}) + The name of the StateType ({6064d90e-1b6b-40fd-9da0-6ebc713efb7d}) of ThingClass huaweiRtuInverter ---------- -The name of the StateType ({f463f36e-69f9-4614-b690-664ce22d76e0}) of ThingClass huaweiInverter +The name of the StateType ({f463f36e-69f9-4614-b690-664ce22d76e0}) of ThingClass huaweiFusionSolarInverter - - Active power changed - The name of the EventType ({f463f36e-69f9-4614-b690-664ce22d76e0}) of ThingClass huaweiInverter - - - - - + Battery critical - The name of the ParamType (ThingClass: huaweiBattery, EventType: batteryCritical, ID: {223ddf60-ff73-4acf-b8ab-6337aeb972e8}) ----------- -The name of the StateType ({223ddf60-ff73-4acf-b8ab-6337aeb972e8}) of ThingClass huaweiBattery + The name of the StateType ({223ddf60-ff73-4acf-b8ab-6337aeb972e8}) of ThingClass huaweiBattery - - Battery critical changed - The name of the EventType ({223ddf60-ff73-4acf-b8ab-6337aeb972e8}) of ThingClass huaweiBattery - - - - - + Battery level - The name of the ParamType (ThingClass: huaweiBattery, EventType: batteryLevel, ID: {94d609bf-1f67-47c4-a23d-2fd14e7c0b21}) ----------- -The name of the StateType ({94d609bf-1f67-47c4-a23d-2fd14e7c0b21}) of ThingClass huaweiBattery + The name of the StateType ({94d609bf-1f67-47c4-a23d-2fd14e7c0b21}) of ThingClass huaweiBattery - - Battery level changed - The name of the EventType ({94d609bf-1f67-47c4-a23d-2fd14e7c0b21}) of ThingClass huaweiBattery - - - - - + Capacity - The name of the ParamType (ThingClass: huaweiBattery, EventType: capacity, ID: {3eed974a-0acb-4e38-bcb8-0e3f6fbfd51a}) ----------- -The name of the StateType ({3eed974a-0acb-4e38-bcb8-0e3f6fbfd51a}) of ThingClass huaweiBattery + The name of the StateType ({3eed974a-0acb-4e38-bcb8-0e3f6fbfd51a}) of ThingClass huaweiBattery - - Capacity changed - The name of the EventType ({3eed974a-0acb-4e38-bcb8-0e3f6fbfd51a}) of ThingClass huaweiBattery - - - - - + Charging state - The name of the ParamType (ThingClass: huaweiBattery, EventType: chargingState, ID: {d9604513-d5a9-463a-ad18-d2f259a7a99d}) ----------- -The name of the StateType ({d9604513-d5a9-463a-ad18-d2f259a7a99d}) of ThingClass huaweiBattery + The name of the StateType ({d9604513-d5a9-463a-ad18-d2f259a7a99d}) of ThingClass huaweiBattery - - Charging state changed - The name of the EventType ({d9604513-d5a9-463a-ad18-d2f259a7a99d}) of ThingClass huaweiBattery - - - - - - - - - + + + + Connected - The name of the ParamType (ThingClass: huaweiBattery, EventType: connected, ID: {917bc284-9d43-430c-a8c3-642d302448e6}) ----------- -The name of the StateType ({917bc284-9d43-430c-a8c3-642d302448e6}) of ThingClass huaweiBattery ----------- -The name of the ParamType (ThingClass: huaweiMeter, EventType: connected, ID: {720ece7a-b0b3-4fa3-9f52-6f23042624a5}) + The name of the StateType ({917bc284-9d43-430c-a8c3-642d302448e6}) of ThingClass huaweiBattery ---------- The name of the StateType ({720ece7a-b0b3-4fa3-9f52-6f23042624a5}) of ThingClass huaweiMeter ---------- -The name of the ParamType (ThingClass: huaweiInverter, EventType: connected, ID: {a51f0ceb-bd2c-444f-8b39-77cf8a4e1bc6}) +The name of the StateType ({191ffa22-de6f-4325-8698-56b817f78df5}) of ThingClass huaweiRtuInverter ---------- -The name of the StateType ({a51f0ceb-bd2c-444f-8b39-77cf8a4e1bc6}) of ThingClass huaweiInverter +The name of the StateType ({a51f0ceb-bd2c-444f-8b39-77cf8a4e1bc6}) of ThingClass huaweiFusionSolarInverter - - - - Connected changed - The name of the EventType ({917bc284-9d43-430c-a8c3-642d302448e6}) of ThingClass huaweiBattery ----------- -The name of the EventType ({720ece7a-b0b3-4fa3-9f52-6f23042624a5}) of ThingClass huaweiMeter ----------- -The name of the EventType ({a51f0ceb-bd2c-444f-8b39-77cf8a4e1bc6}) of ThingClass huaweiInverter - - - - - + Current power phase A - The name of the ParamType (ThingClass: huaweiMeter, EventType: currentPowerPhaseA, ID: {ecc03e9b-88b1-424f-a179-66bbdebaaea9}) ----------- -The name of the StateType ({ecc03e9b-88b1-424f-a179-66bbdebaaea9}) of ThingClass huaweiMeter + The name of the StateType ({ecc03e9b-88b1-424f-a179-66bbdebaaea9}) of ThingClass huaweiMeter - - Current power phase A changed - The name of the EventType ({ecc03e9b-88b1-424f-a179-66bbdebaaea9}) of ThingClass huaweiMeter - - - - - + Current power phase B - The name of the ParamType (ThingClass: huaweiMeter, EventType: currentPowerPhaseB, ID: {7971cbde-b2ea-4474-b68a-71e040ed3b1d}) ----------- -The name of the StateType ({7971cbde-b2ea-4474-b68a-71e040ed3b1d}) of ThingClass huaweiMeter + The name of the StateType ({7971cbde-b2ea-4474-b68a-71e040ed3b1d}) of ThingClass huaweiMeter - - Current power phase B changed - The name of the EventType ({7971cbde-b2ea-4474-b68a-71e040ed3b1d}) of ThingClass huaweiMeter - - - - - + Current power phase C - The name of the ParamType (ThingClass: huaweiMeter, EventType: currentPowerPhaseC, ID: {7ca21c4d-6763-49e4-a056-4c9c76923971}) ----------- -The name of the StateType ({7ca21c4d-6763-49e4-a056-4c9c76923971}) of ThingClass huaweiMeter + The name of the StateType ({7ca21c4d-6763-49e4-a056-4c9c76923971}) of ThingClass huaweiMeter - - Current power phase C changed - The name of the EventType ({7ca21c4d-6763-49e4-a056-4c9c76923971}) of ThingClass huaweiMeter - - - - - + Frequency - The name of the ParamType (ThingClass: huaweiMeter, EventType: frequency, ID: {1e2252be-80b3-4e9a-97f7-105d6d1c50f9}) ----------- -The name of the StateType ({1e2252be-80b3-4e9a-97f7-105d6d1c50f9}) of ThingClass huaweiMeter + The name of the StateType ({1e2252be-80b3-4e9a-97f7-105d6d1c50f9}) of ThingClass huaweiMeter - - Frequency changed - The name of the EventType ({1e2252be-80b3-4e9a-97f7-105d6d1c50f9}) of ThingClass huaweiMeter - - - - + Huawei The name of the vendor ({f654c99d-a286-4abb-b33e-1a71843d8da0}) - + Huawei Battery The name of the ThingClass ({40104aac-0456-475d-8bd6-18f946597d96}) - + Huawei FusionSolar The name of the plugin Huawei ({fc3e4509-47f3-4622-9bc4-0a90fe2b6262}) - - Huawei FusionSolar Inverter + + Huawei FusionSolar Inverter (Modbus RTU) + The name of the ThingClass ({77558007-5076-4ca6-bd46-169f215c3e29}) + + + + + Huawei FusionSolar Inverter (SmartDongle) The name of the ThingClass ({87e75ee0-d544-457b-add3-bd4e58160fcd}) - + Huawei Meter The name of the ThingClass ({529c2a19-ca6a-4df2-b56e-3fb2673fa95f}) - - IP address - The name of the ParamType (ThingClass: huaweiInverter, Type: thing, ID: {d93371db-0954-4dcd-a1a5-6881b78cb0ea}) - - - - + MAC address - The name of the ParamType (ThingClass: huaweiInverter, Type: thing, ID: {93517bff-1928-4c4a-8207-5fe596c86eba}) + The name of the ParamType (ThingClass: huaweiFusionSolarInverter, Type: thing, ID: {93517bff-1928-4c4a-8207-5fe596c86eba}) - - + + Modbus RTU master + The name of the ParamType (ThingClass: huaweiRtuInverter, Type: thing, ID: {5c7b28b1-3691-452e-8f49-d80ae7bcbe2c}) + + + + + Modbus slave address + The name of the ParamType (ThingClass: huaweiRtuInverter, Type: thing, ID: {de06f027-7940-4c45-9c96-30930ac2796d}) + + + + Phase A current - The name of the ParamType (ThingClass: huaweiMeter, EventType: currentPhaseA, ID: {af48ff45-11ba-401e-a812-bb1db0896449}) ----------- -The name of the StateType ({af48ff45-11ba-401e-a812-bb1db0896449}) of ThingClass huaweiMeter + The name of the StateType ({af48ff45-11ba-401e-a812-bb1db0896449}) of ThingClass huaweiMeter - - Phase A current changed - The name of the EventType ({af48ff45-11ba-401e-a812-bb1db0896449}) of ThingClass huaweiMeter - - - - - + Phase B current - The name of the ParamType (ThingClass: huaweiMeter, EventType: currentPhaseB, ID: {fb5082e4-a2d8-4958-a47d-e80928795ece}) ----------- -The name of the StateType ({fb5082e4-a2d8-4958-a47d-e80928795ece}) of ThingClass huaweiMeter + The name of the StateType ({fb5082e4-a2d8-4958-a47d-e80928795ece}) of ThingClass huaweiMeter - - Phase B current changed - The name of the EventType ({fb5082e4-a2d8-4958-a47d-e80928795ece}) of ThingClass huaweiMeter - - - - - + Phase C current - The name of the ParamType (ThingClass: huaweiMeter, EventType: currentPhaseC, ID: {bdd9aa8b-93fe-4b6b-8a31-08e99d85a06c}) ----------- -The name of the StateType ({bdd9aa8b-93fe-4b6b-8a31-08e99d85a06c}) of ThingClass huaweiMeter + The name of the StateType ({bdd9aa8b-93fe-4b6b-8a31-08e99d85a06c}) of ThingClass huaweiMeter - - Phase C current changed - The name of the EventType ({bdd9aa8b-93fe-4b6b-8a31-08e99d85a06c}) of ThingClass huaweiMeter - - - - + Port - The name of the ParamType (ThingClass: huaweiInverter, Type: thing, ID: {55c4ec99-6342-4309-84a8-d1615f19b2e8}) + The name of the ParamType (ThingClass: huaweiFusionSolarInverter, Type: thing, ID: {55c4ec99-6342-4309-84a8-d1615f19b2e8}) - + Slave ID - The name of the ParamType (ThingClass: huaweiInverter, Type: thing, ID: {aa6e978e-a16b-4722-8330-e706f3c7c21e}) + The name of the ParamType (ThingClass: huaweiFusionSolarInverter, Type: thing, ID: {aa6e978e-a16b-4722-8330-e706f3c7c21e}) - - + + Slave address + The name of the ParamType (ThingClass: huaweiRtuInverter, Type: discovery, ID: {93a4d3a8-c7d0-470b-b6e3-d8fc43b8e8d0}) + + + + Total real energy imported - The name of the ParamType (ThingClass: huaweiMeter, EventType: totalEnergyConsumed, ID: {2cf8d885-37f7-478f-819e-c4e20f2dbe01}) ----------- -The name of the StateType ({2cf8d885-37f7-478f-819e-c4e20f2dbe01}) of ThingClass huaweiMeter + The name of the StateType ({2cf8d885-37f7-478f-819e-c4e20f2dbe01}) of ThingClass huaweiMeter - - Total real energy imported changed - The name of the EventType ({2cf8d885-37f7-478f-819e-c4e20f2dbe01}) of ThingClass huaweiMeter - - - - - - - + + Total real power - The name of the ParamType (ThingClass: huaweiBattery, EventType: currentPower, ID: {53ca1f8a-0267-40aa-b563-762a943c8f55}) ----------- -The name of the StateType ({53ca1f8a-0267-40aa-b563-762a943c8f55}) of ThingClass huaweiBattery ----------- -The name of the ParamType (ThingClass: huaweiMeter, EventType: currentPower, ID: {f480dc82-68e2-44e2-839c-df38b9c10310}) + The name of the StateType ({53ca1f8a-0267-40aa-b563-762a943c8f55}) of ThingClass huaweiBattery ---------- The name of the StateType ({f480dc82-68e2-44e2-839c-df38b9c10310}) of ThingClass huaweiMeter - - - Total real power changed - The name of the EventType ({53ca1f8a-0267-40aa-b563-762a943c8f55}) of ThingClass huaweiBattery ----------- -The name of the EventType ({f480dc82-68e2-44e2-839c-df38b9c10310}) of ThingClass huaweiMeter - - - - + Unit The name of the ParamType (ThingClass: huaweiBattery, Type: thing, ID: {019287a6-c593-45a8-9695-2e1ad8e81c32}) - - + Voltage phase A - The name of the ParamType (ThingClass: huaweiMeter, EventType: voltagePhaseA, ID: {ea5d7924-19a8-415c-aeeb-e04ce08bed33}) ----------- -The name of the StateType ({ea5d7924-19a8-415c-aeeb-e04ce08bed33}) of ThingClass huaweiMeter + The name of the StateType ({ea5d7924-19a8-415c-aeeb-e04ce08bed33}) of ThingClass huaweiMeter - - Voltage phase A changed - The name of the EventType ({ea5d7924-19a8-415c-aeeb-e04ce08bed33}) of ThingClass huaweiMeter - - - - - + Voltage phase B - The name of the ParamType (ThingClass: huaweiMeter, EventType: voltagePhaseB, ID: {f15856d1-645f-4d34-89a7-c1585ca329cc}) ----------- -The name of the StateType ({f15856d1-645f-4d34-89a7-c1585ca329cc}) of ThingClass huaweiMeter + The name of the StateType ({f15856d1-645f-4d34-89a7-c1585ca329cc}) of ThingClass huaweiMeter - - Voltage phase B changed - The name of the EventType ({f15856d1-645f-4d34-89a7-c1585ca329cc}) of ThingClass huaweiMeter - - - - - + Voltage phase C - The name of the ParamType (ThingClass: huaweiMeter, EventType: voltagePhaseC, ID: {aafb5de4-caa1-4a90-8149-cdf85ae5dc2b}) ----------- -The name of the StateType ({aafb5de4-caa1-4a90-8149-cdf85ae5dc2b}) of ThingClass huaweiMeter - - - - - Voltage phase C changed - The name of the EventType ({aafb5de4-caa1-4a90-8149-cdf85ae5dc2b}) of ThingClass huaweiMeter + The name of the StateType ({aafb5de4-caa1-4a90-8149-cdf85ae5dc2b}) of ThingClass huaweiMeter IntegrationPluginHuawei - + The network device discovery is not available. - - No IP address given + + Huawei Solar Inverter + + + + + No Modbus RTU interface available. Please set up a Modbus RTU interface first. + + + + + The Modbus slave address must be a value between 1 and 254. + + + + + The MAC address is not vaild. Please reconfigure the device to fix this. + + + + + The Modbus address not valid. It must be a value between 1 and 254. + + + + + The Modbus RTU resource is not available. + + + + + Could not initialize the communication with the SmartDongle. From 8e5a6395913a109070393a79843b98ae58cf20a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 4 Aug 2022 11:01:28 +0200 Subject: [PATCH 03/11] Make sure we received the correct amount of data from SmartDongle, otherwise warn and do not process incomplete blocks --- huawei/huaweifusionsolar.cpp | 113 ++++++++++++++++++++++++----------- 1 file changed, 79 insertions(+), 34 deletions(-) diff --git a/huawei/huaweifusionsolar.cpp b/huawei/huaweifusionsolar.cpp index a57c960..30b8dfb 100644 --- a/huawei/huaweifusionsolar.cpp +++ b/huawei/huaweifusionsolar.cpp @@ -95,23 +95,27 @@ bool HuaweiFusionSolar::update() if (!m_registersQueue.isEmpty()) return true; - // Add the requests + // Add the requests to queue, begin with power values, since they are most important m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterInverterActivePower); - m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterInverterDeviceStatus); - m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterInverterEnergyProduced); - m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery1Status); - if (m_battery1Available) { - m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery1Power); - m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery1Soc); - } m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterPowerMeterActivePower); - m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery2Status); - if (m_battery2Available) { - m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery2Power); - m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery2Soc); - } + if (m_battery1Available) + m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery1Power); - // Note: since huawei can only process one request at the time, we need to queue the requests + if (m_battery2Available) + m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery2Power); + + m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterInverterEnergyProduced); + m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterInverterDeviceStatus); + m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery1Status); + m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery2Status); + + if (m_battery1Available) + m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery1Soc); + + if (m_battery2Available) + m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery2Soc); + + // Note: since huawei can only process one request at the time, we need to queue the requests and have some time between requests... m_currentRegisterRequest = -1; readNextRegister(); @@ -152,8 +156,12 @@ void HuaweiFusionSolar::readNextRegister() handleModbusError(reply->error()); if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); - qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Inverter active power\" register" << 32080 << "size:" << 2 << unit.values(); - processInverterActivePowerRegisterValues(unit.values()); + qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Inverter active power\" register" << 32080 << "size:" << 2 << "valueCount:" << unit.valueCount() << unit.values() << unit.values().count(); + if (unit.values().count() != 2) { + qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values count. Requested" << 2 << "but received" << unit.values().count(); + } else { + processInverterActivePowerRegisterValues(unit.values()); + } } finishRequest(); @@ -193,8 +201,12 @@ void HuaweiFusionSolar::readNextRegister() handleModbusError(reply->error()); if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); - qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Inverter device status\" register" << 32089 << "size:" << 1 << unit.values(); - processInverterDeviceStatusRegisterValues(unit.values()); + qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Inverter device status\" register" << 32089 << "size:" << 1 << "valueCount:" << unit.valueCount() << unit.values() << unit.values().count(); + if (unit.values().count() != 1) { + qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values count. Requested" << 1 << "but received" << unit.values().count(); + } else { + processInverterDeviceStatusRegisterValues(unit.values()); + } } finishRequest(); }); @@ -233,8 +245,12 @@ void HuaweiFusionSolar::readNextRegister() handleModbusError(reply->error()); if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); - qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Inverter energy produced\" register" << 32106 << "size:" << 2 << unit.values(); - processInverterEnergyProducedRegisterValues(unit.values()); + qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Inverter energy produced\" register" << 32106 << "size:" << 2 << "valueCount:" << unit.valueCount() << unit.values() << unit.values().count(); + if (unit.values().count() != 2) { + qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values count. Requested" << 2 << "but received" << unit.values().count(); + } else { + processInverterEnergyProducedRegisterValues(unit.values()); + } } finishRequest(); }); @@ -272,8 +288,13 @@ void HuaweiFusionSolar::readNextRegister() handleModbusError(reply->error()); if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); - qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Power meter active power\" register" << 37113 << "size:" << 2 << unit.values(); - processPowerMeterActivePowerRegisterValues(unit.values()); + + qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Power meter active power\" register" << 37113 << "size:" << 2 << "valueCount:" << unit.valueCount() << unit.values() << unit.values().count(); + if (unit.values().count() != 2) { + qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values count. Requested" << 2 << "but received" << unit.values().count(); + } else { + processPowerMeterActivePowerRegisterValues(unit.values()); + } } finishRequest(); }); @@ -312,8 +333,12 @@ void HuaweiFusionSolar::readNextRegister() handleModbusError(reply->error()); if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); - qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 1 status\" register" << 37000 << "size:" << 1 << unit.values(); - processLunaBattery1StatusRegisterValues(unit.values()); + qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 1 status\" register" << 37000 << "size:" << 1 << "valueCount:" << unit.valueCount() << unit.values() << unit.values().count(); + if (unit.values().count() != 1) { + qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values count. Requested" << 1 << "but received" << unit.values().count(); + } else { + processLunaBattery1StatusRegisterValues(unit.values()); + } } finishRequest(); }); @@ -351,8 +376,12 @@ void HuaweiFusionSolar::readNextRegister() handleModbusError(reply->error()); if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); - qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 1 power\" register" << 37001 << "size:" << 2 << unit.values(); - processLunaBattery1PowerRegisterValues(unit.values()); + qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 1 power\" register" << 37001 << "size:" << 2 << "valueCount:" << unit.valueCount() << unit.values() << unit.values().count(); + if (unit.values().count() != 2) { + qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values count. Requested" << 2 << "but received" << unit.values().count(); + } else { + processLunaBattery1PowerRegisterValues(unit.values()); + } } finishRequest(); }); @@ -390,8 +419,12 @@ void HuaweiFusionSolar::readNextRegister() handleModbusError(reply->error()); if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); - qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 1 state of charge\" register" << 37004 << "size:" << 1 << unit.values(); - processLunaBattery1SocRegisterValues(unit.values()); + qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 1 state of charge\" register" << 37004 << "size:" << 1 << "valueCount:" << unit.valueCount() << unit.values() << unit.values().count(); + if (unit.values().count() != 1) { + qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values count. Requested" << 1 << "but received" << unit.values().count(); + } else { + processLunaBattery1SocRegisterValues(unit.values()); + } } finishRequest(); }); @@ -430,8 +463,12 @@ void HuaweiFusionSolar::readNextRegister() handleModbusError(reply->error()); if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); - qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 2 status\" register" << 37741 << "size:" << 1 << unit.values(); - processLunaBattery2StatusRegisterValues(unit.values()); + qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 2 status\" register" << 37741 << "size:" << 1 << "valueCount:" << unit.valueCount() << unit.values() << unit.values().count(); + if (unit.values().count() != 1) { + qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values count. Requested" << 1 << "but received" << unit.values().count(); + } else { + processLunaBattery2StatusRegisterValues(unit.values()); + } } finishRequest(); }); @@ -469,8 +506,12 @@ void HuaweiFusionSolar::readNextRegister() handleModbusError(reply->error()); if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); - qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 2 power\" register" << 37743 << "size:" << 2 << unit.values(); - processLunaBattery2PowerRegisterValues(unit.values()); + qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 2 power\" register" << 37743 << "size:" << 2 << "valueCount:" << unit.valueCount() << unit.values() << unit.values().count(); + if (unit.values().count() != 2) { + qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values count. Requested" << 2 << "but received" << unit.values().count(); + } else { + processLunaBattery2PowerRegisterValues(unit.values()); + } } finishRequest(); }); @@ -508,8 +549,12 @@ void HuaweiFusionSolar::readNextRegister() handleModbusError(reply->error()); if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); - qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 2 state of charge\" register" << 37738 << "size:" << 1 << unit.values(); - processLunaBattery2SocRegisterValues(unit.values()); + qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 2 state of charge\" register" << 37738 << "size:" << 1 << "valueCount:" << unit.valueCount() << unit.values() << unit.values().count(); + if (unit.values().count() != 1) { + qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values count. Requested" << 1 << "but received" << unit.values().count(); + } else { + processLunaBattery2SocRegisterValues(unit.values()); + } } finishRequest(); }); From 761ff946108e851f5ad5effc11ca07d1325376f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 9 Aug 2022 11:14:26 +0200 Subject: [PATCH 04/11] Introduce check reachability retries again --- huawei/huaweifusionsolar.cpp | 95 ++++++++++++++----- huawei/huaweifusionsolar.h | 11 +++ huawei/huaweifusionsolardiscovery.cpp | 25 +++-- huawei/huaweifusionsolardiscovery.h | 1 - .../tools/connectiontool/modbusrtu.py | 28 +++--- .../tools/connectiontool/modbustcp.py | 32 +++---- libnymea-modbus/tools/generate-connection.py | 91 +++++++++++++++++- 7 files changed, 215 insertions(+), 68 deletions(-) diff --git a/huawei/huaweifusionsolar.cpp b/huawei/huaweifusionsolar.cpp index 30b8dfb..c836159 100644 --- a/huawei/huaweifusionsolar.cpp +++ b/huawei/huaweifusionsolar.cpp @@ -37,6 +37,10 @@ NYMEA_LOGGING_CATEGORY(dcHuaweiFusionSolar, "HuaweiFusionSolar") HuaweiFusionSolar::HuaweiFusionSolar(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent) : HuaweiFusionModbusTcpConnection(hostAddress, port, slaveId, parent) { + // Note: sometimes right after the discovery / setup the check fails the first time due to server busy error, + // this is a very slow or busy device since it returns quiet often that error. Don't faile with the first busy error... + setCheckReachableRetries(3); + connect(this, &HuaweiFusionModbusTcpConnection::connectionStateChanged, this, [=](bool connected){ if (!connected) { m_registersQueue.clear(); @@ -97,13 +101,14 @@ bool HuaweiFusionSolar::update() // Add the requests to queue, begin with power values, since they are most important m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterInverterActivePower); - m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterPowerMeterActivePower); if (m_battery1Available) m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery1Power); if (m_battery2Available) m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery2Power); + m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterPowerMeterActivePower); + m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterInverterEnergyProduced); m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterInverterDeviceStatus); m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery1Status); @@ -122,6 +127,11 @@ bool HuaweiFusionSolar::update() return true; } +double HuaweiFusionSolar::actualInverterPower() const +{ + return m_actualInverterPower; +} + void HuaweiFusionSolar::readNextRegister() { // Check if currently a reply is pending @@ -157,10 +167,11 @@ void HuaweiFusionSolar::readNextRegister() if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Inverter active power\" register" << 32080 << "size:" << 2 << "valueCount:" << unit.valueCount() << unit.values() << unit.values().count(); - if (unit.values().count() != 2) { - qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values count. Requested" << 2 << "but received" << unit.values().count(); + if (!valuesAreVaild(unit.values(), 2)) { + qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values. Requested" << 2 << "but received" << unit.values(); } else { processInverterActivePowerRegisterValues(unit.values()); + calculatActualInverterPower(); } } @@ -202,8 +213,8 @@ void HuaweiFusionSolar::readNextRegister() if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Inverter device status\" register" << 32089 << "size:" << 1 << "valueCount:" << unit.valueCount() << unit.values() << unit.values().count(); - if (unit.values().count() != 1) { - qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values count. Requested" << 1 << "but received" << unit.values().count(); + if (!valuesAreVaild(unit.values(), 1)) { + qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values. Requested" << 1 << "but received" << unit.values(); } else { processInverterDeviceStatusRegisterValues(unit.values()); } @@ -246,8 +257,8 @@ void HuaweiFusionSolar::readNextRegister() if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Inverter energy produced\" register" << 32106 << "size:" << 2 << "valueCount:" << unit.valueCount() << unit.values() << unit.values().count(); - if (unit.values().count() != 2) { - qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values count. Requested" << 2 << "but received" << unit.values().count(); + if (!valuesAreVaild(unit.values(), 2)) { + qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values. Requested" << 2 << "but received" << unit.values(); } else { processInverterEnergyProducedRegisterValues(unit.values()); } @@ -288,10 +299,9 @@ void HuaweiFusionSolar::readNextRegister() handleModbusError(reply->error()); if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); - qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Power meter active power\" register" << 37113 << "size:" << 2 << "valueCount:" << unit.valueCount() << unit.values() << unit.values().count(); - if (unit.values().count() != 2) { - qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values count. Requested" << 2 << "but received" << unit.values().count(); + if (!valuesAreVaild(unit.values(), 2)) { + qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values. Requested" << 2 << "but received" << unit.values(); } else { processPowerMeterActivePowerRegisterValues(unit.values()); } @@ -334,8 +344,8 @@ void HuaweiFusionSolar::readNextRegister() if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 1 status\" register" << 37000 << "size:" << 1 << "valueCount:" << unit.valueCount() << unit.values() << unit.values().count(); - if (unit.values().count() != 1) { - qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values count. Requested" << 1 << "but received" << unit.values().count(); + if (!valuesAreVaild(unit.values(), 1)) { + qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values. Requested" << 1 << "but received" << unit.values(); } else { processLunaBattery1StatusRegisterValues(unit.values()); } @@ -377,10 +387,11 @@ void HuaweiFusionSolar::readNextRegister() if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 1 power\" register" << 37001 << "size:" << 2 << "valueCount:" << unit.valueCount() << unit.values() << unit.values().count(); - if (unit.values().count() != 2) { - qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values count. Requested" << 2 << "but received" << unit.values().count(); + if (!valuesAreVaild(unit.values(), 2)) { + qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values. Requested" << 2 << "but received" << unit.values(); } else { processLunaBattery1PowerRegisterValues(unit.values()); + calculatActualInverterPower(); } } finishRequest(); @@ -420,8 +431,8 @@ void HuaweiFusionSolar::readNextRegister() if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 1 state of charge\" register" << 37004 << "size:" << 1 << "valueCount:" << unit.valueCount() << unit.values() << unit.values().count(); - if (unit.values().count() != 1) { - qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values count. Requested" << 1 << "but received" << unit.values().count(); + if (!valuesAreVaild(unit.values(), 1)) { + qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values. Requested" << 1 << "but received" << unit.values(); } else { processLunaBattery1SocRegisterValues(unit.values()); } @@ -464,8 +475,8 @@ void HuaweiFusionSolar::readNextRegister() if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 2 status\" register" << 37741 << "size:" << 1 << "valueCount:" << unit.valueCount() << unit.values() << unit.values().count(); - if (unit.values().count() != 1) { - qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values count. Requested" << 1 << "but received" << unit.values().count(); + if (!valuesAreVaild(unit.values(), 1)) { + qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values count. Requested" << 1 << "but received" << unit.values(); } else { processLunaBattery2StatusRegisterValues(unit.values()); } @@ -507,10 +518,11 @@ void HuaweiFusionSolar::readNextRegister() if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 2 power\" register" << 37743 << "size:" << 2 << "valueCount:" << unit.valueCount() << unit.values() << unit.values().count(); - if (unit.values().count() != 2) { - qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values count. Requested" << 2 << "but received" << unit.values().count(); + if (!valuesAreVaild(unit.values(), 1)) { + qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values count. Requested" << 2 << "but received" << unit.values(); } else { processLunaBattery2PowerRegisterValues(unit.values()); + calculatActualInverterPower(); } } finishRequest(); @@ -550,8 +562,8 @@ void HuaweiFusionSolar::readNextRegister() if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 2 state of charge\" register" << 37738 << "size:" << 1 << "valueCount:" << unit.valueCount() << unit.values() << unit.values().count(); - if (unit.values().count() != 1) { - qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values count. Requested" << 1 << "but received" << unit.values().count(); + if (!valuesAreVaild(unit.values(), 1)) { + qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values. Requested" << 1 << "but received" << unit.values(); } else { processLunaBattery2SocRegisterValues(unit.values()); } @@ -574,6 +586,45 @@ void HuaweiFusionSolar::readNextRegister() } } +bool HuaweiFusionSolar::valuesAreVaild(const QVector &values, int readSize) +{ + if (values.count() != readSize) + return false; + + // According to the documentation: + // 0x7FFF: invalid value of the floating point type returned by one register + // 0xFFFF: invalid value of a type other than the floating point type returned by one register + // 0xFFFFFFFF: invalid value returned by two registers + + if (values.count() == 2) { + bool floatingPointValid = (values.at(0) != 0x7fff && values.at(1) != 0xffff); + bool otherTypesValid = (values.at(0) != 0xffff && values.at(1) != 0xffff); + return floatingPointValid && otherTypesValid; + } + + if (values.count() == 1) + return values.at(0) != 0x7fff && values.at(0) != 0xffff; + + return true; +} + +void HuaweiFusionSolar::calculatActualInverterPower() +{ + double actualPower = m_inverterActivePower * -1000.0; + if (m_battery1Available) + actualPower += m_lunaBattery1Power; + + if (m_battery2Available) + actualPower += m_lunaBattery2Power; + + qCDebug(dcHuaweiFusionSolar()) << "Inverter power:" << m_inverterActivePower << "W Battery 1:" << m_lunaBattery1Power << "W Battery 2:" << m_lunaBattery2Power << "W -->" << "Actual inverter power:" << actualPower << "W"; + if (m_actualInverterPower != actualPower) + return; + + m_actualInverterPower = actualPower; + emit actualInverterPowerChanged(m_actualInverterPower); +} + void HuaweiFusionSolar::finishRequest() { m_currentRegisterRequest = -1; diff --git a/huawei/huaweifusionsolar.h b/huawei/huaweifusionsolar.h index 7c56ecf..cb32652 100644 --- a/huawei/huaweifusionsolar.h +++ b/huawei/huaweifusionsolar.h @@ -46,6 +46,13 @@ public: bool initialize() override; virtual bool update() override; + // The inverter shows the pv power AND the power of the connected batteries if they discharge. + // This power values represents the power taking the batteries into account. + double actualInverterPower() const; + +signals: + void actualInverterPowerChanged(double actualInverterPower); + private: QQueue m_registersQueue; QModbusReply *m_initReply = nullptr; @@ -56,10 +63,14 @@ private: bool m_battery1Available = true; bool m_battery2Available = true; + double m_actualInverterPower = 0; + QString exceptionToString(QModbusPdu::ExceptionCode exception); private slots: void readNextRegister(); + bool valuesAreVaild(const QVector &values, int readSize); + void calculatActualInverterPower(); }; diff --git a/huawei/huaweifusionsolardiscovery.cpp b/huawei/huaweifusionsolardiscovery.cpp index 10103d8..5443b5a 100644 --- a/huawei/huaweifusionsolardiscovery.cpp +++ b/huawei/huaweifusionsolardiscovery.cpp @@ -37,12 +37,7 @@ HuaweiFusionSolarDiscovery::HuaweiFusionSolarDiscovery(NetworkDeviceDiscovery *n m_port(port), m_modbusAddress(modbusAddress) { - m_gracePeriodTimer.setSingleShot(true); - m_gracePeriodTimer.setInterval(3000); - connect(&m_gracePeriodTimer, &QTimer::timeout, this, [this](){ - qCDebug(dcHuawei()) << "Discovery: Grace period timer triggered."; - finishDiscovery(); - }); + } @@ -71,7 +66,11 @@ void HuaweiFusionSolarDiscovery::startDiscovery() } } - m_gracePeriodTimer.start(); + // Finish with some delay so the last added network device information objects still can be checked. + QTimer::singleShot(3000, this, [this](){ + qCDebug(dcHuawei()) << "Discovery: Grace period timer triggered."; + finishDiscovery(); + }); }); } @@ -108,20 +107,18 @@ void HuaweiFusionSolarDiscovery::checkNetworkDevice(const NetworkDeviceInfo &net return; } - m_discoveryResults.append(networkDeviceInfo); - qCDebug(dcHuawei()) << "Discovery: --> Found" << networkDeviceInfo; + m_discoveryResults.append(networkDeviceInfo); // Done with this connection cleanupConnection(connection); }); + // Initializing... if (!connection->initialize()) { qCDebug(dcHuawei()) << "Discovery: Unable to initialize connection on" << networkDeviceInfo.address().toString() << "Continue...";; cleanupConnection(connection); } - - // Initializing... }); // If we get any error...skip this host... @@ -138,9 +135,12 @@ void HuaweiFusionSolarDiscovery::checkNetworkDevice(const NetworkDeviceInfo &net cleanupConnection(connection); }); + // Try to connect, maybe it works, maybe not, + // but retry only once to communicate with the device for reachability check... + connection->setCheckReachableRetries(1); + // Try to connect, maybe it works, maybe not... connection->connectDevice(); - } void HuaweiFusionSolarDiscovery::cleanupConnection(HuaweiFusionSolar *connection) @@ -160,7 +160,6 @@ void HuaweiFusionSolarDiscovery::finishDiscovery() qCInfo(dcHuawei()) << "Discovery: Finished the discovery process. Found" << m_discoveryResults.count() << "inverters in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz"); - m_gracePeriodTimer.stop(); emit discoveryFinished(); } diff --git a/huawei/huaweifusionsolardiscovery.h b/huawei/huaweifusionsolardiscovery.h index 9cecdf2..be3753a 100644 --- a/huawei/huaweifusionsolardiscovery.h +++ b/huawei/huaweifusionsolardiscovery.h @@ -55,7 +55,6 @@ private: quint16 m_port; quint16 m_modbusAddress; - QTimer m_gracePeriodTimer; QDateTime m_startDateTime; NetworkDeviceInfos m_networkDeviceInfos; diff --git a/libnymea-modbus/tools/connectiontool/modbusrtu.py b/libnymea-modbus/tools/connectiontool/modbusrtu.py index 09d74a1..0cb7630 100644 --- a/libnymea-modbus/tools/connectiontool/modbusrtu.py +++ b/libnymea-modbus/tools/connectiontool/modbusrtu.py @@ -278,35 +278,35 @@ def writeTestReachabilityImplementationsRtu(fileDescriptor, className, registerD writeLine(fileDescriptor, 'void %s::testReachability()' % (className)) writeLine(fileDescriptor, '{') - writeLine(fileDescriptor, ' if (m_testRechableReply)') + writeLine(fileDescriptor, ' if (m_checkRechableReply)') writeLine(fileDescriptor, ' return;') writeLine(fileDescriptor) writeLine(fileDescriptor, ' // Try to read the check reachability register %s in order to verify if the communication is working or not.' % checkReachableRegister['id']) writeLine(fileDescriptor, ' qCDebug(dc%s()) << "--> Test reachability by reading \\"%s\\" register:" << %s << "size:" << %s;' % (className, checkReachableRegister['description'], checkReachableRegister['address'], checkReachableRegister['size'])) - writeLine(fileDescriptor, ' m_testRechableReply = read%s();' % (propertyName[0].upper() + propertyName[1:])) - writeLine(fileDescriptor, ' if (!m_testRechableReply) {') + writeLine(fileDescriptor, ' m_checkRechableReply = read%s();' % (propertyName[0].upper() + propertyName[1:])) + writeLine(fileDescriptor, ' if (!m_checkRechableReply) {') writeLine(fileDescriptor, ' qCDebug(dc%s()) << "Error occurred verifying reachability by reading \\"%s\\" register";' % (className, checkReachableRegister['description'])) - writeLine(fileDescriptor, ' emit checkReachabilityFailed();') + writeLine(fileDescriptor, ' onReachabilityCheckFailed();') writeLine(fileDescriptor, ' return;') writeLine(fileDescriptor, ' }') writeLine(fileDescriptor) - writeLine(fileDescriptor, ' if (m_testRechableReply->isFinished()) {') - writeLine(fileDescriptor, ' m_testRechableReply = nullptr;') - writeLine(fileDescriptor, ' emit checkReachabilityFailed();') + writeLine(fileDescriptor, ' if (m_checkRechableReply->isFinished()) {') + writeLine(fileDescriptor, ' m_checkRechableReply = nullptr;') + writeLine(fileDescriptor, ' onReachabilityCheckFailed();') writeLine(fileDescriptor, ' return;') writeLine(fileDescriptor, ' }') writeLine(fileDescriptor) - writeLine(fileDescriptor, ' connect(m_testRechableReply, &ModbusRtuReply::finished, this, [this](){') + writeLine(fileDescriptor, ' connect(m_checkRechableReply, &ModbusRtuReply::finished, this, [this](){') writeLine(fileDescriptor, ' // Note: we don\'t care about the result here, only the error') - writeLine(fileDescriptor, ' handleModbusError(m_testRechableReply->error());') - writeLine(fileDescriptor, ' if (m_testRechableReply->error() != ModbusRtuReply::NoError)') - writeLine(fileDescriptor, ' emit checkReachabilityFailed();') + writeLine(fileDescriptor, ' handleModbusError(m_checkRechableReply->error());') + writeLine(fileDescriptor, ' if (m_checkRechableReply->error() != ModbusRtuReply::NoError)') + writeLine(fileDescriptor, ' onReachabilityCheckFailed();') writeLine(fileDescriptor) - writeLine(fileDescriptor, ' m_testRechableReply = nullptr;') + writeLine(fileDescriptor, ' m_checkRechableReply = nullptr;') writeLine(fileDescriptor, ' });') writeLine(fileDescriptor) - writeLine(fileDescriptor, ' connect(m_testRechableReply, &ModbusRtuReply::errorOccurred, this, [this] (ModbusRtuReply::Error error){') - writeLine(fileDescriptor, ' qCDebug(dc%s()) << "ModbusRtu reply error occurred while verifying reachability by reading \\"%s\\" register" << error << m_testRechableReply->errorString();' % (className, checkReachableRegister['description'])) + writeLine(fileDescriptor, ' connect(m_checkRechableReply, &ModbusRtuReply::errorOccurred, this, [this] (ModbusRtuReply::Error error){') + writeLine(fileDescriptor, ' qCDebug(dc%s()) << "ModbusRtu reply error occurred while verifying reachability by reading \\"%s\\" register" << error << m_checkRechableReply->errorString();' % (className, checkReachableRegister['description'])) writeLine(fileDescriptor, ' });') writeLine(fileDescriptor, '}') writeLine(fileDescriptor) diff --git a/libnymea-modbus/tools/connectiontool/modbustcp.py b/libnymea-modbus/tools/connectiontool/modbustcp.py index dc2246f..6513107 100644 --- a/libnymea-modbus/tools/connectiontool/modbustcp.py +++ b/libnymea-modbus/tools/connectiontool/modbustcp.py @@ -280,36 +280,36 @@ def writeTestReachabilityImplementationsTcp(fileDescriptor, className, registerD writeLine(fileDescriptor, 'void %s::testReachability()' % (className)) writeLine(fileDescriptor, '{') - writeLine(fileDescriptor, ' if (m_testRechableReply)') + writeLine(fileDescriptor, ' if (m_checkRechableReply)') writeLine(fileDescriptor, ' return;') writeLine(fileDescriptor) writeLine(fileDescriptor, ' // Try to read the check reachability register %s in order to verify if the communication is working or not.' % checkReachableRegister['id']) writeLine(fileDescriptor, ' qCDebug(dc%s()) << "--> Test reachability by reading \\"%s\\" register:" << %s << "size:" << %s;' % (className, checkReachableRegister['description'], checkReachableRegister['address'], checkReachableRegister['size'])) - writeLine(fileDescriptor, ' m_testRechableReply = read%s();' % (propertyName[0].upper() + propertyName[1:])) - writeLine(fileDescriptor, ' if (!m_testRechableReply) {') + writeLine(fileDescriptor, ' m_checkRechableReply = read%s();' % (propertyName[0].upper() + propertyName[1:])) + writeLine(fileDescriptor, ' if (!m_checkRechableReply) {') writeLine(fileDescriptor, ' qCDebug(dc%s()) << "Error occurred verifying reachability by reading \\"%s\\" register";' % (className, checkReachableRegister['description'])) - writeLine(fileDescriptor, ' emit checkReachabilityFailed();') + writeLine(fileDescriptor, ' onReachabilityCheckFailed();') writeLine(fileDescriptor, ' return;') writeLine(fileDescriptor, ' }') writeLine(fileDescriptor) - writeLine(fileDescriptor, ' if (m_testRechableReply->isFinished()) {') - writeLine(fileDescriptor, ' m_testRechableReply->deleteLater(); // Broadcast reply returns immediatly') - writeLine(fileDescriptor, ' m_testRechableReply = nullptr;') - writeLine(fileDescriptor, ' emit checkReachabilityFailed();') + writeLine(fileDescriptor, ' if (m_checkRechableReply->isFinished()) {') + writeLine(fileDescriptor, ' m_checkRechableReply->deleteLater(); // Broadcast reply returns immediatly') + writeLine(fileDescriptor, ' m_checkRechableReply = nullptr;') + writeLine(fileDescriptor, ' onReachabilityCheckFailed();') writeLine(fileDescriptor, ' return;') writeLine(fileDescriptor, ' }') writeLine(fileDescriptor) - writeLine(fileDescriptor, ' connect(m_testRechableReply, &QModbusReply::finished, this, [this](){') - writeLine(fileDescriptor, ' handleModbusError(m_testRechableReply->error());') - writeLine(fileDescriptor, ' if (m_testRechableReply->error() != QModbusDevice::NoError)') - writeLine(fileDescriptor, ' emit checkReachabilityFailed();') + writeLine(fileDescriptor, ' connect(m_checkRechableReply, &QModbusReply::finished, this, [this](){') + writeLine(fileDescriptor, ' handleModbusError(m_checkRechableReply->error());') + writeLine(fileDescriptor, ' if (m_checkRechableReply->error() != QModbusDevice::NoError)') + writeLine(fileDescriptor, ' onReachabilityCheckFailed();') writeLine(fileDescriptor) - writeLine(fileDescriptor, ' m_testRechableReply->deleteLater();') - writeLine(fileDescriptor, ' m_testRechableReply = nullptr;') + writeLine(fileDescriptor, ' m_checkRechableReply->deleteLater();') + writeLine(fileDescriptor, ' m_checkRechableReply = nullptr;') writeLine(fileDescriptor, ' });') writeLine(fileDescriptor) - writeLine(fileDescriptor, ' connect(m_testRechableReply, &QModbusReply::errorOccurred, this, [this] (QModbusDevice::Error error){') - writeLine(fileDescriptor, ' qCDebug(dc%s()) << "Modbus reply error occurred while verifying reachability by reading \\"%s\\" register" << error << m_testRechableReply->errorString();' % (className, checkReachableRegister['description'])) + writeLine(fileDescriptor, ' connect(m_checkRechableReply, &QModbusReply::errorOccurred, this, [this] (QModbusDevice::Error error){') + writeLine(fileDescriptor, ' qCDebug(dc%s()) << "Modbus reply error occurred while verifying reachability by reading \\"%s\\" register" << error << m_checkRechableReply->errorString();' % (className, checkReachableRegister['description'])) writeLine(fileDescriptor, ' });') writeLine(fileDescriptor, '}') writeLine(fileDescriptor) diff --git a/libnymea-modbus/tools/generate-connection.py b/libnymea-modbus/tools/generate-connection.py index 257cd89..3e2a251 100644 --- a/libnymea-modbus/tools/generate-connection.py +++ b/libnymea-modbus/tools/generate-connection.py @@ -71,6 +71,9 @@ def writeTcpHeaderFile(): writeLine(headerFile, ' ModbusDataUtils::ByteOrder endianness() const;') writeLine(headerFile, ' void setEndianness(ModbusDataUtils::ByteOrder endianness);') writeLine(headerFile) + writeLine(headerFile, ' uint checkReachableRetries() const;') + writeLine(headerFile, ' void setCheckReachableRetries(uint checkReachableRetries);') + writeLine(headerFile) # Write registers get method declarations writePropertyGetSetMethodDeclarationsTcp(headerFile, registerJson['registers']) @@ -108,6 +111,7 @@ def writeTcpHeaderFile(): writeLine(headerFile, 'signals:') writeLine(headerFile, ' void reachableChanged(bool reachable);') writeLine(headerFile, ' void checkReachabilityFailed();') + writeLine(headerFile, ' void checkReachableRetriesChanged(uint checkReachableRetries);') writeLine(headerFile) writeLine(headerFile, ' void initializationFinished(bool success);') writeLine(headerFile, ' void updateFinished();') @@ -145,7 +149,11 @@ def writeTcpHeaderFile(): writeLine(headerFile, 'private:') writeLine(headerFile, ' ModbusDataUtils::ByteOrder m_endianness = ModbusDataUtils::ByteOrder%s;' % endianness) writeLine(headerFile, ' quint16 m_slaveId = 1;') + writeLine(headerFile) writeLine(headerFile, ' bool m_reachable = false;') + writeLine(headerFile, ' QModbusReply *m_checkRechableReply = nullptr;') + writeLine(headerFile, ' uint m_checkReachableRetries = 0;') + writeLine(headerFile, ' uint m_checkReachableRetriesCount = 0;') writeLine(headerFile, ' bool m_communicationWorking = false;') writeLine(headerFile, ' quint8 m_communicationFailedMax = %s;' % (errorLimitUntilNotReachable)) writeLine(headerFile, ' quint8 m_communicationFailedCounter = 0;') @@ -159,7 +167,7 @@ def writeTcpHeaderFile(): writeLine(headerFile) writeLine(headerFile, ' void verifyUpdateFinished();') writeLine(headerFile) - writeLine(headerFile, ' QModbusReply *m_testRechableReply = nullptr;') + writeLine(headerFile, ' void onReachabilityCheckFailed();') writeLine(headerFile, ' void evaluateReachableState();') # End of class @@ -180,6 +188,8 @@ def writeTcpSourceFile(): writeLine(sourceFile) writeLine(sourceFile, '#include "%s"' % headerFileName) writeLine(sourceFile, '#include ') + writeLine(sourceFile, '#include ') + writeLine(sourceFile, '#include ') writeLine(sourceFile) writeLine(sourceFile, 'NYMEA_LOGGING_CATEGORY(dc%s, "%s")' % (className, className)) writeLine(sourceFile) @@ -197,11 +207,13 @@ def writeTcpSourceFile(): writeLine(sourceFile, ' m_pendingUpdateReplies.clear();') writeLine(sourceFile, ' m_communicationWorking = false;') writeLine(sourceFile, ' m_communicationFailedCounter = 0;') + writeLine(sourceFile, ' m_checkReachableRetriesCount = 0;') writeLine(sourceFile, ' testReachability();') writeLine(sourceFile, ' } else {') writeLine(sourceFile, ' qCWarning(dc%s()) << "Modbus TCP connection diconnected from" << m_hostAddress.toString() << ". The connection is not reachable any more.";' % (className)) writeLine(sourceFile, ' m_communicationWorking = false;') writeLine(sourceFile, ' m_communicationFailedCounter = 0;') + writeLine(sourceFile, ' m_checkReachableRetriesCount = 0;') writeLine(sourceFile, ' }') writeLine(sourceFile) writeLine(sourceFile, ' evaluateReachableState();') @@ -215,6 +227,22 @@ def writeTcpSourceFile(): writeLine(sourceFile, '}') writeLine(sourceFile) + writeLine(sourceFile, 'uint %s::checkReachableRetries() const' % (className)) + writeLine(sourceFile, '{') + writeLine(sourceFile, ' return m_checkReachableRetries;') + writeLine(sourceFile, '}') + writeLine(sourceFile) + + writeLine(sourceFile, 'void %s::setCheckReachableRetries(uint checkReachableRetries)' % (className)) + writeLine(sourceFile, '{') + writeLine(sourceFile, ' if (m_checkReachableRetries == checkReachableRetries)') + writeLine(sourceFile, ' return;') + writeLine(sourceFile) + writeLine(sourceFile, ' m_checkReachableRetries = checkReachableRetries;') + writeLine(sourceFile, ' emit checkReachableRetriesChanged(m_checkReachableRetries);') + writeLine(sourceFile, '}') + writeLine(sourceFile) + writeLine(sourceFile, 'ModbusDataUtils::ByteOrder %s::endianness() const' % (className)) writeLine(sourceFile, '{') writeLine(sourceFile, ' return m_endianness;') @@ -324,6 +352,21 @@ def writeTcpSourceFile(): writeLine(sourceFile, '}') writeLine(sourceFile) + writeLine(sourceFile, 'void %s::onReachabilityCheckFailed()' % (className)) + writeLine(sourceFile, '{') + writeLine(sourceFile, ' m_checkReachableRetriesCount++;') + writeLine(sourceFile) + writeLine(sourceFile, ' if (m_checkReachableRetriesCount <= m_checkReachableRetries) {') + writeLine(sourceFile, ' qCDebug(dc%s()) << "Reachability test failed. Retry in on second" << m_checkReachableRetriesCount << "/" << m_checkReachableRetries;' % (className)) + writeLine(sourceFile, ' QTimer::singleShot(1000, this, &%s::testReachability);' % (className)) + writeLine(sourceFile, ' return;') + writeLine(sourceFile, ' }') + writeLine(sourceFile) + writeLine(sourceFile, ' // The test reachability method failed, not retrying any more') + writeLine(sourceFile, ' emit checkReachabilityFailed();') + writeLine(sourceFile, '}') + writeLine(sourceFile) + writeLine(sourceFile, 'void %s::evaluateReachableState()' % (className)) writeLine(sourceFile, '{') writeLine(sourceFile, ' bool reachable = m_communicationWorking && connected();') @@ -332,6 +375,7 @@ def writeTcpSourceFile(): writeLine(sourceFile) writeLine(sourceFile, ' m_reachable = reachable;') writeLine(sourceFile, ' emit reachableChanged(m_reachable);') + writeLine(sourceFile, ' m_checkReachableRetriesCount = 0;') writeLine(sourceFile, '}') writeLine(sourceFile) @@ -395,6 +439,9 @@ def writeRtuHeaderFile(): writeLine(headerFile) writeLine(headerFile, ' bool reachable() const;') writeLine(headerFile) + writeLine(headerFile, ' uint checkReachableRetries() const;') + writeLine(headerFile, ' void setCheckReachableRetries(uint checkReachableRetries);') + writeLine(headerFile) writeLine(headerFile, ' ModbusDataUtils::ByteOrder endianness() const;') writeLine(headerFile, ' void setEndianness(ModbusDataUtils::ByteOrder endianness);') writeLine(headerFile) @@ -433,6 +480,7 @@ def writeRtuHeaderFile(): writeLine(headerFile, 'signals:') writeLine(headerFile, ' void reachableChanged(bool reachable);') writeLine(headerFile, ' void checkReachabilityFailed();') + writeLine(headerFile, ' void checkReachableRetriesChanged(uint checkReachableRetries);') writeLine(headerFile) writeLine(headerFile, ' void initializationFinished(bool success);') writeLine(headerFile, ' void updateFinished();') @@ -471,7 +519,11 @@ def writeRtuHeaderFile(): writeLine(headerFile, ' ModbusRtuMaster *m_modbusRtuMaster = nullptr;') writeLine(headerFile, ' ModbusDataUtils::ByteOrder m_endianness = ModbusDataUtils::ByteOrder%s;' % endianness) writeLine(headerFile, ' quint16 m_slaveId = 1;') + writeLine(headerFile) writeLine(headerFile, ' bool m_reachable = false;') + writeLine(headerFile, ' ModbusRtuReply *m_checkRechableReply = nullptr;') + writeLine(headerFile, ' uint m_checkReachableRetries = 0;') + writeLine(headerFile, ' uint m_checkReachableRetriesCount = 0;') writeLine(headerFile, ' bool m_communicationWorking = false;') writeLine(headerFile, ' quint8 m_communicationFailedMax = %s;' % (errorLimitUntilNotReachable)) writeLine(headerFile, ' quint8 m_communicationFailedCounter = 0;') @@ -485,7 +537,7 @@ def writeRtuHeaderFile(): writeLine(headerFile) writeLine(headerFile, ' void verifyUpdateFinished();') writeLine(headerFile) - writeLine(headerFile, ' ModbusRtuReply *m_testRechableReply = nullptr;') + writeLine(headerFile, ' void onReachabilityCheckFailed();') writeLine(headerFile, ' void evaluateReachableState();') @@ -508,6 +560,7 @@ def writeRtuSourceFile(): writeLine(sourceFile, '#include "%s"' % headerFileName) writeLine(sourceFile, '#include ') writeLine(sourceFile, '#include ') + writeLine(sourceFile, '#include ') writeLine(sourceFile) writeLine(sourceFile, 'NYMEA_LOGGING_CATEGORY(dc%s, "%s")' % (className, className)) writeLine(sourceFile) @@ -525,11 +578,13 @@ def writeRtuSourceFile(): writeLine(sourceFile, ' m_pendingUpdateReplies.clear();') writeLine(sourceFile, ' m_communicationWorking = false;') writeLine(sourceFile, ' m_communicationFailedCounter = 0;') + writeLine(sourceFile, ' m_checkReachableRetriesCount = 0;') writeLine(sourceFile, ' testReachability();') writeLine(sourceFile, ' } else {') writeLine(sourceFile, ' qCWarning(dc%s()) << "Modbus RTU resource" << m_modbusRtuMaster->serialPort() << "disconnected. The connection is not reachable any more.";' % (className)) writeLine(sourceFile, ' m_communicationWorking = false;') writeLine(sourceFile, ' m_communicationFailedCounter = 0;') + writeLine(sourceFile, ' m_checkReachableRetriesCount = 0;') writeLine(sourceFile, ' }') writeLine(sourceFile) writeLine(sourceFile, ' evaluateReachableState();') @@ -554,6 +609,22 @@ def writeRtuSourceFile(): writeLine(sourceFile, '}') writeLine(sourceFile) + writeLine(sourceFile, 'uint %s::checkReachableRetries() const' % (className)) + writeLine(sourceFile, '{') + writeLine(sourceFile, ' return m_checkReachableRetries;') + writeLine(sourceFile, '}') + writeLine(sourceFile) + + writeLine(sourceFile, 'void %s::setCheckReachableRetries(uint checkReachableRetries)' % (className)) + writeLine(sourceFile, '{') + writeLine(sourceFile, ' if (m_checkReachableRetries == checkReachableRetries)') + writeLine(sourceFile, ' return;') + writeLine(sourceFile) + writeLine(sourceFile, ' m_checkReachableRetries = checkReachableRetries;') + writeLine(sourceFile, ' emit checkReachableRetriesChanged(m_checkReachableRetries);') + writeLine(sourceFile, '}') + writeLine(sourceFile) + writeLine(sourceFile, 'ModbusDataUtils::ByteOrder %s::endianness() const' % (className)) writeLine(sourceFile, '{') writeLine(sourceFile, ' return m_endianness;') @@ -664,6 +735,21 @@ def writeRtuSourceFile(): writeLine(sourceFile, '}') writeLine(sourceFile) + writeLine(sourceFile, 'void %s::onReachabilityCheckFailed()' % (className)) + writeLine(sourceFile, '{') + writeLine(sourceFile, ' m_checkReachableRetriesCount++;') + writeLine(sourceFile) + writeLine(sourceFile, ' if (m_checkReachableRetriesCount <= m_checkReachableRetries) {') + writeLine(sourceFile, ' qCDebug(dc%s()) << "Reachability test failed. Retry in on second" << m_checkReachableRetriesCount << "/" << m_checkReachableRetries;' % (className)) + writeLine(sourceFile, ' QTimer::singleShot(1000, this, &%s::testReachability);' % (className)) + writeLine(sourceFile, ' return;') + writeLine(sourceFile, ' }') + writeLine(sourceFile) + writeLine(sourceFile, ' // The test reachability method failed, not retrying any more') + writeLine(sourceFile, ' emit checkReachabilityFailed();') + writeLine(sourceFile, '}') + writeLine(sourceFile) + writeLine(sourceFile, 'void %s::evaluateReachableState()' % (className)) writeLine(sourceFile, '{') writeLine(sourceFile, ' bool reachable = m_communicationWorking && m_modbusRtuMaster->connected();') @@ -672,6 +758,7 @@ def writeRtuSourceFile(): writeLine(sourceFile) writeLine(sourceFile, ' m_reachable = reachable;') writeLine(sourceFile, ' emit reachableChanged(m_reachable);') + writeLine(sourceFile, ' m_checkReachableRetriesCount = 0;') writeLine(sourceFile, '}') writeLine(sourceFile) From 225fa3ed84602f49aafe63485f05eb48886e4ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 9 Aug 2022 11:16:28 +0200 Subject: [PATCH 05/11] Make huawei inverter power independent from the battery power --- huawei/integrationpluginhuawei.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/huawei/integrationpluginhuawei.cpp b/huawei/integrationpluginhuawei.cpp index 928fdf0..a759e79 100644 --- a/huawei/integrationpluginhuawei.cpp +++ b/huawei/integrationpluginhuawei.cpp @@ -439,9 +439,13 @@ void IntegrationPluginHuawei::setupFusionSolar(ThingSetupInfo *info) } }); - connect(connection, &HuaweiFusionSolar::inverterActivePowerChanged, this, [thing](float inverterActivePower){ + connect(connection, &HuaweiFusionSolar::inverterActivePowerChanged, this, [](float inverterActivePower){ qCDebug(dcHuawei()) << "Inverter power changed" << inverterActivePower * -1000.0 << "W"; - thing->setStateValue(huaweiFusionSolarInverterCurrentPowerStateTypeId, inverterActivePower * -1000.0); + }); + + connect(connection, &HuaweiFusionSolar::actualInverterPowerChanged, this, [thing](float actualInverterPower){ + qCDebug(dcHuawei()) << "Inverter actual power changed" << actualInverterPower << "W"; + thing->setStateValue(huaweiFusionSolarInverterCurrentPowerStateTypeId, actualInverterPower); }); connect(connection, &HuaweiFusionSolar::inverterDeviceStatusChanged, this, [thing](HuaweiFusionSolar::InverterDeviceStatus inverterDeviceStatus){ From 33a32169f1646af114d38262ee7785443cd087ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 9 Aug 2022 13:01:18 +0200 Subject: [PATCH 06/11] Update huawei and make it working --- huawei/huaweifusionsolar.cpp | 14 ++++- huawei/integrationpluginhuawei.cpp | 94 ++++++++++++++--------------- huawei/integrationpluginhuawei.json | 21 ++++--- 3 files changed, 70 insertions(+), 59 deletions(-) diff --git a/huawei/huaweifusionsolar.cpp b/huawei/huaweifusionsolar.cpp index c836159..6f9beb5 100644 --- a/huawei/huaweifusionsolar.cpp +++ b/huawei/huaweifusionsolar.cpp @@ -518,7 +518,7 @@ void HuaweiFusionSolar::readNextRegister() if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 2 power\" register" << 37743 << "size:" << 2 << "valueCount:" << unit.valueCount() << unit.values() << unit.values().count(); - if (!valuesAreVaild(unit.values(), 1)) { + if (!valuesAreVaild(unit.values(), 2)) { qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values count. Requested" << 2 << "but received" << unit.values(); } else { processLunaBattery2PowerRegisterValues(unit.values()); @@ -588,8 +588,10 @@ void HuaweiFusionSolar::readNextRegister() bool HuaweiFusionSolar::valuesAreVaild(const QVector &values, int readSize) { - if (values.count() != readSize) + if (values.count() != readSize) { + qCDebug(dcHuaweiFusionSolar()) << "Invalid values. The received values count does not match the requested" << readSize << "registers."; return false; + } // According to the documentation: // 0x7FFF: invalid value of the floating point type returned by one register @@ -598,7 +600,13 @@ bool HuaweiFusionSolar::valuesAreVaild(const QVector &values, int readS if (values.count() == 2) { bool floatingPointValid = (values.at(0) != 0x7fff && values.at(1) != 0xffff); + if (!floatingPointValid) + qCDebug(dcHuaweiFusionSolar()) << "Invalid values. The received values match the invalid for floating pointer:" << values; + bool otherTypesValid = (values.at(0) != 0xffff && values.at(1) != 0xffff); + if (!otherTypesValid) + qCDebug(dcHuaweiFusionSolar()) << "Invalid values. The received values match the invalid registers values:" << values; + return floatingPointValid && otherTypesValid; } @@ -618,7 +626,7 @@ void HuaweiFusionSolar::calculatActualInverterPower() actualPower += m_lunaBattery2Power; qCDebug(dcHuaweiFusionSolar()) << "Inverter power:" << m_inverterActivePower << "W Battery 1:" << m_lunaBattery1Power << "W Battery 2:" << m_lunaBattery2Power << "W -->" << "Actual inverter power:" << actualPower << "W"; - if (m_actualInverterPower != actualPower) + if (m_actualInverterPower == actualPower) return; m_actualInverterPower = actualPower; diff --git a/huawei/integrationpluginhuawei.cpp b/huawei/integrationpluginhuawei.cpp index a759e79..442879d 100644 --- a/huawei/integrationpluginhuawei.cpp +++ b/huawei/integrationpluginhuawei.cpp @@ -134,6 +134,9 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info) // Create a monitor so we always get the correct IP in the network and see if the device is reachable without polling on our own NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(macAddress); m_monitors.insert(thing, monitor); + connect(info, &ThingSetupInfo::aborted, monitor, [=](){ + hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); + }); // Continue with setup only if we know that the network device is reachable if (monitor->reachable()) { @@ -176,7 +179,7 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info) ModbusRtuMaster *rtuMaster = hardwareManager()->modbusRtuResource()->getModbusRtuMaster(uuid); HuaweiModbusRtuConnection *connection = new HuaweiModbusRtuConnection(rtuMaster, address, this); - connect(connection, &HuaweiModbusRtuConnection::reachableChanged, this, [this, thing, connection](bool reachable){ + connect(connection, &HuaweiModbusRtuConnection::reachableChanged, thing, [this, thing, connection](bool reachable){ qCDebug(dcHuawei()) << thing->name() << "reachable changed" << reachable; if (reachable) { // Connected true will be set after successfull init @@ -189,7 +192,7 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info) } }); - connect(connection, &HuaweiModbusRtuConnection::initializationFinished, this, [this, thing, connection](bool success){ + connect(connection, &HuaweiModbusRtuConnection::initializationFinished, thing, [this, thing, connection](bool success){ if (success) { thing->setStateValue("connected", true); foreach (Thing *childThing, myThings().filterByParentId(thing->id())) { @@ -200,23 +203,23 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info) } }); - connect(connection, &HuaweiModbusRtuConnection::inverterActivePowerChanged, this, [thing](float inverterActivePower){ + connect(connection, &HuaweiModbusRtuConnection::inverterActivePowerChanged, thing, [thing](float inverterActivePower){ qCDebug(dcHuawei()) << "Inverter power changed" << inverterActivePower * -1000.0 << "W"; thing->setStateValue(huaweiRtuInverterCurrentPowerStateTypeId, inverterActivePower * -1000.0); }); - connect(connection, &HuaweiModbusRtuConnection::inverterDeviceStatusChanged, this, [thing](HuaweiModbusRtuConnection::InverterDeviceStatus inverterDeviceStatus){ + connect(connection, &HuaweiModbusRtuConnection::inverterDeviceStatusChanged, thing, [thing](HuaweiModbusRtuConnection::InverterDeviceStatus inverterDeviceStatus){ qCDebug(dcHuawei()) << "Inverter device status changed" << inverterDeviceStatus; Q_UNUSED(thing) }); - connect(connection, &HuaweiModbusRtuConnection::inverterEnergyProducedChanged, this, [thing](float inverterEnergyProduced){ + connect(connection, &HuaweiModbusRtuConnection::inverterEnergyProducedChanged, thing, [thing](float inverterEnergyProduced){ qCDebug(dcHuawei()) << "Inverter total energy produced changed" << inverterEnergyProduced << "kWh"; thing->setStateValue(huaweiRtuInverterTotalEnergyProducedStateTypeId, inverterEnergyProduced); }); // Meter - connect(connection, &HuaweiModbusRtuConnection::powerMeterActivePowerChanged, this, [this, thing](qint32 powerMeterActivePower){ + connect(connection, &HuaweiModbusRtuConnection::powerMeterActivePowerChanged, thing, [this, thing](qint32 powerMeterActivePower){ Things meterThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiMeterThingClassId); if (!meterThings.isEmpty()) { qCDebug(dcHuawei()) << "Meter power changed" << powerMeterActivePower << "W"; @@ -226,7 +229,7 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info) }); // Battery 1 - connect(connection, &HuaweiModbusRtuConnection::lunaBattery1StatusChanged, this, [this, thing](HuaweiModbusRtuConnection::BatteryDeviceStatus lunaBattery1Status){ + connect(connection, &HuaweiModbusRtuConnection::lunaBattery1StatusChanged, thing, [this, thing](HuaweiModbusRtuConnection::BatteryDeviceStatus lunaBattery1Status){ qCDebug(dcHuawei()) << "Battery 1 status changed" << lunaBattery1Status; if (lunaBattery1Status != HuaweiModbusRtuConnection::BatteryDeviceStatusOffline) { // Check if w have to create the energy storage @@ -249,7 +252,7 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info) } }); - connect(connection, &HuaweiModbusRtuConnection::lunaBattery1PowerChanged, this, [this, thing](qint32 lunaBattery1Power){ + connect(connection, &HuaweiModbusRtuConnection::lunaBattery1PowerChanged, thing, [this, thing](qint32 lunaBattery1Power){ qCDebug(dcHuawei()) << "Battery 1 power changed" << lunaBattery1Power << "W"; Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 1); if (!batteryThings.isEmpty()) { @@ -264,7 +267,7 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info) } }); - connect(connection, &HuaweiModbusRtuConnection::lunaBattery1SocChanged, this, [this, thing](float lunaBattery1Soc){ + connect(connection, &HuaweiModbusRtuConnection::lunaBattery1SocChanged, thing, [this, thing](float lunaBattery1Soc){ qCDebug(dcHuawei()) << "Battery 1 SOC changed" << lunaBattery1Soc << "%"; Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 1); if (!batteryThings.isEmpty()) { @@ -274,7 +277,7 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info) }); // Battery 2 - connect(connection, &HuaweiModbusRtuConnection::lunaBattery2StatusChanged, this, [this, thing](HuaweiModbusRtuConnection::BatteryDeviceStatus lunaBattery1Status){ + connect(connection, &HuaweiModbusRtuConnection::lunaBattery2StatusChanged, thing, [this, thing](HuaweiModbusRtuConnection::BatteryDeviceStatus lunaBattery1Status){ qCDebug(dcHuawei()) << "Battery 2 status changed" << lunaBattery1Status; if (lunaBattery1Status != HuaweiModbusRtuConnection::BatteryDeviceStatusOffline) { // Check if w have to create the energy storage @@ -297,7 +300,7 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info) } }); - connect(connection, &HuaweiModbusRtuConnection::lunaBattery2PowerChanged, this, [this, thing](qint32 lunaBattery2Power){ + connect(connection, &HuaweiModbusRtuConnection::lunaBattery2PowerChanged, thing, [this, thing](qint32 lunaBattery2Power){ qCDebug(dcHuawei()) << "Battery 2 power changed" << lunaBattery2Power << "W"; Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 2); if (!batteryThings.isEmpty()) { @@ -313,7 +316,7 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info) } }); - connect(connection, &HuaweiModbusRtuConnection::lunaBattery2SocChanged, this, [this, thing](float lunaBattery2Soc){ + connect(connection, &HuaweiModbusRtuConnection::lunaBattery2SocChanged, thing, [this, thing](float lunaBattery2Soc){ qCDebug(dcHuawei()) << "Battery 2 SOC changed" << lunaBattery2Soc << "%"; Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 2); if (!batteryThings.isEmpty()) { @@ -356,7 +359,7 @@ void IntegrationPluginHuawei::postSetupThing(Thing *thing) if (thing->thingClassId() == huaweiFusionSolarInverterThingClassId || thing->thingClassId() == huaweiRtuInverterThingClassId) { if (!m_pluginTimer) { qCDebug(dcHuawei()) << "Starting plugin timer..."; - m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(5); + m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(2); connect(m_pluginTimer, &PluginTimer::timeout, this, [this] { foreach(HuaweiFusionSolar *connection, m_connections) { if (connection->connected()) { @@ -411,55 +414,52 @@ void IntegrationPluginHuawei::setupFusionSolar(ThingSetupInfo *info) qCDebug(dcHuawei()) << "Finish setup huawei fusion solar dongle" << monitor->networkDeviceInfo().address().toString() << port << slaveId; - connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){ - if (!thing->setupComplete()) - return; - - qCDebug(dcHuawei()) << "Network device monitor for" << thing->name() << (reachable ? "is now reachable" : "is not reachable any more" ); - if (reachable) { - // Update address and refresh - thing->setStateValue("hostAddress", monitor->networkDeviceInfo().address().toString()); - connection->setHostAddress(monitor->networkDeviceInfo().address()); - connection->update(); - } - }); - - connect(connection, &HuaweiFusionSolar::reachableChanged, this, [this, thing, connection](bool reachable){ + connect(connection, &HuaweiFusionSolar::reachableChanged, thing, [=](bool reachable){ qCDebug(dcHuawei()) << "Reachable changed to" << reachable << "for" << thing; - if (reachable) { - // Connected true will be set after successfull init - connection->initialize(); - thing->setStateValue("connected", true); - } else { - thing->setStateValue("connected", false); - } - + thing->setStateValue("connected", reachable); foreach (Thing *childThing, myThings().filterByParentId(thing->id())) { childThing->setStateValue("connected", reachable); } }); - connect(connection, &HuaweiFusionSolar::inverterActivePowerChanged, this, [](float inverterActivePower){ - qCDebug(dcHuawei()) << "Inverter power changed" << inverterActivePower * -1000.0 << "W"; + connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){ + if (!thing->setupComplete()) + return; + + qCDebug(dcHuawei()) << "Network device monitor for" << thing->name() << (reachable ? "is now reachable" : "is not reachable any more" ); + + if (reachable && !thing->stateValue("connected").toBool()) { + connection->setHostAddress(monitor->networkDeviceInfo().address()); + connection->connectDevice(); + } else if (!reachable) { + // Note: We disable autoreconnect explicitly and we will + // connect the device once the monitor says it is reachable again + connection->disconnectDevice(); + } }); - connect(connection, &HuaweiFusionSolar::actualInverterPowerChanged, this, [thing](float actualInverterPower){ + connect(connection, &HuaweiFusionSolar::inverterActivePowerChanged, thing, [](float inverterActivePower){ + qCDebug(dcHuawei()) << "Inverter power changed" << inverterActivePower * -1000.0 << "W"; + //thing->setStateValue(huaweiFusionSolarInverterCurrentPowerStateTypeId, inverterActivePower * -1000.0); + }); + + connect(connection, &HuaweiFusionSolar::actualInverterPowerChanged, thing, [thing](float actualInverterPower){ qCDebug(dcHuawei()) << "Inverter actual power changed" << actualInverterPower << "W"; thing->setStateValue(huaweiFusionSolarInverterCurrentPowerStateTypeId, actualInverterPower); }); - connect(connection, &HuaweiFusionSolar::inverterDeviceStatusChanged, this, [thing](HuaweiFusionSolar::InverterDeviceStatus inverterDeviceStatus){ + connect(connection, &HuaweiFusionSolar::inverterDeviceStatusChanged, thing, [thing](HuaweiFusionSolar::InverterDeviceStatus inverterDeviceStatus){ qCDebug(dcHuawei()) << "Inverter device status changed" << inverterDeviceStatus; Q_UNUSED(thing) }); - connect(connection, &HuaweiFusionSolar::inverterEnergyProducedChanged, this, [thing](float inverterEnergyProduced){ + connect(connection, &HuaweiFusionSolar::inverterEnergyProducedChanged, thing, [thing](float inverterEnergyProduced){ qCDebug(dcHuawei()) << "Inverter total energy produced changed" << inverterEnergyProduced << "kWh"; thing->setStateValue(huaweiFusionSolarInverterTotalEnergyProducedStateTypeId, inverterEnergyProduced); }); // Meter - connect(connection, &HuaweiFusionSolar::powerMeterActivePowerChanged, this, [this, thing](qint32 powerMeterActivePower){ + connect(connection, &HuaweiFusionSolar::powerMeterActivePowerChanged, thing, [this, thing](qint32 powerMeterActivePower){ Things meterThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiMeterThingClassId); if (!meterThings.isEmpty()) { qCDebug(dcHuawei()) << "Meter power changed" << powerMeterActivePower << "W"; @@ -469,7 +469,7 @@ void IntegrationPluginHuawei::setupFusionSolar(ThingSetupInfo *info) }); // Battery 1 - connect(connection, &HuaweiFusionSolar::lunaBattery1StatusChanged, this, [this, thing](HuaweiFusionSolar::BatteryDeviceStatus lunaBattery1Status){ + connect(connection, &HuaweiFusionSolar::lunaBattery1StatusChanged, thing, [this, thing](HuaweiFusionSolar::BatteryDeviceStatus lunaBattery1Status){ qCDebug(dcHuawei()) << "Battery 1 status changed" << lunaBattery1Status; if (lunaBattery1Status != HuaweiFusionSolar::BatteryDeviceStatusOffline) { // Check if w have to create the energy storage @@ -492,7 +492,7 @@ void IntegrationPluginHuawei::setupFusionSolar(ThingSetupInfo *info) } }); - connect(connection, &HuaweiFusionSolar::lunaBattery1PowerChanged, this, [this, thing](qint32 lunaBattery1Power){ + connect(connection, &HuaweiFusionSolar::lunaBattery1PowerChanged, thing, [this, thing](qint32 lunaBattery1Power){ qCDebug(dcHuawei()) << "Battery 1 power changed" << lunaBattery1Power << "W"; Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 1); if (!batteryThings.isEmpty()) { @@ -507,7 +507,7 @@ void IntegrationPluginHuawei::setupFusionSolar(ThingSetupInfo *info) } }); - connect(connection, &HuaweiFusionSolar::lunaBattery1SocChanged, this, [this, thing](float lunaBattery1Soc){ + connect(connection, &HuaweiFusionSolar::lunaBattery1SocChanged, thing, [this, thing](float lunaBattery1Soc){ qCDebug(dcHuawei()) << "Battery 1 SOC changed" << lunaBattery1Soc << "%"; Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 1); if (!batteryThings.isEmpty()) { @@ -517,7 +517,7 @@ void IntegrationPluginHuawei::setupFusionSolar(ThingSetupInfo *info) }); // Battery 2 - connect(connection, &HuaweiFusionSolar::lunaBattery2StatusChanged, this, [this, thing](HuaweiFusionSolar::BatteryDeviceStatus lunaBattery1Status){ + connect(connection, &HuaweiFusionSolar::lunaBattery2StatusChanged, thing, [this, thing](HuaweiFusionSolar::BatteryDeviceStatus lunaBattery1Status){ qCDebug(dcHuawei()) << "Battery 2 status changed" << lunaBattery1Status; if (lunaBattery1Status != HuaweiFusionSolar::BatteryDeviceStatusOffline) { // Check if w have to create the energy storage @@ -540,7 +540,7 @@ void IntegrationPluginHuawei::setupFusionSolar(ThingSetupInfo *info) } }); - connect(connection, &HuaweiFusionSolar::lunaBattery2PowerChanged, this, [this, thing](qint32 lunaBattery2Power){ + connect(connection, &HuaweiFusionSolar::lunaBattery2PowerChanged, thing, [this, thing](qint32 lunaBattery2Power){ qCDebug(dcHuawei()) << "Battery 2 power changed" << lunaBattery2Power << "W"; Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 2); if (!batteryThings.isEmpty()) { @@ -556,7 +556,7 @@ void IntegrationPluginHuawei::setupFusionSolar(ThingSetupInfo *info) } }); - connect(connection, &HuaweiFusionSolar::lunaBattery2SocChanged, this, [this, thing](float lunaBattery2Soc){ + connect(connection, &HuaweiFusionSolar::lunaBattery2SocChanged, thing, [this, thing](float lunaBattery2Soc){ qCDebug(dcHuawei()) << "Battery 2 SOC changed" << lunaBattery2Soc << "%"; Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 2); if (!batteryThings.isEmpty()) { diff --git a/huawei/integrationpluginhuawei.json b/huawei/integrationpluginhuawei.json index f54d5bc..7d7054d 100644 --- a/huawei/integrationpluginhuawei.json +++ b/huawei/integrationpluginhuawei.json @@ -10,7 +10,7 @@ "thingClasses": [ { "name": "huaweiFusionSolarInverter", - "displayName": "Huawei FusionSolar Inverter", + "displayName": "Huawei FusionSolar Inverter (SmartDongle)", "id": "87e75ee0-d544-457b-add3-bd4e58160fcd", "createMethods": ["discovery", "user"], "interfaces": ["solarinverter", "connectable"], @@ -57,7 +57,7 @@ "type": "double", "unit": "Watt", "defaultValue": 0, - "cached": false + "cached": true }, { "id": "e97fe328-6ca4-4fe4-86f7-fee6e9e406a5", @@ -66,14 +66,15 @@ "displayNameEvent": "AC energy changed", "type": "double", "unit": "KiloWattHour", - "defaultValue": 0.00 + "defaultValue": 0.00, + "cached": true } ], "actionTypes": [ ] }, { "name": "huaweiRtuInverter", - "displayName": "Huawei Inverter", + "displayName": "Huawei FusionSolar Inverter ((Modbus RTU)", "id": "77558007-5076-4ca6-bd46-169f215c3e29", "createMethods": ["discovery"], "interfaces": ["solarinverter", "connectable"], @@ -122,7 +123,7 @@ "type": "double", "unit": "Watt", "defaultValue": 0, - "cached": false + "cached": true }, { "id": "49b92919-301c-4ff7-ae63-0c1a2184e3f4", @@ -131,7 +132,8 @@ "displayNameEvent": "AC energy changed", "type": "double", "unit": "KiloWattHour", - "defaultValue": 0.00 + "defaultValue": 0.00, + "cached": true } ], "actionTypes": [ ] @@ -162,7 +164,7 @@ "type": "double", "unit": "Watt", "defaultValue": 0.00, - "cached": false + "cached": true }, { "id": "759554dd-74c5-4836-9792-96e02eb816f0", @@ -171,7 +173,8 @@ "displayNameEvent": "AC energy changed", "type": "double", "unit": "KiloWattHour", - "defaultValue": 0.00 + "defaultValue": 0.00, + "cached": true }, { "id": "2cf8d885-37f7-478f-819e-c4e20f2dbe01", @@ -337,7 +340,7 @@ "type": "double", "unit": "Watt", "defaultValue": 0.00, - "cached": false + "cached": true }, { "id": "3eed974a-0acb-4e38-bcb8-0e3f6fbfd51a", From afaaa04d624f4fc3fd019c84c516af6c54fd5497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 9 Aug 2022 14:11:54 +0200 Subject: [PATCH 07/11] Fix battery state --- huawei/huawei-fusion-solar-registers.json | 6 +- huawei/huawei-registers.json | 6 +- huawei/huaweifusionsolardiscovery.cpp | 30 +- huawei/integrationpluginhuawei.cpp | 326 +++++++++++----------- huawei/integrationpluginhuawei.h | 2 +- 5 files changed, 184 insertions(+), 186 deletions(-) diff --git a/huawei/huawei-fusion-solar-registers.json b/huawei/huawei-fusion-solar-registers.json index 6386d6c..2581715 100644 --- a/huawei/huawei-fusion-solar-registers.json +++ b/huawei/huawei-fusion-solar-registers.json @@ -135,15 +135,15 @@ }, { "key": "Running", - "value": 1 + "value": 2 }, { "key": "Fault", - "value": 1 + "value": 3 }, { "key": "SleepMode", - "value": 1 + "value": 4 } ] } diff --git a/huawei/huawei-registers.json b/huawei/huawei-registers.json index 3ae47e3..d6f235b 100644 --- a/huawei/huawei-registers.json +++ b/huawei/huawei-registers.json @@ -135,15 +135,15 @@ }, { "key": "Running", - "value": 1 + "value": 2 }, { "key": "Fault", - "value": 1 + "value": 3 }, { "key": "SleepMode", - "value": 1 + "value": 4 } ] } diff --git a/huawei/huaweifusionsolardiscovery.cpp b/huawei/huaweifusionsolardiscovery.cpp index 5443b5a..07cfaf8 100644 --- a/huawei/huaweifusionsolardiscovery.cpp +++ b/huawei/huaweifusionsolardiscovery.cpp @@ -99,26 +99,8 @@ void HuaweiFusionSolarDiscovery::checkNetworkDevice(const NetworkDeviceInfo &net return; } - // Modbus TCP connected...ok, let's try to initialize it! - connect(connection, &HuaweiFusionSolar::initializationFinished, this, [=](bool success){ - if (!success) { - qCDebug(dcHuawei()) << "Discovery: Initialization failed on" << networkDeviceInfo.address().toString() << "Continue...";; - cleanupConnection(connection); - return; - } - - qCDebug(dcHuawei()) << "Discovery: --> Found" << networkDeviceInfo; - m_discoveryResults.append(networkDeviceInfo); - - // Done with this connection - cleanupConnection(connection); - }); - - // Initializing... - if (!connection->initialize()) { - qCDebug(dcHuawei()) << "Discovery: Unable to initialize connection on" << networkDeviceInfo.address().toString() << "Continue...";; - cleanupConnection(connection); - } + qCDebug(dcHuawei()) << "Discovery: --> Found" << networkDeviceInfo; + m_discoveryResults.append(networkDeviceInfo); }); // If we get any error...skip this host... @@ -145,9 +127,11 @@ void HuaweiFusionSolarDiscovery::checkNetworkDevice(const NetworkDeviceInfo &net void HuaweiFusionSolarDiscovery::cleanupConnection(HuaweiFusionSolar *connection) { - m_connections.removeAll(connection); - connection->disconnectDevice(); - connection->deleteLater(); + if (m_connections.contains(connection)) { + m_connections.removeAll(connection); + connection->disconnectDevice(); + connection->deleteLater(); + } } void HuaweiFusionSolarDiscovery::finishDiscovery() diff --git a/huawei/integrationpluginhuawei.cpp b/huawei/integrationpluginhuawei.cpp index 442879d..49df53d 100644 --- a/huawei/integrationpluginhuawei.cpp +++ b/huawei/integrationpluginhuawei.cpp @@ -115,13 +115,12 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info) if (thing->thingClassId() == huaweiFusionSolarInverterThingClassId) { // Handle reconfigure - if (m_connections.contains(thing)) { - m_connections.take(thing)->deleteLater(); + if (m_connections.contains(thing)) + delete m_connections.take(thing); + + if (m_monitors.contains(thing)) + hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); - if (m_monitors.contains(thing)) { - hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); - } - } // Make sure we have a valid mac address, otherwise no monitor and not auto searching is possible MacAddress macAddress = MacAddress(thing->paramValue(huaweiFusionSolarInverterThingMacAddressParamTypeId).toString()); @@ -385,15 +384,17 @@ void IntegrationPluginHuawei::postSetupThing(Thing *thing) void IntegrationPluginHuawei::thingRemoved(Thing *thing) { - if (thing->thingClassId() == huaweiFusionSolarInverterThingClassId && m_connections.contains(thing)) { - m_connections.take(thing)->deleteLater(); - - if (m_monitors.contains(thing)) { - hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); - } + if (m_monitors.contains(thing)) { + hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); } - if (thing->thingClassId() == huaweiRtuInverterThingClassId && m_rtuConnections.contains(thing)) { + if (m_connections.contains(thing)) { + HuaweiFusionSolar *connection = m_connections.take(thing); + connection->disconnectDevice(); + delete connection; + } + + if (m_rtuConnections.contains(thing)) { m_rtuConnections.take(thing)->deleteLater(); } @@ -410,165 +411,178 @@ void IntegrationPluginHuawei::setupFusionSolar(ThingSetupInfo *info) uint port = thing->paramValue(huaweiFusionSolarInverterThingPortParamTypeId).toUInt(); quint16 slaveId = thing->paramValue(huaweiFusionSolarInverterThingSlaveIdParamTypeId).toUInt(); + qCDebug(dcHuawei()) << "Setup connection to fusion solar dongle" << monitor->networkDeviceInfo().address().toString() << port << slaveId; + HuaweiFusionSolar *connection = new HuaweiFusionSolar(monitor->networkDeviceInfo().address(), port, slaveId, this); - - qCDebug(dcHuawei()) << "Finish setup huawei fusion solar dongle" << monitor->networkDeviceInfo().address().toString() << port << slaveId; - - connect(connection, &HuaweiFusionSolar::reachableChanged, thing, [=](bool reachable){ - qCDebug(dcHuawei()) << "Reachable changed to" << reachable << "for" << thing; - thing->setStateValue("connected", reachable); - foreach (Thing *childThing, myThings().filterByParentId(thing->id())) { - childThing->setStateValue("connected", reachable); - } - }); - - connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){ - if (!thing->setupComplete()) + connect(info, &ThingSetupInfo::aborted, connection, &HuaweiFusionSolar::deleteLater); + connect(connection, &HuaweiFusionSolar::reachableChanged, info, [=](bool reachable){ + if (!reachable) { + qCWarning(dcHuawei()) << "Connection init finished with errors" << thing->name() << connection->hostAddress().toString(); + hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(monitor); + connection->disconnectDevice(); + connection->deleteLater(); + info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Could not initialize the communication with the SmartDongle.")); return; - - qCDebug(dcHuawei()) << "Network device monitor for" << thing->name() << (reachable ? "is now reachable" : "is not reachable any more" ); - - if (reachable && !thing->stateValue("connected").toBool()) { - connection->setHostAddress(monitor->networkDeviceInfo().address()); - connection->connectDevice(); - } else if (!reachable) { - // Note: We disable autoreconnect explicitly and we will - // connect the device once the monitor says it is reachable again - connection->disconnectDevice(); - } - }); - - connect(connection, &HuaweiFusionSolar::inverterActivePowerChanged, thing, [](float inverterActivePower){ - qCDebug(dcHuawei()) << "Inverter power changed" << inverterActivePower * -1000.0 << "W"; - //thing->setStateValue(huaweiFusionSolarInverterCurrentPowerStateTypeId, inverterActivePower * -1000.0); - }); - - connect(connection, &HuaweiFusionSolar::actualInverterPowerChanged, thing, [thing](float actualInverterPower){ - qCDebug(dcHuawei()) << "Inverter actual power changed" << actualInverterPower << "W"; - thing->setStateValue(huaweiFusionSolarInverterCurrentPowerStateTypeId, actualInverterPower); - }); - - connect(connection, &HuaweiFusionSolar::inverterDeviceStatusChanged, thing, [thing](HuaweiFusionSolar::InverterDeviceStatus inverterDeviceStatus){ - qCDebug(dcHuawei()) << "Inverter device status changed" << inverterDeviceStatus; - Q_UNUSED(thing) - }); - - connect(connection, &HuaweiFusionSolar::inverterEnergyProducedChanged, thing, [thing](float inverterEnergyProduced){ - qCDebug(dcHuawei()) << "Inverter total energy produced changed" << inverterEnergyProduced << "kWh"; - thing->setStateValue(huaweiFusionSolarInverterTotalEnergyProducedStateTypeId, inverterEnergyProduced); - }); - - // Meter - connect(connection, &HuaweiFusionSolar::powerMeterActivePowerChanged, thing, [this, thing](qint32 powerMeterActivePower){ - Things meterThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiMeterThingClassId); - if (!meterThings.isEmpty()) { - qCDebug(dcHuawei()) << "Meter power changed" << powerMeterActivePower << "W"; - // Note: > 0 -> return, < 0 consume - meterThings.first()->setStateValue(huaweiMeterCurrentPowerStateTypeId, -powerMeterActivePower); } - }); - // Battery 1 - connect(connection, &HuaweiFusionSolar::lunaBattery1StatusChanged, thing, [this, thing](HuaweiFusionSolar::BatteryDeviceStatus lunaBattery1Status){ - qCDebug(dcHuawei()) << "Battery 1 status changed" << lunaBattery1Status; - if (lunaBattery1Status != HuaweiFusionSolar::BatteryDeviceStatusOffline) { - // Check if w have to create the energy storage - Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId); - bool alreadySetUp = false; - foreach (Thing *batteryThing, batteryThings) { - if (batteryThing->paramValue(huaweiBatteryThingUnitParamTypeId).toUInt() == 1) { - alreadySetUp = true; + m_connections.insert(thing, connection); + info->finish(Thing::ThingErrorNoError); + + qCDebug(dcHuawei()) << "Setup huawei fusion solar smart dongle finished successfully" << monitor->networkDeviceInfo().address().toString() << port << slaveId; + + // Set connected state + thing->setStateValue("connected", true); + foreach (Thing *childThing, myThings().filterByParentId(thing->id())) { + childThing->setStateValue("connected", true); + } + + connect(connection, &HuaweiFusionSolar::reachableChanged, thing, [=](bool reachable){ + qCDebug(dcHuawei()) << "Reachable changed to" << reachable << "for" << thing; + thing->setStateValue("connected", reachable); + foreach (Thing *childThing, myThings().filterByParentId(thing->id())) { + childThing->setStateValue("connected", reachable); + } + }); + + connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){ + if (!thing->setupComplete()) + return; + + qCDebug(dcHuawei()) << "Network device monitor for" << thing->name() << (reachable ? "is now reachable" : "is not reachable any more" ); + + if (reachable && !thing->stateValue("connected").toBool()) { + connection->setHostAddress(monitor->networkDeviceInfo().address()); + connection->connectDevice(); + } else if (!reachable) { + // Note: We disable autoreconnect explicitly and we will + // connect the device once the monitor says it is reachable again + connection->disconnectDevice(); + } + }); + + connect(connection, &HuaweiFusionSolar::actualInverterPowerChanged, thing, [thing](float actualInverterPower){ + qCDebug(dcHuawei()) << "Inverter actual power changed" << actualInverterPower << "W"; + thing->setStateValue(huaweiFusionSolarInverterCurrentPowerStateTypeId, actualInverterPower); + }); + + connect(connection, &HuaweiFusionSolar::inverterDeviceStatusReadFinished, thing, [thing](HuaweiFusionSolar::InverterDeviceStatus inverterDeviceStatus){ + qCDebug(dcHuawei()) << "Inverter device status changed" << inverterDeviceStatus; + Q_UNUSED(thing) + }); + + connect(connection, &HuaweiFusionSolar::inverterEnergyProducedReadFinished, thing, [thing](float inverterEnergyProduced){ + qCDebug(dcHuawei()) << "Inverter total energy produced changed" << inverterEnergyProduced << "kWh"; + thing->setStateValue(huaweiFusionSolarInverterTotalEnergyProducedStateTypeId, inverterEnergyProduced); + }); + + // Meter + connect(connection, &HuaweiFusionSolar::powerMeterActivePowerReadFinished, thing, [this, thing](qint32 powerMeterActivePower){ + Things meterThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiMeterThingClassId); + if (!meterThings.isEmpty()) { + qCDebug(dcHuawei()) << "Meter power changed" << powerMeterActivePower << "W"; + // Note: > 0 -> return, < 0 consume + meterThings.first()->setStateValue(huaweiMeterCurrentPowerStateTypeId, -powerMeterActivePower); + } + }); + + // Battery 1 + connect(connection, &HuaweiFusionSolar::lunaBattery1StatusReadFinished, thing, [this, thing](HuaweiFusionSolar::BatteryDeviceStatus lunaBattery1Status){ + qCDebug(dcHuawei()) << "Battery 1 status changed" << lunaBattery1Status; + if (lunaBattery1Status != HuaweiFusionSolar::BatteryDeviceStatusOffline) { + // Check if w have to create the energy storage + Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId); + bool alreadySetUp = false; + foreach (Thing *batteryThing, batteryThings) { + if (batteryThing->paramValue(huaweiBatteryThingUnitParamTypeId).toUInt() == 1) { + alreadySetUp = true; + } + } + + if (!alreadySetUp) { + qCDebug(dcHuawei()) << "Set up huawei energy storage 1 for" << thing; + ThingDescriptor descriptor(huaweiBatteryThingClassId, "Luna 2000 Battery", QString(), thing->id()); + ParamList params; + params.append(Param(huaweiBatteryThingUnitParamTypeId, 1)); + descriptor.setParams(params); + emit autoThingsAppeared(ThingDescriptors() << descriptor); } } + }); - if (!alreadySetUp) { - qCDebug(dcHuawei()) << "Set up huawei energy storage 1 for" << thing; - ThingDescriptor descriptor(huaweiBatteryThingClassId, "Luna 2000 Battery", QString(), thing->id()); - ParamList params; - params.append(Param(huaweiBatteryThingUnitParamTypeId, 1)); - descriptor.setParams(params); - emit autoThingsAppeared(ThingDescriptors() << descriptor); - } - } - }); - - connect(connection, &HuaweiFusionSolar::lunaBattery1PowerChanged, thing, [this, thing](qint32 lunaBattery1Power){ - qCDebug(dcHuawei()) << "Battery 1 power changed" << lunaBattery1Power << "W"; - Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 1); - if (!batteryThings.isEmpty()) { - batteryThings.first()->setStateValue(huaweiBatteryCurrentPowerStateTypeId, lunaBattery1Power); - if (lunaBattery1Power < 0) { - batteryThings.first()->setStateValue(huaweiBatteryChargingStateStateTypeId, "discharging"); - } else if (lunaBattery1Power > 0) { - batteryThings.first()->setStateValue(huaweiBatteryChargingStateStateTypeId, "charging"); - } else { - batteryThings.first()->setStateValue(huaweiBatteryChargingStateStateTypeId, "idle"); - } - } - }); - - connect(connection, &HuaweiFusionSolar::lunaBattery1SocChanged, thing, [this, thing](float lunaBattery1Soc){ - qCDebug(dcHuawei()) << "Battery 1 SOC changed" << lunaBattery1Soc << "%"; - Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 1); - if (!batteryThings.isEmpty()) { - batteryThings.first()->setStateValue(huaweiBatteryBatteryLevelStateTypeId, lunaBattery1Soc); - batteryThings.first()->setStateValue(huaweiBatteryBatteryCriticalStateTypeId, lunaBattery1Soc < 10); - } - }); - - // Battery 2 - connect(connection, &HuaweiFusionSolar::lunaBattery2StatusChanged, thing, [this, thing](HuaweiFusionSolar::BatteryDeviceStatus lunaBattery1Status){ - qCDebug(dcHuawei()) << "Battery 2 status changed" << lunaBattery1Status; - if (lunaBattery1Status != HuaweiFusionSolar::BatteryDeviceStatusOffline) { - // Check if w have to create the energy storage - Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId); - bool alreadySetUp = false; - foreach (Thing *batteryThing, batteryThings) { - if (batteryThing->paramValue(huaweiBatteryThingUnitParamTypeId).toUInt() == 2) { - alreadySetUp = true; + connect(connection, &HuaweiFusionSolar::lunaBattery1PowerReadFinished, thing, [this, thing](qint32 lunaBattery1Power){ + qCDebug(dcHuawei()) << "Battery 1 power changed" << lunaBattery1Power << "W"; + Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 1); + if (!batteryThings.isEmpty()) { + batteryThings.first()->setStateValue(huaweiBatteryCurrentPowerStateTypeId, lunaBattery1Power); + if (lunaBattery1Power < 0) { + batteryThings.first()->setStateValue(huaweiBatteryChargingStateStateTypeId, "discharging"); + } else if (lunaBattery1Power > 0) { + batteryThings.first()->setStateValue(huaweiBatteryChargingStateStateTypeId, "charging"); + } else { + batteryThings.first()->setStateValue(huaweiBatteryChargingStateStateTypeId, "idle"); } } + }); - if (!alreadySetUp) { - qCDebug(dcHuawei()) << "Set up huawei energy storage 2 for" << thing; - ThingDescriptor descriptor(huaweiBatteryThingClassId, "Luna 2000 Battery", QString(), thing->id()); - ParamList params; - params.append(Param(huaweiBatteryThingUnitParamTypeId, 2)); - descriptor.setParams(params); - emit autoThingsAppeared(ThingDescriptors() << descriptor); + connect(connection, &HuaweiFusionSolar::lunaBattery1SocReadFinished, thing, [this, thing](float lunaBattery1Soc){ + qCDebug(dcHuawei()) << "Battery 1 SOC changed" << lunaBattery1Soc << "%"; + Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 1); + if (!batteryThings.isEmpty()) { + batteryThings.first()->setStateValue(huaweiBatteryBatteryLevelStateTypeId, lunaBattery1Soc); + batteryThings.first()->setStateValue(huaweiBatteryBatteryCriticalStateTypeId, lunaBattery1Soc < 10); } - } - }); + }); - connect(connection, &HuaweiFusionSolar::lunaBattery2PowerChanged, thing, [this, thing](qint32 lunaBattery2Power){ - qCDebug(dcHuawei()) << "Battery 2 power changed" << lunaBattery2Power << "W"; - Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 2); - if (!batteryThings.isEmpty()) { - batteryThings.first()->setStateValue(huaweiBatteryCurrentPowerStateTypeId, lunaBattery2Power); + // Battery 2 + connect(connection, &HuaweiFusionSolar::lunaBattery2StatusReadFinished, thing, [this, thing](HuaweiFusionSolar::BatteryDeviceStatus lunaBattery2Status){ + qCDebug(dcHuawei()) << "Battery 2 status changed" << lunaBattery2Status; + if (lunaBattery2Status != HuaweiFusionSolar::BatteryDeviceStatusOffline) { + // Check if w have to create the energy storage + Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId); + bool alreadySetUp = false; + foreach (Thing *batteryThing, batteryThings) { + if (batteryThing->paramValue(huaweiBatteryThingUnitParamTypeId).toUInt() == 2) { + alreadySetUp = true; + } + } - if (lunaBattery2Power < 0) { - batteryThings.first()->setStateValue(huaweiBatteryChargingStateStateTypeId, "discharging"); - } else if (lunaBattery2Power > 0) { - batteryThings.first()->setStateValue(huaweiBatteryChargingStateStateTypeId, "charging"); - } else { - batteryThings.first()->setStateValue(huaweiBatteryChargingStateStateTypeId, "idle"); + if (!alreadySetUp) { + qCDebug(dcHuawei()) << "Set up huawei energy storage 2 for" << thing; + ThingDescriptor descriptor(huaweiBatteryThingClassId, "Luna 2000 Battery", QString(), thing->id()); + ParamList params; + params.append(Param(huaweiBatteryThingUnitParamTypeId, 2)); + descriptor.setParams(params); + emit autoThingsAppeared(ThingDescriptors() << descriptor); + } } - } + }); + + connect(connection, &HuaweiFusionSolar::lunaBattery2PowerReadFinished, thing, [this, thing](qint32 lunaBattery2Power){ + qCDebug(dcHuawei()) << "Battery 2 power changed" << lunaBattery2Power << "W"; + Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 2); + if (!batteryThings.isEmpty()) { + batteryThings.first()->setStateValue(huaweiBatteryCurrentPowerStateTypeId, lunaBattery2Power); + + if (lunaBattery2Power < 0) { + batteryThings.first()->setStateValue(huaweiBatteryChargingStateStateTypeId, "discharging"); + } else if (lunaBattery2Power > 0) { + batteryThings.first()->setStateValue(huaweiBatteryChargingStateStateTypeId, "charging"); + } else { + batteryThings.first()->setStateValue(huaweiBatteryChargingStateStateTypeId, "idle"); + } + } + }); + + connect(connection, &HuaweiFusionSolar::lunaBattery2SocReadFinished, thing, [this, thing](float lunaBattery2Soc){ + qCDebug(dcHuawei()) << "Battery 2 SOC changed" << lunaBattery2Soc << "%"; + Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 2); + if (!batteryThings.isEmpty()) { + batteryThings.first()->setStateValue(huaweiBatteryBatteryLevelStateTypeId, lunaBattery2Soc); + batteryThings.first()->setStateValue(huaweiBatteryBatteryCriticalStateTypeId, lunaBattery2Soc < 10); + } + }); }); - connect(connection, &HuaweiFusionSolar::lunaBattery2SocChanged, thing, [this, thing](float lunaBattery2Soc){ - qCDebug(dcHuawei()) << "Battery 2 SOC changed" << lunaBattery2Soc << "%"; - Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 2); - if (!batteryThings.isEmpty()) { - batteryThings.first()->setStateValue(huaweiBatteryBatteryLevelStateTypeId, lunaBattery2Soc); - batteryThings.first()->setStateValue(huaweiBatteryBatteryCriticalStateTypeId, lunaBattery2Soc < 10); - } - }); - - m_connections.insert(thing, connection); connection->connectDevice(); - - // FIXME: make async and check if this is really a huawei - info->finish(Thing::ThingErrorNoError); } diff --git a/huawei/integrationpluginhuawei.h b/huawei/integrationpluginhuawei.h index fd45cbe..1c70492 100644 --- a/huawei/integrationpluginhuawei.h +++ b/huawei/integrationpluginhuawei.h @@ -56,11 +56,11 @@ public: private: PluginTimer *m_pluginTimer = nullptr; + QHash m_monitors; QHash m_connections; QHash m_rtuConnections; - void setupFusionSolar(ThingSetupInfo *info); }; From f704e84ffd0571f08d4d87e5183ddb89de5a312c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 10 Aug 2022 10:22:07 +0200 Subject: [PATCH 08/11] Fix response values validation --- huawei/huaweifusionsolar.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/huawei/huaweifusionsolar.cpp b/huawei/huaweifusionsolar.cpp index 6f9beb5..094b17e 100644 --- a/huawei/huaweifusionsolar.cpp +++ b/huawei/huaweifusionsolar.cpp @@ -593,17 +593,17 @@ bool HuaweiFusionSolar::valuesAreVaild(const QVector &values, int readS return false; } - // According to the documentation: + // According to the documentation from 2016: // 0x7FFF: invalid value of the floating point type returned by one register // 0xFFFF: invalid value of a type other than the floating point type returned by one register // 0xFFFFFFFF: invalid value returned by two registers if (values.count() == 2) { - bool floatingPointValid = (values.at(0) != 0x7fff && values.at(1) != 0xffff); + bool floatingPointValid = (values != QVector() << 0x7fff << 0xffff); if (!floatingPointValid) qCDebug(dcHuaweiFusionSolar()) << "Invalid values. The received values match the invalid for floating pointer:" << values; - bool otherTypesValid = (values.at(0) != 0xffff && values.at(1) != 0xffff); + bool otherTypesValid = (values != QVector(2, 0xffff)); if (!otherTypesValid) qCDebug(dcHuaweiFusionSolar()) << "Invalid values. The received values match the invalid registers values:" << values; From f96adcfb3c6d6306b2b433312bd61969d1e21295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 10 Aug 2022 10:53:51 +0200 Subject: [PATCH 09/11] Update documentation and remove energystorage interface from batteries --- huawei/README.md | 5 ++++- huawei/huaweifusionsolar.cpp | 28 ++-------------------------- huawei/huaweifusionsolar.h | 8 -------- huawei/integrationpluginhuawei.cpp | 5 ++--- huawei/integrationpluginhuawei.json | 2 +- 5 files changed, 9 insertions(+), 39 deletions(-) diff --git a/huawei/README.md b/huawei/README.md index 82d7646..51816a4 100644 --- a/huawei/README.md +++ b/huawei/README.md @@ -4,7 +4,7 @@ Connects to a Huawei FusionSolar using Modbus RTU or TCP. ## Huawei FusionSolar -In order to communicate with the Huawei FusionSolar inverter, a working communikation must be provided. This can be done with a Huawei SmartDongle or with a direct modbus RTU connection. +In order to communicate with the Huawei FusionSolar inverter, a working communication must be provided. This can be done with a Huawei SmartDongle or with a directly with the modbus RTU connection. Once nymea has connected successfully to the inverter, following devices will be supported: @@ -12,6 +12,9 @@ Once nymea has connected successfully to the inverter, following devices will be * Huawei Meter (connected internally to the Inverter) * Luna2000 battery units 1 and 2 if connected +The batteries will be shown in the system as informative things, but will not be considered in the energy view and and energy balance. Since the batteries are connected internally behind the inverter, power coming from the battery will be shown as power coming from the inverter. The inverter power represents power from the PV or from the battery. + + ### Huawei SmartDongle The [SmartDongle](https://solar.huawei.com/-/media/Solar/attachment/pdf/apac/datasheet/SmartDongle-WLAN-FE.pdf) can be used to communicate diff --git a/huawei/huaweifusionsolar.cpp b/huawei/huaweifusionsolar.cpp index 094b17e..0d68652 100644 --- a/huawei/huaweifusionsolar.cpp +++ b/huawei/huaweifusionsolar.cpp @@ -108,9 +108,10 @@ bool HuaweiFusionSolar::update() m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery2Power); m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterPowerMeterActivePower); - m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterInverterEnergyProduced); m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterInverterDeviceStatus); + + // Note: we constantly read the status in any case so we detect if a battery came online m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery1Status); m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery2Status); @@ -127,11 +128,6 @@ bool HuaweiFusionSolar::update() return true; } -double HuaweiFusionSolar::actualInverterPower() const -{ - return m_actualInverterPower; -} - void HuaweiFusionSolar::readNextRegister() { // Check if currently a reply is pending @@ -171,7 +167,6 @@ void HuaweiFusionSolar::readNextRegister() qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values. Requested" << 2 << "but received" << unit.values(); } else { processInverterActivePowerRegisterValues(unit.values()); - calculatActualInverterPower(); } } @@ -391,7 +386,6 @@ void HuaweiFusionSolar::readNextRegister() qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values. Requested" << 2 << "but received" << unit.values(); } else { processLunaBattery1PowerRegisterValues(unit.values()); - calculatActualInverterPower(); } } finishRequest(); @@ -522,7 +516,6 @@ void HuaweiFusionSolar::readNextRegister() qCWarning(dcHuaweiFusionSolar()) << "<-- Received invalid values count. Requested" << 2 << "but received" << unit.values(); } else { processLunaBattery2PowerRegisterValues(unit.values()); - calculatActualInverterPower(); } } finishRequest(); @@ -616,23 +609,6 @@ bool HuaweiFusionSolar::valuesAreVaild(const QVector &values, int readS return true; } -void HuaweiFusionSolar::calculatActualInverterPower() -{ - double actualPower = m_inverterActivePower * -1000.0; - if (m_battery1Available) - actualPower += m_lunaBattery1Power; - - if (m_battery2Available) - actualPower += m_lunaBattery2Power; - - qCDebug(dcHuaweiFusionSolar()) << "Inverter power:" << m_inverterActivePower << "W Battery 1:" << m_lunaBattery1Power << "W Battery 2:" << m_lunaBattery2Power << "W -->" << "Actual inverter power:" << actualPower << "W"; - if (m_actualInverterPower == actualPower) - return; - - m_actualInverterPower = actualPower; - emit actualInverterPowerChanged(m_actualInverterPower); -} - void HuaweiFusionSolar::finishRequest() { m_currentRegisterRequest = -1; diff --git a/huawei/huaweifusionsolar.h b/huawei/huaweifusionsolar.h index cb32652..df879ff 100644 --- a/huawei/huaweifusionsolar.h +++ b/huawei/huaweifusionsolar.h @@ -46,13 +46,6 @@ public: bool initialize() override; virtual bool update() override; - // The inverter shows the pv power AND the power of the connected batteries if they discharge. - // This power values represents the power taking the batteries into account. - double actualInverterPower() const; - -signals: - void actualInverterPowerChanged(double actualInverterPower); - private: QQueue m_registersQueue; QModbusReply *m_initReply = nullptr; @@ -70,7 +63,6 @@ private: private slots: void readNextRegister(); bool valuesAreVaild(const QVector &values, int readSize); - void calculatActualInverterPower(); }; diff --git a/huawei/integrationpluginhuawei.cpp b/huawei/integrationpluginhuawei.cpp index 49df53d..1785611 100644 --- a/huawei/integrationpluginhuawei.cpp +++ b/huawei/integrationpluginhuawei.cpp @@ -460,9 +460,8 @@ void IntegrationPluginHuawei::setupFusionSolar(ThingSetupInfo *info) } }); - connect(connection, &HuaweiFusionSolar::actualInverterPowerChanged, thing, [thing](float actualInverterPower){ - qCDebug(dcHuawei()) << "Inverter actual power changed" << actualInverterPower << "W"; - thing->setStateValue(huaweiFusionSolarInverterCurrentPowerStateTypeId, actualInverterPower); + connect(connection, &HuaweiFusionSolar::inverterActivePowerChanged, thing, [thing](float inverterActivePower){ + thing->setStateValue(huaweiFusionSolarInverterCurrentPowerStateTypeId, inverterActivePower * -1000.0); }); connect(connection, &HuaweiFusionSolar::inverterDeviceStatusReadFinished, thing, [thing](HuaweiFusionSolar::InverterDeviceStatus inverterDeviceStatus){ diff --git a/huawei/integrationpluginhuawei.json b/huawei/integrationpluginhuawei.json index 7d7054d..8df9556 100644 --- a/huawei/integrationpluginhuawei.json +++ b/huawei/integrationpluginhuawei.json @@ -293,7 +293,7 @@ "displayName": "Huawei Battery", "id": "40104aac-0456-475d-8bd6-18f946597d96", "createMethods": ["auto"], - "interfaces": [ "energystorage", "connectable"], + "interfaces": [ "battery", "connectable"], "paramTypes": [ { "id": "019287a6-c593-45a8-9695-2e1ad8e81c32", From 66b42e26aeaa2d4b150b54041a80b5650dfbe803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 22 Aug 2022 16:55:52 +0200 Subject: [PATCH 10/11] Update documentation and fix typos --- huawei/README.md | 6 +++--- huawei/huaweifusionsolar.h | 2 +- huawei/huaweifusionsolardiscovery.cpp | 2 +- huawei/integrationpluginhuawei.json | 9 +++++---- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/huawei/README.md b/huawei/README.md index 51816a4..aacc4c8 100644 --- a/huawei/README.md +++ b/huawei/README.md @@ -4,9 +4,9 @@ Connects to a Huawei FusionSolar using Modbus RTU or TCP. ## Huawei FusionSolar -In order to communicate with the Huawei FusionSolar inverter, a working communication must be provided. This can be done with a Huawei SmartDongle or with a directly with the modbus RTU connection. +The Huawei FusionSolar can be connected either via the Huawei SmartDongle or via a Modbus RTU (RS485) connection. -Once nymea has connected successfully to the inverter, following devices will be supported: +The following devices are supported: * Huawei FusionSolar Inverter (all model supported by the SmartDongle) * Huawei Meter (connected internally to the Inverter) @@ -27,7 +27,7 @@ with the Huawei Solar Inverter. In order to allow nymea to read from the device You can also contact the [official Huawei support](mailto:eu_inverter_support@huawei.com) in order to get the update files and instructions, or get it from [here](https://support.huawei.com/enterprise/en/digital-power/sdongle-pid-23826585/software). -> The SmartDongle provides only access to the registers specified in the Huawei `openAPI`. Full modbus register access requires a modbus RTU connction. +> The SmartDongle provides only access to the registers specified in the Huawei `openAPI`. Full modbus register access requires a modbus RTU connection. ### Direct modbus RTU connection (RS485). diff --git a/huawei/huaweifusionsolar.h b/huawei/huaweifusionsolar.h index df879ff..ed3d87d 100644 --- a/huawei/huaweifusionsolar.h +++ b/huawei/huaweifusionsolar.h @@ -43,7 +43,7 @@ public: explicit HuaweiFusionSolar(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent = nullptr); ~HuaweiFusionSolar() = default; - bool initialize() override; + virtual bool initialize() override; virtual bool update() override; private: diff --git a/huawei/huaweifusionsolardiscovery.cpp b/huawei/huaweifusionsolardiscovery.cpp index 07cfaf8..f27d06e 100644 --- a/huawei/huaweifusionsolardiscovery.cpp +++ b/huawei/huaweifusionsolardiscovery.cpp @@ -51,7 +51,7 @@ void HuaweiFusionSolarDiscovery::startDiscovery() checkNetworkDevice(networkDeviceInfo); } - // Imedialty check any new device gets discovered + // Immedialty check any new device gets discovered connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &HuaweiFusionSolarDiscovery::checkNetworkDevice); // Check what might be left on finished diff --git a/huawei/integrationpluginhuawei.json b/huawei/integrationpluginhuawei.json index 8df9556..2441249 100644 --- a/huawei/integrationpluginhuawei.json +++ b/huawei/integrationpluginhuawei.json @@ -74,7 +74,7 @@ }, { "name": "huaweiRtuInverter", - "displayName": "Huawei FusionSolar Inverter ((Modbus RTU)", + "displayName": "Huawei FusionSolar Inverter (Modbus RTU)", "id": "77558007-5076-4ca6-bd46-169f215c3e29", "createMethods": ["discovery"], "interfaces": ["solarinverter", "connectable"], @@ -123,7 +123,7 @@ "type": "double", "unit": "Watt", "defaultValue": 0, - "cached": true + "cached": false }, { "id": "49b92919-301c-4ff7-ae63-0c1a2184e3f4", @@ -164,7 +164,7 @@ "type": "double", "unit": "Watt", "defaultValue": 0.00, - "cached": true + "cached": false }, { "id": "759554dd-74c5-4836-9792-96e02eb816f0", @@ -183,7 +183,8 @@ "displayNameEvent": "Total real energy imported changed", "type": "double", "unit": "KiloWattHour", - "defaultValue": 0.00 + "defaultValue": 0.00, + "cached": true }, { "id": "af48ff45-11ba-401e-a812-bb1db0896449", From 62a615c4d3f51fa3134acb9ca0222b320e97563c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 22 Aug 2022 16:56:38 +0200 Subject: [PATCH 11/11] Update translations --- .../fc3e4509-47f3-4622-9bc4-0a90fe2b6262-en_US.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/huawei/translations/fc3e4509-47f3-4622-9bc4-0a90fe2b6262-en_US.ts b/huawei/translations/fc3e4509-47f3-4622-9bc4-0a90fe2b6262-en_US.ts index 28ede6f..dfe29b1 100644 --- a/huawei/translations/fc3e4509-47f3-4622-9bc4-0a90fe2b6262-en_US.ts +++ b/huawei/translations/fc3e4509-47f3-4622-9bc4-0a90fe2b6262-en_US.ts @@ -107,7 +107,7 @@ The name of the StateType ({a51f0ceb-bd2c-444f-8b39-77cf8a4e1bc6}) of ThingClass - Huawei FusionSolar Inverter (Modbus RTU) + Huawei FusionSolar Inverter ((Modbus RTU) The name of the ThingClass ({77558007-5076-4ca6-bd46-169f215c3e29}) @@ -240,22 +240,22 @@ The name of the StateType ({f480dc82-68e2-44e2-839c-df38b9c10310}) of ThingClass - + The MAC address is not vaild. Please reconfigure the device to fix this. - + The Modbus address not valid. It must be a value between 1 and 254. - + The Modbus RTU resource is not available. - + Could not initialize the communication with the SmartDongle.