Adding Huawei modbus RTU functionality and update SmartDongle connection and discovery

This commit is contained in:
Simon Stürz 2022-07-26 15:48:15 +02:00
parent 2a19b033ab
commit 674752e3f4
11 changed files with 1525 additions and 366 deletions

View File

@ -1,12 +1,39 @@
# Huawei FusionSolar
Connects to a Huawei FusionSolar using Modbus TCP.
Connects to a Huawei FusionSolar using Modbus RTU or TCP.
## Supported things
## Huawei FusionSolar
* Inverter
* Meter (only current power)
* Battery (on Unit 1 and 2)
In order to communicate with the Huawei FusionSolar inverter, a working communikation must be provided. This can be done with a Huawei SmartDongle or with a direct modbus RTU connection.
Once nymea has connected successfully to the inverter, following devices will be supported:
* Huawei FusionSolar Inverter (all model supported by the SmartDongle)
* Huawei Meter (connected internally to the Inverter)
* Luna2000 battery units 1 and 2 if connected
### Huawei SmartDongle
The [SmartDongle](https://solar.huawei.com/-/media/Solar/attachment/pdf/apac/datasheet/SmartDongle-WLAN-FE.pdf) can be used to communicate
with the Huawei Solar Inverter. In order to allow nymea to read from the device please make sure following requirements are met:
* SmartDongle software version must be at least `SP130`.
* Connect the Huawei SmartDongle to the network
* Use the official FusonSolar App to enable Modbus TCP access for the Dongle (full access). You can find more informations [here](https://forum.huawei.com/enterprise/en/modbus-tcp-guide/thread/789585-100027?page=1#comments-area).
You can also contact the [official Huawei support](mailto:eu_inverter_support@huawei.com) in order to get the update files and instructions, or get it from [here](https://support.huawei.com/enterprise/en/digital-power/sdongle-pid-23826585/software).
> The SmartDongle provides only access to the registers specified in the Huawei `openAPI`. Full modbus register access requires a modbus RTU connction.
### Direct modbus RTU connection (RS485).
If you want to communicate directly with the inverter using modbus RTU, you need to wire up the connection correctly and set up the Modbus RTU interface in the nymea settings using the right configuations. Then you can discover the Modbus RTU interface while adding the Huawei Modbus RTU inverter in nymea.
The Modbus RTU offers a full access to all modbus registers available on the inverter.
Please contact your installer how to enable a modbus RTU connection for your setup.
## More
https://solar.huawei.com/eu/

View File

@ -0,0 +1,280 @@
{
"className": "HuaweiFusion",
"protocol": "TCP",
"endianness": "BigEndian",
"errorLimitUntilNotReachable": 15,
"checkReachableRegister": "inverterActivePower",
"enums": [
{
"name": "InverterDeviceStatus",
"values": [
{
"key": "StandbyInitializing",
"value": 0
},
{
"key": "StandbyDetectingInsulationResistance",
"value": 1
},
{
"key": "StandbyDetectingIrradiation",
"value": 2
},
{
"key": "StandbyDridDetecting",
"value": 3
},
{
"key": "Starting",
"value": 256
},
{
"key": "OnGrid",
"value": 512
},
{
"key": "PowerLimited",
"value": 513
},
{
"key": "SelfDerating",
"value": 514
},
{
"key": "ShutdownFault",
"value": 768
},
{
"key": "ShutdownCommand",
"value": 769
},
{
"key": "ShutdownOVGR",
"value": 770
},
{
"key": "ShutdownCommunicationDisconnected",
"value": 771
},
{
"key": "ShutdownPowerLimit",
"value": 772
},
{
"key": "ShutdownManualStartupRequired",
"value": 773
},
{
"key": "ShutdownInputUnderpower",
"value": 774
},
{
"key": "GridSchedulingPCurve",
"value": 1025
},
{
"key": "GridSchedulingQUCurve",
"value": 1026
},
{
"key": "GridSchedulingPFUCurve",
"value": 1027
},
{
"key": "GridSchedulingDryContact",
"value": 1028
},
{
"key": "GridSchedulingQPCurve",
"value": 1029
},
{
"key": "SpotCheckReady",
"value": 1280
},
{
"key": "SpotChecking",
"value": 1281
},
{
"key": "Inspecting",
"value": 1536
},
{
"key": "AfciSelfCheck",
"value": 1792
},
{
"key": "IVScanning",
"value": 2048
},
{
"key": "DCInputDetection",
"value": 2304
},
{
"key": "RunningOffGridCharging",
"value": 2560
},
{
"key": "StandbyNoIrradiation",
"value": 40960
}
]
},
{
"name": "BatteryDeviceStatus",
"values": [
{
"key": "Offline",
"value": 0
},
{
"key": "Standby",
"value": 1
},
{
"key": "Running",
"value": 1
},
{
"key": "Fault",
"value": 1
},
{
"key": "SleepMode",
"value": 1
}
]
}
],
"blocks": [
],
"registers": [
{
"id": "inverterActivePower",
"address": 32080,
"size": 2,
"type": "int32",
"registerType": "holdingRegister",
"readSchedule": "update",
"description": "Inverter active power",
"unit": "kW",
"staticScaleFactor": -3,
"defaultValue": "0",
"access": "RO"
},
{
"id": "inverterDeviceStatus",
"address": 32089,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"readSchedule": "update",
"description": "Inverter device status",
"enum": "InverterDeviceStatus",
"defaultValue": "InverterDeviceStatusStandbyInitializing",
"access": "RO"
},
{
"id": "inverterEnergyProduced",
"address": 32106,
"size": 2,
"type": "uint32",
"registerType": "holdingRegister",
"readSchedule": "update",
"description": "Inverter energy produced",
"unit": "kWh",
"staticScaleFactor": -2,
"defaultValue": "0",
"access": "RO"
},
{
"id": "powerMeterActivePower",
"address": 37113,
"size": 2,
"type": "int32",
"registerType": "holdingRegister",
"readSchedule": "update",
"description": "Power meter active power",
"unit": "W",
"defaultValue": "0",
"access": "RO"
},
{
"id": "lunaBattery1Status",
"address": 37000,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"readSchedule": "update",
"description": "Luna 2000 Battery 1 status",
"enum": "BatteryDeviceStatus",
"defaultValue": "BatteryDeviceStatusOffline",
"access": "RO"
},
{
"id": "lunaBattery1Power",
"address": 37001,
"size": 2,
"type": "int32",
"registerType": "holdingRegister",
"readSchedule": "update",
"description": "Luna 2000 Battery 1 power",
"unit": "W",
"defaultValue": "0",
"access": "RO"
},
{
"id": "lunaBattery1Soc",
"address": 37004,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"readSchedule": "update",
"description": "Luna 2000 Battery 1 state of charge",
"staticScaleFactor": -1,
"unit": "%",
"defaultValue": "0",
"access": "RO"
},
{
"id": "lunaBattery2Status",
"address": 37741,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"readSchedule": "update",
"description": "Luna 2000 Battery 2 status",
"enum": "BatteryDeviceStatus",
"defaultValue": "BatteryDeviceStatusOffline",
"access": "RO"
},
{
"id": "lunaBattery2Power",
"address": 37743,
"size": 2,
"type": "int32",
"registerType": "holdingRegister",
"readSchedule": "update",
"description": "Luna 2000 Battery 2 power",
"unit": "W",
"defaultValue": "0",
"access": "RO"
},
{
"id": "lunaBattery2Soc",
"address": 37738,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"readSchedule": "update",
"description": "Luna 2000 Battery 2 state of charge",
"staticScaleFactor": -1,
"unit": "%",
"defaultValue": "0",
"access": "RO"
}
]
}

View File

@ -1,6 +1,6 @@
{
"className": "Huawei",
"protocol": "TCP",
"protocol": "RTU",
"endianness": "BigEndian",
"errorLimitUntilNotReachable": 15,
"checkReachableRegister": "inverterActivePower",
@ -149,6 +149,135 @@
}
],
"blocks": [
{
"id": "identifyer",
"readSchedule": "init",
"registers": [
{
"id": "model",
"address": 30000,
"size": 15,
"type": "string",
"registerType": "holdingRegister",
"description": "Model",
"access": "RO"
},
{
"id": "serialNumber",
"address": 30015,
"size": 10,
"type": "string",
"registerType": "holdingRegister",
"description": "Serial number",
"access": "RO"
},
{
"id": "productNumber",
"address": 30025,
"size": 10,
"type": "string",
"registerType": "holdingRegister",
"description": "Product number",
"access": "RO"
}
]
},
{
"id": "setup",
"readSchedule": "init",
"registers": [
{
"id": "modelId",
"address": 30070,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"description": "Model ID",
"defaultValue": "0",
"access": "RO"
},
{
"id": "numberOfPvStrings",
"address": 30071,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"description": "Number of PV strings",
"defaultValue": "0",
"access": "RO"
},
{
"id": "numberOfMppTracks",
"address": 30072,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"description": "Number of MPP tracks",
"defaultValue": "0",
"access": "RO"
},
{
"id": "ratedPower",
"address": 30073,
"size": 2,
"type": "uint32",
"registerType": "holdingRegister",
"description": "Rated power (Pn)",
"unit": "kW",
"staticScaleFactor": -3,
"defaultValue": "0",
"access": "RO"
},
{
"id": "maxActivePower",
"address": 30075,
"size": 2,
"type": "uint32",
"registerType": "holdingRegister",
"description": "Maximum active power (P max)",
"unit": "kW",
"staticScaleFactor": -3,
"defaultValue": "0",
"access": "RO"
},
{
"id": "maxApparentPower",
"address": 30077,
"size": 2,
"type": "uint32",
"registerType": "holdingRegister",
"description": "Maximum apparant power (S max)",
"unit": "kVA",
"staticScaleFactor": -3,
"defaultValue": "0",
"access": "RO"
},
{
"id": "maxReactivePowerToGrid",
"address": 30079,
"size": 2,
"type": "int32",
"registerType": "holdingRegister",
"description": "Maximum reactive power (Q max) - fed to grid",
"unit": "kVar",
"staticScaleFactor": -3,
"defaultValue": "0",
"access": "RO"
},
{
"id": "maxReactivePowerFromGrid",
"address": 30081,
"size": 2,
"type": "int32",
"registerType": "holdingRegister",
"description": "Maximum reactive power (Q max) - absorbed from grid",
"unit": "kVar",
"staticScaleFactor": -3,
"defaultValue": "0",
"access": "RO"
}
]
}
],
"registers": [
{

View File

@ -1,14 +1,16 @@
include(../plugins.pri)
# Generate modbus connection
MODBUS_CONNECTIONS += huawei-registers.json
#MODBUS_TOOLS_CONFIG += VERBOSE
MODBUS_CONNECTIONS += huawei-fusion-solar-registers.json huawei-registers.json
include(../modbus.pri)
HEADERS += \
huaweifusionsolar.h \
huaweifusionsolardiscovery.h \
integrationpluginhuawei.h
SOURCES += \
huaweifusionsolar.cpp \
huaweifusionsolardiscovery.cpp \
integrationpluginhuawei.cpp

View File

@ -1,4 +1,4 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2022, nymea GmbH
* Contact: contact@nymea.io
@ -35,15 +35,57 @@
NYMEA_LOGGING_CATEGORY(dcHuaweiFusionSolar, "HuaweiFusionSolar")
HuaweiFusionSolar::HuaweiFusionSolar(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent) :
HuaweiModbusTcpConnection(hostAddress, port, slaveId, parent)
HuaweiFusionModbusTcpConnection(hostAddress, port, slaveId, parent)
{
connect(this, &HuaweiFusionModbusTcpConnection::connectionStateChanged, this, [=](bool connected){
if (!connected) {
m_registersQueue.clear();
}
});
}
bool HuaweiFusionSolar::initialize()
{
// No init registers defined. Nothing to be done and we are finished.
emit initializationFinished(true);
if (!reachable()) {
qCWarning(dcHuaweiFusionSolar()) << "Tried to initialize but the device is not to be reachable.";
return false;
}
if (m_initReply) {
qCWarning(dcHuaweiFusionSolar()) << "Tried to initialize but the init process is already running.";
return false;
}
qCDebug(dcHuaweiFusionSolar()) << "Initialize connection by reading \"Inverter active power\" register:" << 32080 << "size:" << 2;
m_initReply = readInverterActivePower();
if (!m_initReply) {
qCWarning(dcHuaweiFusionSolar()) << "Error occurred while initializing connection and read \"Inverter active power\" register from" << hostAddress().toString() << errorString();
return false;
}
if (m_initReply->isFinished()) {
m_initReply->deleteLater(); // Broadcast reply returns immediatly
m_initReply = nullptr;
return false;
}
connect(m_initReply, &QModbusReply::finished, this, [this](){
if (m_initReply->error() == QModbusDevice::NoError) {
qCDebug(dcHuaweiFusionSolar()) << "Initialization finished of HuaweiFusionSolar" << hostAddress().toString() << "finished successfully";
emit initializationFinished(true);
} else {
qCWarning(dcHuaweiFusionSolar()) << "Initialization finished of HuaweiFusionSolar" << hostAddress().toString() << "failed.";
emit initializationFinished(false);
}
m_initReply->deleteLater();
m_initReply = nullptr;
});
connect(m_initReply, &QModbusReply::errorOccurred, this, [this] (QModbusDevice::Error error){
qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while initializing connection and read \"Inverter active power\" registers from" << hostAddress().toString() << error << m_initReply->errorString();
});
return true;
}
@ -54,19 +96,19 @@ bool HuaweiFusionSolar::update()
return true;
// Add the requests
m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterInverterActivePower);
m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterInverterDeviceStatus);
m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterInverterEnergyProduced);
m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery1Status);
m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterInverterActivePower);
m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterInverterDeviceStatus);
m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterInverterEnergyProduced);
m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery1Status);
if (m_battery1Available) {
m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery1Power);
m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery1Soc);
m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery1Power);
m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery1Soc);
}
m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterPowerMeterActivePower);
m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery2Status);
m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterPowerMeterActivePower);
m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery2Status);
if (m_battery2Available) {
m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery2Power);
m_registersQueue.enqueue(HuaweiModbusTcpConnection::RegisterLunaBattery2Soc);
m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery2Power);
m_registersQueue.enqueue(HuaweiFusionModbusTcpConnection::RegisterLunaBattery2Soc);
}
// Note: since huawei can only process one request at the time, we need to queue the requests
@ -89,316 +131,399 @@ void HuaweiFusionSolar::readNextRegister()
m_currentRegisterRequest = m_registersQueue.dequeue();
switch (m_currentRegisterRequest) {
case HuaweiModbusTcpConnection::RegisterInverterActivePower: {
case HuaweiFusionModbusTcpConnection::RegisterInverterActivePower: {
// Update registers from Inverter active power
qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Inverter active power\" register:" << 32080 << "size:" << 2;
QModbusReply *reply = readInverterActivePower();
if (reply) {
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
const QVector<quint16> values = unit.values();
qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Inverter active power\" register" << 32080 << "size:" << 2 << values;
processInverterActivePowerRegisterValues(values);
}
finishRequest();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Inverter active power\" registers from" << hostAddress().toString() << error << reply->errorString();
});
} else {
reply->deleteLater(); // Broadcast reply returns immediatly
finishRequest();
}
} else {
if (!reply) {
qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Inverter active power\" registers from" << hostAddress().toString() << errorString();
finishRequest();
return;
}
if (reply->isFinished()) {
reply->deleteLater(); // Broadcast reply returns immediatly
finishRequest();
return;
}
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
handleModbusError(reply->error());
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Inverter active power\" register" << 32080 << "size:" << 2 << unit.values();
processInverterActivePowerRegisterValues(unit.values());
}
finishRequest();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
if (reply->error() == QModbusDevice::ProtocolError) {
QModbusResponse response = reply->rawResult();
if (response.isException()) {
qCDebug(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Inverter active power\" registers from" << hostAddress().toString() << exceptionToString(response.exceptionCode());
}
} else {
qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Inverter active power\" registers from" << hostAddress().toString() << error << reply->errorString();
}
});
break;
}
case HuaweiModbusTcpConnection::RegisterInverterDeviceStatus: {
case HuaweiFusionModbusTcpConnection::RegisterInverterDeviceStatus: {
// Update registers from Inverter device status
qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Inverter device status\" register:" << 32089 << "size:" << 1;
QModbusReply *reply = readInverterDeviceStatus();
if (reply) {
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
const QVector<quint16> values = unit.values();
qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Inverter device status\" register" << 32089 << "size:" << 1 << values;
processInverterDeviceStatusRegisterValues(values);
qCDebug(dcHuaweiFusionSolar()) << "Inverter status" << inverterDeviceStatus();
}
finishRequest();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Inverter device status\" registers from" << hostAddress().toString() << error << reply->errorString();
});
} else {
reply->deleteLater(); // Broadcast reply returns immediatly
finishRequest();
}
} else {
if (!reply) {
qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Inverter device status\" registers from" << hostAddress().toString() << errorString();
finishRequest();
return;
}
if (reply->isFinished()) {
reply->deleteLater(); // Broadcast reply returns immediatly
finishRequest();
return;
}
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
handleModbusError(reply->error());
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Inverter device status\" register" << 32089 << "size:" << 1 << unit.values();
processInverterDeviceStatusRegisterValues(unit.values());
}
finishRequest();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
if (reply->error() == QModbusDevice::ProtocolError) {
QModbusResponse response = reply->rawResult();
if (response.isException()) {
qCDebug(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Inverter device status\" registers from" << hostAddress().toString() << exceptionToString(response.exceptionCode());
}
} else {
qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Inverter device status\" registers from" << hostAddress().toString() << error << reply->errorString();
}
});
break;
}
case HuaweiModbusTcpConnection::RegisterInverterEnergyProduced: {
case HuaweiFusionModbusTcpConnection::RegisterInverterEnergyProduced: {
// Update registers from Inverter energy produced
qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Inverter energy produced\" register:" << 32106 << "size:" << 2;
QModbusReply *reply = readInverterEnergyProduced();
if (reply) {
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
const QVector<quint16> values = unit.values();
qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Inverter energy produced\" register" << 32106 << "size:" << 2 << values;
processInverterEnergyProducedRegisterValues(values);
}
finishRequest();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Inverter energy produced\" registers from" << hostAddress().toString() << error << reply->errorString();
});
} else {
reply->deleteLater(); // Broadcast reply returns immediatly
finishRequest();
}
} else {
if (!reply) {
qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Inverter energy produced\" registers from" << hostAddress().toString() << errorString();
finishRequest();
return;
}
break;
}
case HuaweiModbusTcpConnection::RegisterLunaBattery1Status: {
// Update registers from Luna 2000 Battery 1 status
qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Luna 2000 Battery 1 status\" register:" << 37000 << "size:" << 1;
QModbusReply *reply = readLunaBattery1Status();
if (reply) {
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
const QVector<quint16> values = unit.values();
qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 1 status\" register" << 37000 << "size:" << 1 << values;
processLunaBattery1StatusRegisterValues(values);
qCDebug(dcHuaweiFusionSolar()) << "Battery 1 status" << m_lunaBattery1Status;
if (m_lunaBattery1Status == BatteryDeviceStatusOffline) {
m_battery1Available = false;
} else {
m_battery1Available = true;
}
}
finishRequest();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 1 status\" registers from" << hostAddress().toString() << error << reply->errorString();
});
} else {
reply->deleteLater(); // Broadcast reply returns immediatly
finishRequest();
}
} else {
qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Luna 2000 Battery 1 status\" registers from" << hostAddress().toString() << errorString();
if (reply->isFinished()) {
reply->deleteLater(); // Broadcast reply returns immediatly
finishRequest();
return;
}
break;
}
case HuaweiModbusTcpConnection::RegisterLunaBattery1Power: {
// Update registers from Luna 2000 Battery 1 power
qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Luna 2000 Battery 1 power\" register:" << 37001 << "size:" << 2;
QModbusReply *reply = readLunaBattery1Power();
if (reply) {
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
const QVector<quint16> values = unit.values();
qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 1 power\" register" << 37001 << "size:" << 2 << values;
processLunaBattery1PowerRegisterValues(values);
}
finishRequest();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 1 power\" registers from" << hostAddress().toString() << error << reply->errorString();
});
} else {
reply->deleteLater(); // Broadcast reply returns immediatly
finishRequest();
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
handleModbusError(reply->error());
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Inverter energy produced\" register" << 32106 << "size:" << 2 << unit.values();
processInverterEnergyProducedRegisterValues(unit.values());
}
} else {
qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Luna 2000 Battery 1 power\" registers from" << hostAddress().toString() << errorString();
finishRequest();
}
break;
}
case HuaweiModbusTcpConnection::RegisterLunaBattery1Soc: {
// Update registers from Luna 2000 Battery 1 state of charge
qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Luna 2000 Battery 1 state of charge\" register:" << 37004 << "size:" << 1;
QModbusReply *reply = readLunaBattery1Soc();
if (reply) {
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
const QVector<quint16> values = unit.values();
qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 1 state of charge\" register" << 37004 << "size:" << 1 << values;
processLunaBattery1SocRegisterValues(values);
}
finishRequest();
});
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 1 state of charge\" registers from" << hostAddress().toString() << error << reply->errorString();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
if (reply->error() == QModbusDevice::ProtocolError) {
QModbusResponse response = reply->rawResult();
if (response.isException()) {
qCDebug(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Inverter energy produced\" registers from" << hostAddress().toString() << exceptionToString(response.exceptionCode());
}
} else {
reply->deleteLater(); // Broadcast reply returns immediatly
finishRequest();
qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Inverter energy produced\" registers from" << hostAddress().toString() << error << reply->errorString();
}
} else {
qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Luna 2000 Battery 1 state of charge\" registers from" << hostAddress().toString() << errorString();
finishRequest();
}
});
break;
}
case HuaweiModbusTcpConnection::RegisterPowerMeterActivePower: {
case HuaweiFusionModbusTcpConnection::RegisterPowerMeterActivePower: {
// Update registers from Power meter active power
qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Power meter active power\" register:" << 37113 << "size:" << 2;
QModbusReply *reply = readPowerMeterActivePower();
if (reply) {
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
const QVector<quint16> values = unit.values();
qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Power meter active power\" register" << 37113 << "size:" << 2 << values;
processPowerMeterActivePowerRegisterValues(values);
}
finishRequest();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Power meter active power\" registers from" << hostAddress().toString() << error << reply->errorString();
});
} else {
reply->deleteLater(); // Broadcast reply returns immediatly
finishRequest();
}
} else {
if (!reply) {
qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Power meter active power\" registers from" << hostAddress().toString() << errorString();
finishRequest();
return;
}
if (reply->isFinished()) {
reply->deleteLater(); // Broadcast reply returns immediatly
finishRequest();
return;
}
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
handleModbusError(reply->error());
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Power meter active power\" register" << 37113 << "size:" << 2 << unit.values();
processPowerMeterActivePowerRegisterValues(unit.values());
}
finishRequest();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
if (reply->error() == QModbusDevice::ProtocolError) {
QModbusResponse response = reply->rawResult();
if (response.isException()) {
qCDebug(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Power meter active power\" registers from" << hostAddress().toString() << exceptionToString(response.exceptionCode());
}
} else {
qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Power meter active power\" registers from" << hostAddress().toString() << error << reply->errorString();
}
});
break;
}
case HuaweiModbusTcpConnection::RegisterLunaBattery2Status: {
case HuaweiFusionModbusTcpConnection::RegisterLunaBattery1Status: {
// Update registers from Luna 2000 Battery 1 status
qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Luna 2000 Battery 1 status\" register:" << 37000 << "size:" << 1;
QModbusReply *reply = readLunaBattery1Status();
if (!reply) {
qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Luna 2000 Battery 1 status\" registers from" << hostAddress().toString() << errorString();
finishRequest();
return;
}
if (reply->isFinished()) {
reply->deleteLater(); // Broadcast reply returns immediatly
finishRequest();
return;
}
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
handleModbusError(reply->error());
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 1 status\" register" << 37000 << "size:" << 1 << unit.values();
processLunaBattery1StatusRegisterValues(unit.values());
}
finishRequest();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
if (reply->error() == QModbusDevice::ProtocolError) {
QModbusResponse response = reply->rawResult();
if (response.isException()) {
qCDebug(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 1 status\" registers from" << hostAddress().toString() << exceptionToString(response.exceptionCode());
}
} else {
qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 1 status\" registers from" << hostAddress().toString() << error << reply->errorString();
}
});
break;
}
case HuaweiFusionModbusTcpConnection::RegisterLunaBattery1Power: {
// Update registers from Luna 2000 Battery 1 power
qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Luna 2000 Battery 1 power\" register:" << 37001 << "size:" << 2;
QModbusReply *reply = readLunaBattery1Power();
if (!reply) {
qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Luna 2000 Battery 1 power\" registers from" << hostAddress().toString() << errorString();
finishRequest();
return;
}
if (reply->isFinished()) {
reply->deleteLater(); // Broadcast reply returns immediatly
finishRequest();
return;
}
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
handleModbusError(reply->error());
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 1 power\" register" << 37001 << "size:" << 2 << unit.values();
processLunaBattery1PowerRegisterValues(unit.values());
}
finishRequest();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
if (reply->error() == QModbusDevice::ProtocolError) {
QModbusResponse response = reply->rawResult();
if (response.isException()) {
qCDebug(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 1 power\" registers from" << hostAddress().toString() << exceptionToString(response.exceptionCode());
}
} else {
qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 1 power\" registers from" << hostAddress().toString() << error << reply->errorString();
}
});
break;
}
case HuaweiFusionModbusTcpConnection::RegisterLunaBattery1Soc: {
// Update registers from Luna 2000 Battery 1 state of charge
qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Luna 2000 Battery 1 state of charge\" register:" << 37004 << "size:" << 1;
QModbusReply *reply = readLunaBattery1Soc();
if (!reply) {
qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Luna 2000 Battery 1 state of charge\" registers from" << hostAddress().toString() << errorString();
finishRequest();
return;
}
if (reply->isFinished()) {
reply->deleteLater(); // Broadcast reply returns immediatly
finishRequest();
return;
}
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
handleModbusError(reply->error());
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 1 state of charge\" register" << 37004 << "size:" << 1 << unit.values();
processLunaBattery1SocRegisterValues(unit.values());
}
finishRequest();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
if (reply->error() == QModbusDevice::ProtocolError) {
QModbusResponse response = reply->rawResult();
if (response.isException()) {
qCDebug(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 1 state of charge\" registers from" << hostAddress().toString() << exceptionToString(response.exceptionCode());
}
} else {
qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 1 state of charge\" registers from" << hostAddress().toString() << error << reply->errorString();
}
});
break;
}
case HuaweiFusionModbusTcpConnection::RegisterLunaBattery2Status: {
// Update registers from Luna 2000 Battery 2 status
qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Luna 2000 Battery 2 status\" register:" << 37741 << "size:" << 1;
QModbusReply *reply = readLunaBattery2Status();
if (reply) {
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
const QVector<quint16> values = unit.values();
qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 2 status\" register" << 37741 << "size:" << 1 << values;
processLunaBattery2StatusRegisterValues(values);
qCDebug(dcHuaweiFusionSolar()) << "Battery 2 status" << m_lunaBattery2Status;
if (m_lunaBattery2Status == BatteryDeviceStatusOffline) {
m_battery2Available = false;
} else {
m_battery2Available = true;
}
}
finishRequest();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 2 status\" registers from" << hostAddress().toString() << error << reply->errorString();
});
} else {
reply->deleteLater(); // Broadcast reply returns immediatly
finishRequest();
}
} else {
if (!reply) {
qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Luna 2000 Battery 2 status\" registers from" << hostAddress().toString() << errorString();
finishRequest();
return;
}
if (reply->isFinished()) {
reply->deleteLater(); // Broadcast reply returns immediatly
finishRequest();
return;
}
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
handleModbusError(reply->error());
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 2 status\" register" << 37741 << "size:" << 1 << unit.values();
processLunaBattery2StatusRegisterValues(unit.values());
}
finishRequest();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
if (reply->error() == QModbusDevice::ProtocolError) {
QModbusResponse response = reply->rawResult();
if (response.isException()) {
qCDebug(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 2 status\" registers from" << hostAddress().toString() << exceptionToString(response.exceptionCode());
}
} else {
qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 2 status\" registers from" << hostAddress().toString() << error << reply->errorString();
}
});
break;
}
case HuaweiModbusTcpConnection::RegisterLunaBattery2Power: {
case HuaweiFusionModbusTcpConnection::RegisterLunaBattery2Power: {
// Update registers from Luna 2000 Battery 2 power
qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Luna 2000 Battery 2 power\" register:" << 37743 << "size:" << 2;
QModbusReply *reply = readLunaBattery2Power();
if (reply) {
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
const QVector<quint16> values = unit.values();
qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 2 power\" register" << 37743 << "size:" << 2 << values;
processLunaBattery2PowerRegisterValues(values);
}
finishRequest();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 2 power\" registers from" << hostAddress().toString() << error << reply->errorString();
});
} else {
reply->deleteLater(); // Broadcast reply returns immediatly
finishRequest();
}
} else {
if (!reply) {
qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Luna 2000 Battery 2 power\" registers from" << hostAddress().toString() << errorString();
finishRequest();
return;
}
if (reply->isFinished()) {
reply->deleteLater(); // Broadcast reply returns immediatly
finishRequest();
return;
}
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
handleModbusError(reply->error());
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 2 power\" register" << 37743 << "size:" << 2 << unit.values();
processLunaBattery2PowerRegisterValues(unit.values());
}
finishRequest();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
if (reply->error() == QModbusDevice::ProtocolError) {
QModbusResponse response = reply->rawResult();
if (response.isException()) {
qCDebug(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 2 power\" registers from" << hostAddress().toString() << exceptionToString(response.exceptionCode());
}
} else {
qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 2 power\" registers from" << hostAddress().toString() << error << reply->errorString();
}
});
break;
}
case HuaweiModbusTcpConnection::RegisterLunaBattery2Soc: {
case HuaweiFusionModbusTcpConnection::RegisterLunaBattery2Soc: {
// Update registers from Luna 2000 Battery 2 state of charge
qCDebug(dcHuaweiFusionSolar()) << "--> Read \"Luna 2000 Battery 2 state of charge\" register:" << 37738 << "size:" << 1;
QModbusReply *reply = readLunaBattery2Soc();
if (reply) {
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
const QVector<quint16> values = unit.values();
qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 2 state of charge\" register" << 37738 << "size:" << 1 << values;
processLunaBattery2SocRegisterValues(values);
}
finishRequest();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 2 state of charge\" registers from" << hostAddress().toString() << error << reply->errorString();
});
} else {
reply->deleteLater(); // Broadcast reply returns immediatly
finishRequest();
}
} else {
if (!reply) {
qCWarning(dcHuaweiFusionSolar()) << "Error occurred while reading \"Luna 2000 Battery 2 state of charge\" registers from" << hostAddress().toString() << errorString();
finishRequest();
return;
}
if (reply->isFinished()) {
reply->deleteLater(); // Broadcast reply returns immediatly
finishRequest();
return;
}
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
handleModbusError(reply->error());
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
qCDebug(dcHuaweiFusionSolar()) << "<-- Response from \"Luna 2000 Battery 2 state of charge\" register" << 37738 << "size:" << 1 << unit.values();
processLunaBattery2SocRegisterValues(unit.values());
}
finishRequest();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
if (reply->error() == QModbusDevice::ProtocolError) {
QModbusResponse response = reply->rawResult();
if (response.isException()) {
qCDebug(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 2 state of charge\" registers from" << hostAddress().toString() << exceptionToString(response.exceptionCode());
}
} else {
qCWarning(dcHuaweiFusionSolar()) << "Modbus reply error occurred while updating \"Luna 2000 Battery 2 state of charge\" registers from" << hostAddress().toString() << error << reply->errorString();
}
});
break;
}
}
@ -409,3 +534,45 @@ void HuaweiFusionSolar::finishRequest()
m_currentRegisterRequest = -1;
QTimer::singleShot(1000, this, &HuaweiFusionSolar::readNextRegister);
}
QString HuaweiFusionSolar::exceptionToString(QModbusPdu::ExceptionCode exception)
{
QString exceptionString;
switch (exception) {
case QModbusPdu::IllegalFunction:
exceptionString = "Illegal function";
break;
case QModbusPdu::IllegalDataAddress:
exceptionString = "Illegal data address";
break;
case QModbusPdu::IllegalDataValue:
exceptionString = "Illegal data value";
break;
case QModbusPdu::ServerDeviceFailure:
exceptionString = "Server device failure";
break;
case QModbusPdu::Acknowledge:
exceptionString = "Acknowledge";
break;
case QModbusPdu::ServerDeviceBusy:
exceptionString = "Server device busy";
break;
case QModbusPdu::NegativeAcknowledge:
exceptionString = "Negative acknowledge";
break;
case QModbusPdu::MemoryParityError:
exceptionString = "Memory parity error";
break;
case QModbusPdu::GatewayPathUnavailable:
exceptionString = "Gateway path unavailable";
break;
case QModbusPdu::GatewayTargetDeviceFailedToRespond:
exceptionString = "Gateway target device failed to respond";
break;
case QModbusPdu::ExtendedException:
exceptionString = "Extended exception";
break;
}
return exceptionString;
}

View File

@ -34,20 +34,21 @@
#include <QObject>
#include <QQueue>
#include "huaweimodbustcpconnection.h"
#include "huaweifusionmodbustcpconnection.h"
class HuaweiFusionSolar : public HuaweiModbusTcpConnection
class HuaweiFusionSolar : public HuaweiFusionModbusTcpConnection
{
Q_OBJECT
public:
explicit HuaweiFusionSolar(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent = nullptr);
~HuaweiFusionSolar() = default;
virtual bool initialize() override;
bool initialize() override;
virtual bool update() override;
private:
QQueue<HuaweiModbusTcpConnection::Registers> m_registersQueue;
QQueue<HuaweiFusionModbusTcpConnection::Registers> m_registersQueue;
QModbusReply *m_initReply = nullptr;
int m_currentRegisterRequest = -1;
void finishRequest();
@ -55,6 +56,8 @@ private:
bool m_battery1Available = true;
bool m_battery2Available = true;
QString exceptionToString(QModbusPdu::ExceptionCode exception);
private slots:
void readNextRegister();

View File

@ -0,0 +1,166 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2022, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; version 3. This project is distributed in the hope that
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "huaweifusionsolardiscovery.h"
#include "extern-plugininfo.h"
HuaweiFusionSolarDiscovery::HuaweiFusionSolarDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint16 port, quint16 modbusAddress, QObject *parent) :
QObject(parent),
m_networkDeviceDiscovery(networkDeviceDiscovery),
m_port(port),
m_modbusAddress(modbusAddress)
{
m_gracePeriodTimer.setSingleShot(true);
m_gracePeriodTimer.setInterval(3000);
connect(&m_gracePeriodTimer, &QTimer::timeout, this, [this](){
qCDebug(dcHuawei()) << "Discovery: Grace period timer triggered.";
finishDiscovery();
});
}
void HuaweiFusionSolarDiscovery::startDiscovery()
{
qCInfo(dcHuawei()) << "Discovery: Start searching for Huawei FusionSolar SmartDongle in the network...";
NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover();
// Check any already discovered infos..
foreach (const NetworkDeviceInfo &networkDeviceInfo, discoveryReply->networkDeviceInfos()) {
checkNetworkDevice(networkDeviceInfo);
}
// Imedialty check any new device gets discovered
connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &HuaweiFusionSolarDiscovery::checkNetworkDevice);
// Check what might be left on finished
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){
qCDebug(dcHuawei()) << "Discovery: Network discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "network devices";
m_networkDeviceInfos = discoveryReply->networkDeviceInfos();
qCDebug(dcHuawei()) << "Discovery: Network discovery finished. Start finishing discovery...";
// Send a report request to nework device info not sent already...
foreach (const NetworkDeviceInfo &networkDeviceInfo, m_networkDeviceInfos) {
if (!m_verifiedNetworkDeviceInfos.contains(networkDeviceInfo)) {
checkNetworkDevice(networkDeviceInfo);
}
}
m_gracePeriodTimer.start();
});
}
NetworkDeviceInfos HuaweiFusionSolarDiscovery::discoveryResults() const
{
return m_discoveryResults;
}
void HuaweiFusionSolarDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo)
{
if (m_verifiedNetworkDeviceInfos.contains(networkDeviceInfo))
return;
// The dongle must have a huawei registered mac address
if (!networkDeviceInfo.macAddressManufacturer().toLower().contains("huawei"))
return;
HuaweiFusionSolar *connection = new HuaweiFusionSolar(networkDeviceInfo.address(), m_port, m_modbusAddress, this);
m_connections.append(connection);
m_verifiedNetworkDeviceInfos.append(networkDeviceInfo);
connect(connection, &HuaweiFusionSolar::reachableChanged, this, [=](bool reachable){
if (!reachable) {
// Disconnected ... done with this connection
cleanupConnection(connection);
return;
}
// Modbus TCP connected...ok, let's try to initialize it!
connect(connection, &HuaweiFusionSolar::initializationFinished, this, [=](bool success){
if (!success) {
qCDebug(dcHuawei()) << "Discovery: Initialization failed on" << networkDeviceInfo.address().toString() << "Continue...";;
cleanupConnection(connection);
return;
}
m_discoveryResults.append(networkDeviceInfo);
qCDebug(dcHuawei()) << "Discovery: --> Found" << networkDeviceInfo;
// Done with this connection
cleanupConnection(connection);
});
if (!connection->initialize()) {
qCDebug(dcHuawei()) << "Discovery: Unable to initialize connection on" << networkDeviceInfo.address().toString() << "Continue...";;
cleanupConnection(connection);
}
// Initializing...
});
// If we get any error...skip this host...
connect(connection, &HuaweiFusionSolar::connectionErrorOccurred, this, [=](QModbusDevice::Error error){
if (error != QModbusDevice::NoError) {
qCDebug(dcHuawei()) << "Discovery: Connection error on" << networkDeviceInfo.address().toString() << "Continue...";;
cleanupConnection(connection);
}
});
// If check reachability failed...skip this host...
connect(connection, &HuaweiFusionSolar::checkReachabilityFailed, this, [=](){
qCDebug(dcHuawei()) << "Discovery: Check reachability failed on" << networkDeviceInfo.address().toString() << "Continue...";;
cleanupConnection(connection);
});
// Try to connect, maybe it works, maybe not...
connection->connectDevice();
}
void HuaweiFusionSolarDiscovery::cleanupConnection(HuaweiFusionSolar *connection)
{
m_connections.removeAll(connection);
connection->disconnectDevice();
connection->deleteLater();
}
void HuaweiFusionSolarDiscovery::finishDiscovery()
{
qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch();
// Cleanup any leftovers...we don't care any more
foreach (HuaweiFusionSolar *connection, m_connections)
cleanupConnection(connection);
qCInfo(dcHuawei()) << "Discovery: Finished the discovery process. Found" << m_discoveryResults.count()
<< "inverters in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz");
m_gracePeriodTimer.stop();
emit discoveryFinished();
}

View File

@ -0,0 +1,75 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2022, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; version 3. This project is distributed in the hope that
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef HUAWEIFUSIONSOLARDISCOVERY_H
#define HUAWEIFUSIONSOLARDISCOVERY_H
#include <QObject>
#include <network/networkdevicediscovery.h>
#include "huaweifusionsolar.h"
class HuaweiFusionSolarDiscovery : public QObject
{
Q_OBJECT
public:
explicit HuaweiFusionSolarDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint16 port = 502, quint16 modbusAddress = 1, QObject *parent = nullptr);
void startDiscovery();
NetworkDeviceInfos discoveryResults() const;
signals:
void discoveryFinished();
private:
NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr;
quint16 m_port;
quint16 m_modbusAddress;
QTimer m_gracePeriodTimer;
QDateTime m_startDateTime;
NetworkDeviceInfos m_networkDeviceInfos;
NetworkDeviceInfos m_verifiedNetworkDeviceInfos;
QList<HuaweiFusionSolar *> m_connections;
NetworkDeviceInfos m_discoveryResults;
void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo);
void cleanupConnection(HuaweiFusionSolar *connection);
void finishDiscovery();
};
#endif // HUAWEIFUSIONSOLARDISCOVERY_H

View File

@ -28,10 +28,11 @@
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "huaweifusionsolardiscovery.h"
#include "integrationpluginhuawei.h"
#include "plugininfo.h"
#include <network/networkdevicediscovery.h>
#include <hardware/modbus/modbusrtuhardwareresource.h>
#include <hardwaremanager.h>
IntegrationPluginHuawei::IntegrationPluginHuawei()
@ -41,59 +42,69 @@ IntegrationPluginHuawei::IntegrationPluginHuawei()
void IntegrationPluginHuawei::discoverThings(ThingDiscoveryInfo *info)
{
if (!hardwareManager()->networkDeviceDiscovery()->available()) {
qCWarning(dcHuawei()) << "The network discovery is not available on this platform.";
info->finish(Thing::ThingErrorUnsupportedFeature, QT_TR_NOOP("The network device discovery is not available."));
return;
}
if (info->thingClassId() == huaweiFusionSolarInverterThingClassId) {
if (!hardwareManager()->networkDeviceDiscovery()->available()) {
qCWarning(dcHuawei()) << "The network discovery is not available on this platform.";
info->finish(Thing::ThingErrorUnsupportedFeature, QT_TR_NOOP("The network device discovery is not available."));
return;
}
NetworkDeviceDiscoveryReply *discoveryReply = hardwareManager()->networkDeviceDiscovery()->discover();
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){
foreach (const NetworkDeviceInfo &networkDeviceInfo, discoveryReply->networkDeviceInfos()) {
// Create a discovery with the info as parent for auto deleting the object once the discovery info is done
HuaweiFusionSolarDiscovery *discovery = new HuaweiFusionSolarDiscovery(hardwareManager()->networkDeviceDiscovery(), 502, 1, info);
connect(discovery, &HuaweiFusionSolarDiscovery::discoveryFinished, info, [=](){
foreach (const NetworkDeviceInfo &networkDeviceInfo, discovery->discoveryResults()) {
qCDebug(dcHuawei()) << "Found" << networkDeviceInfo;
ThingDescriptor descriptor(huaweiFusionSolarInverterThingClassId, QT_TR_NOOP("Huawei Solar Inverter"), networkDeviceInfo.macAddress() + " - " + networkDeviceInfo.address().toString());
qCDebug(dcHuawei()) << "Discovered:" << descriptor.title() << descriptor.description();
// Filter for mac manufacturer
if (!networkDeviceInfo.macAddressManufacturer().contains("Huawei"))
// Check if we already have set up this device
Things existingThings = myThings().filterByParam(huaweiFusionSolarInverterThingMacAddressParamTypeId, networkDeviceInfo.macAddress());
if (existingThings.count() == 1) {
qCDebug(dcHuawei()) << "This inverter already exists in the system:" << networkDeviceInfo;
descriptor.setThingId(existingThings.first()->id());
}
ParamList params;
params << Param(huaweiFusionSolarInverterThingMacAddressParamTypeId, networkDeviceInfo.macAddress());
// Note: if we discover also the port and modbusaddress, we must fill them in from the discovery here, for now everywhere the defaults...
descriptor.setParams(params);
info->addThingDescriptor(descriptor);
}
info->finish(Thing::ThingErrorNoError);
});
// Start the discovery process
discovery->startDiscovery();
} else if (info->thingClassId() == huaweiRtuInverterThingClassId) {
qCDebug(dcHuawei()) << "Discovering modbus RTU resources...";
if (hardwareManager()->modbusRtuResource()->modbusRtuMasters().isEmpty()) {
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("No Modbus RTU interface available. Please set up a Modbus RTU interface first."));
return;
}
uint slaveAddress = info->params().paramValue(huaweiRtuInverterThingSlaveAddressParamTypeId).toUInt();
if (slaveAddress > 254 || slaveAddress == 0) {
info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The Modbus slave address must be a value between 1 and 254."));
return;
}
foreach (ModbusRtuMaster *modbusMaster, hardwareManager()->modbusRtuResource()->modbusRtuMasters()) {
qCDebug(dcHuawei()) << "Found RTU master resource" << modbusMaster << "connected" << modbusMaster->connected();
if (!modbusMaster->connected())
continue;
QString title;
if (networkDeviceInfo.hostName().isEmpty()) {
title = "Huawei FusionSolar";
} else {
title = networkDeviceInfo.hostName() + " (" + networkDeviceInfo.address().toString() + ")";
}
QString description;
if (networkDeviceInfo.macAddressManufacturer().isEmpty()) {
description = networkDeviceInfo.macAddress();
} else {
description = networkDeviceInfo.macAddress() + " (" + networkDeviceInfo.macAddressManufacturer() + ")";
}
ThingDescriptor descriptor(huaweiInverterThingClassId, title, description);
ThingDescriptor descriptor(info->thingClassId(), "Huawei Inverter", QString::number(slaveAddress) + " " + modbusMaster->serialPort());
ParamList params;
params << Param(huaweiInverterThingIpAddressParamTypeId, networkDeviceInfo.address().toString());
params << Param(huaweiInverterThingMacAddressParamTypeId, networkDeviceInfo.macAddress());
params << Param(huaweiRtuInverterThingSlaveAddressParamTypeId, slaveAddress);
params << Param(huaweiRtuInverterThingModbusMasterUuidParamTypeId, modbusMaster->modbusUuid());
descriptor.setParams(params);
// Check if we already have set up this device
Things existingThings = myThings().filterByParam(huaweiInverterThingMacAddressParamTypeId, networkDeviceInfo.macAddress());
if (existingThings.count() == 1) {
qCDebug(dcHuawei()) << "This connection already exists in the system:" << networkDeviceInfo;
descriptor.setThingId(existingThings.first()->id());
}
info->addThingDescriptor(descriptor);
}
info->finish(Thing::ThingErrorNoError);
});
}
void IntegrationPluginHuawei::startMonitoringAutoThings()
{
}
}
void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info)
@ -101,50 +112,111 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info)
Thing *thing = info->thing();
qCDebug(dcHuawei()) << "Setup" << thing << thing->params();
if (thing->thingClassId() == huaweiInverterThingClassId) {
QHostAddress hostAddress = QHostAddress(thing->paramValue(huaweiInverterThingIpAddressParamTypeId).toString());
if (hostAddress.isNull()) {
info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("No IP address given"));
if (thing->thingClassId() == huaweiFusionSolarInverterThingClassId) {
// Handle reconfigure
if (m_connections.contains(thing)) {
m_connections.take(thing)->deleteLater();
if (m_monitors.contains(thing)) {
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
}
}
// Make sure we have a valid mac address, otherwise no monitor and not auto searching is possible
MacAddress macAddress = MacAddress(thing->paramValue(huaweiFusionSolarInverterThingMacAddressParamTypeId).toString());
if (macAddress.isNull()) {
qCWarning(dcHuawei()) << "Failed to set up Fusion Solar because the MAC address is not valid:" << thing->paramValue(huaweiFusionSolarInverterThingMacAddressParamTypeId).toString() << macAddress.toString();
info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The MAC address is not vaild. Please reconfigure the device to fix this."));
return;
}
uint port = thing->paramValue(huaweiInverterThingPortParamTypeId).toUInt();
quint16 slaveId = thing->paramValue(huaweiInverterThingSlaveIdParamTypeId).toUInt();
// Create a monitor so we always get the correct IP in the network and see if the device is reachable without polling on our own
NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(macAddress);
m_monitors.insert(thing, monitor);
HuaweiFusionSolar *connection = new HuaweiFusionSolar(hostAddress, port, slaveId, this);
// Continue with setup only if we know that the network device is reachable
if (monitor->reachable()) {
setupFusionSolar(info);
} else {
// otherwise wait until we reach the networkdevice before setting up the device
qCDebug(dcHuawei()) << "Network device" << thing->name() << "is not reachable yet. Continue with the setup once reachable.";
connect(monitor, &NetworkDeviceMonitor::reachableChanged, info, [=](bool reachable){
if (reachable) {
qCDebug(dcHuawei()) << "Network device" << thing->name() << "is now reachable. Continue with the setup...";
setupFusionSolar(info);
}
});
}
connect(connection, &HuaweiFusionSolar::connectionStateChanged, this, [this, thing, connection](bool status){
qCDebug(dcHuawei()) << "Connected changed to" << status << "for" << thing;
if (status) {
return;
}
if (thing->thingClassId() == huaweiRtuInverterThingClassId) {
uint address = thing->paramValue(huaweiRtuInverterThingSlaveAddressParamTypeId).toUInt();
if (address > 254 || address == 0) {
qCWarning(dcHuawei()) << "Setup failed, slave address is not valid" << address;
info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("The Modbus address not valid. It must be a value between 1 and 254."));
return;
}
QUuid uuid = thing->paramValue(huaweiRtuInverterThingModbusMasterUuidParamTypeId).toUuid();
if (!hardwareManager()->modbusRtuResource()->hasModbusRtuMaster(uuid)) {
qCWarning(dcHuawei()) << "Setup failed, hardware manager not available";
info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("The Modbus RTU resource is not available."));
return;
}
if (m_rtuConnections.contains(thing)) {
qCDebug(dcHuawei()) << "Already have a Huawei connection for this thing. Cleaning up old connection and initializing new one...";
delete m_rtuConnections.take(thing);
}
ModbusRtuMaster *rtuMaster = hardwareManager()->modbusRtuResource()->getModbusRtuMaster(uuid);
HuaweiModbusRtuConnection *connection = new HuaweiModbusRtuConnection(rtuMaster, address, this);
connect(connection, &HuaweiModbusRtuConnection::reachableChanged, this, [this, thing, connection](bool reachable){
qCDebug(dcHuawei()) << thing->name() << "reachable changed" << reachable;
if (reachable) {
// Connected true will be set after successfull init
connection->initialize();
thing->setStateValue(huaweiInverterConnectedStateTypeId, true);
} else {
thing->setStateValue(huaweiInverterConnectedStateTypeId, false);
}
foreach (Thing *childThing, myThings().filterByParentId(thing->id())) {
childThing->setStateValue("connected", status);
thing->setStateValue("connected", false);
foreach (Thing *childThing, myThings().filterByParentId(thing->id())) {
childThing->setStateValue("connected", false);
}
}
});
connect(connection, &HuaweiFusionSolar::inverterActivePowerChanged, this, [thing](float inverterActivePower){
connect(connection, &HuaweiModbusRtuConnection::initializationFinished, this, [this, thing, connection](bool success){
if (success) {
thing->setStateValue("connected", true);
foreach (Thing *childThing, myThings().filterByParentId(thing->id())) {
childThing->setStateValue("connected", true);
}
connection->update();
}
});
connect(connection, &HuaweiModbusRtuConnection::inverterActivePowerChanged, this, [thing](float inverterActivePower){
qCDebug(dcHuawei()) << "Inverter power changed" << inverterActivePower * -1000.0 << "W";
thing->setStateValue(huaweiInverterCurrentPowerStateTypeId, inverterActivePower * -1000.0);
thing->setStateValue(huaweiRtuInverterCurrentPowerStateTypeId, inverterActivePower * -1000.0);
});
connect(connection, &HuaweiFusionSolar::inverterDeviceStatusChanged, this, [thing](HuaweiFusionSolar::InverterDeviceStatus inverterDeviceStatus){
connect(connection, &HuaweiModbusRtuConnection::inverterDeviceStatusChanged, this, [thing](HuaweiModbusRtuConnection::InverterDeviceStatus inverterDeviceStatus){
qCDebug(dcHuawei()) << "Inverter device status changed" << inverterDeviceStatus;
Q_UNUSED(thing)
});
connect(connection, &HuaweiFusionSolar::inverterEnergyProducedChanged, this, [thing](float inverterEnergyProduced){
connect(connection, &HuaweiModbusRtuConnection::inverterEnergyProducedChanged, this, [thing](float inverterEnergyProduced){
qCDebug(dcHuawei()) << "Inverter total energy produced changed" << inverterEnergyProduced << "kWh";
thing->setStateValue(huaweiInverterTotalEnergyProducedStateTypeId, inverterEnergyProduced);
thing->setStateValue(huaweiRtuInverterTotalEnergyProducedStateTypeId, inverterEnergyProduced);
});
// Meter
connect(connection, &HuaweiFusionSolar::powerMeterActivePowerChanged, this, [this, thing](qint32 powerMeterActivePower){
connect(connection, &HuaweiModbusRtuConnection::powerMeterActivePowerChanged, this, [this, thing](qint32 powerMeterActivePower){
Things meterThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiMeterThingClassId);
if (!meterThings.isEmpty()) {
qCDebug(dcHuawei()) << "Meter power changed" << powerMeterActivePower << "W";
@ -154,9 +226,9 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info)
});
// Battery 1
connect(connection, &HuaweiFusionSolar::lunaBattery1StatusChanged, this, [this, thing](HuaweiFusionSolar::BatteryDeviceStatus lunaBattery1Status){
connect(connection, &HuaweiModbusRtuConnection::lunaBattery1StatusChanged, this, [this, thing](HuaweiModbusRtuConnection::BatteryDeviceStatus lunaBattery1Status){
qCDebug(dcHuawei()) << "Battery 1 status changed" << lunaBattery1Status;
if (lunaBattery1Status != HuaweiFusionSolar::BatteryDeviceStatusOffline) {
if (lunaBattery1Status != HuaweiModbusRtuConnection::BatteryDeviceStatusOffline) {
// Check if w have to create the energy storage
Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId);
bool alreadySetUp = false;
@ -177,7 +249,7 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info)
}
});
connect(connection, &HuaweiFusionSolar::lunaBattery1PowerChanged, this, [this, thing](qint32 lunaBattery1Power){
connect(connection, &HuaweiModbusRtuConnection::lunaBattery1PowerChanged, this, [this, thing](qint32 lunaBattery1Power){
qCDebug(dcHuawei()) << "Battery 1 power changed" << lunaBattery1Power << "W";
Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 1);
if (!batteryThings.isEmpty()) {
@ -192,7 +264,7 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info)
}
});
connect(connection, &HuaweiFusionSolar::lunaBattery1SocChanged, this, [this, thing](float lunaBattery1Soc){
connect(connection, &HuaweiModbusRtuConnection::lunaBattery1SocChanged, this, [this, thing](float lunaBattery1Soc){
qCDebug(dcHuawei()) << "Battery 1 SOC changed" << lunaBattery1Soc << "%";
Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 1);
if (!batteryThings.isEmpty()) {
@ -202,9 +274,9 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info)
});
// Battery 2
connect(connection, &HuaweiFusionSolar::lunaBattery2StatusChanged, this, [this, thing](HuaweiFusionSolar::BatteryDeviceStatus lunaBattery1Status){
connect(connection, &HuaweiModbusRtuConnection::lunaBattery2StatusChanged, this, [this, thing](HuaweiModbusRtuConnection::BatteryDeviceStatus lunaBattery1Status){
qCDebug(dcHuawei()) << "Battery 2 status changed" << lunaBattery1Status;
if (lunaBattery1Status != HuaweiFusionSolar::BatteryDeviceStatusOffline) {
if (lunaBattery1Status != HuaweiModbusRtuConnection::BatteryDeviceStatusOffline) {
// Check if w have to create the energy storage
Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId);
bool alreadySetUp = false;
@ -225,7 +297,7 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info)
}
});
connect(connection, &HuaweiFusionSolar::lunaBattery2PowerChanged, this, [this, thing](qint32 lunaBattery2Power){
connect(connection, &HuaweiModbusRtuConnection::lunaBattery2PowerChanged, this, [this, thing](qint32 lunaBattery2Power){
qCDebug(dcHuawei()) << "Battery 2 power changed" << lunaBattery2Power << "W";
Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 2);
if (!batteryThings.isEmpty()) {
@ -241,7 +313,7 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info)
}
});
connect(connection, &HuaweiFusionSolar::lunaBattery2SocChanged, this, [this, thing](float lunaBattery2Soc){
connect(connection, &HuaweiModbusRtuConnection::lunaBattery2SocChanged, this, [this, thing](float lunaBattery2Soc){
qCDebug(dcHuawei()) << "Battery 2 SOC changed" << lunaBattery2Soc << "%";
Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 2);
if (!batteryThings.isEmpty()) {
@ -250,11 +322,12 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info)
}
});
m_connections.insert(thing, connection);
connection->connectDevice();
m_rtuConnections.insert(thing, connection);
connection->initialize();
// FIXME: make async and check if this is really a huawei
info->finish(Thing::ThingErrorNoError);
return;
}
if (thing->thingClassId() == huaweiMeterThingClassId) {
@ -264,7 +337,7 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info)
if (parentThing) {
thing->setStateValue("connected", parentThing->stateValue("connected").toBool());
}
return;
}
if (thing->thingClassId() == huaweiBatteryThingClassId) {
@ -274,12 +347,13 @@ void IntegrationPluginHuawei::setupThing(ThingSetupInfo *info)
if (parentThing) {
thing->setStateValue("connected", parentThing->stateValue("connected").toBool());
}
return;
}
}
void IntegrationPluginHuawei::postSetupThing(Thing *thing)
{
if (thing->thingClassId() == huaweiInverterThingClassId) {
if (thing->thingClassId() == huaweiFusionSolarInverterThingClassId || thing->thingClassId() == huaweiRtuInverterThingClassId) {
if (!m_pluginTimer) {
qCDebug(dcHuawei()) << "Starting plugin timer...";
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(5);
@ -289,6 +363,10 @@ void IntegrationPluginHuawei::postSetupThing(Thing *thing)
connection->update();
}
}
foreach(HuaweiModbusRtuConnection *connection, m_rtuConnections) {
connection->update();
}
});
m_pluginTimer->start();
@ -304,8 +382,16 @@ void IntegrationPluginHuawei::postSetupThing(Thing *thing)
void IntegrationPluginHuawei::thingRemoved(Thing *thing)
{
if (thing->thingClassId() == huaweiInverterThingClassId && m_connections.contains(thing)) {
if (thing->thingClassId() == huaweiFusionSolarInverterThingClassId && m_connections.contains(thing)) {
m_connections.take(thing)->deleteLater();
if (m_monitors.contains(thing)) {
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
}
}
if (thing->thingClassId() == huaweiRtuInverterThingClassId && m_rtuConnections.contains(thing)) {
m_rtuConnections.take(thing)->deleteLater();
}
if (myThings().isEmpty() && m_pluginTimer) {
@ -314,9 +400,171 @@ void IntegrationPluginHuawei::thingRemoved(Thing *thing)
}
}
void IntegrationPluginHuawei::executeAction(ThingActionInfo *info)
void IntegrationPluginHuawei::setupFusionSolar(ThingSetupInfo *info)
{
Thing *thing = info->thing();
NetworkDeviceMonitor *monitor = m_monitors.value(thing);
uint port = thing->paramValue(huaweiFusionSolarInverterThingPortParamTypeId).toUInt();
quint16 slaveId = thing->paramValue(huaweiFusionSolarInverterThingSlaveIdParamTypeId).toUInt();
HuaweiFusionSolar *connection = new HuaweiFusionSolar(monitor->networkDeviceInfo().address(), port, slaveId, this);
qCDebug(dcHuawei()) << "Finish setup huawei fusion solar dongle" << monitor->networkDeviceInfo().address().toString() << port << slaveId;
connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){
if (!thing->setupComplete())
return;
qCDebug(dcHuawei()) << "Network device monitor for" << thing->name() << (reachable ? "is now reachable" : "is not reachable any more" );
if (reachable) {
// Update address and refresh
thing->setStateValue("hostAddress", monitor->networkDeviceInfo().address().toString());
connection->setHostAddress(monitor->networkDeviceInfo().address());
connection->update();
}
});
connect(connection, &HuaweiFusionSolar::reachableChanged, this, [this, thing, connection](bool reachable){
qCDebug(dcHuawei()) << "Reachable changed to" << reachable << "for" << thing;
if (reachable) {
// Connected true will be set after successfull init
connection->initialize();
thing->setStateValue("connected", true);
} else {
thing->setStateValue("connected", false);
}
foreach (Thing *childThing, myThings().filterByParentId(thing->id())) {
childThing->setStateValue("connected", reachable);
}
});
connect(connection, &HuaweiFusionSolar::inverterActivePowerChanged, this, [thing](float inverterActivePower){
qCDebug(dcHuawei()) << "Inverter power changed" << inverterActivePower * -1000.0 << "W";
thing->setStateValue(huaweiFusionSolarInverterCurrentPowerStateTypeId, inverterActivePower * -1000.0);
});
connect(connection, &HuaweiFusionSolar::inverterDeviceStatusChanged, this, [thing](HuaweiFusionSolar::InverterDeviceStatus inverterDeviceStatus){
qCDebug(dcHuawei()) << "Inverter device status changed" << inverterDeviceStatus;
Q_UNUSED(thing)
});
connect(connection, &HuaweiFusionSolar::inverterEnergyProducedChanged, this, [thing](float inverterEnergyProduced){
qCDebug(dcHuawei()) << "Inverter total energy produced changed" << inverterEnergyProduced << "kWh";
thing->setStateValue(huaweiFusionSolarInverterTotalEnergyProducedStateTypeId, inverterEnergyProduced);
});
// Meter
connect(connection, &HuaweiFusionSolar::powerMeterActivePowerChanged, this, [this, thing](qint32 powerMeterActivePower){
Things meterThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiMeterThingClassId);
if (!meterThings.isEmpty()) {
qCDebug(dcHuawei()) << "Meter power changed" << powerMeterActivePower << "W";
// Note: > 0 -> return, < 0 consume
meterThings.first()->setStateValue(huaweiMeterCurrentPowerStateTypeId, -powerMeterActivePower);
}
});
// Battery 1
connect(connection, &HuaweiFusionSolar::lunaBattery1StatusChanged, this, [this, thing](HuaweiFusionSolar::BatteryDeviceStatus lunaBattery1Status){
qCDebug(dcHuawei()) << "Battery 1 status changed" << lunaBattery1Status;
if (lunaBattery1Status != HuaweiFusionSolar::BatteryDeviceStatusOffline) {
// Check if w have to create the energy storage
Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId);
bool alreadySetUp = false;
foreach (Thing *batteryThing, batteryThings) {
if (batteryThing->paramValue(huaweiBatteryThingUnitParamTypeId).toUInt() == 1) {
alreadySetUp = true;
}
}
if (!alreadySetUp) {
qCDebug(dcHuawei()) << "Set up huawei energy storage 1 for" << thing;
ThingDescriptor descriptor(huaweiBatteryThingClassId, "Luna 2000 Battery", QString(), thing->id());
ParamList params;
params.append(Param(huaweiBatteryThingUnitParamTypeId, 1));
descriptor.setParams(params);
emit autoThingsAppeared(ThingDescriptors() << descriptor);
}
}
});
connect(connection, &HuaweiFusionSolar::lunaBattery1PowerChanged, this, [this, thing](qint32 lunaBattery1Power){
qCDebug(dcHuawei()) << "Battery 1 power changed" << lunaBattery1Power << "W";
Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 1);
if (!batteryThings.isEmpty()) {
batteryThings.first()->setStateValue(huaweiBatteryCurrentPowerStateTypeId, lunaBattery1Power);
if (lunaBattery1Power < 0) {
batteryThings.first()->setStateValue(huaweiBatteryChargingStateStateTypeId, "discharging");
} else if (lunaBattery1Power > 0) {
batteryThings.first()->setStateValue(huaweiBatteryChargingStateStateTypeId, "charging");
} else {
batteryThings.first()->setStateValue(huaweiBatteryChargingStateStateTypeId, "idle");
}
}
});
connect(connection, &HuaweiFusionSolar::lunaBattery1SocChanged, this, [this, thing](float lunaBattery1Soc){
qCDebug(dcHuawei()) << "Battery 1 SOC changed" << lunaBattery1Soc << "%";
Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 1);
if (!batteryThings.isEmpty()) {
batteryThings.first()->setStateValue(huaweiBatteryBatteryLevelStateTypeId, lunaBattery1Soc);
batteryThings.first()->setStateValue(huaweiBatteryBatteryCriticalStateTypeId, lunaBattery1Soc < 10);
}
});
// Battery 2
connect(connection, &HuaweiFusionSolar::lunaBattery2StatusChanged, this, [this, thing](HuaweiFusionSolar::BatteryDeviceStatus lunaBattery1Status){
qCDebug(dcHuawei()) << "Battery 2 status changed" << lunaBattery1Status;
if (lunaBattery1Status != HuaweiFusionSolar::BatteryDeviceStatusOffline) {
// Check if w have to create the energy storage
Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId);
bool alreadySetUp = false;
foreach (Thing *batteryThing, batteryThings) {
if (batteryThing->paramValue(huaweiBatteryThingUnitParamTypeId).toUInt() == 2) {
alreadySetUp = true;
}
}
if (!alreadySetUp) {
qCDebug(dcHuawei()) << "Set up huawei energy storage 2 for" << thing;
ThingDescriptor descriptor(huaweiBatteryThingClassId, "Luna 2000 Battery", QString(), thing->id());
ParamList params;
params.append(Param(huaweiBatteryThingUnitParamTypeId, 2));
descriptor.setParams(params);
emit autoThingsAppeared(ThingDescriptors() << descriptor);
}
}
});
connect(connection, &HuaweiFusionSolar::lunaBattery2PowerChanged, this, [this, thing](qint32 lunaBattery2Power){
qCDebug(dcHuawei()) << "Battery 2 power changed" << lunaBattery2Power << "W";
Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 2);
if (!batteryThings.isEmpty()) {
batteryThings.first()->setStateValue(huaweiBatteryCurrentPowerStateTypeId, lunaBattery2Power);
if (lunaBattery2Power < 0) {
batteryThings.first()->setStateValue(huaweiBatteryChargingStateStateTypeId, "discharging");
} else if (lunaBattery2Power > 0) {
batteryThings.first()->setStateValue(huaweiBatteryChargingStateStateTypeId, "charging");
} else {
batteryThings.first()->setStateValue(huaweiBatteryChargingStateStateTypeId, "idle");
}
}
});
connect(connection, &HuaweiFusionSolar::lunaBattery2SocChanged, this, [this, thing](float lunaBattery2Soc){
qCDebug(dcHuawei()) << "Battery 2 SOC changed" << lunaBattery2Soc << "%";
Things batteryThings = myThings().filterByParentId(thing->id()).filterByThingClassId(huaweiBatteryThingClassId).filterByParam(huaweiBatteryThingUnitParamTypeId, 2);
if (!batteryThings.isEmpty()) {
batteryThings.first()->setStateValue(huaweiBatteryBatteryLevelStateTypeId, lunaBattery2Soc);
batteryThings.first()->setStateValue(huaweiBatteryBatteryCriticalStateTypeId, lunaBattery2Soc < 10);
}
});
m_connections.insert(thing, connection);
connection->connectDevice();
// FIXME: make async and check if this is really a huawei
info->finish(Thing::ThingErrorNoError);
}

View File

@ -33,8 +33,11 @@
#include <plugintimer.h>
#include <integrations/integrationplugin.h>
#include <network/networkdevicediscovery.h>
#include "extern-plugininfo.h"
#include "huaweifusionsolar.h"
#include "huaweimodbusrtuconnection.h"
class IntegrationPluginHuawei: public IntegrationPlugin
{
@ -47,16 +50,18 @@ public:
explicit IntegrationPluginHuawei();
void discoverThings(ThingDiscoveryInfo *info) override;
void startMonitoringAutoThings() override;
void setupThing(ThingSetupInfo *info) override;
void postSetupThing(Thing *thing) override;
void thingRemoved(Thing *thing) override;
void executeAction(ThingActionInfo *info) override;
private:
PluginTimer *m_pluginTimer = nullptr;
QHash<Thing *, NetworkDeviceMonitor *> m_monitors;
QHash<Thing *, HuaweiFusionSolar *> m_connections;
QHash<Thing *, HuaweiModbusRtuConnection *> m_rtuConnections;
void setupFusionSolar(ThingSetupInfo *info);
};
#endif // INTEGRATIONPLUGINHUAWEI_H

View File

@ -9,21 +9,13 @@
"id": "f654c99d-a286-4abb-b33e-1a71843d8da0",
"thingClasses": [
{
"name": "huaweiInverter",
"name": "huaweiFusionSolarInverter",
"displayName": "Huawei FusionSolar Inverter",
"id": "87e75ee0-d544-457b-add3-bd4e58160fcd",
"createMethods": ["discovery", "user"],
"interfaces": ["solarinverter", "connectable"],
"providedInterfaces": [ "solarinverter", "energymeter", "energystorage"],
"paramTypes": [
{
"id": "d93371db-0954-4dcd-a1a5-6881b78cb0ea",
"name": "ipAddress",
"displayName": "IP address",
"type": "QString",
"inputType": "IPv4Address",
"defaultValue": "127.0.0.1"
},
{
"id": "93517bff-1928-4c4a-8207-5fe596c86eba",
"name":"macAddress",
@ -79,6 +71,71 @@
],
"actionTypes": [ ]
},
{
"name": "huaweiRtuInverter",
"displayName": "Huawei Inverter",
"id": "77558007-5076-4ca6-bd46-169f215c3e29",
"createMethods": ["discovery"],
"interfaces": ["solarinverter", "connectable"],
"providedInterfaces": [ "solarinverter", "energymeter", "energystorage"],
"discoveryParamTypes": [
{
"id": "93a4d3a8-c7d0-470b-b6e3-d8fc43b8e8d0",
"name": "slaveAddress",
"displayName": "Slave address",
"type": "int",
"defaultValue": 1
}
],
"paramTypes": [
{
"id": "5c7b28b1-3691-452e-8f49-d80ae7bcbe2c",
"name": "modbusMasterUuid",
"displayName": "Modbus RTU master",
"type": "QUuid",
"defaultValue": "",
"readOnly": true
},
{
"id": "de06f027-7940-4c45-9c96-30930ac2796d",
"name": "slaveAddress",
"displayName": "Modbus slave address",
"type": "uint",
"defaultValue": 1
}
],
"stateTypes": [
{
"id": "191ffa22-de6f-4325-8698-56b817f78df5",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "6064d90e-1b6b-40fd-9da0-6ebc713efb7d",
"name": "currentPower",
"displayName": "Active power",
"displayNameEvent": "Active power changed",
"type": "double",
"unit": "Watt",
"defaultValue": 0,
"cached": false
},
{
"id": "49b92919-301c-4ff7-ae63-0c1a2184e3f4",
"name": "totalEnergyProduced",
"displayName": "AC energy",
"displayNameEvent": "AC energy changed",
"type": "double",
"unit": "KiloWattHour",
"defaultValue": 0.00
}
],
"actionTypes": [ ]
},
{
"name": "huaweiMeter",
"displayName": "Huawei Meter",