From 0bdfeb81dca7a4b29dc8192a0015a0982bb51acf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 30 May 2023 15:12:33 +0200 Subject: [PATCH 1/2] SunSpec: Fix autoremove devices if the model does not exist any more --- sunspec/integrationpluginsunspec.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sunspec/integrationpluginsunspec.cpp b/sunspec/integrationpluginsunspec.cpp index 9fad0f0..d427b21 100644 --- a/sunspec/integrationpluginsunspec.cpp +++ b/sunspec/integrationpluginsunspec.cpp @@ -626,12 +626,16 @@ void IntegrationPluginSunSpec::processDiscoveryResult(Thing *thing, SunSpecConne // - Some SunSpec device seem to communicate different model id depending on the startup phase // i.e. they communicate a SinglePhase Meter on register x, few mnutes later it is a 3 phase meter on x // This code should handle such weird setups... + + if (connection->models().isEmpty()) + return; + foreach (Thing *child, myThings().filterByParentId(thing->id())) { - if (!m_modelIdParamTypeIds.contains(child->thingClassId()) || connection->models().isEmpty()) + if (!m_modelIdParamTypeIds.contains(child->thingClassId()) || !m_modbusAddressParamTypeIds.contains(child->thingClassId())) continue; - uint childModelId = child->paramValue(m_modelIdParamTypeIds.value(thing->thingClassId())).toUInt(); - uint childModbusAddress = child->paramValue(m_modbusAddressParamTypeIds.value(thing->thingClassId())).toUInt(); + uint childModelId = child->paramValue(m_modelIdParamTypeIds.value(child->thingClassId())).toUInt(); + uint childModbusAddress = child->paramValue(m_modbusAddressParamTypeIds.value(child->thingClassId())).toUInt(); bool modelFoundForChild = false; foreach (SunSpecModel *model, connection->models()) { From ae3a1cb407cba33f9a270ba9c715489bc9c6543e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 31 May 2023 11:27:20 +0200 Subject: [PATCH 2/2] SunSpec: Improve discovery and add slave ID scanning --- sunspec/integrationpluginsunspec.cpp | 9 +- sunspec/sunspecdiscovery.cpp | 129 ++++++++++++++++----------- sunspec/sunspecdiscovery.h | 12 ++- 3 files changed, 94 insertions(+), 56 deletions(-) diff --git a/sunspec/integrationpluginsunspec.cpp b/sunspec/integrationpluginsunspec.cpp index d427b21..b16c4ec 100644 --- a/sunspec/integrationpluginsunspec.cpp +++ b/sunspec/integrationpluginsunspec.cpp @@ -133,7 +133,13 @@ void IntegrationPluginSunSpec::discoverThings(ThingDiscoveryInfo *info) return; } - SunSpecDiscovery *discovery = new SunSpecDiscovery(hardwareManager()->networkDeviceDiscovery(), 1, info); + QList slaveIds = {1, 2}; + SunSpecDataPoint::ByteOrder byteOrder = SunSpecDataPoint::ByteOrderLittleEndian; + if (info->thingClassId() == solarEdgeConnectionThingClassId) { + byteOrder = SunSpecDataPoint::ByteOrderBigEndian; + } + + SunSpecDiscovery *discovery = new SunSpecDiscovery(hardwareManager()->networkDeviceDiscovery(), slaveIds, byteOrder, info); // Note: we could add here more connect(discovery, &SunSpecDiscovery::discoveryFinished, info, [=](){ foreach (const SunSpecDiscovery::Result &result, discovery->results()) { @@ -189,6 +195,7 @@ void IntegrationPluginSunSpec::discoverThings(ThingDiscoveryInfo *info) ParamList params; params << Param(m_connectionPortParamTypeIds.value(info->thingClassId()), result.port); params << Param(m_connectionMacAddressParamTypeIds.value(info->thingClassId()), result.networkDeviceInfo.macAddress()); + params << Param(m_connectionSlaveIdParamTypeIds.value(info->thingClassId()), result.slaveId); descriptor.setParams(params); info->addThingDescriptor(descriptor); } diff --git a/sunspec/sunspecdiscovery.cpp b/sunspec/sunspecdiscovery.cpp index e52dd7c..dcb1e3f 100644 --- a/sunspec/sunspecdiscovery.cpp +++ b/sunspec/sunspecdiscovery.cpp @@ -34,10 +34,11 @@ #include #include -SunSpecDiscovery::SunSpecDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint16 modbusAddress, QObject *parent) +SunSpecDiscovery::SunSpecDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, const QList &slaveIds, SunSpecDataPoint::ByteOrder byteOrder, QObject *parent) : QObject{parent}, m_networkDeviceDiscovery{networkDeviceDiscovery}, - m_modbusAddress{modbusAddress} + m_slaveIds{slaveIds}, + m_byteOrder{byteOrder} { m_scanPorts.append(502); m_scanPorts.append(1502); @@ -79,74 +80,98 @@ void SunSpecDiscovery::startDiscovery() }); } +void SunSpecDiscovery::testNextConnection(const QHostAddress &address) +{ + if (!m_pendingConnectionAttempts.contains(address)) + return; + + SunSpecConnection *connection = m_pendingConnectionAttempts[address].dequeue(); + if (m_pendingConnectionAttempts.value(address).isEmpty()) + m_pendingConnectionAttempts.remove(address); + + qCDebug(dcSunSpec()) << "Discovery: Start searching on" << QString("%1:%2").arg(address.toString()).arg(connection->port()) << "slave ID:" << connection->slaveId(); + // Try to connect, maybe it works, maybe not... + if (!connection->connectDevice()) { + qCDebug(dcSunSpec()) << "Discovery: Failed to connect to" << QString("%1:%2").arg(address.toString()).arg(connection->port()) << "slave ID:" << connection->slaveId() << "Continue...";; + cleanupConnection(connection); + } +} + void SunSpecDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo) { - // Create a sunspec connection and try to initialize it (read models). - if (m_verifiedNetworkDeviceInfos.contains(networkDeviceInfo)) - return; + // Create a connection queue for this network device + + QQueue connectionQueue; // Check all ports for this host foreach (quint16 port, m_scanPorts) { - SunSpecConnection *connection = new SunSpecConnection(networkDeviceInfo.address(), port, m_modbusAddress, this); - m_connections.append(connection); - m_verifiedNetworkDeviceInfos.append(networkDeviceInfo); - connect(connection, &SunSpecConnection::connectedChanged, this, [=](bool connected){ - if (!connected) { - // Disconnected ... done with this connection - cleanupConnection(connection); - return; - } + foreach (quint16 slaveId, m_slaveIds) { - // Modbus TCP connected, try to discovery sunspec models... - connect(connection, &SunSpecConnection::discoveryFinished, this, [=](bool success){ - if (!success) { - qCDebug(dcSunSpec()) << "Discovery: SunSpec discovery failed on" << networkDeviceInfo.address().toString() << "Continue...";; + SunSpecConnection *connection = new SunSpecConnection(networkDeviceInfo.address(), port, slaveId, m_byteOrder, this); + connection->setNumberOfRetries(1); + connection->setTimeout(500); + m_connections.append(connection); + connectionQueue.enqueue(connection); + + connect(connection, &SunSpecConnection::connectedChanged, this, [=](bool connected){ + if (!connected) { + // Disconnected ... done with this connection cleanupConnection(connection); return; } - // Success, we found some sunspec models here, let's read some infomation from the models + // Modbus TCP connected, try to discovery sunspec models... + connect(connection, &SunSpecConnection::discoveryFinished, this, [=](bool success){ + if (!success) { + qCDebug(dcSunSpec()) << "Discovery: SunSpec discovery failed on" << QString("%1:%2").arg(networkDeviceInfo.address().toString()).arg(port) << "slave ID:" << slaveId << "Continue...";; + cleanupConnection(connection); + return; + } - Result result; - result.networkDeviceInfo = networkDeviceInfo; - result.port = connection->port(); + // Success, we found some sunspec models here, let's read some infomation from the models - qCDebug(dcSunSpec()) << "Discovery: --> Found SunSpec devices on" << result.networkDeviceInfo << "port" << result.port; - foreach (SunSpecModel *model, connection->models()) { - if (model->modelId() == SunSpecModelFactory::ModelIdCommon) { - SunSpecCommonModel *commonModel = qobject_cast(model); - QString manufacturer = commonModel->manufacturer(); - if (!manufacturer.isEmpty() && !result.modelManufacturers.contains(manufacturer)) { - result.modelManufacturers.append(manufacturer); + Result result; + result.networkDeviceInfo = networkDeviceInfo; + result.port = connection->port(); + result.slaveId = connection->slaveId(); + + qCDebug(dcSunSpec()) << "Discovery: --> Found SunSpec devices on" << result.networkDeviceInfo << "port" << result.port << "slave ID:" << result.slaveId; + foreach (SunSpecModel *model, connection->models()) { + if (model->modelId() == SunSpecModelFactory::ModelIdCommon) { + SunSpecCommonModel *commonModel = qobject_cast(model); + QString manufacturer = commonModel->manufacturer(); + if (!manufacturer.isEmpty() && !result.modelManufacturers.contains(manufacturer)) { + result.modelManufacturers.append(manufacturer); + } } } + + m_results.append(result); + + // Done with this connection + cleanupConnection(connection); + }); + + // Run SunSpec discovery on connection... + if (!connection->startDiscovery()) { + qCDebug(dcSunSpec()) << "Discovery: Unable to discover SunSpec data on connection" << QString("%1:%2").arg(networkDeviceInfo.address().toString()).arg(port) << "slave ID:" << slaveId << "Continue...";; + cleanupConnection(connection); } - - m_results.append(result); - - // Done with this connection - cleanupConnection(connection); }); - // Run SunSPec discovery on connection... - if (!connection->startDiscovery()) { - qCDebug(dcSunSpec()) << "Discovery: Unable to discover SunSpec data on connection" << networkDeviceInfo.address().toString() << "Continue...";; - cleanupConnection(connection); - } - }); - - // If we get any error...skip this host... - connect(connection->modbusTcpClient(), &QModbusTcpClient::errorOccurred, this, [=](QModbusDevice::Error error){ - if (error != QModbusDevice::NoError) { - qCDebug(dcSunSpec()) << "Discovery: Connection error on" << networkDeviceInfo.address().toString() << "Continue...";; - cleanupConnection(connection); - } - }); - - // Try to connect, maybe it works, maybe not... - connection->connectDevice(); + // If we get any error...skip this host... + connect(connection->modbusTcpClient(), &QModbusTcpClient::errorOccurred, this, [=](QModbusDevice::Error error){ + if (error != QModbusDevice::NoError) { + qCDebug(dcSunSpec()) << "Discovery: Connection error on" << QString("%1:%2").arg(networkDeviceInfo.address().toString()).arg(port) << "slave ID:" << slaveId << "Continue...";; + cleanupConnection(connection); + } + }); + } } + + m_pendingConnectionAttempts[networkDeviceInfo.address()] = connectionQueue; + testNextConnection(networkDeviceInfo.address()); } void SunSpecDiscovery::cleanupConnection(SunSpecConnection *connection) @@ -154,6 +179,8 @@ void SunSpecDiscovery::cleanupConnection(SunSpecConnection *connection) m_connections.removeAll(connection); connection->disconnectDevice(); connection->deleteLater(); + + testNextConnection(connection->hostAddress()); } void SunSpecDiscovery::finishDiscovery() diff --git a/sunspec/sunspecdiscovery.h b/sunspec/sunspecdiscovery.h index c01e167..f54cee6 100644 --- a/sunspec/sunspecdiscovery.h +++ b/sunspec/sunspecdiscovery.h @@ -31,6 +31,7 @@ #ifndef SUNSPECDISCOVERY_H #define SUNSPECDISCOVERY_H +#include #include #include @@ -41,10 +42,11 @@ class SunSpecDiscovery : public QObject { Q_OBJECT public: - explicit SunSpecDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint16 modbusAddress = 1, QObject *parent = nullptr); + explicit SunSpecDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, const QList &slaveIds, SunSpecDataPoint::ByteOrder byteOrder = SunSpecDataPoint::ByteOrderLittleEndian, QObject *parent = nullptr); typedef struct Result { NetworkDeviceInfo networkDeviceInfo; quint16 port; + quint16 slaveId; QStringList modelManufacturers; } Result; @@ -58,16 +60,18 @@ signals: private: NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr; - quint16 m_modbusAddress; QList m_scanPorts; + QList m_slaveIds; + SunSpecDataPoint::ByteOrder m_byteOrder; QDateTime m_startDateTime; - - NetworkDeviceInfos m_verifiedNetworkDeviceInfos; + QHash> m_pendingConnectionAttempts; QList m_connections; QList m_results; + void testNextConnection(const QHostAddress &address); + void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo); void cleanupConnection(SunSpecConnection *connection);