diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index de3c7036..843008a9 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -30,9 +30,10 @@ #include "integrationpluginsma.h" #include "plugininfo.h" +#include "speedwirediscovery.h" +#include "sunnywebboxdiscovery.h" #include -#include "speedwirediscovery.h" IntegrationPluginSma::IntegrationPluginSma() { @@ -53,16 +54,12 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info) return; } - qCDebug(dcSma()) << "Starting network discovery..."; - NetworkDeviceDiscoveryReply *discoveryReply = hardwareManager()->networkDeviceDiscovery()->discover(); - connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){ + qCDebug(dcSma()) << "Starting Sunny WebBox discovery..."; + SunnyWebBoxDiscovery *webBoxDiscovery = new SunnyWebBoxDiscovery(hardwareManager()->networkManager(), hardwareManager()->networkDeviceDiscovery(), info); + connect(webBoxDiscovery, &SunnyWebBoxDiscovery::discoveryFinished, this, [=](){ + webBoxDiscovery->deleteLater(); ThingDescriptors descriptors; - qCDebug(dcSma()) << "Discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "devices"; - foreach (const NetworkDeviceInfo &networkDeviceInfo, discoveryReply->networkDeviceInfos()) { - // Filter for sma hosts - if (!networkDeviceInfo.hostName().toLower().contains("sma")) - continue; - + foreach (const NetworkDeviceInfo &networkDeviceInfo, webBoxDiscovery->discoveryResults()) { QString title = networkDeviceInfo.hostName() + " (" + networkDeviceInfo.address().toString() + ")"; QString description; if (networkDeviceInfo.macAddressManufacturer().isEmpty()) { @@ -90,6 +87,9 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info) info->addThingDescriptors(descriptors); info->finish(Thing::ThingErrorNoError); }); + + webBoxDiscovery->startDiscovery(); + } else if (info->thingClassId() == speedwireMeterThingClassId) { SpeedwireDiscovery *speedwireDiscovery = new SpeedwireDiscovery(hardwareManager()->networkDeviceDiscovery(), info); if (!speedwireDiscovery->initialize()) { diff --git a/sma/sma.pro b/sma/sma.pro index e663b33a..7e031fce 100644 --- a/sma/sma.pro +++ b/sma/sma.pro @@ -10,7 +10,8 @@ SOURCES += \ speedwireinverterreply.cpp \ speedwireinverterrequest.cpp \ speedwiremeter.cpp \ - sunnywebbox.cpp + sunnywebbox.cpp \ + sunnywebboxdiscovery.cpp HEADERS += \ integrationpluginsma.h \ @@ -21,4 +22,5 @@ HEADERS += \ speedwireinverterreply.h \ speedwireinverterrequest.h \ speedwiremeter.h \ - sunnywebbox.h + sunnywebbox.h \ + sunnywebboxdiscovery.h diff --git a/sma/sunnywebbox.cpp b/sma/sunnywebbox.cpp index dd1a6e07..d148ea27 100644 --- a/sma/sunnywebbox.cpp +++ b/sma/sunnywebbox.cpp @@ -142,6 +142,43 @@ void SunnyWebBox::setMacAddress(const QString &macAddress) m_macAddress = macAddress; } +QNetworkReply *SunnyWebBox::sendRequest(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms, const QString &requestId) +{ + qCDebug(dcSma()) << "SunnyWebBox: Send message to" << address.toString() << "Procedure:" << procedure << "Params:" << params; + + QString finalRequestId = requestId; + if (finalRequestId.isEmpty()) + finalRequestId = generateRequestId(); + + QJsonDocument doc; + QJsonObject obj; + obj["format"] = "JSON"; + obj["id"] = requestId; + obj["proc"] = procedure; + obj["version"] = "1.0"; + + if (!params.isEmpty()) { + obj.insert("params", params); + } + doc.setObject(obj); + + QUrl url; + url.setHost(address.toString()); + url.setPath("/rpc"); + url.setPort(80); + url.setScheme("http"); + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); + QByteArray data = doc.toJson(QJsonDocument::JsonFormat::Compact); + data.prepend("RPC="); + return m_networkManager->post(request, data); +} + +QString SunnyWebBox::generateRequestId() +{ + return QUuid::createUuid().toString().remove('{').remove('-').left(14); +} + void SunnyWebBox::parseMessage(const QString &messageId, const QString &messageType, const QVariantMap &result) { if (messageType == "GetPlantOverview") { @@ -263,31 +300,8 @@ QString SunnyWebBox::sendMessage(const QHostAddress &address, const QString &pro QString SunnyWebBox::sendMessage(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms) { - qCDebug(dcSma()) << "SunnyWebBox: Send message to" << address.toString() << "Procedure:" << procedure << "Params:" << params; - QString requestId = QUuid::createUuid().toString().remove('{').remove('-').left(14); - - QJsonDocument doc; - QJsonObject obj; - obj["format"] = "JSON"; - obj["id"] = requestId; - obj["proc"] = procedure; - obj["version"] = "1.0"; - - if (!params.isEmpty()) { - obj.insert("params", params); - } - doc.setObject(obj); - - QUrl url; - url.setHost(address.toString()); - url.setPath("/rpc"); - url.setPort(80); - url.setScheme("http"); - QNetworkRequest request(url); - request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); - QByteArray data = doc.toJson(QJsonDocument::JsonFormat::Compact); - data.prepend("RPC="); - QNetworkReply *reply = m_networkManager->post(request, data); + QString requestId = generateRequestId(); + QNetworkReply *reply = sendRequest(m_hostAddresss, procedure, params, requestId); connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, this, [this, address, requestId, reply]{ diff --git a/sma/sunnywebbox.h b/sma/sunnywebbox.h index ba1d669a..9981aeb6 100644 --- a/sma/sunnywebbox.h +++ b/sma/sunnywebbox.h @@ -35,6 +35,7 @@ #include "network/networkaccessmanager.h" #include +#include #include #include @@ -90,6 +91,10 @@ public: QString macAddress() const; void setMacAddress(const QString &macAddress); + QNetworkReply *sendRequest(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms = QJsonObject(), const QString &requestId = QString()); + + static QString generateRequestId(); + private: bool m_connected = false; QHostAddress m_hostAddresss; diff --git a/sma/sunnywebboxdiscovery.cpp b/sma/sunnywebboxdiscovery.cpp new file mode 100644 index 00000000..0a3988af --- /dev/null +++ b/sma/sunnywebboxdiscovery.cpp @@ -0,0 +1,164 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "sunnywebboxdiscovery.h" +#include "sunnywebbox.h" + +#include "extern-plugininfo.h" + +#include + +SunnyWebBoxDiscovery::SunnyWebBoxDiscovery(NetworkAccessManager *networkAccessManager, NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent) : + QObject(parent), + m_networkAccessManager(networkAccessManager), + m_networkDeviceDiscovery(networkDeviceDiscovery) +{ + +} + +void SunnyWebBoxDiscovery::startDiscovery() +{ + // Clean up + m_discoveryResults.clear(); + m_verifiedNetworkDeviceInfos.clear(); + + m_startDateTime = QDateTime::currentDateTime(); + + qCInfo(dcSma()) << "Discovery: SunnyWebBox: Starting network discovery..."; + m_discoveryReply = m_networkDeviceDiscovery->discover(); + + // Check all network device infos which might already be discovered here to save time... + foreach (const NetworkDeviceInfo &networkDeviceInfo, m_discoveryReply->networkDeviceInfos()) { + checkNetworkDevice(networkDeviceInfo); + } + + // Test any network device beeing discovered + connect(m_discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &SunnyWebBoxDiscovery::checkNetworkDevice); + + // When the network discovery has finished, we process the rest and give some time to finish the pending replies + connect(m_discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){ + // The network device discovery is done + m_discoveredNetworkDeviceInfos = m_discoveryReply->networkDeviceInfos(); + m_discoveryReply = nullptr; + + // Check if all network device infos have been verified + foreach (const NetworkDeviceInfo &networkDeviceInfo, m_discoveredNetworkDeviceInfos) { + if (m_verifiedNetworkDeviceInfos.contains(networkDeviceInfo)) + continue; + + checkNetworkDevice(networkDeviceInfo); + } + + // If there might be some response after the grace period time, + // we don't care any more since there might just waiting for some timeouts... + // If there would be a device, if would have responded. + QTimer::singleShot(3000, this, [this](){ + qCDebug(dcSma()) << "Discovery: SunnyWebBox: Grace period timer triggered."; + finishDiscovery(); + }); + }); +} + +NetworkDeviceInfos SunnyWebBoxDiscovery::discoveryResults() const +{ + return m_discoveryResults; +} + +void SunnyWebBoxDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo) +{ + if (m_verifiedNetworkDeviceInfos.contains(networkDeviceInfo)) + return; + + m_verifiedNetworkDeviceInfos.append(networkDeviceInfo); + + // Make a simple request and verify if it worked and the expected data gets returned. + SunnyWebBox webBox(m_networkAccessManager, networkDeviceInfo.address(), this); + QNetworkReply *reply = webBox.sendRequest(networkDeviceInfo.address(), "GetPlantOverview"); + m_pendingReplies.append(reply); + connect(reply, &QNetworkReply::finished, this, [=](){ + m_pendingReplies.removeAll(reply); + reply->deleteLater(); + + // Check HTTP reply + if (reply->error() != QNetworkReply::NoError) { + qCDebug(dcSma()) << "Discovery: SunnyWebBox: Checked" << networkDeviceInfo.address().toString() + << "and a HTTP error occurred:" << reply->errorString() << "Continue..."; + return; + } + + QByteArray data = reply->readAll(); + + // Check JSON + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + qCDebug(dcSma()) << "Discovery: SunnyWebBox: Checked" << networkDeviceInfo.address().toString() + << "and received invalid JSON data:" << error.errorString() << "Continue..."; + return; + } + + if (!jsonDoc.isObject()) { + qCDebug(dcSma()) << "Discovery: SunnyWebBox: Response JSON is not an Object" << networkDeviceInfo.address().toString() << "Continue..."; + return; + } + + QVariantMap map = jsonDoc.toVariant().toMap(); + if (map["version"] != "1.0") { + qCDebug(dcSma()) << "Discovery: SunnyWebBox: API version not supported on" << networkDeviceInfo.address().toString() << "Continue...";; + return; + } + + if (map.contains("proc") && map.contains("result")) { + // Ok, seems to be a Sunny WebBox we are talking to...add to the discovery results... + qCDebug(dcSma()) << "Discovery: SunnyWebBox: --> Found Sunny WebBox on" << networkDeviceInfo; + m_discoveryResults.append(networkDeviceInfo); + } else { + qCDebug(dcSma()) << "Discovery: SunnyWebBox: Missing proc or result value in response from" << networkDeviceInfo.address().toString() << "Continue..."; + return; + } + }); +} + +void SunnyWebBoxDiscovery::cleanupPendingReplies() +{ + foreach (QNetworkReply *reply, m_pendingReplies) { + reply->abort(); + } +} + +void SunnyWebBoxDiscovery::finishDiscovery() +{ + qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch(); + qCInfo(dcSma()) << "Discovery: SunnyWebBox: Finished the discovery process. Found" << m_discoveryResults.count() + << "Sunny WebBoxes in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz"); + + cleanupPendingReplies(); + emit discoveryFinished(); +} diff --git a/sma/sunnywebboxdiscovery.h b/sma/sunnywebboxdiscovery.h new file mode 100644 index 00000000..066e4cd5 --- /dev/null +++ b/sma/sunnywebboxdiscovery.h @@ -0,0 +1,71 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef SUNNYWEBBOXDISCOVERY_H +#define SUNNYWEBBOXDISCOVERY_H + +#include + +#include +#include + +class SunnyWebBoxDiscovery : public QObject +{ + Q_OBJECT +public: + explicit SunnyWebBoxDiscovery(NetworkAccessManager *networkAccessManager, NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent = nullptr); + + void startDiscovery(); + + NetworkDeviceInfos discoveryResults() const; + +signals: + void discoveryFinished(); + +private slots: + void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo); + void cleanupPendingReplies(); + void finishDiscovery(); + +private: + NetworkAccessManager *m_networkAccessManager = nullptr; + NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr; + NetworkDeviceDiscoveryReply *m_discoveryReply = nullptr; + + NetworkDeviceInfos m_discoveryResults; + NetworkDeviceInfos m_discoveredNetworkDeviceInfos; + NetworkDeviceInfos m_verifiedNetworkDeviceInfos; + + QDateTime m_startDateTime; + QList m_pendingReplies; + +}; + +#endif // SUNNYWEBBOXDISCOVERY_H