diff --git a/fronius/fronius.pro b/fronius/fronius.pro index e730ebf0..84e9ebd3 100644 --- a/fronius/fronius.pro +++ b/fronius/fronius.pro @@ -3,11 +3,13 @@ include(../plugins.pri) QT += network SOURCES += \ + froniusdiscovery.cpp \ froniusnetworkreply.cpp \ froniussolarconnection.cpp \ integrationpluginfronius.cpp \ HEADERS += \ + froniusdiscovery.h \ froniusnetworkreply.h \ froniussolarconnection.h \ integrationpluginfronius.h \ diff --git a/fronius/froniusdiscovery.cpp b/fronius/froniusdiscovery.cpp new file mode 100644 index 00000000..2eeeb5e8 --- /dev/null +++ b/fronius/froniusdiscovery.cpp @@ -0,0 +1,137 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2023, 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 "froniusdiscovery.h" + +#include "extern-plugininfo.h" + +#include +#include + +FroniusDiscovery::FroniusDiscovery(NetworkAccessManager *networkManager, NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent): + QObject(parent), + m_networkManager(networkManager), + m_networkDeviceDiscovery{networkDeviceDiscovery} +{ + m_gracePeriodTimer.setSingleShot(true); + m_gracePeriodTimer.setInterval(3000); + connect(&m_gracePeriodTimer, &QTimer::timeout, this, [this](){ + qCDebug(dcFronius()) << "Discovery: Grace period timer triggered."; + finishDiscovery(); + }); + +} + +void FroniusDiscovery::startDiscovery() +{ + qCInfo(dcFronius()) << "Discovery: Searching for Fronius solar devices in the network..."; + NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover(); + + connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &FroniusDiscovery::checkNetworkDevice); + + connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){ + qCDebug(dcFronius()) << "Discovery: Network discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "network devices"; + m_gracePeriodTimer.start(); + discoveryReply->deleteLater(); + }); +} + +QList FroniusDiscovery::discoveryResults() const +{ + return m_discoveryResults; +} + +void FroniusDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo) +{ + qCDebug(dcFronius()) << "Checking network device:" << networkDeviceInfo; + + FroniusSolarConnection *connection = new FroniusSolarConnection(m_networkManager, networkDeviceInfo.address(), this); + m_connections.append(connection); + + FroniusNetworkReply *reply = connection->getVersion(); + connect(reply, &FroniusNetworkReply::finished, this, [=] { + QByteArray data = reply->networkReply()->readAll(); + if (reply->networkReply()->error() != QNetworkReply::NoError) { + if (reply->networkReply()->error() == QNetworkReply::ContentNotFoundError) { + qCInfo(dcFronius()) << "The device does not reply to our requests. Please verify that the Fronius Solar API is enabled on the device."; + } else { + qCDebug(dcFronius()) << "device" << networkDeviceInfo.address() << "is not reachable."; + } + cleanupConnection(connection); + return; + } + + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcFronius()) << "Failed to parse JSON data" << data << ":" << error.errorString() << data; + cleanupConnection(connection); + return; + } + + QVariantMap versionResponseMap = jsonDoc.toVariant().toMap(); + if (!versionResponseMap.contains("CompatibilityRange")) { + qCInfo(dcFronius()) << "Unexpected JSON reply. PRobably not a Fronius device."; + cleanupConnection(connection); + return; + } + qCDebug(dcFronius()) << "Compatibility version" << versionResponseMap.value("CompatibilityRange").toString(); + + // Knwon version with broken JSON API. Still allowing to discover so the user will get a proper error message during setup + if (versionResponseMap.value("CompatibilityRange").toString() == "1.6-2") { + qCWarning(dcFronius()) << "The Fronius data logger has a version which is known to have a broken JSON API firmware."; + } + + m_discoveryResults.append(networkDeviceInfo); + cleanupConnection(connection); + }); +} + +void FroniusDiscovery::cleanupConnection(FroniusSolarConnection *connection) +{ + m_connections.removeAll(connection); + connection->deleteLater(); +} + +void FroniusDiscovery::finishDiscovery() +{ + qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch(); + + foreach (FroniusSolarConnection *connection, m_connections) { + cleanupConnection(connection); + } + + qCInfo(dcFronius()) << "Discovery: Finished the discovery process. Found" << m_discoveryResults.count() + << "Fronius devices in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz"); + m_gracePeriodTimer.stop(); + + emit discoveryFinished(); + +} diff --git a/fronius/froniusdiscovery.h b/fronius/froniusdiscovery.h new file mode 100644 index 00000000..6b7b3249 --- /dev/null +++ b/fronius/froniusdiscovery.h @@ -0,0 +1,70 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2023, 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 FRONIUSDISCOVERY_H +#define FRONIUSDISCOVERY_H + +#include +#include + +#include +#include "froniussolarconnection.h" + +class FroniusDiscovery : public QObject +{ + Q_OBJECT +public: + explicit FroniusDiscovery(NetworkAccessManager *networkManager, NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent = nullptr); + + void startDiscovery(); + + QList discoveryResults() const; + +signals: + void discoveryFinished(); + +private: + NetworkAccessManager *m_networkManager = nullptr; + NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr; + + QTimer m_gracePeriodTimer; + QDateTime m_startDateTime; + + QList m_connections; + + QList m_discoveryResults; + + void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo); + void cleanupConnection(FroniusSolarConnection *connection); + + void finishDiscovery(); +}; + +#endif // FRONIUSDISCOVERY_H diff --git a/fronius/integrationpluginfronius.cpp b/fronius/integrationpluginfronius.cpp index f46949a9..738f04a2 100644 --- a/fronius/integrationpluginfronius.cpp +++ b/fronius/integrationpluginfronius.cpp @@ -31,6 +31,7 @@ #include "plugininfo.h" #include "plugintimer.h" #include "integrationpluginfronius.h" +#include "froniusdiscovery.h" #include "network/networkaccessmanager.h" #include "network/networkdevicediscovery.h" @@ -56,20 +57,15 @@ void IntegrationPluginFronius::discoverThings(ThingDiscoveryInfo *info) } qCInfo(dcFronius()) << "Starting network discovery..."; - NetworkDeviceDiscoveryReply *discoveryReply = hardwareManager()->networkDeviceDiscovery()->discover(); - connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, discoveryReply, &NetworkDeviceDiscoveryReply::deleteLater); - connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, info, [=](){ + FroniusDiscovery *discovery = new FroniusDiscovery(hardwareManager()->networkManager(), hardwareManager()->networkDeviceDiscovery(), info); + connect(discovery, &FroniusDiscovery::discoveryFinished, info, [=](){ ThingDescriptors descriptors; - qCDebug(dcFronius()) << "Discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "devices"; - foreach (const NetworkDeviceInfo &networkDeviceInfo, discoveryReply->networkDeviceInfos()) { + qCDebug(dcFronius()) << "Discovery finished. Found" << discovery->discoveryResults().count() << "devices"; + foreach (const NetworkDeviceInfo &networkDeviceInfo, discovery->discoveryResults()) { qCDebug(dcFronius()) << networkDeviceInfo; if (networkDeviceInfo.macAddress().isNull()) continue; - // Hostname or MAC manufacturer must include Fronius - if (!(networkDeviceInfo.macAddressManufacturer().toLower().contains("fronius") || networkDeviceInfo.hostName().toLower().contains("fronius"))) - continue; - QString title; if (networkDeviceInfo.hostName().isEmpty()) { title += "Fronius Solar";