Add Sungrow plug-in

This commit is contained in:
trinnes 2024-03-04 11:12:07 +01:00
parent d27c72e0c5
commit 5e29ea6d17
14 changed files with 1887 additions and 0 deletions

8
debian/control vendored
View File

@ -227,6 +227,14 @@ Description: nymea.io plugin for Stiebel Eltron heat pumps
This package will install the nymea.io plugin for Stiebel Eltron heat pumps. This package will install the nymea.io plugin for Stiebel Eltron heat pumps.
Package: nymea-plugin-sungrow
Architecture: any
Depends: ${shlibs:Depends},
${misc:Depends},
Description: nymea integration plugin for Sungrow devices
This package contains the nymea integration plugin for Sungrow solar inverters, meters and batteries.
Package: nymea-plugin-sunspec Package: nymea-plugin-sunspec
Architecture: any Architecture: any
Depends: ${shlibs:Depends}, Depends: ${shlibs:Depends},

View File

@ -0,0 +1,2 @@
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginsungrow.so
sungrow/translations/*qm usr/share/nymea/translations/

View File

@ -24,6 +24,7 @@ PLUGIN_DIRS = \
sma \ sma \
solax \ solax \
stiebeleltron \ stiebeleltron \
sungrow \
sunspec \ sunspec \
unipi \ unipi \
vestel \ vestel \

26
sungrow/README.md Normal file
View File

@ -0,0 +1,26 @@
# Sungrow
Connects Sungrow inverters to nymea.
Currently supported models:
* SH3K6
* SH4K6
* SH5K-20
* SH5K-V13
* SH3K6-30
* SH4K6-30
* SH5K-30
* SH3.0RS
* SH3.6RS
* SH4.0RS
* SH5.0RS
* SH6.0RS
* SH5.0RT
* SH6.0RT
* SH8.0RT
* SH10RT
# Requirements
nymea uses the modbus TCP connection in order to connect to the Sungrow inverter. Therefore the inverter must be reachable using the local network.

View File

@ -0,0 +1,379 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, 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 "integrationpluginsungrow.h"
#include "plugininfo.h"
#include "sungrowdiscovery.h"
#include <network/networkdevicediscovery.h>
#include <hardwaremanager.h>
IntegrationPluginSungrow::IntegrationPluginSungrow()
{
}
void IntegrationPluginSungrow::discoverThings(ThingDiscoveryInfo *info)
{
if (!hardwareManager()->networkDeviceDiscovery()->available()) {
qCWarning(dcSungrow()) << "The network discovery is not available on this platform.";
info->finish(Thing::ThingErrorUnsupportedFeature, QT_TR_NOOP("The network device discovery is not available."));
return;
}
// Create a discovery with the info as parent for auto deleting the object once the discovery info is done
SungrowDiscovery *discovery = new SungrowDiscovery(hardwareManager()->networkDeviceDiscovery(), m_modbusTcpPort, m_modbusSlaveAddress, info);
connect(discovery, &SungrowDiscovery::discoveryFinished, discovery, &SungrowDiscovery::deleteLater);
connect(discovery, &SungrowDiscovery::discoveryFinished, info, [=](){
foreach (const SungrowDiscovery::SungrowDiscoveryResult &result, discovery->discoveryResults()) {
QString title = "Sungrow " + QString::number(result.nominalOutputPower) + "kW Inverter";
if (!result.serialNumber.isEmpty())
title.append(" " + result.serialNumber);
ThingDescriptor descriptor(sungrowInverterTcpThingClassId, title, result.networkDeviceInfo.address().toString() + " " + result.networkDeviceInfo.macAddress());
qCInfo(dcSungrow()) << "Discovered:" << descriptor.title() << descriptor.description();
// Check if we already have set up this device
Things existingThings = myThings().filterByParam(sungrowInverterTcpThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
if (existingThings.count() == 1) {
qCDebug(dcSungrow()) << "This Sungrow inverter already exists in the system:" << result.networkDeviceInfo;
descriptor.setThingId(existingThings.first()->id());
}
ParamList params;
params << Param(sungrowInverterTcpThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
descriptor.setParams(params);
info->addThingDescriptor(descriptor);
}
info->finish(Thing::ThingErrorNoError);
});
// Start the discovery process
discovery->startDiscovery();
}
void IntegrationPluginSungrow::setupThing(ThingSetupInfo *info)
{
Thing *thing = info->thing();
qCInfo(dcSungrow()) << "Setup" << thing << thing->params();
if (thing->thingClassId() == sungrowInverterTcpThingClassId) {
// Handle reconfiguration
if (m_tcpConnections.contains(thing)) {
qCDebug(dcSungrow()) << "Reconfiguring existing thing" << thing->name();
m_tcpConnections.take(thing)->deleteLater();
if (m_monitors.contains(thing)) {
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
}
}
MacAddress macAddress = MacAddress(thing->paramValue(sungrowInverterTcpThingMacAddressParamTypeId).toString());
if (!macAddress.isValid()) {
qCWarning(dcSungrow()) << "The configured MAC address is not valid" << thing->params();
info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The MAC address is not known. Please reconfigure this inverter."));
return;
}
// Create the monitor
NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(macAddress);
m_monitors.insert(thing, monitor);
connect(info, &ThingSetupInfo::aborted, monitor, [=](){
// Clean up in case the setup gets aborted
if (m_monitors.contains(thing)) {
qCDebug(dcSungrow()) << "Unregister monitor because the setup has been aborted.";
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
}
});
QHostAddress address = m_monitors.value(thing)->networkDeviceInfo().address();
qCInfo(dcSungrow()) << "Setting up Sungrow on" << address.toString();
auto sungrowConnection = new SungrowModbusTcpConnection(address, m_modbusTcpPort , m_modbusSlaveAddress, this);
connect(info, &ThingSetupInfo::aborted, sungrowConnection, &SungrowModbusTcpConnection::deleteLater);
// Reconnect on monitor reachable changed
connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){
qCDebug(dcSungrow()) << "Network device monitor reachable changed for" << thing->name() << reachable;
if (!thing->setupComplete())
return;
if (reachable && !thing->stateValue("connected").toBool()) {
sungrowConnection->modbusTcpMaster()->setHostAddress(monitor->networkDeviceInfo().address());
sungrowConnection->reconnectDevice();
} else if (!reachable) {
// Note: Auto reconnect is disabled explicitly and
// the device will be connected once the monitor says it is reachable again
sungrowConnection->disconnectDevice();
}
});
connect(sungrowConnection, &SungrowModbusTcpConnection::reachableChanged, thing, [this, thing, sungrowConnection](bool reachable){
qCInfo(dcSungrow()) << "Reachable changed to" << reachable << "for" << thing;
if (reachable) {
// Connected true will be set after successfull init
sungrowConnection->initialize();
} else {
thing->setStateValue("connected", false);
thing->setStateValue(sungrowInverterTcpCurrentPowerStateTypeId, 0);
foreach (Thing *childThing, myThings().filterByParentId(thing->id())) {
childThing->setStateValue("connected", false);
}
Thing *child = getMeterThing(thing);
if (child) {
child->setStateValue(sungrowMeterCurrentPowerStateTypeId, 0);
child->setStateValue(sungrowMeterCurrentPhaseAStateTypeId, 0);
child->setStateValue(sungrowMeterCurrentPhaseBStateTypeId, 0);
child->setStateValue(sungrowMeterCurrentPhaseCStateTypeId, 0);
child->setStateValue(sungrowMeterApparentPowerPhaseAStateTypeId, 0);
child->setStateValue(sungrowMeterApparentPowerPhaseBStateTypeId, 0);
child->setStateValue(sungrowMeterApparentPowerPhaseCStateTypeId, 0);
}
child = getBatteryThing(thing);
if (child) {
child->setStateValue(sungrowBatteryCurrentPowerStateTypeId, 0);
}
}
});
connect(sungrowConnection, &SungrowModbusTcpConnection::initializationFinished, thing, [=](bool success){
thing->setStateValue("connected", success);
foreach (Thing *childThing, myThings().filterByParentId(thing->id())) {
childThing->setStateValue("connected", success);
}
if (!success) {
// Try once to reconnect the device
sungrowConnection->reconnectDevice();
} else {
qCInfo(dcSungrow()) << "Connection initialized successfully for" << thing;
sungrowConnection->update();
}
});
connect(sungrowConnection, &SungrowModbusTcpConnection::updateFinished, thing, [=](){
qCDebug(dcSungrow()) << "Updated" << sungrowConnection;
if (myThings().filterByParentId(thing->id()).filterByThingClassId(sungrowMeterThingClassId).isEmpty()) {
qCDebug(dcSungrow()) << "There is no meter set up for this inverter. Creating a meter for" << thing << sungrowConnection->modbusTcpMaster();
ThingClass meterThingClass = thingClass(sungrowMeterThingClassId);
ThingDescriptor descriptor(sungrowMeterThingClassId, meterThingClass.displayName() + " " + sungrowConnection->serialNumber(), QString(), thing->id());
emit autoThingsAppeared(ThingDescriptors() << descriptor);
}
// Check if a battery is connected to this Sungrow inverter
if (sungrowConnection->batteryType() != SungrowModbusTcpConnection::BatteryTypeNoBattery &&
myThings().filterByParentId(thing->id()).filterByThingClassId(sungrowBatteryThingClassId).isEmpty()) {
qCDebug(dcSungrow()) << "There is a battery connected but not set up yet. Creating a battery.";
ThingClass batteryThingClass = thingClass(sungrowBatteryThingClassId);
ThingDescriptor descriptor(sungrowBatteryThingClassId, batteryThingClass.displayName() + " " + sungrowConnection->serialNumber(), QString(), thing->id());
emit autoThingsAppeared(ThingDescriptors() << descriptor);
}
// Update inverter states
thing->setStateValue(sungrowInverterTcpCurrentPowerStateTypeId, static_cast<double>(sungrowConnection->totalPVPower()) * -1);
thing->setStateValue(sungrowInverterTcpTemperatureStateTypeId, sungrowConnection->inverterTemperature());
thing->setStateValue(sungrowInverterTcpFrequencyStateTypeId, sungrowConnection->gridFrequency());
thing->setStateValue(sungrowInverterTcpTotalEnergyProducedStateTypeId, sungrowConnection->totalPVGeneration());
// Update the meter if available
Thing *meterThing = getMeterThing(thing);
if (meterThing) {
auto runningState = sungrowConnection->runningState();
qCDebug(dcSungrow()) << "Power generated from PV:" << (runningState & (0x1 << 0) ? "true" : "false");
qCDebug(dcSungrow()) << "Battery charging:" << (runningState & (0x1 << 1) ? "true" : "false");
qCDebug(dcSungrow()) << "Battery discharging:" << (runningState & (0x1 << 2) ? "true" : "false");
qCDebug(dcSungrow()) << "Positive load power:" << (runningState & (0x1 << 3) ? "true" : "false");
qCDebug(dcSungrow()) << "Feed-in power:" << (runningState & (0x1 << 4) ? "true" : "false");
qCDebug(dcSungrow()) << "Import power from grid:" << (runningState & (0x1 << 5) ? "true" : "false");
qCDebug(dcSungrow()) << "Negative load power:" << (runningState & (0x1 << 7) ? "true" : "false");
meterThing->setStateValue(sungrowMeterCurrentPowerStateTypeId, sungrowConnection->totalActivePower() * -1);
meterThing->setStateValue(sungrowMeterTotalEnergyConsumedStateTypeId, sungrowConnection->totalImportEnergy());
meterThing->setStateValue(sungrowMeterTotalEnergyProducedStateTypeId, sungrowConnection->totalExportEnergy());
meterThing->setStateValue(sungrowMeterCurrentPhaseAStateTypeId, sungrowConnection->phaseACurrent() * -1);
meterThing->setStateValue(sungrowMeterCurrentPhaseBStateTypeId, sungrowConnection->phaseBCurrent() * -1);
meterThing->setStateValue(sungrowMeterCurrentPhaseCStateTypeId, sungrowConnection->phaseCCurrent() * -1);
meterThing->setStateValue(sungrowMeterVoltagePhaseAStateTypeId, sungrowConnection->phaseAVoltage());
meterThing->setStateValue(sungrowMeterVoltagePhaseBStateTypeId, sungrowConnection->phaseBVoltage());
meterThing->setStateValue(sungrowMeterVoltagePhaseCStateTypeId, sungrowConnection->phaseCVoltage());
meterThing->setStateValue(sungrowMeterApparentPowerPhaseAStateTypeId, sungrowConnection->phaseAVoltage() * sungrowConnection->phaseACurrent() * -1);
meterThing->setStateValue(sungrowMeterApparentPowerPhaseBStateTypeId, sungrowConnection->phaseBVoltage() * sungrowConnection->phaseBCurrent() * -1);
meterThing->setStateValue(sungrowMeterApparentPowerPhaseCStateTypeId, sungrowConnection->phaseCVoltage() * sungrowConnection->phaseCCurrent() * -1);
meterThing->setStateValue(sungrowMeterFrequencyStateTypeId, sungrowConnection->gridFrequency());
}
// Update the battery if available
Thing *batteryThing = getBatteryThing(thing);
if (batteryThing) {
batteryThing->setStateValue(sungrowBatteryVoltageStateTypeId, sungrowConnection->batteryVoltage());
batteryThing->setStateValue(sungrowBatteryTemperatureStateTypeId, sungrowConnection->batteryTemperature());
batteryThing->setStateValue(sungrowBatteryBatteryLevelStateTypeId, sungrowConnection->batteryLevel());
batteryThing->setStateValue(sungrowBatteryBatteryCriticalStateTypeId, sungrowConnection->batteryLevel() < 5);
batteryThing->setStateValue(sungrowBatteryCurrentPowerStateTypeId, sungrowConnection->batteryPower());
quint16 runningState = sungrowConnection->runningState();
if (runningState & (0x1 << 1)) { //Bit 1: Battery charging bit
batteryThing->setStateValue(sungrowBatteryChargingStateStateTypeId, "charging");
} else if (runningState & (0x1 << 2)) { //Bit 2: Battery discharging bit
batteryThing->setStateValue(sungrowBatteryChargingStateStateTypeId, "discharging");
} else {
batteryThing->setStateValue(sungrowBatteryChargingStateStateTypeId, "idle");
}
}
});
m_tcpConnections.insert(thing, sungrowConnection);
if (monitor->reachable())
sungrowConnection->connectDevice();
info->finish(Thing::ThingErrorNoError);
return;
}
if (thing->thingClassId() == sungrowMeterThingClassId) {
// Get the parent thing and the associated connection
Thing *connectionThing = myThings().findById(thing->parentId());
if (!connectionThing) {
qCWarning(dcSungrow()) << "Failed to set up Sungrow energy meter because the parent thing with ID" << thing->parentId().toString() << "could not be found.";
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
auto sungrowConnection = m_tcpConnections.value(connectionThing);
if (!sungrowConnection) {
qCWarning(dcSungrow()) << "Failed to set up Sungrow energy meter because the connection for" << connectionThing << "does not exist.";
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
// Note: The states will be handled in the parent inverter thing on updated
info->finish(Thing::ThingErrorNoError);
return;
}
if (thing->thingClassId() == sungrowBatteryThingClassId) {
// Get the parent thing and the associated connection
Thing *connectionThing = myThings().findById(thing->parentId());
if (!connectionThing) {
qCWarning(dcSungrow()) << "Failed to set up Sungrow battery because the parent thing with ID" << thing->parentId().toString() << "could not be found.";
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
auto sungrowConnection = m_tcpConnections.value(connectionThing);
if (!sungrowConnection) {
qCWarning(dcSungrow()) << "Failed to set up Sungrow battery because the connection for" << connectionThing << "does not exist.";
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
// Note: The states will be handled in the parent inverter thing on updated
info->finish(Thing::ThingErrorNoError);
return;
}
}
void IntegrationPluginSungrow::postSetupThing(Thing *thing)
{
if (thing->thingClassId() == sungrowInverterTcpThingClassId) {
// Create the update timer if not already set up
if (!m_refreshTimer) {
qCDebug(dcSungrow()) << "Starting plugin timer...";
m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(2);
connect(m_refreshTimer, &PluginTimer::timeout, this, [this] {
foreach(SungrowModbusTcpConnection *connection, m_tcpConnections) {
if (connection->initializing()) {
qCDebug(dcSungrow()) << "Skip updating" << connection->modbusTcpMaster() << "since the connection is still initializing.";
continue;
}
qCDebug(dcSungrow()) << "Updating connection" << connection->modbusTcpMaster()->hostAddress().toString();
connection->update();
}
});
m_refreshTimer->start();
}
return;
}
if (thing->thingClassId() == sungrowMeterThingClassId || thing->thingClassId() == sungrowBatteryThingClassId) {
Thing *connectionThing = myThings().findById(thing->parentId());
if (connectionThing) {
thing->setStateValue("connected", connectionThing->stateValue("connected"));
}
return;
}
}
void IntegrationPluginSungrow::thingRemoved(Thing *thing)
{
if (thing->thingClassId() == sungrowInverterTcpThingClassId && m_tcpConnections.contains(thing)) {
auto connection = m_tcpConnections.take(thing);
connection->modbusTcpMaster()->disconnectDevice();
delete connection;
}
// Unregister related hardware resources
if (m_monitors.contains(thing))
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
if (myThings().isEmpty() && m_refreshTimer) {
qCDebug(dcSungrow()) << "Stopping refresh timer";
hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer);
m_refreshTimer = nullptr;
}
}
Thing *IntegrationPluginSungrow::getMeterThing(Thing *parentThing)
{
Things meterThings = myThings().filterByParentId(parentThing->id()).filterByThingClassId(sungrowMeterThingClassId);
if (meterThings.isEmpty())
return nullptr;
return meterThings.first();
}
Thing *IntegrationPluginSungrow::getBatteryThing(Thing *parentThing)
{
Things batteryThings = myThings().filterByParentId(parentThing->id()).filterByThingClassId(sungrowBatteryThingClassId);
if (batteryThings.isEmpty())
return nullptr;
return batteryThings.first();
}

View File

@ -0,0 +1,73 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, 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 INTEGRATIONPLUGINSUNGROW_H
#define INTEGRATIONPLUGINSUNGROW_H
#include <plugintimer.h>
#include <integrations/integrationplugin.h>
#include <network/networkdevicemonitor.h>
#include "extern-plugininfo.h"
#include "sungrowmodbustcpconnection.h"
class IntegrationPluginSungrow: public IntegrationPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginsungrow.json")
Q_INTERFACES(IntegrationPlugin)
public:
explicit IntegrationPluginSungrow();
void discoverThings(ThingDiscoveryInfo *info) override;
void setupThing(ThingSetupInfo *info) override;
void postSetupThing(Thing *thing) override;
void thingRemoved(Thing *thing) override;
private:
const int m_modbusTcpPort = 502;
const quint16 m_modbusSlaveAddress = 1;
PluginTimer *m_refreshTimer = nullptr;
QHash<Thing *, NetworkDeviceMonitor *> m_monitors;
QHash<Thing *, SungrowModbusTcpConnection *> m_tcpConnections;
void setupSungrowTcpConnection(ThingSetupInfo *info);
Thing *getMeterThing(Thing *parentThing);
Thing *getBatteryThing(Thing *parentThing);
};
#endif // INTEGRATIONPLUGINSUNGROW_H

View File

@ -0,0 +1,289 @@
{
"name": "Sungrow",
"displayName": "Sungrow",
"id": "250c9b83-1127-4013-bbd0-11e7ea482057",
"vendors": [
{
"name": "sungrow",
"displayName": "Sungrow",
"id": "cdc58e0d-bfdb-45d9-b961-9c0b036c35aa",
"thingClasses": [
{
"name": "sungrowInverterTcp",
"displayName": "Sungrow Inverter",
"id": "59cb2da4-da07-11ee-adea-7397f8a9afe9",
"createMethods": ["discovery"],
"discoveryType": "weak",
"interfaces": ["solarinverter", "connectable"],
"providedInterfaces": [ "energymeter", "energystorage"],
"paramTypes": [
{
"id": "62137142-da07-11ee-9522-2f74f3b1fc5d",
"name":"macAddress",
"displayName": "MAC address",
"type": "QString",
"inputType": "MacAddress",
"defaultValue": ""
}
],
"stateTypes": [
{
"id": "6c68b170-da07-11ee-9891-3335ced0ad72",
"name": "connected",
"displayName": "Connected",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "6fb11fe8-da07-11ee-b77f-8393cc11be21",
"name": "currentPower",
"displayName": "Active power",
"type": "double",
"unit": "Watt",
"defaultValue": 0,
"cached": false
},
{
"id": "7bf092a2-da07-11ee-abe9-138e66e6f2a5",
"name": "totalEnergyProduced",
"displayName": "Total energy produced",
"type": "double",
"unit": "KiloWattHour",
"defaultValue": 0.0,
"cached": true
},
{
"id": "80b35fc2-da07-11ee-a73d-5774b082c92b",
"name": "temperature",
"displayName": "Temperature",
"type": "double",
"unit": "DegreeCelsius",
"defaultValue": 0.00,
"cached": false
},
{
"id": "84bcbc58-da07-11ee-b94d-a39792ee6f59",
"name": "frequency",
"displayName": "Frequency",
"type": "double",
"unit": "Hertz",
"defaultValue": 0.00,
"cached": false
}
]
},
{
"name": "sungrowMeter",
"displayName": "Sungrow Meter",
"id": "a935e49c-da07-11ee-bd11-3fcf27dc6373",
"createMethods": ["auto"],
"interfaces": [ "energymeter", "connectable"],
"stateTypes": [
{
"id": "d26b59dc-da07-11ee-b494-3781cc3081a7",
"name": "connected",
"displayName": "Connected",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "d777530e-da07-11ee-a526-c7d23dd34f57",
"name": "currentPower",
"displayName": "Current power",
"type": "double",
"unit": "Watt",
"defaultValue": 0.00,
"cached": false
},
{
"id": "e80bb836-da07-11ee-afd1-dbff8484ba11",
"name": "voltagePhaseA",
"displayName": "Voltage phase A",
"type": "double",
"unit": "Volt",
"defaultValue": 0,
"cached": false
},
{
"id": "ebefd252-da07-11ee-a8a4-c7ea9df0b6f9",
"name": "voltagePhaseB",
"displayName": "Voltage phase B",
"type": "double",
"unit": "Volt",
"defaultValue": 0,
"cached": false
},
{
"id": "efd82b30-da07-11ee-9668-7b523696940d",
"name": "voltagePhaseC",
"displayName": "Voltage phase C",
"type": "double",
"unit": "Volt",
"defaultValue": 0,
"cached": false
},
{
"id": "f3d850c0-da07-11ee-883f-4ff8b7e55bdc",
"name": "currentPhaseA",
"displayName": "Current phase A",
"type": "double",
"unit": "Ampere",
"defaultValue": 0,
"cached": false
},
{
"id": "f82603de-da07-11ee-93bc-6b9f9f333c30",
"name": "currentPhaseB",
"displayName": "Current phase B",
"type": "double",
"unit": "Ampere",
"defaultValue": 0,
"cached": false
},
{
"id": "fcf77988-da07-11ee-99da-3b2415014506",
"name": "currentPhaseC",
"displayName": "Current phase C",
"type": "double",
"unit": "Ampere",
"defaultValue": 0,
"cached": false
},
{
"id": "167aa300-faef-11ee-859a-bb6f4e8be7c9",
"name": "apparentPowerPhaseA",
"displayName": "Apparent power phase A",
"type": "double",
"unit": "VoltAmpere",
"defaultValue": 0
},
{
"id": "2c3ac134-faef-11ee-9c28-9f6bb77683d3",
"name": "apparentPowerPhaseB",
"displayName": "Apparent power phase B",
"type": "double",
"unit": "VoltAmpere",
"defaultValue": 0
},
{
"id": "3f7ca9e2-faef-11ee-81e7-6f53d07e9197",
"name": "apparentPowerPhaseC",
"displayName": "Apparent power phase C",
"type": "double",
"unit": "VoltAmpere",
"defaultValue": 0
},
{
"id": "00eb83c2-da08-11ee-b67d-1f74a41e6218",
"name": "totalEnergyProduced",
"displayName": "Total returned energy",
"type": "double",
"unit": "KiloWattHour",
"defaultValue": 0.00,
"cached": true
},
{
"id": "03ef972a-da08-11ee-9a1f-d741d1e276be",
"name": "totalEnergyConsumed",
"displayName": "Total imported energy",
"type": "double",
"unit": "KiloWattHour",
"defaultValue": 0.00,
"cached": true
},
{
"id": "07b526ea-da08-11ee-ab24-ab0d1ca8555d",
"name": "frequency",
"displayName": "Frequency",
"type": "double",
"unit": "Hertz",
"defaultValue": 0.00,
"cached": false
}
]
},
{
"name": "sungrowBattery",
"displayName": "Sungrow Battery",
"id": "0aea1b90-da08-11ee-9195-afc9f857a324",
"createMethods": ["auto"],
"interfaces": ["energystorage", "connectable"],
"stateTypes": [
{
"id": "0ff3c834-da08-11ee-bac7-0f1044f86ea3",
"name": "connected",
"displayName": "Connected",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "13f48cde-da08-11ee-81c8-3362a92b58c8",
"name": "batteryCritical",
"displayName": "Battery critical",
"type": "bool",
"defaultValue": false
},
{
"id": "1738bcf8-da08-11ee-b1d0-a3e1183dabba",
"name": "batteryLevel",
"displayName": "Battery level",
"type": "int",
"unit": "Percentage",
"minValue": 0,
"maxValue": 100,
"defaultValue": 0,
"cached": false
},
{
"id": "1aa8cf86-da08-11ee-9697-3b0a8023db88",
"name": "currentPower",
"displayName": "Total real power",
"type": "double",
"unit": "Watt",
"defaultValue": 0.00,
"cached": false
},
{
"id": "1e6bf760-da08-11ee-ba39-c31ecdbb8fc9",
"name": "voltage",
"displayName": "Voltage",
"type": "double",
"unit": "Volt",
"defaultValue": 0.00,
"cached": false
},
{
"id": "221ccae2-da08-11ee-9e4d-4b6abbf9e564",
"name": "temperature",
"displayName": "Temperature",
"type": "double",
"unit": "DegreeCelsius",
"defaultValue": 0.00,
"cached": false
},
{
"id": "264ac092-da08-11ee-ad8d-9f0751d6c499",
"name": "capacity",
"displayName": "Capacity",
"type": "double",
"unit": "KiloWattHour",
"defaultValue": 0.00
},
{
"id": "29e8a6d8-da08-11ee-87ba-539307b7d2ee",
"name": "chargingState",
"displayName": "Charging state",
"type": "QString",
"possibleValues": ["idle", "charging", "discharging"],
"defaultValue": "idle",
"cached": false
}
]
}
]
}
]
}

13
sungrow/meta.json Normal file
View File

@ -0,0 +1,13 @@
{
"title": "Sungrow Inverter",
"tagline": "Connect to Sungrow inverters.",
"icon": "sungrow.png",
"stability": "consumer",
"offline": true,
"technologies": [
"network"
],
"categories": [
"energy"
]
}

View File

@ -0,0 +1,642 @@
{
"className": "Sungrow",
"protocol": "TCP",
"endianness": "LittleEndian",
"errorLimitUntilNotReachable": 5,
"queuedRequests": true,
"queuedRequestsDelay": 200,
"checkReachableRegister": "inverterTemperature",
"enums": [
{
"name": "SystemState",
"values": [
{
"key": "Stop",
"value": 2
},
{
"key": "Standby",
"value": 8
},
{
"key": "InitialStandby",
"value": 16
},
{
"key": "Startup",
"value": 32
},
{
"key": "Running",
"value": 64
},
{
"key": "Fault",
"value": 256
},
{
"key": "RunningMainMode",
"value": 1024
},
{
"key": "RunningForcedMode",
"value": 2048
},
{
"key": "RunningOffGridMode",
"value": 4096
},
{
"key": "Restarting",
"value": 9473
},
{
"key": "RunningExternalEMSMode",
"value": 16384
}
]
},
{
"name": "BatteryType",
"values": [
{
"key": "LeadAcidNarada",
"value": 0
},
{
"key": "LiIonSamsung",
"value": 1
},
{
"key": "NoBattery",
"value": 2
},
{
"key": "LeadAcidOther",
"value": 3
},
{
"key": "LiIonUS2000A",
"value": 4
},
{
"key": "LiIonLG",
"value": 5
},
{
"key": "LiIonUS2000B",
"value": 6
},
{
"key": "LiIonGCL",
"value": 7
},
{
"key": "LiIonBSG",
"value": 8
},
{
"key": "LiIonSungrow",
"value": 9
},
{
"key": "LiIonBYD",
"value": 10
},
{
"key": "LiIonTawaki",
"value": 11
}
]
}
],
"blocks": [
{
"id": "version",
"readSchedule": "init",
"registers": [
{
"id": "protocolNumber",
"address": 4949,
"size": 2,
"type": "uint32",
"registerType": "inputRegister",
"description": "Protocol number",
"defaultValue": "0",
"access": "RO"
},
{
"id": "protocolVersion",
"address": 4951,
"size": 2,
"type": "uint32",
"registerType": "inputRegister",
"description": "Device type code",
"defaultValue": "0",
"access": "RO"
},
{
"id": "armSoftwareVersion",
"address": 4953,
"size": 15,
"type": "string",
"registerType": "inputRegister",
"description": "ARM software version",
"access": "RO"
},
{
"id": "dspSoftwareVersion",
"address": 4968,
"size": 15,
"type": "string",
"registerType": "inputRegister",
"description": "ARM software version",
"access": "RO"
}
]
},
{
"id": "identification",
"readSchedule": "init",
"registers": [
{
"id": "serialNumber",
"address": 4989,
"size": 10,
"type": "string",
"registerType": "inputRegister",
"description": "Serial number",
"access": "RO"
},
{
"id": "deviceTypeCode",
"address": 4999,
"size": 1,
"type": "uint16",
"registerType": "inputRegister",
"description": "Device type code",
"defaultValue": "0",
"access": "RO"
},
{
"id": "nominalOutputPower",
"address": 5000,
"size": 1,
"type": "uint16",
"registerType": "inputRegister",
"description": "Nominal output power",
"unit": "kW",
"staticScaleFactor": -1,
"defaultValue": "0",
"access": "RO"
}
]
},
{
"id": "energyValues1",
"readSchedule": "update",
"registers": [
{
"id": "totalPVPower",
"address": 5016,
"size": 2,
"type": "uint32",
"registerType": "inputRegister",
"description": "Total PV power",
"defaultValue": "0",
"unit": "W",
"access": "RO"
},
{
"id": "phaseAVoltage",
"address": 5018,
"size": 1,
"type": "uint16",
"registerType": "inputRegister",
"description": "Phase A voltage",
"unit": "V",
"staticScaleFactor": -1,
"defaultValue": "0",
"access": "RO"
},
{
"id": "phaseBVoltage",
"address": 5019,
"size": 1,
"type": "int16",
"registerType": "inputRegister",
"description": "Phase B voltage",
"unit": "V",
"staticScaleFactor": -1,
"defaultValue": "0",
"access": "RO"
},
{
"id": "phaseCVoltage",
"address": 5020,
"size": 1,
"type": "int16",
"registerType": "inputRegister",
"description": "Phase C voltage",
"unit": "V",
"staticScaleFactor": -1,
"defaultValue": "0",
"access": "RO"
}
]
},
{
"id": "energyValues2",
"readSchedule": "update",
"registers": [
{
"id": "reactivePower",
"address": 5032,
"size": 2,
"type": "int32",
"registerType": "inputRegister",
"description": "Reactive power",
"defaultValue": "0",
"unit": "var",
"access": "RO"
},
{
"id": "powerFactor",
"address": 5034,
"size": 1,
"type": "int32",
"registerType": "inputRegister",
"description": "Power factor",
"defaultValue": "0",
"staticScaleFactor": -3,
"access": "RO"
},
{
"id": "gridFrequency",
"address": 5035,
"size": 1,
"type": "uint16",
"registerType": "inputRegister",
"description": "Grid frequency",
"defaultValue": "0",
"unit": "Hz",
"staticScaleFactor": -2,
"access": "RO"
}
]
},
{
"id": "energyValues3",
"readSchedule": "update",
"registers": [
{
"id": "systemState",
"address": 12999,
"size": 1,
"type": "uint16",
"enum": "SystemState",
"registerType": "inputRegister",
"description": "System state",
"defaultValue": "SystemStateStop",
"access": "RO"
},
{
"id": "runningState",
"address": 13000,
"size": 1,
"type": "uint16",
"registerType": "inputRegister",
"description": "Running state",
"defaultValue": "0",
"access": "RO"
},
{
"id": "dailyPVGeneration",
"address": 13001,
"size": 1,
"type": "uint16",
"registerType": "inputRegister",
"description": "Daily PV generation",
"unit": "kWh",
"defaultValue": "0",
"staticScaleFactor": -1,
"access": "RO"
},
{
"id": "totalPVGeneration",
"address": 13002,
"size": 2,
"type": "uint32",
"registerType": "inputRegister",
"description": "Total PV generation",
"unit": "kWh",
"defaultValue": "0",
"staticScaleFactor": -1,
"access": "RO"
},
{
"id": "dailyPVExport",
"address": 13004,
"size": 1,
"type": "uint16",
"registerType": "inputRegister",
"description": "Daily PV export",
"unit": "kWh",
"defaultValue": "0",
"staticScaleFactor": -1,
"access": "RO"
},
{
"id": "totalPVExport",
"address": 13005,
"size": 2,
"type": "uint32",
"registerType": "inputRegister",
"description": "Total PV export´",
"unit": "kWh",
"defaultValue": "0",
"staticScaleFactor": -1,
"access": "RO"
},
{
"id": "loadPower",
"address": 13007,
"size": 2,
"type": "int16",
"registerType": "inputRegister",
"description": "Load power",
"unit": "W",
"defaultValue": "0",
"access": "RO"
},
{
"id": "exportPower",
"address": 13009,
"size": 2,
"type": "int16",
"registerType": "inputRegister",
"description": "Export power",
"unit": "W",
"defaultValue": "0",
"access": "RO"
},
{
"id": "dailyBatteryChargePV",
"address": 13011,
"size": 1,
"type": "uint16",
"registerType": "inputRegister",
"description": "Daily battery charge from PV",
"unit": "kWh",
"defaultValue": "0",
"staticScaleFactor": -1,
"access": "RO"
},
{
"id": "totalBatteryChargePV",
"address": 13012,
"size": 2,
"type": "uint32",
"registerType": "inputRegister",
"description": "Total battery charge from PV",
"unit": "kWh",
"defaultValue": "0",
"staticScaleFactor": -1,
"access": "RO"
}
]
},
{
"id": "energyValues4",
"readSchedule": "update",
"registers": [
{
"id": "gridState",
"address": 13029,
"size": 1,
"type": "uint16",
"registerType": "inputRegister",
"description": "Grid state",
"defaultValue": "0",
"access": "RO"
},
{
"id": "phaseACurrent",
"address": 13030,
"size": 1,
"type": "int16",
"registerType": "inputRegister",
"description": "Phase A current",
"unit": "A",
"staticScaleFactor": -1,
"defaultValue": "0",
"access": "RO"
},
{
"id": "phaseBCurrent",
"address": 13031,
"size": 1,
"type": "int16",
"registerType": "inputRegister",
"description": "Phase B current",
"unit": "A",
"staticScaleFactor": -1,
"defaultValue": "0",
"access": "RO"
},
{
"id": "phaseCCurrent",
"address": 13032,
"size": 1,
"type": "int16",
"registerType": "inputRegister",
"description": "Phase C current",
"unit": "A",
"staticScaleFactor": -1,
"defaultValue": "0",
"access": "RO"
},
{
"id": "totalActivePower",
"address": 13033,
"size": 2,
"type": "int32",
"registerType": "inputRegister",
"description": "Total active power",
"unit": "W",
"defaultValue": "0",
"access": "RO"
},
{
"id": "dailyImportEnergy",
"address": 13035,
"size": 1,
"type": "uint16",
"registerType": "inputRegister",
"description": "Daily import energy",
"unit": "kWh",
"defaultValue": "0",
"staticScaleFactor": -1,
"access": "RO"
},
{
"id": "totalImportEnergy",
"address": 13036,
"size": 2,
"type": "uint32",
"registerType": "inputRegister",
"description": "Total import energy",
"unit": "kWh",
"defaultValue": "0",
"staticScaleFactor": -1,
"access": "RO"
}
]
},
{
"id": "batteryInformation",
"readSchedule": "init",
"registers": [
{
"id": "batteryType",
"address": 13054,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"description": "Battery type",
"enum": "BatteryType",
"defaultValue": "BatteryTypeNoBattery",
"access": "RO"
},
{
"id": "batteryNominalVoltage",
"address": 13055,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"description": "Battery nominal voltage",
"unit": "V",
"defaultValue": "0",
"staticScaleFactor": -1,
"access": "RO"
},
{
"id": "batteryCapacity",
"address": 13056,
"size": 1,
"type": "uint16",
"registerType": "holdingRegister",
"description": "Battery capacity",
"unit": "Ah",
"defaultValue": "10",
"access": "RO"
}
]
},
{
"id": "batteryValues",
"readSchedule": "update",
"registers": [
{
"id": "batteryVoltage",
"address": 13019,
"size": 1,
"type": "uint16",
"registerType": "inputRegister",
"description": "Battery voltage",
"unit": "V",
"defaultValue": "0",
"staticScaleFactor": -1,
"access": "RO"
},
{
"id": "batteryCurrent",
"address": 13020,
"size": 1,
"type": "uint16",
"registerType": "inputRegister",
"description": "Battery current",
"unit": "A",
"defaultValue": "0",
"staticScaleFactor": -1,
"access": "RO"
},
{
"id": "batteryPower",
"address": 13021,
"size": 1,
"type": "uint16",
"registerType": "inputRegister",
"description": "Battery power",
"unit": "W",
"defaultValue": "0",
"access": "RO"
},
{
"id": "batteryLevel",
"address": 13022,
"size": 1,
"type": "uint16",
"registerType": "inputRegister",
"description": "Battery level",
"unit": "%",
"defaultValue": "0",
"staticScaleFactor": -1,
"access": "RO"
},
{
"id": "batteryHealthState",
"address": 13023,
"size": 1,
"type": "uint16",
"registerType": "inputRegister",
"description": "Battery health state",
"unit": "%",
"defaultValue": "0",
"staticScaleFactor": -1,
"access": "RO"
},
{
"id": "batteryTemperature",
"address": 13024,
"size": 1,
"type": "int16",
"registerType": "inputRegister",
"description": "Battery temperature",
"unit": "°C",
"defaultValue": "0",
"staticScaleFactor": -1,
"access": "RO"
}
]
}
],
"registers": [
{
"id": "inverterTemperature",
"address": 5007,
"size": 1,
"readSchedule": "update",
"type": "int16",
"registerType": "inputRegister",
"description": "Inverter temperature",
"unit": "°C",
"staticScaleFactor": -1,
"defaultValue": "0",
"access": "RO"
},
{
"id": "totalExportEnergy",
"address": 13045,
"size": 2,
"readSchedule": "update",
"type": "uint32",
"registerType": "inputRegister",
"description": "Total export energy",
"unit": "kWh",
"defaultValue": "0",
"staticScaleFactor": -1,
"access": "RO"
}
]
}

BIN
sungrow/sungrow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

14
sungrow/sungrow.pro Normal file
View File

@ -0,0 +1,14 @@
include(../plugins.pri)
# Generate modbus connection
MODBUS_CONNECTIONS += sungrow-registers.json
#MODBUS_TOOLS_CONFIG += VERBOSE
include(../modbus.pri)
HEADERS += \
integrationpluginsungrow.h \
sungrowdiscovery.h
SOURCES += \
integrationpluginsungrow.cpp \
sungrowdiscovery.cpp

View File

@ -0,0 +1,161 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, 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 "sungrowdiscovery.h"
#include "extern-plugininfo.h"
SungrowDiscovery::SungrowDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint16 port, quint16 modbusAddress, QObject *parent) :
QObject{parent},
m_networkDeviceDiscovery{networkDeviceDiscovery},
m_port{port},
m_modbusAddress{modbusAddress}
{
}
void SungrowDiscovery::startDiscovery()
{
qCDebug(dcSungrow()) << "Discovery: Start searching for Sungrow inverters in the network";
m_startDateTime = QDateTime::currentDateTime();
NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover();
connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &SungrowDiscovery::checkNetworkDevice);
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, discoveryReply, &NetworkDeviceDiscoveryReply::deleteLater);
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=] () {
qCDebug(dcSungrow()) << "Discovery: Network discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "network devices";
// Give the last connections added right before the network discovery finished a chance to check the device...
QTimer::singleShot(3000, this, [this] () {
qCDebug(dcSungrow()) << "Discovery: Grace period timer triggered.";
finishDiscovery();
});
});
}
QList<SungrowDiscovery::SungrowDiscoveryResult> SungrowDiscovery::discoveryResults() const
{
return m_discoveryResults;
}
void SungrowDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo)
{
/* Create a Sungrow connection and try to initialize it.
* Only if initialized successfully and all information have been fetched correctly from
* the device we can assume this is what we are locking for (ip, port, modbus address, correct registers).
*/
qCDebug(dcSungrow()) << "Creating Sungrow Modbus TCP connection for" << networkDeviceInfo.address() << "Port:" << m_port << "Slave Address" << m_modbusAddress;
SungrowModbusTcpConnection *connection = new SungrowModbusTcpConnection(networkDeviceInfo.address(), m_port, m_modbusAddress, this);
connection->modbusTcpMaster()->setTimeout(5000);
connection->modbusTcpMaster()->setNumberOfRetries(0);
m_connections.append(connection);
connect(connection, &SungrowModbusTcpConnection::reachableChanged, this, [=](bool reachable){
qCDebug(dcSungrow()) << "Sungrow Modbus TCP Connection reachable changed:" << reachable;
if (!reachable) {
cleanupConnection(connection);
return;
}
qCDebug(dcSungrow()) << "Connected, proceeding with initialization";
connect(connection, &SungrowModbusTcpConnection::initializationFinished, this, [=](bool success){
if (!success) {
qCDebug(dcSungrow()) << "Discovery: Initialization failed on" << networkDeviceInfo.address().toString() << "Continue...";
cleanupConnection(connection);
return;
}
qCDebug(dcSungrow()) << "Discovery: Initialized successfully" << networkDeviceInfo << connection->serialNumber();
qCDebug(dcSungrow()) << " - Protocol number:" << connection->protocolNumber();
qCDebug(dcSungrow()) << " - Protocol version:" << connection->protocolVersion();
qCDebug(dcSungrow()) << " - ARM software version:" << connection->armSoftwareVersion();
qCDebug(dcSungrow()) << " - DSP software version:" << connection->dspSoftwareVersion();
if (connection->deviceTypeCode() >= 0xd00 && connection->deviceTypeCode() <= 0xeff) {
SungrowDiscoveryResult result;
result.networkDeviceInfo = networkDeviceInfo;
result.serialNumber = connection->serialNumber();
result.nominalOutputPower = connection->nominalOutputPower();
result.deviceType = connection->deviceTypeCode();
m_discoveryResults.append(result);
}
connection->disconnectDevice();
});
qCDebug(dcSungrow()) << "Discovery: The host" << networkDeviceInfo << "is reachable. Starting with initialization.";
if (!connection->initialize()) {
qCDebug(dcSungrow()) << "Discovery: Unable to initialize connection on" << networkDeviceInfo.address().toString() << "Continue...";
cleanupConnection(connection);
}
});
// In case of an error skip the host
connect(connection->modbusTcpMaster(), &ModbusTcpMaster::connectionStateChanged, this, [=](bool connected){
if (connected) {
qCDebug(dcSungrow()) << "Discovery: Connected with" << networkDeviceInfo.address().toString() << m_port;
}
});
// In case of an error skip the host
connect(connection->modbusTcpMaster(), &ModbusTcpMaster::connectionErrorOccurred, this, [=](QModbusDevice::Error error){
if (error != QModbusDevice::NoError) {
qCDebug(dcSungrow()) << "Discovery: Connection error on" << networkDeviceInfo.address().toString() << "Continue...";
cleanupConnection(connection);
}
});
// If the reachability check failed skip the host
connect(connection, &SungrowModbusTcpConnection::checkReachabilityFailed, this, [=](){
qCDebug(dcSungrow()) << "Discovery: Check reachability failed on" << networkDeviceInfo.address().toString() << "Continue...";
cleanupConnection(connection);
});
connection->connectDevice();
}
void SungrowDiscovery::cleanupConnection(SungrowModbusTcpConnection *connection)
{
qCDebug(dcSungrow()) << "Discovery: Cleanup connection" << connection->modbusTcpMaster();
m_connections.removeAll(connection);
connection->disconnectDevice();
connection->deleteLater();
}
void SungrowDiscovery::finishDiscovery()
{
qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch();
foreach (SungrowModbusTcpConnection *connection, m_connections)
cleanupConnection(connection);
qCDebug(dcSungrow()) << "Discovery: Finished the discovery process. Found" << m_discoveryResults.count() << "Sungrow inverters in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz");
emit discoveryFinished();
}

View File

@ -0,0 +1,76 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, 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 SUNGROWDISCOVERY_H
#define SUNGROWDISCOVERY_H
#include <QObject>
#include <QTimer>
#include <network/networkdevicediscovery.h>
#include "sungrowmodbustcpconnection.h"
class SungrowDiscovery : public QObject
{
Q_OBJECT
public:
explicit SungrowDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint16 port = 502, quint16 modbusAddress = 1, QObject *parent = nullptr);
typedef struct SungrowDiscoveryResult {
QString serialNumber;
NetworkDeviceInfo networkDeviceInfo;
float nominalOutputPower;
int deviceType;
} SungrowDiscoveryResult;
void startDiscovery();
QList<SungrowDiscoveryResult> discoveryResults() const;
signals:
void discoveryFinished();
private:
NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr;
quint16 m_port;
quint16 m_modbusAddress;
QDateTime m_startDateTime;
QList<SungrowModbusTcpConnection *> m_connections;
QList<SungrowDiscoveryResult> m_discoveryResults;
void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo);
void cleanupConnection(SungrowModbusTcpConnection *connection);
void finishDiscovery();
};
#endif // SUNGROWDISCOVERY_H

View File

@ -0,0 +1,203 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>IntegrationPluginSungrow</name>
<message>
<location filename="../integrationpluginsungrow.cpp" line="47"/>
<source>The network device discovery is not available.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../integrationpluginsungrow.cpp" line="103"/>
<source>The MAC address is not known. Please reconfigure this inverter.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Sungrow</name>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="55"/>
<source>Active power</source>
<extracomment>The name of the StateType ({6fb11fe8-da07-11ee-b77f-8393cc11be21}) of ThingClass sungrowInverterTcp</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="58"/>
<source>Battery critical</source>
<extracomment>The name of the StateType ({13f48cde-da08-11ee-81c8-3362a92b58c8}) of ThingClass sungrowBattery</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="61"/>
<source>Battery level</source>
<extracomment>The name of the StateType ({1738bcf8-da08-11ee-b1d0-a3e1183dabba}) of ThingClass sungrowBattery</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="64"/>
<source>Capacity</source>
<extracomment>The name of the StateType ({264ac092-da08-11ee-ad8d-9f0751d6c499}) of ThingClass sungrowBattery</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="67"/>
<source>Charging state</source>
<extracomment>The name of the StateType ({29e8a6d8-da08-11ee-87ba-539307b7d2ee}) of ThingClass sungrowBattery</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="70"/>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="73"/>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="76"/>
<source>Connected</source>
<extracomment>The name of the StateType ({0ff3c834-da08-11ee-bac7-0f1044f86ea3}) of ThingClass sungrowBattery
----------
The name of the StateType ({d26b59dc-da07-11ee-b494-3781cc3081a7}) of ThingClass sungrowMeter
----------
The name of the StateType ({6c68b170-da07-11ee-9891-3335ced0ad72}) of ThingClass sungrowInverterTcp</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="79"/>
<source>Current phase A</source>
<extracomment>The name of the StateType ({f3d850c0-da07-11ee-883f-4ff8b7e55bdc}) of ThingClass sungrowMeter</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="82"/>
<source>Current phase B</source>
<extracomment>The name of the StateType ({f82603de-da07-11ee-93bc-6b9f9f333c30}) of ThingClass sungrowMeter</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="85"/>
<source>Current phase C</source>
<extracomment>The name of the StateType ({fcf77988-da07-11ee-99da-3b2415014506}) of ThingClass sungrowMeter</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="88"/>
<source>Current power</source>
<extracomment>The name of the StateType ({d777530e-da07-11ee-a526-c7d23dd34f57}) of ThingClass sungrowMeter</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="91"/>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="94"/>
<source>Frequency</source>
<extracomment>The name of the StateType ({07b526ea-da08-11ee-ab24-ab0d1ca8555d}) of ThingClass sungrowMeter
----------
The name of the StateType ({84bcbc58-da07-11ee-b94d-a39792ee6f59}) of ThingClass sungrowInverterTcp</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="97"/>
<source>MAC address</source>
<extracomment>The name of the ParamType (ThingClass: sungrowInverterTcp, Type: thing, ID: {62137142-da07-11ee-9522-2f74f3b1fc5d})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="100"/>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="103"/>
<source>Sungrow</source>
<extracomment>The name of the vendor ({cdc58e0d-bfdb-45d9-b961-9c0b036c35aa})
----------
The name of the plugin Sungrow ({250c9b83-1127-4013-bbd0-11e7ea482057})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="106"/>
<source>Sungrow Battery</source>
<extracomment>The name of the ThingClass ({0aea1b90-da08-11ee-9195-afc9f857a324})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="109"/>
<source>Sungrow Inverter</source>
<extracomment>The name of the ThingClass ({59cb2da4-da07-11ee-adea-7397f8a9afe9})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="112"/>
<source>Sungrow Meter</source>
<extracomment>The name of the ThingClass ({a935e49c-da07-11ee-bd11-3fcf27dc6373})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="115"/>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="118"/>
<source>Temperature</source>
<extracomment>The name of the StateType ({221ccae2-da08-11ee-9e4d-4b6abbf9e564}) of ThingClass sungrowBattery
----------
The name of the StateType ({80b35fc2-da07-11ee-a73d-5774b082c92b}) of ThingClass sungrowInverterTcp</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="121"/>
<source>Total energy produced</source>
<extracomment>The name of the StateType ({7bf092a2-da07-11ee-abe9-138e66e6f2a5}) of ThingClass sungrowInverterTcp</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="124"/>
<source>Total imported energy</source>
<extracomment>The name of the StateType ({03ef972a-da08-11ee-9a1f-d741d1e276be}) of ThingClass sungrowMeter</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="127"/>
<source>Total real power</source>
<extracomment>The name of the StateType ({1aa8cf86-da08-11ee-9697-3b0a8023db88}) of ThingClass sungrowBattery</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="130"/>
<source>Total returned energy</source>
<extracomment>The name of the StateType ({00eb83c2-da08-11ee-b67d-1f74a41e6218}) of ThingClass sungrowMeter</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="133"/>
<source>Voltage</source>
<extracomment>The name of the StateType ({1e6bf760-da08-11ee-ba39-c31ecdbb8fc9}) of ThingClass sungrowBattery</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="136"/>
<source>Voltage phase A</source>
<extracomment>The name of the StateType ({e80bb836-da07-11ee-afd1-dbff8484ba11}) of ThingClass sungrowMeter</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="139"/>
<source>Voltage phase B</source>
<extracomment>The name of the StateType ({ebefd252-da07-11ee-a8a4-c7ea9df0b6f9}) of ThingClass sungrowMeter</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="142"/>
<source>Voltage phase C</source>
<extracomment>The name of the StateType ({efd82b30-da07-11ee-9668-7b523696940d}) of ThingClass sungrowMeter</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="145"/>
<source>charging</source>
<extracomment>The name of a possible value of StateType {29e8a6d8-da08-11ee-87ba-539307b7d2ee} of ThingClass sungrowBattery</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="148"/>
<source>discharging</source>
<extracomment>The name of a possible value of StateType {29e8a6d8-da08-11ee-87ba-539307b7d2ee} of ThingClass sungrowBattery</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/sungrow/plugininfo.h" line="151"/>
<source>idle</source>
<extracomment>The name of a possible value of StateType {29e8a6d8-da08-11ee-87ba-539307b7d2ee} of ThingClass sungrowBattery</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
</TS>