From 0e7390555d4023e58617b610fbcd73202c1d307b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 1 Dec 2022 10:37:03 +0100 Subject: [PATCH] Update SMA speedwire discovery and improve meter behavior --- sma/integrationpluginsma.cpp | 106 +++++++------ sma/integrationpluginsma.h | 4 + sma/integrationpluginsma.json | 17 --- sma/sma.h | 2 + sma/speedwire/speedwirediscovery.cpp | 217 ++++++++++++++++++--------- sma/speedwire/speedwirediscovery.h | 18 ++- sma/speedwire/speedwireinterface.cpp | 31 +++- sma/speedwire/speedwireinterface.h | 8 +- sma/speedwire/speedwireinverter.cpp | 10 +- sma/speedwire/speedwireinverter.h | 2 +- sma/speedwire/speedwiremeter.cpp | 35 +++-- sma/speedwire/speedwiremeter.h | 4 +- 12 files changed, 283 insertions(+), 171 deletions(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 44bc212..c286a5d 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -94,8 +94,10 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info) webBoxDiscovery->startDiscovery(); } else if (info->thingClassId() == speedwireMeterThingClassId) { + + // Note: does not require the network device discovery... SpeedwireDiscovery *speedwireDiscovery = new SpeedwireDiscovery(hardwareManager()->networkDeviceDiscovery(), info); - if (!speedwireDiscovery->initialize()) { + if (!speedwireDiscovery->initialize(SpeedwireInterface::DeviceTypeMeter)) { qCWarning(dcSma()) << "Could not discovery inverter. The speedwire interface initialization failed."; info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Unable to discover the network.")); return; @@ -107,27 +109,27 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info) ThingDescriptors descriptors; foreach (const SpeedwireDiscovery::SpeedwireDiscoveryResult &result, speedwireDiscovery->discoveryResult()) { - if (result.deviceType == SpeedwireInterface::DeviceTypeMeter) { - if (result.serialNumber == 0) - continue; - ThingDescriptor descriptor(speedwireMeterThingClassId, "SMA Energy Meter", "Serial: " + QString::number(result.serialNumber) + " - " + result.address.toString()); - // We found an energy meter, let's check if we already added this one - foreach (Thing *existingThing, myThings()) { - if (existingThing->paramValue(speedwireMeterThingSerialNumberParamTypeId).toUInt() == result.serialNumber) { - descriptor.setThingId(existingThing->id()); - break; - } + if (result.deviceType != SpeedwireInterface::DeviceTypeMeter) + continue; + + if (result.serialNumber == 0) + continue; + + ThingDescriptor descriptor(speedwireMeterThingClassId, "SMA Energy Meter (" + QString::number(result.serialNumber) + ")" , result.address.toString()); + // We found an energy meter, let's check if we already added this one + foreach (Thing *existingThing, myThings()) { + if (existingThing->paramValue(speedwireMeterThingSerialNumberParamTypeId).toUInt() == result.serialNumber) { + descriptor.setThingId(existingThing->id()); + break; } - - ParamList params; - params << Param(speedwireMeterThingHostParamTypeId, result.address.toString()); - params << Param(speedwireMeterThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress()); - params << Param(speedwireMeterThingSerialNumberParamTypeId, result.serialNumber); - params << Param(speedwireMeterThingModelIdParamTypeId, result.modelId); - descriptor.setParams(params); - descriptors.append(descriptor); } + + ParamList params; + params << Param(speedwireMeterThingSerialNumberParamTypeId, result.serialNumber); + params << Param(speedwireMeterThingModelIdParamTypeId, result.modelId); + descriptor.setParams(params); + descriptors.append(descriptor); } info->addThingDescriptors(descriptors); @@ -135,9 +137,16 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info) }); speedwireDiscovery->startDiscovery(); + } else if (info->thingClassId() == speedwireInverterThingClassId) { + if (!hardwareManager()->networkDeviceDiscovery()->available()) { + qCWarning(dcSma()) << "Failed to discover network devices. The network device discovery is not available."; + info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Unable to discover devices in your network.")); + return; + } + SpeedwireDiscovery *speedwireDiscovery = new SpeedwireDiscovery(hardwareManager()->networkDeviceDiscovery(), info); - if (!speedwireDiscovery->initialize()) { + if (!speedwireDiscovery->initialize(SpeedwireInterface::DeviceTypeInverter)) { qCWarning(dcSma()) << "Could not discovery inverter. The speedwire interface initialization failed."; info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Unable to discover the network.")); return; @@ -149,27 +158,29 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info) ThingDescriptors descriptors; foreach (const SpeedwireDiscovery::SpeedwireDiscoveryResult &result, speedwireDiscovery->discoveryResult()) { - if (result.deviceType == SpeedwireInterface::DeviceTypeInverter) { - if (result.serialNumber == 0) - continue; - ThingDescriptor descriptor(speedwireInverterThingClassId, Sma::getModelName(result.modelId), "Serial: " + QString::number(result.serialNumber) + " - " + result.address.toString()); - // We found an energy meter, let's check if we already added this one - foreach (Thing *existingThing, myThings()) { - if (existingThing->paramValue(speedwireInverterThingSerialNumberParamTypeId).toUInt() == result.serialNumber) { - descriptor.setThingId(existingThing->id()); - break; - } + if (result.deviceType != SpeedwireInterface::DeviceTypeInverter) + continue; + + if (result.serialNumber == 0) + continue; + + ThingDescriptor descriptor(speedwireInverterThingClassId, "SMA inverter (" + QString::number(result.serialNumber) + ")", result.address.toString()); + // We found an inverter, let's check if we already added this one + foreach (Thing *existingThing, myThings()) { + if (existingThing->paramValue(speedwireInverterThingSerialNumberParamTypeId).toUInt() == result.serialNumber) { + descriptor.setThingId(existingThing->id()); + break; } - - ParamList params; - params << Param(speedwireInverterThingHostParamTypeId, result.address.toString()); - params << Param(speedwireInverterThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress()); - params << Param(speedwireInverterThingSerialNumberParamTypeId, result.serialNumber); - params << Param(speedwireInverterThingModelIdParamTypeId, result.modelId); - descriptor.setParams(params); - descriptors.append(descriptor); } + + ParamList params; + params << Param(speedwireInverterThingHostParamTypeId, result.address.toString()); + params << Param(speedwireInverterThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress()); + params << Param(speedwireInverterThingSerialNumberParamTypeId, result.serialNumber); + params << Param(speedwireInverterThingModelIdParamTypeId, result.modelId); + descriptor.setParams(params); + descriptors.append(descriptor); } info->addThingDescriptors(descriptors); @@ -177,6 +188,7 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info) }); speedwireDiscovery->startDiscovery(); + } else if (info->thingClassId() == modbusInverterThingClassId) { if (!hardwareManager()->networkDeviceDiscovery()->available()) { qCWarning(dcSma()) << "The network discovery is not available on this platform."; @@ -289,16 +301,20 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) } else if (thing->thingClassId() == speedwireMeterThingClassId) { - QHostAddress address = QHostAddress(thing->paramValue(speedwireMeterThingHostParamTypeId).toString()); + // Create the multicast interface if not created already. + if (!m_multicastInterface) + m_multicastInterface = new SpeedwireInterface(true, this); + quint32 serialNumber = static_cast(thing->paramValue(speedwireMeterThingSerialNumberParamTypeId).toUInt()); quint16 modelId = static_cast(thing->paramValue(speedwireMeterThingModelIdParamTypeId).toUInt()); - if (m_speedwireMeters.contains(thing)) { + // Handle reconfigure + if (m_speedwireMeters.contains(thing)) m_speedwireMeters.take(thing)->deleteLater(); - } - SpeedwireMeter *meter = new SpeedwireMeter(address, modelId, serialNumber, this); + SpeedwireMeter *meter = new SpeedwireMeter(m_multicastInterface, modelId, serialNumber, this); if (!meter->initialize()) { + meter->deleteLater(); qCWarning(dcSma()) << "Setup failed. Could not initialize meter interface."; info->finish(Thing::ThingErrorHardwareFailure); return; @@ -522,6 +538,12 @@ void IntegrationPluginSma::thingRemoved(Thing *thing) hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); } + if (myThings().filterByThingClassId(speedwireMeterThingClassId).isEmpty() && m_multicastInterface) { + // Delete shared multicast socket... + m_multicastInterface->deleteLater(); + m_multicastInterface = nullptr; + } + if (myThings().isEmpty()) { qCDebug(dcSma()) << "Stopping timer"; hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer); diff --git a/sma/integrationpluginsma.h b/sma/integrationpluginsma.h index 50609bf..d282a6d 100644 --- a/sma/integrationpluginsma.h +++ b/sma/integrationpluginsma.h @@ -40,6 +40,7 @@ #include "sunnywebbox/sunnywebbox.h" #include "speedwire/speedwiremeter.h" #include "speedwire/speedwireinverter.h" +#include "speedwire/speedwireinterface.h" #include "smainvertermodbustcpconnection.h" @@ -79,6 +80,9 @@ private: QHash m_speedwireInverters; QHash m_modbusInverters; + // Shared interface accross meters + SpeedwireInterface *m_multicastInterface = nullptr; + // Sma modbus data validation bool isModbusValueValid(quint32 value); bool isModbusValueValid(qint32 value); diff --git a/sma/integrationpluginsma.json b/sma/integrationpluginsma.json index e79f74f..2527ce9 100644 --- a/sma/integrationpluginsma.json +++ b/sma/integrationpluginsma.json @@ -90,23 +90,6 @@ "createMethods": ["discovery", "user"], "interfaces": [ "energymeter" ], "paramTypes": [ - { - "id": "d90193e6-a996-4e49-bf6d-564d596d7e74", - "name": "host", - "displayName": "Host address", - "type": "QString", - "inputType": "IPv4Address", - "defaultValue": "192.168.0.168" - }, - { - "id": "2780eab7-1f1c-4cc7-a789-a8790329ca9e", - "name": "macAddress", - "displayName": "MAC address", - "type": "QString", - "inputType": "TextLine", - "readOnly": true, - "defaultValue": "" - }, { "id": "7c81a0c5-9bc6-43bb-a01a-4de5fe656bba", "name": "serialNumber", diff --git a/sma/sma.h b/sma/sma.h index fef1fcf..f8a820c 100644 --- a/sma/sma.h +++ b/sma/sma.h @@ -93,6 +93,8 @@ public: case 5: revisionCharacter = 'S'; break; + default: + revisionCharacter = QChar(revision); } return QString("%1.%2.%3-%4").arg(major).arg(minor).arg(build).arg(revisionCharacter); diff --git a/sma/speedwire/speedwirediscovery.cpp b/sma/speedwire/speedwirediscovery.cpp index 83547f1..06d399c 100644 --- a/sma/speedwire/speedwirediscovery.cpp +++ b/sma/speedwire/speedwirediscovery.cpp @@ -39,33 +39,17 @@ SpeedwireDiscovery::SpeedwireDiscovery(NetworkDeviceDiscovery *networkDeviceDisc { // More details: https://github.com/RalfOGit/libspeedwire/ + // Request: 534d4100000402a00000000100260010 606509a0 ffffffffffff0000 7d0052be283a0000 000000000180 00020000 00000000 00000000 00000000 => command = 0x00000200, first = 0x00000000; last = 0x00000000; trailer = 0x00000000 + // Response 534d4100000402a000000001004e0010 606513a0 7d0052be283a00c0 7a01842a71b30000 000000000180 01020000 00000000 00000000 00030000 00ff0000 00000000 01007a01 842a71b3 00000a00 0c000000 00000000 00000000 01010000 00000000 - - // // Request: 534d4100000402a00000000100260010 606509a0 ffffffffffff0000 7d0052be283a0000 000000000180 00020000 00000000 00000000 00000000 => command = 0x00000200, first = 0x00000000; last = 0x00000000; trailer = 0x00000000 - // // Response 534d4100000402a000000001004e0010 606513a0 7d0052be283a00c0 7a01842a71b30000 000000000180 01020000 00000000 00000000 00030000 00ff0000 00000000 01007a01 842a71b3 00000a00 0c000000 00000000 00000000 01010000 00000000 - - // qCDebug(dcSma()) << "SpeedwireDiscovery: Create speed wire interface for multicast" << m_multicastAddress.toString() << "on port" << m_port; - // QByteArray exampleData = QByteArray::fromHex("534d4100000402a000000001024400106069010e714369aee618a41600010400000000000001080000000021391229100002040000004415000208000000001575a137d800030400000000000003080000000003debed0e800040400000017c6000408000000001008c2070000090400000000000009080000000027c77bed20000a04000000481d000a08000000001722823410000d0400000003b00015040000000000001508000000000d1e1e0e3000160400000015120016080000000006c5a2d8b800170400000000000017080000000001bd6f680000180400000007990018080000000004def712b8001d040000000000001d08000000000eeefaafd0001e040000001666001e0800000000074b38bf88001f040000000a300020040000037bcb00210400000003ad0029040000000000002908000000000a9b1afec8002a040000001a81002a08000000000803e62b88002b040000000000002b080000000001511459b8002c0400000006d5002c0800000000052c8455b80031040000000000003108000000000cf83b37100032040000001b5f0032080000000008a6e257f80033040000000c3f003404000003747900350400000003c8003d040000000000003d08000000000a53d0ba08003e040000001482003e080000000007800fd188003f040000000000003f080000000001185820c8004004000000095800400800000000064563b1900045040000000000004508000000000d26d3eae0004604000000168900460800000000082b4fc5a80047040000000a440048040000037ed1004904000000038e900000000102085200000000"); - // processDatagram(QHostAddress("127.0.0.1"), m_port, exampleData); - - m_multicastSocket = new QUdpSocket(this); - connect(m_multicastSocket, &QUdpSocket::readyRead, this, &SpeedwireDiscovery::readPendingDatagramsMulticast); - connect(m_multicastSocket, &QUdpSocket::stateChanged, this, &SpeedwireDiscovery::onSocketStateChanged); - connect(m_multicastSocket, SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(onSocketError(QAbstractSocket::SocketError))); - - m_unicastSocket = new QUdpSocket(this); - connect(m_unicastSocket, &QUdpSocket::readyRead, this, &SpeedwireDiscovery::readPendingDatagramsUnicast); - connect(m_unicastSocket, &QUdpSocket::stateChanged, this, &SpeedwireDiscovery::onSocketStateChanged); - connect(m_unicastSocket, SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(onSocketError(QAbstractSocket::SocketError))); - - m_discoveryTimer.setInterval(1000); - m_discoveryTimer.setSingleShot(false); - connect(&m_discoveryTimer, &QTimer::timeout, this, &SpeedwireDiscovery::sendDiscoveryRequest); + m_multicastSearchRequestTimer.setInterval(1000); + m_multicastSearchRequestTimer.setSingleShot(false); + connect(&m_multicastSearchRequestTimer, &QTimer::timeout, this, &SpeedwireDiscovery::sendDiscoveryRequest); } SpeedwireDiscovery::~SpeedwireDiscovery() { - if (m_initialized) { + if (m_initialized && m_multicastSocket) { if (!m_multicastSocket->leaveMulticastGroup(m_multicastAddress)) { qCWarning(dcSma()) << "SpeedwireDiscovery: Failed to leave multicast group" << m_multicastAddress.toString(); } @@ -74,30 +58,24 @@ SpeedwireDiscovery::~SpeedwireDiscovery() } } -bool SpeedwireDiscovery::initialize() +bool SpeedwireDiscovery::initialize(SpeedwireInterface::DeviceType deviceType) { - m_multicastSocket->close(); - m_initialized = false; - - // Setup multicast socket - if (!m_multicastSocket->bind(QHostAddress::AnyIPv4, m_port, QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint)) { - qCWarning(dcSma()) << "SpeedwireDiscovery: Initialization failed. Could not bind multicast socket to port" << m_port << m_multicastSocket->errorString(); - return false; + m_deviceType = deviceType; + switch(deviceType) { + case SpeedwireInterface::DeviceTypeMeter: + m_initialized = setupMulticastSocket(); + break; + case SpeedwireInterface::DeviceTypeInverter: + m_initialized = setupUnicastSocket(); + break; + default: + m_initialized = setupMulticastSocket() && setupUnicastSocket(); + break; } - if (!m_multicastSocket->joinMulticastGroup(m_multicastAddress)) { - qCWarning(dcSma()) << "SpeedwireDiscovery: Initialization failed. Could not join multicast group" << m_multicastAddress.toString() << m_multicastSocket->errorString(); - return false; - } + if (m_initialized) + qCDebug(dcSma()) << "SpeedwireDiscovery: Interfaces initialized successfully."; - // Setup unicast socket - if (!m_unicastSocket->bind(QHostAddress::AnyIPv4, m_port, QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint)) { - qCWarning(dcSma()) << "SpeedwireDiscovery: Initialization failed. Could not bind to port" << m_port << m_multicastSocket->errorString(); - return false; - } - - qCDebug(dcSma()) << "SpeedwireDiscovery: Interface initialized successfully."; - m_initialized = true; return m_initialized; } @@ -108,10 +86,7 @@ bool SpeedwireDiscovery::initialized() const bool SpeedwireDiscovery::startDiscovery() { - // 1. Discover all network devices - // 2. Send upd multicast and unicast messages to verify if it is a SMA speedwire device - - if (m_discoveryRunning) + if (discoveryRunning()) return true; if (!m_initialized) { @@ -119,31 +94,31 @@ bool SpeedwireDiscovery::startDiscovery() return false; } - // CLean up + // Start clean m_results.clear(); m_networkDeviceInfos.clear(); + m_multicastRunning = false; + m_unicastRunning = false; - qCDebug(dcSma()) << "SpeedwireDiscovery: Start discovering network..."; - NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover(); - connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, discoveryReply, &NetworkDeviceDiscoveryReply::deleteLater); - connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){ - qCDebug(dcSma()) << "Discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "devices"; - m_networkDeviceInfos = discoveryReply->networkDeviceInfos(); - - foreach (const NetworkDeviceInfo &networkDeviceInfo, discoveryReply->networkDeviceInfos()) { - // 2. Send unicast to all results and start requesting on multicast address - sendUnicastDiscoveryRequest(networkDeviceInfo.address()); - } - + switch(m_deviceType) { + case SpeedwireInterface::DeviceTypeMeter: startMulticastDiscovery(); - }); + break; + case SpeedwireInterface::DeviceTypeInverter: + startUnicastDiscovery(); + break; + default: + startUnicastDiscovery(); + startMulticastDiscovery(); + break; + } return true; } bool SpeedwireDiscovery::discoveryRunning() const { - return m_discoveryRunning; + return m_unicastRunning || m_multicastRunning; } QList SpeedwireDiscovery::discoveryResult() const @@ -151,15 +126,92 @@ QList SpeedwireDiscovery::discover return m_results.values(); } +bool SpeedwireDiscovery::setupMulticastSocket() +{ + if (m_multicastSocket) + return true; + + m_multicastSocket = new QUdpSocket(this); + connect(m_multicastSocket, &QUdpSocket::readyRead, this, &SpeedwireDiscovery::readPendingDatagramsMulticast); + connect(m_multicastSocket, &QUdpSocket::stateChanged, this, &SpeedwireDiscovery::onSocketStateChanged); + connect(m_multicastSocket, SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(onSocketError(QAbstractSocket::SocketError))); + + // Setup multicast socket + if (!m_multicastSocket->bind(QHostAddress::AnyIPv4, m_port, QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint)) { + qCWarning(dcSma()) << "SpeedwireDiscovery: Initialization failed. Could not bind multicast socket to port" << m_port << m_multicastSocket->errorString(); + m_multicastSocket->deleteLater(); + m_multicastSocket = nullptr; + return false; + } + + if (!m_multicastSocket->joinMulticastGroup(m_multicastAddress)) { + qCWarning(dcSma()) << "SpeedwireDiscovery: Initialization failed. Could not join multicast group" << m_multicastAddress.toString() << m_multicastSocket->errorString(); + m_multicastSocket->deleteLater(); + m_multicastSocket = nullptr; + return false; + } + + return true; +} + void SpeedwireDiscovery::startMulticastDiscovery() { + qCDebug(dcSma()) << "SpeedwireDiscovery: Start multicast discovery..."; + m_multicastRunning = true; + // Start sending multicast messages sendDiscoveryRequest(); - m_discoveryRunning = true; - QTimer::singleShot(5000, this, &SpeedwireDiscovery::onDiscoveryProcessFinished); + // Give 5 seconds time, on one of the requests sent in this period the meter would have responded... + QTimer::singleShot(5000, this, [this](){ + m_multicastRunning = false; + evaluateDiscoveryFinished(); + }); - m_discoveryTimer.start(); + m_multicastSearchRequestTimer.start(); +} + +bool SpeedwireDiscovery::setupUnicastSocket() +{ + if (m_unicastSocket) + return true; + + m_unicastSocket = new QUdpSocket(this); + connect(m_unicastSocket, &QUdpSocket::readyRead, this, &SpeedwireDiscovery::readPendingDatagramsUnicast); + connect(m_unicastSocket, &QUdpSocket::stateChanged, this, &SpeedwireDiscovery::onSocketStateChanged); + connect(m_unicastSocket, SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(onSocketError(QAbstractSocket::SocketError))); + + // Setup unicast socket + if (!m_unicastSocket->bind(QHostAddress::AnyIPv4, m_port, QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint)) { + qCWarning(dcSma()) << "SpeedwireDiscovery: Initialization failed. Could not bind to port" << m_port << m_multicastSocket->errorString(); + m_unicastSocket->deleteLater(); + m_unicastSocket = nullptr; + return false; + } + + return true; +} + +void SpeedwireDiscovery::startUnicastDiscovery() +{ + qCDebug(dcSma()) << "SpeedwireDiscovery: Start discovering network..."; + m_unicastRunning = true; + + NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover(); + connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, discoveryReply, &NetworkDeviceDiscoveryReply::deleteLater); + connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, [this](const NetworkDeviceInfo &networkDeviceInfo){ + m_networkDeviceInfos.append(networkDeviceInfo); + sendUnicastDiscoveryRequest(networkDeviceInfo.address()); + }); + + connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){ + qCDebug(dcSma()) << "Discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "network devices for unicast requests."; + // Wait some extra second in otder to give the last hosts joined some time to respond. + QTimer::singleShot(3000, this, [this](){ + m_unicastRunning = false; + evaluateDiscoveryFinished(); + }); + }); } void SpeedwireDiscovery::sendUnicastDiscoveryRequest(const QHostAddress &targetHostAddress) @@ -206,7 +258,6 @@ void SpeedwireDiscovery::readPendingDatagramsUnicast() } } - void SpeedwireDiscovery::onSocketError(QAbstractSocket::SocketError error) { qCDebug(dcSma()) << "SpeedwireDiscovery: Socket error" << error; @@ -252,8 +303,7 @@ void SpeedwireDiscovery::processDatagram(const QHostAddress &senderAddress, quin if (!m_results.contains(senderAddress)) { qCDebug(dcSma()) << "SpeedwireDiscovery: --> Found SMA device on" << senderAddress.toString(); if (!m_networkDeviceInfos.hasHostAddress(senderAddress)) { - qCWarning(dcSma()) << "SpeedwireDiscovery: Found SMA using UDP discovery but the host is not in the network discovery result list. Not adding to results" << senderAddress.toString(); - return; + qCDebug(dcSma()) << "SpeedwireDiscovery: Found SMA using UDP discovery but the host is not in the network discovery result list. Not adding to results" << senderAddress.toString(); } SpeedwireDiscoveryResult result; @@ -327,22 +377,43 @@ void SpeedwireDiscovery::sendDiscoveryRequest() qCDebug(dcSma()) << "SpeedwireDiscovery: Sent successfully the discovery request to multicast address" << m_multicastAddress.toString(); } -void SpeedwireDiscovery::onDiscoveryProcessFinished() +void SpeedwireDiscovery::evaluateDiscoveryFinished() +{ + if (!m_multicastRunning && !m_unicastRunning) { + finishDiscovery(); + } +} + +void SpeedwireDiscovery::finishDiscovery() { qCDebug(dcSma()) << "SpeedwireDiscovery: Discovey finished. Found" << m_results.count() << "SMA devices in the network"; - m_discoveryTimer.stop(); - m_discoveryRunning = false; + m_multicastSearchRequestTimer.stop(); + + if (m_multicastSocket) { + m_multicastSocket->close(); + m_multicastSocket->deleteLater(); + m_multicastSocket = nullptr; + } + + if (m_unicastSocket) { + m_unicastSocket->close(); + m_unicastSocket->deleteLater(); + m_unicastSocket = nullptr; + } foreach (const SpeedwireDiscoveryResult &result, m_results) { qCDebug(dcSma()) << "SpeedwireDiscovery: ============================================"; qCDebug(dcSma()) << "SpeedwireDiscovery: Device type:" << result.deviceType; qCDebug(dcSma()) << "SpeedwireDiscovery: Address:" << result.address.toString(); - qCDebug(dcSma()) << "SpeedwireDiscovery: Hostname:" << result.networkDeviceInfo.hostName(); - qCDebug(dcSma()) << "SpeedwireDiscovery: MAC:" << result.networkDeviceInfo.macAddress(); - qCDebug(dcSma()) << "SpeedwireDiscovery: MAC manufacturer:" << result.networkDeviceInfo.macAddressManufacturer(); + if (result.networkDeviceInfo.isValid()) { + qCDebug(dcSma()) << "SpeedwireDiscovery: Hostname:" << result.networkDeviceInfo.hostName(); + qCDebug(dcSma()) << "SpeedwireDiscovery: MAC:" << result.networkDeviceInfo.macAddress(); + qCDebug(dcSma()) << "SpeedwireDiscovery: MAC manufacturer:" << result.networkDeviceInfo.macAddressManufacturer(); + } qCDebug(dcSma()) << "SpeedwireDiscovery: Model ID:" << result.modelId; qCDebug(dcSma()) << "SpeedwireDiscovery: Serial number:" << result.serialNumber; } emit discoveryFinished(); } + diff --git a/sma/speedwire/speedwirediscovery.h b/sma/speedwire/speedwirediscovery.h index 0c181dd..b71072e 100644 --- a/sma/speedwire/speedwirediscovery.h +++ b/sma/speedwire/speedwirediscovery.h @@ -55,7 +55,7 @@ public: explicit SpeedwireDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent = nullptr); ~SpeedwireDiscovery(); - bool initialize(); + bool initialize(SpeedwireInterface::DeviceType deviceType = SpeedwireInterface::DeviceTypeUnknown); bool initialized() const; bool startDiscovery(); @@ -68,6 +68,7 @@ signals: private: NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr; + SpeedwireInterface::DeviceType m_deviceType = SpeedwireInterface::DeviceTypeUnknown; QUdpSocket *m_multicastSocket = nullptr; QUdpSocket *m_unicastSocket = nullptr; QHostAddress m_multicastAddress = Speedwire::multicastAddress(); @@ -75,12 +76,19 @@ private: bool m_initialized = false; // Discovery - QTimer m_discoveryTimer; - bool m_discoveryRunning = false; + QTimer m_multicastSearchRequestTimer; NetworkDeviceInfos m_networkDeviceInfos; QHash m_results; + bool m_multicastRunning = false; + bool m_unicastRunning = false; + + bool setupMulticastSocket(); void startMulticastDiscovery(); + + bool setupUnicastSocket(); + void startUnicastDiscovery(); + ; void sendUnicastDiscoveryRequest(const QHostAddress &targetHostAddress); private slots: @@ -93,7 +101,9 @@ private slots: void sendDiscoveryRequest(); - void onDiscoveryProcessFinished(); + void evaluateDiscoveryFinished(); + + void finishDiscovery(); }; diff --git a/sma/speedwire/speedwireinterface.cpp b/sma/speedwire/speedwireinterface.cpp index a5fa1d7..e6a5deb 100644 --- a/sma/speedwire/speedwireinterface.cpp +++ b/sma/speedwire/speedwireinterface.cpp @@ -31,13 +31,11 @@ #include "speedwireinterface.h" #include "extern-plugininfo.h" -SpeedwireInterface::SpeedwireInterface(const QHostAddress &address, bool multicast, QObject *parent) : +SpeedwireInterface::SpeedwireInterface(bool multicast, QObject *parent) : QObject(parent), - m_address(address), m_multicast(multicast) { - qCDebug(dcSma()) << "SpeedwireInterface: Create interface for" << address.toString() << (multicast ? "multicast" : "unicast"); m_socket = new QUdpSocket(this); connect(m_socket, &QUdpSocket::readyRead, this, &SpeedwireInterface::readPendingDatagrams); connect(m_socket, &QUdpSocket::stateChanged, this, &SpeedwireInterface::onSocketStateChanged); @@ -49,8 +47,18 @@ SpeedwireInterface::~SpeedwireInterface() deinitialize(); } -bool SpeedwireInterface::initialize() +bool SpeedwireInterface::initialize(const QHostAddress &address) { + if (m_initialized && !m_multicast) { + qCWarning(dcSma()) << "Try to initialize an already initialized speed wire interface. Please deinitialize() before calling this method again."; + return false; + } + + // If already initialized and multicast, nothing could habe changed...done here + if (m_initialized && m_multicast) + return true; + + if (!m_socket->bind(QHostAddress::AnyIPv4, m_port, QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint)) { qCWarning(dcSma()) << "SpeedwireInterface: Initialization failed. Could not bind to port" << m_port; return false; @@ -61,7 +69,8 @@ bool SpeedwireInterface::initialize() return false; } - qCDebug(dcSma()) << "SpeedwireInterface: Interface initialized successfully."; + qCDebug(dcSma()) << "SpeedwireInterface: Interface initialized successfully for" << address.toString() << (m_multicast ? "multicast" : "unicast"); + m_address = address; m_initialized = true; return m_initialized; } @@ -75,11 +84,17 @@ void SpeedwireInterface::deinitialize() } } + m_address = QHostAddress(); m_socket->close(); m_initialized = false; } } +bool SpeedwireInterface::multicast() const +{ + return m_multicast; +} + bool SpeedwireInterface::initialized() const { return m_initialized; @@ -113,13 +128,13 @@ void SpeedwireInterface::readPendingDatagrams() datagram.resize(m_socket->pendingDatagramSize()); m_socket->readDatagram(datagram.data(), datagram.size(), &senderAddress, &senderPort); - // Process only data coming from our target address - if (senderAddress != m_address) + // Process only data coming from our target address if there is any + if (!m_address.isNull() && senderAddress != m_address) continue; qCDebug(dcSma()) << "SpeedwireInterface: Received data from" << QString("%1:%2").arg(senderAddress.toString()).arg(senderPort); //qCDebug(dcSma()) << "SpeedwireInterface: " << datagram.toHex(); - emit dataReceived(datagram); + emit dataReceived(senderAddress, senderPort, datagram); } } diff --git a/sma/speedwire/speedwireinterface.h b/sma/speedwire/speedwireinterface.h index fa24287..4a1efe1 100644 --- a/sma/speedwire/speedwireinterface.h +++ b/sma/speedwire/speedwireinterface.h @@ -48,12 +48,14 @@ public: }; Q_ENUM(DeviceType) - explicit SpeedwireInterface(const QHostAddress &address, bool multicast, QObject *parent = nullptr); + explicit SpeedwireInterface(bool multicast, QObject *parent = nullptr); ~SpeedwireInterface(); - bool initialize(); + bool initialize(const QHostAddress &address = QHostAddress()); void deinitialize(); + bool multicast() const; + bool initialized() const; quint16 sourceModelId() const; @@ -63,7 +65,7 @@ public slots: void sendData(const QByteArray &data); signals: - void dataReceived(const QByteArray &data); + void dataReceived(const QHostAddress &senderAddress, quint16 senderPort, const QByteArray &data); private: QUdpSocket *m_socket = nullptr; diff --git a/sma/speedwire/speedwireinverter.cpp b/sma/speedwire/speedwireinverter.cpp index 39329c9..50ac67b 100644 --- a/sma/speedwire/speedwireinverter.cpp +++ b/sma/speedwire/speedwireinverter.cpp @@ -40,13 +40,13 @@ SpeedwireInverter::SpeedwireInverter(const QHostAddress &address, quint16 modelI m_serialNumber(serialNumber) { qCDebug(dcSma()) << "Inverter: setup interface on" << m_address.toString(); - m_interface = new SpeedwireInterface(m_address, false, this); + m_interface = new SpeedwireInterface(false, this); connect(m_interface, &SpeedwireInterface::dataReceived, this, &SpeedwireInverter::processData); } bool SpeedwireInverter::initialize() { - return m_interface->initialize(); + return m_interface->initialize(m_address); } bool SpeedwireInverter::initialized() const @@ -976,8 +976,12 @@ void SpeedwireInverter::setReachable(bool reachable) emit reachableChanged(m_reachable); } -void SpeedwireInverter::processData(const QByteArray &data) +void SpeedwireInverter::processData(const QHostAddress &senderAddress, quint16 senderPort, const QByteArray &data) { + // Note: the interface is already filtering out data from other hosts m_address + Q_UNUSED(senderAddress) + Q_UNUSED(senderPort) + if (data.size() < 18) { qCDebug(dcSma()) << "Inverter: The received datagram is to short to be a SMA speedwire message. Ignoring data..."; return; diff --git a/sma/speedwire/speedwireinverter.h b/sma/speedwire/speedwireinverter.h index dd814d6..57888e7 100644 --- a/sma/speedwire/speedwireinverter.h +++ b/sma/speedwire/speedwireinverter.h @@ -193,7 +193,7 @@ private: void setReachable(bool reachable); private slots: - void processData(const QByteArray &data); + void processData(const QHostAddress &senderAddress, quint16 senderPort, const QByteArray &data); void onReplyTimeout(); void onReplyFinished(); diff --git a/sma/speedwire/speedwiremeter.cpp b/sma/speedwire/speedwiremeter.cpp index 5178b56..f90354b 100644 --- a/sma/speedwire/speedwiremeter.cpp +++ b/sma/speedwire/speedwiremeter.cpp @@ -33,13 +33,12 @@ #include "sma.h" -SpeedwireMeter::SpeedwireMeter(const QHostAddress &address, quint16 modelId, quint32 serialNumber, QObject *parent) : +SpeedwireMeter::SpeedwireMeter(SpeedwireInterface *multicastInterface, quint16 modelId, quint32 serialNumber, QObject *parent) : QObject(parent), - m_address(address), + m_interface(multicastInterface), m_modelId(modelId), m_serialNumber(serialNumber) { - m_interface = new SpeedwireInterface(m_address, true, this); connect(m_interface, &SpeedwireInterface::dataReceived, this, &SpeedwireMeter::processData); // Reachable timestamp @@ -165,10 +164,9 @@ QString SpeedwireMeter::softwareVersion() const void SpeedwireMeter::evaluateReachable() { // Note: the meter sends every second the data on the multicast - qint64 currentTimestamp = QDateTime::currentDateTime().toMSecsSinceEpoch() / 1000; // If the meter has not sent data within the last 5 seconds it seems not to be reachable bool reachable = false; - if (currentTimestamp - m_lastSeenTimestamp < 10) { + if (QDateTime::currentDateTime().toMSecsSinceEpoch() - m_lastSeenTimestamp < 5000) { reachable = true; } @@ -188,9 +186,8 @@ void SpeedwireMeter::evaluateReachable() } } -void SpeedwireMeter::processData(const QByteArray &data) +void SpeedwireMeter::processData(const QHostAddress &senderAddress, quint16 senderPort, const QByteArray &data) { - qCDebug(dcSma()) << "Meter: data received" << data.toHex(); QDataStream stream(data); stream.setByteOrder(QDataStream::BigEndian); @@ -200,27 +197,30 @@ void SpeedwireMeter::processData(const QByteArray &data) return; } - if (header.protocolId != Speedwire::ProtocolIdMeter) { - qCDebug(dcSma()) << "Meter: received header protocol which is not from the meter protocol. Ignoring data..."; + if (header.protocolId != Speedwire::ProtocolIdMeter) return; - } quint16 modelId; quint32 serialNumber; stream >> modelId >> serialNumber; - if (m_modelId != modelId && serialNumber != m_serialNumber) { - qCDebug(dcSma()) << "Meter: received meter data from an other meter. Ignoring data..."; - } - qCDebug(dcSma()) << "Meter: Model ID:" << modelId; - qCDebug(dcSma()) << "Meter: Serial number:" << serialNumber; + // Make sure this payload belongs to us + if (m_modelId != modelId || serialNumber != m_serialNumber) + return; + + //qCDebug(dcSma()) << "Meter: data received" << data.toHex(); + qCDebug(dcSma()).noquote() << "Meter: Measurements received from" << QString("%1:%2").arg(senderAddress.toString()).arg(senderPort) << "Serial number:" << serialNumber << "Model ID:" << modelId; + + // Make sure the rate is at max 1Hz, some meters send much more data, which creates an uneccessary load + if (QDateTime::currentDateTime().toMSecsSinceEpoch() - m_lastSeenTimestamp < 1000) + return; // Parse the packet data // Timestamp e618a416 qCDebug(dcSma()) << "Meter: ======================= Meter measurements"; quint32 timestamp; stream >> timestamp; - qCDebug(dcSma()) << "Meter: Timestamp:" << timestamp; + qCDebug(dcSma()) << "Meter: Timestamp:" << timestamp << QDateTime::fromMSecsSinceEpoch(timestamp * 1000); // Obis data //00 01 04 00 00000000 00 01 08 00 0000002139122910 00 02 04 00 00004415 00 02 08 00 0000001575a137d8 00 03 04 00 00000000 00 03 08 00 00000003debed0e8 00040400000017c6000408000000001008c2070000090400000000000009080000000027c77bed20000a04000000481d000a08000000001722823410000d0400000003b00015040000000000001508000000000d1e1e0e3000160400000015120016080000000006c5a2d8b800170400000000000017080000000001bd6f680000180400000007990018080000000004def712b8001d040000000000001d08000000000eeefaafd0001e040000001666001e0800000000074b38bf88001f040000000a300020040000037bcb00210400000003ad0029040000000000002908000000000a9b1afec8002a040000001a81002a08000000000803e62b88002b040000000000002b080000000001511459b8002c0400000006d5002c0800000000052c8455b80031040000000000003108000000000cf83b37100032040000001b5f0032080000000008a6e257f80033040000000c3f003404000003747900350400000003c8003d040000000000003d08000000000a53d0ba08003e040000001482003e080000000007800fd188003f040000000000003f080000000001185820c8004004000000095800400800000000064563b1900045040000000000004508000000000d26d3eae0004604000000168900460800000000082b4fc5a80047040000000a440048040000037ed1004904000000038e90000000 01020852 00000000 @@ -323,7 +323,6 @@ void SpeedwireMeter::processData(const QByteArray &data) quint32 versionData; stream >> versionData; m_softwareVersion = Sma::buildSoftwareVersionString(versionData); - qCDebug(dcSma()) << "Meter: Software version" << m_softwareVersion; } else if (measurementChannel == 0 && measurementIndex == 0 && measurmentType == 0 && measurmentTariff == 0) { // 00 00 00 00 @@ -332,7 +331,7 @@ void SpeedwireMeter::processData(const QByteArray &data) } // Save the current timestamp for reachable evaluation - m_lastSeenTimestamp = QDateTime::currentDateTime().toMSecsSinceEpoch() / 1000; + m_lastSeenTimestamp = QDateTime::currentDateTime().toMSecsSinceEpoch(); evaluateReachable(); emit valuesUpdated(); diff --git a/sma/speedwire/speedwiremeter.h b/sma/speedwire/speedwiremeter.h index 63989c3..e689c02 100644 --- a/sma/speedwire/speedwiremeter.h +++ b/sma/speedwire/speedwiremeter.h @@ -41,7 +41,7 @@ class SpeedwireMeter : public QObject { Q_OBJECT public: - explicit SpeedwireMeter(const QHostAddress &address, quint16 modelId, quint32 serialNumber, QObject *parent = nullptr); + explicit SpeedwireMeter(SpeedwireInterface *multicastInterface, quint16 modelId, quint32 serialNumber, QObject *parent = nullptr); bool initialize(); bool initialized() const; @@ -118,7 +118,7 @@ private: private slots: void evaluateReachable(); - void processData(const QByteArray &data); + void processData(const QHostAddress &senderAddress, quint16 senderPort, const QByteArray &data); };