From cd9c048ebda02fe99524d4a7011be06de04011b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Fri, 3 Dec 2021 14:16:21 +0100 Subject: [PATCH] Add speedwire test interface --- sma/integrationpluginsma.cpp | 17 +++++- sma/integrationpluginsma.h | 2 +- sma/integrationpluginsma.json | 57 ++++++++++++++++- sma/sma.pro | 2 + sma/speedwireinterface.cpp | 111 ++++++++++++++++++++++++++++++++++ sma/speedwireinterface.h | 37 ++++++++++++ 6 files changed, 222 insertions(+), 4 deletions(-) create mode 100644 sma/speedwireinterface.cpp create mode 100644 sma/speedwireinterface.h diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 6419d7f7..0230ec27 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -84,8 +84,23 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info) info->addThingDescriptors(descriptors); info->finish(Thing::ThingErrorNoError); }); - } + } else if (info->thingClassId() == speedwireInverterThingClassId) { + SpeedwireInterface *speedwireDiscovery = new SpeedwireInterface(info); + if (!speedwireDiscovery->initialize()) { + qCWarning(dcSma()) << "Could not discovery inverter. The speedwire interface initialization failed."; + info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Unable to discover the network.")); + return; + } + connect(speedwireDiscovery, &SpeedwireInterface::discoveryFinished, this, [=](){ + qCDebug(dcSma()) << "Speed wire discovery finished."; + + speedwireDiscovery->deleteLater(); + info->finish(Thing::ThingErrorNoError); + }); + + speedwireDiscovery->startDiscovery(); + } } void IntegrationPluginSma::setupThing(ThingSetupInfo *info) diff --git a/sma/integrationpluginsma.h b/sma/integrationpluginsma.h index 3c20919f..e2c77fd0 100644 --- a/sma/integrationpluginsma.h +++ b/sma/integrationpluginsma.h @@ -35,7 +35,7 @@ #include "plugintimer.h" #include "sunnywebbox.h" -#include +#include "speedwireinterface.h" class IntegrationPluginSma: public IntegrationPlugin { Q_OBJECT diff --git a/sma/integrationpluginsma.json b/sma/integrationpluginsma.json index 5953b4f4..de0a763d 100644 --- a/sma/integrationpluginsma.json +++ b/sma/integrationpluginsma.json @@ -12,8 +12,8 @@ "id": "49304127-ce9b-45dd-8511-05030a4ac003", "name": "sunnyWebBox", "displayName": "Sunny WebBox", - "createMethods": ["user", "discovery"], - "interfaces": ["smartmeterproducer"], + "createMethods": ["discovery", "user"], + "interfaces": ["solarinverter"], "paramTypes": [ { "id": "864d4162-e3ce-48b8-b8ac-c1b971b52d42", @@ -85,6 +85,59 @@ "defaultValue": "None" } ] + }, + { + "id": "0c5097af-e136-4430-9fb4-0ccbb30c3e1c", + "name": "speedwireInverter", + "displayName": "SMA Inverter Speedwire", + "createMethods": ["discovery", "user"], + "interfaces": ["solarinverter"], + "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": "hardware address", + "type": "QString", + "inputType": "TextLine", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "35733d27-4fe0-439a-be71-7c1597481659", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "015868bd-cc35-44cf-b631-78ea7c73b967", + "name": "currentPower", + "displayName": "Current power", + "displayNameEvent": "Current power changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "f29b6283-873b-45f5-8a14-622d34f11d4f", + "name": "totalEnergyProduced", + "displayName": "Total energy produced", + "displayNameEvent": "Total energy produced changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + } + ] } ] } diff --git a/sma/sma.pro b/sma/sma.pro index 5e124771..6a989d92 100644 --- a/sma/sma.pro +++ b/sma/sma.pro @@ -4,8 +4,10 @@ QT += network SOURCES += \ integrationpluginsma.cpp \ + speedwireinterface.cpp \ sunnywebbox.cpp HEADERS += \ integrationpluginsma.h \ + speedwireinterface.h \ sunnywebbox.h diff --git a/sma/speedwireinterface.cpp b/sma/speedwireinterface.cpp new file mode 100644 index 00000000..a7b42b32 --- /dev/null +++ b/sma/speedwireinterface.cpp @@ -0,0 +1,111 @@ +#include "speedwireinterface.h" +#include "extern-plugininfo.h" + +#include + +SpeedwireInterface::SpeedwireInterface(QObject *parent) : + QObject(parent) +{ + m_socket = new QUdpSocket(this); + connect(m_socket, &QUdpSocket::readyRead, this, &SpeedwireInterface::readPendingDatagrams); + connect(m_socket, &QUdpSocket::stateChanged, this, &SpeedwireInterface::onSocketStateChanged); + connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(onSocketError(QAbstractSocket::SocketError))); +} + +SpeedwireInterface::~SpeedwireInterface() +{ + if (m_initialized) { + if (!m_socket->leaveMulticastGroup(m_multicastAddress)) { + qCWarning(dcSma()) << "SpeedwireInterface: Failed to leave multicast group" << m_multicastAddress.toString(); + } + + m_socket->close(); + } +} + +bool SpeedwireInterface::initialize() +{ + // If we already initialized the socket, we are done + if (m_initialized) + return true; + + m_socket->close(); + m_initialized = false; + + if (!m_socket->bind(QHostAddress::AnyIPv4, m_port, QAbstractSocket::ShareAddress)) { + qCWarning(dcSma()) << "SpeedwireInterface: Cannot bind to port" << m_port << m_socket->errorString(); + return false; + } + + if (!m_socket->joinMulticastGroup(m_multicastAddress)) { + qCWarning(dcSma()) << "SpeedwireInterface: Failed to join multicast group" << m_multicastAddress.toString() << m_socket->errorString(); + return false; + } + + m_initialized = true; + return true; +} + +bool SpeedwireInterface::initialized() const +{ + return m_initialized; +} + +bool SpeedwireInterface::startDiscovery() +{ + if (m_discoveryRunning) + return true; + + qCDebug(dcSma()) << "SpeedwireInterface: Start discovering network..."; + if (!m_initialized) { + qCDebug(dcSma()) << "SpeedwireInterface: Failed to start discovery because the socket has not been initialized successfully."; + return false; + } + + // Discovery message + QByteArray discoveryDatagram = QByteArray::fromHex("534d4100000402a0ffffffff0000002000000000"); + if (m_socket->write(discoveryDatagram) < 0) { + qCWarning(dcSma()) << "SpeedwireInterface: Failed to send discovery datagram to multicast address" << m_multicastAddress.toString(); + return false; + } + + m_discoveryRunning = true; + QTimer::singleShot(10000, this, [=](){ + qCDebug(dcSma()) << "SpeedwireInterface: Discovey finished."; + m_discoveryRunning = false; + emit discoveryFinished(); + }); + + return true; +} + +bool SpeedwireInterface::discoveryRunning() const +{ + return m_discoveryRunning; +} + +void SpeedwireInterface::readPendingDatagrams() +{ + QUdpSocket *socket= qobject_cast(sender()); + + QByteArray datagram; + QHostAddress senderAddress; + quint16 senderPort; + + while (socket->hasPendingDatagrams()) { + datagram.resize(socket->pendingDatagramSize()); + socket->readDatagram(datagram.data(), datagram.size(), &senderAddress, &senderPort); + qCDebug(dcSma()) << "SpeedwireInterface: Data received from" << senderAddress.toString() << datagram.toHex(); + //emit datagramReceived(senderAddress, datagram); + } +} + +void SpeedwireInterface::onSocketError(QAbstractSocket::SocketError error) +{ + qCDebug(dcSma()) << "SpeedwireInterface: Socket error" << error; +} + +void SpeedwireInterface::onSocketStateChanged(QAbstractSocket::SocketState socketState) +{ + qCDebug(dcSma()) << "SpeedwireInterface: Socket state changed" << socketState; +} diff --git a/sma/speedwireinterface.h b/sma/speedwireinterface.h new file mode 100644 index 00000000..31df61b9 --- /dev/null +++ b/sma/speedwireinterface.h @@ -0,0 +1,37 @@ +#ifndef SPEEDWIREINTERFACE_H +#define SPEEDWIREINTERFACE_H + +#include +#include + +class SpeedwireInterface : public QObject +{ + Q_OBJECT +public: + explicit SpeedwireInterface(QObject *parent = nullptr); + ~SpeedwireInterface(); + + bool initialize(); + bool initialized() const; + + bool startDiscovery(); + bool discoveryRunning() const; + +signals: + void discoveryFinished(); + +private: + QUdpSocket *m_socket = nullptr; + QHostAddress m_multicastAddress = QHostAddress("239.12.255.254"); + quint16 m_port = 9522; + bool m_initialized = false; + bool m_discoveryRunning = false; + +private slots: + void readPendingDatagrams(); + void onSocketError(QAbstractSocket::SocketError error); + void onSocketStateChanged(QAbstractSocket::SocketState socketState); + +}; + +#endif // SPEEDWIREINTERFACE_H