Merge PR #133: SunSpec: Fix autoremove devices if the model does not exist any more

This commit is contained in:
jenkins 2023-06-11 19:44:23 +02:00
commit ea23c74892
3 changed files with 101 additions and 59 deletions

View File

@ -133,7 +133,13 @@ void IntegrationPluginSunSpec::discoverThings(ThingDiscoveryInfo *info)
return; return;
} }
SunSpecDiscovery *discovery = new SunSpecDiscovery(hardwareManager()->networkDeviceDiscovery(), 1, info); QList<quint16> 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 // Note: we could add here more
connect(discovery, &SunSpecDiscovery::discoveryFinished, info, [=](){ connect(discovery, &SunSpecDiscovery::discoveryFinished, info, [=](){
foreach (const SunSpecDiscovery::Result &result, discovery->results()) { foreach (const SunSpecDiscovery::Result &result, discovery->results()) {
@ -189,6 +195,7 @@ void IntegrationPluginSunSpec::discoverThings(ThingDiscoveryInfo *info)
ParamList params; ParamList params;
params << Param(m_connectionPortParamTypeIds.value(info->thingClassId()), result.port); params << Param(m_connectionPortParamTypeIds.value(info->thingClassId()), result.port);
params << Param(m_connectionMacAddressParamTypeIds.value(info->thingClassId()), result.networkDeviceInfo.macAddress()); params << Param(m_connectionMacAddressParamTypeIds.value(info->thingClassId()), result.networkDeviceInfo.macAddress());
params << Param(m_connectionSlaveIdParamTypeIds.value(info->thingClassId()), result.slaveId);
descriptor.setParams(params); descriptor.setParams(params);
info->addThingDescriptor(descriptor); info->addThingDescriptor(descriptor);
} }
@ -626,12 +633,16 @@ void IntegrationPluginSunSpec::processDiscoveryResult(Thing *thing, SunSpecConne
// - Some SunSpec device seem to communicate different model id depending on the startup phase // - 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 // 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... // This code should handle such weird setups...
if (connection->models().isEmpty())
return;
foreach (Thing *child, myThings().filterByParentId(thing->id())) { 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; continue;
uint childModelId = child->paramValue(m_modelIdParamTypeIds.value(thing->thingClassId())).toUInt(); uint childModelId = child->paramValue(m_modelIdParamTypeIds.value(child->thingClassId())).toUInt();
uint childModbusAddress = child->paramValue(m_modbusAddressParamTypeIds.value(thing->thingClassId())).toUInt(); uint childModbusAddress = child->paramValue(m_modbusAddressParamTypeIds.value(child->thingClassId())).toUInt();
bool modelFoundForChild = false; bool modelFoundForChild = false;
foreach (SunSpecModel *model, connection->models()) { foreach (SunSpecModel *model, connection->models()) {

View File

@ -34,10 +34,11 @@
#include <models/sunspecmodelfactory.h> #include <models/sunspecmodelfactory.h>
#include <models/sunspeccommonmodel.h> #include <models/sunspeccommonmodel.h>
SunSpecDiscovery::SunSpecDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint16 modbusAddress, QObject *parent) SunSpecDiscovery::SunSpecDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, const QList<quint16> &slaveIds, SunSpecDataPoint::ByteOrder byteOrder, QObject *parent)
: QObject{parent}, : QObject{parent},
m_networkDeviceDiscovery{networkDeviceDiscovery}, m_networkDeviceDiscovery{networkDeviceDiscovery},
m_modbusAddress{modbusAddress} m_slaveIds{slaveIds},
m_byteOrder{byteOrder}
{ {
m_scanPorts.append(502); m_scanPorts.append(502);
m_scanPorts.append(1502); 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) void SunSpecDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo)
{ {
// Create a sunspec connection and try to initialize it (read models). // Create a connection queue for this network device
if (m_verifiedNetworkDeviceInfos.contains(networkDeviceInfo))
return; QQueue<SunSpecConnection *> connectionQueue;
// Check all ports for this host // Check all ports for this host
foreach (quint16 port, m_scanPorts) { 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){ foreach (quint16 slaveId, m_slaveIds) {
if (!connected) {
// Disconnected ... done with this connection
cleanupConnection(connection);
return;
}
// Modbus TCP connected, try to discovery sunspec models... SunSpecConnection *connection = new SunSpecConnection(networkDeviceInfo.address(), port, slaveId, m_byteOrder, this);
connect(connection, &SunSpecConnection::discoveryFinished, this, [=](bool success){ connection->setNumberOfRetries(1);
if (!success) { connection->setTimeout(500);
qCDebug(dcSunSpec()) << "Discovery: SunSpec discovery failed on" << networkDeviceInfo.address().toString() << "Continue...";; m_connections.append(connection);
connectionQueue.enqueue(connection);
connect(connection, &SunSpecConnection::connectedChanged, this, [=](bool connected){
if (!connected) {
// Disconnected ... done with this connection
cleanupConnection(connection); cleanupConnection(connection);
return; 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; // Success, we found some sunspec models here, let's read some infomation from the models
result.networkDeviceInfo = networkDeviceInfo;
result.port = connection->port();
qCDebug(dcSunSpec()) << "Discovery: --> Found SunSpec devices on" << result.networkDeviceInfo << "port" << result.port; Result result;
foreach (SunSpecModel *model, connection->models()) { result.networkDeviceInfo = networkDeviceInfo;
if (model->modelId() == SunSpecModelFactory::ModelIdCommon) { result.port = connection->port();
SunSpecCommonModel *commonModel = qobject_cast<SunSpecCommonModel *>(model); result.slaveId = connection->slaveId();
QString manufacturer = commonModel->manufacturer();
if (!manufacturer.isEmpty() && !result.modelManufacturers.contains(manufacturer)) { qCDebug(dcSunSpec()) << "Discovery: --> Found SunSpec devices on" << result.networkDeviceInfo << "port" << result.port << "slave ID:" << result.slaveId;
result.modelManufacturers.append(manufacturer); foreach (SunSpecModel *model, connection->models()) {
if (model->modelId() == SunSpecModelFactory::ModelIdCommon) {
SunSpecCommonModel *commonModel = qobject_cast<SunSpecCommonModel *>(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 we get any error...skip this host...
if (!connection->startDiscovery()) { connect(connection->modbusTcpClient(), &QModbusTcpClient::errorOccurred, this, [=](QModbusDevice::Error error){
qCDebug(dcSunSpec()) << "Discovery: Unable to discover SunSpec data on connection" << networkDeviceInfo.address().toString() << "Continue...";; if (error != QModbusDevice::NoError) {
cleanupConnection(connection); qCDebug(dcSunSpec()) << "Discovery: Connection error on" << QString("%1:%2").arg(networkDeviceInfo.address().toString()).arg(port) << "slave ID:" << slaveId << "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();
} }
m_pendingConnectionAttempts[networkDeviceInfo.address()] = connectionQueue;
testNextConnection(networkDeviceInfo.address());
} }
void SunSpecDiscovery::cleanupConnection(SunSpecConnection *connection) void SunSpecDiscovery::cleanupConnection(SunSpecConnection *connection)
@ -154,6 +179,8 @@ void SunSpecDiscovery::cleanupConnection(SunSpecConnection *connection)
m_connections.removeAll(connection); m_connections.removeAll(connection);
connection->disconnectDevice(); connection->disconnectDevice();
connection->deleteLater(); connection->deleteLater();
testNextConnection(connection->hostAddress());
} }
void SunSpecDiscovery::finishDiscovery() void SunSpecDiscovery::finishDiscovery()

View File

@ -31,6 +31,7 @@
#ifndef SUNSPECDISCOVERY_H #ifndef SUNSPECDISCOVERY_H
#define SUNSPECDISCOVERY_H #define SUNSPECDISCOVERY_H
#include <QQueue>
#include <QObject> #include <QObject>
#include <QDateTime> #include <QDateTime>
@ -41,10 +42,11 @@ class SunSpecDiscovery : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit SunSpecDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint16 modbusAddress = 1, QObject *parent = nullptr); explicit SunSpecDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, const QList<quint16> &slaveIds, SunSpecDataPoint::ByteOrder byteOrder = SunSpecDataPoint::ByteOrderLittleEndian, QObject *parent = nullptr);
typedef struct Result { typedef struct Result {
NetworkDeviceInfo networkDeviceInfo; NetworkDeviceInfo networkDeviceInfo;
quint16 port; quint16 port;
quint16 slaveId;
QStringList modelManufacturers; QStringList modelManufacturers;
} Result; } Result;
@ -58,16 +60,18 @@ signals:
private: private:
NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr; NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr;
quint16 m_modbusAddress;
QList<quint16> m_scanPorts; QList<quint16> m_scanPorts;
QList<quint16> m_slaveIds;
SunSpecDataPoint::ByteOrder m_byteOrder;
QDateTime m_startDateTime; QDateTime m_startDateTime;
QHash<QHostAddress, QQueue<SunSpecConnection *>> m_pendingConnectionAttempts;
NetworkDeviceInfos m_verifiedNetworkDeviceInfos;
QList<SunSpecConnection *> m_connections; QList<SunSpecConnection *> m_connections;
QList<Result> m_results; QList<Result> m_results;
void testNextConnection(const QHostAddress &address);
void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo); void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo);
void cleanupConnection(SunSpecConnection *connection); void cleanupConnection(SunSpecConnection *connection);