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