Merge PR #213: Wattsonic: Refactor connection structure and improve gen2 / gen3 support
This commit is contained in:
commit
525e4ac3fc
@ -1,4 +1,44 @@
|
|||||||
# Wattsonic
|
# Wattsonic
|
||||||
|
|
||||||
This plugin allows to connect wattsonic hybrid inverters to nymea
|
This plugin allows to connect wattsonic hybrid inverters to nymea. If there are any meters and energy storages connected to the system, they should be detected automatically.
|
||||||
|
|
||||||
|
## Supported inverters
|
||||||
|
|
||||||
|
Currently all `Gen2` and `Gen3` single and three phase inverters should be supported.
|
||||||
|
|
||||||
|
The full list of supported models can be found here:
|
||||||
|
|
||||||
|
Single Phase Hybrid
|
||||||
|
|
||||||
|
* 3.0K-30A-1P
|
||||||
|
* 3.6K-30A-1P
|
||||||
|
* 4.2K-30A-1P
|
||||||
|
* 4.6K-30A-1P
|
||||||
|
* 5.0K-30A-1P
|
||||||
|
* 6.0K-30A-1P
|
||||||
|
* 7.0K-30A-1P
|
||||||
|
* 8.0K-30A-1P
|
||||||
|
* 3.0K-30A-1P-S
|
||||||
|
* 3.6K-30A-1P-S
|
||||||
|
|
||||||
|
Three Phase Hybrid
|
||||||
|
|
||||||
|
* 4.0K-25A-3P
|
||||||
|
* 5.0K-25A-3P
|
||||||
|
* 6.0K-25A-3P
|
||||||
|
* 8.0K-25A-3P
|
||||||
|
* 10K-25A-3P
|
||||||
|
* 12K-25A-3P
|
||||||
|
* 10K-40A-3P
|
||||||
|
* 12K-40A-3P
|
||||||
|
* 15K-40A-3P
|
||||||
|
* 20K-40A-3P
|
||||||
|
|
||||||
|
|
||||||
|
Three Phase Hybrid (25-50K)
|
||||||
|
|
||||||
|
* 25K-100A-3P
|
||||||
|
* 30K-100A-3P
|
||||||
|
* 36K-100A-3P
|
||||||
|
* 40K-100A-3P
|
||||||
|
* 50K-100A-3P
|
||||||
|
|||||||
@ -54,8 +54,8 @@ void IntegrationPluginWattsonic::discoverThings(ThingDiscoveryInfo *info)
|
|||||||
|
|
||||||
foreach (const WattsonicDiscovery::Result &result, discovery->discoveryResults()) {
|
foreach (const WattsonicDiscovery::Result &result, discovery->discoveryResults()) {
|
||||||
|
|
||||||
QString name = "Wattsonic inverter (" + result.serialNumber + ")";
|
QString name = "Wattsonic " + result.inverterInfo.type + + " " + result.inverterInfo.model;
|
||||||
ThingDescriptor descriptor(inverterThingClassId, name, QString("Modbus ID: %1").arg(result.slaveId));
|
ThingDescriptor descriptor(inverterThingClassId, name, QString("Serial number: %1").arg(result.serialNumber));
|
||||||
qCDebug(dcWattsonic()) << "Discovered:" << descriptor.title() << descriptor.description();
|
qCDebug(dcWattsonic()) << "Discovered:" << descriptor.title() << descriptor.description();
|
||||||
|
|
||||||
ParamList params {
|
ParamList params {
|
||||||
@ -157,16 +157,16 @@ void IntegrationPluginWattsonic::setupWattsonicConnection(ThingSetupInfo *info)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
WattsonicModbusRtuConnection *connection = new WattsonicModbusRtuConnection(hardwareManager()->modbusRtuResource()->getModbusRtuMaster(uuid), slaveId, this);
|
WattsonicInverter *connection = new WattsonicInverter(hardwareManager()->modbusRtuResource()->getModbusRtuMaster(uuid), slaveId, this);
|
||||||
m_connections.insert(thing, connection);
|
m_connections.insert(thing, connection);
|
||||||
|
|
||||||
// Only for setup
|
// Only for setup
|
||||||
connect(info, &ThingSetupInfo::aborted, connection, &WattsonicModbusRtuConnection::deleteLater);
|
connect(info, &ThingSetupInfo::aborted, connection, &WattsonicInverter::deleteLater);
|
||||||
connect(info, &ThingSetupInfo::aborted, this, [=](){
|
connect(info, &ThingSetupInfo::aborted, this, [=](){
|
||||||
m_connections.take(info->thing())->deleteLater();
|
m_connections.take(info->thing())->deleteLater();
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(connection, &WattsonicModbusRtuConnection::initializationFinished, info, [=](bool success){
|
connect(connection, &WattsonicInverter::customInitializationFinished, info, [=](bool success){
|
||||||
qCDebug(dcWattsonic()) << "Initialization finished" << success;
|
qCDebug(dcWattsonic()) << "Initialization finished" << success;
|
||||||
if (info->isInitialSetup() && !success) {
|
if (info->isInitialSetup() && !success) {
|
||||||
m_connections.take(info->thing())->deleteLater();
|
m_connections.take(info->thing())->deleteLater();
|
||||||
@ -179,7 +179,7 @@ void IntegrationPluginWattsonic::setupWattsonicConnection(ThingSetupInfo *info)
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Runtime connections
|
// Runtime connections
|
||||||
connect(connection, &WattsonicModbusRtuConnection::reachableChanged, thing, [connection, thing, this](bool reachable){
|
connect(connection, &WattsonicInverter::reachableChanged, thing, [connection, thing, this](bool reachable){
|
||||||
qCDebug(dcWattsonic()) << "Reachable state changed" << reachable;
|
qCDebug(dcWattsonic()) << "Reachable state changed" << reachable;
|
||||||
if (reachable) {
|
if (reachable) {
|
||||||
connection->initialize();
|
connection->initialize();
|
||||||
@ -188,11 +188,11 @@ void IntegrationPluginWattsonic::setupWattsonicConnection(ThingSetupInfo *info)
|
|||||||
setConnectedStates(thing, reachable);
|
setConnectedStates(thing, reachable);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(connection, &WattsonicModbusRtuConnection::reachableChanged, thing, [this, thing](bool reachable){
|
connect(connection, &WattsonicInverter::reachableChanged, thing, [this, thing](bool reachable){
|
||||||
setConnectedStates(thing, reachable);
|
setConnectedStates(thing, reachable);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(connection, &WattsonicModbusRtuConnection::updateFinished, thing, [this, connection, thing](){
|
connect(connection, &WattsonicInverter::updateFinished, thing, [this, connection, thing](){
|
||||||
qCDebug(dcWattsonic()) << "Update finished:" << thing->name() << connection;
|
qCDebug(dcWattsonic()) << "Update finished:" << thing->name() << connection;
|
||||||
|
|
||||||
// We received an update, make sure we are connected
|
// We received an update, make sure we are connected
|
||||||
@ -207,7 +207,13 @@ void IntegrationPluginWattsonic::setupWattsonicConnection(ThingSetupInfo *info)
|
|||||||
|
|
||||||
// Check if a meter is connected or not. We detect a meter by reading the totalEnergyPurchasedFromGrid totalEnergyInjectedToGrid registers,
|
// Check if a meter is connected or not. We detect a meter by reading the totalEnergyPurchasedFromGrid totalEnergyInjectedToGrid registers,
|
||||||
// As of now, there is no other way to detect the presence of the meter. Voltage is always there, even if there is no meter connected.
|
// As of now, there is no other way to detect the presence of the meter. Voltage is always there, even if there is no meter connected.
|
||||||
bool meterDetected = connection->totalEnergyPurchasedFromGrid() != 0 || connection->totalEnergyInjectedToGrid() != 0;
|
|
||||||
|
// Some meters do not have the total counters, for whatever reason...
|
||||||
|
bool meterDetected = connection->totalEnergyInjectedToGrid() != 0 ||
|
||||||
|
connection->totalEnergyPurchasedFromGrid() != 0 ||
|
||||||
|
connection->phaseAPower() != 0 ||
|
||||||
|
connection->phaseBPower() != 0 ||
|
||||||
|
connection->phaseCPower() != 0;
|
||||||
|
|
||||||
if (meterDetected) {
|
if (meterDetected) {
|
||||||
if (meters.isEmpty()) {
|
if (meters.isEmpty()) {
|
||||||
@ -232,21 +238,21 @@ void IntegrationPluginWattsonic::setupWattsonicConnection(ThingSetupInfo *info)
|
|||||||
if (!meters.isEmpty()) {
|
if (!meters.isEmpty()) {
|
||||||
Thing *meter = meters.first();
|
Thing *meter = meters.first();
|
||||||
meter->setStateValue(meterCurrentPowerStateTypeId, connection->totalPowerOnMeter() * -1.0);
|
meter->setStateValue(meterCurrentPowerStateTypeId, connection->totalPowerOnMeter() * -1.0);
|
||||||
meter->setStateValue(meterTotalEnergyConsumedStateTypeId, connection->totalEnergyPurchasedFromGrid() / 10.0);
|
meter->setStateValue(meterTotalEnergyConsumedStateTypeId, connection->totalEnergyPurchasedFromGrid());
|
||||||
meter->setStateValue(meterTotalEnergyProducedStateTypeId, connection->totalEnergyInjectedToGrid() / 10.0);
|
meter->setStateValue(meterTotalEnergyProducedStateTypeId, connection->totalEnergyInjectedToGrid());
|
||||||
meter->setStateValue(meterCurrentPowerPhaseAStateTypeId, connection->phaseAPower() * -1.0);
|
meter->setStateValue(meterCurrentPowerPhaseAStateTypeId, connection->phaseAPower() * -1.0);
|
||||||
meter->setStateValue(meterCurrentPowerPhaseBStateTypeId, connection->phaseBPower() * -1.0);
|
meter->setStateValue(meterCurrentPowerPhaseBStateTypeId, connection->phaseBPower() * -1.0);
|
||||||
meter->setStateValue(meterCurrentPowerPhaseCStateTypeId, connection->phaseCPower() * -1.0);
|
meter->setStateValue(meterCurrentPowerPhaseCStateTypeId, connection->phaseCPower() * -1.0);
|
||||||
meter->setStateValue(meterVoltagePhaseAStateTypeId, connection->gridPhaseAVoltage() / 10.0);
|
meter->setStateValue(meterVoltagePhaseAStateTypeId, connection->gridPhaseAVoltage());
|
||||||
meter->setStateValue(meterVoltagePhaseBStateTypeId, connection->gridPhaseBVoltage() / 10.0);
|
meter->setStateValue(meterVoltagePhaseBStateTypeId, connection->gridPhaseBVoltage());
|
||||||
meter->setStateValue(meterVoltagePhaseCStateTypeId, connection->gridPhaseCVoltage() / 10.0);
|
meter->setStateValue(meterVoltagePhaseCStateTypeId, connection->gridPhaseCVoltage());
|
||||||
// The phase current registers don't seem to contain proper values. Calculating ourselves instead
|
// The phase current registers don't seem to contain proper values. Calculating ourselves instead
|
||||||
// meter->setStateValue(meterCurrentPhaseAStateTypeId, connection->gridPhaseACurrent() / 10.0);
|
// meter->setStateValue(meterCurrentPhaseAStateTypeId, connection->gridPhaseACurrent());
|
||||||
// meter->setStateValue(meterCurrentPhaseBStateTypeId, connection->gridPhaseBCurrent() / 10.0);
|
// meter->setStateValue(meterCurrentPhaseBStateTypeId, connection->gridPhaseBCurrent());
|
||||||
// meter->setStateValue(meterCurrentPhaseCStateTypeId, connection->gridPhaseCCurrent() / 10.0);
|
// meter->setStateValue(meterCurrentPhaseCStateTypeId, connection->gridPhaseCCurrent());
|
||||||
meter->setStateValue(meterCurrentPhaseAStateTypeId, (connection->phaseAPower() * -1.0) / (connection->gridPhaseAVoltage() / 10.0));
|
meter->setStateValue(meterCurrentPhaseAStateTypeId, (connection->phaseAPower() * -1.0) / connection->gridPhaseAVoltage());
|
||||||
meter->setStateValue(meterCurrentPhaseBStateTypeId, (connection->phaseBPower() * -1.0) / (connection->gridPhaseBVoltage() / 10.0));
|
meter->setStateValue(meterCurrentPhaseBStateTypeId, (connection->phaseBPower() * -1.0) / connection->gridPhaseBVoltage());
|
||||||
meter->setStateValue(meterCurrentPhaseCStateTypeId, (connection->phaseCPower() * -1.0) / (connection->gridPhaseCVoltage() / 10.0));
|
meter->setStateValue(meterCurrentPhaseCStateTypeId, (connection->phaseCPower() * -1.0) / connection->gridPhaseCVoltage());
|
||||||
qCInfo(dcWattsonic()) << "Updating meter:" << meter->stateValue(meterCurrentPowerStateTypeId).toDouble() << "W" << meter->stateValue(meterTotalEnergyProducedStateTypeId).toDouble() << "kWh";
|
qCInfo(dcWattsonic()) << "Updating meter:" << meter->stateValue(meterCurrentPowerStateTypeId).toDouble() << "W" << meter->stateValue(meterTotalEnergyProducedStateTypeId).toDouble() << "kWh";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,14 +279,15 @@ void IntegrationPluginWattsonic::setupWattsonicConnection(ThingSetupInfo *info)
|
|||||||
|
|
||||||
if (!batteries.isEmpty() && connection->SOC() > 0) {
|
if (!batteries.isEmpty() && connection->SOC() > 0) {
|
||||||
Thing *battery = batteries.first();
|
Thing *battery = batteries.first();
|
||||||
QHash<WattsonicModbusRtuConnection::BatteryMode, QString> map {
|
QHash<WattsonicInverter::BatteryMode, QString> map {
|
||||||
{WattsonicModbusRtuConnection::BatteryModeDischarge, "discharging"},
|
{WattsonicInverter::BatteryModeDischarge, "discharging"},
|
||||||
{WattsonicModbusRtuConnection::BatteryModeCharge, "charging"}
|
{WattsonicInverter::BatteryModeCharge, "charging"}
|
||||||
};
|
};
|
||||||
|
|
||||||
battery->setStateValue(batteryChargingStateStateTypeId, map.value(connection->batteryMode()));
|
battery->setStateValue(batteryChargingStateStateTypeId, map.value(connection->batteryMode()));
|
||||||
battery->setStateValue(batteryCurrentPowerStateTypeId, connection->batteryPower() * -1.0);
|
battery->setStateValue(batteryCurrentPowerStateTypeId, connection->batteryPower() * -1.0);
|
||||||
battery->setStateValue(batteryBatteryLevelStateTypeId, connection->SOC() / 100.0);
|
battery->setStateValue(batteryBatteryLevelStateTypeId, connection->SOC());
|
||||||
battery->setStateValue(batteryBatteryCriticalStateTypeId, connection->SOC() < 500);
|
battery->setStateValue(batteryBatteryCriticalStateTypeId, connection->SOC() < 5);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
*
|
*
|
||||||
* Copyright 2013 - 2024, nymea GmbH
|
* Copyright 2013 - 2025, nymea GmbH
|
||||||
* Contact: contact@nymea.io
|
* Contact: contact@nymea.io
|
||||||
*
|
*
|
||||||
* This file is part of nymea.
|
* This file is part of nymea.
|
||||||
@ -36,8 +36,7 @@
|
|||||||
#include <network/networkdevicemonitor.h>
|
#include <network/networkdevicemonitor.h>
|
||||||
|
|
||||||
#include "extern-plugininfo.h"
|
#include "extern-plugininfo.h"
|
||||||
|
#include "wattsonicinverter.h"
|
||||||
#include "wattsonicmodbusrtuconnection.h"
|
|
||||||
|
|
||||||
class IntegrationPluginWattsonic: public IntegrationPlugin
|
class IntegrationPluginWattsonic: public IntegrationPlugin
|
||||||
{
|
{
|
||||||
@ -58,7 +57,7 @@ private:
|
|||||||
void setupWattsonicConnection(ThingSetupInfo *info);
|
void setupWattsonicConnection(ThingSetupInfo *info);
|
||||||
|
|
||||||
PluginTimer *m_pluginTimer = nullptr;
|
PluginTimer *m_pluginTimer = nullptr;
|
||||||
QHash<Thing *, WattsonicModbusRtuConnection *> m_connections;
|
QHash<Thing *, WattsonicInverter *> m_connections;
|
||||||
|
|
||||||
void setConnectedStates(Thing * thing, bool connected);
|
void setConnectedStates(Thing * thing, bool connected);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -48,8 +48,7 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"blocks": [
|
"blocks": [ ],
|
||||||
],
|
|
||||||
"registers": [
|
"registers": [
|
||||||
{
|
{
|
||||||
"id": "serialNumber",
|
"id": "serialNumber",
|
||||||
@ -61,6 +60,16 @@
|
|||||||
"description": "Serial number",
|
"description": "Serial number",
|
||||||
"access": "RO"
|
"access": "RO"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "equipmentInfo",
|
||||||
|
"address": 10008,
|
||||||
|
"size": 1,
|
||||||
|
"type": "uint16",
|
||||||
|
"readSchedule": "init",
|
||||||
|
"registerType": "holdingRegister",
|
||||||
|
"description": "EquipmentInfo",
|
||||||
|
"access": "RO"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "firmwareVersion",
|
"id": "firmwareVersion",
|
||||||
"address": 10011,
|
"address": 10011,
|
||||||
@ -140,7 +149,8 @@
|
|||||||
"registerType": "holdingRegister",
|
"registerType": "holdingRegister",
|
||||||
"description": "Total grid injection energy on meter",
|
"description": "Total grid injection energy on meter",
|
||||||
"defaultValue": 0,
|
"defaultValue": 0,
|
||||||
"unit": "1/100 kWh",
|
"staticScaleFactor": -2,
|
||||||
|
"unit": "kWh",
|
||||||
"access": "RO"
|
"access": "RO"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -152,7 +162,8 @@
|
|||||||
"registerType": "holdingRegister",
|
"registerType": "holdingRegister",
|
||||||
"description": "Total purchasing energy from grid on meter",
|
"description": "Total purchasing energy from grid on meter",
|
||||||
"defaultValue": 0,
|
"defaultValue": 0,
|
||||||
"unit": "1/100 kWh",
|
"staticScaleFactor": -2,
|
||||||
|
"unit": "kWh",
|
||||||
"access": "RO"
|
"access": "RO"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -164,7 +175,8 @@
|
|||||||
"registerType": "holdingRegister",
|
"registerType": "holdingRegister",
|
||||||
"description": "Grid Phase A Voltage",
|
"description": "Grid Phase A Voltage",
|
||||||
"defaultValue": 0,
|
"defaultValue": 0,
|
||||||
"unit": "1/10 V",
|
"staticScaleFactor": -1,
|
||||||
|
"unit": "V",
|
||||||
"access": "RO"
|
"access": "RO"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -176,7 +188,8 @@
|
|||||||
"registerType": "holdingRegister",
|
"registerType": "holdingRegister",
|
||||||
"description": "Grid Phase A Current",
|
"description": "Grid Phase A Current",
|
||||||
"defaultValue": 0,
|
"defaultValue": 0,
|
||||||
"unit": "1/10 A",
|
"staticScaleFactor": -1,
|
||||||
|
"unit": "A",
|
||||||
"access": "RO"
|
"access": "RO"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -188,7 +201,8 @@
|
|||||||
"registerType": "holdingRegister",
|
"registerType": "holdingRegister",
|
||||||
"description": "Grid Phase B Voltage",
|
"description": "Grid Phase B Voltage",
|
||||||
"defaultValue": 0,
|
"defaultValue": 0,
|
||||||
"unit": "1/10 V",
|
"staticScaleFactor": -1,
|
||||||
|
"unit": "V",
|
||||||
"access": "RO"
|
"access": "RO"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -200,7 +214,8 @@
|
|||||||
"registerType": "holdingRegister",
|
"registerType": "holdingRegister",
|
||||||
"description": "Grid Phase B Current",
|
"description": "Grid Phase B Current",
|
||||||
"defaultValue": 0,
|
"defaultValue": 0,
|
||||||
"unit": "1/10 A",
|
"staticScaleFactor": -1,
|
||||||
|
"unit": "A",
|
||||||
"access": "RO"
|
"access": "RO"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -212,7 +227,8 @@
|
|||||||
"registerType": "holdingRegister",
|
"registerType": "holdingRegister",
|
||||||
"description": "Grid Phase C Voltage",
|
"description": "Grid Phase C Voltage",
|
||||||
"defaultValue": 0,
|
"defaultValue": 0,
|
||||||
"unit": "1/10 V",
|
"staticScaleFactor": -1,
|
||||||
|
"unit": "V",
|
||||||
"access": "RO"
|
"access": "RO"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -224,7 +240,8 @@
|
|||||||
"registerType": "holdingRegister",
|
"registerType": "holdingRegister",
|
||||||
"description": "Grid Phase C Current",
|
"description": "Grid Phase C Current",
|
||||||
"defaultValue": 0,
|
"defaultValue": 0,
|
||||||
"unit": "1/10 A",
|
"staticScaleFactor": -1,
|
||||||
|
"unit": "A",
|
||||||
"access": "RO"
|
"access": "RO"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -248,7 +265,8 @@
|
|||||||
"registerType": "holdingRegister",
|
"registerType": "holdingRegister",
|
||||||
"description": "Total PV Generation from installation",
|
"description": "Total PV Generation from installation",
|
||||||
"defaultValue": 0,
|
"defaultValue": 0,
|
||||||
"unit": "1/10 kWh",
|
"staticScaleFactor": -1,
|
||||||
|
"unit": "kWh",
|
||||||
"access": "RO"
|
"access": "RO"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -263,111 +281,180 @@
|
|||||||
"unit": "W",
|
"unit": "W",
|
||||||
"access": "RO"
|
"access": "RO"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"id": "totalBackupP",
|
"id": "batteryVoltageDcGen3",
|
||||||
"address": 30230,
|
|
||||||
"size": 2,
|
|
||||||
"type": "int32",
|
|
||||||
"readSchedule": "update",
|
|
||||||
"registerType": "holdingRegister",
|
|
||||||
"description": "Total_Backup_P/AC Active Power",
|
|
||||||
"unit": "W",
|
|
||||||
"defaultValue": 0,
|
|
||||||
"access": "RO"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "batteryVoltageDc",
|
|
||||||
"address": 30254,
|
"address": 30254,
|
||||||
"size": 1,
|
"size": 1,
|
||||||
"type": "uint16",
|
"type": "uint16",
|
||||||
"readSchedule": "update",
|
"readSchedule": "update",
|
||||||
"registerType": "holdingRegister",
|
"registerType": "holdingRegister",
|
||||||
"description": "Battery voltage DC",
|
"description": "Battery voltage DC (gen3)",
|
||||||
"defaultValue": 0,
|
"defaultValue": 0,
|
||||||
"unit": "1/10 V",
|
"staticScaleFactor": -1,
|
||||||
|
"unit": "V",
|
||||||
"access": "RO"
|
"access": "RO"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "batteryMode",
|
"id": "batteryModeGen3",
|
||||||
"address": 30256,
|
"address": 30256,
|
||||||
"size": 1,
|
"size": 1,
|
||||||
"type": "uint16",
|
"type": "uint16",
|
||||||
"readSchedule": "update",
|
"readSchedule": "update",
|
||||||
"registerType": "holdingRegister",
|
"registerType": "holdingRegister",
|
||||||
"description": "Battery mode",
|
"description": "Battery mode (gen3)",
|
||||||
"enum": "BatteryMode",
|
"enum": "BatteryMode",
|
||||||
"defaultValue": "BatteryModeDischarge",
|
"defaultValue": "BatteryModeDischarge",
|
||||||
"access": "RO"
|
"access": "RO"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "batteryPower",
|
"id": "batteryPowerGen3",
|
||||||
"address": 30258,
|
"address": 30258,
|
||||||
"size": 2,
|
"size": 2,
|
||||||
"type": "int32",
|
"type": "int32",
|
||||||
"readSchedule": "update",
|
"readSchedule": "update",
|
||||||
"registerType": "holdingRegister",
|
"registerType": "holdingRegister",
|
||||||
"description": "Battery power",
|
"description": "Battery power (gen3)",
|
||||||
"defaultValue": 0,
|
"defaultValue": 0,
|
||||||
"unit": "W",
|
"unit": "W",
|
||||||
"access": "RO"
|
"access": "RO"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "totalEnergyInjectedToGrid",
|
"id": "totalEnergyInjectedToGridGen3",
|
||||||
"address": 31102,
|
"address": 31102,
|
||||||
"size": 2,
|
"size": 2,
|
||||||
"type": "uint32",
|
"type": "uint32",
|
||||||
"readSchedule": "update",
|
"readSchedule": "update",
|
||||||
"registerType": "holdingRegister",
|
"registerType": "holdingRegister",
|
||||||
"description": "Total energy injected to grid",
|
"description": "Total energy injected to grid (gen3)",
|
||||||
"defaultValue": 0,
|
"defaultValue": 0,
|
||||||
"unit": "1/10 kWh",
|
"staticScaleFactor": -1,
|
||||||
|
"unit": "kWh",
|
||||||
"access": "RO"
|
"access": "RO"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "totalEnergyPurchasedFromGrid",
|
"id": "totalEnergyPurchasedFromGridGen3",
|
||||||
"address": 31104,
|
"address": 31104,
|
||||||
"size": 2,
|
"size": 2,
|
||||||
"type": "uint32",
|
"type": "uint32",
|
||||||
"readSchedule": "update",
|
"readSchedule": "update",
|
||||||
"registerType": "holdingRegister",
|
"registerType": "holdingRegister",
|
||||||
"description": "Total energy purchased from grid",
|
"description": "Total energy purchased from grid (gen3)",
|
||||||
"defaultValue": 0,
|
"defaultValue": 0,
|
||||||
"unit": "1/10 kWh",
|
"staticScaleFactor": -1,
|
||||||
|
"unit": "kWh",
|
||||||
"access": "RO"
|
"access": "RO"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "batteryStrings",
|
"id": "SOCGen3",
|
||||||
"address": 32001,
|
|
||||||
"size": 1,
|
|
||||||
"type": "uint16",
|
|
||||||
"readSchedule": "update",
|
|
||||||
"registerType": "holdingRegister",
|
|
||||||
"description": "Battery strings",
|
|
||||||
"defaultValue": 0,
|
|
||||||
"access": "RO"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "SOC",
|
|
||||||
"address": 33000,
|
"address": 33000,
|
||||||
"size": 1,
|
"size": 1,
|
||||||
"type": "uint16",
|
"type": "uint16",
|
||||||
"readSchedule": "update",
|
"readSchedule": "update",
|
||||||
"registerType": "holdingRegister",
|
"registerType": "holdingRegister",
|
||||||
"description": "SOC",
|
"description": "SOC (gen3)",
|
||||||
"defaultValue": 0,
|
"defaultValue": 0,
|
||||||
"unit": "% * 100",
|
"staticScaleFactor": -2,
|
||||||
|
"unit": "%",
|
||||||
"access": "RO"
|
"access": "RO"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "SOH",
|
"id": "SOHGen3",
|
||||||
"address": 33001,
|
"address": 33001,
|
||||||
"size": 1,
|
"size": 1,
|
||||||
"type": "uint16",
|
"type": "uint16",
|
||||||
"readSchedule": "update",
|
"readSchedule": "update",
|
||||||
"registerType": "holdingRegister",
|
"registerType": "holdingRegister",
|
||||||
"description": "SOH",
|
"description": "SOH (gen3)",
|
||||||
"defaultValue": 0,
|
"defaultValue": 0,
|
||||||
"unit": "% * 100",
|
"staticScaleFactor": -2,
|
||||||
|
"unit": "%",
|
||||||
|
"access": "RO"
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": "batteryVoltageDcGen2",
|
||||||
|
"address": 40254,
|
||||||
|
"size": 1,
|
||||||
|
"type": "uint16",
|
||||||
|
"readSchedule": "update",
|
||||||
|
"registerType": "holdingRegister",
|
||||||
|
"description": "Battery voltage DC (gen2)",
|
||||||
|
"defaultValue": 0,
|
||||||
|
"staticScaleFactor": -1,
|
||||||
|
"unit": "V",
|
||||||
|
"access": "RO"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "batteryModeGen2",
|
||||||
|
"address": 40256,
|
||||||
|
"size": 1,
|
||||||
|
"type": "uint16",
|
||||||
|
"registerType": "holdingRegister",
|
||||||
|
"description": "Battery mode (gen2)",
|
||||||
|
"enum": "BatteryMode",
|
||||||
|
"defaultValue": "BatteryModeDischarge",
|
||||||
|
"access": "RO"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "batteryPowerGen2",
|
||||||
|
"address": 40258,
|
||||||
|
"size": 2,
|
||||||
|
"type": "int32",
|
||||||
|
"registerType": "holdingRegister",
|
||||||
|
"description": "Battery power (gen2)",
|
||||||
|
"defaultValue": 0,
|
||||||
|
"unit": "W",
|
||||||
|
"access": "RO"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "totalEnergyInjectedToGridGen2",
|
||||||
|
"address": 41102,
|
||||||
|
"size": 2,
|
||||||
|
"type": "uint32",
|
||||||
|
"registerType": "holdingRegister",
|
||||||
|
"description": "Total energy injected to grid (gen2)",
|
||||||
|
"defaultValue": 0,
|
||||||
|
"staticScaleFactor": -1,
|
||||||
|
"unit": "kWh",
|
||||||
|
"access": "RO"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "totalEnergyPurchasedFromGridGen2",
|
||||||
|
"address": 41104,
|
||||||
|
"size": 2,
|
||||||
|
"type": "uint32",
|
||||||
|
"registerType": "holdingRegister",
|
||||||
|
"description": "Total energy purchased from grid (gen2)",
|
||||||
|
"defaultValue": 0,
|
||||||
|
"staticScaleFactor": -1,
|
||||||
|
"unit": "kWh",
|
||||||
|
"access": "RO"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "SOCGen2",
|
||||||
|
"address": 43000,
|
||||||
|
"size": 1,
|
||||||
|
"type": "uint16",
|
||||||
|
"registerType": "holdingRegister",
|
||||||
|
"description": "SOC (gen2)",
|
||||||
|
"defaultValue": 0,
|
||||||
|
"staticScaleFactor": -2,
|
||||||
|
"unit": "%",
|
||||||
|
"access": "RO"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "SOHGen2",
|
||||||
|
"address": 43001,
|
||||||
|
"size": 1,
|
||||||
|
"type": "uint16",
|
||||||
|
"registerType": "holdingRegister",
|
||||||
|
"description": "SOH (gen2)",
|
||||||
|
"defaultValue": 0,
|
||||||
|
"staticScaleFactor": -2,
|
||||||
|
"unit": "%",
|
||||||
"access": "RO"
|
"access": "RO"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -8,8 +8,10 @@ include(../modbus.pri)
|
|||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
wattsonicdiscovery.h \
|
wattsonicdiscovery.h \
|
||||||
integrationpluginwattsonic.h
|
integrationpluginwattsonic.h \
|
||||||
|
wattsonicinverter.h
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
wattsonicdiscovery.cpp \
|
wattsonicdiscovery.cpp \
|
||||||
integrationpluginwattsonic.cpp
|
integrationpluginwattsonic.cpp \
|
||||||
|
wattsonicinverter.cpp
|
||||||
|
|||||||
@ -43,21 +43,22 @@ WattsonicDiscovery::WattsonicDiscovery(ModbusRtuHardwareResource *modbusRtuResou
|
|||||||
void WattsonicDiscovery::startDiscovery(quint16 slaveId)
|
void WattsonicDiscovery::startDiscovery(quint16 slaveId)
|
||||||
{
|
{
|
||||||
qCInfo(dcWattsonic()) << "Discovery: Searching for Wattsonic device on modbus RTU...";
|
qCInfo(dcWattsonic()) << "Discovery: Searching for Wattsonic device on modbus RTU...";
|
||||||
|
m_candidateMasters.clear();
|
||||||
|
m_verifiedMasters.clear();
|
||||||
|
|
||||||
QList<ModbusRtuMaster*> candidateMasters;
|
|
||||||
foreach (ModbusRtuMaster *master, m_modbusRtuResource->modbusRtuMasters()) {
|
foreach (ModbusRtuMaster *master, m_modbusRtuResource->modbusRtuMasters()) {
|
||||||
if (master->baudrate() == 9600 && master->dataBits() == 8 && master->stopBits() == 1 && master->parity() == QSerialPort::NoParity) {
|
if (master->baudrate() == 9600 && master->dataBits() == 8 && master->stopBits() == 1 && master->parity() == QSerialPort::NoParity) {
|
||||||
candidateMasters.append(master);
|
m_candidateMasters.append(master);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (candidateMasters.isEmpty()) {
|
if (m_candidateMasters.isEmpty()) {
|
||||||
qCWarning(dcWattsonic()) << "Discovery: No usable modbus RTU master found.";
|
qCWarning(dcWattsonic()) << "Discovery: No usable modbus RTU master found.";
|
||||||
emit discoveryFinished(false);
|
emit discoveryFinished(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (ModbusRtuMaster *master, candidateMasters) {
|
foreach (ModbusRtuMaster *master, m_candidateMasters) {
|
||||||
if (master->connected()) {
|
if (master->connected()) {
|
||||||
tryConnect(master, slaveId);
|
tryConnect(master, slaveId);
|
||||||
} else {
|
} else {
|
||||||
@ -74,18 +75,35 @@ QList<WattsonicDiscovery::Result> WattsonicDiscovery::discoveryResults() const
|
|||||||
void WattsonicDiscovery::tryConnect(ModbusRtuMaster *master, quint16 slaveId)
|
void WattsonicDiscovery::tryConnect(ModbusRtuMaster *master, quint16 slaveId)
|
||||||
{
|
{
|
||||||
qCDebug(dcWattsonic()) << "Discovery: Scanning modbus RTU master" << master->modbusUuid() << "Slave ID:" << slaveId;
|
qCDebug(dcWattsonic()) << "Discovery: Scanning modbus RTU master" << master->modbusUuid() << "Slave ID:" << slaveId;
|
||||||
|
m_verifiedMasters.append(master);
|
||||||
|
|
||||||
ModbusRtuReply *reply = master->readHoldingRegister(slaveId, 10000, 8);
|
WattsonicInverter *connection = new WattsonicInverter(master, slaveId, this);
|
||||||
connect(reply, &ModbusRtuReply::finished, this, [this, master, reply, slaveId](){
|
connect(connection, &WattsonicInverter::reachableChanged, this, [connection](bool reachable){
|
||||||
|
if (reachable) {
|
||||||
|
qCDebug(dcWattsonic()) << "Discovery: The connection is now reachable. Starting the initialization";
|
||||||
|
connection->initialize();
|
||||||
|
} else {
|
||||||
|
connection->deleteLater();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (reply->error() == ModbusRtuReply::NoError) {
|
|
||||||
QString serialNumber = ModbusDataUtils::convertToString(reply->result(), ModbusDataUtils::ByteOrderBigEndian);
|
|
||||||
qCDebug(dcWattsonic()) << "Discovery: Test reply finished!" << reply->error() << reply->result() << serialNumber;
|
|
||||||
|
|
||||||
Result result {master->modbusUuid(), serialNumber, slaveId};
|
connect(connection, &WattsonicInverter::customInitializationFinished, this, [this, connection, master, slaveId](bool success){
|
||||||
m_discoveryResults.append(result);
|
|
||||||
|
if (!success) {
|
||||||
|
qCDebug(dcWattsonic()) << "Initialization failed on" << master << "skip ";
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit discoveryFinished(true);
|
qCDebug(dcWattsonic()) << "Discovery: Init finished successfully:" << connection->serialNumber()
|
||||||
|
<< connection->inverterInfo().type << connection->inverterInfo().model << connection->generation();
|
||||||
|
|
||||||
|
Result result {master->modbusUuid(), connection->serialNumber(), slaveId, connection->generation(), connection->inverterInfo()};
|
||||||
|
m_discoveryResults.append(result);
|
||||||
|
|
||||||
|
if (m_verifiedMasters.count() == m_candidateMasters.count()) {
|
||||||
|
qCDebug(dcWattsonic()) << "Discovery: Verified all masters. Finish discovery with" << m_discoveryResults.count() << "discovered inverters.";
|
||||||
|
emit discoveryFinished(true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
*
|
*
|
||||||
* Copyright 2013 - 2023, nymea GmbH
|
* Copyright 2013 - 2025, nymea GmbH
|
||||||
* Contact: contact@nymea.io
|
* Contact: contact@nymea.io
|
||||||
*
|
*
|
||||||
* This file is part of nymea.
|
* This file is part of nymea.
|
||||||
@ -32,8 +32,11 @@
|
|||||||
#define WATTSONICDISCOVERY_H
|
#define WATTSONICDISCOVERY_H
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
#include <hardware/modbus/modbusrtuhardwareresource.h>
|
#include <hardware/modbus/modbusrtuhardwareresource.h>
|
||||||
|
|
||||||
|
#include "wattsonicinverter.h"
|
||||||
|
|
||||||
class WattsonicDiscovery : public QObject
|
class WattsonicDiscovery : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -44,12 +47,15 @@ public:
|
|||||||
QUuid modbusRtuMasterId;
|
QUuid modbusRtuMasterId;
|
||||||
QString serialNumber;
|
QString serialNumber;
|
||||||
quint16 slaveId;
|
quint16 slaveId;
|
||||||
|
WattsonicInverter::Generation generation;
|
||||||
|
WattsonicInverter::Info inverterInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
void startDiscovery(quint16 slaveId);
|
void startDiscovery(quint16 slaveId);
|
||||||
|
|
||||||
QList<Result> discoveryResults() const;
|
QList<Result> discoveryResults() const;
|
||||||
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void discoveryFinished(bool modbusRtuMasterAvailable);
|
void discoveryFinished(bool modbusRtuMasterAvailable);
|
||||||
|
|
||||||
@ -58,7 +64,8 @@ private slots:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
ModbusRtuHardwareResource *m_modbusRtuResource = nullptr;
|
ModbusRtuHardwareResource *m_modbusRtuResource = nullptr;
|
||||||
|
QList<ModbusRtuMaster *> m_candidateMasters;
|
||||||
|
QList<ModbusRtuMaster *> m_verifiedMasters;
|
||||||
QList<Result> m_discoveryResults;
|
QList<Result> m_discoveryResults;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
1241
wattsonic/wattsonicinverter.cpp
Normal file
1241
wattsonic/wattsonicinverter.cpp
Normal file
File diff suppressed because it is too large
Load Diff
82
wattsonic/wattsonicinverter.h
Normal file
82
wattsonic/wattsonicinverter.h
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
*
|
||||||
|
* Copyright 2013 - 2025, 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 WATTSONICINVERTER_H
|
||||||
|
#define WATTSONICINVERTER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "wattsonicmodbusrtuconnection.h"
|
||||||
|
|
||||||
|
class WattsonicInverter : public WattsonicModbusRtuConnection
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
struct Info {
|
||||||
|
QString type;
|
||||||
|
QString model;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Generation {
|
||||||
|
GenerationUnknwon,
|
||||||
|
Generation2,
|
||||||
|
Generation3
|
||||||
|
};
|
||||||
|
Q_ENUM(Generation)
|
||||||
|
|
||||||
|
explicit WattsonicInverter(ModbusRtuMaster *modbusRtuMaster, quint16 slaveId, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
Generation generation() const;
|
||||||
|
WattsonicInverter::Info inverterInfo() const;
|
||||||
|
|
||||||
|
// Generation specific registers
|
||||||
|
float batteryVoltageDc() const;
|
||||||
|
BatteryMode batteryMode() const;
|
||||||
|
qint32 batteryPower() const;
|
||||||
|
float totalEnergyInjectedToGrid() const;
|
||||||
|
float totalEnergyPurchasedFromGrid() const;
|
||||||
|
float SOC() const;
|
||||||
|
float SOH() const;
|
||||||
|
|
||||||
|
bool update() override;
|
||||||
|
|
||||||
|
static WattsonicInverter::Info getInverterInfo(quint16 equipmentInfo);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void generationChanged();
|
||||||
|
void customInitializationFinished(bool success);
|
||||||
|
|
||||||
|
private:
|
||||||
|
WattsonicInverter::Info m_inverterInfo;
|
||||||
|
Generation m_generation = GenerationUnknwon;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // WATTSONICINVERTER_H
|
||||||
Reference in New Issue
Block a user