Merge PR #140: SMA: Add support for batteries
This commit is contained in:
commit
b755935606
@ -7,9 +7,8 @@ nymea plug-in for SMA solar equipment.
|
||||
* Sunny WebBox
|
||||
* SMA speedwire Meters
|
||||
* SMA speedwire Inverters
|
||||
* SMA inverters using modbus
|
||||
|
||||
> Note: the SMA battery equipment is still missing due to testing possibilities. Will be added as soon someone can with the appropriate setup.
|
||||
* SMA solar inverters using modbus
|
||||
* SMA battery inverters using modbus
|
||||
|
||||
## Requirements
|
||||
|
||||
|
||||
@ -34,7 +34,8 @@
|
||||
#include "sma.h"
|
||||
#include "speedwire/speedwirediscovery.h"
|
||||
#include "sunnywebbox/sunnywebboxdiscovery.h"
|
||||
#include "modbus/smamodbusdiscovery.h"
|
||||
#include "modbus/smamodbussolarinverterdiscovery.h"
|
||||
#include "modbus/smamodbusbatteryinverterdiscovery.h"
|
||||
|
||||
#include <network/networkdevicediscovery.h>
|
||||
|
||||
@ -201,7 +202,7 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info)
|
||||
|
||||
speedwireDiscovery->startDiscovery();
|
||||
|
||||
} else if (info->thingClassId() == modbusInverterThingClassId) {
|
||||
} else if (info->thingClassId() == modbusSolarInverterThingClassId) {
|
||||
if (!hardwareManager()->networkDeviceDiscovery()->available()) {
|
||||
qCWarning(dcSma()) << "The network discovery is not available on this platform.";
|
||||
info->finish(Thing::ThingErrorUnsupportedFeature, QT_TR_NOOP("The network device discovery is not available."));
|
||||
@ -209,25 +210,25 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info)
|
||||
}
|
||||
|
||||
// Create a discovery with the info as parent for auto deleting the object once the discovery info is done
|
||||
SmaModbusDiscovery *discovery = new SmaModbusDiscovery(hardwareManager()->networkDeviceDiscovery(), 502, 3, info);
|
||||
connect(discovery, &SmaModbusDiscovery::discoveryFinished, info, [=](){
|
||||
foreach (const SmaModbusDiscovery::SmaModbusDiscoveryResult &result, discovery->discoveryResults()) {
|
||||
SmaModbusSolarInverterDiscovery *discovery = new SmaModbusSolarInverterDiscovery(hardwareManager()->networkDeviceDiscovery(), 502, 3, info);
|
||||
connect(discovery, &SmaModbusSolarInverterDiscovery::discoveryFinished, info, [=](){
|
||||
foreach (const SmaModbusSolarInverterDiscovery::SmaModbusDiscoveryResult &result, discovery->discoveryResults()) {
|
||||
|
||||
ThingDescriptor descriptor(modbusInverterThingClassId, "SMA inverter " + result.productName, QT_TR_NOOP("Serial: ") + result.serialNumber + " (" + result.networkDeviceInfo.address().toString() + ")");
|
||||
ThingDescriptor descriptor(modbusSolarInverterThingClassId, "SMA inverter " + result.productName, QT_TR_NOOP("Serial: ") + result.serialNumber + " (" + result.networkDeviceInfo.address().toString() + ")");
|
||||
qCDebug(dcSma()) << "Discovered:" << descriptor.title() << descriptor.description();
|
||||
|
||||
// Note: use the serial and not the mac address as identifier because more than one inverter might be behind a network device
|
||||
Things existingThings = myThings().filterByParam(modbusInverterThingSerialNumberParamTypeId, result.serialNumber);
|
||||
Things existingThings = myThings().filterByParam(modbusSolarInverterThingSerialNumberParamTypeId, result.serialNumber);
|
||||
if (existingThings.count() == 1) {
|
||||
qCDebug(dcSma()) << "This SMA inverter already exists in the system:" << result.serialNumber;
|
||||
descriptor.setThingId(existingThings.first()->id());
|
||||
}
|
||||
|
||||
ParamList params;
|
||||
params << Param(modbusInverterThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
|
||||
params << Param(modbusInverterThingPortParamTypeId, result.port);
|
||||
params << Param(modbusInverterThingSlaveIdParamTypeId, result.modbusAddress);
|
||||
params << Param(modbusInverterThingSerialNumberParamTypeId, result.serialNumber);
|
||||
params << Param(modbusSolarInverterThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
|
||||
params << Param(modbusSolarInverterThingPortParamTypeId, result.port);
|
||||
params << Param(modbusSolarInverterThingSlaveIdParamTypeId, result.modbusAddress);
|
||||
params << Param(modbusSolarInverterThingSerialNumberParamTypeId, result.serialNumber);
|
||||
descriptor.setParams(params);
|
||||
info->addThingDescriptor(descriptor);
|
||||
}
|
||||
@ -237,6 +238,39 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info)
|
||||
|
||||
// Start the discovery process
|
||||
discovery->startDiscovery();
|
||||
} else if (info->thingClassId() == modbusBatteryInverterThingClassId) {
|
||||
if (!hardwareManager()->networkDeviceDiscovery()->available()) {
|
||||
qCWarning(dcSma()) << "The network discovery is not available on this platform.";
|
||||
info->finish(Thing::ThingErrorUnsupportedFeature, QT_TR_NOOP("Unable to scan the network. Please ensure that the system is installed correctly."));
|
||||
return;
|
||||
}
|
||||
|
||||
SmaModbusBatteryInverterDiscovery *discovery = new SmaModbusBatteryInverterDiscovery(hardwareManager()->networkDeviceDiscovery(), 502, 3, info);
|
||||
connect(discovery, &SmaModbusBatteryInverterDiscovery::discoveryFinished, info, [=](){
|
||||
foreach (const SmaModbusBatteryInverterDiscovery::Result &result, discovery->discoveryResults()) {
|
||||
|
||||
qCInfo(dcSma()) << "Discovered:" << result.deviceName << result.serialNumber << result.networkDeviceInfo.address().toString();
|
||||
ThingDescriptor descriptor(modbusBatteryInverterThingClassId, "SMA battery inverter", QT_TR_NOOP("Serial: ") + result.serialNumber + " (" + result.networkDeviceInfo.address().toString() + ")");
|
||||
|
||||
Things existingThings = myThings().filterByParam(modbusBatteryInverterThingSerialNumberParamTypeId, result.serialNumber);
|
||||
if (existingThings.count() == 1) {
|
||||
qCInfo(dcSma()) << "This SMA inverter already exists in the system:" << result.serialNumber;
|
||||
descriptor.setThingId(existingThings.first()->id());
|
||||
}
|
||||
|
||||
ParamList params;
|
||||
params << Param(modbusBatteryInverterThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
|
||||
params << Param(modbusBatteryInverterThingPortParamTypeId, result.port);
|
||||
params << Param(modbusBatteryInverterThingSlaveIdParamTypeId, result.modbusAddress);
|
||||
params << Param(modbusBatteryInverterThingSerialNumberParamTypeId, result.serialNumber);
|
||||
descriptor.setParams(params);
|
||||
info->addThingDescriptor(descriptor);
|
||||
}
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
discovery->startDiscovery();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -473,19 +507,19 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info)
|
||||
qCDebug(dcSma()) << "Battery: Setup SMA battery" << thing;
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
|
||||
} else if (thing->thingClassId() == modbusInverterThingClassId) {
|
||||
} else if (thing->thingClassId() == modbusSolarInverterThingClassId) {
|
||||
|
||||
// Handle reconfigure
|
||||
if (m_modbusInverters.contains(thing)) {
|
||||
if (m_modbusSolarInverters.contains(thing)) {
|
||||
qCDebug(dcSma()) << "Reconfiguring existing thing" << thing->name();
|
||||
m_modbusInverters.take(thing)->deleteLater();
|
||||
m_modbusSolarInverters.take(thing)->deleteLater();
|
||||
|
||||
if (m_monitors.contains(thing)) {
|
||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
||||
}
|
||||
}
|
||||
|
||||
MacAddress macAddress = MacAddress(thing->paramValue(modbusInverterThingMacAddressParamTypeId).toString());
|
||||
MacAddress macAddress = MacAddress(thing->paramValue(modbusSolarInverterThingMacAddressParamTypeId).toString());
|
||||
if (!macAddress.isValid()) {
|
||||
qCWarning(dcSma()) << "The configured mac address is not valid" << thing->params();
|
||||
info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The MAC address is not known. Please reconfigure the thing."));
|
||||
@ -515,13 +549,65 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info)
|
||||
// Wait for the monitor to be ready
|
||||
if (monitor->reachable()) {
|
||||
// Thing already reachable...let's continue with the setup
|
||||
setupModbusInverterConnection(info);
|
||||
setupModbusSolarInverterConnection(info);
|
||||
} else {
|
||||
qCDebug(dcSma()) << "Waiting for the network monitor to get reachable before continue to set up the connection" << thing->name() << address.toString() << "...";
|
||||
connect(monitor, &NetworkDeviceMonitor::reachableChanged, info, [=](bool reachable){
|
||||
if (reachable) {
|
||||
qCDebug(dcSma()) << "The monitor for thing setup" << thing->name() << "is now reachable. Continue setup...";
|
||||
setupModbusInverterConnection(info);
|
||||
setupModbusSolarInverterConnection(info);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} else if (thing->thingClassId() == modbusBatteryInverterThingClassId) {
|
||||
|
||||
if (m_modbusBatteryInverters.contains(thing)) {
|
||||
qCDebug(dcSma()) << "Reconfiguring existing thing" << thing->name();
|
||||
m_modbusBatteryInverters.take(thing)->deleteLater();
|
||||
|
||||
if (m_monitors.contains(thing)) {
|
||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
||||
}
|
||||
}
|
||||
|
||||
MacAddress macAddress = MacAddress(thing->paramValue(modbusBatteryInverterThingMacAddressParamTypeId).toString());
|
||||
if (!macAddress.isValid()) {
|
||||
qCWarning(dcSma()) << "The configured mac address is not valid" << thing->params();
|
||||
info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The MAC address is not known. Please reconfigure the thing."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the monitor
|
||||
NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(macAddress);
|
||||
m_monitors.insert(thing, monitor);
|
||||
|
||||
QHostAddress address = monitor->networkDeviceInfo().address();
|
||||
if (address.isNull()) {
|
||||
qCWarning(dcSma()) << "Cannot set up sma modbus battery inverter. The host address is not known yet. Maybe it will be available in the next run...";
|
||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
||||
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The host address is not known yet. Trying again later."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up in case the setup gets aborted
|
||||
connect(info, &ThingSetupInfo::aborted, monitor, [=](){
|
||||
if (m_monitors.contains(thing)) {
|
||||
qCDebug(dcSma()) << "Unregister monitor because setup has been aborted.";
|
||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for the monitor to be ready
|
||||
if (monitor->reachable()) {
|
||||
// Thing already reachable...let's continue with the setup
|
||||
setupModbusBatteryInverterConnection(info);
|
||||
} else {
|
||||
qCDebug(dcSma()) << "Waiting for the network monitor to become reachable before continue to set up the connection" << thing->name() << address.toString() << "...";
|
||||
connect(monitor, &NetworkDeviceMonitor::reachableChanged, info, [=](bool reachable){
|
||||
if (reachable) {
|
||||
qCDebug(dcSma()) << "The monitor for thing setup" << thing->name() << "is now reachable. Continuing with setup...";
|
||||
setupModbusBatteryInverterConnection(info);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -571,8 +657,8 @@ void IntegrationPluginSma::postSetupThing(Thing *thing)
|
||||
|
||||
setupRefreshTimer();
|
||||
|
||||
} else if (thing->thingClassId() == modbusInverterThingClassId) {
|
||||
SmaInverterModbusTcpConnection *connection = m_modbusInverters.value(thing);
|
||||
} else if (thing->thingClassId() == modbusSolarInverterThingClassId) {
|
||||
SmaSolarInverterModbusTcpConnection *connection = m_modbusSolarInverters.value(thing);
|
||||
if (connection) {
|
||||
thing->setStateValue("connected", connection->reachable());
|
||||
if (!connection->reachable()) {
|
||||
@ -606,8 +692,8 @@ void IntegrationPluginSma::thingRemoved(Thing *thing)
|
||||
m_speedwireInverters.take(thing)->deleteLater();
|
||||
}
|
||||
|
||||
if (thing->thingClassId() == modbusInverterThingClassId && m_modbusInverters.contains(thing)) {
|
||||
m_modbusInverters.take(thing)->deleteLater();
|
||||
if (thing->thingClassId() == modbusSolarInverterThingClassId && m_modbusSolarInverters.contains(thing)) {
|
||||
m_modbusSolarInverters.take(thing)->deleteLater();
|
||||
}
|
||||
|
||||
if (m_monitors.contains(thing)) {
|
||||
@ -677,7 +763,10 @@ void IntegrationPluginSma::setupRefreshTimer()
|
||||
inverter->refresh();
|
||||
}
|
||||
|
||||
foreach (SmaInverterModbusTcpConnection *connection, m_modbusInverters) {
|
||||
foreach (SmaSolarInverterModbusTcpConnection *connection, m_modbusSolarInverters) {
|
||||
connection->update();
|
||||
}
|
||||
foreach (SmaBatteryInverterModbusTcpConnection *connection, m_modbusBatteryInverters) {
|
||||
connection->update();
|
||||
}
|
||||
});
|
||||
@ -685,17 +774,17 @@ void IntegrationPluginSma::setupRefreshTimer()
|
||||
m_refreshTimer->start();
|
||||
}
|
||||
|
||||
void IntegrationPluginSma::setupModbusInverterConnection(ThingSetupInfo *info)
|
||||
void IntegrationPluginSma::setupModbusSolarInverterConnection(ThingSetupInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
|
||||
QHostAddress address = m_monitors.value(thing)->networkDeviceInfo().address();
|
||||
uint port = thing->paramValue(modbusInverterThingPortParamTypeId).toUInt();
|
||||
quint16 slaveId = thing->paramValue(modbusInverterThingSlaveIdParamTypeId).toUInt();
|
||||
uint port = thing->paramValue(modbusSolarInverterThingPortParamTypeId).toUInt();
|
||||
quint16 slaveId = thing->paramValue(modbusSolarInverterThingSlaveIdParamTypeId).toUInt();
|
||||
|
||||
qCDebug(dcSma()) << "Setting up SMA inverter on" << address.toString() << port << "unit ID:" << slaveId;
|
||||
SmaInverterModbusTcpConnection *connection = new SmaInverterModbusTcpConnection(address, port, slaveId, this);
|
||||
connect(info, &ThingSetupInfo::aborted, connection, &SmaInverterModbusTcpConnection::deleteLater);
|
||||
SmaSolarInverterModbusTcpConnection *connection = new SmaSolarInverterModbusTcpConnection(address, port, slaveId, this);
|
||||
connect(info, &ThingSetupInfo::aborted, connection, &SmaSolarInverterModbusTcpConnection::deleteLater);
|
||||
|
||||
// Reconnect on monitor reachable changed
|
||||
NetworkDeviceMonitor *monitor = m_monitors.value(thing);
|
||||
@ -711,25 +800,25 @@ void IntegrationPluginSma::setupModbusInverterConnection(ThingSetupInfo *info)
|
||||
// Note: We disable autoreconnect explicitly and we will
|
||||
// connect the device once the monitor says it is reachable again
|
||||
connection->disconnectDevice();
|
||||
markModbusInverterAsDisconnected(thing);
|
||||
markModbusSolarInverterAsDisconnected(thing);
|
||||
}
|
||||
});
|
||||
|
||||
connect(connection, &SmaInverterModbusTcpConnection::reachableChanged, thing, [this, thing, connection](bool reachable){
|
||||
connect(connection, &SmaSolarInverterModbusTcpConnection::reachableChanged, thing, [this, thing, connection](bool reachable){
|
||||
qCDebug(dcSma()) << "Reachable changed to" << reachable << "for" << thing;
|
||||
if (reachable) {
|
||||
// Connected true will be set after successfull init
|
||||
connection->initialize();
|
||||
} else {
|
||||
thing->setStateValue("connected", false);
|
||||
markModbusInverterAsDisconnected(thing);
|
||||
markModbusSolarInverterAsDisconnected(thing);
|
||||
foreach (Thing *childThing, myThings().filterByParentId(thing->id())) {
|
||||
childThing->setStateValue("connected", false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
connect(connection, &SmaInverterModbusTcpConnection::initializationFinished, thing, [=](bool success){
|
||||
connect(connection, &SmaSolarInverterModbusTcpConnection::initializationFinished, thing, [=](bool success){
|
||||
if (!thing->setupComplete())
|
||||
return;
|
||||
|
||||
@ -741,11 +830,11 @@ void IntegrationPluginSma::setupModbusInverterConnection(ThingSetupInfo *info)
|
||||
if (!success) {
|
||||
// Try once to reconnect the device
|
||||
connection->reconnectDevice();
|
||||
markModbusInverterAsDisconnected(thing);
|
||||
markModbusSolarInverterAsDisconnected(thing);
|
||||
}
|
||||
});
|
||||
|
||||
connect(connection, &SmaInverterModbusTcpConnection::initializationFinished, info, [=](bool success){
|
||||
connect(connection, &SmaSolarInverterModbusTcpConnection::initializationFinished, info, [=](bool success){
|
||||
if (!success) {
|
||||
qCWarning(dcSma()) << "Connection init finished with errors" << thing->name() << connection->modbusTcpMaster()->hostAddress().toString();
|
||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(monitor);
|
||||
@ -755,7 +844,7 @@ void IntegrationPluginSma::setupModbusInverterConnection(ThingSetupInfo *info)
|
||||
}
|
||||
|
||||
qCDebug(dcSma()) << "Connection init finished successfully" << connection;
|
||||
m_modbusInverters.insert(thing, connection);
|
||||
m_modbusSolarInverters.insert(thing, connection);
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
|
||||
// Set connected true
|
||||
@ -764,52 +853,142 @@ void IntegrationPluginSma::setupModbusInverterConnection(ThingSetupInfo *info)
|
||||
childThing->setStateValue("connected", true);
|
||||
}
|
||||
|
||||
connect(connection, &SmaInverterModbusTcpConnection::updateFinished, thing, [=](){
|
||||
connect(connection, &SmaSolarInverterModbusTcpConnection::updateFinished, thing, [=](){
|
||||
qCDebug(dcSma()) << "Updated" << connection;
|
||||
|
||||
// Grid voltage
|
||||
if (isModbusValueValid(connection->gridVoltagePhaseA()))
|
||||
thing->setStateValue(modbusInverterVoltagePhaseAStateTypeId, connection->gridVoltagePhaseA() / 100.0);
|
||||
thing->setStateValue(modbusSolarInverterVoltagePhaseAStateTypeId, connection->gridVoltagePhaseA() / 100.0);
|
||||
|
||||
if (isModbusValueValid(connection->gridVoltagePhaseB()))
|
||||
thing->setStateValue(modbusInverterVoltagePhaseBStateTypeId, connection->gridVoltagePhaseB() / 100.0);
|
||||
thing->setStateValue(modbusSolarInverterVoltagePhaseBStateTypeId, connection->gridVoltagePhaseB() / 100.0);
|
||||
|
||||
if (isModbusValueValid(connection->gridVoltagePhaseC()))
|
||||
thing->setStateValue(modbusInverterVoltagePhaseCStateTypeId, connection->gridVoltagePhaseC() / 100.0);
|
||||
thing->setStateValue(modbusSolarInverterVoltagePhaseCStateTypeId, connection->gridVoltagePhaseC() / 100.0);
|
||||
|
||||
// Grid current
|
||||
if (isModbusValueValid(connection->gridCurrentPhaseA()))
|
||||
thing->setStateValue(modbusInverterCurrentPhaseAStateTypeId, connection->gridCurrentPhaseA() / 1000.0);
|
||||
thing->setStateValue(modbusSolarInverterCurrentPhaseAStateTypeId, connection->gridCurrentPhaseA() / 1000.0);
|
||||
|
||||
if (isModbusValueValid(connection->gridCurrentPhaseB()))
|
||||
thing->setStateValue(modbusInverterCurrentPhaseBStateTypeId, connection->gridCurrentPhaseB() / 1000.0);
|
||||
thing->setStateValue(modbusSolarInverterCurrentPhaseBStateTypeId, connection->gridCurrentPhaseB() / 1000.0);
|
||||
|
||||
if (isModbusValueValid(connection->gridCurrentPhaseC()))
|
||||
thing->setStateValue(modbusInverterCurrentPhaseCStateTypeId, connection->gridCurrentPhaseC() / 1000.0);
|
||||
thing->setStateValue(modbusSolarInverterCurrentPhaseCStateTypeId, connection->gridCurrentPhaseC() / 1000.0);
|
||||
|
||||
// Phase power
|
||||
if (isModbusValueValid(connection->currentPowerPhaseA()))
|
||||
thing->setStateValue(modbusInverterCurrentPowerPhaseAStateTypeId, connection->currentPowerPhaseA());
|
||||
thing->setStateValue(modbusSolarInverterCurrentPowerPhaseAStateTypeId, connection->currentPowerPhaseA());
|
||||
|
||||
if (isModbusValueValid(connection->currentPowerPhaseB()))
|
||||
thing->setStateValue(modbusInverterCurrentPowerPhaseBStateTypeId, connection->currentPowerPhaseB());
|
||||
thing->setStateValue(modbusSolarInverterCurrentPowerPhaseBStateTypeId, connection->currentPowerPhaseB());
|
||||
|
||||
if (isModbusValueValid(connection->currentPowerPhaseC()))
|
||||
thing->setStateValue(modbusInverterCurrentPowerPhaseCStateTypeId, connection->currentPowerPhaseC());
|
||||
thing->setStateValue(modbusSolarInverterCurrentPowerPhaseCStateTypeId, connection->currentPowerPhaseC());
|
||||
|
||||
// Others
|
||||
if (isModbusValueValid(connection->totalYield()))
|
||||
thing->setStateValue(modbusInverterTotalEnergyProducedStateTypeId, connection->totalYield() / 1000.0); // kWh
|
||||
thing->setStateValue(modbusSolarInverterTotalEnergyProducedStateTypeId, connection->totalYield() / 1000.0); // kWh
|
||||
|
||||
if (isModbusValueValid(connection->dailyYield()))
|
||||
thing->setStateValue(modbusInverterEnergyProducedTodayStateTypeId, connection->dailyYield() / 1000.0); // kWh
|
||||
thing->setStateValue(modbusSolarInverterEnergyProducedTodayStateTypeId, connection->dailyYield() / 1000.0); // kWh
|
||||
|
||||
// Power
|
||||
if (isModbusValueValid(connection->currentPower()))
|
||||
thing->setStateValue(modbusInverterCurrentPowerStateTypeId, -connection->currentPower());
|
||||
thing->setStateValue(modbusSolarInverterCurrentPowerStateTypeId, -connection->currentPower());
|
||||
|
||||
// Version
|
||||
thing->setStateValue(modbusInverterFirmwareVersionStateTypeId, Sma::buildSoftwareVersionString(connection->softwarePackage()));
|
||||
thing->setStateValue(modbusSolarInverterFirmwareVersionStateTypeId, Sma::buildSoftwareVersionString(connection->softwarePackage()));
|
||||
});
|
||||
|
||||
// Update registers
|
||||
connection->update();
|
||||
});
|
||||
|
||||
connection->connectDevice();
|
||||
}
|
||||
|
||||
void IntegrationPluginSma::setupModbusBatteryInverterConnection(ThingSetupInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
|
||||
QHostAddress address = m_monitors.value(thing)->networkDeviceInfo().address();
|
||||
uint port = thing->paramValue(modbusBatteryInverterThingPortParamTypeId).toUInt();
|
||||
quint16 slaveId = thing->paramValue(modbusBatteryInverterThingSlaveIdParamTypeId).toUInt();
|
||||
|
||||
qCDebug(dcSma()) << "Setting up SMA inverter on" << address.toString() << port << "unit ID:" << slaveId;
|
||||
SmaBatteryInverterModbusTcpConnection *connection = new SmaBatteryInverterModbusTcpConnection(address, port, slaveId, this);
|
||||
connect(info, &ThingSetupInfo::aborted, connection, &SmaBatteryInverterModbusTcpConnection::deleteLater);
|
||||
|
||||
// Reconnect on monitor reachable changed
|
||||
NetworkDeviceMonitor *monitor = m_monitors.value(thing);
|
||||
connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){
|
||||
qCDebug(dcSma()) << "Network device monitor reachable changed for" << thing->name() << reachable;
|
||||
if (!thing->setupComplete())
|
||||
return;
|
||||
|
||||
if (reachable && !thing->stateValue("connected").toBool()) {
|
||||
connection->modbusTcpMaster()->setHostAddress(monitor->networkDeviceInfo().address());
|
||||
connection->connectDevice();
|
||||
} else if (!reachable) {
|
||||
// Note: We disable autoreconnect explicitly and we will
|
||||
// connect the device once the monitor says it is reachable again
|
||||
connection->disconnectDevice();
|
||||
markModbusBatteryInverterAsDisconnected(thing);
|
||||
}
|
||||
});
|
||||
|
||||
connect(connection, &SmaBatteryInverterModbusTcpConnection::reachableChanged, thing, [this, thing, connection](bool reachable){
|
||||
qCDebug(dcSma()) << "Reachable changed to" << reachable << "for" << thing;
|
||||
if (reachable) {
|
||||
// Connected true will be set after successfull init
|
||||
connection->initialize();
|
||||
} else {
|
||||
thing->setStateValue("connected", false);
|
||||
markModbusBatteryInverterAsDisconnected(thing);
|
||||
}
|
||||
});
|
||||
|
||||
connect(connection, &SmaBatteryInverterModbusTcpConnection::initializationFinished, thing, [=](bool success){
|
||||
if (!thing->setupComplete())
|
||||
return;
|
||||
|
||||
thing->setStateValue("connected", success);
|
||||
foreach (Thing *childThing, myThings().filterByParentId(thing->id())) {
|
||||
childThing->setStateValue("connected", success);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
// Try once to reconnect the device
|
||||
connection->reconnectDevice();
|
||||
markModbusBatteryInverterAsDisconnected(thing);
|
||||
}
|
||||
});
|
||||
|
||||
connect(connection, &SmaBatteryInverterModbusTcpConnection::initializationFinished, info, [=](bool success){
|
||||
if (!success) {
|
||||
qCWarning(dcSma()) << "Connection init finished with errors" << thing->name() << connection->modbusTcpMaster()->hostAddress().toString();
|
||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(monitor);
|
||||
connection->deleteLater();
|
||||
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Could not initialize the communication with the battery inverter."));
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(dcSma()) << "Connection init finished successfully" << connection;
|
||||
m_modbusBatteryInverters.insert(thing, connection);
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
|
||||
thing->setStateValue("connected", true);
|
||||
|
||||
connect(connection, &SmaBatteryInverterModbusTcpConnection::updateFinished, thing, [=](){
|
||||
qCDebug(dcSma()) << "Updated" << connection;
|
||||
thing->setStateValue(modbusBatteryInverterFirmwareVersionStateTypeId, Sma::buildSoftwareVersionString(connection->softwarePackage()));
|
||||
|
||||
thing->setStateValue(modbusBatteryInverterBatteryLevelStateTypeId, connection->batterySOC());
|
||||
thing->setStateValue(modbusBatteryInverterBatteryCriticalStateTypeId, connection->batterySOC() <= 5);
|
||||
thing->setStateValue(modbusBatteryInverterCurrentPowerStateTypeId, -connection->currentPower());
|
||||
thing->setStateValue(modbusBatteryInverterChargingStateStateTypeId, connection->currentPower() == 0 ? "idle" : (connection->currentPower() > 0 ? "charging" : "discharging"));
|
||||
|
||||
});
|
||||
|
||||
// Update registers
|
||||
@ -866,18 +1045,23 @@ void IntegrationPluginSma::markSpeedwireBatteryAsDisconnected(Thing *thing)
|
||||
thing->setStateValue(speedwireBatteryChargingStateStateTypeId, "idle");
|
||||
}
|
||||
|
||||
void IntegrationPluginSma::markModbusInverterAsDisconnected(Thing *thing)
|
||||
void IntegrationPluginSma::markModbusSolarInverterAsDisconnected(Thing *thing)
|
||||
{
|
||||
thing->setStateValue(modbusInverterVoltagePhaseAStateTypeId, 0);
|
||||
thing->setStateValue(modbusInverterVoltagePhaseBStateTypeId, 0);
|
||||
thing->setStateValue(modbusInverterVoltagePhaseCStateTypeId, 0);
|
||||
thing->setStateValue(modbusInverterCurrentPhaseAStateTypeId, 0);
|
||||
thing->setStateValue(modbusInverterCurrentPhaseBStateTypeId, 0);
|
||||
thing->setStateValue(modbusInverterCurrentPhaseCStateTypeId, 0);
|
||||
thing->setStateValue(modbusInverterCurrentPowerPhaseAStateTypeId, 0);
|
||||
thing->setStateValue(modbusInverterCurrentPowerPhaseBStateTypeId, 0);
|
||||
thing->setStateValue(modbusInverterCurrentPowerPhaseCStateTypeId, 0);
|
||||
thing->setStateValue(modbusInverterCurrentPowerStateTypeId, 0);
|
||||
thing->setStateValue(modbusSolarInverterVoltagePhaseAStateTypeId, 0);
|
||||
thing->setStateValue(modbusSolarInverterVoltagePhaseBStateTypeId, 0);
|
||||
thing->setStateValue(modbusSolarInverterVoltagePhaseCStateTypeId, 0);
|
||||
thing->setStateValue(modbusSolarInverterCurrentPhaseAStateTypeId, 0);
|
||||
thing->setStateValue(modbusSolarInverterCurrentPhaseBStateTypeId, 0);
|
||||
thing->setStateValue(modbusSolarInverterCurrentPhaseCStateTypeId, 0);
|
||||
thing->setStateValue(modbusSolarInverterCurrentPowerPhaseAStateTypeId, 0);
|
||||
thing->setStateValue(modbusSolarInverterCurrentPowerPhaseBStateTypeId, 0);
|
||||
thing->setStateValue(modbusSolarInverterCurrentPowerPhaseCStateTypeId, 0);
|
||||
thing->setStateValue(modbusSolarInverterCurrentPowerStateTypeId, 0);
|
||||
}
|
||||
|
||||
void IntegrationPluginSma::markModbusBatteryInverterAsDisconnected(Thing *thing)
|
||||
{
|
||||
thing->setStateValue(modbusBatteryInverterCurrentPowerStateTypeId, 0);
|
||||
}
|
||||
|
||||
quint64 IntegrationPluginSma::getLocalSerialNumber()
|
||||
|
||||
@ -42,7 +42,8 @@
|
||||
#include "speedwire/speedwireinverter.h"
|
||||
#include "speedwire/speedwireinterface.h"
|
||||
|
||||
#include "smainvertermodbustcpconnection.h"
|
||||
#include "smasolarinvertermodbustcpconnection.h"
|
||||
#include "smabatteryinvertermodbustcpconnection.h"
|
||||
|
||||
class IntegrationPluginSma: public IntegrationPlugin {
|
||||
Q_OBJECT
|
||||
@ -68,7 +69,8 @@ private slots:
|
||||
|
||||
void setupRefreshTimer();
|
||||
|
||||
void setupModbusInverterConnection(ThingSetupInfo *info);
|
||||
void setupModbusSolarInverterConnection(ThingSetupInfo *info);
|
||||
void setupModbusBatteryInverterConnection(ThingSetupInfo *info);
|
||||
|
||||
private:
|
||||
PluginTimer *m_refreshTimer = nullptr;
|
||||
@ -78,7 +80,8 @@ private:
|
||||
QHash<Thing *, SunnyWebBox *> m_sunnyWebBoxes;
|
||||
QHash<Thing *, SpeedwireMeter *> m_speedwireMeters;
|
||||
QHash<Thing *, SpeedwireInverter *> m_speedwireInverters;
|
||||
QHash<Thing *, SmaInverterModbusTcpConnection *> m_modbusInverters;
|
||||
QHash<Thing *, SmaSolarInverterModbusTcpConnection *> m_modbusSolarInverters;
|
||||
QHash<Thing *, SmaBatteryInverterModbusTcpConnection *> m_modbusBatteryInverters;
|
||||
|
||||
quint32 m_localSerialNumber = 0;
|
||||
|
||||
@ -89,7 +92,8 @@ private:
|
||||
void markSpeedwireMeterAsDisconnected(Thing *thing);
|
||||
void markSpeedwireInverterAsDisconnected(Thing *thing);
|
||||
void markSpeedwireBatteryAsDisconnected(Thing *thing);
|
||||
void markModbusInverterAsDisconnected(Thing *thing);
|
||||
void markModbusSolarInverterAsDisconnected(Thing *thing);
|
||||
void markModbusBatteryInverterAsDisconnected(Thing *thing);
|
||||
|
||||
quint64 getLocalSerialNumber();
|
||||
|
||||
|
||||
@ -534,8 +534,8 @@
|
||||
},
|
||||
{
|
||||
"id": "12e0429e-e8ce-48bd-a11c-faaf0bd71856",
|
||||
"name": "modbusInverter",
|
||||
"displayName": "SMA Inverter (Modbus)",
|
||||
"name": "modbusSolarInverter",
|
||||
"displayName": "SMA Solar Inverter (Modbus)",
|
||||
"createMethods": ["discovery", "user"],
|
||||
"interfaces": [ "solarinverter" ],
|
||||
"paramTypes": [
|
||||
@ -693,6 +693,104 @@
|
||||
"defaultValue": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "06bed8fd-cadb-4cef-8440-7806fb0165e6",
|
||||
"name": "modbusBatteryInverter",
|
||||
"displayName": "SMA Battery Inverter (Modbus)",
|
||||
"createMethods": ["discovery", "user"],
|
||||
"interfaces": [ "energystorage", "connectable" ],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "03a5a009-0edc-4370-924a-785e7fcee30a",
|
||||
"name":"macAddress",
|
||||
"displayName": "MAC address",
|
||||
"type": "QString",
|
||||
"inputType": "MacAddress",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "089d29e3-8ce0-42ca-93cf-463ad5a486af",
|
||||
"name":"port",
|
||||
"displayName": "Port",
|
||||
"type": "int",
|
||||
"defaultValue": 502
|
||||
},
|
||||
{
|
||||
"id": "081814d7-26bb-445e-bccd-7f33c0d933ea",
|
||||
"name":"slaveId",
|
||||
"displayName": "Slave ID",
|
||||
"type": "int",
|
||||
"defaultValue": 3
|
||||
},
|
||||
{
|
||||
"id": "9e2a69a0-c62c-4c53-b9f4-a2f9cb54f02c",
|
||||
"name":"serialNumber",
|
||||
"displayName": "Serial number",
|
||||
"type": "QString",
|
||||
"defaultValue": "",
|
||||
"readOnly": true
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "9c4999a1-304d-4724-99cd-eb0cd27590ef",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "fe5ca68e-ddc2-45e7-aac2-b0e67ac40f87",
|
||||
"name": "batteryLevel",
|
||||
"displayName": "Battery level",
|
||||
"type": "int",
|
||||
"unit": "Percentage",
|
||||
"minValue": 0,
|
||||
"maxValue": 100,
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "56f18b28-ed88-4c1a-a297-a5cad109b055",
|
||||
"name": "batteryCritical",
|
||||
"displayName": "Battery critical",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "e1a91af1-8d1a-4564-9ade-b5488d63b90d",
|
||||
"name": "currentPower",
|
||||
"displayName": "Current power",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "13cdb994-dd9e-49ac-a347-d2ab9aef5b45",
|
||||
"name": "capacity",
|
||||
"displayName": "Capacity",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "a313b416-5ded-43c9-b1a1-a9af50492d0b",
|
||||
"name": "chargingState",
|
||||
"displayName": "Charging state",
|
||||
"type": "QString",
|
||||
"possibleValues": ["idle", "charging", "discharging"],
|
||||
"defaultValue": "idle"
|
||||
},
|
||||
{
|
||||
"id": "952b3d30-1c09-4b0e-b303-56c89d3fa108",
|
||||
"name": "firmwareVersion",
|
||||
"displayName": "Firmware version",
|
||||
"type": "QString",
|
||||
"defaultValue": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
150
sma/modbus/smamodbusbatteryinverterdiscovery.cpp
Normal file
150
sma/modbus/smamodbusbatteryinverterdiscovery.cpp
Normal file
@ -0,0 +1,150 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2023, 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 "smamodbusbatteryinverterdiscovery.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
#include "sma.h"
|
||||
|
||||
SmaModbusBatteryInverterDiscovery::SmaModbusBatteryInverterDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint16 port, quint16 modbusAddress, QObject *parent):
|
||||
QObject(parent),
|
||||
m_networkDeviceDiscovery{networkDeviceDiscovery},
|
||||
m_port(port),
|
||||
m_modbusAddress(modbusAddress)
|
||||
{
|
||||
m_gracePeriodTimer.setSingleShot(true);
|
||||
m_gracePeriodTimer.setInterval(3000);
|
||||
connect(&m_gracePeriodTimer, &QTimer::timeout, this, [this](){
|
||||
qCDebug(dcSma()) << "Discovery: Grace period timer triggered.";
|
||||
finishDiscovery();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void SmaModbusBatteryInverterDiscovery::startDiscovery()
|
||||
{
|
||||
qCInfo(dcSma()) << "Discovery: Searching for SMA battery inverters in the network...";
|
||||
NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover();
|
||||
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &SmaModbusBatteryInverterDiscovery::checkNetworkDevice);
|
||||
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){
|
||||
qCDebug(dcSma()) << "Discovery: Network discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "network devices";
|
||||
m_gracePeriodTimer.start();
|
||||
discoveryReply->deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
QList<SmaModbusBatteryInverterDiscovery::Result> SmaModbusBatteryInverterDiscovery::discoveryResults() const
|
||||
{
|
||||
return m_discoveryResults;
|
||||
}
|
||||
|
||||
void SmaModbusBatteryInverterDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo)
|
||||
{
|
||||
qCInfo(dcSma()) << "Checking network device:" << networkDeviceInfo << "Port:" << m_port << "Slave ID:" << m_modbusAddress;
|
||||
|
||||
SmaBatteryInverterModbusTcpConnection *connection = new SmaBatteryInverterModbusTcpConnection(networkDeviceInfo.address(), m_port, m_modbusAddress, this);
|
||||
m_connections.append(connection);
|
||||
|
||||
connect(connection, &SmaBatteryInverterModbusTcpConnection::reachableChanged, this, [=](bool reachable){
|
||||
if (!reachable) {
|
||||
cleanupConnection(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
connect(connection, &SmaBatteryInverterModbusTcpConnection::initializationFinished, this, [=](bool success){
|
||||
if (!success) {
|
||||
qCInfo(dcSma()) << "Discovery: Initialization failed on" << networkDeviceInfo.address().toString() << "Skipping result...";;
|
||||
cleanupConnection(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
if (connection->deviceClass() != Sma::DeviceClassBatteryInverter) {
|
||||
qCInfo(dcSma()) << "Discovery: Initialization successful for" << networkDeviceInfo.address().toString() << "but the device class is not a battery inverter. Skipping result...";;
|
||||
cleanupConnection(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
Result result;
|
||||
result.deviceName = connection->deviceName();
|
||||
result.serialNumber = QString::number(connection->serialNumber());
|
||||
result.port = m_port;
|
||||
result.modbusAddress = m_modbusAddress;
|
||||
result.softwareVersion = Sma::buildSoftwareVersionString(connection->softwarePackage());
|
||||
result.networkDeviceInfo = networkDeviceInfo;
|
||||
m_discoveryResults.append(result);
|
||||
|
||||
qCInfo(dcSma()) << "Discovery: --> Found";
|
||||
qCInfo(dcSma()) << " Device name:" << result.deviceName;
|
||||
qCInfo(dcSma()) << " Serial number:" << result.serialNumber;
|
||||
qCInfo(dcSma()) << " Software version:" << result.softwareVersion;
|
||||
qCInfo(dcSma()) << " " << result.networkDeviceInfo;
|
||||
|
||||
cleanupConnection(connection);
|
||||
});
|
||||
|
||||
if (!connection->initialize()) {
|
||||
qCDebug(dcSma()) << "Discovery: Unable to initialize connection on" << networkDeviceInfo.address().toString();
|
||||
cleanupConnection(connection);
|
||||
}
|
||||
});
|
||||
|
||||
connect(connection, &SmaBatteryInverterModbusTcpConnection::checkReachabilityFailed, this, [=](){
|
||||
qCDebug(dcSma()) << "Discovery: Checking reachability failed on" << networkDeviceInfo.address().toString();
|
||||
cleanupConnection(connection);
|
||||
});
|
||||
|
||||
connection->connectDevice();
|
||||
|
||||
}
|
||||
|
||||
void SmaModbusBatteryInverterDiscovery::cleanupConnection(SmaBatteryInverterModbusTcpConnection *connection)
|
||||
{
|
||||
m_connections.removeAll(connection);
|
||||
connection->disconnectDevice();
|
||||
connection->deleteLater();
|
||||
}
|
||||
|
||||
void SmaModbusBatteryInverterDiscovery::finishDiscovery()
|
||||
{
|
||||
qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch();
|
||||
|
||||
// Cleanup any leftovers...we don't care any more
|
||||
foreach (SmaBatteryInverterModbusTcpConnection *connection, m_connections)
|
||||
cleanupConnection(connection);
|
||||
|
||||
qCInfo(dcSma()) << "Discovery: Finished the discovery process. Found" << m_discoveryResults.count()
|
||||
<< "SMA battery inverters in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz");
|
||||
m_gracePeriodTimer.stop();
|
||||
|
||||
emit discoveryFinished();
|
||||
|
||||
}
|
||||
81
sma/modbus/smamodbusbatteryinverterdiscovery.h
Normal file
81
sma/modbus/smamodbusbatteryinverterdiscovery.h
Normal file
@ -0,0 +1,81 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2023, 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 SMAMODBUSBATTERYINVERTERDISCOVERY_H
|
||||
#define SMAMODBUSBATTERYINVERTERDISCOVERY_H
|
||||
|
||||
#include <network/networkdevicediscovery.h>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "smabatteryinvertermodbustcpconnection.h"
|
||||
|
||||
class SmaModbusBatteryInverterDiscovery : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SmaModbusBatteryInverterDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint16 port = 502, quint16 modbusAddress = 3, QObject *parent = nullptr);
|
||||
|
||||
struct Result {
|
||||
QString deviceName;
|
||||
QString serialNumber;
|
||||
int port;
|
||||
int modbusAddress;
|
||||
QString softwareVersion;
|
||||
NetworkDeviceInfo networkDeviceInfo;
|
||||
};
|
||||
|
||||
void startDiscovery();
|
||||
|
||||
QList<Result> discoveryResults() const;
|
||||
|
||||
signals:
|
||||
void discoveryFinished();
|
||||
|
||||
private:
|
||||
NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr;
|
||||
uint m_port;
|
||||
uint m_modbusAddress;
|
||||
|
||||
QTimer m_gracePeriodTimer;
|
||||
QDateTime m_startDateTime;
|
||||
|
||||
QList<SmaBatteryInverterModbusTcpConnection *> m_connections;
|
||||
|
||||
QList<Result> m_discoveryResults;
|
||||
|
||||
void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo);
|
||||
void cleanupConnection(SmaBatteryInverterModbusTcpConnection *connection);
|
||||
|
||||
void finishDiscovery();
|
||||
|
||||
};
|
||||
|
||||
#endif // SMAMODBUSBATTERYINVERTERDISCOVERY_H
|
||||
@ -28,13 +28,13 @@
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "smamodbusdiscovery.h"
|
||||
#include "smamodbussolarinverterdiscovery.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
#include "sma.h"
|
||||
|
||||
|
||||
SmaModbusDiscovery::SmaModbusDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint16 port, quint16 modbusAddress,QObject *parent)
|
||||
SmaModbusSolarInverterDiscovery::SmaModbusSolarInverterDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint16 port, quint16 modbusAddress,QObject *parent)
|
||||
: QObject{parent},
|
||||
m_networkDeviceDiscovery{networkDeviceDiscovery},
|
||||
m_port{port},
|
||||
@ -43,13 +43,13 @@ SmaModbusDiscovery::SmaModbusDiscovery(NetworkDeviceDiscovery *networkDeviceDisc
|
||||
|
||||
}
|
||||
|
||||
void SmaModbusDiscovery::startDiscovery()
|
||||
void SmaModbusSolarInverterDiscovery::startDiscovery()
|
||||
{
|
||||
qCInfo(dcSma()) << "Discovery: Start searching for SMA modbus inverters in the network...";
|
||||
NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover();
|
||||
|
||||
// Imedialty check any new device gets discovered
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &SmaModbusDiscovery::checkNetworkDevice);
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &SmaModbusSolarInverterDiscovery::checkNetworkDevice);
|
||||
|
||||
// Check what might be left on finished
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, discoveryReply, &NetworkDeviceDiscoveryReply::deleteLater);
|
||||
@ -71,12 +71,12 @@ void SmaModbusDiscovery::startDiscovery()
|
||||
});
|
||||
}
|
||||
|
||||
QList<SmaModbusDiscovery::SmaModbusDiscoveryResult> SmaModbusDiscovery::discoveryResults() const
|
||||
QList<SmaModbusSolarInverterDiscovery::SmaModbusDiscoveryResult> SmaModbusSolarInverterDiscovery::discoveryResults() const
|
||||
{
|
||||
return m_discoveryResults;
|
||||
}
|
||||
|
||||
void SmaModbusDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo)
|
||||
void SmaModbusSolarInverterDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo)
|
||||
{
|
||||
// Create a kostal connection and try to initialize it.
|
||||
// Only if initialized successfully and all information have been fetched correctly from
|
||||
@ -86,11 +86,11 @@ void SmaModbusDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevi
|
||||
if (m_verifiedNetworkDeviceInfos.contains(networkDeviceInfo))
|
||||
return;
|
||||
|
||||
SmaInverterModbusTcpConnection *connection = new SmaInverterModbusTcpConnection(networkDeviceInfo.address(), m_port, m_modbusAddress, this);
|
||||
SmaSolarInverterModbusTcpConnection *connection = new SmaSolarInverterModbusTcpConnection(networkDeviceInfo.address(), m_port, m_modbusAddress, this);
|
||||
m_connections.append(connection);
|
||||
m_verifiedNetworkDeviceInfos.append(networkDeviceInfo);
|
||||
|
||||
connect(connection, &SmaInverterModbusTcpConnection::reachableChanged, this, [=](bool reachable){
|
||||
connect(connection, &SmaSolarInverterModbusTcpConnection::reachableChanged, this, [=](bool reachable){
|
||||
if (!reachable) {
|
||||
// Disconnected ... done with this connection
|
||||
cleanupConnection(connection);
|
||||
@ -98,7 +98,7 @@ void SmaModbusDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevi
|
||||
}
|
||||
|
||||
// Modbus TCP connected...ok, let's try to initialize it!
|
||||
connect(connection, &SmaInverterModbusTcpConnection::initializationFinished, this, [=](bool success){
|
||||
connect(connection, &SmaSolarInverterModbusTcpConnection::initializationFinished, this, [=](bool success){
|
||||
if (!success) {
|
||||
qCDebug(dcSma()) << "Discovery: Initialization failed on" << networkDeviceInfo.address().toString() << "Continue...";;
|
||||
cleanupConnection(connection);
|
||||
@ -147,7 +147,7 @@ void SmaModbusDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevi
|
||||
});
|
||||
|
||||
// If check reachability failed...skip this host...
|
||||
connect(connection, &SmaInverterModbusTcpConnection::checkReachabilityFailed, this, [=](){
|
||||
connect(connection, &SmaSolarInverterModbusTcpConnection::checkReachabilityFailed, this, [=](){
|
||||
qCDebug(dcSma()) << "Discovery: Check reachability failed on" << networkDeviceInfo.address().toString() << "Continue...";;
|
||||
cleanupConnection(connection);
|
||||
});
|
||||
@ -156,19 +156,19 @@ void SmaModbusDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevi
|
||||
connection->connectDevice();
|
||||
}
|
||||
|
||||
void SmaModbusDiscovery::cleanupConnection(SmaInverterModbusTcpConnection *connection)
|
||||
void SmaModbusSolarInverterDiscovery::cleanupConnection(SmaSolarInverterModbusTcpConnection *connection)
|
||||
{
|
||||
m_connections.removeAll(connection);
|
||||
connection->disconnectDevice();
|
||||
connection->deleteLater();
|
||||
}
|
||||
|
||||
void SmaModbusDiscovery::finishDiscovery()
|
||||
void SmaModbusSolarInverterDiscovery::finishDiscovery()
|
||||
{
|
||||
qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch();
|
||||
|
||||
// Cleanup any leftovers...we don't care any more
|
||||
foreach (SmaInverterModbusTcpConnection *connection, m_connections)
|
||||
foreach (SmaSolarInverterModbusTcpConnection *connection, m_connections)
|
||||
cleanupConnection(connection);
|
||||
|
||||
qCInfo(dcSma()) << "Discovery: Finished the discovery process. Found" << m_discoveryResults.count() << "SMA inverters in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz");
|
||||
@ -28,21 +28,21 @@
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef SMAMODBUSDISCOVERY_H
|
||||
#define SMAMODBUSDISCOVERY_H
|
||||
#ifndef SMAMODBUSSOLARINVERTERDISCOVERY_H
|
||||
#define SMAMODBUSSOLARINVERTERDISCOVERY_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
#include <network/networkdevicediscovery.h>
|
||||
|
||||
#include "smainvertermodbustcpconnection.h"
|
||||
#include "smasolarinvertermodbustcpconnection.h"
|
||||
|
||||
class SmaModbusDiscovery : public QObject
|
||||
class SmaModbusSolarInverterDiscovery : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SmaModbusDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint16 port = 502, quint16 modbusAddress = 3, QObject *parent = nullptr);
|
||||
explicit SmaModbusSolarInverterDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint16 port = 502, quint16 modbusAddress = 3, QObject *parent = nullptr);
|
||||
typedef struct SmaModbusDiscoveryResult {
|
||||
QString productName;
|
||||
QString deviceName;
|
||||
@ -68,15 +68,15 @@ private:
|
||||
QDateTime m_startDateTime;
|
||||
NetworkDeviceInfos m_verifiedNetworkDeviceInfos;
|
||||
|
||||
QList<SmaInverterModbusTcpConnection *> m_connections;
|
||||
QList<SmaSolarInverterModbusTcpConnection *> m_connections;
|
||||
|
||||
QList<SmaModbusDiscoveryResult> m_discoveryResults;
|
||||
|
||||
void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo);
|
||||
void cleanupConnection(SmaInverterModbusTcpConnection *connection);
|
||||
void cleanupConnection(SmaSolarInverterModbusTcpConnection *connection);
|
||||
|
||||
void finishDiscovery();
|
||||
|
||||
};
|
||||
|
||||
#endif // SMAMODBUSDISCOVERY_H
|
||||
#endif // SMAMODBUSSOLARINVERTERDISCOVERY_H
|
||||
97
sma/sma-battery-inverter-registers.json
Normal file
97
sma/sma-battery-inverter-registers.json
Normal file
@ -0,0 +1,97 @@
|
||||
{
|
||||
"className": "SmaBatteryInverter",
|
||||
"protocol": "TCP",
|
||||
"endianness": "BigEndian",
|
||||
"errorLimitUntilNotReachable": 20,
|
||||
"checkReachableRegister": "currentPower",
|
||||
"blocks": [
|
||||
{
|
||||
"id": "identification",
|
||||
"readSchedule": "init",
|
||||
"registers": [
|
||||
{
|
||||
"id": "deviceClass",
|
||||
"address": 30051,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Device class",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "modelIdentifier",
|
||||
"address": 30053,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Device type (model identifier)",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "information",
|
||||
"readSchedule": "init",
|
||||
"registers": [
|
||||
{
|
||||
"id": "serialNumber",
|
||||
"address": 30057,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Serial number",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "softwarePackage",
|
||||
"address": 30059,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Firmware version",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"registers": [
|
||||
{
|
||||
"id": "deviceName",
|
||||
"address": 40631,
|
||||
"size": 32,
|
||||
"type": "string",
|
||||
"readSchedule": "init",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Device name",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "currentPower",
|
||||
"address": 30775,
|
||||
"size": 2,
|
||||
"type": "int32",
|
||||
"readSchedule": "update",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Current power",
|
||||
"unit": "W",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "batterySOC",
|
||||
"address": 30845,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"readSchedule": "update",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Battery State Of Charge",
|
||||
"unit": "%",
|
||||
"defaultValue": 0,
|
||||
"access": "RO"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"className": "SmaInverter",
|
||||
"className": "SmaSolarInverter",
|
||||
"protocol": "TCP",
|
||||
"endianness": "BigEndian",
|
||||
"errorLimitUntilNotReachable": 20,
|
||||
@ -3,13 +3,14 @@ include(../plugins.pri)
|
||||
QT += network
|
||||
|
||||
# Generate modbus connection
|
||||
MODBUS_CONNECTIONS += sma-inverter-registers.json
|
||||
MODBUS_CONNECTIONS += sma-solar-inverter-registers.json sma-battery-inverter-registers.json
|
||||
MODBUS_TOOLS_CONFIG += VERBOSE
|
||||
include(../modbus.pri)
|
||||
|
||||
SOURCES += \
|
||||
integrationpluginsma.cpp \
|
||||
modbus/smamodbusdiscovery.cpp \
|
||||
modbus/smamodbusbatteryinverterdiscovery.cpp \
|
||||
modbus/smamodbussolarinverterdiscovery.cpp \
|
||||
speedwire/speedwirediscovery.cpp \
|
||||
speedwire/speedwireinterface.cpp \
|
||||
speedwire/speedwireinverter.cpp \
|
||||
@ -21,7 +22,8 @@ SOURCES += \
|
||||
|
||||
HEADERS += \
|
||||
integrationpluginsma.h \
|
||||
modbus/smamodbusdiscovery.h \
|
||||
modbus/smamodbusbatteryinverterdiscovery.h \
|
||||
modbus/smamodbussolarinverterdiscovery.h \
|
||||
sma.h \
|
||||
speedwire/speedwire.h \
|
||||
speedwire/speedwirediscovery.h \
|
||||
|
||||
Reference in New Issue
Block a user