diff --git a/debian/control b/debian/control index f63e1acf..4d4348fa 100644 --- a/debian/control +++ b/debian/control @@ -395,9 +395,6 @@ Package: nymea-plugin-networkdetector Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, - nmap, - fping, - arping, Conflicts: nymea-plugins-translations (< 1.0.1) Description: nymea integration plugin for networkdetector This package contains the nymea integration plugin for detecting and monitoring diff --git a/networkdetector/README.md b/networkdetector/README.md index 8c2e0064..3957abc7 100644 --- a/networkdetector/README.md +++ b/networkdetector/README.md @@ -1,20 +1,11 @@ # Network detector -This plugin allows to find and monitor network devices in your local network by using the hostname of the devices. +This plugin allows to find and monitor network devices in your local network by using the MAC address of the device. -## Supported Things +## Supported things -* Network Device +* Network device + * Information about the network device like IP, hostname, MAC address manufacturer * Presence sensor appearance - * Grace period adjustable * Present and last seen state -## Requirements - -* The application `nmap` has to be installed and nymea has to run as `root`. -* The network devices needs to be in the same local area network as nymea. -* The package 'nymea-plugin-networkdetector' - -## More - -https://nmap.org/ diff --git a/networkdetector/broadcastping.cpp b/networkdetector/broadcastping.cpp deleted file mode 100644 index 84438989..00000000 --- a/networkdetector/broadcastping.cpp +++ /dev/null @@ -1,81 +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 "broadcastping.h" -#include "extern-plugininfo.h" - -#include -#include - -BroadcastPing::BroadcastPing(QObject *parent) : QObject(parent) -{ - -} - -void BroadcastPing::run() -{ - qDeleteAll(m_runningPings.keys()); - m_runningPings.clear(); - - foreach (const QNetworkInterface &interface, QNetworkInterface::allInterfaces()) { - if (!interface.flags().testFlag(QNetworkInterface::IsUp) || !interface.flags().testFlag(QNetworkInterface::CanBroadcast) || interface.flags().testFlag(QNetworkInterface::IsLoopBack)) { - continue; - } - foreach (const QNetworkAddressEntry &addressEntry, interface.addressEntries()) { - if (addressEntry.broadcast().isNull()) { - continue; - } - qCDebug(dcNetworkDetector()) << "Sending Broadcast Ping on" << addressEntry.broadcast().toString() + '/' + QString::number(addressEntry.prefixLength()) + "..."; - QProcess *p = new QProcess(this); - m_runningPings.insert(p, addressEntry); - p->start("fping", {"-a", "-c", "1", "-g", addressEntry.broadcast().toString() + "/" + QString::number(addressEntry.prefixLength())}); - connect(p, SIGNAL(finished(int)), this, SLOT(broadcastPingFinished(int))); - } - } - if (m_runningPings.isEmpty()) { - qCWarning(dcNetworkDetector()) << "Cound not find any suitable interface for broadcast pinging"; - emit finished(); - } -} - -void BroadcastPing::broadcastPingFinished(int exitCode) -{ - Q_UNUSED(exitCode); - QProcess *p = static_cast(sender()); - QNetworkAddressEntry addressEntry = m_runningPings.take(p); - qCDebug(dcNetworkDetector()) << "Broadcast ping finished for network" << addressEntry.broadcast().toString() + '/' + QString::number(addressEntry.prefixLength()); -// qCDebug(dcNetworkDetector()) << p->readAllStandardError(); - p->deleteLater(); - - if (m_runningPings.isEmpty()) { - qCDebug(dcNetworkDetector()) << "All broadcast pings finished"; - emit finished(); - } -} diff --git a/networkdetector/broadcastping.h b/networkdetector/broadcastping.h deleted file mode 100644 index c49915fe..00000000 --- a/networkdetector/broadcastping.h +++ /dev/null @@ -1,58 +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 BROADCASTPING_H -#define BROADCASTPING_H - -#include -#include -#include -#include - -class BroadcastPing : public QObject -{ - Q_OBJECT -public: - explicit BroadcastPing(QObject *parent = nullptr); - -signals: - void finished(); - -public slots: - void run(); - -private slots: - void broadcastPingFinished(int exitCode); - -private: - QHash m_runningPings; -}; - -#endif // BROADCASTPING_H diff --git a/networkdetector/devicemonitor.cpp b/networkdetector/devicemonitor.cpp deleted file mode 100644 index 5c3ad603..00000000 --- a/networkdetector/devicemonitor.cpp +++ /dev/null @@ -1,257 +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 "devicemonitor.h" - -#include "extern-plugininfo.h" - -#include - -DeviceMonitor::DeviceMonitor(const QString &name, const QString &macAddress, const QString &ipAddress, bool initialState, QObject *parent): - QObject(parent), - m_name(name), - m_macAddress(macAddress), - m_ipAddress(ipAddress), - m_reachable(initialState) -{ - m_arpLookupProcess = new QProcess(this); - connect(m_arpLookupProcess, SIGNAL(finished(int)), this, SLOT(arpLookupFinished(int))); - - m_arpingProcess = new QProcess(this); - m_arpingProcess->setProcessChannelMode(QProcess::MergedChannels); -#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) - // Actually we'd need this fix on older platforms too, but it's hard to figure this out without this API... - connect(m_arpingProcess, &QProcess::errorOccurred, this, [this](QProcess::ProcessError error) { - if (error == QProcess::FailedToStart) { - warn(QString("arping process failed to start. Falling back to ping. This plugin might not work properly on this system.")); - ping(); - } - }); -#endif - connect(m_arpingProcess, SIGNAL(finished(int)), this, SLOT(arpingFinished(int))); - - m_pingProcess = new QProcess(this); - m_pingProcess->setProcessChannelMode(QProcess::MergedChannels); - connect(m_pingProcess, SIGNAL(finished(int)), this, SLOT(pingFinished(int))); -} - -DeviceMonitor::~DeviceMonitor() -{ -} - -void DeviceMonitor::setGracePeriod(int minutes) -{ - log("Setting grace period to " + QString::number(minutes) + " minutes."); - m_gracePeriod = minutes; -} - -void DeviceMonitor::update() -{ - if (m_arpingProcess->state() != QProcess::NotRunning || m_pingProcess->state() != QProcess::NotRunning) { -// log("Previous ping still running. Not updating."); - return; - } - lookupArpCache(); -} - -void DeviceMonitor::lookupArpCache() -{ - m_arpLookupProcess->start("ip", {"-4", "-s", "neighbor", "list"}); -} - -void DeviceMonitor::arpLookupFinished(int exitCode) -{ - if (exitCode != 0) { - warn("Error looking up ARP cache."); - return; - } - - QString data = QString::fromLatin1(m_arpLookupProcess->readAll()); - bool found = false; - bool needsPing = true; - QString mostRecentIP = m_ipAddress; - qlonglong secsSinceLastSeen = -1; - foreach (QString line, data.split('\n')) { - line.replace(QRegExp("[ ]{1,}"), " "); - QStringList parts = line.split(" "); - int lladdrIndex = parts.indexOf("lladdr"); - if (lladdrIndex == -1 || parts.count() <= lladdrIndex) { - continue; - } - QString entryIP = parts.first(); - QString entryMAC = parts.at(lladdrIndex + 1); - - if (entryMAC.toLower() == m_macAddress.toLower()) { - found = true; - QString entryIP = parts.first(); - if (parts.last() == "REACHABLE") { - log("Device found in ARP cache and claims to be REACHABLE (Cache IP: " + entryIP + ")"); - if (!m_reachable) { - m_reachable = true; - emit reachableChanged(true); - } - emit seen(); - m_lastSeenTime = QDateTime::currentDateTime(); - // Verify if IP address is still the same - if (entryIP != mostRecentIP) { - mostRecentIP = entryIP; - } - // If we have a reachable entry, stop processing here - needsPing = false; - break; - } else { - // ARP claims the thing to be stale... Flagging thing to require a ping. - log("Device found in ARP cache but is marked as " + parts.last() + " (Cache IP: " + entryIP + ")"); - - int usedIndex = parts.indexOf("used"); - if (usedIndex >= 0 && parts.count() > usedIndex + 1) { - QString usedFields = parts.at(usedIndex + 1); - qlonglong newSecsSinceLastSeen = usedFields.split("/").first().toInt(); - if (secsSinceLastSeen == -1 || newSecsSinceLastSeen < secsSinceLastSeen) { - secsSinceLastSeen = newSecsSinceLastSeen; - mostRecentIP = entryIP; - } - } - } - } else if (entryIP == m_ipAddress) { - warn("There seems to be a thing with our IP but different MAC. Resetting IP config."); - if (mostRecentIP == m_ipAddress) { - mostRecentIP.clear(); - } - } - } - if (mostRecentIP != m_ipAddress) { - log("Device has changed IP: " + m_ipAddress + " -> " + mostRecentIP + ")"); - m_ipAddress = mostRecentIP; - emit addressChanged(mostRecentIP); - } - if (m_ipAddress.isEmpty()) { - warn("Device not found in ARP cache and no IP config available. Marking as gone."); - if (m_reachable) { - m_reachable = false; - emit reachableChanged(false); - } - return; - } - if (!found) { - log("Device not found in ARP cache."); - arping(); - } else if (needsPing) { - arping(); - } -} - -void DeviceMonitor::arping() -{ - QNetworkInterface targetInterface; - foreach (const QNetworkInterface &interface, QNetworkInterface::allInterfaces()) { - foreach (const QNetworkAddressEntry &addressEntry, interface.addressEntries()) { - QHostAddress clientAddress(m_ipAddress); - if (clientAddress.isInSubnet(addressEntry.ip(), addressEntry.prefixLength())) { - targetInterface = interface; - break; - } - } - } - if (!targetInterface.isValid()) { - warn("Could not find a suitable interface to ARP Ping."); - if (m_reachable) { - m_reachable = false; - emit reachableChanged(false); - } - return; - } - - log("Sending ARP Ping to " + m_ipAddress + "..."); - m_arpingProcess->start("arping", {"-I", targetInterface.name(), "-f", "-w", "30", m_ipAddress}); -} - -void DeviceMonitor::arpingFinished(int exitCode) -{ - if (exitCode == 0) { - // we were able to ping the thing - log("ARP Ping successful."); - if (!m_reachable) { - m_reachable = true; - emit reachableChanged(true); - } - emit seen(); - m_lastSeenTime = QDateTime::currentDateTime(); - } else { - log("ARP Ping failed."); - ping(); - } - // read data to discard it from socket - QString data = QString::fromLatin1(m_arpingProcess->readAll()); - Q_UNUSED(data) -// qCDebug(dcNetworkDetector()) << "have ping data" << data; -} - -void DeviceMonitor::ping() -{ - log("Sending ICMP Ping to " + m_ipAddress + "..."); - m_pingProcess->start("ping", {"-c", "30", m_ipAddress}); -} - -void DeviceMonitor::pingFinished(int exitCode) - -{ - if (exitCode == 0) { - // we were able to ping the thing - log("ICMP Ping successful."); - if (!m_reachable) { - m_reachable = true; - emit reachableChanged(true); - } - emit seen(); - m_lastSeenTime = QDateTime::currentDateTime(); - } else { - log("ICMP Ping failed. Last seen: " + m_lastSeenTime.toString() + ", grace period: " + QString::number(m_gracePeriod) + " (until " + m_lastSeenTime.addSecs(60 * m_gracePeriod).toString() + ")"); - if (m_reachable && m_lastSeenTime.addSecs(m_gracePeriod * 60) < QDateTime::currentDateTime()) { - log("Exceeded grace period of " + QString::number(m_gracePeriod) + " minutes. Marking thing as offline."); - m_reachable = false; - emit reachableChanged(false); - } - } - // read data to discard it from socket - QString data = QString::fromLatin1(m_pingProcess->readAll()); - Q_UNUSED(data) -// qCDebug(dcNetworkDetector()) << "have ping data" << data; -} - -void DeviceMonitor::log(const QString &message) -{ - qCDebug(dcNetworkDetector()).noquote().nospace() << m_name << " (" << m_macAddress << ", " << m_ipAddress << "): " << message; -} - -void DeviceMonitor::warn(const QString &message) -{ - qCWarning(dcNetworkDetector()).noquote().nospace() << m_name << " (" << m_macAddress << ", " << m_ipAddress << "): " << message; -} diff --git a/networkdetector/devicemonitor.h b/networkdetector/devicemonitor.h deleted file mode 100644 index 630a2d81..00000000 --- a/networkdetector/devicemonitor.h +++ /dev/null @@ -1,82 +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 DEVICEMONITOR_H -#define DEVICEMONITOR_H - -#include -#include -#include - -class DeviceMonitor : public QObject -{ - Q_OBJECT -public: - explicit DeviceMonitor(const QString &name, const QString &macAddress, const QString &ipAddress, bool initialState, QObject *parent = nullptr); - - ~DeviceMonitor(); - - void setGracePeriod(int minutes); - - void update(); - -signals: - void addressChanged(const QString &address); - void reachableChanged(bool reachable); - void seen(); - -private: - void lookupArpCache(); - void arping(); - void ping(); - - void log(const QString &message); - void warn(const QString &message); - -private slots: - void arpLookupFinished(int exitCode); - void arpingFinished(int exitCode); - void pingFinished(int exitCode); - -private: - QString m_name; - QString m_macAddress; - QString m_ipAddress; - QDateTime m_lastSeenTime; - - bool m_reachable = false; - int m_gracePeriod = 5; - - QProcess *m_arpLookupProcess = nullptr; - QProcess *m_arpingProcess = nullptr; - QProcess *m_pingProcess = nullptr; -}; - -#endif // DEVICEMONITOR_H diff --git a/networkdetector/discovery.cpp b/networkdetector/discovery.cpp deleted file mode 100644 index 979f3d17..00000000 --- a/networkdetector/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(dcNetworkDetector()) << "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(dcNetworkDetector) << "Scanning network:" << "nmap" << arguments.join(" "); - discoveryProcess->start(QStringLiteral("nmap"), arguments); - } - -} - -void Discovery::abort() -{ - foreach (QProcess *discoveryProcess, m_discoveryProcesses) { - if (discoveryProcess->state() == QProcess::Running) { - qCDebug(dcNetworkDetector()) << "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(dcNetworkDetector()) << "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(dcNetworkDetector()) << "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(dcNetworkDetector()) << "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(dcNetworkDetector()) << "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(dcNetworkDetector()) << "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(dcNetworkDetector()) << "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(dcNetworkDetector()) << "Emitting thing discovered for" << hosts.count() << "devices"; - m_timeoutTimer.stop(); - emit finished(hosts); -} diff --git a/networkdetector/discovery.h b/networkdetector/discovery.h deleted file mode 100644 index b74fe2cb..00000000 --- a/networkdetector/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/networkdetector/host.cpp b/networkdetector/host.cpp deleted file mode 100644 index 2aec4ab6..00000000 --- a/networkdetector/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/networkdetector/host.h b/networkdetector/host.h deleted file mode 100644 index 84e48b0d..00000000 --- a/networkdetector/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/networkdetector/integrationpluginnetworkdetector.cpp b/networkdetector/integrationpluginnetworkdetector.cpp index 02d609cd..eb515dc1 100644 --- a/networkdetector/integrationpluginnetworkdetector.cpp +++ b/networkdetector/integrationpluginnetworkdetector.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2020, nymea GmbH +* Copyright 2013 - 2022, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -30,7 +30,6 @@ #include "integrationpluginnetworkdetector.h" -#include "integrations/thing.h" #include "plugininfo.h" #include @@ -38,131 +37,254 @@ IntegrationPluginNetworkDetector::IntegrationPluginNetworkDetector() { - m_broadcastPing = new BroadcastPing(this); - connect(m_broadcastPing, &BroadcastPing::finished, this, &IntegrationPluginNetworkDetector::broadcastPingFinished); + } + IntegrationPluginNetworkDetector::~IntegrationPluginNetworkDetector() { + } void IntegrationPluginNetworkDetector::init() { + +} + +void IntegrationPluginNetworkDetector::discoverThings(ThingDiscoveryInfo *info) +{ + if (!hardwareManager()->networkDeviceDiscovery()->available()) { + qCWarning(dcNetworkDetector()) << "Failed to discover network devices. The network device discovery is not available."; + info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Unable to discovery devices in your network.")); + return; + } + + qCDebug(dcNetworkDetector()) << "Starting network discovery..."; + NetworkDeviceDiscoveryReply *discoveryReply = hardwareManager()->networkDeviceDiscovery()->discover(); + connect(discoveryReply, &NetworkDeviceDiscoveryReply::hostAddressDiscovered, this, [=](const QHostAddress &address){ + qCDebug(dcNetworkDetector()) << "Address discovered" << address.toString(); + }); + + connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, [=](const NetworkDeviceInfo &networkDeviceInfo){ + qCDebug(dcNetworkDetector()) << "-->" << networkDeviceInfo; + }); + + connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, info, [=](){ + ThingDescriptors descriptors; + qCDebug(dcNetworkDetector()) << "Discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "devices"; + foreach (const NetworkDeviceInfo &networkDeviceInfo, discoveryReply->networkDeviceInfos()) { + + qCDebug(dcNetworkDetector()) << "-->" << networkDeviceInfo; + QString title; + if (networkDeviceInfo.hostName().isEmpty()) { + title = networkDeviceInfo.macAddress(); + } else { + title = networkDeviceInfo.hostName() + " (" + networkDeviceInfo.macAddress() + ")"; + } + QString description; + if (networkDeviceInfo.macAddressManufacturer().isEmpty()) { + description = networkDeviceInfo.address().toString(); + } else { + description = networkDeviceInfo.address().toString() + " - " + networkDeviceInfo.macAddressManufacturer(); + } + + ThingDescriptor descriptor(networkDeviceThingClassId, title, description); + ParamList params; + params.append(Param(networkDeviceThingMacAddressParamTypeId, networkDeviceInfo.macAddress())); + descriptor.setParams(params); + + // Check if we already have set up this device + Things existingThings = myThings().filterByParam(networkDeviceThingMacAddressParamTypeId, networkDeviceInfo.macAddress()); + if (existingThings.count() == 1) { + qCDebug(dcNetworkDetector()) << "This network device already exists in the system" << networkDeviceInfo; + descriptor.setThingId(existingThings.first()->id()); + } + descriptors.append(descriptor); + } + info->addThingDescriptors(descriptors); + info->finish(Thing::ThingErrorNoError); + }); } void IntegrationPluginNetworkDetector::setupThing(ThingSetupInfo *info) { Thing *thing = info->thing(); qCDebug(dcNetworkDetector()) << "Setup" << thing->name() << thing->params(); - DeviceMonitor *monitor = new DeviceMonitor(thing->name(), - thing->paramValue(networkDeviceThingMacAddressParamTypeId).toString(), - thing->paramValue(networkDeviceThingAddressParamTypeId).toString(), - thing->stateValue(networkDeviceIsPresentStateTypeId).toBool(), - this); - connect(monitor, &DeviceMonitor::reachableChanged, this, &IntegrationPluginNetworkDetector::deviceReachableChanged); - connect(monitor, &DeviceMonitor::addressChanged, this, &IntegrationPluginNetworkDetector::deviceAddressChanged); - connect(monitor, &DeviceMonitor::seen, this, &IntegrationPluginNetworkDetector::deviceSeen); - monitor->setGracePeriod(thing->setting(networkDeviceSettingsGracePeriodParamTypeId).toInt()); - m_monitors.insert(monitor, thing); - connect(thing, &Thing::settingChanged, this, [this, thing](const ParamTypeId ¶mTypeId, const QVariant &value){ - if (paramTypeId != networkDeviceSettingsGracePeriodParamTypeId) { + if (thing->thingClassId() == networkDeviceThingClassId) { + + MacAddress macAddress(thing->paramValue(networkDeviceThingMacAddressParamTypeId).toString()); + if (macAddress.isNull()) { + qCWarning(dcNetworkDetector()) << "Invalid mac address:" << thing->paramValue(networkDeviceThingMacAddressParamTypeId).toString(); + info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The configured MAC address is not valid.")); return; } - DeviceMonitor *monitor = m_monitors.key(thing); - if (monitor) { - monitor->setGracePeriod(value.toInt()); + + // Handle reconfigure + if (m_monitors.contains(thing)) { + hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); } - }); - if (!m_pluginTimer) { - m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(30); - connect(m_pluginTimer, &PluginTimer::timeout, m_broadcastPing, &BroadcastPing::run); + NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(macAddress); + m_monitors.insert(thing, monitor); + info->finish(Thing::ThingErrorNoError); - m_broadcastPing->run(); - } + QHostAddress cachedAddress; + if (!monitor->networkDeviceInfo().address().isNull()) { + cachedAddress = monitor->networkDeviceInfo().address(); + } else { + cachedAddress = QHostAddress(thing->stateValue(networkDeviceAddressStateTypeId).toString()); + } - info->finish(Thing::ThingErrorNoError); -} + // If the address is not known yet, let the monitor do the work and finish the setup... + if (cachedAddress.isNull()) { + setupMonitorConnections(thing, monitor); + thing->setStateValue(networkDeviceIsPresentStateTypeId, monitor->reachable()); + return; + } -void IntegrationPluginNetworkDetector::discoverThings(ThingDiscoveryInfo *info) -{ - Discovery *discovery = new Discovery(this); - discovery->discoverHosts(25); + // Make an initial ping in order to check if the device reachable state has changed compaired to the cached state + qCDebug(dcNetworkDetector()) << "Send initial ping to" << cachedAddress.toString() << "in order to get initial reachable state..."; + PingReply *pingReply = hardwareManager()->networkDeviceDiscovery()->ping(cachedAddress); + connect(pingReply, &PingReply::finished, this, [=](){ - // clean up discovery object when this discovery info is deleted - connect(info, &ThingDiscoveryInfo::destroyed, discovery, &Discovery::deleteLater); + qCDebug(dcNetworkDetector()) << "Initial ping finished" << pingReply->error(); - connect(discovery, &Discovery::finished, info, [this, info](const QList &hosts) { - qCDebug(dcNetworkDetector()) << "Discovery finished. Found" << hosts.count() << "devices"; - foreach (const Host &host, hosts) { - ThingDescriptor descriptor(networkDeviceThingClassId, host.hostName().isEmpty() ? host.address() : host.hostName(), host.address() + " (" + host.macAddress() + ")"); + // However the ping result is, the monitor has catched up internally with the reachable state... - foreach (Thing *existingThing, myThings()) { - if (existingThing->paramValue(networkDeviceThingMacAddressParamTypeId).toString() == host.macAddress()) { - descriptor.setThingId(existingThing->id()); - break; - } + if (!monitor->networkDeviceInfo().address().isNull()) + thing->setStateValue(networkDeviceAddressStateTypeId, monitor->networkDeviceInfo().address().toString()); + + thing->setStateValue(networkDeviceHostNameStateTypeId, monitor->networkDeviceInfo().hostName()); + thing->setStateValue(networkDeviceMacManufacturerNameStateTypeId, monitor->networkDeviceInfo().macAddressManufacturer()); + thing->setStateValue(networkDeviceNetworkInterfaceStateTypeId, monitor->networkDeviceInfo().networkInterface().name()); + thing->setStateValue(networkDeviceIsPresentStateTypeId, monitor->reachable()); + + setupMonitorConnections(thing, monitor); + + if (!monitor->lastSeen().isNull()) { + QDateTime minuteBased = QDateTime::fromMSecsSinceEpoch((monitor->lastSeen().toMSecsSinceEpoch() / 60000) * 60000); + thing->setStateValue(networkDeviceLastSeenTimeStateTypeId, minuteBased.toMSecsSinceEpoch() / 1000); } - ParamList params; - params << Param(networkDeviceThingMacAddressParamTypeId, host.macAddress()); - params << Param(networkDeviceThingAddressParamTypeId, host.address()); - descriptor.setParams(params); - - info->addThingDescriptor(descriptor); - - } - info->finish(Thing::ThingErrorNoError); - }); + }); + } else { + info->finish(Thing::ThingErrorThingClassNotFound); + } } void IntegrationPluginNetworkDetector::thingRemoved(Thing *thing) { - DeviceMonitor *monitor = m_monitors.key(thing); - m_monitors.remove(monitor); - delete monitor; - - if (m_monitors.isEmpty()) { - hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); - m_pluginTimer = nullptr; + if (m_monitors.contains(thing)) { + hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); + } + if (m_gracePeriodTimers.contains(thing)) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_gracePeriodTimers.take(thing)); } } -void IntegrationPluginNetworkDetector::broadcastPingFinished() +void IntegrationPluginNetworkDetector::executeAction(ThingActionInfo *info) { - foreach (DeviceMonitor *monitor, m_monitors.keys()) { - monitor->update(); + if (info->thing()->thingClassId() == networkDeviceThingClassId) { + + NetworkDeviceMonitor *monitor = m_monitors.value(info->thing()); + if (!monitor) { + qCWarning(dcNetworkDetector()) << "Could not execute action. There is no monitor registered for" << info->thing(); + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + + if (info->action().actionTypeId() == networkDevicePingActionTypeId) { + PingReply *pingReply = hardwareManager()->networkDeviceDiscovery()->ping(monitor->networkDeviceInfo().address()); + connect(pingReply, &PingReply::finished, info, [=](){ + if (pingReply->error() == PingReply::ErrorNoError) { + qCDebug(dcNetworkDetector()) << "Ping finished for" << monitor->networkDeviceInfo().address().toString() << pingReply->duration() << "ms"; + info->finish(Thing::ThingErrorNoError); + } else { + qCWarning(dcNetworkDetector()) << "Ping" << monitor->networkDeviceInfo().address().toString() << "finished with error" << pingReply->error(); + info->finish(Thing::ThingErrorHardwareNotAvailable); + } + }); + } else if (info->action().actionTypeId() == networkDeviceArpRequestActionTypeId) { + bool result = hardwareManager()->networkDeviceDiscovery()->sendArpRequest(monitor->networkDeviceInfo().address()); + if (result) { + qCDebug(dcNetworkDetector()) << "ARP request sent successfully to" << monitor->networkDeviceInfo().address().toString(); + info->finish(Thing::ThingErrorNoError); + } else { + qCWarning(dcNetworkDetector()) << "Failed to send ARP request to" << monitor->networkDeviceInfo().address().toString(); + info->finish(Thing::ThingErrorHardwareFailure); + } + } else if (info->action().actionTypeId() == networkDeviceLookupHostActionTypeId) { + int lookupId = QHostInfo::lookupHost(monitor->networkDeviceInfo().address().toString(), this, SLOT(onHostLookupFinished(QHostInfo))); + m_pendingHostLookup.insert(lookupId, info); + connect(info, &ThingActionInfo::aborted, this, [=](){ + m_pendingHostLookup.remove(lookupId); + }); + } } } -void IntegrationPluginNetworkDetector::deviceReachableChanged(bool reachable) +void IntegrationPluginNetworkDetector::setupMonitorConnections(Thing *thing, NetworkDeviceMonitor *monitor) { - DeviceMonitor *monitor = static_cast(sender()); - Thing *thing = m_monitors.value(monitor); - if (thing->stateValue(networkDeviceIsPresentStateTypeId).toBool() != reachable) { - qCDebug(dcNetworkDetector()) << "Device" << thing->name() << thing->paramValue(networkDeviceThingMacAddressParamTypeId).toString() << "reachable changed" << reachable; - thing->setStateValue(networkDeviceIsPresentStateTypeId, reachable); - } + connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){ + qCDebug(dcNetworkDetector()) << thing->name() << "monitor reachable changed to" << reachable; + if (reachable) { + thing->setStateValue(networkDeviceIsPresentStateTypeId, reachable); + // Remove any possible running grace priod timer + if (m_gracePeriodTimers.contains(thing)) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_gracePeriodTimers.take(thing)); + } + } else { + int gracePeriodSeconds = thing->setting(networkDeviceSettingsGracePeriodParamTypeId).toInt() * 60; + if (gracePeriodSeconds == 0) { + thing->setStateValue(networkDeviceIsPresentStateTypeId, reachable); + } else { + // Let's wait the grace period before setting not present + PluginTimer *timer = hardwareManager()->pluginTimerManager()->registerTimer(gracePeriodSeconds); + connect(timer, &PluginTimer::timeout, thing, [=](){ + if (thing->stateValue(networkDeviceIsPresentStateTypeId).toBool() && monitor->lastSeen().addSecs(gracePeriodSeconds) < QDateTime::currentDateTime()) { + qCDebug(dcNetworkDetector()) << thing->name() << "still not reachable after a grace period of" << gracePeriodSeconds << "seconds. Mark device as not reachable."; + thing->setStateValue(networkDeviceIsPresentStateTypeId, reachable); + hardwareManager()->pluginTimerManager()->unregisterTimer(m_gracePeriodTimers.take(thing)); + } + }); + + m_gracePeriodTimers.insert(thing, timer); + qCDebug(dcNetworkDetector()) << "Starting grace period timer" << m_gracePeriodTimers << "seconds"; + timer->start(); + } + } + }); + + connect(monitor, &NetworkDeviceMonitor::lastSeenChanged, thing, [=](const QDateTime &lastSeen){ + QDateTime minuteBased = QDateTime::fromMSecsSinceEpoch((monitor->lastSeen().toMSecsSinceEpoch() / 60000) * 60000); + qCDebug(dcNetworkDetector()) << thing << "last seen changed to" << lastSeen.toString() << minuteBased.toString(); + thing->setStateValue(networkDeviceLastSeenTimeStateTypeId, minuteBased.toMSecsSinceEpoch() / 1000); + }); + + connect(monitor, &NetworkDeviceMonitor::networkDeviceInfoChanged, thing, [=](const NetworkDeviceInfo &networkInfo){ + qCDebug(dcNetworkDetector()) << thing << "changed" << networkInfo; + thing->setStateValue(networkDeviceAddressStateTypeId, networkInfo.address().toString()); + thing->setStateValue(networkDeviceHostNameStateTypeId, networkInfo.hostName()); + thing->setStateValue(networkDeviceMacManufacturerNameStateTypeId, networkInfo.macAddressManufacturer()); + thing->setStateValue(networkDeviceNetworkInterfaceStateTypeId, monitor->networkDeviceInfo().networkInterface().name()); + }); } -void IntegrationPluginNetworkDetector::deviceAddressChanged(const QString &address) +void IntegrationPluginNetworkDetector::onHostLookupFinished(const QHostInfo &info) { - DeviceMonitor *monitor = static_cast(sender()); - Thing *thing = m_monitors.value(monitor); - if (thing->paramValue(networkDeviceThingAddressParamTypeId).toString() != address) { - qCDebug(dcNetworkDetector()) << "Device" << thing->name() << thing->paramValue(networkDeviceThingMacAddressParamTypeId).toString() << "changed IP address to" << address; - thing->setParamValue(networkDeviceThingAddressParamTypeId, address); + ThingActionInfo *actionInfo = m_pendingHostLookup.take(info.lookupId()); + if (!actionInfo) { + qCWarning(dcNetworkDetector()) << "Host loopup finished for" << info.addresses() << info.hostName() << "but the action does not exist any more."; + return; } -} -void IntegrationPluginNetworkDetector::deviceSeen() -{ - DeviceMonitor *monitor = static_cast(sender()); - Thing *thing = m_monitors.value(monitor); - QDateTime oldLastSeen = QDateTime::fromTime_t(thing->stateValue(networkDeviceLastSeenTimeStateTypeId).toInt()); - if (oldLastSeen.addSecs(60) < QDateTime::currentDateTime()) { - thing->setStateValue(networkDeviceLastSeenTimeStateTypeId, QDateTime::currentDateTime().toTime_t()); + qCDebug(dcNetworkDetector()) << "Host lookup finished" << info.addresses() << info.hostName() << info.error(); + if (info.error() != QHostInfo::NoError) { + qCWarning(dcNetworkDetector()) << "Error occured during host lookup:" << info.errorString(); + actionInfo->finish(Thing::ThingErrorHardwareFailure); + } else { + actionInfo->finish(Thing::ThingErrorNoError); } } diff --git a/networkdetector/integrationpluginnetworkdetector.h b/networkdetector/integrationpluginnetworkdetector.h index a5f0c59c..2fd1a2a0 100644 --- a/networkdetector/integrationpluginnetworkdetector.h +++ b/networkdetector/integrationpluginnetworkdetector.h @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2020, nymea GmbH +* Copyright 2013 - 2022, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -31,15 +31,10 @@ #ifndef INTEGRATIONPLUGINNETWORKDETECTOR_H #define INTEGRATIONPLUGINNETWORKDETECTOR_H -#include "integrations/integrationplugin.h" -#include "host.h" -#include "discovery.h" -#include "plugintimer.h" -#include "devicemonitor.h" -#include "broadcastping.h" +#include +#include +#include -#include -#include #include class IntegrationPluginNetworkDetector : public IntegrationPlugin @@ -54,23 +49,22 @@ public: ~IntegrationPluginNetworkDetector(); void init() override; - - void setupThing(ThingSetupInfo *info) override; void discoverThings(ThingDiscoveryInfo *info) override; + void setupThing(ThingSetupInfo *info) override; void thingRemoved(Thing *thing) override; - -private slots: - void deviceReachableChanged(bool reachable); - void deviceAddressChanged(const QString &address); - void deviceSeen(); - - void broadcastPingFinished(); + void executeAction(ThingActionInfo *info) override; private: - PluginTimer *m_pluginTimer = nullptr; - BroadcastPing *m_broadcastPing = nullptr; - QHash m_monitors; + QHash m_monitors; + QHash m_gracePeriodTimers; + QHash m_pendingHostLookup; + + void setupMonitorConnections(Thing *thing, NetworkDeviceMonitor *monitor); + +private slots: + void onHostLookupFinished(const QHostInfo &info); + }; #endif // INTEGRATIONPLUGINNETWORKDETECTOR_H diff --git a/networkdetector/integrationpluginnetworkdetector.json b/networkdetector/integrationpluginnetworkdetector.json index 303cc28e..043a4e61 100644 --- a/networkdetector/integrationpluginnetworkdetector.json +++ b/networkdetector/integrationpluginnetworkdetector.json @@ -15,19 +15,12 @@ "interfaces": [ "presencesensor" ], "createMethods": ["user", "discovery"], "paramTypes": [ - { - "id": "c6707093-3b51-469d-9fc0-f167bff2a987", - "name": "address", - "displayName": "address", - "type": "QString", - "inputType": "TextLine" - }, { "id": "18fd3b05-478a-49cf-b8ae-3c6a98675ccc", "name": "macAddress", - "displayName": "hardware address", + "displayName": "MAC address", "type": "QString", - "inputType": "TextLine" + "inputType": "MacAddress" } ], "settingsTypes": [ @@ -40,13 +33,50 @@ } ], "stateTypes": [ + { + "id": "acee9260-4d01-471a-85c5-4d6116b35ff1", + "name": "address", + "displayName": "IP address", + "displayNameEvent": "IP address changed", + "type": "QString", + "defaultValue": "127.0.0.1", + "cached": true + }, + { + "id": "29c65dcf-090e-4316-8554-68f038a8416f", + "name": "hostName", + "displayName": "Host name", + "displayNameEvent": "Host name changed", + "type": "QString", + "defaultValue": "", + "cached": true + }, + { + "id": "395623b2-5b25-4582-803e-61cd6d40844c", + "name": "macManufacturerName", + "displayName": "MAC manufacturer name", + "displayNameEvent": "MAC manufacturer name changed", + "type": "QString", + "defaultValue": "", + "cached": true + }, + { + "id": "412f0e24-26e7-450b-8e60-bfaf938ea23e", + "name": "networkInterface", + "displayName": "Network interface", + "displayNameEvent": "Network interface changed", + "type": "QString", + "defaultValue": "", + "cached": true + }, { "id": "cb43e1b5-4f61-4538-bfa2-c33055c542cf", "name": "isPresent", "displayName": "Device is present", "displayNameEvent": "Device is present changed", "type": "bool", - "defaultValue": false + "defaultValue": false, + "cached": true }, { "id": "b51d54c9-cce1-43f0-a35d-52fc2d8d302c", @@ -55,7 +85,25 @@ "displayNameEvent": "Last seen time changed", "type": "int", "unit": "UnixTime", - "defaultValue": 0 + "defaultValue": 0, + "cached": true + } + ], + "actionTypes": [ + { + "id": "e98793b6-2dad-4090-91db-77f8493b4e45", + "name": "ping", + "displayName": "Ping device" + }, + { + "id": "2ca3f99f-7315-4f17-b48d-99f0c151a62c", + "name": "arpRequest", + "displayName": "Send ARP request" + }, + { + "id": "9797dac5-d99a-4bcf-a99a-5b9356bbce76", + "name": "lookupHost", + "displayName": "Lookup host name" } ] } diff --git a/networkdetector/networkdetector.pro b/networkdetector/networkdetector.pro index dc73ff1a..b0e50179 100644 --- a/networkdetector/networkdetector.pro +++ b/networkdetector/networkdetector.pro @@ -5,17 +5,9 @@ QT += network TARGET = $$qtLibraryTarget(nymea_integrationpluginnetworkdetector) SOURCES += \ - integrationpluginnetworkdetector.cpp \ - host.cpp \ - discovery.cpp \ - devicemonitor.cpp \ - broadcastping.cpp + integrationpluginnetworkdetector.cpp HEADERS += \ - integrationpluginnetworkdetector.h \ - host.h \ - discovery.h \ - devicemonitor.h \ - broadcastping.h + integrationpluginnetworkdetector.h diff --git a/networkdetector/translations/8e0f791e-b273-4267-8605-b7c2f55a68ab-de.ts b/networkdetector/translations/8e0f791e-b273-4267-8605-b7c2f55a68ab-de.ts index 390f81de..29d50199 100644 --- a/networkdetector/translations/8e0f791e-b273-4267-8605-b7c2f55a68ab-de.ts +++ b/networkdetector/translations/8e0f791e-b273-4267-8605-b7c2f55a68ab-de.ts @@ -1,72 +1,97 @@ + + IntegrationPluginNetworkDetector + + + Unable to discovery devices in your network. + + + + + The configured MAC address is not valid. + + + NetworkDetector + + + Host name + The name of the StateType ({29c65dcf-090e-4316-8554-68f038a8416f}) of ThingClass networkDevice + + + + + IP address + The name of the StateType ({acee9260-4d01-471a-85c5-4d6116b35ff1}) of ThingClass networkDevice + + + + + Lookup host name + The name of the ActionType ({9797dac5-d99a-4bcf-a99a-5b9356bbce76}) of ThingClass networkDevice + + + MAC address + The name of the ParamType (ThingClass: networkDevice, Type: thing, ID: {18fd3b05-478a-49cf-b8ae-3c6a98675ccc}) + + + + + MAC manufacturer name + The name of the StateType ({395623b2-5b25-4582-803e-61cd6d40844c}) of ThingClass networkDevice + + + + Network Detector The name of the plugin NetworkDetector ({8e0f791e-b273-4267-8605-b7c2f55a68ab}) Netzwerk Detektor + Network interface + The name of the StateType ({412f0e24-26e7-450b-8e60-bfaf938ea23e}) of ThingClass networkDevice + + + + + Ping device + The name of the ActionType ({e98793b6-2dad-4090-91db-77f8493b4e45}) of ThingClass networkDevice + + + + + Send ARP request + The name of the ActionType ({2ca3f99f-7315-4f17-b48d-99f0c151a62c}) of ThingClass networkDevice + + + + nymea The name of the vendor ({2062d64d-3232-433c-88bc-0d33c0ba2ba6}) nymea - + Network Device The name of the ThingClass ({bd216356-f1ec-4324-9785-6982d2174e17}) Netzwerkgerät - - - address - The name of the ParamType (ThingClass: networkDevice, Type: thing, ID: {c6707093-3b51-469d-9fc0-f167bff2a987}) - Adresse - - - - hardware address - The name of the ParamType (ThingClass: networkDevice, Type: thing, ID: {18fd3b05-478a-49cf-b8ae-3c6a98675ccc}) - Geräteadresse - - - - Grace period (Minutes) - The name of the ParamType (ThingClass: networkDevice, Type: settings, ID: {6c1ec0c8-6a02-4b3c-9064-ee33cfd61fbe}) - Gnadenfrist (Minuten) - - Device is present changed - The name of the EventType ({cb43e1b5-4f61-4538-bfa2-c33055c542cf}) of ThingClass networkDevice - Gerät anwesend geändert - - - - Device is present - The name of the ParamType (ThingClass: networkDevice, EventType: isPresent, ID: {cb43e1b5-4f61-4538-bfa2-c33055c542cf}) ----------- -The name of the StateType ({cb43e1b5-4f61-4538-bfa2-c33055c542cf}) of ThingClass networkDevice + The name of the StateType ({cb43e1b5-4f61-4538-bfa2-c33055c542cf}) of ThingClass networkDevice Gerät ist anwesend - - Last seen time changed - The name of the EventType ({b51d54c9-cce1-43f0-a35d-52fc2d8d302c}) of ThingClass networkDevice - Zuletzt gesehen geändert - - - Last seen time - The name of the ParamType (ThingClass: networkDevice, EventType: lastSeenTime, ID: {b51d54c9-cce1-43f0-a35d-52fc2d8d302c}) ----------- -The name of the StateType ({b51d54c9-cce1-43f0-a35d-52fc2d8d302c}) of ThingClass networkDevice + The name of the StateType ({b51d54c9-cce1-43f0-a35d-52fc2d8d302c}) of ThingClass networkDevice Zuletzt gesehen geändert diff --git a/networkdetector/translations/8e0f791e-b273-4267-8605-b7c2f55a68ab-en_US.ts b/networkdetector/translations/8e0f791e-b273-4267-8605-b7c2f55a68ab-en_US.ts index 9ecf1d8a..1e6bb856 100644 --- a/networkdetector/translations/8e0f791e-b273-4267-8605-b7c2f55a68ab-en_US.ts +++ b/networkdetector/translations/8e0f791e-b273-4267-8605-b7c2f55a68ab-en_US.ts @@ -1,72 +1,97 @@ + + IntegrationPluginNetworkDetector + + + Unable to discovery devices in your network. + + + + + The configured MAC address is not valid. + + + NetworkDetector + + + Host name + The name of the StateType ({29c65dcf-090e-4316-8554-68f038a8416f}) of ThingClass networkDevice + + + + + IP address + The name of the StateType ({acee9260-4d01-471a-85c5-4d6116b35ff1}) of ThingClass networkDevice + + + + + Lookup host name + The name of the ActionType ({9797dac5-d99a-4bcf-a99a-5b9356bbce76}) of ThingClass networkDevice + + + MAC address + The name of the ParamType (ThingClass: networkDevice, Type: thing, ID: {18fd3b05-478a-49cf-b8ae-3c6a98675ccc}) + + + + + MAC manufacturer name + The name of the StateType ({395623b2-5b25-4582-803e-61cd6d40844c}) of ThingClass networkDevice + + + + Network Detector The name of the plugin NetworkDetector ({8e0f791e-b273-4267-8605-b7c2f55a68ab}) + Network interface + The name of the StateType ({412f0e24-26e7-450b-8e60-bfaf938ea23e}) of ThingClass networkDevice + + + + + Ping device + The name of the ActionType ({e98793b6-2dad-4090-91db-77f8493b4e45}) of ThingClass networkDevice + + + + + Send ARP request + The name of the ActionType ({2ca3f99f-7315-4f17-b48d-99f0c151a62c}) of ThingClass networkDevice + + + + nymea The name of the vendor ({2062d64d-3232-433c-88bc-0d33c0ba2ba6}) - + Network Device The name of the ThingClass ({bd216356-f1ec-4324-9785-6982d2174e17}) - - - address - The name of the ParamType (ThingClass: networkDevice, Type: thing, ID: {c6707093-3b51-469d-9fc0-f167bff2a987}) - - - - - hardware address - The name of the ParamType (ThingClass: networkDevice, Type: thing, ID: {18fd3b05-478a-49cf-b8ae-3c6a98675ccc}) - - - - - Grace period (Minutes) - The name of the ParamType (ThingClass: networkDevice, Type: settings, ID: {6c1ec0c8-6a02-4b3c-9064-ee33cfd61fbe}) - - - Device is present changed - The name of the EventType ({cb43e1b5-4f61-4538-bfa2-c33055c542cf}) of ThingClass networkDevice - - - - - Device is present - The name of the ParamType (ThingClass: networkDevice, EventType: isPresent, ID: {cb43e1b5-4f61-4538-bfa2-c33055c542cf}) ----------- -The name of the StateType ({cb43e1b5-4f61-4538-bfa2-c33055c542cf}) of ThingClass networkDevice + The name of the StateType ({cb43e1b5-4f61-4538-bfa2-c33055c542cf}) of ThingClass networkDevice - - Last seen time changed - The name of the EventType ({b51d54c9-cce1-43f0-a35d-52fc2d8d302c}) of ThingClass networkDevice - - - - Last seen time - The name of the ParamType (ThingClass: networkDevice, EventType: lastSeenTime, ID: {b51d54c9-cce1-43f0-a35d-52fc2d8d302c}) ----------- -The name of the StateType ({b51d54c9-cce1-43f0-a35d-52fc2d8d302c}) of ThingClass networkDevice + The name of the StateType ({b51d54c9-cce1-43f0-a35d-52fc2d8d302c}) of ThingClass networkDevice