Update SMA speedwire discovery and improve meter behavior

pull/99/head
Simon Stürz 2022-12-01 10:37:03 +01:00
parent a60c361df7
commit 0e7390555d
12 changed files with 283 additions and 171 deletions

View File

@ -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<quint32>(thing->paramValue(speedwireMeterThingSerialNumberParamTypeId).toUInt());
quint16 modelId = static_cast<quint16>(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);

View File

@ -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<Thing *, SpeedwireInverter *> m_speedwireInverters;
QHash<Thing *, SmaInverterModbusTcpConnection *> m_modbusInverters;
// Shared interface accross meters
SpeedwireInterface *m_multicastInterface = nullptr;
// Sma modbus data validation
bool isModbusValueValid(quint32 value);
bool isModbusValueValid(qint32 value);

View File

@ -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",

View File

@ -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);

View File

@ -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::SpeedwireDiscoveryResult> SpeedwireDiscovery::discoveryResult() const
@ -151,15 +126,92 @@ QList<SpeedwireDiscovery::SpeedwireDiscoveryResult> 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();
}

View File

@ -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<QHostAddress, SpeedwireDiscoveryResult> 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();
};

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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();

View File

@ -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);
};