diff --git a/sma/discovery.cpp b/sma/discovery.cpp deleted file mode 100644 index fca14ef5..00000000 --- a/sma/discovery.cpp +++ /dev/null @@ -1,271 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* Copyright 2013 - 2020, 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 "discovery.h" -#include "extern-plugininfo.h" - -#include -#include -#include -#include -#include - -Discovery::Discovery(QObject *parent) : QObject(parent) -{ - connect(&m_timeoutTimer, &QTimer::timeout, this, &Discovery::onTimeout); -} - -void Discovery::discoverHosts(int timeout) -{ - if (isRunning()) { - qWarning(dcSma()) << "Discovery already running. Cannot start twice."; - return; - } - m_timeoutTimer.start(timeout * 1000); - - foreach (const QString &target, getDefaultTargets()) { - QProcess *discoveryProcess = new QProcess(this); - m_discoveryProcesses.append(discoveryProcess); - connect(discoveryProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(discoveryFinished(int,QProcess::ExitStatus))); - - QStringList arguments; - arguments << "-oX" << "-" << "-n" << "-sn"; - arguments << target; - - qCDebug(dcSma()) << "Scanning network:" << "nmap" << arguments.join(" "); - discoveryProcess->start(QStringLiteral("nmap"), arguments); - } - -} - -void Discovery::abort() -{ - foreach (QProcess *discoveryProcess, m_discoveryProcesses) { - if (discoveryProcess->state() == QProcess::Running) { - qCDebug(dcSma()) << "Kill running discovery process"; - discoveryProcess->terminate(); - discoveryProcess->waitForFinished(5000); - } - } - foreach (QProcess *p, m_pendingArpLookups.keys()) { - p->terminate(); - delete p; - } - m_pendingArpLookups.clear(); - m_pendingNameLookups.clear(); - qDeleteAll(m_scanResults); - m_scanResults.clear(); -} - -bool Discovery::isRunning() const -{ - return !m_discoveryProcesses.isEmpty() || !m_pendingArpLookups.isEmpty() || !m_pendingNameLookups.isEmpty(); -} - -void Discovery::discoveryFinished(int exitCode, QProcess::ExitStatus exitStatus) -{ - QProcess *discoveryProcess = static_cast(sender()); - - if (exitCode != 0 || exitStatus != QProcess::NormalExit) { - qCWarning(dcSma()) << "Nmap error failed. Is nmap installed correctly?"; - m_discoveryProcesses.removeAll(discoveryProcess); - discoveryProcess->deleteLater(); - discoveryProcess = nullptr; - finishDiscovery(); - return; - } - - QByteArray data = discoveryProcess->readAll(); - m_discoveryProcesses.removeAll(discoveryProcess); - discoveryProcess->deleteLater(); - discoveryProcess = nullptr; - - QXmlStreamReader reader(data); - - int foundHosts = 0; - - while (!reader.atEnd() && !reader.hasError()) { - QXmlStreamReader::TokenType token = reader.readNext(); - if(token == QXmlStreamReader::StartDocument) - continue; - - if(token == QXmlStreamReader::StartElement && reader.name() == "host") { - bool isUp = false; - QString address; - QString macAddress; - QString vendor; - while (!reader.atEnd() && !reader.hasError() && !(token == QXmlStreamReader::EndElement && reader.name() == "host")) { - token = reader.readNext(); - - if (reader.name() == "address") { - QString addr = reader.attributes().value("addr").toString(); - QString type = reader.attributes().value("addrtype").toString(); - if (type == "ipv4" && !addr.isEmpty()) { - address = addr; - } else if (type == "mac") { - macAddress = addr; - vendor = reader.attributes().value("vendor").toString(); - } - } - - if (reader.name() == "status") { - QString state = reader.attributes().value("state").toString(); - if (!state.isEmpty()) - isUp = state == "up"; - } - } - - if (isUp) { - foundHosts++; - qCDebug(dcSma()) << "Have host:" << address; - - Host *host = new Host(); - host->setAddress(address); - - if (!macAddress.isEmpty()) { - host->setMacAddress(macAddress); - } else { - QProcess *arpLookup = new QProcess(this); - m_pendingArpLookups.insert(arpLookup, host); - connect(arpLookup, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(arpLookupDone(int,QProcess::ExitStatus))); - arpLookup->start("arp", {"-vn"}); - } - - host->setHostName(vendor); - QHostInfo::lookupHost(address, this, SLOT(hostLookupDone(QHostInfo))); - m_pendingNameLookups.insert(address, host); - - m_scanResults.append(host); - } - } - } - - if (foundHosts == 0 && m_discoveryProcesses.isEmpty()) { - qCDebug(dcSma()) << "Network scan successful but no hosts found in this network"; - finishDiscovery(); - } -} - -void Discovery::hostLookupDone(const QHostInfo &info) -{ - Host *host = m_pendingNameLookups.take(info.addresses().first().toString()); - if (!host) { - // Probably aborted... - return; - } - if (info.error() != QHostInfo::NoError) { - qWarning(dcSma()) << "Host lookup failed:" << info.errorString(); - } - if (host->hostName().isEmpty() || info.hostName() != host->address()) { - host->setHostName(info.hostName()); - } - - finishDiscovery(); -} - -void Discovery::arpLookupDone(int exitCode, QProcess::ExitStatus exitStatus) -{ - QProcess *p = static_cast(sender()); - p->deleteLater(); - - Host *host = m_pendingArpLookups.take(p); - - if (exitCode != 0 || exitStatus != QProcess::NormalExit) { - qCWarning(dcSma()) << "ARP lookup process failed for host" << host->address(); - finishDiscovery(); - return; - } - - QString data = QString::fromLatin1(p->readAll()); - foreach (QString line, data.split('\n')) { - line.replace(QRegExp("[ ]{1,}"), " "); - QStringList parts = line.split(" "); - if (parts.count() >= 3 && parts.first() == host->address() && parts.at(1) == "ether") { - host->setMacAddress(parts.at(2)); - break; - } - } - finishDiscovery(); -} - -void Discovery::onTimeout() -{ - qWarning(dcSma()) << "Timeout hit. Stopping discovery"; - while (!m_discoveryProcesses.isEmpty()) { - QProcess *discoveryProcess = m_discoveryProcesses.takeFirst(); - disconnect(this, SLOT(discoveryFinished(int,QProcess::ExitStatus))); - discoveryProcess->terminate(); - delete discoveryProcess; - } - foreach (QProcess *p, m_pendingArpLookups.keys()) { - p->terminate(); - m_scanResults.removeAll(m_pendingArpLookups.value(p)); - delete p; - } - m_pendingArpLookups.clear(); - m_pendingNameLookups.clear(); - finishDiscovery(); -} - -QStringList Discovery::getDefaultTargets() -{ - QStringList targets; - foreach (const QHostAddress &interface, QNetworkInterface::allAddresses()) { - if (!interface.isLoopback() && interface.scopeId().isEmpty() && interface.protocol() == QAbstractSocket::IPv4Protocol) { - QPair pair = QHostAddress::parseSubnet(interface.toString() + "/24"); - QString newTarget = QString("%1/%2").arg(pair.first.toString()).arg(pair.second); - if (!targets.contains(newTarget)) { - targets.append(newTarget); - } - } - } - return targets; -} - -void Discovery::finishDiscovery() -{ - if (m_discoveryProcesses.count() > 0 || m_pendingNameLookups.count() > 0 || m_pendingArpLookups.count() > 0) { - // Still busy... - return; - } - - QList hosts; - foreach (Host *host, m_scanResults) { - if (!host->macAddress().isEmpty()) { - hosts.append(*host); - } - } - qDeleteAll(m_scanResults); - m_scanResults.clear(); - - qCDebug(dcSma()) << "Emitting thing discovered for" << hosts.count() << "devices"; - m_timeoutTimer.stop(); - emit finished(hosts); -} diff --git a/sma/discovery.h b/sma/discovery.h deleted file mode 100644 index b74fe2cb..00000000 --- a/sma/discovery.h +++ /dev/null @@ -1,77 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* Copyright 2013 - 2020, 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 DISCOVERY_H -#define DISCOVERY_H - -#include -#include -#include -#include - -#include "host.h" - -class Discovery : public QObject -{ - Q_OBJECT -public: - explicit Discovery(QObject *parent = nullptr); - - void discoverHosts(int timeout); - void abort(); - - bool isRunning() const; - - -signals: - void finished(QList hosts); - -private: - QStringList getDefaultTargets(); - - void finishDiscovery(); - -private slots: - void discoveryFinished(int exitCode, QProcess::ExitStatus exitStatus); - void hostLookupDone(const QHostInfo &info); - void arpLookupDone(int exitCode, QProcess::ExitStatus exitStatus); - void onTimeout(); - -private: - QList m_discoveryProcesses; - QTimer m_timeoutTimer; - - QHash m_pendingArpLookups; - QHash m_pendingNameLookups; - QList m_scanResults; - -}; - -#endif // DISCOVERY_H diff --git a/sma/host.cpp b/sma/host.cpp deleted file mode 100644 index 2aec4ab6..00000000 --- a/sma/host.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* Copyright 2013 - 2020, 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 "host.h" - -Host::Host() -{ - qRegisterMetaType(); - qRegisterMetaType >(); -} - -QString Host::macAddress() const -{ - return m_macAddress; -} - -void Host::setMacAddress(const QString &macAddress) -{ - m_macAddress = macAddress; -} - -QString Host::hostName() const -{ - return m_hostName; -} - -void Host::setHostName(const QString &hostName) -{ - m_hostName = hostName; -} - -QString Host::address() const -{ - return m_address; -} - -void Host::setAddress(const QString &address) -{ - m_address = address; -} - -void Host::seen() -{ - m_lastSeenTime = QDateTime::currentDateTime(); -} - -QDateTime Host::lastSeenTime() const -{ - return m_lastSeenTime; -} - -bool Host::reachable() const -{ - return m_reachable; -} - -void Host::setReachable(bool reachable) -{ - m_reachable = reachable; -} - -QDebug operator<<(QDebug dbg, const Host &host) -{ - dbg.nospace() << "Host(" << host.macAddress() << "," << host.hostName() << ", " << host.address() << ", " << (host.reachable() ? "up" : "down") << ")"; - return dbg.space(); -} diff --git a/sma/host.h b/sma/host.h deleted file mode 100644 index 84e48b0d..00000000 --- a/sma/host.h +++ /dev/null @@ -1,69 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* Copyright 2013 - 2020, 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 HOST_H -#define HOST_H - -#include -#include -#include - -class Host -{ -public: - Host(); - - QString macAddress() const; - void setMacAddress(const QString &macAddress); - - QString hostName() const; - void setHostName(const QString &hostName); - - QString address() const; - void setAddress(const QString &address); - - void seen(); - QDateTime lastSeenTime() const; - - bool reachable() const; - void setReachable(bool reachable); - -private: - QString m_macAddress; - QString m_hostName; - QString m_address; - QDateTime m_lastSeenTime; - bool m_reachable; -}; -Q_DECLARE_METATYPE(Host) - -QDebug operator<<(QDebug dbg, const Host &host); - -#endif // HOST_H diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 6a8b2cff..1968c28a 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -31,6 +31,8 @@ #include "integrationpluginsma.h" #include "plugininfo.h" +#include "network/networkdevicediscovery.h" + IntegrationPluginSma::IntegrationPluginSma() { @@ -38,36 +40,50 @@ IntegrationPluginSma::IntegrationPluginSma() void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info) { - if (info->thingClassId() == sunnyWebBoxThingClassId) { + 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; + } - Discovery *discovery = new Discovery(this); - discovery->discoverHosts(25); + qCDebug(dcSma()) << "Starting network discovery..."; + NetworkDeviceDiscoveryReply *discoveryReply = hardwareManager()->networkDeviceDiscovery()->discover(); + connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){ + 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; - // clean up discovery object when this discovery info is deleted - connect(info, &ThingDiscoveryInfo::destroyed, discovery, &Discovery::deleteLater); - connect(discovery, &Discovery::finished, info, [this, info](const QList &hosts) { - qCDebug(dcSma()) << "Discovery finished. Found" << hosts.count() << "devices"; - foreach (const Host &host, hosts) { - if (host.hostName().contains("SMA")){ - ThingDescriptor descriptor(info->thingClassId(), host.hostName(), host.address() + " (" + host.macAddress() + ")"); + QString title = networkDeviceInfo.hostName() + " (" + networkDeviceInfo.address().toString() + ")"; + QString description; + if (networkDeviceInfo.macAddressManufacturer().isEmpty()) { + description = networkDeviceInfo.macAddress(); + } else { + description = networkDeviceInfo.macAddress() + " (" + networkDeviceInfo.macAddressManufacturer() + ")"; + } - foreach (Thing *existingThing, myThings()) { - if (existingThing->paramValue(sunnyWebBoxThingMacAddressParamTypeId).toString() == host.macAddress()) { - descriptor.setThingId(existingThing->id()); - break; - } - } - ParamList params; - params << Param(sunnyWebBoxThingMacAddressParamTypeId, host.macAddress()); - params << Param(sunnyWebBoxThingHostParamTypeId, host.address()); - descriptor.setParams(params); + ThingDescriptor descriptor(sunnyWebBoxThingClassId, title, description); - info->addThingDescriptor(descriptor); + // Check for reconfiguration + foreach (Thing *existingThing, myThings()) { + if (existingThing->paramValue(sunnyWebBoxThingMacAddressParamTypeId).toString() == networkDeviceInfo.macAddress()) { + descriptor.setThingId(existingThing->id()); + break; } } - info->finish(Thing::ThingErrorNoError); - }); - } + + ParamList params; + params << Param(sunnyWebBoxThingMacAddressParamTypeId, networkDeviceInfo.macAddress()); + params << Param(sunnyWebBoxThingHostParamTypeId, networkDeviceInfo.address().toString()); + descriptor.setParams(params); + descriptors.append(descriptor); + } + info->addThingDescriptors(descriptors); + info->finish(Thing::ThingErrorNoError); + }); + } void IntegrationPluginSma::setupThing(ThingSetupInfo *info) diff --git a/sma/integrationpluginsma.h b/sma/integrationpluginsma.h index 348de1d5..3c20919f 100644 --- a/sma/integrationpluginsma.h +++ b/sma/integrationpluginsma.h @@ -34,7 +34,6 @@ #include "integrations/integrationplugin.h" #include "plugintimer.h" #include "sunnywebbox.h" -#include "discovery.h" #include diff --git a/sma/sma.pro b/sma/sma.pro index 168f97f3..5e124771 100644 --- a/sma/sma.pro +++ b/sma/sma.pro @@ -1,16 +1,11 @@ include(../plugins.pri) -QT += \ - network \ +QT += network SOURCES += \ integrationpluginsma.cpp \ - sunnywebbox.cpp \ - host.cpp \ - discovery.cpp + sunnywebbox.cpp HEADERS += \ integrationpluginsma.h \ - sunnywebbox.h \ - host.h \ - discovery.h + sunnywebbox.h