diff --git a/debian/changelog b/debian/changelog index e975cce..45d10bf 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,4 @@ + nymea-plugins-modbus (0.30.0) xenial; urgency=medium [ Michael Zanetti ] diff --git a/debian/control b/debian/control index e11bcbf..ca0a9de 100644 --- a/debian/control +++ b/debian/control @@ -1,7 +1,7 @@ Source: nymea-plugins-modbus Section: utils Priority: options -Maintainer: Bernhard Trinnes +Maintainer: nymea GmbH Build-depends: debhelper (>= 9.0.0), libnymea-dev (>= 0.17), libnymea-gpio-dev, @@ -20,13 +20,7 @@ Section: libs Depends: ${shlibs:Depends}, ${misc:Depends} Description: nymea.io sunspec library - The nymea daemon is a plugin based IoT (Internet of Things) server. The - server works like a translator for devices, things and services and - allows them to interact. - With the powerful rule engine you are able to connect any device available - in the system and create individual scenes and behaviors for your environment. - . - This package will install the nymea.io sunspec library + This package contains the nymea sunspec library. Package: libnymea-sunspec-dev Section: libdevel @@ -38,28 +32,15 @@ Depends: ${shlibs:Depends}, pkg-config, qtbase5-dev, Description: The main libraries and header files for developing with nymea sunspec. - The nymea daemon is a plugin based IoT (Internet of Things) server. The - server works like a translator for devices, things and services and - allows them to interact. - With the powerful rule engine you are able to connect any device available - in the system and create individual scenes and behaviors for your environment. - . - This package will install the development files for nymea sunspec. + This package contains the nymea sunspec library - development files. Package: nymea-plugin-alphainnotec Architecture: any Section: libs Depends: ${shlibs:Depends}, ${misc:Depends}, - nymea-plugins-modbus-translations -Description: nymea.io plugin for alpha innotec heat pumps - The nymea daemon is a plugin based IoT (Internet of Things) server. The - server works like a translator for devices, things and services and - allows them to interact. - With the powerful rule engine you are able to connect any device available - in the system and create individual scenes and behaviors for your environment. - . - This package will install the nymea.io plugin for alpha innotec heat pumps +Description: nymea integration plugin for alpha innotec heat pumps + This package contains the nymea integration plugin for alpha innotec head pumps. Package: nymea-plugin-drexelundweiss @@ -67,15 +48,8 @@ Architecture: any Section: libs Depends: ${shlibs:Depends}, ${misc:Depends}, - nymea-plugins-modbus-translations -Description: nymea.io plugin for Drexel & Weiss heat pumps - The nymea daemon is a plugin based IoT (Internet of Things) server. The - server works like a translator for devices, things and services and - allows them to interact. - With the powerful rule engine you are able to connect any device available - in the system and create individual scenes and behaviors for your environment. - . - This package will install the nymea.io plugin for Drexel & Weiss heat pumps +Description: nymea integration plugin for Drexel & Weiss heat pumps + This package contains the nymea integration plugin for Drexel & Weiss heat pumps. Package: nymea-plugin-idm @@ -83,15 +57,8 @@ Architecture: any Section: libs Depends: ${shlibs:Depends}, ${misc:Depends}, - nymea-plugins-modbus-translations, -Description: nymea.io plugin for iDM heat pumps - The nymea daemon is a plugin based IoT (Internet of Things) server. The - server works like a translator for devices, things and services and - allows them to interact. - With the powerful rule engine you are able to connect any device available - in the system and create individual scenes and behaviors for your environment. - . - This package will install the nymea.io plugin for iDM heat pumps +Description: nymea integration plugin for iDM heat pumps + This package contains the nymea integration plugin for iDM heat pumps. Package: nymea-plugin-energymeters @@ -100,15 +67,19 @@ Multi-Arch: same Section: libs Depends: ${shlibs:Depends}, ${misc:Depends}, - nymea-plugins-modbus-translations -Description: nymea.io plugin for Modbus based energy meters - The nymea daemon is a plugin based IoT (Internet of Things) server. The - server works like a translator for devices, things and services and - allows them to interact. - With the powerful rule engine you are able to connect any device available - in the system and create individual scenes and behaviors for your environment. - . - This package will install the nymea.io plugin for energy meters +Description: nymea integration plugin for Modbus based energy meters + This package contains a collection of energy meters. + + +Package: nymea-plugin-huawei +Architecture: any +Multi-Arch: same +Section: libs +Depends: ${shlibs:Depends}, + ${misc:Depends} +Description: nymea integration plugin for Huawei FusionSolar energy devices + This package contains the nymea integration plugin for Huawei FusionSolar devices + based on Modbus TCP. Package: nymea-plugin-modbuscommander @@ -116,15 +87,9 @@ Architecture: any Section: libs Depends: ${shlibs:Depends}, ${misc:Depends}, - nymea-plugins-modbus-translations -Description: nymea.io plugin to send and receive generic modbus commands - The nymea daemon is a plugin based IoT (Internet of Things) server. The - server works like a translator for devices, things and services and - allows them to interact. - With the powerful rule engine you are able to connect any device available - in the system and create individual scenes and behaviors for your environment. - . - This package will install the nymea.io modbus commander plug-in +Description: nymea integration plugin to send and receive generic modbus commands + This package contains a nymea integration plugin which allows to send and receive + generic Modbus commands to/from a Modbus device. Package: nymea-plugin-mypv @@ -132,15 +97,8 @@ Architecture: any Section: libs Depends: ${shlibs:Depends}, ${misc:Depends}, - nymea-plugins-modbus-translations -Description: nymea.io plugin for my-pv heating rods - The nymea daemon is a plugin based IoT (Internet of Things) server. The - server works like a translator for devices, things and services and - allows them to interact. - With the powerful rule engine you are able to connect any device available - in the system and create individual scenes and behaviors for your environment. - . - This package will install the nymea.io plugin for my-pv +Description: nymea integration plugin for my-pv heating rods + This package contains the nymea integration plugin for my-pv solar inverters. Package: nymea-plugin-mtec @@ -148,30 +106,16 @@ Architecture: any Section: libs Depends: ${shlibs:Depends}, ${misc:Depends}, - nymea-plugins-modbus-translations -Description: nymea.io plugin for M-TEC heat pumps - The nymea daemon is a plugin based IoT (Internet of Things) server. The - server works like a translator for devices, things and services and - allows them to interact. - With the powerful rule engine you are able to connect any device available - in the system and create individual scenes and behaviors for your environment. - . - This package will install the nymea.io plugin for M-TEC heat pumps +Description: nymea integration plugin for M-TEC heat pumps + This package contains the nymea integration plugin for M-TEC heat pumps. Package: nymea-plugin-sunspec Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, - nymea-plugins-modbus-translations -Description: nymea.io plugin for SunSpec Modbus devices. - The nymea daemon is a plugin based IoT (Internet of Things) server. The - server works like a translator for devices, things and services and - allows them to interact. - With the powerful rule engine you are able to connect any device available - in the system and create individual scenes and behaviors for your environment. - . - This package will install the nymea.io plugin for SunSpec. +Description: nymea integration plugin for SunSpec Modbus devices + This package contains the nymea integration plugin for SunSpec compatible solar inverters. Package: nymea-plugin-unipi2 @@ -181,16 +125,9 @@ Section: libs Depends: ${shlibs:Depends}, ${misc:Depends}, wiringpi, - nymea-plugins-modbus-translations Replaces: nymea-plugin-unipi -Description: nymea.io plugin for UniPi devices - The nymea daemon is a plugin based IoT (Internet of Things) server. The - server works like a translator for devices, things and services and - allows them to interact. - With the powerful rule engine you are able to connect any device available - in the system and create individual scenes and behaviors for your environment. - . - This package will install the nymea.io plugin for unipi devices +Description: nymea integration plugin for UniPi devices + This package contains the nymea integration plugin for UniPi devices. Package: nymea-plugin-wallbe @@ -198,41 +135,15 @@ Architecture: any Section: libs Depends: ${shlibs:Depends}, ${misc:Depends}, - nymea-plugins-modbus-translations -Description: nymea.io plugin for wallbe ev charging stations - The nymea daemon is a plugin based IoT (Internet of Things) server. The - server works like a translator for devices, things and services and - allows them to interact. - With the powerful rule engine you are able to connect any device available - in the system and create individual scenes and behaviors for your environment. - . - This package will install the nymea.io plugin for wallbe +Description: nymea integration plugin for Wallbe wallboxes + This package contains the nymea integration plugin for wallboxes made + by wallbe. Package: nymea-plugin-webasto Architecture: any Section: libs Depends: ${shlibs:Depends}, ${misc:Depends}, - nymea-plugins-modbus-translations -Description: nymea.io plugin for Webasto Live EV charging stations - The nymea daemon is a plugin based IoT (Internet of Things) server. The - server works like a translator for devices, things and services and - allows them to interact. - With the powerful rule engine you are able to connect any device available - in the system and create individual scenes and behaviors for your environment. - . - This package will install the nymea.io plugin for webasto - - -Package: nymea-plugins-modbus-translations -Section: misc -Architecture: all -Depends: ${misc:Depends} -Description: Translation files for nymea modbus plugins - translations - The nymea daemon is a plugin based IoT (Internet of Things) server. The - server works like a translator for devices, things and services and - allows them to interact. - With the powerful rule engine you are able to connect any device available - in the system and create individual scenes and behaviors for your environment. - . - This package provides the translation files for all nymea modbus plugins. +Description: nymea integration plugin for Webasto Live wallboxes + This package contains the nymea integration plugin for wallboxes made + by Webasto. diff --git a/debian/nymea-plugin-alphainnotec.install.in b/debian/nymea-plugin-alphainnotec.install.in index ed59ad5..1978de4 100644 --- a/debian/nymea-plugin-alphainnotec.install.in +++ b/debian/nymea-plugin-alphainnotec.install.in @@ -1 +1,2 @@ usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginalphainnotec.so +alphainnotec/translations/*qm usr/share/nymea/translations/ diff --git a/debian/nymea-plugin-drexelundweiss.install.in b/debian/nymea-plugin-drexelundweiss.install.in index 93f5740..19a7769 100644 --- a/debian/nymea-plugin-drexelundweiss.install.in +++ b/debian/nymea-plugin-drexelundweiss.install.in @@ -1 +1,2 @@ usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationplugindrexelundweiss.so +drexelundweiss/translations/*qm usr/share/nymea/translations/ diff --git a/debian/nymea-plugin-energymeters.install.in b/debian/nymea-plugin-energymeters.install.in index 2f08f1f..cc7039a 100644 --- a/debian/nymea-plugin-energymeters.install.in +++ b/debian/nymea-plugin-energymeters.install.in @@ -1 +1,2 @@ usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginenergymeters.so +energymeters/translations/*qm usr/share/nymea/translations/ diff --git a/debian/nymea-plugin-huawei.install.in b/debian/nymea-plugin-huawei.install.in new file mode 100644 index 0000000..c15a4fe --- /dev/null +++ b/debian/nymea-plugin-huawei.install.in @@ -0,0 +1,2 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginhuawei.so +huawei/translations/*qm usr/share/nymea/translations/ diff --git a/debian/nymea-plugin-idm.install.in b/debian/nymea-plugin-idm.install.in index f215b60..07f64c1 100644 --- a/debian/nymea-plugin-idm.install.in +++ b/debian/nymea-plugin-idm.install.in @@ -1 +1,2 @@ usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginidm.so +idm/translations/*qm usr/share/nymea/translations/ diff --git a/debian/nymea-plugin-modbuscommander.install.in b/debian/nymea-plugin-modbuscommander.install.in index 9116e7a..34efefc 100644 --- a/debian/nymea-plugin-modbuscommander.install.in +++ b/debian/nymea-plugin-modbuscommander.install.in @@ -1 +1,2 @@ usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginmodbuscommander.so +modbuscommander/translations/*qm usr/share/nymea/translations/ diff --git a/debian/nymea-plugin-mypv.install.in b/debian/nymea-plugin-mypv.install.in index 27366e3..ec0bb9e 100644 --- a/debian/nymea-plugin-mypv.install.in +++ b/debian/nymea-plugin-mypv.install.in @@ -1 +1,2 @@ usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginmypv.so +mypv/translations/*qm usr/share/nymea/translations/ diff --git a/debian/nymea-plugin-sunspec.install.in b/debian/nymea-plugin-sunspec.install.in index aae234d..988f250 100644 --- a/debian/nymea-plugin-sunspec.install.in +++ b/debian/nymea-plugin-sunspec.install.in @@ -1 +1,2 @@ usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginsunspec.so +sunspec/translations/*qm usr/share/nymea/translations/ diff --git a/debian/nymea-plugin-unipi2.install.in b/debian/nymea-plugin-unipi2.install.in index 322dcfa..dbb2f7b 100644 --- a/debian/nymea-plugin-unipi2.install.in +++ b/debian/nymea-plugin-unipi2.install.in @@ -1,2 +1,3 @@ usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginunipi.so unipi/modbus_maps/* usr/share/nymea/modbus +unipi/translations/*qm usr/share/nymea/translations/ diff --git a/debian/nymea-plugin-wallbe.install.in b/debian/nymea-plugin-wallbe.install.in index ba78fae..e608695 100644 --- a/debian/nymea-plugin-wallbe.install.in +++ b/debian/nymea-plugin-wallbe.install.in @@ -1 +1,2 @@ usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginwallbe.so +wallbe/translations/*qm usr/share/nymea/translations/ diff --git a/debian/nymea-plugin-webasto.install.in b/debian/nymea-plugin-webasto.install.in index 1eb7eaa..c722c73 100644 --- a/debian/nymea-plugin-webasto.install.in +++ b/debian/nymea-plugin-webasto.install.in @@ -1 +1,2 @@ usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginwebasto.so +webasto/translations/*qm usr/share/nymea/translations/ diff --git a/huawei/README.md b/huawei/README.md new file mode 100644 index 0000000..dba2cef --- /dev/null +++ b/huawei/README.md @@ -0,0 +1,13 @@ +# Huawei FusionSolar + +Connects to a Huawei FusionSolar using Modbus TCP. + +## Supported things + +* Inverter +* Meter (only current power) +* Battery (on Unit 1 and 2) + +## More +https://solar.huawei.com/eu/ + diff --git a/huawei/huawei-registers.json b/huawei/huawei-registers.json new file mode 100644 index 0000000..0e559d1 --- /dev/null +++ b/huawei/huawei-registers.json @@ -0,0 +1,277 @@ +{ + "protocol": "TCP", + "endianness": "BigEndian", + "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.png b/huawei/huawei.png new file mode 100644 index 0000000..f846632 Binary files /dev/null and b/huawei/huawei.png differ diff --git a/huawei/huawei.pro b/huawei/huawei.pro new file mode 100644 index 0000000..194853d --- /dev/null +++ b/huawei/huawei.pro @@ -0,0 +1,17 @@ +include(../plugins.pri) + +QT += network serialbus + +HEADERS += \ + huaweifusionsolar.h \ + huaweimodbustcpconnection.h \ + integrationpluginhuawei.h \ + ../modbus/modbustcpmaster.h \ + ../modbus/modbusdatautils.h + +SOURCES += \ + huaweifusionsolar.cpp \ + huaweimodbustcpconnection.cpp \ + integrationpluginhuawei.cpp \ + ../modbus/modbustcpmaster.cpp \ + ../modbus/modbusdatautils.cpp diff --git a/huawei/huaweifusionsolar.cpp b/huawei/huaweifusionsolar.cpp new file mode 100644 index 0000000..3374c2a --- /dev/null +++ b/huawei/huaweifusionsolar.cpp @@ -0,0 +1,460 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, nymea GmbH +* Contact: contact@nymea.io +* +* This fileDescriptor 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 "huaweifusionsolar.h" +#include "extern-plugininfo.h" +#include "loggingcategories.h" + +NYMEA_LOGGING_CATEGORY(dcHuaweiFusionSolar, "HuaweiFusionSolar") + +HuaweiFusionSolar::HuaweiFusionSolar(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent) : + HuaweiModbusTcpConnection(hostAddress, port, slaveId, parent) +{ + +} + +void HuaweiFusionSolar::initialize() +{ + // No init registers defined. Nothing to be done and we are finished. + emit initializationFinished(); +} + +void HuaweiFusionSolar::update() +{ + // Make sure there is not an update still running + if (!m_registersQueue.isEmpty()) + return; + + // Add the requests + m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterInverterActivePower); + m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterInverterDeviceStatus); + m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterInverterEnergyProduced); + m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery1Status); + if (m_battery1Available) { + m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery1Power); + m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery1Soc); + } + m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterPowerMeterActivePower); + m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery2Status); + if (m_battery2Available) { + m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery2Power); + m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery2Soc); + } + + // Note: since huawei can only process one request at the time, we need to queue the requests + + m_currentRegisterRequest = -1; + readNextRegister(); +} + +void HuaweiFusionSolar::readNextRegister() +{ + // Check if currently a reply is pending + if (m_currentRegisterRequest >= 0) + return; + + // Check if there is still a register to read + if (m_registersQueue.isEmpty()) + return; + + m_currentRegisterRequest = m_registersQueue.dequeue(); + + switch (m_currentRegisterRequest) { + case HuaweiModbusTcpConnection::RegisterInverterActivePower: { + 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; + float receivedInverterActivePower = ModbusDataUtils::convertToInt32(values, ModbusDataUtils::ByteOrderBigEndian) * 1.0 * pow(10, -3); + if (m_inverterActivePower != receivedInverterActivePower) { + m_inverterActivePower = receivedInverterActivePower; + emit inverterActivePowerChanged(m_inverterActivePower); + } + } + 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(); + emit reply->finished(); // To make sure it will be deleted + }); + } else { + delete reply; // Broadcast reply returns immediatly + finishRequest(); + } + } else { + qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Inverter active power\" registers from" << hostAddress().toString() << errorString(); + finishRequest(); + } + break; + } + case HuaweiModbusTcpConnection::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; + InverterDeviceStatus receivedInverterDeviceStatus = static_cast(ModbusDataUtils::convertToUInt16(values)); + qCDebug(dcHuaweiFusionSolar()) << "Inverter status" << receivedInverterDeviceStatus; + if (m_inverterDeviceStatus != receivedInverterDeviceStatus) { + m_inverterDeviceStatus = receivedInverterDeviceStatus; + emit inverterDeviceStatusChanged(m_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(); + emit reply->finished(); // To make sure it will be deleted + }); + } else { + delete reply; // Broadcast reply returns immediatly + finishRequest(); + } + } else { + qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Inverter device status\" registers from" << hostAddress().toString() << errorString(); + finishRequest(); + } + break; + } + case HuaweiModbusTcpConnection::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; + float receivedInverterEnergyProduced = ModbusDataUtils::convertToUInt32(values, ModbusDataUtils::ByteOrderBigEndian) * 1.0 * pow(10, -2); + if (m_inverterEnergyProduced != receivedInverterEnergyProduced) { + m_inverterEnergyProduced = receivedInverterEnergyProduced; + emit inverterEnergyProducedChanged(m_inverterEnergyProduced); + } + } + 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(); + emit reply->finished(); // To make sure it will be deleted + }); + } else { + delete reply; // Broadcast reply returns immediatly + finishRequest(); + } + } else { + qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Inverter energy produced\" registers from" << hostAddress().toString() << errorString(); + finishRequest(); + } + 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; + BatteryDeviceStatus receivedLunaBattery1Status = static_cast(ModbusDataUtils::convertToUInt16(values)); + qCDebug(dcHuaweiFusionSolar()) << "Battery 1 status" << receivedLunaBattery1Status; + if (receivedLunaBattery1Status == BatteryDeviceStatusOffline) { + m_battery1Available = false; + } else { + m_battery1Available = true; + } + + if (m_lunaBattery1Status != receivedLunaBattery1Status) { + m_lunaBattery1Status = receivedLunaBattery1Status; + emit lunaBattery1StatusChanged(m_lunaBattery1Status); + } + } + 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(); + emit reply->finished(); // To make sure it will be deleted + }); + } else { + delete reply; // Broadcast reply returns immediatly + finishRequest(); + } + } else { + qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Luna 2000 Battery 1 status\" registers from" << hostAddress().toString() << errorString(); + finishRequest(); + } + 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; + qint32 receivedLunaBattery1Power = ModbusDataUtils::convertToInt32(values, ModbusDataUtils::ByteOrderBigEndian); + if (m_lunaBattery1Power != receivedLunaBattery1Power) { + m_lunaBattery1Power = receivedLunaBattery1Power; + emit lunaBattery1PowerChanged(m_lunaBattery1Power); + } + } + 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(); + emit reply->finished(); // To make sure it will be deleted + }); + } else { + delete reply; // Broadcast reply returns immediatly + finishRequest(); + } + } 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; + float receivedLunaBattery1Soc = ModbusDataUtils::convertToUInt16(values) * 1.0 * pow(10, -1); + if (m_lunaBattery1Soc != receivedLunaBattery1Soc) { + m_lunaBattery1Soc = receivedLunaBattery1Soc; + emit lunaBattery1SocChanged(m_lunaBattery1Soc); + } + } + 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(); + emit reply->finished(); // To make sure it will be deleted + }); + } else { + delete reply; // Broadcast reply returns immediatly + finishRequest(); + } + } else { + qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Luna 2000 Battery 1 state of charge\" registers from" << hostAddress().toString() << errorString(); + finishRequest(); + } + break; + } + case HuaweiModbusTcpConnection::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; + qint32 receivedPowerMeterActivePower = ModbusDataUtils::convertToInt32(values, ModbusDataUtils::ByteOrderBigEndian); + if (m_powerMeterActivePower != receivedPowerMeterActivePower) { + m_powerMeterActivePower = receivedPowerMeterActivePower; + emit powerMeterActivePowerChanged(m_powerMeterActivePower); + } + } + 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(); + emit reply->finished(); // To make sure it will be deleted + }); + } else { + delete reply; // Broadcast reply returns immediatly + finishRequest(); + } + } else { + qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Power meter active power\" registers from" << hostAddress().toString() << errorString(); + finishRequest(); + } + break; + } + case HuaweiModbusTcpConnection::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; + BatteryDeviceStatus receivedLunaBattery2Status = static_cast(ModbusDataUtils::convertToUInt16(values)); + qCDebug(dcHuaweiFusionSolar()) << "Battery 2 status" << receivedLunaBattery2Status; + if (receivedLunaBattery2Status == BatteryDeviceStatusOffline) { + m_battery2Available = false; + } else { + m_battery2Available = true; + } + if (m_lunaBattery2Status != receivedLunaBattery2Status) { + m_lunaBattery2Status = receivedLunaBattery2Status; + emit lunaBattery2StatusChanged(m_lunaBattery2Status); + } + } + 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(); + emit reply->finished(); // To make sure it will be deleted + }); + } else { + delete reply; // Broadcast reply returns immediatly + finishRequest(); + } + } else { + qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Luna 2000 Battery 2 status\" registers from" << hostAddress().toString() << errorString(); + finishRequest(); + } + break; + } + case HuaweiModbusTcpConnection::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; + qint32 receivedLunaBattery2Power = ModbusDataUtils::convertToInt32(values, ModbusDataUtils::ByteOrderBigEndian); + if (m_lunaBattery2Power != receivedLunaBattery2Power) { + m_lunaBattery2Power = receivedLunaBattery2Power; + emit lunaBattery2PowerChanged(m_lunaBattery2Power); + } + } + 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(); + emit reply->finished(); // To make sure it will be deleted + }); + } else { + delete reply; // Broadcast reply returns immediatly + finishRequest(); + } + } else { + qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Luna 2000 Battery 2 power\" registers from" << hostAddress().toString() << errorString(); + finishRequest(); + } + break; + } + case HuaweiModbusTcpConnection::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; + float receivedLunaBattery2Soc = ModbusDataUtils::convertToUInt16(values) * 1.0 * pow(10, -1); + if (m_lunaBattery2Soc != receivedLunaBattery2Soc) { + m_lunaBattery2Soc = receivedLunaBattery2Soc; + emit lunaBattery2SocChanged(m_lunaBattery2Soc); + } + } + 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(); + emit reply->finished(); // To make sure it will be deleted + }); + } else { + delete reply; // Broadcast reply returns immediatly + finishRequest(); + } + } else { + qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Luna 2000 Battery 2 state of charge\" registers from" << hostAddress().toString() << errorString(); + finishRequest(); + } + break; + } + } +} + +void HuaweiFusionSolar::finishRequest() +{ + m_currentRegisterRequest = -1; + QTimer::singleShot(1000, this, &HuaweiFusionSolar::readNextRegister); +} diff --git a/huawei/huaweifusionsolar.h b/huawei/huaweifusionsolar.h new file mode 100644 index 0000000..a208fbb --- /dev/null +++ b/huawei/huaweifusionsolar.h @@ -0,0 +1,63 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, nymea GmbH +* Contact: contact@nymea.io +* +* This fileDescriptor 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 HUAWEIFUSIONSOLAR_H +#define HUAWEIFUSIONSOLAR_H + +#include +#include + +#include "huaweimodbustcpconnection.h" + +class HuaweiFusionSolar : public HuaweiModbusTcpConnection +{ + Q_OBJECT +public: + explicit HuaweiFusionSolar(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent = nullptr); + + virtual void initialize() override; + virtual void update() override; + + +private: + QQueue m_registersQueue; + + int m_currentRegisterRequest = -1; + void finishRequest(); + + bool m_battery1Available = true; + bool m_battery2Available = true; + +private slots: + void readNextRegister(); + +}; + +#endif // HUAWEIFUSIONSOLAR_H diff --git a/huawei/huaweimodbustcpconnection.cpp b/huawei/huaweimodbustcpconnection.cpp new file mode 100644 index 0000000..cca8e02 --- /dev/null +++ b/huawei/huaweimodbustcpconnection.cpp @@ -0,0 +1,523 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, nymea GmbH +* Contact: contact@nymea.io +* +* This fileDescriptor 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 "huaweimodbustcpconnection.h" +#include "loggingcategories.h" + +NYMEA_LOGGING_CATEGORY(dcHuaweiModbusTcpConnection, "HuaweiModbusTcpConnection") + +HuaweiModbusTcpConnection::HuaweiModbusTcpConnection(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent) : + ModbusTCPMaster(hostAddress, port, parent), + m_slaveId(slaveId) +{ + +} + +float HuaweiModbusTcpConnection::inverterActivePower() const +{ + return m_inverterActivePower; +} + +HuaweiModbusTcpConnection::InverterDeviceStatus HuaweiModbusTcpConnection::inverterDeviceStatus() const +{ + return m_inverterDeviceStatus; +} + +float HuaweiModbusTcpConnection::inverterEnergyProduced() const +{ + return m_inverterEnergyProduced; +} + +qint32 HuaweiModbusTcpConnection::powerMeterActivePower() const +{ + return m_powerMeterActivePower; +} + +HuaweiModbusTcpConnection::BatteryDeviceStatus HuaweiModbusTcpConnection::lunaBattery1Status() const +{ + return m_lunaBattery1Status; +} + +qint32 HuaweiModbusTcpConnection::lunaBattery1Power() const +{ + return m_lunaBattery1Power; +} + +float HuaweiModbusTcpConnection::lunaBattery1Soc() const +{ + return m_lunaBattery1Soc; +} + +HuaweiModbusTcpConnection::BatteryDeviceStatus HuaweiModbusTcpConnection::lunaBattery2Status() const +{ + return m_lunaBattery2Status; +} + +qint32 HuaweiModbusTcpConnection::lunaBattery2Power() const +{ + return m_lunaBattery2Power; +} + +float HuaweiModbusTcpConnection::lunaBattery2Soc() const +{ + return m_lunaBattery2Soc; +} + +void HuaweiModbusTcpConnection::initialize() +{ + // No init registers defined. Nothing to be done and we are finished. + emit initializationFinished(); +} + +void HuaweiModbusTcpConnection::update() +{ + updateInverterActivePower(); + updateInverterDeviceStatus(); + updateInverterEnergyProduced(); + updatePowerMeterActivePower(); + updateLunaBattery1Status(); + updateLunaBattery1Power(); + updateLunaBattery1Soc(); + updateLunaBattery2Status(); + updateLunaBattery2Power(); + updateLunaBattery2Soc(); +} + +void HuaweiModbusTcpConnection::updateInverterActivePower() +{ + // Update registers from Inverter active power + qCDebug(dcHuaweiModbusTcpConnection()) << "--> 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(dcHuaweiModbusTcpConnection()) << "<-- Response from \"Inverter active power\" register" << 32080 << "size:" << 2 << values; + float receivedInverterActivePower = ModbusDataUtils::convertToInt32(values, ModbusDataUtils::ByteOrderBigEndian) * 1.0 * pow(10, -3); + if (m_inverterActivePower != receivedInverterActivePower) { + m_inverterActivePower = receivedInverterActivePower; + emit inverterActivePowerChanged(m_inverterActivePower); + } + } + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + qCWarning(dcHuaweiModbusTcpConnection()) << "Modbus reply error occurred while updating \"Inverter active power\" registers from" << hostAddress().toString() << error << reply->errorString(); + emit reply->finished(); // To make sure it will be deleted + }); + } else { + delete reply; // Broadcast reply returns immediatly + } + } else { + qCWarning(dcHuaweiModbusTcpConnection()) << "Error occurred while reading \"Inverter active power\" registers from" << hostAddress().toString() << errorString(); + } +} + +void HuaweiModbusTcpConnection::updateInverterDeviceStatus() +{ + // Update registers from Inverter device status + qCDebug(dcHuaweiModbusTcpConnection()) << "--> 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(dcHuaweiModbusTcpConnection()) << "<-- Response from \"Inverter device status\" register" << 32089 << "size:" << 1 << values; + InverterDeviceStatus receivedInverterDeviceStatus = static_cast(ModbusDataUtils::convertToUInt16(values)); + if (m_inverterDeviceStatus != receivedInverterDeviceStatus) { + m_inverterDeviceStatus = receivedInverterDeviceStatus; + emit inverterDeviceStatusChanged(m_inverterDeviceStatus); + } + } + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + qCWarning(dcHuaweiModbusTcpConnection()) << "Modbus reply error occurred while updating \"Inverter device status\" registers from" << hostAddress().toString() << error << reply->errorString(); + emit reply->finished(); // To make sure it will be deleted + }); + } else { + delete reply; // Broadcast reply returns immediatly + } + } else { + qCWarning(dcHuaweiModbusTcpConnection()) << "Error occurred while reading \"Inverter device status\" registers from" << hostAddress().toString() << errorString(); + } +} + +void HuaweiModbusTcpConnection::updateInverterEnergyProduced() +{ + // Update registers from Inverter energy produced + qCDebug(dcHuaweiModbusTcpConnection()) << "--> 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(dcHuaweiModbusTcpConnection()) << "<-- Response from \"Inverter energy produced\" register" << 32106 << "size:" << 2 << values; + float receivedInverterEnergyProduced = ModbusDataUtils::convertToUInt32(values, ModbusDataUtils::ByteOrderBigEndian) * 1.0 * pow(10, -2); + if (m_inverterEnergyProduced != receivedInverterEnergyProduced) { + m_inverterEnergyProduced = receivedInverterEnergyProduced; + emit inverterEnergyProducedChanged(m_inverterEnergyProduced); + } + } + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + qCWarning(dcHuaweiModbusTcpConnection()) << "Modbus reply error occurred while updating \"Inverter energy produced\" registers from" << hostAddress().toString() << error << reply->errorString(); + emit reply->finished(); // To make sure it will be deleted + }); + } else { + delete reply; // Broadcast reply returns immediatly + } + } else { + qCWarning(dcHuaweiModbusTcpConnection()) << "Error occurred while reading \"Inverter energy produced\" registers from" << hostAddress().toString() << errorString(); + } +} + +void HuaweiModbusTcpConnection::updatePowerMeterActivePower() +{ + // Update registers from Power meter active power + qCDebug(dcHuaweiModbusTcpConnection()) << "--> 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(dcHuaweiModbusTcpConnection()) << "<-- Response from \"Power meter active power\" register" << 37113 << "size:" << 2 << values; + qint32 receivedPowerMeterActivePower = ModbusDataUtils::convertToInt32(values, ModbusDataUtils::ByteOrderBigEndian); + if (m_powerMeterActivePower != receivedPowerMeterActivePower) { + m_powerMeterActivePower = receivedPowerMeterActivePower; + emit powerMeterActivePowerChanged(m_powerMeterActivePower); + } + } + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + qCWarning(dcHuaweiModbusTcpConnection()) << "Modbus reply error occurred while updating \"Power meter active power\" registers from" << hostAddress().toString() << error << reply->errorString(); + emit reply->finished(); // To make sure it will be deleted + }); + } else { + delete reply; // Broadcast reply returns immediatly + } + } else { + qCWarning(dcHuaweiModbusTcpConnection()) << "Error occurred while reading \"Power meter active power\" registers from" << hostAddress().toString() << errorString(); + } +} + +void HuaweiModbusTcpConnection::updateLunaBattery1Status() +{ + // Update registers from Luna 2000 Battery 1 status + qCDebug(dcHuaweiModbusTcpConnection()) << "--> 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(dcHuaweiModbusTcpConnection()) << "<-- Response from \"Luna 2000 Battery 1 status\" register" << 37000 << "size:" << 1 << values; + BatteryDeviceStatus receivedLunaBattery1Status = static_cast(ModbusDataUtils::convertToUInt16(values)); + m_lunaBattery1Status = receivedLunaBattery1Status; + emit lunaBattery1StatusChanged(m_lunaBattery1Status); + } + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + qCWarning(dcHuaweiModbusTcpConnection()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 1 status\" registers from" << hostAddress().toString() << error << reply->errorString(); + emit reply->finished(); // To make sure it will be deleted + }); + } else { + delete reply; // Broadcast reply returns immediatly + } + } else { + qCWarning(dcHuaweiModbusTcpConnection()) << "Error occurred while reading \"Luna 2000 Battery 1 status\" registers from" << hostAddress().toString() << errorString(); + } +} + +void HuaweiModbusTcpConnection::updateLunaBattery1Power() +{ + // Update registers from Luna 2000 Battery 1 power + qCDebug(dcHuaweiModbusTcpConnection()) << "--> 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(dcHuaweiModbusTcpConnection()) << "<-- Response from \"Luna 2000 Battery 1 power\" register" << 37001 << "size:" << 2 << values; + qint32 receivedLunaBattery1Power = ModbusDataUtils::convertToInt32(values, ModbusDataUtils::ByteOrderBigEndian); + if (m_lunaBattery1Power != receivedLunaBattery1Power) { + m_lunaBattery1Power = receivedLunaBattery1Power; + emit lunaBattery1PowerChanged(m_lunaBattery1Power); + } + } + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + qCWarning(dcHuaweiModbusTcpConnection()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 1 power\" registers from" << hostAddress().toString() << error << reply->errorString(); + emit reply->finished(); // To make sure it will be deleted + }); + } else { + delete reply; // Broadcast reply returns immediatly + } + } else { + qCWarning(dcHuaweiModbusTcpConnection()) << "Error occurred while reading \"Luna 2000 Battery 1 power\" registers from" << hostAddress().toString() << errorString(); + } +} + +void HuaweiModbusTcpConnection::updateLunaBattery1Soc() +{ + // Update registers from Luna 2000 Battery 1 state of charge + qCDebug(dcHuaweiModbusTcpConnection()) << "--> 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(dcHuaweiModbusTcpConnection()) << "<-- Response from \"Luna 2000 Battery 1 state of charge\" register" << 37004 << "size:" << 1 << values; + float receivedLunaBattery1Soc = ModbusDataUtils::convertToUInt16(values) * 1.0 * pow(10, -1); + if (m_lunaBattery1Soc != receivedLunaBattery1Soc) { + m_lunaBattery1Soc = receivedLunaBattery1Soc; + emit lunaBattery1SocChanged(m_lunaBattery1Soc); + } + } + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + qCWarning(dcHuaweiModbusTcpConnection()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 1 state of charge\" registers from" << hostAddress().toString() << error << reply->errorString(); + emit reply->finished(); // To make sure it will be deleted + }); + } else { + delete reply; // Broadcast reply returns immediatly + } + } else { + qCWarning(dcHuaweiModbusTcpConnection()) << "Error occurred while reading \"Luna 2000 Battery 1 state of charge\" registers from" << hostAddress().toString() << errorString(); + } +} + +void HuaweiModbusTcpConnection::updateLunaBattery2Status() +{ + // Update registers from Luna 2000 Battery 2 status + qCDebug(dcHuaweiModbusTcpConnection()) << "--> 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(dcHuaweiModbusTcpConnection()) << "<-- Response from \"Luna 2000 Battery 2 status\" register" << 37741 << "size:" << 1 << values; + BatteryDeviceStatus receivedLunaBattery2Status = static_cast(ModbusDataUtils::convertToUInt16(values)); + m_lunaBattery2Status = receivedLunaBattery2Status; + emit lunaBattery2StatusChanged(m_lunaBattery2Status); + } + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + qCWarning(dcHuaweiModbusTcpConnection()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 2 status\" registers from" << hostAddress().toString() << error << reply->errorString(); + emit reply->finished(); // To make sure it will be deleted + }); + } else { + delete reply; // Broadcast reply returns immediatly + } + } else { + qCWarning(dcHuaweiModbusTcpConnection()) << "Error occurred while reading \"Luna 2000 Battery 2 status\" registers from" << hostAddress().toString() << errorString(); + } +} + +void HuaweiModbusTcpConnection::updateLunaBattery2Power() +{ + // Update registers from Luna 2000 Battery 2 power + qCDebug(dcHuaweiModbusTcpConnection()) << "--> 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(dcHuaweiModbusTcpConnection()) << "<-- Response from \"Luna 2000 Battery 2 power\" register" << 37743 << "size:" << 2 << values; + qint32 receivedLunaBattery2Power = ModbusDataUtils::convertToInt32(values, ModbusDataUtils::ByteOrderBigEndian); + if (m_lunaBattery2Power != receivedLunaBattery2Power) { + m_lunaBattery2Power = receivedLunaBattery2Power; + emit lunaBattery2PowerChanged(m_lunaBattery2Power); + } + } + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + qCWarning(dcHuaweiModbusTcpConnection()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 2 power\" registers from" << hostAddress().toString() << error << reply->errorString(); + emit reply->finished(); // To make sure it will be deleted + }); + } else { + delete reply; // Broadcast reply returns immediatly + } + } else { + qCWarning(dcHuaweiModbusTcpConnection()) << "Error occurred while reading \"Luna 2000 Battery 2 power\" registers from" << hostAddress().toString() << errorString(); + } +} + +void HuaweiModbusTcpConnection::updateLunaBattery2Soc() +{ + // Update registers from Luna 2000 Battery 2 state of charge + qCDebug(dcHuaweiModbusTcpConnection()) << "--> 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(dcHuaweiModbusTcpConnection()) << "<-- Response from \"Luna 2000 Battery 2 state of charge\" register" << 37738 << "size:" << 1 << values; + float receivedLunaBattery2Soc = ModbusDataUtils::convertToUInt16(values) * 1.0 * pow(10, -1); + if (m_lunaBattery2Soc != receivedLunaBattery2Soc) { + m_lunaBattery2Soc = receivedLunaBattery2Soc; + emit lunaBattery2SocChanged(m_lunaBattery2Soc); + } + } + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + qCWarning(dcHuaweiModbusTcpConnection()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 2 state of charge\" registers from" << hostAddress().toString() << error << reply->errorString(); + emit reply->finished(); // To make sure it will be deleted + }); + } else { + delete reply; // Broadcast reply returns immediatly + } + } else { + qCWarning(dcHuaweiModbusTcpConnection()) << "Error occurred while reading \"Luna 2000 Battery 2 state of charge\" registers from" << hostAddress().toString() << errorString(); + } +} + +QModbusReply *HuaweiModbusTcpConnection::readInverterActivePower() +{ + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, 32080, 2); + return sendReadRequest(request, m_slaveId); +} + +QModbusReply *HuaweiModbusTcpConnection::readInverterDeviceStatus() +{ + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, 32089, 1); + return sendReadRequest(request, m_slaveId); +} + +QModbusReply *HuaweiModbusTcpConnection::readInverterEnergyProduced() +{ + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, 32106, 2); + return sendReadRequest(request, m_slaveId); +} + +QModbusReply *HuaweiModbusTcpConnection::readPowerMeterActivePower() +{ + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, 37113, 2); + return sendReadRequest(request, m_slaveId); +} + +QModbusReply *HuaweiModbusTcpConnection::readLunaBattery1Status() +{ + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, 37000, 1); + return sendReadRequest(request, m_slaveId); +} + +QModbusReply *HuaweiModbusTcpConnection::readLunaBattery1Power() +{ + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, 37001, 2); + return sendReadRequest(request, m_slaveId); +} + +QModbusReply *HuaweiModbusTcpConnection::readLunaBattery1Soc() +{ + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, 37004, 1); + return sendReadRequest(request, m_slaveId); +} + +QModbusReply *HuaweiModbusTcpConnection::readLunaBattery2Status() +{ + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, 37741, 1); + return sendReadRequest(request, m_slaveId); +} + +QModbusReply *HuaweiModbusTcpConnection::readLunaBattery2Power() +{ + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, 37743, 2); + return sendReadRequest(request, m_slaveId); +} + +QModbusReply *HuaweiModbusTcpConnection::readLunaBattery2Soc() +{ + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, 37738, 1); + return sendReadRequest(request, m_slaveId); +} + +void HuaweiModbusTcpConnection::verifyInitFinished() +{ + if (m_pendingInitReplies.isEmpty()) { + qCDebug(dcHuaweiModbusTcpConnection()) << "Initialization finished of HuaweiModbusTcpConnection" << hostAddress().toString(); + emit initializationFinished(); + } +} + +QDebug operator<<(QDebug debug, HuaweiModbusTcpConnection *huaweiModbusTcpConnection) +{ + debug.nospace().noquote() << "HuaweiModbusTcpConnection(" << huaweiModbusTcpConnection->hostAddress().toString() << ":" << huaweiModbusTcpConnection->port() << ")" << "\n"; + debug.nospace().noquote() << " - Inverter active power:" << huaweiModbusTcpConnection->inverterActivePower() << " [kW]" << "\n"; + debug.nospace().noquote() << " - Inverter device status:" << huaweiModbusTcpConnection->inverterDeviceStatus() << "\n"; + debug.nospace().noquote() << " - Inverter energy produced:" << huaweiModbusTcpConnection->inverterEnergyProduced() << " [kWh]" << "\n"; + debug.nospace().noquote() << " - Power meter active power:" << huaweiModbusTcpConnection->powerMeterActivePower() << " [W]" << "\n"; + debug.nospace().noquote() << " - Luna 2000 Battery 1 status:" << huaweiModbusTcpConnection->lunaBattery1Status() << "\n"; + debug.nospace().noquote() << " - Luna 2000 Battery 1 power:" << huaweiModbusTcpConnection->lunaBattery1Power() << " [W]" << "\n"; + debug.nospace().noquote() << " - Luna 2000 Battery 1 state of charge:" << huaweiModbusTcpConnection->lunaBattery1Soc() << " [%]" << "\n"; + debug.nospace().noquote() << " - Luna 2000 Battery 2 status:" << huaweiModbusTcpConnection->lunaBattery2Status() << "\n"; + debug.nospace().noquote() << " - Luna 2000 Battery 2 power:" << huaweiModbusTcpConnection->lunaBattery2Power() << " [W]" << "\n"; + debug.nospace().noquote() << " - Luna 2000 Battery 2 state of charge:" << huaweiModbusTcpConnection->lunaBattery2Soc() << " [%]" << "\n"; + return debug.quote().space(); +} + diff --git a/huawei/huaweimodbustcpconnection.h b/huawei/huaweimodbustcpconnection.h new file mode 100644 index 0000000..40ce1b3 --- /dev/null +++ b/huawei/huaweimodbustcpconnection.h @@ -0,0 +1,194 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, nymea GmbH +* Contact: contact@nymea.io +* +* This fileDescriptor 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 HUAWEIMODBUSTCPCONNECTION_H +#define HUAWEIMODBUSTCPCONNECTION_H + +#include + +#include "../modbus/modbusdatautils.h" +#include "../modbus/modbustcpmaster.h" + +class HuaweiModbusTcpConnection : public ModbusTCPMaster +{ + Q_OBJECT +public: + enum Registers { + RegisterInverterActivePower = 32080, + RegisterInverterDeviceStatus = 32089, + RegisterInverterEnergyProduced = 32106, + RegisterLunaBattery1Status = 37000, + RegisterLunaBattery1Power = 37001, + RegisterLunaBattery1Soc = 37004, + RegisterPowerMeterActivePower = 37113, + RegisterLunaBattery2Soc = 37738, + RegisterLunaBattery2Status = 37741, + RegisterLunaBattery2Power = 37743 + }; + Q_ENUM(Registers) + + enum InverterDeviceStatus { + InverterDeviceStatusStandbyInitializing = 0, + InverterDeviceStatusStandbyDetectingInsulationResistance = 1, + InverterDeviceStatusStandbyDetectingIrradiation = 2, + InverterDeviceStatusStandbyDridDetecting = 3, + InverterDeviceStatusStarting = 256, + InverterDeviceStatusOnGrid = 512, + InverterDeviceStatusPowerLimited = 513, + InverterDeviceStatusSelfDerating = 514, + InverterDeviceStatusShutdownFault = 768, + InverterDeviceStatusShutdownCommand = 769, + InverterDeviceStatusShutdownOVGR = 770, + InverterDeviceStatusShutdownCommunicationDisconnected = 771, + InverterDeviceStatusShutdownPowerLimit = 772, + InverterDeviceStatusShutdownManualStartupRequired = 773, + InverterDeviceStatusShutdownInputUnderpower = 774, + InverterDeviceStatusGridSchedulingPCurve = 1025, + InverterDeviceStatusGridSchedulingQUCurve = 1026, + InverterDeviceStatusGridSchedulingPFUCurve = 1027, + InverterDeviceStatusGridSchedulingDryContact = 1028, + InverterDeviceStatusGridSchedulingQPCurve = 1029, + InverterDeviceStatusSpotCheckReady = 1280, + InverterDeviceStatusSpotChecking = 1281, + InverterDeviceStatusInspecting = 1536, + InverterDeviceStatusAfciSelfCheck = 1792, + InverterDeviceStatusIVScanning = 2048, + InverterDeviceStatusDCInputDetection = 2304, + InverterDeviceStatusRunningOffGridCharging = 2560, + InverterDeviceStatusStandbyNoIrradiation = 40960 + }; + Q_ENUM(InverterDeviceStatus) + + enum BatteryDeviceStatus { + BatteryDeviceStatusOffline = 0, + BatteryDeviceStatusStandby = 1, + BatteryDeviceStatusRunning = 1, + BatteryDeviceStatusFault = 1, + BatteryDeviceStatusSleepMode = 1 + }; + Q_ENUM(BatteryDeviceStatus) + + explicit HuaweiModbusTcpConnection(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent = nullptr); + ~HuaweiModbusTcpConnection() = default; + + /* Inverter active power [kW] - Address: 32080, Size: 2 */ + float inverterActivePower() const; + + /* Inverter device status - Address: 32089, Size: 1 */ + InverterDeviceStatus inverterDeviceStatus() const; + + /* Inverter energy produced [kWh] - Address: 32106, Size: 2 */ + float inverterEnergyProduced() const; + + /* Power meter active power [W] - Address: 37113, Size: 2 */ + qint32 powerMeterActivePower() const; + + /* Luna 2000 Battery 1 status - Address: 37000, Size: 1 */ + BatteryDeviceStatus lunaBattery1Status() const; + + /* Luna 2000 Battery 1 power [W] - Address: 37001, Size: 2 */ + qint32 lunaBattery1Power() const; + + /* Luna 2000 Battery 1 state of charge [%] - Address: 37004, Size: 1 */ + float lunaBattery1Soc() const; + + /* Luna 2000 Battery 2 status - Address: 37741, Size: 1 */ + BatteryDeviceStatus lunaBattery2Status() const; + + /* Luna 2000 Battery 2 power [W] - Address: 37743, Size: 2 */ + qint32 lunaBattery2Power() const; + + /* Luna 2000 Battery 2 state of charge [%] - Address: 37738, Size: 1 */ + float lunaBattery2Soc() const; + + + virtual void initialize(); + virtual void update(); + + void updateInverterActivePower(); + void updateInverterDeviceStatus(); + void updateInverterEnergyProduced(); + void updatePowerMeterActivePower(); + void updateLunaBattery1Status(); + void updateLunaBattery1Power(); + void updateLunaBattery1Soc(); + void updateLunaBattery2Status(); + void updateLunaBattery2Power(); + void updateLunaBattery2Soc(); + +signals: + void initializationFinished(); + + void inverterActivePowerChanged(float inverterActivePower); + void inverterDeviceStatusChanged(InverterDeviceStatus inverterDeviceStatus); + void inverterEnergyProducedChanged(float inverterEnergyProduced); + void powerMeterActivePowerChanged(qint32 powerMeterActivePower); + void lunaBattery1StatusChanged(BatteryDeviceStatus lunaBattery1Status); + void lunaBattery1PowerChanged(qint32 lunaBattery1Power); + void lunaBattery1SocChanged(float lunaBattery1Soc); + void lunaBattery2StatusChanged(BatteryDeviceStatus lunaBattery2Status); + void lunaBattery2PowerChanged(qint32 lunaBattery2Power); + void lunaBattery2SocChanged(float lunaBattery2Soc); + +protected: + QModbusReply *readInverterActivePower(); + QModbusReply *readInverterDeviceStatus(); + QModbusReply *readInverterEnergyProduced(); + QModbusReply *readPowerMeterActivePower(); + QModbusReply *readLunaBattery1Status(); + QModbusReply *readLunaBattery1Power(); + QModbusReply *readLunaBattery1Soc(); + QModbusReply *readLunaBattery2Status(); + QModbusReply *readLunaBattery2Power(); + QModbusReply *readLunaBattery2Soc(); + + float m_inverterActivePower = 0; + InverterDeviceStatus m_inverterDeviceStatus = InverterDeviceStatusStandbyInitializing; + float m_inverterEnergyProduced = 0; + qint32 m_powerMeterActivePower = 0; + BatteryDeviceStatus m_lunaBattery1Status = BatteryDeviceStatusOffline; + qint32 m_lunaBattery1Power = 0; + float m_lunaBattery1Soc = 0; + BatteryDeviceStatus m_lunaBattery2Status = BatteryDeviceStatusOffline; + qint32 m_lunaBattery2Power = 0; + float m_lunaBattery2Soc = 0; + +private: + quint16 m_slaveId = 1; + QVector m_pendingInitReplies; + + void verifyInitFinished(); + + +}; + +QDebug operator<<(QDebug debug, HuaweiModbusTcpConnection *huaweiModbusTcpConnection); + +#endif // HUAWEIMODBUSTCPCONNECTION_H diff --git a/huawei/integrationpluginhuawei.cpp b/huawei/integrationpluginhuawei.cpp new file mode 100644 index 0000000..fcdfee7 --- /dev/null +++ b/huawei/integrationpluginhuawei.cpp @@ -0,0 +1,322 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, nymea GmbH +* Contact: contact@nymea.io +* +* This fileDescriptor 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 "integrationpluginhuawei.h" + +#include "network/networkdevicediscovery.h" +#include "hardwaremanager.h" +#include "plugininfo.h" + +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; + } + + NetworkDeviceDiscoveryReply *discoveryReply = hardwareManager()->networkDeviceDiscovery()->discover(); + connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){ + foreach (const NetworkDeviceInfo &networkDeviceInfo, discoveryReply->networkDeviceInfos()) { + + qCDebug(dcHuawei()) << "Found" << networkDeviceInfo; + + // Filter for mac manufacturer + if (!networkDeviceInfo.macAddressManufacturer().contains("Huawei")) + 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); + ParamList params; + params << Param(huaweiInverterThingIpAddressParamTypeId, networkDeviceInfo.address().toString()); + params << Param(huaweiInverterThingMacAddressParamTypeId, networkDeviceInfo.macAddress()); + 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) +{ + 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")); + return; + } + + uint port = thing->paramValue(huaweiInverterThingPortParamTypeId).toUInt(); + quint16 slaveId = thing->paramValue(huaweiInverterThingSlaveIdParamTypeId).toUInt(); + + HuaweiFusionSolar *connection = new HuaweiFusionSolar(hostAddress, port, slaveId, this); + + connect(connection, &HuaweiFusionSolar::connectionStateChanged, this, [this, thing, connection](bool status){ + qCDebug(dcHuawei()) << "Connected changed to" << status << "for" << thing; + if (status) { + // 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); + } + }); + + connect(connection, &HuaweiFusionSolar::inverterActivePowerChanged, this, [thing](float inverterActivePower){ + qCDebug(dcHuawei()) << "Inverter power changed" << inverterActivePower * -1000.0 << "W"; + thing->setStateValue(huaweiInverterCurrentPowerStateTypeId, 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(huaweiInverterTotalEnergyProducedStateTypeId, 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); + } + + if (thing->thingClassId() == huaweiMeterThingClassId) { + // Nothing to do here, we get all information from the inverter connection + info->finish(Thing::ThingErrorNoError); + Thing *parentThing = myThings().findById(thing->parentId()); + if (parentThing) { + thing->setStateValue("connected", parentThing->stateValue("connected").toBool()); + } + + } + + if (thing->thingClassId() == huaweiBatteryThingClassId) { + // Nothing to do here, we get all information from the inverter connection + info->finish(Thing::ThingErrorNoError); + Thing *parentThing = myThings().findById(thing->parentId()); + if (parentThing) { + thing->setStateValue("connected", parentThing->stateValue("connected").toBool()); + } + } +} + +void IntegrationPluginHuawei::postSetupThing(Thing *thing) +{ + if (thing->thingClassId() == huaweiInverterThingClassId) { + if (!m_pluginTimer) { + qCDebug(dcHuawei()) << "Starting plugin timer..."; + m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(5); + connect(m_pluginTimer, &PluginTimer::timeout, this, [this] { + foreach(HuaweiFusionSolar *connection, m_connections) { + if (connection->connected()) { + connection->update(); + } + } + }); + + m_pluginTimer->start(); + } + + // Check if w have to set up a child meter for this inverter connection + if (myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiMeterThingClassId).isEmpty()) { + qCDebug(dcHuawei()) << "Set up huawei meter for" << thing; + emit autoThingsAppeared(ThingDescriptors() << ThingDescriptor(huaweiMeterThingClassId, "Huawei Power Meter", QString(), thing->id())); + } + } +} + +void IntegrationPluginHuawei::thingRemoved(Thing *thing) +{ + if (thing->thingClassId() == huaweiInverterThingClassId && m_connections.contains(thing)) { + m_connections.take(thing)->deleteLater(); + } + + if (myThings().isEmpty() && m_pluginTimer) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); + m_pluginTimer = nullptr; + } +} + +void IntegrationPluginHuawei::executeAction(ThingActionInfo *info) +{ + info->finish(Thing::ThingErrorNoError); +} + + diff --git a/huawei/integrationpluginhuawei.h b/huawei/integrationpluginhuawei.h new file mode 100644 index 0000000..5c05e09 --- /dev/null +++ b/huawei/integrationpluginhuawei.h @@ -0,0 +1,62 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, nymea GmbH +* Contact: contact@nymea.io +* +* This fileDescriptor 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 INTEGRATIONPLUGINHUAWEI_H +#define INTEGRATIONPLUGINHUAWEI_H + +#include "plugintimer.h" +#include "integrations/integrationplugin.h" +#include "huaweifusionsolar.h" + +class IntegrationPluginHuawei: public IntegrationPlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginhuawei.json") + Q_INTERFACES(IntegrationPlugin) + +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_connections; + +}; + +#endif // INTEGRATIONPLUGINHUAWEI_H + diff --git a/huawei/integrationpluginhuawei.json b/huawei/integrationpluginhuawei.json new file mode 100644 index 0000000..22510d2 --- /dev/null +++ b/huawei/integrationpluginhuawei.json @@ -0,0 +1,297 @@ +{ + "name": "Huawei", + "displayName": "Huawei FusionSolar", + "id": "fc3e4509-47f3-4622-9bc4-0a90fe2b6262", + "vendors": [ + { + "name": "huawei", + "displayName": "Huawei", + "id": "f654c99d-a286-4abb-b33e-1a71843d8da0", + "thingClasses": [ + { + "name": "huaweiInverter", + "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", + "displayName": "MAC address", + "type": "QString", + "inputType": "MacAddress", + "defaultValue": "" + }, + { + "id": "55c4ec99-6342-4309-84a8-d1615f19b2e8", + "name":"port", + "displayName": "Port", + "type": "int", + "defaultValue": 502 + }, + { + "id": "aa6e978e-a16b-4722-8330-e706f3c7c21e", + "name":"slaveId", + "displayName": "Slave ID", + "type": "int", + "defaultValue": 1 + } + ], + "stateTypes": [ + { + "id": "a51f0ceb-bd2c-444f-8b39-77cf8a4e1bc6", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "f463f36e-69f9-4614-b690-664ce22d76e0", + "name": "currentPower", + "displayName": "Active power", + "displayNameEvent": "Active power changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "e97fe328-6ca4-4fe4-86f7-fee6e9e406a5", + "name": "totalEnergyProduced", + "displayName": "AC energy", + "displayNameEvent": "AC energy changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0.00 + } + ], + "actionTypes": [ ] + }, + { + "name": "huaweiMeter", + "displayName": "Huawei Meter", + "id": "529c2a19-ca6a-4df2-b56e-3fb2673fa95f", + "createMethods": ["auto"], + "interfaces": [ "energymeter", "connectable"], + "paramTypes": [ + ], + "stateTypes": [ + { + "id": "720ece7a-b0b3-4fa3-9f52-6f23042624a5", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "f480dc82-68e2-44e2-839c-df38b9c10310", + "name": "currentPower", + "displayName": "Total real power", + "displayNameEvent": "Total real power changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0.00 + }, + { + "id": "759554dd-74c5-4836-9792-96e02eb816f0", + "name": "totalEnergyProduced", + "displayName": "AC energy", + "displayNameEvent": "AC energy changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0.00 + }, + { + "id": "2cf8d885-37f7-478f-819e-c4e20f2dbe01", + "name": "totalEnergyConsumed", + "displayName": "Total real energy imported", + "displayNameEvent": "Total real energy imported changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0.00 + }, + { + "id": "af48ff45-11ba-401e-a812-bb1db0896449", + "name": "currentPhaseA", + "displayName": "Phase A current", + "displayNameEvent": "Phase A current changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0.00 + }, + { + "id": "fb5082e4-a2d8-4958-a47d-e80928795ece", + "name": "currentPhaseB", + "displayName": "Phase B current", + "displayNameEvent": "Phase B current changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0.00 + }, + { + "id": "bdd9aa8b-93fe-4b6b-8a31-08e99d85a06c", + "name": "currentPhaseC", + "displayName": "Phase C current", + "displayNameEvent": "Phase C current changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0.00 + }, + { + "id": "ecc03e9b-88b1-424f-a179-66bbdebaaea9", + "name": "currentPowerPhaseA", + "displayName": "Current power phase A", + "displayNameEvent": "Current power phase A changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0.00 + }, + { + "id": "7971cbde-b2ea-4474-b68a-71e040ed3b1d", + "name": "currentPowerPhaseB", + "displayName": "Current power phase B", + "displayNameEvent": "Current power phase B changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0.00 + }, + { + "id": "7ca21c4d-6763-49e4-a056-4c9c76923971", + "name": "currentPowerPhaseC", + "displayName": "Current power phase C", + "displayNameEvent": "Current power phase C changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0.00 + }, + { + "id": "ea5d7924-19a8-415c-aeeb-e04ce08bed33", + "name": "voltagePhaseA", + "displayName": "Voltage phase A", + "displayNameEvent": "Voltage phase A changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0.00 + }, + { + "id": "f15856d1-645f-4d34-89a7-c1585ca329cc", + "name": "voltagePhaseB", + "displayName": "Voltage phase B", + "displayNameEvent": "Voltage phase B changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0.00 + }, + { + "id": "aafb5de4-caa1-4a90-8149-cdf85ae5dc2b", + "name": "voltagePhaseC", + "displayName": "Voltage phase C", + "displayNameEvent": "Voltage phase C changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0.00 + }, + { + "id": "1e2252be-80b3-4e9a-97f7-105d6d1c50f9", + "name": "frequency", + "displayName": "Frequency", + "displayNameEvent": "Frequency changed", + "type": "double", + "unit": "Hertz", + "defaultValue": 0.00 + } + ], + "actionTypes": [ ] + }, + { + "name": "huaweiBattery", + "displayName": "Huawei Battery", + "id": "40104aac-0456-475d-8bd6-18f946597d96", + "createMethods": ["auto"], + "interfaces": [ "energystorage", "connectable"], + "paramTypes": [ + { + "id": "019287a6-c593-45a8-9695-2e1ad8e81c32", + "name":"unit", + "displayName": "Unit", + "type": "uint", + "defaultValue": 0 + } + ], + "stateTypes": [ + { + "id": "917bc284-9d43-430c-a8c3-642d302448e6", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "223ddf60-ff73-4acf-b8ab-6337aeb972e8", + "name": "batteryCritical", + "displayName": "Battery critical", + "displayNameEvent": "Battery critical changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "94d609bf-1f67-47c4-a23d-2fd14e7c0b21", + "name": "batteryLevel", + "displayName": "Battery level", + "displayNameEvent": "Battery level changed", + "type": "int", + "unit": "Percentage", + "minValue": 0, + "maxValue": 100, + "defaultValue": 0 + }, + { + "id": "53ca1f8a-0267-40aa-b563-762a943c8f55", + "name": "currentPower", + "displayName": "Total real power", + "displayNameEvent": "Total real power changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0.00 + }, + { + "id": "3eed974a-0acb-4e38-bcb8-0e3f6fbfd51a", + "name": "capacity", + "displayName": "Capacity", + "displayNameEvent": "Capacity changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0.00 + }, + { + "id": "d9604513-d5a9-463a-ad18-d2f259a7a99d", + "name": "chargingState", + "displayName": "Charging state", + "displayNameEvent": "Charging state changed", + "type": "QString", + "possibleValues": ["idle", "charging", "discharging"], + "defaultValue": "idle" + } + ], + "actionTypes": [ ] + } + ] + } + ] +} + diff --git a/huawei/meta.json b/huawei/meta.json new file mode 100644 index 0000000..b21eb67 --- /dev/null +++ b/huawei/meta.json @@ -0,0 +1,13 @@ +{ + "title": "Huawei FusionSolar", + "tagline": "Connect to Huawei FusionSolar devices.", + "icon": "huawei.png", + "stability": "consumer", + "offline": true, + "technologies": [ + "network" + ], + "categories": [ + "energy" + ] +} diff --git a/huawei/translations/fc3e4509-47f3-4622-9bc4-0a90fe2b6262-en_US.ts b/huawei/translations/fc3e4509-47f3-4622-9bc4-0a90fe2b6262-en_US.ts new file mode 100644 index 0000000..ec98e90 --- /dev/null +++ b/huawei/translations/fc3e4509-47f3-4622-9bc4-0a90fe2b6262-en_US.ts @@ -0,0 +1,401 @@ + + + + + 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 ParamType (ThingClass: huaweiInverter, EventType: totalEnergyProduced, ID: {e97fe328-6ca4-4fe4-86f7-fee6e9e406a5}) +---------- +The name of the StateType ({e97fe328-6ca4-4fe4-86f7-fee6e9e406a5}) of ThingClass huaweiInverter + + + + + + 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 ({f463f36e-69f9-4614-b690-664ce22d76e0}) of ThingClass huaweiInverter + + + + + 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 + + + + + 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 + + + + + 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 + + + + + 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 + + + + + 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 ({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 ({a51f0ceb-bd2c-444f-8b39-77cf8a4e1bc6}) of ThingClass huaweiInverter + + + + + + + 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 + + + + + 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 + + + + + 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 + + + + + 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 + + + + + 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 + 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}) + + + + + + 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 + + + + + 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 + + + + + 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 + + + + + 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}) + + + + + Slave ID + The name of the ParamType (ThingClass: huaweiInverter, Type: thing, ID: {aa6e978e-a16b-4722-8330-e706f3c7c21e}) + + + + + + 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 + + + + + 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 ({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 + + + + + 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 + + + + + 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 + + + + + IntegrationPluginHuawei + + + The network device discovery is not available. + + + + + No IP address given + + + + diff --git a/nymea-plugins-modbus.pro b/nymea-plugins-modbus.pro index d27afcf..c851b8c 100644 --- a/nymea-plugins-modbus.pro +++ b/nymea-plugins-modbus.pro @@ -7,7 +7,8 @@ SUBDIRS += libnymea-sunspec PLUGIN_DIRS = \ alphainnotec \ drexelundweiss \ - energymeters \ + energymeters \ + huawei \ modbuscommander \ mtec \ mypv \