Replace network device detector with internal monitor

This commit is contained in:
Simon Stürz 2022-04-06 12:01:01 +02:00
parent 1f411c2244
commit 9828e6ec11
16 changed files with 344 additions and 1224 deletions

3
debian/control vendored
View File

@ -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

View File

@ -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/

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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 <QNetworkInterface>
#include <QProcess>
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<QProcess*>(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();
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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 <QObject>
#include <QHash>
#include <QProcess>
#include <QNetworkAddressEntry>
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<QProcess*, QNetworkAddressEntry> m_runningPings;
};
#endif // BROADCASTPING_H

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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 <QNetworkInterface>
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;
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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 <QObject>
#include <QProcess>
#include <QDateTime>
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

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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 <QDebug>
#include <QXmlStreamReader>
#include <QNetworkInterface>
#include <QHostInfo>
#include <QTimer>
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<QProcess*>(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<QProcess*>(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<QHostAddress, int> 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<Host> 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);
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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 <QObject>
#include <QProcess>
#include <QHostInfo>
#include <QTimer>
#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<Host> 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<QProcess*> m_discoveryProcesses;
QTimer m_timeoutTimer;
QHash<QProcess*, Host*> m_pendingArpLookups;
QHash<QString, Host*> m_pendingNameLookups;
QList<Host*> m_scanResults;
};
#endif // DISCOVERY_H

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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<Host>();
qRegisterMetaType<QList<Host> >();
}
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();
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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 <QString>
#include <QDebug>
#include <QDateTime>
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

View File

@ -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 <QDebug>
@ -38,131 +37,187 @@
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, this, [=](){
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 &paramTypeId, 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());
}
});
if (!m_pluginTimer) {
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(30);
connect(m_pluginTimer, &PluginTimer::timeout, m_broadcastPing, &BroadcastPing::run);
NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(macAddress);
connect(monitor, &NetworkDeviceMonitor::reachableChanged, this, [=](bool reachable){
qCDebug(dcNetworkDetector()) << thing << "reachable changed to" << reachable;
thing->setStateValue(networkDeviceIsPresentStateTypeId, reachable);
});
m_broadcastPing->run();
connect(monitor, &NetworkDeviceMonitor::lastSeenChanged, this, [=](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, this, [=](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());
});
m_monitors.insert(thing, monitor);
info->finish(Thing::ThingErrorNoError);
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());
if (!monitor->lastSeen().isNull()) {
QDateTime minuteBased = QDateTime::fromMSecsSinceEpoch((monitor->lastSeen().toMSecsSinceEpoch() / 60000) * 60000);
thing->setStateValue(networkDeviceLastSeenTimeStateTypeId, minuteBased.toMSecsSinceEpoch() / 1000); }
return;
}
info->finish(Thing::ThingErrorNoError);
}
void IntegrationPluginNetworkDetector::discoverThings(ThingDiscoveryInfo *info)
{
Discovery *discovery = new Discovery(this);
discovery->discoverHosts(25);
// 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<Host> &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() + ")");
foreach (Thing *existingThing, myThings()) {
if (existingThing->paramValue(networkDeviceThingMacAddressParamTypeId).toString() == host.macAddress()) {
descriptor.setThingId(existingThing->id());
break;
}
}
ParamList params;
params << Param(networkDeviceThingMacAddressParamTypeId, host.macAddress());
params << Param(networkDeviceThingAddressParamTypeId, host.address());
descriptor.setParams(params);
info->addThingDescriptor(descriptor);
}
info->finish(Thing::ThingErrorNoError);
});
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));
}
}
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::onHostLookupFinished(const QHostInfo &info)
{
DeviceMonitor *monitor = static_cast<DeviceMonitor*>(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);
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::deviceAddressChanged(const QString &address)
{
DeviceMonitor *monitor = static_cast<DeviceMonitor*>(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);
}
}
void IntegrationPluginNetworkDetector::deviceSeen()
{
DeviceMonitor *monitor = static_cast<DeviceMonitor*>(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);
}
}

View File

@ -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,9 @@
#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 <integrations/integrationplugin.h>
#include <network/networkdevicediscovery.h>
#include <QProcess>
#include <QXmlStreamReader>
#include <QHostInfo>
class IntegrationPluginNetworkDetector : public IntegrationPlugin
@ -54,23 +48,19 @@ 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<DeviceMonitor*, Thing*> m_monitors;
QHash<Thing *, NetworkDeviceMonitor *> m_monitors;
QHash<int, ThingActionInfo *> m_pendingHostLookup;
private slots:
void onHostLookupFinished(const QHostInfo &info);
};
#endif // INTEGRATIONPLUGINNETWORKDETECTOR_H

View File

@ -15,31 +15,47 @@
"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"
}
],
"settingsTypes": [
{
"id": "6c1ec0c8-6a02-4b3c-9064-ee33cfd61fbe",
"name": "gracePeriod",
"displayName": "Grace period (Minutes)",
"type": "uint",
"defaultValue": 5
"inputType": "MacAddress"
}
],
"stateTypes": [
{
"id": "acee9260-4d01-471a-85c5-4d6116b35ff1",
"name": "address",
"displayName": "IP address",
"displayNameEvent": "IP address changed",
"type": "QString",
"defaultValue": "127.0.0.1"
},
{
"id": "29c65dcf-090e-4316-8554-68f038a8416f",
"name": "hostName",
"displayName": "Host name",
"displayNameEvent": "Host name changed",
"type": "QString",
"defaultValue": ""
},
{
"id": "395623b2-5b25-4582-803e-61cd6d40844c",
"name": "macManufacturerName",
"displayName": "MAC manufacturer name",
"displayNameEvent": "MAC manufacturer name changed",
"type": "QString",
"defaultValue": ""
},
{
"id": "412f0e24-26e7-450b-8e60-bfaf938ea23e",
"name": "networkInterface",
"displayName": "Network interface",
"displayNameEvent": "Network interface changed",
"type": "QString",
"defaultValue": ""
},
{
"id": "cb43e1b5-4f61-4538-bfa2-c33055c542cf",
"name": "isPresent",
@ -57,6 +73,23 @@
"unit": "UnixTime",
"defaultValue": 0
}
],
"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"
}
]
}
]

View File

@ -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

View File

@ -1,72 +1,97 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="de">
<context>
<name>IntegrationPluginNetworkDetector</name>
<message>
<location filename="../integrationpluginnetworkdetector.cpp" line="58"/>
<source>Unable to discovery devices in your network.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../integrationpluginnetworkdetector.cpp" line="119"/>
<source>The configured MAC address is not valid.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>NetworkDetector</name>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="41"/>
<source>Host name</source>
<extracomment>The name of the StateType ({29c65dcf-090e-4316-8554-68f038a8416f}) of ThingClass networkDevice</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="44"/>
<source>IP address</source>
<extracomment>The name of the StateType ({acee9260-4d01-471a-85c5-4d6116b35ff1}) of ThingClass networkDevice</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="50"/>
<source>Lookup host name</source>
<extracomment>The name of the ActionType ({9797dac5-d99a-4bcf-a99a-5b9356bbce76}) of ThingClass networkDevice</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="53"/>
<source>MAC address</source>
<extracomment>The name of the ParamType (ThingClass: networkDevice, Type: thing, ID: {18fd3b05-478a-49cf-b8ae-3c6a98675ccc})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="56"/>
<source>MAC manufacturer name</source>
<extracomment>The name of the StateType ({395623b2-5b25-4582-803e-61cd6d40844c}) of ThingClass networkDevice</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="59"/>
<source>Network Detector</source>
<extracomment>The name of the plugin NetworkDetector ({8e0f791e-b273-4267-8605-b7c2f55a68ab})</extracomment>
<translation>Netzwerk Detektor</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="65"/>
<source>Network interface</source>
<extracomment>The name of the StateType ({412f0e24-26e7-450b-8e60-bfaf938ea23e}) of ThingClass networkDevice</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="68"/>
<source>Ping device</source>
<extracomment>The name of the ActionType ({e98793b6-2dad-4090-91db-77f8493b4e45}) of ThingClass networkDevice</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="71"/>
<source>Send ARP request</source>
<extracomment>The name of the ActionType ({2ca3f99f-7315-4f17-b48d-99f0c151a62c}) of ThingClass networkDevice</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="74"/>
<source>nymea</source>
<extracomment>The name of the vendor ({2062d64d-3232-433c-88bc-0d33c0ba2ba6})</extracomment>
<translation>nymea</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="56"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="62"/>
<source>Network Device</source>
<extracomment>The name of the ThingClass ({bd216356-f1ec-4324-9785-6982d2174e17})</extracomment>
<translation>Netzwerkgerät</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="59"/>
<source>address</source>
<extracomment>The name of the ParamType (ThingClass: networkDevice, Type: thing, ID: {c6707093-3b51-469d-9fc0-f167bff2a987})</extracomment>
<translation>Adresse</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="62"/>
<source>hardware address</source>
<extracomment>The name of the ParamType (ThingClass: networkDevice, Type: thing, ID: {18fd3b05-478a-49cf-b8ae-3c6a98675ccc})</extracomment>
<translation>Geräteadresse</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="41"/>
<source>Grace period (Minutes)</source>
<extracomment>The name of the ParamType (ThingClass: networkDevice, Type: settings, ID: {6c1ec0c8-6a02-4b3c-9064-ee33cfd61fbe})</extracomment>
<translation>Gnadenfrist (Minuten)</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="38"/>
<source>Device is present changed</source>
<extracomment>The name of the EventType ({cb43e1b5-4f61-4538-bfa2-c33055c542cf}) of ThingClass networkDevice</extracomment>
<translation>Gerät anwesend geändert</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="32"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="35"/>
<source>Device is present</source>
<extracomment>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</extracomment>
<extracomment>The name of the StateType ({cb43e1b5-4f61-4538-bfa2-c33055c542cf}) of ThingClass networkDevice</extracomment>
<translation>Gerät ist anwesend</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="50"/>
<source>Last seen time changed</source>
<extracomment>The name of the EventType ({b51d54c9-cce1-43f0-a35d-52fc2d8d302c}) of ThingClass networkDevice</extracomment>
<translation>Zuletzt gesehen geändert</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="44"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="47"/>
<source>Last seen time</source>
<extracomment>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</extracomment>
<extracomment>The name of the StateType ({b51d54c9-cce1-43f0-a35d-52fc2d8d302c}) of ThingClass networkDevice</extracomment>
<translation>Zuletzt gesehen geändert</translation>
</message>
</context>

View File

@ -1,72 +1,97 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>IntegrationPluginNetworkDetector</name>
<message>
<location filename="../integrationpluginnetworkdetector.cpp" line="58"/>
<source>Unable to discovery devices in your network.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../integrationpluginnetworkdetector.cpp" line="119"/>
<source>The configured MAC address is not valid.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>NetworkDetector</name>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="41"/>
<source>Host name</source>
<extracomment>The name of the StateType ({29c65dcf-090e-4316-8554-68f038a8416f}) of ThingClass networkDevice</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="44"/>
<source>IP address</source>
<extracomment>The name of the StateType ({acee9260-4d01-471a-85c5-4d6116b35ff1}) of ThingClass networkDevice</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="50"/>
<source>Lookup host name</source>
<extracomment>The name of the ActionType ({9797dac5-d99a-4bcf-a99a-5b9356bbce76}) of ThingClass networkDevice</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="53"/>
<source>MAC address</source>
<extracomment>The name of the ParamType (ThingClass: networkDevice, Type: thing, ID: {18fd3b05-478a-49cf-b8ae-3c6a98675ccc})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="56"/>
<source>MAC manufacturer name</source>
<extracomment>The name of the StateType ({395623b2-5b25-4582-803e-61cd6d40844c}) of ThingClass networkDevice</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="59"/>
<source>Network Detector</source>
<extracomment>The name of the plugin NetworkDetector ({8e0f791e-b273-4267-8605-b7c2f55a68ab})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="65"/>
<source>Network interface</source>
<extracomment>The name of the StateType ({412f0e24-26e7-450b-8e60-bfaf938ea23e}) of ThingClass networkDevice</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="68"/>
<source>Ping device</source>
<extracomment>The name of the ActionType ({e98793b6-2dad-4090-91db-77f8493b4e45}) of ThingClass networkDevice</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="71"/>
<source>Send ARP request</source>
<extracomment>The name of the ActionType ({2ca3f99f-7315-4f17-b48d-99f0c151a62c}) of ThingClass networkDevice</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="74"/>
<source>nymea</source>
<extracomment>The name of the vendor ({2062d64d-3232-433c-88bc-0d33c0ba2ba6})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="56"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="62"/>
<source>Network Device</source>
<extracomment>The name of the ThingClass ({bd216356-f1ec-4324-9785-6982d2174e17})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="59"/>
<source>address</source>
<extracomment>The name of the ParamType (ThingClass: networkDevice, Type: thing, ID: {c6707093-3b51-469d-9fc0-f167bff2a987})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="62"/>
<source>hardware address</source>
<extracomment>The name of the ParamType (ThingClass: networkDevice, Type: thing, ID: {18fd3b05-478a-49cf-b8ae-3c6a98675ccc})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="41"/>
<source>Grace period (Minutes)</source>
<extracomment>The name of the ParamType (ThingClass: networkDevice, Type: settings, ID: {6c1ec0c8-6a02-4b3c-9064-ee33cfd61fbe})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="38"/>
<source>Device is present changed</source>
<extracomment>The name of the EventType ({cb43e1b5-4f61-4538-bfa2-c33055c542cf}) of ThingClass networkDevice</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="32"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="35"/>
<source>Device is present</source>
<extracomment>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</extracomment>
<extracomment>The name of the StateType ({cb43e1b5-4f61-4538-bfa2-c33055c542cf}) of ThingClass networkDevice</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="50"/>
<source>Last seen time changed</source>
<extracomment>The name of the EventType ({b51d54c9-cce1-43f0-a35d-52fc2d8d302c}) of ThingClass networkDevice</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="44"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/networkdetector/plugininfo.h" line="47"/>
<source>Last seen time</source>
<extracomment>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</extracomment>
<extracomment>The name of the StateType ({b51d54c9-cce1-43f0-a35d-52fc2d8d302c}) of ThingClass networkDevice</extracomment>
<translation type="unfinished"></translation>
</message>
</context>