From 1b813d29831bd3bff25fdae2256bd430581c2493 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Tue, 4 Jul 2023 14:12:05 +0200 Subject: [PATCH] SMA: Add support for batteries --- sma/README.md | 5 +- sma/integrationpluginsma.cpp | 302 ++++++++++++++---- sma/integrationpluginsma.h | 12 +- sma/integrationpluginsma.json | 102 +++++- .../smamodbusbatteryinverterdiscovery.cpp | 150 +++++++++ .../smamodbusbatteryinverterdiscovery.h | 81 +++++ ...pp => smamodbussolarinverterdiscovery.cpp} | 26 +- ...ry.h => smamodbussolarinverterdiscovery.h} | 16 +- sma/sma-battery-inverter-registers.json | 97 ++++++ ...json => sma-solar-inverter-registers.json} | 2 +- sma/sma.pro | 8 +- 11 files changed, 708 insertions(+), 93 deletions(-) create mode 100644 sma/modbus/smamodbusbatteryinverterdiscovery.cpp create mode 100644 sma/modbus/smamodbusbatteryinverterdiscovery.h rename sma/modbus/{smamodbusdiscovery.cpp => smamodbussolarinverterdiscovery.cpp} (84%) rename sma/modbus/{smamodbusdiscovery.h => smamodbussolarinverterdiscovery.h} (81%) create mode 100644 sma/sma-battery-inverter-registers.json rename sma/{sma-inverter-registers.json => sma-solar-inverter-registers.json} (99%) diff --git a/sma/README.md b/sma/README.md index ad56627..c543859 100644 --- a/sma/README.md +++ b/sma/README.md @@ -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 diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 765cc0e..672ba86 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -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 @@ -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() diff --git a/sma/integrationpluginsma.h b/sma/integrationpluginsma.h index 3ffd43b..d5b8243 100644 --- a/sma/integrationpluginsma.h +++ b/sma/integrationpluginsma.h @@ -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 m_sunnyWebBoxes; QHash m_speedwireMeters; QHash m_speedwireInverters; - QHash m_modbusInverters; + QHash m_modbusSolarInverters; + QHash 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(); diff --git a/sma/integrationpluginsma.json b/sma/integrationpluginsma.json index 02f9f4d..a939ae2 100644 --- a/sma/integrationpluginsma.json +++ b/sma/integrationpluginsma.json @@ -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": "" + } + ] } ] } diff --git a/sma/modbus/smamodbusbatteryinverterdiscovery.cpp b/sma/modbus/smamodbusbatteryinverterdiscovery.cpp new file mode 100644 index 0000000..94612f6 --- /dev/null +++ b/sma/modbus/smamodbusbatteryinverterdiscovery.cpp @@ -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 . +* +* 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::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(); + +} diff --git a/sma/modbus/smamodbusbatteryinverterdiscovery.h b/sma/modbus/smamodbusbatteryinverterdiscovery.h new file mode 100644 index 0000000..053ff9a --- /dev/null +++ b/sma/modbus/smamodbusbatteryinverterdiscovery.h @@ -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 . +* +* 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 + +#include + +#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 discoveryResults() const; + +signals: + void discoveryFinished(); + +private: + NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr; + uint m_port; + uint m_modbusAddress; + + QTimer m_gracePeriodTimer; + QDateTime m_startDateTime; + + QList m_connections; + + QList m_discoveryResults; + + void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo); + void cleanupConnection(SmaBatteryInverterModbusTcpConnection *connection); + + void finishDiscovery(); + +}; + +#endif // SMAMODBUSBATTERYINVERTERDISCOVERY_H diff --git a/sma/modbus/smamodbusdiscovery.cpp b/sma/modbus/smamodbussolarinverterdiscovery.cpp similarity index 84% rename from sma/modbus/smamodbusdiscovery.cpp rename to sma/modbus/smamodbussolarinverterdiscovery.cpp index 22c7d2b..22a49ee 100644 --- a/sma/modbus/smamodbusdiscovery.cpp +++ b/sma/modbus/smamodbussolarinverterdiscovery.cpp @@ -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::discoveryResults() const +QList 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"); diff --git a/sma/modbus/smamodbusdiscovery.h b/sma/modbus/smamodbussolarinverterdiscovery.h similarity index 81% rename from sma/modbus/smamodbusdiscovery.h rename to sma/modbus/smamodbussolarinverterdiscovery.h index 0ed6290..e79de9e 100644 --- a/sma/modbus/smamodbusdiscovery.h +++ b/sma/modbus/smamodbussolarinverterdiscovery.h @@ -28,21 +28,21 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -#ifndef SMAMODBUSDISCOVERY_H -#define SMAMODBUSDISCOVERY_H +#ifndef SMAMODBUSSOLARINVERTERDISCOVERY_H +#define SMAMODBUSSOLARINVERTERDISCOVERY_H #include #include #include -#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 m_connections; + QList m_connections; QList m_discoveryResults; void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo); - void cleanupConnection(SmaInverterModbusTcpConnection *connection); + void cleanupConnection(SmaSolarInverterModbusTcpConnection *connection); void finishDiscovery(); }; -#endif // SMAMODBUSDISCOVERY_H +#endif // SMAMODBUSSOLARINVERTERDISCOVERY_H diff --git a/sma/sma-battery-inverter-registers.json b/sma/sma-battery-inverter-registers.json new file mode 100644 index 0000000..daf64c3 --- /dev/null +++ b/sma/sma-battery-inverter-registers.json @@ -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" + } + ] +} diff --git a/sma/sma-inverter-registers.json b/sma/sma-solar-inverter-registers.json similarity index 99% rename from sma/sma-inverter-registers.json rename to sma/sma-solar-inverter-registers.json index 6132087..e0f9df5 100644 --- a/sma/sma-inverter-registers.json +++ b/sma/sma-solar-inverter-registers.json @@ -1,5 +1,5 @@ { - "className": "SmaInverter", + "className": "SmaSolarInverter", "protocol": "TCP", "endianness": "BigEndian", "errorLimitUntilNotReachable": 20, diff --git a/sma/sma.pro b/sma/sma.pro index ce064d7..c5bba89 100644 --- a/sma/sma.pro +++ b/sma/sma.pro @@ -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 \