Merge PR #171: New plugin: Sungrow

This commit is contained in:
jenkins 2024-07-19 21:06:34 +02:00
commit 12e233b22a
14 changed files with 1923 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.
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
Architecture: any
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 \
solax \
stiebeleltron \
sungrow \
sunspec \
unipi \
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,390 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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(auto thing, myThings().filterByThingClassId(sungrowInverterTcpThingClassId)) {
auto monitor = m_monitors.value(thing);
if (!monitor->reachable()) {
continue;
}
auto connection = m_tcpConnections.value(thing);
if (connection->initializing()) {
qCDebug(dcSungrow()) << "Skip updating" << connection->modbusTcpMaster() << "since the connection is still initializing.";
continue;
}
if (connection->reachable()) {
qCDebug(dcSungrow()) << "Updating connection" << connection->modbusTcpMaster()->hostAddress().toString();
connection->update();
} else {
qCDebug(dcSungrow()) << "Device not reachable. Probably a TCP connection error. Reconnecting TCP socket";
connection->reconnectDevice();
}
}
});
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,667 @@
{
"className": "Sungrow",
"protocol": "TCP",
"endianness": "LittleEndian",
"errorLimitUntilNotReachable": 5,
"queuedRequests": true,
"queuedRequestsDelay": 400,
"checkReachableRegister": "totalPVPower",
"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": "inverterTemperature",
"address": 5007,
"size": 1,
"type": "int16",
"registerType": "inputRegister",
"description": "Inverter temperature",
"unit": "°C",
"staticScaleFactor": -1,
"defaultValue": "0",
"access": "RO"
},
{
"id": "dummy0",
"address": 5008,
"size": 8,
"type": "raw",
"registerType": "inputRegister",
"description": "none",
"access": "RO"
},
{
"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": "dummy1",
"address": 5021,
"size": 11,
"type": "raw",
"registerType": "inputRegister",
"description": "none",
"access": "RO"
},
{
"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": "energyValues2",
"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": "dummy2",
"address": 13014,
"size": 5,
"type": "raw",
"registerType": "inputRegister",
"description": "none",
"access": "RO"
},
{
"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"
},
{
"id": "dummy3",
"address": 13025,
"size": 4,
"type": "raw",
"registerType": "inputRegister",
"description": "none",
"access": "RO"
},
{
"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": "dummy4",
"address": 13038,
"size": 7,
"type": "raw",
"registerType": "inputRegister",
"description": "none",
"access": "RO"
},
{
"id": "totalExportEnergy",
"address": 13045,
"size": 2,
"type": "uint32",
"registerType": "inputRegister",
"description": "Total export 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"
}
]
}
],
"registers": [
]
}

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>