From 89f152060f9f5483623d64d69b14c0bcefa189d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 13 Jan 2022 12:10:16 +0100 Subject: [PATCH 1/7] Add huawei basic structure --- huawei/huawei-registers.json | 216 +++++++++++++++ huawei/huawei.pro | 17 ++ huawei/huaweifusionsolar.cpp | 77 ++++++ huawei/huaweifusionsolar.h | 26 ++ huawei/huaweimodbustcpconnection.cpp | 389 +++++++++++++++++++++++++++ huawei/huaweimodbustcpconnection.h | 161 +++++++++++ huawei/integrationpluginhuawei.cpp | 184 +++++++++++++ huawei/integrationpluginhuawei.h | 62 +++++ huawei/integrationpluginhuawei.json | 370 +++++++++++++++++++++++++ nymea-plugins-modbus.pro | 6 +- 10 files changed, 1507 insertions(+), 1 deletion(-) create mode 100644 huawei/huawei-registers.json create mode 100644 huawei/huawei.pro create mode 100644 huawei/huaweifusionsolar.cpp create mode 100644 huawei/huaweifusionsolar.h create mode 100644 huawei/huaweimodbustcpconnection.cpp create mode 100644 huawei/huaweimodbustcpconnection.h create mode 100644 huawei/integrationpluginhuawei.cpp create mode 100644 huawei/integrationpluginhuawei.h create mode 100644 huawei/integrationpluginhuawei.json diff --git a/huawei/huawei-registers.json b/huawei/huawei-registers.json new file mode 100644 index 0000000..c504fcb --- /dev/null +++ b/huawei/huawei-registers.json @@ -0,0 +1,216 @@ +{ + "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 + } + ] + } + ], + "blocks": [ + ], + "registers": [ + { + "id": "inverterDCPower", + "address": 32064, + "size": 2, + "type": "int32", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Inverter DC power", + "unit": "kW", + "staticScaleFactor": -3, + "defaultValue": "0", + "access": "RO" + }, + { + "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": "powerMeterActivePower", + "address": 37113, + "size": 2, + "type": "int32", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Power meter active power", + "unit": "W", + "defaultValue": "0", + "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": "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.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..23b1be0 --- /dev/null +++ b/huawei/huaweifusionsolar.cpp @@ -0,0 +1,77 @@ +#include "huaweifusionsolar.h" +#include "extern-plugininfo.h" + +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::RegisterInverterDCPower); + m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterInverterActivePower); + m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery1Power); + m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery1Soc); + m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterPowerMeterActivePower); + m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery2Soc); + m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery2Power); + + // Note: since huawei can only process one request at the time, we need to queue the requests + + readNextRegister(); +} + +void HuaweiFusionSolar::readNextRegister() +{ + // Check if currently a reply is pending + if (m_currentRegisterRequest >= 0) + return; + + switch (m_currentRegisterRequest) { + case HuaweiModbusTcpConnection::RegisterInverterDCPower: + // Update registers from Inverter DC power + qCDebug(dcHuawei()) << "--> Read \"Inverter DC power\" register:" << 32064 << "size:" << 2; + QModbusReply *reply = readInverterDCPower(); + 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(dcHuawei()) << "<-- Response from \"Inverter DC power\" register" << 32064 << "size:" << 2 << values; + float receivedInverterDCPower = ModbusDataUtils::convertToInt32(values, ModbusDataUtils::ByteOrderBigEndian) * 1.0 * pow(10, -3); + if (m_inverterDCPower != receivedInverterDCPower) { + m_inverterDCPower = receivedInverterDCPower; + emit inverterDCPowerChanged(m_inverterDCPower); + } + } + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + qCWarning(dcHuawei()) << "Modbus reply error occurred while updating \"Inverter DC 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(dcHuawei()) << "Error occurred while reading \"Inverter DC power\" registers from" << hostAddress().toString() << errorString(); + } + + + break; + } +} diff --git a/huawei/huaweifusionsolar.h b/huawei/huaweifusionsolar.h new file mode 100644 index 0000000..b0e16be --- /dev/null +++ b/huawei/huaweifusionsolar.h @@ -0,0 +1,26 @@ +#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 readNextRegister(); +}; + +#endif // HUAWEIFUSIONSOLAR_H diff --git a/huawei/huaweimodbustcpconnection.cpp b/huawei/huaweimodbustcpconnection.cpp new file mode 100644 index 0000000..fb2b6c5 --- /dev/null +++ b/huawei/huaweimodbustcpconnection.cpp @@ -0,0 +1,389 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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::inverterDCPower() const +{ + return m_inverterDCPower; +} + +float HuaweiModbusTcpConnection::inverterActivePower() const +{ + return m_inverterActivePower; +} + +qint32 HuaweiModbusTcpConnection::powerMeterActivePower() const +{ + return m_powerMeterActivePower; +} + +qint32 HuaweiModbusTcpConnection::lunaBattery1Power() const +{ + return m_lunaBattery1Power; +} + +float HuaweiModbusTcpConnection::lunaBattery1Soc() const +{ + return m_lunaBattery1Soc; +} + +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() +{ + updateInverterDCPower(); + updateInverterActivePower(); + updatePowerMeterActivePower(); + updateLunaBattery1Power(); + updateLunaBattery1Soc(); + updateLunaBattery2Power(); + updateLunaBattery2Soc(); +} + +void HuaweiModbusTcpConnection::updateInverterDCPower() +{ + // Update registers from Inverter DC power + qCDebug(dcHuaweiModbusTcpConnection()) << "--> Read \"Inverter DC power\" register:" << 32064 << "size:" << 2; + QModbusReply *reply = readInverterDCPower(); + 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 DC power\" register" << 32064 << "size:" << 2 << values; + float receivedInverterDCPower = ModbusDataUtils::convertToInt32(values, ModbusDataUtils::ByteOrderBigEndian) * 1.0 * pow(10, -3); + if (m_inverterDCPower != receivedInverterDCPower) { + m_inverterDCPower = receivedInverterDCPower; + emit inverterDCPowerChanged(m_inverterDCPower); + } + } + }); + + connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ + qCWarning(dcHuaweiModbusTcpConnection()) << "Modbus reply error occurred while updating \"Inverter DC 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 DC power\" registers from" << hostAddress().toString() << errorString(); + } +} + +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::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::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::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::readInverterDCPower() +{ + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, 32064, 2); + return sendReadRequest(request, m_slaveId); +} + +QModbusReply *HuaweiModbusTcpConnection::readInverterActivePower() +{ + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, 32080, 2); + return sendReadRequest(request, m_slaveId); +} + +QModbusReply *HuaweiModbusTcpConnection::readPowerMeterActivePower() +{ + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, 37113, 2); + 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::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 DC power:" << huaweiModbusTcpConnection->inverterDCPower() << " [kW]" << "\n"; + debug.nospace().noquote() << " - Inverter active power:" << huaweiModbusTcpConnection->inverterActivePower() << " [kW]" << "\n"; + debug.nospace().noquote() << " - Power meter active power:" << huaweiModbusTcpConnection->powerMeterActivePower() << " [W]" << "\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 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..8a21117 --- /dev/null +++ b/huawei/huaweimodbustcpconnection.h @@ -0,0 +1,161 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 { + RegisterInverterDCPower = 32064, + RegisterInverterActivePower = 32080, + RegisterLunaBattery1Power = 37001, + RegisterLunaBattery1Soc = 37004, + RegisterPowerMeterActivePower = 37113, + RegisterLunaBattery2Soc = 37738, + 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) + + explicit HuaweiModbusTcpConnection(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent = nullptr); + ~HuaweiModbusTcpConnection() = default; + + /* Inverter DC power [kW] - Address: 32064, Size: 2 */ + float inverterDCPower() const; + + /* Inverter active power [kW] - Address: 32080, Size: 2 */ + float inverterActivePower() const; + + /* Power meter active power [W] - Address: 37113, Size: 2 */ + qint32 powerMeterActivePower() 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 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 updateInverterDCPower(); + void updateInverterActivePower(); + void updatePowerMeterActivePower(); + void updateLunaBattery1Power(); + void updateLunaBattery1Soc(); + void updateLunaBattery2Power(); + void updateLunaBattery2Soc(); + +signals: + void initializationFinished(); + + void inverterDCPowerChanged(float inverterDCPower); + void inverterActivePowerChanged(float inverterActivePower); + void powerMeterActivePowerChanged(qint32 powerMeterActivePower); + void lunaBattery1PowerChanged(qint32 lunaBattery1Power); + void lunaBattery1SocChanged(float lunaBattery1Soc); + void lunaBattery2PowerChanged(qint32 lunaBattery2Power); + void lunaBattery2SocChanged(float lunaBattery2Soc); + +protected: + QModbusReply *readInverterDCPower(); + QModbusReply *readInverterActivePower(); + QModbusReply *readPowerMeterActivePower(); + QModbusReply *readLunaBattery1Power(); + QModbusReply *readLunaBattery1Soc(); + QModbusReply *readLunaBattery2Power(); + QModbusReply *readLunaBattery2Soc(); + +private: + quint16 m_slaveId = 1; + QVector m_pendingInitReplies; + + float m_inverterDCPower = 0; + float m_inverterActivePower = 0; + qint32 m_powerMeterActivePower = 0; + qint32 m_lunaBattery1Power = 0; + float m_lunaBattery1Soc = 0; + qint32 m_lunaBattery2Power = 0; + float m_lunaBattery2Soc = 0; + + 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..a362181 --- /dev/null +++ b/huawei/integrationpluginhuawei.cpp @@ -0,0 +1,184 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "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 = networkDeviceInfo.address().toString(); + } 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(); + + HuaweiModbusTcpConnection *connection = new HuaweiModbusTcpConnection(hostAddress, port, slaveId, this); + connect(connection, &HuaweiModbusTcpConnection::initializationFinished, this, [this, thing, connection, info]{ + qCDebug(dcHuawei()) << "Connection init" << connection; + + // FIXME: check if success + + m_connections.insert(thing, connection); + info->finish(Thing::ThingErrorNoError); + + // Set connected true + thing->setStateValue(huaweiInverterConnectedStateTypeId, true); + + // Update registers + connection->update(); + }); + + connect(connection, &HuaweiModbusTcpConnection::connectionStateChanged, 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(); + } else { + thing->setStateValue(huaweiInverterConnectedStateTypeId, false); + + } + }); + + connection->connectDevice(); + + // FIXME: make async and check if this is really a huawei + info->finish(Thing::ThingErrorNoError); + } +} + +void IntegrationPluginHuawei::postSetupThing(Thing *thing) +{ + Q_UNUSED(thing) + if (thing->thingClassId() == huaweiInverterThingClassId) { + if (!m_pluginTimer) { + qCDebug(dcHuawei()) << "Starting plugin timer..."; + m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(10); + connect(m_pluginTimer, &PluginTimer::timeout, this, [this] { + foreach(HuaweiModbusTcpConnection *connection, m_connections) { + if (connection->connected()) { + connection->update(); + } + } + }); + } + } +} + +void IntegrationPluginHuawei::thingRemoved(Thing *thing) +{ + if (thing->thingClassId() == huaweiInverterThingClassId && m_connections.contains(thing)) { + HuaweiModbusTcpConnection *connection = m_connections.take(thing); + delete connection; + } + + 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..5668d76 --- /dev/null +++ b/huawei/integrationpluginhuawei.h @@ -0,0 +1,62 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef INTEGRATIONPLUGINHUAWEI_H +#define INTEGRATIONPLUGINHUAWEI_H + +#include "plugintimer.h" +#include "integrations/integrationplugin.h" +#include "huaweimodbustcpconnection.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..f3ee39b --- /dev/null +++ b/huawei/integrationpluginhuawei.json @@ -0,0 +1,370 @@ +{ + "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"], + "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 + }, + { + "id": "6609a589-8f0f-4747-9240-c0d0e0d87f29", + "name": "phaseACurrent", + "displayName": "Phase A current", + "displayNameEvent": "Phase A current changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0.00 + }, + { + "id": "1bff8853-ec1e-4fe8-b148-c04d1db67e74", + "name": "phaseBCurrent", + "displayName": "Phase B current", + "displayNameEvent": "Phase B current changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0.00 + }, + { + "id": "0977c3bc-7798-4161-a75f-19858d44c463", + "name": "phaseCCurrent", + "displayName": "Phase C current", + "displayNameEvent": "Phase C current changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0.00 + }, + { + "id": "e91281b3-d89f-4d3e-b81e-96645a8de63f", + "name": "voltagePhaseA", + "displayName": "Voltage phase A", + "displayNameEvent": "Voltage phase A changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0.00 + }, + { + "id": "ee921f23-ceeb-4818-adca-8c2fa1e75b17", + "name": "voltagePhaseB", + "displayName": "Voltage phase B", + "displayNameEvent": "Voltage phase B changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0.00 + }, + { + "id": "5613cea9-11cb-4b81-9b61-0e9a7ec737a3", + "name": "voltagePhaseC", + "displayName": "Voltage phase C", + "displayNameEvent": "Voltage phase C changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0.00 + }, + { + "id": "4db7c66c-7676-449d-94c5-1b8c0ddf368a", + "name": "currentPowerPhaseA", + "displayName": "Current power phase A", + "displayNameEvent": "Current power phase A changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0.00 + }, + { + "id": "59026351-1cea-45e8-bffe-9ab65b44ddb2", + "name": "currentPowerPhaseB", + "displayName": "Current power phase B", + "displayNameEvent": "Current power phase B changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0.00 + }, + { + "id": "89ccd9d7-fd26-431a-b198-5001564373dc", + "name": "currentPowerPhaseC", + "displayName": "Current power phase C", + "displayNameEvent": "Current power phase C changed", + "type": "double", + "unit": "Watt", + "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": [ + ], + "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/nymea-plugins-modbus.pro b/nymea-plugins-modbus.pro index d27afcf..74ab19b 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 \ @@ -46,6 +47,9 @@ QMAKE_EXTRA_TARGETS += lrelease # For Qt-Creator's code model: Add CPATH to INCLUDEPATH explicitly INCLUDEPATH += $$(CPATH) +CONFIG += selection +PLUGINS=huawei + # Verify if building only a selection of plugins contains(CONFIG, selection) { # Check each plugin if the subdir exists From cb8fdb01058a58c7c67e01182fb61577c7032d41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Fri, 28 Jan 2022 15:23:32 +0100 Subject: [PATCH 2/7] Finish first implementation of inverter, meter and battery --- debian/changelog | 8 + huawei/huawei-registers.json | 87 +++++- huawei/huaweifusionsolar.cpp | 389 +++++++++++++++++++++++++-- huawei/huaweifusionsolar.h | 7 + huawei/huaweimodbustcpconnection.cpp | 226 ++++++++++++---- huawei/huaweimodbustcpconnection.h | 63 +++-- huawei/integrationpluginhuawei.cpp | 182 +++++++++++-- huawei/integrationpluginhuawei.h | 4 +- huawei/integrationpluginhuawei.json | 89 +----- 9 files changed, 858 insertions(+), 197 deletions(-) diff --git a/debian/changelog b/debian/changelog index e975cce..a07aa97 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +nymea-plugins-modbus (0.30.0+202201281421~c194a9f~buster+202201281425~2c664e6~buster) UNRELEASED; urgency=medium + + + [ ] + * New Package build + + -- Fri, 28 Jan 2022 14:25:13 +0000 + nymea-plugins-modbus (0.30.0) xenial; urgency=medium [ Michael Zanetti ] diff --git a/huawei/huawei-registers.json b/huawei/huawei-registers.json index c504fcb..0e559d1 100644 --- a/huawei/huawei-registers.json +++ b/huawei/huawei-registers.json @@ -118,24 +118,36 @@ "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": "inverterDCPower", - "address": 32064, - "size": 2, - "type": "int32", - "registerType": "holdingRegister", - "readSchedule": "update", - "description": "Inverter DC power", - "unit": "kW", - "staticScaleFactor": -3, - "defaultValue": "0", - "access": "RO" - }, { "id": "inverterActivePower", "address": 32080, @@ -149,6 +161,31 @@ "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, @@ -161,6 +198,18 @@ "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, @@ -186,6 +235,18 @@ "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, diff --git a/huawei/huaweifusionsolar.cpp b/huawei/huaweifusionsolar.cpp index 23b1be0..b32867f 100644 --- a/huawei/huaweifusionsolar.cpp +++ b/huawei/huaweifusionsolar.cpp @@ -1,5 +1,8 @@ #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) @@ -20,16 +23,24 @@ void HuaweiFusionSolar::update() return; // Add the requests - m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterInverterDCPower); m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterInverterActivePower); - m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery1Power); - m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery1Soc); + 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::RegisterLunaBattery2Soc); - m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery2Power); + 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(); } @@ -39,11 +50,16 @@ void HuaweiFusionSolar::readNextRegister() 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::RegisterInverterDCPower: - // Update registers from Inverter DC power - qCDebug(dcHuawei()) << "--> Read \"Inverter DC power\" register:" << 32064 << "size:" << 2; - QModbusReply *reply = readInverterDCPower(); + 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); @@ -51,27 +67,364 @@ void HuaweiFusionSolar::readNextRegister() if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); const QVector values = unit.values(); - qCDebug(dcHuawei()) << "<-- Response from \"Inverter DC power\" register" << 32064 << "size:" << 2 << values; - float receivedInverterDCPower = ModbusDataUtils::convertToInt32(values, ModbusDataUtils::ByteOrderBigEndian) * 1.0 * pow(10, -3); - if (m_inverterDCPower != receivedInverterDCPower) { - m_inverterDCPower = receivedInverterDCPower; - emit inverterDCPowerChanged(m_inverterDCPower); + 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(dcHuawei()) << "Modbus reply error occurred while updating \"Inverter DC power\" registers from" << hostAddress().toString() << error << reply->errorString(); + 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(dcHuawei()) << "Error occurred while reading \"Inverter DC power\" registers from" << hostAddress().toString() << errorString(); + 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 index b0e16be..98e38d4 100644 --- a/huawei/huaweifusionsolar.h +++ b/huawei/huaweifusionsolar.h @@ -15,12 +15,19 @@ public: 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 index fb2b6c5..cca8e02 100644 --- a/huawei/huaweimodbustcpconnection.cpp +++ b/huawei/huaweimodbustcpconnection.cpp @@ -41,21 +41,31 @@ HuaweiModbusTcpConnection::HuaweiModbusTcpConnection(const QHostAddress &hostAdd } -float HuaweiModbusTcpConnection::inverterDCPower() const -{ - return m_inverterDCPower; -} - 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; @@ -66,6 +76,11 @@ float HuaweiModbusTcpConnection::lunaBattery1Soc() const return m_lunaBattery1Soc; } +HuaweiModbusTcpConnection::BatteryDeviceStatus HuaweiModbusTcpConnection::lunaBattery2Status() const +{ + return m_lunaBattery2Status; +} + qint32 HuaweiModbusTcpConnection::lunaBattery2Power() const { return m_lunaBattery2Power; @@ -84,48 +99,18 @@ void HuaweiModbusTcpConnection::initialize() void HuaweiModbusTcpConnection::update() { - updateInverterDCPower(); updateInverterActivePower(); + updateInverterDeviceStatus(); + updateInverterEnergyProduced(); updatePowerMeterActivePower(); + updateLunaBattery1Status(); updateLunaBattery1Power(); updateLunaBattery1Soc(); + updateLunaBattery2Status(); updateLunaBattery2Power(); updateLunaBattery2Soc(); } -void HuaweiModbusTcpConnection::updateInverterDCPower() -{ - // Update registers from Inverter DC power - qCDebug(dcHuaweiModbusTcpConnection()) << "--> Read \"Inverter DC power\" register:" << 32064 << "size:" << 2; - QModbusReply *reply = readInverterDCPower(); - 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 DC power\" register" << 32064 << "size:" << 2 << values; - float receivedInverterDCPower = ModbusDataUtils::convertToInt32(values, ModbusDataUtils::ByteOrderBigEndian) * 1.0 * pow(10, -3); - if (m_inverterDCPower != receivedInverterDCPower) { - m_inverterDCPower = receivedInverterDCPower; - emit inverterDCPowerChanged(m_inverterDCPower); - } - } - }); - - connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){ - qCWarning(dcHuaweiModbusTcpConnection()) << "Modbus reply error occurred while updating \"Inverter DC 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 DC power\" registers from" << hostAddress().toString() << errorString(); - } -} - void HuaweiModbusTcpConnection::updateInverterActivePower() { // Update registers from Inverter active power @@ -159,6 +144,72 @@ void HuaweiModbusTcpConnection::updateInverterActivePower() } } +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 @@ -192,6 +243,37 @@ void HuaweiModbusTcpConnection::updatePowerMeterActivePower() } } +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 @@ -258,6 +340,37 @@ void HuaweiModbusTcpConnection::updateLunaBattery1Soc() } } +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 @@ -324,24 +437,36 @@ void HuaweiModbusTcpConnection::updateLunaBattery2Soc() } } -QModbusReply *HuaweiModbusTcpConnection::readInverterDCPower() -{ - QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, 32064, 2); - return sendReadRequest(request, m_slaveId); -} - 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); @@ -354,6 +479,12 @@ QModbusReply *HuaweiModbusTcpConnection::readLunaBattery1Soc() 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); @@ -377,11 +508,14 @@ void HuaweiModbusTcpConnection::verifyInitFinished() QDebug operator<<(QDebug debug, HuaweiModbusTcpConnection *huaweiModbusTcpConnection) { debug.nospace().noquote() << "HuaweiModbusTcpConnection(" << huaweiModbusTcpConnection->hostAddress().toString() << ":" << huaweiModbusTcpConnection->port() << ")" << "\n"; - debug.nospace().noquote() << " - Inverter DC power:" << huaweiModbusTcpConnection->inverterDCPower() << " [kW]" << "\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 index 8a21117..40ce1b3 100644 --- a/huawei/huaweimodbustcpconnection.h +++ b/huawei/huaweimodbustcpconnection.h @@ -41,12 +41,15 @@ class HuaweiModbusTcpConnection : public ModbusTCPMaster Q_OBJECT public: enum Registers { - RegisterInverterDCPower = 32064, RegisterInverterActivePower = 32080, + RegisterInverterDeviceStatus = 32089, + RegisterInverterEnergyProduced = 32106, + RegisterLunaBattery1Status = 37000, RegisterLunaBattery1Power = 37001, RegisterLunaBattery1Soc = 37004, RegisterPowerMeterActivePower = 37113, RegisterLunaBattery2Soc = 37738, + RegisterLunaBattery2Status = 37741, RegisterLunaBattery2Power = 37743 }; Q_ENUM(Registers) @@ -83,24 +86,42 @@ public: }; 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 DC power [kW] - Address: 32064, Size: 2 */ - float inverterDCPower() const; - /* 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; @@ -111,46 +132,58 @@ public: virtual void initialize(); virtual void update(); - void updateInverterDCPower(); void updateInverterActivePower(); + void updateInverterDeviceStatus(); + void updateInverterEnergyProduced(); void updatePowerMeterActivePower(); + void updateLunaBattery1Status(); void updateLunaBattery1Power(); void updateLunaBattery1Soc(); + void updateLunaBattery2Status(); void updateLunaBattery2Power(); void updateLunaBattery2Soc(); signals: void initializationFinished(); - void inverterDCPowerChanged(float inverterDCPower); 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 *readInverterDCPower(); 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; - float m_inverterDCPower = 0; - float m_inverterActivePower = 0; - qint32 m_powerMeterActivePower = 0; - qint32 m_lunaBattery1Power = 0; - float m_lunaBattery1Soc = 0; - qint32 m_lunaBattery2Power = 0; - float m_lunaBattery2Soc = 0; - void verifyInitFinished(); diff --git a/huawei/integrationpluginhuawei.cpp b/huawei/integrationpluginhuawei.cpp index a362181..5fe8ba7 100644 --- a/huawei/integrationpluginhuawei.cpp +++ b/huawei/integrationpluginhuawei.cpp @@ -59,7 +59,7 @@ void IntegrationPluginHuawei::discoverThings(ThingDiscoveryInfo *info) QString title; if (networkDeviceInfo.hostName().isEmpty()) { - title = networkDeviceInfo.address().toString(); + title = "Huawei FusionSolar"; } else { title = networkDeviceInfo.hostName() + " (" + networkDeviceInfo.address().toString() + ")"; } @@ -111,54 +111,193 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info) uint port = thing->paramValue(huaweiInverterThingPortParamTypeId).toUInt(); quint16 slaveId = thing->paramValue(huaweiInverterThingSlaveIdParamTypeId).toUInt(); - HuaweiModbusTcpConnection *connection = new HuaweiModbusTcpConnection(hostAddress, port, slaveId, this); - connect(connection, &HuaweiModbusTcpConnection::initializationFinished, this, [this, thing, connection, info]{ - qCDebug(dcHuawei()) << "Connection init" << connection; + HuaweiFusionSolar *connection = new HuaweiFusionSolar(hostAddress, port, slaveId, this); - // FIXME: check if success - - m_connections.insert(thing, connection); - info->finish(Thing::ThingErrorNoError); - - // Set connected true - thing->setStateValue(huaweiInverterConnectedStateTypeId, true); - - // Update registers - connection->update(); - }); - - connect(connection, &HuaweiModbusTcpConnection::connectionStateChanged, this, [thing, connection](bool status){ + 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) { - Q_UNUSED(thing) if (thing->thingClassId() == huaweiInverterThingClassId) { if (!m_pluginTimer) { qCDebug(dcHuawei()) << "Starting plugin timer..."; - m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(10); + m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(5); connect(m_pluginTimer, &PluginTimer::timeout, this, [this] { - foreach(HuaweiModbusTcpConnection *connection, m_connections) { + 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())); } } } @@ -166,8 +305,7 @@ void IntegrationPluginHuawei::postSetupThing(Thing *thing) void IntegrationPluginHuawei::thingRemoved(Thing *thing) { if (thing->thingClassId() == huaweiInverterThingClassId && m_connections.contains(thing)) { - HuaweiModbusTcpConnection *connection = m_connections.take(thing); - delete connection; + m_connections.take(thing)->deleteLater(); } if (myThings().isEmpty() && m_pluginTimer) { diff --git a/huawei/integrationpluginhuawei.h b/huawei/integrationpluginhuawei.h index 5668d76..83e1915 100644 --- a/huawei/integrationpluginhuawei.h +++ b/huawei/integrationpluginhuawei.h @@ -33,7 +33,7 @@ #include "plugintimer.h" #include "integrations/integrationplugin.h" -#include "huaweimodbustcpconnection.h" +#include "huaweifusionsolar.h" class IntegrationPluginHuawei: public IntegrationPlugin { @@ -54,7 +54,7 @@ public: private: PluginTimer *m_pluginTimer = nullptr; - QHash m_connections; + QHash m_connections; }; diff --git a/huawei/integrationpluginhuawei.json b/huawei/integrationpluginhuawei.json index f3ee39b..22510d2 100644 --- a/huawei/integrationpluginhuawei.json +++ b/huawei/integrationpluginhuawei.json @@ -14,6 +14,7 @@ "id": "87e75ee0-d544-457b-add3-bd4e58160fcd", "createMethods": ["discovery", "user"], "interfaces": ["solarinverter", "connectable"], + "providedInterfaces": [ "solarinverter", "energymeter", "energystorage"], "paramTypes": [ { "id": "d93371db-0954-4dcd-a1a5-6881b78cb0ea", @@ -73,87 +74,6 @@ "type": "double", "unit": "KiloWattHour", "defaultValue": 0.00 - }, - { - "id": "6609a589-8f0f-4747-9240-c0d0e0d87f29", - "name": "phaseACurrent", - "displayName": "Phase A current", - "displayNameEvent": "Phase A current changed", - "type": "double", - "unit": "Ampere", - "defaultValue": 0.00 - }, - { - "id": "1bff8853-ec1e-4fe8-b148-c04d1db67e74", - "name": "phaseBCurrent", - "displayName": "Phase B current", - "displayNameEvent": "Phase B current changed", - "type": "double", - "unit": "Ampere", - "defaultValue": 0.00 - }, - { - "id": "0977c3bc-7798-4161-a75f-19858d44c463", - "name": "phaseCCurrent", - "displayName": "Phase C current", - "displayNameEvent": "Phase C current changed", - "type": "double", - "unit": "Ampere", - "defaultValue": 0.00 - }, - { - "id": "e91281b3-d89f-4d3e-b81e-96645a8de63f", - "name": "voltagePhaseA", - "displayName": "Voltage phase A", - "displayNameEvent": "Voltage phase A changed", - "type": "double", - "unit": "Volt", - "defaultValue": 0.00 - }, - { - "id": "ee921f23-ceeb-4818-adca-8c2fa1e75b17", - "name": "voltagePhaseB", - "displayName": "Voltage phase B", - "displayNameEvent": "Voltage phase B changed", - "type": "double", - "unit": "Volt", - "defaultValue": 0.00 - }, - { - "id": "5613cea9-11cb-4b81-9b61-0e9a7ec737a3", - "name": "voltagePhaseC", - "displayName": "Voltage phase C", - "displayNameEvent": "Voltage phase C changed", - "type": "double", - "unit": "Volt", - "defaultValue": 0.00 - }, - { - "id": "4db7c66c-7676-449d-94c5-1b8c0ddf368a", - "name": "currentPowerPhaseA", - "displayName": "Current power phase A", - "displayNameEvent": "Current power phase A changed", - "type": "double", - "unit": "Watt", - "defaultValue": 0.00 - }, - { - "id": "59026351-1cea-45e8-bffe-9ab65b44ddb2", - "name": "currentPowerPhaseB", - "displayName": "Current power phase B", - "displayNameEvent": "Current power phase B changed", - "type": "double", - "unit": "Watt", - "defaultValue": 0.00 - }, - { - "id": "89ccd9d7-fd26-431a-b198-5001564373dc", - "name": "currentPowerPhaseC", - "displayName": "Current power phase C", - "displayNameEvent": "Current power phase C changed", - "type": "double", - "unit": "Watt", - "defaultValue": 0.00 } ], "actionTypes": [ ] @@ -303,6 +223,13 @@ "createMethods": ["auto"], "interfaces": [ "energystorage", "connectable"], "paramTypes": [ + { + "id": "019287a6-c593-45a8-9695-2e1ad8e81c32", + "name":"unit", + "displayName": "Unit", + "type": "uint", + "defaultValue": 0 + } ], "stateTypes": [ { From 8f6459ae79975ee55b834384c62a4b90f9b3a5f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Fri, 28 Jan 2022 15:42:50 +0100 Subject: [PATCH 3/7] Add translations --- ...e4509-47f3-4622-9bc4-0a90fe2b6262-en_US.ts | 401 ++++++++++++++++++ 1 file changed, 401 insertions(+) create mode 100644 huawei/translations/fc3e4509-47f3-4622-9bc4-0a90fe2b6262-en_US.ts 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 + + + + From 4525fc5c95fdfd0c6961bca5561f377d5f677d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Fri, 28 Jan 2022 15:48:40 +0100 Subject: [PATCH 4/7] Add documentation --- huawei/README.md | 13 +++++++++++++ huawei/huawei.png | Bin 0 -> 13545 bytes huawei/meta.json | 13 +++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 huawei/README.md create mode 100644 huawei/huawei.png create mode 100644 huawei/meta.json 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.png b/huawei/huawei.png new file mode 100644 index 0000000000000000000000000000000000000000..f8466329ea7be2642a262d182938839af7b76da6 GIT binary patch literal 13545 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 Date: Fri, 28 Jan 2022 15:52:11 +0100 Subject: [PATCH 5/7] Add debian packaging --- debian/control | 10 ++++++++++ debian/nymea-plugin-huawei.install.in | 2 ++ 2 files changed, 12 insertions(+) create mode 100644 debian/nymea-plugin-huawei.install.in diff --git a/debian/control b/debian/control index e11bcbf..ba80600 100644 --- a/debian/control +++ b/debian/control @@ -111,6 +111,16 @@ Description: nymea.io plugin for Modbus based energy meters This package will install the nymea.io plugin for energy meters +Package: nymea-plugin-huawei +Architecture: any +Multi-Arch: same +Section: libs +Depends: ${shlibs:Depends}, + ${misc:Depends} +Description: nymea.io plugin for Huawei FusionSolar + This package will install the nymea.io plugin for Huawei FusionSolar based on Modbus TCP + + Package: nymea-plugin-modbuscommander Architecture: any Section: libs 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/ From 9a35aa937930f695736654f86e4999573f1f5ebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 31 Jan 2022 10:12:19 +0100 Subject: [PATCH 6/7] Remove debug stuff and fix license headers --- debian/changelog | 7 ------- huawei/huaweifusionsolar.cpp | 30 ++++++++++++++++++++++++++++++ huawei/huaweifusionsolar.h | 30 ++++++++++++++++++++++++++++++ huawei/integrationpluginhuawei.cpp | 4 ++-- huawei/integrationpluginhuawei.h | 4 ++-- nymea-plugins-modbus.pro | 3 --- 6 files changed, 64 insertions(+), 14 deletions(-) diff --git a/debian/changelog b/debian/changelog index a07aa97..45d10bf 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,10 +1,3 @@ -nymea-plugins-modbus (0.30.0+202201281421~c194a9f~buster+202201281425~2c664e6~buster) UNRELEASED; urgency=medium - - - [ ] - * New Package build - - -- Fri, 28 Jan 2022 14:25:13 +0000 nymea-plugins-modbus (0.30.0) xenial; urgency=medium diff --git a/huawei/huaweifusionsolar.cpp b/huawei/huaweifusionsolar.cpp index b32867f..3374c2a 100644 --- a/huawei/huaweifusionsolar.cpp +++ b/huawei/huaweifusionsolar.cpp @@ -1,3 +1,33 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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" diff --git a/huawei/huaweifusionsolar.h b/huawei/huaweifusionsolar.h index 98e38d4..a208fbb 100644 --- a/huawei/huaweifusionsolar.h +++ b/huawei/huaweifusionsolar.h @@ -1,3 +1,33 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 diff --git a/huawei/integrationpluginhuawei.cpp b/huawei/integrationpluginhuawei.cpp index 5fe8ba7..fcdfee7 100644 --- a/huawei/integrationpluginhuawei.cpp +++ b/huawei/integrationpluginhuawei.cpp @@ -1,9 +1,9 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2021, nymea GmbH +* Copyright 2013 - 2022, nymea GmbH * Contact: contact@nymea.io * -* This file is part of nymea. +* 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 diff --git a/huawei/integrationpluginhuawei.h b/huawei/integrationpluginhuawei.h index 83e1915..5c05e09 100644 --- a/huawei/integrationpluginhuawei.h +++ b/huawei/integrationpluginhuawei.h @@ -1,9 +1,9 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2020, nymea GmbH +* Copyright 2013 - 2022, nymea GmbH * Contact: contact@nymea.io * -* This file is part of nymea. +* 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 diff --git a/nymea-plugins-modbus.pro b/nymea-plugins-modbus.pro index 74ab19b..c851b8c 100644 --- a/nymea-plugins-modbus.pro +++ b/nymea-plugins-modbus.pro @@ -47,9 +47,6 @@ QMAKE_EXTRA_TARGETS += lrelease # For Qt-Creator's code model: Add CPATH to INCLUDEPATH explicitly INCLUDEPATH += $$(CPATH) -CONFIG += selection -PLUGINS=huawei - # Verify if building only a selection of plugins contains(CONFIG, selection) { # Check each plugin if the subdir exists From abb7dd86ba72d7444c60df75319790929befd00d Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Thu, 27 Jan 2022 15:19:59 +0100 Subject: [PATCH 7/7] Packaging: Split translations and consolidate package descriptions --- debian/control | 161 ++++-------------- debian/nymea-plugin-alphainnotec.install.in | 1 + debian/nymea-plugin-drexelundweiss.install.in | 1 + debian/nymea-plugin-energymeters.install.in | 1 + debian/nymea-plugin-idm.install.in | 1 + .../nymea-plugin-modbuscommander.install.in | 1 + debian/nymea-plugin-mypv.install.in | 1 + debian/nymea-plugin-sunspec.install.in | 1 + debian/nymea-plugin-unipi2.install.in | 1 + debian/nymea-plugin-wallbe.install.in | 1 + debian/nymea-plugin-webasto.install.in | 1 + 11 files changed, 41 insertions(+), 130 deletions(-) diff --git a/debian/control b/debian/control index ba80600..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,8 @@ 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 @@ -117,8 +77,9 @@ Multi-Arch: same Section: libs Depends: ${shlibs:Depends}, ${misc:Depends} -Description: nymea.io plugin for Huawei FusionSolar - This package will install the nymea.io plugin for Huawei FusionSolar based on Modbus TCP +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 @@ -126,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 @@ -142,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 @@ -158,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 @@ -191,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 @@ -208,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-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/