Merge PR #689: Rework network discovery

This commit is contained in:
jenkins 2025-03-20 14:15:07 +01:00
commit 95ebffee0c
47 changed files with 1885 additions and 538 deletions

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright 2013 - 2022, nymea GmbH * Copyright 2013 - 2024, nymea GmbH
* Contact: contact@nymea.io * Contact: contact@nymea.io
* *
* This file is part of nymea. * This file is part of nymea.
@ -32,12 +32,15 @@
#include "nymeasettings.h" #include "nymeasettings.h"
#include "loggingcategories.h" #include "loggingcategories.h"
#include <math.h> #include <math.h>
#include <network/ping.h> #include <network/ping.h>
#include <network/arpsocket.h> #include <network/arpsocket.h>
#include <network/networkutils.h> #include <network/networkutils.h>
#define CACHE_VERSION 1
NYMEA_LOGGING_CATEGORY(dcNetworkDeviceDiscovery, "NetworkDeviceDiscovery") NYMEA_LOGGING_CATEGORY(dcNetworkDeviceDiscovery, "NetworkDeviceDiscovery")
namespace nymeaserver { namespace nymeaserver {
@ -86,7 +89,7 @@ NetworkDeviceDiscoveryImpl::NetworkDeviceDiscoveryImpl(QObject *parent) :
// Timer for updating the monitors // Timer for updating the monitors
m_monitorTimer = new QTimer(this); m_monitorTimer = new QTimer(this);
m_monitorTimer->setInterval(5000); m_monitorTimer->setInterval(10000);
m_monitorTimer->setSingleShot(false); m_monitorTimer->setSingleShot(false);
connect(m_monitorTimer, &QTimer::timeout, this, &NetworkDeviceDiscoveryImpl::evaluateMonitors); connect(m_monitorTimer, &QTimer::timeout, this, &NetworkDeviceDiscoveryImpl::evaluateMonitors);
@ -128,19 +131,19 @@ NetworkDeviceDiscoveryReply *NetworkDeviceDiscoveryImpl::discover()
} else { } else {
qCDebug(dcNetworkDeviceDiscovery()) << "Starting internally a new discovery."; qCDebug(dcNetworkDeviceDiscovery()) << "Starting internally a new discovery.";
m_currentDiscoveryReply = new NetworkDeviceDiscoveryReplyImpl(this); m_currentDiscoveryReply = new NetworkDeviceDiscoveryReplyImpl(this);
connect(m_currentDiscoveryReply, &NetworkDeviceDiscoveryReplyImpl::networkDeviceInfoAdded, this, &NetworkDeviceDiscoveryImpl::updateCache); // connect(m_currentDiscoveryReply, &NetworkDeviceDiscoveryReplyImpl::networkDeviceInfoAdded, this, &NetworkDeviceDiscoveryImpl::updateCache);
connect(m_currentDiscoveryReply, &NetworkDeviceDiscoveryReplyImpl::finished, this, [this](){ connect(m_currentDiscoveryReply, &NetworkDeviceDiscoveryReplyImpl::finished, this, [this](){
// Finish all pending replies // Finish all pending replies
foreach (NetworkDeviceDiscoveryReplyImpl *reply, m_pendingDiscoveryReplies) { foreach (NetworkDeviceDiscoveryReplyImpl *reply, m_pendingDiscoveryReplies) {
// Sync all network device infos with all pending replies // Sync all network device infos with all pending replies
foreach (const NetworkDeviceInfo &info, m_currentDiscoveryReply->networkDeviceInfos()) { foreach (const NetworkDeviceInfo &info, m_currentDiscoveryReply->networkDeviceInfos()) {
reply->addCompleteNetworkDeviceInfo(info); reply->addCompleteNetworkDeviceInfo(info);
} }
}
foreach (const NetworkDeviceInfo &info, m_currentDiscoveryReply->virtualNetworkDeviceInfos()) { // Update local cache right after a discovery finished
reply->addVirtualNetworkDeviceInfo(info); foreach (const NetworkDeviceInfo &info, m_currentDiscoveryReply->networkDeviceInfos()) {
} updateCache(info);
} }
// Delete the current reply before finishing the pending replies. // Delete the current reply before finishing the pending replies.
@ -159,8 +162,6 @@ NetworkDeviceDiscoveryReply *NetworkDeviceDiscoveryImpl::discover()
// Create the reply for the user // Create the reply for the user
NetworkDeviceDiscoveryReplyImpl *reply = new NetworkDeviceDiscoveryReplyImpl(this); NetworkDeviceDiscoveryReplyImpl *reply = new NetworkDeviceDiscoveryReplyImpl(this);
connect(m_currentDiscoveryReply, &NetworkDeviceDiscoveryReplyImpl::networkDeviceInfoAdded, reply, &NetworkDeviceDiscoveryReplyImpl::addCompleteNetworkDeviceInfo);
connect(m_currentDiscoveryReply, &NetworkDeviceDiscoveryReplyImpl::hostAddressDiscovered, reply, &NetworkDeviceDiscoveryReplyImpl::hostAddressDiscovered);
m_pendingDiscoveryReplies.append(reply); m_pendingDiscoveryReplies.append(reply);
if (!available()) { if (!available()) {
@ -171,19 +172,23 @@ NetworkDeviceDiscoveryReply *NetworkDeviceDiscoveryImpl::discover()
} }
if (alreadyRunning) { if (alreadyRunning) {
// Add already discovered network device infos in the next event loop // Emit allready discovered hosts so any integration can implement safly a connect on the host hostAddressDiscovered signal
// so any connections after this method call will work as expected // even if the internal discvoery was already running
QTimer::singleShot(0, reply, [this, reply](){ QTimer::singleShot(0, reply, [this, reply](){
if (!m_currentDiscoveryReply) if (!m_currentDiscoveryReply)
return; return;
foreach (const NetworkDeviceInfo &networkDeviceInfo, m_currentDiscoveryReply->networkDeviceInfos()) { foreach (const QHostAddress &address, m_currentDiscoveryReply->currentCache().keys()) {
reply->addCompleteNetworkDeviceInfo(networkDeviceInfo); reply->hostAddressDiscovered(address);
} }
connect(m_currentDiscoveryReply, &NetworkDeviceDiscoveryReplyImpl::hostAddressDiscovered, reply, &NetworkDeviceDiscoveryReplyImpl::hostAddressDiscovered);
}); });
} else { } else {
qCInfo(dcNetworkDeviceDiscovery()) << "Starting network device discovery ..."; qCInfo(dcNetworkDeviceDiscovery()) << "Starting network device discovery ...";
connect(m_currentDiscoveryReply, &NetworkDeviceDiscoveryReplyImpl::hostAddressDiscovered, reply, &NetworkDeviceDiscoveryReplyImpl::hostAddressDiscovered);
if (m_ping->available()) if (m_ping->available())
pingAllNetworkDevices(); pingAllNetworkDevices();
@ -213,74 +218,101 @@ bool NetworkDeviceDiscoveryImpl::running() const
return m_running; return m_running;
} }
NetworkDeviceMonitor *NetworkDeviceDiscoveryImpl::registerMonitor(const MacAddress &macAddress) NetworkDeviceMonitor *NetworkDeviceDiscoveryImpl::registerMonitor(Thing *thing)
{ {
if (macAddress.isNull()) { if (!thing->thingClass().interfaces().contains("networkdevice")) {
qCWarning(dcNetworkDeviceDiscovery()) << "Could not register monitor for invalid" << macAddress; qCWarning(dcNetworkDeviceDiscovery()) << "Cannot register network device monitor because the thing"
<< thing << "does not implement the \"networkdevice\" interface.";
return nullptr; return nullptr;
} }
// Make sure we create only one monitor per MAC and keep track how many user MacAddress macAddress(thing->paramValue("macAddress").toString());
// have access to this monitor otherwise an unregister could cause a crash in QString hostName = thing->paramValue("hostName").toString();
// an other plugin plugin which might still need it QHostAddress address(thing->paramValue("address").toString());
if (m_monitors.contains(macAddress)) {
m_monitorsReferenceCount[macAddress] += 1;
qCInfo(dcNetworkDeviceDiscovery()) << "Register network device monitor for" << macAddress << "which already exists. Returning existing monitor having now" << m_monitorsReferenceCount[macAddress] << "references.";
return m_monitors.value(macAddress);
}
qCInfo(dcNetworkDeviceDiscovery()) << "Register new network device monitor for" << macAddress; NetworkDeviceInfo::MonitorMode mode = NetworkDeviceInfo::MonitorModeMac;
if (macAddress.isNull()) {
if (!hostName.isEmpty()) {
mode = NetworkDeviceInfo::MonitorModeHostName;
} else {
if (address.isNull()) {
qCWarning(dcNetworkDeviceDiscovery()) << "Cannot register monitor for thing" << thing <<
"because there are not enough information available." <<
"At least one parameter from the networkdevice interface needs to be provided." << thing->params();
return nullptr;
} else {
mode = NetworkDeviceInfo::MonitorModeIp;
}
}
} // else we use the MAC address mode
// Fill in cached information // Check if we already have a monitor for these settings...
NetworkDeviceInfo info; NetworkDeviceMonitorImpl *internalMonitor = nullptr;
if (m_networkInfoCache.contains(macAddress)) { for (QHash<NetworkDeviceMonitorImpl *, QVector<NetworkDeviceMonitorImpl *>>::const_iterator iterator = m_monitors.cbegin(),
info = m_networkInfoCache.value(macAddress); end = m_monitors.cend(); iterator != end; ++iterator) {
} else { if (iterator.key()->address() == address && iterator.key()->macAddress() == macAddress && iterator.key()->hostName() == hostName) {
info.setMacAddress(macAddress.toString()); internalMonitor = iterator.key();
} break;
NetworkDeviceMonitorImpl *monitor = new NetworkDeviceMonitorImpl(macAddress, this);
monitor->setNetworkDeviceInfo(info);
monitor->setLastSeen(m_lastSeen.value(macAddress, QDateTime()));
m_monitors.insert(macAddress, monitor);
m_monitorsReferenceCount[macAddress] = 1;
if (!available()) {
qCWarning(dcNetworkDeviceDiscovery()) << "Registered monitor but the hardware resource is not available. The monitor will not work as expected" << monitor;
return monitor;
}
// Restart the monitor timer since we evaluate this one now
m_monitorTimer->start();
if (!monitor->networkDeviceInfo().isValid()) {
qCDebug(dcNetworkDeviceDiscovery()) << "Adding network device monitor for unresolved mac address. Starting a discovery...";
NetworkDeviceDiscoveryReply *reply = discover();
connect(reply, &NetworkDeviceDiscoveryReply::finished, reply, &NetworkDeviceDiscoveryReply::deleteLater);
} else {
evaluateMonitor(monitor);
}
qCDebug(dcNetworkDeviceDiscovery()) << "Registered successfully" << monitor;
return monitor;
}
void NetworkDeviceDiscoveryImpl::unregisterMonitor(const MacAddress &macAddress)
{
if (m_monitorsReferenceCount.contains(macAddress)) {
m_monitorsReferenceCount[macAddress] -= 1;
if (m_monitorsReferenceCount[macAddress] > 0) {
qCDebug(dcNetworkDeviceDiscovery()) << "Unregistered monitor for" << macAddress.toString() << "but keeping the monitor. There are still" << m_monitorsReferenceCount[macAddress] << "references to it.";
return;
} }
} }
if (m_monitors.contains(macAddress)) { bool newMonitor = true;
NetworkDeviceMonitorImpl *monitor = m_monitors.take(macAddress); if (internalMonitor) {
qCInfo(dcNetworkDeviceDiscovery()) << "Unregister" << monitor; qCDebug(dcNetworkDeviceDiscovery()) << "Already have an internal monitor for this network device" << internalMonitor;
monitor->deleteLater(); newMonitor = false;
m_monitorsReferenceCount.remove(macAddress); } else {
// Create a new monitor for the internal use
internalMonitor = new NetworkDeviceMonitorImpl(macAddress, hostName, address, this);
m_monitors.insert(internalMonitor, QVector<NetworkDeviceMonitorImpl*>());
} }
internalMonitor->setMonitorMode(mode);
if (m_networkInfoCache.isEmpty()) {
qCDebug(dcNetworkDeviceDiscovery()) << "Cache is empty. Starting an internal discovery...";
NetworkDeviceDiscoveryReply *reply = discover();
connect(reply, &NetworkDeviceDiscoveryReply::finished, reply, &NetworkDeviceDiscoveryReply::deleteLater);
}
// Find and set the network device info
for (int i = 0; i < m_networkInfoCache.count(); i++) {
const NetworkDeviceInfo networkDeviceInfo = m_networkInfoCache.at(i);
switch (internalMonitor->monitorMode()) {
case NetworkDeviceInfo::MonitorModeMac:
// Search the unique mac address
if (networkDeviceInfo.macAddressInfos().hasMacAddress(internalMonitor->macAddress())) {
qCDebug(dcNetworkDeviceDiscovery()) << "MAC monitor:" << networkDeviceInfo;
internalMonitor->setNetworkDeviceInfo(networkDeviceInfo);
}
break;
case NetworkDeviceInfo::MonitorModeHostName:
// Search the unique mac address
if (networkDeviceInfo.hostName() == internalMonitor->hostName()) {
qCDebug(dcNetworkDeviceDiscovery()) << "Host name monitor:" << networkDeviceInfo;
internalMonitor->setNetworkDeviceInfo(networkDeviceInfo);
}
break;
case NetworkDeviceInfo::MonitorModeIp:
// Search the unique mac address
if (networkDeviceInfo.address() == internalMonitor->address()) {
qCDebug(dcNetworkDeviceDiscovery()) << "IP monitor:" << networkDeviceInfo;
internalMonitor->setNetworkDeviceInfo(networkDeviceInfo);
}
break;
}
}
// Create a new plugin monitor object we are going to return...
NetworkDeviceMonitorImpl *pluginMonitor = createPluginMonitor(internalMonitor);
m_monitors[internalMonitor].append(pluginMonitor);
qCDebug(dcNetworkDeviceDiscovery()) << "Registered successfully" << pluginMonitor;
// In case this is a new monitor, let's evaluate it right the way so know asap if the device is reachable or not
if (newMonitor)
evaluateMonitor(internalMonitor);
return pluginMonitor;
} }
void NetworkDeviceDiscoveryImpl::unregisterMonitor(NetworkDeviceMonitor *networkDeviceMonitor) void NetworkDeviceDiscoveryImpl::unregisterMonitor(NetworkDeviceMonitor *networkDeviceMonitor)
@ -288,10 +320,7 @@ void NetworkDeviceDiscoveryImpl::unregisterMonitor(NetworkDeviceMonitor *network
if (!networkDeviceMonitor) if (!networkDeviceMonitor)
return; return;
if (!m_monitors.values().contains(qobject_cast<NetworkDeviceMonitorImpl *>(networkDeviceMonitor))) cleanupPluginMonitor(qobject_cast<NetworkDeviceMonitorImpl *>(networkDeviceMonitor));
return;
unregisterMonitor(MacAddress(networkDeviceMonitor->networkDeviceInfo().macAddress()));
} }
PingReply *NetworkDeviceDiscoveryImpl::ping(const QHostAddress &address, uint retries) PingReply *NetworkDeviceDiscoveryImpl::ping(const QHostAddress &address, uint retries)
@ -302,6 +331,14 @@ PingReply *NetworkDeviceDiscoveryImpl::ping(const QHostAddress &address, uint re
return reply; return reply;
} }
PingReply *NetworkDeviceDiscoveryImpl::ping(const QString &hostName, uint retries)
{
PingReply *reply = m_ping->ping(hostName, retries);
// Note: we use any ping used trough this method also for the monitor evaluation
watchPingReply(reply);
return reply;
}
PingReply *NetworkDeviceDiscoveryImpl::ping(const QHostAddress &address, bool lookupHost, uint retries) PingReply *NetworkDeviceDiscoveryImpl::ping(const QHostAddress &address, bool lookupHost, uint retries)
{ {
PingReply *reply = m_ping->ping(address, lookupHost, retries); PingReply *reply = m_ping->ping(address, lookupHost, retries);
@ -328,7 +365,7 @@ bool NetworkDeviceDiscoveryImpl::sendArpRequest(const QHostAddress &address)
return false; return false;
} }
QHash<MacAddress, NetworkDeviceInfo> NetworkDeviceDiscoveryImpl::cache() const NetworkDeviceInfos NetworkDeviceDiscoveryImpl::cache() const
{ {
return m_networkInfoCache; return m_networkInfoCache;
} }
@ -449,7 +486,35 @@ void NetworkDeviceDiscoveryImpl::processMonitorPingResult(PingReply *reply, Netw
// Save the last time we tried to communicate // Save the last time we tried to communicate
if (reply->error() == PingReply::ErrorNoError) { if (reply->error() == PingReply::ErrorNoError) {
qCDebug(dcNetworkDeviceDiscovery()) << "Ping response from" << monitor << reply->duration() << "ms"; qCDebug(dcNetworkDeviceDiscovery()) << "Ping response from" << monitor << reply->duration() << "ms";
monitor->setLastSeen(QDateTime::currentDateTime());
QDateTime currentDateTime = QDateTime::currentDateTime();
m_lastSeen[reply->targetHostAddress()] = currentDateTime;
for (int i = 0; i < m_networkInfoCache.count(); i++) {
if (monitor->monitorMode() == NetworkDeviceInfo::MonitorModeHostName) {
if (m_networkInfoCache.at(i).hostName() == monitor->hostName() && m_networkInfoCache.at(i).address() != reply->targetHostAddress()) {
QHostAddress oldAddress = m_networkInfoCache.at(i).address();
qCDebug(dcNetworkDeviceDiscovery()) << "Hostname" << monitor->hostName() << "changed the IP address from"
<< oldAddress.toString()
<< "-->"
<< reply->targetHostAddress().toString();
removeFromNetworkDeviceCache(oldAddress);
NetworkDeviceInfo info = m_networkInfoCache.at(i);
info.setAddress(reply->targetHostAddress());
monitor->setNetworkDeviceInfo(info);
m_networkInfoCache[i] = info;
m_networkInfoCache.sortNetworkDevices();
saveNetworkDeviceCache(info);
break;
}
}
}
monitor->setLastSeen(currentDateTime);
monitor->setReachable(true); monitor->setReachable(true);
} else { } else {
qCDebug(dcNetworkDeviceDiscovery()) << "Failed to ping device from" << monitor << "retrying" << reply->retries() << "times:" << reply->error(); qCDebug(dcNetworkDeviceDiscovery()) << "Failed to ping device from" << monitor << "retrying" << reply->retries() << "times:" << reply->error();
@ -459,24 +524,22 @@ void NetworkDeviceDiscoveryImpl::processMonitorPingResult(PingReply *reply, Netw
void NetworkDeviceDiscoveryImpl::watchPingReply(PingReply *reply) void NetworkDeviceDiscoveryImpl::watchPingReply(PingReply *reply)
{ {
connect(reply, &PingReply::finished, this, [=](){ connect(reply, &PingReply::finished, this, [this, reply](){
// Search cache for mac address and update last seen
if (reply->error() == PingReply::ErrorNoError) { if (reply->error() == PingReply::ErrorNoError) {
foreach (const NetworkDeviceInfo &info, m_networkInfoCache) {
if (info.address() == reply->targetHostAddress()) { int index = m_networkInfoCache.indexFromHostAddress(reply->targetHostAddress());
// Found info for this ip, update the cache if (index < 0)
MacAddress macAddress(info.macAddress()); return;
if (!macAddress.isNull() && m_networkInfoCache.contains(macAddress)) {
m_lastSeen[macAddress] = QDateTime::currentDateTime(); m_lastSeen[reply->targetHostAddress()] = QDateTime::currentDateTime();
saveNetworkDeviceCache(m_networkInfoCache.value(macAddress)); saveNetworkDeviceCache(m_networkInfoCache.at(index));
}
}
}
} }
// Update any monitor // Update any relevant monitor
foreach (NetworkDeviceMonitorImpl *monitor, m_monitors.values()) { foreach (NetworkDeviceMonitorImpl *monitor, m_monitors.keys()) {
if (monitor->networkDeviceInfo().address() == reply->targetHostAddress()) { if ((monitor->monitorMode() == NetworkDeviceInfo::MonitorModeIp && reply->targetHostAddress() == monitor->address()) ||
(monitor->monitorMode() == NetworkDeviceInfo::MonitorModeHostName && reply->hostName() == monitor->hostName()) ||
(monitor->monitorMode() == NetworkDeviceInfo::MonitorModeMac && reply->targetHostAddress() == monitor->networkDeviceInfo().address())) {
processMonitorPingResult(reply, monitor); processMonitorPingResult(reply, monitor);
} }
} }
@ -488,41 +551,61 @@ void NetworkDeviceDiscoveryImpl::loadNetworkDeviceCache()
qCInfo(dcNetworkDeviceDiscovery()) << "Loading cached network device information from" << m_cacheSettings->fileName(); qCInfo(dcNetworkDeviceDiscovery()) << "Loading cached network device information from" << m_cacheSettings->fileName();
m_networkInfoCache.clear(); m_networkInfoCache.clear();
QDateTime now = QDateTime::currentDateTime(); QDateTime currentDateTime = QDateTime::currentDateTime();
m_cacheSettings->beginGroup("NetworkDeviceInfos"); uint cacheVersion = m_cacheSettings->value("version", 0).toUInt();
foreach (const QString &macAddress, m_cacheSettings->childGroups()) {
m_cacheSettings->beginGroup(macAddress);
MacAddress mac(macAddress); // Load only the cache if we have the same format, otherwise we discard the cache and start over with the current version
QDateTime lastSeen = QDateTime::fromMSecsSinceEpoch(m_cacheSettings->value("lastSeen").toLongLong()); // No need to migrate the caches, a new discovery will to that for us ...
// Remove the info from the cache if not seen fo the last 30 days... if (cacheVersion == CACHE_VERSION) {
if (lastSeen.date().addDays(m_cacheCleanupPeriod) < now.date()) { m_cacheSettings->beginGroup("NetworkDeviceInfos");
qCDebug(dcNetworkDeviceDiscovery()) << "Removing network device cache entry since it did not show up within the last" << m_cacheCleanupPeriod << "days" << mac.toString(); foreach (const QString &addressString, m_cacheSettings->childGroups()) {
m_cacheSettings->remove("");
m_cacheSettings->endGroup(); // mac address
continue;
}
NetworkDeviceInfo info(mac.toString()); m_cacheSettings->beginGroup(addressString);
info.setAddress(QHostAddress(m_cacheSettings->value("address").toString()));
info.setHostName(m_cacheSettings->value("hostName").toString());
info.setMacAddressManufacturer(m_cacheSettings->value("manufacturer").toString());
info.setNetworkInterface(QNetworkInterface::interfaceFromName(m_cacheSettings->value("interface").toString()));
if (info.isValid() && info.isComplete()) { QHostAddress address(addressString);
QDateTime lastSeen = QDateTime::fromMSecsSinceEpoch(m_cacheSettings->value("lastSeen").toLongLong());
// Remove the info from the cache if not seen fo the last 30 days...
if (lastSeen.date().addDays(m_cacheCleanupPeriod) < currentDateTime.date()) {
qCDebug(dcNetworkDeviceDiscovery()) << "Removing network device cache entry since it did not show up within the last" << m_cacheCleanupPeriod << "days" << address.toString();
m_cacheSettings->remove("");
m_cacheSettings->endGroup(); // mac address
continue;
}
NetworkDeviceInfo info(address);
info.setHostName(m_cacheSettings->value("hostName").toString());
info.setNetworkInterface(QNetworkInterface::interfaceFromName(m_cacheSettings->value("interface").toString()));
int size = m_cacheSettings->beginReadArray("mac");
for (int i = 0; i < size; i++) {
m_cacheSettings->setArrayIndex(i);
MacAddress macAddress(m_cacheSettings->value("mac").toString());
QString vendor = m_cacheSettings->value("vendor").toString();
info.addMacAddress(macAddress, vendor);
// Cache the mac information for less DB access
if (!macAddress.isNull() && !vendor.isEmpty()) {
m_macVendorCache[macAddress] = vendor;
}
}
m_cacheSettings->endArray();
m_cacheSettings->endGroup(); // address
qCDebug(dcNetworkDeviceDiscovery()) << "Loaded cached" << info << "last seen" << lastSeen.toString(); qCDebug(dcNetworkDeviceDiscovery()) << "Loaded cached" << info << "last seen" << lastSeen.toString();
m_networkInfoCache[mac] = info; m_networkInfoCache.append(info);
m_lastSeen[mac] = lastSeen; m_lastSeen[info.address()] = lastSeen;
} else {
qCWarning(dcNetworkDeviceDiscovery()) << "Clean up invalid cached network device info from cache" << info;
m_cacheSettings->remove("");
} }
m_cacheSettings->endGroup(); // NetworkDeviceInfos
m_cacheSettings->endGroup(); // mac address } else {
qCDebug(dcNetworkDeviceDiscovery()) << "The cache format version has changed. Discard the network device cache ...";
m_cacheSettings->setValue("version", CACHE_VERSION);
m_cacheSettings->beginGroup("NetworkDeviceInfos");
m_cacheSettings->remove("");
m_cacheSettings->endGroup(); // NetworkDeviceInfos
} }
m_cacheSettings->endGroup(); // NetworkDeviceInfos
qCInfo(dcNetworkDeviceDiscovery()) << "Loaded" << m_networkInfoCache.count() << "network device infos from cache."; qCInfo(dcNetworkDeviceDiscovery()) << "Loaded" << m_networkInfoCache.count() << "network device infos from cache.";
@ -530,17 +613,17 @@ void NetworkDeviceDiscoveryImpl::loadNetworkDeviceCache()
m_lastCacheHousekeeping = QDateTime::currentDateTime(); m_lastCacheHousekeeping = QDateTime::currentDateTime();
} }
void NetworkDeviceDiscoveryImpl::removeFromNetworkDeviceCache(const MacAddress &macAddress) void NetworkDeviceDiscoveryImpl::removeFromNetworkDeviceCache(const QHostAddress &address)
{ {
if (macAddress.isNull()) if (address.isNull())
return; return;
m_networkInfoCache.remove(macAddress); m_networkInfoCache.removeHostAddress(address);
m_lastSeen.remove(macAddress); m_lastSeen.remove(address);
m_cacheSettings->beginGroup("NetworkDeviceInfos"); m_cacheSettings->beginGroup("NetworkDeviceInfos");
m_cacheSettings->beginGroup(macAddress.toString()); m_cacheSettings->beginGroup(address.toString());
m_cacheSettings->remove(""); m_cacheSettings->remove("");
m_cacheSettings->endGroup(); // mac address m_cacheSettings->endGroup(); // address
m_cacheSettings->endGroup(); // NetworkDeviceInfos m_cacheSettings->endGroup(); // NetworkDeviceInfos
m_cacheSettings->sync(); m_cacheSettings->sync();
} }
@ -551,47 +634,70 @@ void NetworkDeviceDiscoveryImpl::saveNetworkDeviceCache(const NetworkDeviceInfo
return; return;
m_cacheSettings->beginGroup("NetworkDeviceInfos"); m_cacheSettings->beginGroup("NetworkDeviceInfos");
m_cacheSettings->beginGroup(deviceInfo.macAddress()); m_cacheSettings->beginGroup(deviceInfo.address().toString());
m_cacheSettings->setValue("address", deviceInfo.address().toString());
m_cacheSettings->setValue("hostName", deviceInfo.hostName()); m_cacheSettings->setValue("hostName", deviceInfo.hostName());
m_cacheSettings->setValue("manufacturer", deviceInfo.macAddressManufacturer());
m_cacheSettings->setValue("interface", deviceInfo.networkInterface().name()); m_cacheSettings->setValue("interface", deviceInfo.networkInterface().name());
m_cacheSettings->setValue("lastSeen", convertMinuteBased(m_lastSeen.value(MacAddress(deviceInfo.macAddress()))).toMSecsSinceEpoch()); m_cacheSettings->setValue("lastSeen", convertMinuteBased(m_lastSeen.value(deviceInfo.address())).toMSecsSinceEpoch());
m_cacheSettings->endGroup(); // mac address
if (!deviceInfo.macAddressInfos().isEmpty()){
m_cacheSettings->beginWriteArray("mac");
for (int i = 0; i < deviceInfo.macAddressInfos().size(); i++) {
m_cacheSettings->setArrayIndex(i);
m_cacheSettings->setValue("mac", deviceInfo.macAddressInfos().at(i).macAddress().toString());
m_cacheSettings->setValue("vendor", deviceInfo.macAddressInfos().at(i).vendorName());
}
m_cacheSettings->endArray(); // mac
}
m_cacheSettings->endGroup(); // address
m_cacheSettings->endGroup(); // NetworkDeviceInfos m_cacheSettings->endGroup(); // NetworkDeviceInfos
m_cacheSettings->sync(); m_cacheSettings->sync();
} }
void NetworkDeviceDiscoveryImpl::updateCache(const NetworkDeviceInfo &deviceInfo) void NetworkDeviceDiscoveryImpl::updateCache(const NetworkDeviceInfo &deviceInfo)
{ {
MacAddress macAddress(deviceInfo.macAddress()); // Update monitors
if (macAddress.isNull()) foreach (NetworkDeviceMonitorImpl *monitor, m_monitors.keys()) {
return; if (monitor->isMyNetworkDeviceInfo(deviceInfo)) {
monitor->setNetworkDeviceInfo(deviceInfo);
if (m_monitors.contains(macAddress)) { break;
NetworkDeviceMonitorImpl *monitor = m_monitors.value(macAddress); }
monitor->setNetworkDeviceInfo(deviceInfo);
} }
if (m_networkInfoCache.value(macAddress) == deviceInfo) // Save only if changed
int index = m_networkInfoCache.indexFromHostAddress(deviceInfo.address());
if (index >= 0 && m_networkInfoCache.at(index) == deviceInfo)
return; return;
m_networkInfoCache[macAddress] = deviceInfo; if (index < 0) {
m_networkInfoCache.append(deviceInfo);
} else {
m_networkInfoCache[index] = deviceInfo;
}
saveNetworkDeviceCache(deviceInfo); saveNetworkDeviceCache(deviceInfo);
} }
void NetworkDeviceDiscoveryImpl::evaluateMonitor(NetworkDeviceMonitorImpl *monitor) void NetworkDeviceDiscoveryImpl::evaluateMonitor(NetworkDeviceMonitorImpl *monitor)
{ {
if (!monitor->networkDeviceInfo().isValid()) if (monitor->currentPingReply()) {
qCDebug(dcNetworkDeviceDiscovery()) << "Monitor has still a ping reply pending:" << monitor;
return; return;
}
if (monitor->currentPingReply()) qCDebug(dcNetworkDeviceDiscovery()) << "Evaluate monitor" << monitor;
return;
// Start action if we have not seen the device for gracePeriod seconds // Start action if we have not seen the device for gracePeriod seconds
QDateTime currentDateTime = QDateTime::currentDateTime(); QDateTime currentDateTime = QDateTime::currentDateTime();
bool requiresRefresh = false; bool requiresRefresh = false;
if (!monitor->networkDeviceInfo().isValid()) {
qCDebug(dcNetworkDeviceDiscovery()) << "Network device info not valid for" << monitor;
requiresRefresh = true;
}
if (monitor->lastSeen().isNull()) { if (monitor->lastSeen().isNull()) {
qCDebug(dcNetworkDeviceDiscovery()) << monitor << "requires refresh. Not seen since application start."; qCDebug(dcNetworkDeviceDiscovery()) << monitor << "requires refresh. Not seen since application start.";
requiresRefresh = true; requiresRefresh = true;
@ -618,53 +724,83 @@ void NetworkDeviceDiscoveryImpl::evaluateMonitor(NetworkDeviceMonitorImpl *monit
if (!requiresRefresh) if (!requiresRefresh)
return; return;
// Try to ping first // Try to ping first.
qCDebug(dcNetworkDeviceDiscovery()) << monitor << "try to ping" << monitor->networkDeviceInfo().address().toString();
PingReply *reply = m_ping->ping(monitor->networkDeviceInfo().address(), monitor->pingRetries()); // IMPORTANT: use the ping methods from this object, so the result will automatically
// be evaluated for the monitors and cache
PingReply *reply = nullptr;
if (monitor->monitorMode() == NetworkDeviceInfo::MonitorModeHostName) {
qCDebug(dcNetworkDeviceDiscovery()) << monitor << "try to ping" << monitor->hostName();
reply = ping(monitor->hostName(), monitor->pingRetries());
} else {
qCDebug(dcNetworkDeviceDiscovery()) << monitor << "try to ping" << monitor->networkDeviceInfo().address().toString();
reply = ping(monitor->networkDeviceInfo().address(), monitor->pingRetries());
}
monitor->setCurrentPingReply(reply); monitor->setCurrentPingReply(reply);
monitor->setLastConnectionAttempt(currentDateTime); monitor->setLastConnectionAttempt(currentDateTime);
connect(reply, &PingReply::retry, monitor, [=](PingReply::Error error, uint retryCount){ connect(reply, &PingReply::retry, monitor, [monitor](PingReply::Error error, uint retryCount){
Q_UNUSED(error) Q_UNUSED(error)
Q_UNUSED(retryCount) Q_UNUSED(retryCount)
monitor->setLastConnectionAttempt(QDateTime::currentDateTime()); monitor->setLastConnectionAttempt(QDateTime::currentDateTime());
}); });
connect(reply, &PingReply::destroyed, monitor, [=](){ connect(reply, &PingReply::destroyed, monitor, [monitor, reply](){
monitor->setCurrentPingReply(nullptr); if (monitor->currentPingReply() == reply) {
monitor->setCurrentPingReply(nullptr);
}
}); });
connect(reply, &PingReply::finished, monitor, [=](){ connect(reply, &PingReply::finished, monitor, [monitor, reply](){
processMonitorPingResult(reply, monitor); if (monitor->currentPingReply() == reply) {
monitor->setCurrentPingReply(nullptr);
}
}); });
} }
void NetworkDeviceDiscoveryImpl::processArpTraffic(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress) void NetworkDeviceDiscoveryImpl::processArpTraffic(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress)
{ {
QDateTime now = QDateTime::currentDateTime(); QDateTime currentDateTime = QDateTime::currentDateTime();
m_lastSeen[macAddress] = now; m_lastSeen[address] = currentDateTime;
if (m_networkInfoCache.contains(macAddress)) { // Update monitors and cache
if (m_networkInfoCache.value(macAddress).address() != address) { for (int i = 0; i < m_networkInfoCache.count(); i++) {
m_networkInfoCache[macAddress].setAddress(address); switch (m_networkInfoCache.at(i).monitorMode()) {
saveNetworkDeviceCache(m_networkInfoCache.value(macAddress)); case NetworkDeviceInfo::MonitorModeMac:
} if (m_networkInfoCache.at(i).macAddressInfos().hasMacAddress(macAddress) &&
} m_networkInfoCache.at(i).address() != address) {
// Update the monitors QHostAddress oldAddress = m_networkInfoCache.at(i).address();
NetworkDeviceMonitorImpl *monitor = m_monitors.value(macAddress); qCDebug(dcNetworkDeviceDiscovery()) << "Host" << macAddress.toString() << "changed the IP address from"
if (monitor) { << oldAddress.toString()
monitor->setLastSeen(now); << "-->"
if (monitor->networkDeviceInfo().address() != address) { << address.toString();
NetworkDeviceInfo info = monitor->networkDeviceInfo();
info.setAddress(address);
monitor->setNetworkDeviceInfo(info);
qCDebug(dcNetworkDeviceDiscovery()) << "NetworkDeviceMonitor" << monitor << "ip address changed";
emit monitor->networkDeviceInfoChanged(monitor->networkDeviceInfo());
}
if (monitor->networkDeviceInfo().isComplete()) { removeFromNetworkDeviceCache(oldAddress);
monitor->setReachable(true);
NetworkDeviceInfo info = m_networkInfoCache.at(i);
info.setAddress(address);
m_networkInfoCache[i] = info;
m_networkInfoCache.sortNetworkDevices();
saveNetworkDeviceCache(info);
foreach (NetworkDeviceMonitorImpl *monitor, m_monitors.keys()) {
if (monitor->macAddress() == macAddress) {
monitor->setNetworkDeviceInfo(info);
monitor->setLastSeen(currentDateTime);
monitor->setReachable(true);
break;
}
}
break;
}
break;
case NetworkDeviceInfo::MonitorModeHostName:
case NetworkDeviceInfo::MonitorModeIp:
saveNetworkDeviceCache(m_networkInfoCache.at(i));
break;
} }
} }
@ -677,8 +813,8 @@ void NetworkDeviceDiscoveryImpl::processArpTraffic(const QNetworkInterface &inte
// Check if we know the mac address manufacturer from the cache // Check if we know the mac address manufacturer from the cache
bool requiresMacAddressLookup = true; bool requiresMacAddressLookup = true;
if (m_networkInfoCache.contains(macAddress)) { if (m_macVendorCache.contains(macAddress)) {
QString cachedManufacturer = m_networkInfoCache[macAddress].macAddressManufacturer(); QString cachedManufacturer = m_macVendorCache.value(macAddress);
if (!cachedManufacturer.isEmpty()) { if (!cachedManufacturer.isEmpty()) {
// Found the mac address manufacturer in the cache, let's use that one... // Found the mac address manufacturer in the cache, let's use that one...
qCDebug(dcNetworkDeviceDiscovery()) << "Using cached manufacturer " << cachedManufacturer << "for" << macAddress.toString(); qCDebug(dcNetworkDeviceDiscovery()) << "Using cached manufacturer " << cachedManufacturer << "for" << macAddress.toString();
@ -700,6 +836,9 @@ void NetworkDeviceDiscoveryImpl::processArpTraffic(const QNetworkInterface &inte
// Note: set the mac manufacturer explicitly to make the info complete (even an empty sring) // Note: set the mac manufacturer explicitly to make the info complete (even an empty sring)
qCDebug(dcNetworkDeviceDiscovery()) << "MAC manufacturer lookup finished for" << macAddress << ":" << reply->manufacturer(); qCDebug(dcNetworkDeviceDiscovery()) << "MAC manufacturer lookup finished for" << macAddress << ":" << reply->manufacturer();
if (!reply->manufacturer().isEmpty())
m_macVendorCache.insert(macAddress, reply->manufacturer());
if (m_currentDiscoveryReply) { if (m_currentDiscoveryReply) {
m_currentDiscoveryReply->processMacManufacturer(macAddress, reply->manufacturer()); m_currentDiscoveryReply->processMacManufacturer(macAddress, reply->manufacturer());
@ -739,6 +878,58 @@ QDateTime NetworkDeviceDiscoveryImpl::convertMinuteBased(const QDateTime &dateTi
return dateTimeToConvert; return dateTimeToConvert;
} }
NetworkDeviceMonitorImpl *NetworkDeviceDiscoveryImpl::createPluginMonitor(NetworkDeviceMonitorImpl *internalMonitor)
{
NetworkDeviceMonitorImpl *pluginMonitor = new NetworkDeviceMonitorImpl(internalMonitor->macAddress(), internalMonitor->hostName(), internalMonitor->address(), this);
pluginMonitor->setNetworkDeviceInfo(internalMonitor->networkDeviceInfo());
pluginMonitor->setMonitorMode(internalMonitor->monitorMode());
pluginMonitor->setLastSeen(internalMonitor->lastSeen());
pluginMonitor->setLastConnectionAttempt(internalMonitor->lastConnectionAttempt());
pluginMonitor->setPingRetries(internalMonitor->pingRetries());
pluginMonitor->setReachable(internalMonitor->reachable());
// internal monitor --> plugin monitor
connect(internalMonitor, &NetworkDeviceMonitorImpl::reachableChanged, pluginMonitor, [pluginMonitor](bool reachable){
pluginMonitor->setReachable(reachable);
});
connect(internalMonitor, &NetworkDeviceMonitorImpl::lastSeenChanged, pluginMonitor, [pluginMonitor](const QDateTime &lastSeen){
pluginMonitor->setLastSeen(lastSeen);
});
connect(internalMonitor, &NetworkDeviceMonitorImpl::networkDeviceInfoChanged, pluginMonitor, [pluginMonitor](const NetworkDeviceInfo &networkDeviceInfo){
pluginMonitor->setNetworkDeviceInfo(networkDeviceInfo);
});
// plugin monitor --> internal monitor
connect(pluginMonitor, &NetworkDeviceMonitorImpl::pingRetriesChanged, internalMonitor, [internalMonitor](uint pingRetries){
internalMonitor->setPingRetries(pingRetries);
});
// In case the plugin user is deleting the monitor object, we need to clean up here and check if we can remove the internal monitor
connect(pluginMonitor, &NetworkDeviceDiscoveryImpl::destroyed, this, [this, pluginMonitor](QObject *) {
cleanupPluginMonitor(pluginMonitor);
});
return pluginMonitor;
}
void NetworkDeviceDiscoveryImpl::cleanupPluginMonitor(NetworkDeviceMonitorImpl *pluginMonitor)
{
qCDebug(dcNetworkDeviceDiscovery()) << "Unregister plugin monitor" << pluginMonitor;
foreach (NetworkDeviceMonitorImpl *internalMonitor, m_monitors.keys()) {
if (m_monitors.value(internalMonitor).contains(pluginMonitor)) {
m_monitors[internalMonitor].removeAll(pluginMonitor);
pluginMonitor->deleteLater();
if (m_monitors.value(internalMonitor).isEmpty()) {
qCDebug(dcNetworkDeviceDiscovery()) << "No monitor registered for this network device any more. Unregister internal monitor" << internalMonitor;
// Last refference for this monitor, nobody need this any more. Clean up...
m_monitors.remove(internalMonitor);
internalMonitor->deleteLater();
}
}
}
}
void NetworkDeviceDiscoveryImpl::onArpResponseReceived(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress) void NetworkDeviceDiscoveryImpl::onArpResponseReceived(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress)
{ {
// Ignore ARP from zero mac // Ignore ARP from zero mac
@ -766,7 +957,7 @@ void NetworkDeviceDiscoveryImpl::onArpRequstReceived(const QNetworkInterface &in
void NetworkDeviceDiscoveryImpl::evaluateMonitors() void NetworkDeviceDiscoveryImpl::evaluateMonitors()
{ {
bool monitorRequiresRediscovery = false; bool monitorRequiresRediscovery = false;
foreach (NetworkDeviceMonitorImpl *monitor, m_monitors) { foreach (NetworkDeviceMonitorImpl *monitor, m_monitors.keys()) {
evaluateMonitor(monitor); evaluateMonitor(monitor);
// Check if there is any monitor which has not be seen since // Check if there is any monitor which has not be seen since
@ -781,20 +972,20 @@ void NetworkDeviceDiscoveryImpl::evaluateMonitors()
connect(reply, &NetworkDeviceDiscoveryReply::finished, reply, &NetworkDeviceDiscoveryReply::deleteLater); connect(reply, &NetworkDeviceDiscoveryReply::finished, reply, &NetworkDeviceDiscoveryReply::deleteLater);
} }
// Do some cache housekeeping if required // FIXME
if (m_lastCacheHousekeeping.addDays(1) < QDateTime::currentDateTime()) { // // Do some cache housekeeping if required
qCInfo(dcNetworkDeviceDiscovery()) << "Starting cache housekeeping since it is more than one day since the last clanup..."; // if (m_lastCacheHousekeeping.addDays(1) < QDateTime::currentDateTime()) {
QDateTime now = QDateTime::currentDateTime(); // qCInfo(dcNetworkDeviceDiscovery()) << "Starting cache housekeeping since it is more than one day since the last clanup...";
foreach (const MacAddress &mac, m_lastSeen.keys()) { // QDateTime currentDateTime = QDateTime::currentDateTime();
// Remove the info from the cache if not seen fo the last 30 days... // foreach (const MacAddress &mac, m_lastSeen.keys()) {
if (m_lastSeen.value(mac).date().addDays(m_cacheCleanupPeriod) < QDateTime::currentDateTime().date()) { // // Remove the info from the cache if not seen fo the last 30 days...
qCDebug(dcNetworkDeviceDiscovery()) << "Removing network device cache entry since it did not show up within the last" << m_cacheCleanupPeriod << "days" << mac.toString(); // if (m_lastSeen.value(mac).date().addDays(m_cacheCleanupPeriod) < QDateTime::currentDateTime().date()) {
removeFromNetworkDeviceCache(mac); // qCDebug(dcNetworkDeviceDiscovery()) << "Removing network device cache entry since it did not show up within the last" << m_cacheCleanupPeriod << "days" << mac.toString();
} // removeFromNetworkDeviceCache(mac);
} // }
// }
m_lastCacheHousekeeping = now; // m_lastCacheHousekeeping = currentDateTime;
} // }
} }
void NetworkDeviceDiscoveryImpl::finishDiscovery() void NetworkDeviceDiscoveryImpl::finishDiscovery()

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright 2013 - 2022, nymea GmbH * Copyright 2013 - 2024, nymea GmbH
* Contact: contact@nymea.io * Contact: contact@nymea.io
* *
* This file is part of nymea. * This file is part of nymea.
@ -31,10 +31,11 @@
#ifndef NETWORKDEVICEDISCOVERYIMPL_H #ifndef NETWORKDEVICEDISCOVERYIMPL_H
#define NETWORKDEVICEDISCOVERYIMPL_H #define NETWORKDEVICEDISCOVERYIMPL_H
#include <QHash>
#include <QObject> #include <QObject>
#include <QSettings> #include <QSettings>
#include <QLoggingCategory>
#include <QDateTime> #include <QDateTime>
#include <QLoggingCategory>
#include <network/networkdeviceinfo.h> #include <network/networkdeviceinfo.h>
#include <network/networkdevicediscovery.h> #include <network/networkdevicediscovery.h>
@ -66,12 +67,11 @@ public:
NetworkDeviceDiscoveryReply *discover() override; NetworkDeviceDiscoveryReply *discover() override;
NetworkDeviceMonitor *registerMonitor(const MacAddress &macAddress) override; NetworkDeviceMonitor *registerMonitor(Thing *thing) override;
void unregisterMonitor(const MacAddress &macAddress) override;
void unregisterMonitor(NetworkDeviceMonitor *networkDeviceMonitor) override; void unregisterMonitor(NetworkDeviceMonitor *networkDeviceMonitor) override;
PingReply *ping(const QHostAddress &address, uint retries = 3) override; PingReply *ping(const QHostAddress &address, uint retries = 3) override;
PingReply *ping(const QString &hostName, uint retries = 3) override;
PingReply *ping(const QHostAddress &address, bool lookupHost, uint retries = 3); PingReply *ping(const QHostAddress &address, bool lookupHost, uint retries = 3);
MacAddressDatabaseReply *lookupMacAddress(const QString &macAddress) override; MacAddressDatabaseReply *lookupMacAddress(const QString &macAddress) override;
@ -79,7 +79,7 @@ public:
bool sendArpRequest(const QHostAddress &address) override; bool sendArpRequest(const QHostAddress &address) override;
QHash<MacAddress, NetworkDeviceInfo> cache() const override; NetworkDeviceInfos cache() const override;
protected: protected:
void setEnabled(bool enabled) override; void setEnabled(bool enabled) override;
@ -94,6 +94,7 @@ private:
MacAddressDatabase *m_macAddressDatabase = nullptr; MacAddressDatabase *m_macAddressDatabase = nullptr;
ArpSocket *m_arpSocket = nullptr; ArpSocket *m_arpSocket = nullptr;
Ping *m_ping = nullptr; Ping *m_ping = nullptr;
bool m_enabled = true; bool m_enabled = true;
bool m_running = false; bool m_running = false;
@ -112,12 +113,15 @@ private:
QList<MacAddressDatabaseReply *> m_runningMacDatabaseReplies; QList<MacAddressDatabaseReply *> m_runningMacDatabaseReplies;
QList<PingReply *> m_runningPingReplies; QList<PingReply *> m_runningPingReplies;
QHash<MacAddress, NetworkDeviceMonitorImpl *> m_monitors; QHash<NetworkDeviceMonitorImpl *, QVector<NetworkDeviceMonitorImpl *>> m_monitors;
QHash<MacAddress, int> m_monitorsReferenceCount; QHash<QHostAddress, QDateTime> m_lastSeen;
QHash<MacAddress, QDateTime> m_lastSeen;
QHash<MacAddress, QString> m_macVendorCache;
QHash<QHostAddress, NetworkDeviceInfo> m_infos;
QSettings *m_cacheSettings; QSettings *m_cacheSettings;
QHash<MacAddress, NetworkDeviceInfo> m_networkInfoCache; NetworkDeviceInfos m_networkInfoCache;
void pingAllNetworkDevices(); void pingAllNetworkDevices();
@ -127,6 +131,7 @@ private:
void loadNetworkDeviceCache(); void loadNetworkDeviceCache();
void removeFromNetworkDeviceCache(const MacAddress &macAddress); void removeFromNetworkDeviceCache(const MacAddress &macAddress);
void removeFromNetworkDeviceCache(const QHostAddress &address);
void saveNetworkDeviceCache(const NetworkDeviceInfo &deviceInfo); void saveNetworkDeviceCache(const NetworkDeviceInfo &deviceInfo);
void updateCache(const NetworkDeviceInfo &deviceInfo); void updateCache(const NetworkDeviceInfo &deviceInfo);
@ -134,10 +139,15 @@ private:
void processArpTraffic(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress); void processArpTraffic(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress);
void testPingMonitor(NetworkDeviceMonitorImpl *monitor);
// Time helpers // Time helpers
bool longerAgoThan(const QDateTime &dateTime, uint minutes); bool longerAgoThan(const QDateTime &dateTime, uint minutes);
QDateTime convertMinuteBased(const QDateTime &dateTime = QDateTime()); QDateTime convertMinuteBased(const QDateTime &dateTime = QDateTime());
NetworkDeviceMonitorImpl *createPluginMonitor(NetworkDeviceMonitorImpl *internalMonitor);
void cleanupPluginMonitor(NetworkDeviceMonitorImpl *pluginMonitor);
private slots: private slots:
void onArpResponseReceived(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress); void onArpResponseReceived(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress);
void onArpRequstReceived(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress); void onArpRequstReceived(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress);

View File

@ -29,6 +29,7 @@
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "networkdevicediscoveryreplyimpl.h" #include "networkdevicediscoveryreplyimpl.h"
#include "network/networkutils.h"
#include "loggingcategories.h" #include "loggingcategories.h"
#include <QDateTime> #include <QDateTime>
@ -48,11 +49,6 @@ NetworkDeviceInfos NetworkDeviceDiscoveryReplyImpl::networkDeviceInfos() const
return m_networkDeviceInfos; return m_networkDeviceInfos;
} }
NetworkDeviceInfos NetworkDeviceDiscoveryReplyImpl::virtualNetworkDeviceInfos() const
{
return m_virtualNetworkDeviceInfos;
}
bool NetworkDeviceDiscoveryReplyImpl::isFinished() const bool NetworkDeviceDiscoveryReplyImpl::isFinished() const
{ {
return m_isFinished; return m_isFinished;
@ -65,49 +61,34 @@ void NetworkDeviceDiscoveryReplyImpl::setFinished(bool finished)
void NetworkDeviceDiscoveryReplyImpl::processPingResponse(const QHostAddress &address, const QString &hostName) void NetworkDeviceDiscoveryReplyImpl::processPingResponse(const QHostAddress &address, const QString &hostName)
{ {
foreach (const NetworkDeviceInfo &info, m_networkDeviceCache) { if (m_networkDeviceCache.contains(address)) {
if (info.address() == address) { m_networkDeviceCache[address].setHostName(hostName);
// Already found info, set host name and check if complete } else {
MacAddress macAddress(info.macAddress()); NetworkDeviceInfo info;
m_networkDeviceCache[macAddress].setHostName(hostName); info.setAddress(address);
verifyComplete(macAddress); info.setHostName(hostName);
return; m_networkDeviceCache.insert(address, info);
}
// First time seeing this host address
emit hostAddressDiscovered(address);
} }
// Unknown and we have no mac address yet, add it to the ping cache
NetworkDeviceInfo info;
info.setAddress(address);
info.setHostName(hostName);
m_pingCache.insert(address, info);
// First time seeing this host address
emit hostAddressDiscovered(address);
} }
void NetworkDeviceDiscoveryReplyImpl::processArpResponse(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress) void NetworkDeviceDiscoveryReplyImpl::processArpResponse(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress)
{ {
if (m_pingCache.contains(address)) { if (m_networkDeviceCache.contains(address)) {
// We know this device from a ping response m_networkDeviceCache[address].addMacAddress(macAddress);
NetworkDeviceInfo info = m_pingCache.take(address); m_networkDeviceCache[address].setNetworkInterface(interface);
} else {
NetworkDeviceInfo info(macAddress.toString());
info.setAddress(address); info.setAddress(address);
info.setNetworkInterface(interface); info.setNetworkInterface(interface);
info.setMacAddress(macAddress.toString()); m_networkDeviceCache[address] = info;
m_networkDeviceCache[macAddress] = info;
} else {
if (m_networkDeviceCache.contains(macAddress)) {
m_networkDeviceCache[macAddress].setAddress(address);
m_networkDeviceCache[macAddress].setNetworkInterface(interface);
} else {
NetworkDeviceInfo info(macAddress.toString());
info.setAddress(address);
info.setNetworkInterface(interface);
m_networkDeviceCache[macAddress] = info;
// First time seeing this host address
emit hostAddressDiscovered(address);
}
}
verifyComplete(macAddress); // First time seeing this host address
emit hostAddressDiscovered(address);
}
} }
void NetworkDeviceDiscoveryReplyImpl::processMacManufacturer(const MacAddress &macAddress, const QString &manufacturer) void NetworkDeviceDiscoveryReplyImpl::processMacManufacturer(const MacAddress &macAddress, const QString &manufacturer)
@ -115,45 +96,37 @@ void NetworkDeviceDiscoveryReplyImpl::processMacManufacturer(const MacAddress &m
if (macAddress.isNull()) if (macAddress.isNull())
return; return;
if (m_networkDeviceCache.contains(macAddress)) { foreach (const NetworkDeviceInfo &info, m_networkDeviceCache) {
m_networkDeviceCache[macAddress].setMacAddressManufacturer(manufacturer); if (info.macAddressInfos().hasMacAddress(macAddress)) {
} else { m_networkDeviceCache[info.address()].addMacAddress(macAddress, manufacturer);
NetworkDeviceInfo info(macAddress.toString()); }
info.setMacAddressManufacturer(manufacturer);
m_networkDeviceCache[macAddress] = info;
} }
verifyComplete(macAddress);
} }
void NetworkDeviceDiscoveryReplyImpl::processDiscoveryFinished() void NetworkDeviceDiscoveryReplyImpl::processDiscoveryFinished()
{ {
// Lets see if we have any incomplete infos but enougth data to be shown // Add the discovery cache to the final result
foreach (const MacAddress &macAddress, m_networkDeviceCache.keys()) { foreach (const QHostAddress &address, m_networkDeviceCache.keys()) {
// If already in the result, ignore it
if (m_networkDeviceInfos.hasMacAddress(macAddress))
continue;
NetworkDeviceInfo info = m_networkDeviceCache.value(macAddress); if (m_networkDeviceCache.value(address).macAddressInfos().isEmpty() && !m_networkDeviceCache.value(address).networkInterface().isValid()) {
MacAddress infoMacAddress(info.macAddress()); // Set the network interface for the virtual hosts like VPN where we are not receiving any ARP information
m_networkDeviceCache[address].setNetworkInterface(NetworkUtils::getInterfaceForHostaddress(address));
}
NetworkDeviceInfo info = m_networkDeviceCache.value(address);
qCDebug(dcNetworkDeviceDiscovery()) << "--> " << info qCDebug(dcNetworkDeviceDiscovery()) << "--> " << info
<< "Valid:" << info.isValid() << "Valid:" << info.isValid()
<< "Complete:" << info.isComplete() << "Complete:" << info.isComplete()
<< info.incompleteProperties(); << info.incompleteProperties();
// We need at least a valid mac address and a valid ip address, the rest ist pure informative qCDebug(dcNetworkDeviceDiscovery()) << "Adding incomplete" << info << "to the final result:" << info.incompleteProperties();
if (infoMacAddress == macAddress && !infoMacAddress.isNull() && !info.address().isNull()) { m_networkDeviceCache[address].forceComplete();
qCDebug(dcNetworkDeviceDiscovery()) << "Adding incomplete" << info << "to the final result:" << info.incompleteProperties(); m_networkDeviceInfos.append(m_networkDeviceCache.take(address));
// Note: makeing it complete
m_networkDeviceCache[macAddress].setAddress(info.address());
m_networkDeviceCache[macAddress].setHostName(info.hostName());
m_networkDeviceCache[macAddress].setMacAddress(info.macAddress());
m_networkDeviceCache[macAddress].setMacAddressManufacturer(info.macAddressManufacturer());
m_networkDeviceCache[macAddress].setNetworkInterface(info.networkInterface());
verifyComplete(macAddress);
}
} }
// Evaluate overall monitor mode...
evaluateMonitorMode();
// Done, lets sort the result and inform // Done, lets sort the result and inform
m_networkDeviceInfos.sortNetworkDevices(); m_networkDeviceInfos.sortNetworkDevices();
@ -165,88 +138,87 @@ void NetworkDeviceDiscoveryReplyImpl::processDiscoveryFinished()
qCDebug(dcNetworkDeviceDiscovery()) << "--> " << info; qCDebug(dcNetworkDeviceDiscovery()) << "--> " << info;
} }
// Create valid infos from the ping cache and offer them in the virtual infos
foreach (const NetworkDeviceInfo &info, m_pingCache) {
NetworkDeviceInfo finalInfo = info;
finalInfo.setAddress(finalInfo.address());
finalInfo.setHostName(finalInfo.hostName());
finalInfo.setMacAddress(finalInfo.macAddress());
finalInfo.setNetworkInterface(finalInfo.networkInterface());
finalInfo.setMacAddressManufacturer(finalInfo.macAddressManufacturer());
m_virtualNetworkDeviceInfos.append(info);
}
m_virtualNetworkDeviceInfos.sortNetworkDevices();
qCDebug(dcNetworkDeviceDiscovery()) << "Virtual hosts (" << m_virtualNetworkDeviceInfos.count() << ")";
foreach (const NetworkDeviceInfo &info, m_virtualNetworkDeviceInfos) {
qCDebug(dcNetworkDeviceDiscovery()) << "--> " << info;
}
foreach (const MacAddress &macAddress, m_networkDeviceCache.keys()) {
if (m_networkDeviceInfos.hasMacAddress(macAddress))
continue;
NetworkDeviceInfo info = m_networkDeviceCache.value(macAddress);
qCDebug(dcNetworkDeviceDiscovery()) << "Unhandled information:" << info << "Valid:" << info.isValid() << "Complete:" << info.isComplete() << info.incompleteProperties();
}
m_isFinished = true; m_isFinished = true;
emit finished(); emit finished();
} }
QHash<QHostAddress, NetworkDeviceInfo> NetworkDeviceDiscoveryReplyImpl::currentCache() const
{
return m_networkDeviceCache;
}
void NetworkDeviceDiscoveryReplyImpl::addCompleteNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo) void NetworkDeviceDiscoveryReplyImpl::addCompleteNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo)
{ {
// Note: this method will be called only from the discovery if (!m_networkDeviceInfos.hasHostAddress(networkDeviceInfo.address()))
if (!m_networkDeviceInfos.hasMacAddress(networkDeviceInfo.macAddress())) {
m_networkDeviceInfos.append(networkDeviceInfo); m_networkDeviceInfos.append(networkDeviceInfo);
emit hostAddressDiscovered(networkDeviceInfo.address());
emit networkDeviceInfoAdded(networkDeviceInfo);
}
} }
void NetworkDeviceDiscoveryReplyImpl::addVirtualNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo) void NetworkDeviceDiscoveryReplyImpl::evaluateMonitorMode()
{ {
// Note: this method will be called only from the discovery for (int i = 0; i < m_networkDeviceInfos.size(); i++) {
if (!m_networkDeviceInfos.hasHostAddress(networkDeviceInfo.address())) {
m_virtualNetworkDeviceInfos.append(networkDeviceInfo);
}
}
QString NetworkDeviceDiscoveryReplyImpl::macAddressFromHostAddress(const QHostAddress &address) const NetworkDeviceInfo info = m_networkDeviceInfos.at(i);
{ qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: Evaluating host" << info.address().toString();
foreach (const NetworkDeviceInfo &info, m_networkDeviceCache) {
if (info.address() == address) {
return info.macAddress();
}
}
return QString(); NetworkDeviceInfo::MonitorMode mode = NetworkDeviceInfo::MonitorModeMac;
}
bool NetworkDeviceDiscoveryReplyImpl::hasHostAddress(const QHostAddress &address) if (info.macAddressInfos().isEmpty()) {
{
return ! macAddressFromHostAddress(address).isEmpty();
}
void NetworkDeviceDiscoveryReplyImpl::verifyComplete(const MacAddress &macAddress) // No MAC address found, no ARP for this host, probably a VPN client
{ if (info.hostName().isEmpty()) {
if (!m_networkDeviceCache.contains(macAddress)) qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: --> No MAC address and no host name, using MonitorModeIp";
return; mode = NetworkDeviceInfo::MonitorModeIp;
} else {
if (m_networkDeviceCache[macAddress].isComplete() && m_networkDeviceCache[macAddress].isValid()) { qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: --> No MAC address, but we have a host name, suing MonitorModeHostName";
if (m_networkDeviceInfos.hasMacAddress(macAddress)) { mode = NetworkDeviceInfo::MonitorModeHostName;
if (m_networkDeviceInfos.get(macAddress) != m_networkDeviceCache.value(macAddress)) { }
qCDebug(dcNetworkDeviceDiscovery()) << "One MAC address seem to be reachable using 2 IP addresses, which is OK. Not updating the network device info and keeping the current information.";
qCDebug(dcNetworkDeviceDiscovery()) << "--> Keeping " << m_networkDeviceInfos.get(macAddress); } else if (info.macAddressInfos().size() == 1) {
qCDebug(dcNetworkDeviceDiscovery()) << "--> Ignoring" << m_networkDeviceCache.value(macAddress);
// Single mac address for this host..
MacAddress macAddress = info.macAddressInfos().constFirst().macAddress();
bool macAddressIsUnique = true;
// Check if this mac is unique
foreach (const NetworkDeviceInfo &networkDeviceInfo, m_networkDeviceInfos) {
// Skip our self...
if (networkDeviceInfo.address() == info.address())
continue;
if (networkDeviceInfo.macAddressInfos().hasMacAddress(macAddress)) {
macAddressIsUnique = false;
break;
}
}
if (!macAddressIsUnique) {
if (info.hostName().isEmpty()) {
qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: --> the MAC address of" << info.address().toString() << "is not unique in this network and no host name available, usgin MonitorModeIp";
mode = NetworkDeviceInfo::MonitorModeIp;
} else {
qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: --> the MAC address of" << info.address().toString() << "is not unique in this network but we have a host name, usgin MonitorModeHostName";
mode = NetworkDeviceInfo::MonitorModeHostName;
}
} else {
qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: --> the MAC address of" << info.address().toString() << "is unique in this network, usgin MonitorModeMac";
mode = NetworkDeviceInfo::MonitorModeMac;
}
} else if (info.macAddressInfos().size() > 1) {
// Multiple MAC addresses
if (info.hostName().isEmpty()) {
qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: --> multiple MAC addresses and no host name, usgin MonitorModeIp";
mode = NetworkDeviceInfo::MonitorModeIp;
} else {
qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: --> multiple MAC addresses, but we have a host name, usgin MonitorModeHostName";
mode = NetworkDeviceInfo::MonitorModeHostName;
} }
} else {
m_networkDeviceInfos.append(m_networkDeviceCache.value(macAddress));
emit networkDeviceInfoAdded(m_networkDeviceCache[macAddress]);
} }
m_networkDeviceInfos[i].setMonitorMode(mode);
qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: --> Final" << m_networkDeviceInfos.at(i);
} }
} }

View File

@ -48,7 +48,6 @@ public:
~NetworkDeviceDiscoveryReplyImpl() override = default; ~NetworkDeviceDiscoveryReplyImpl() override = default;
NetworkDeviceInfos networkDeviceInfos() const override; NetworkDeviceInfos networkDeviceInfos() const override;
NetworkDeviceInfos virtualNetworkDeviceInfos() const override;
bool isFinished() const override; bool isFinished() const override;
void setFinished(bool finished); void setFinished(bool finished);
@ -60,28 +59,19 @@ public:
void processDiscoveryFinished(); void processDiscoveryFinished();
QHash<QHostAddress, NetworkDeviceInfo> currentCache() const;
public slots: public slots:
void addCompleteNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo); void addCompleteNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo);
void addVirtualNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo);
private: private:
NetworkDeviceInfos m_networkDeviceInfos; // Contains only complete and valid infos QHash<QHostAddress, NetworkDeviceInfo> m_networkDeviceCache;
NetworkDeviceInfos m_virtualNetworkDeviceInfos; // Contains ping responses without ARP, like VPN devices
QHash<MacAddress, NetworkDeviceInfo> m_networkDeviceCache;
qint64 m_startTimestamp; qint64 m_startTimestamp;
bool m_isFinished = false; bool m_isFinished = false;
// Temporary cache for ping responses where the mac is not known yet (like VPN devices) NetworkDeviceInfos m_networkDeviceInfos;
QHash<QHostAddress, NetworkDeviceInfo> m_pingCache;
QString macAddressFromHostAddress(const QHostAddress &address);
bool hasHostAddress(const QHostAddress &address);
void verifyComplete(const MacAddress &macAddress);
void evaluateMonitorMode();
}; };
} }

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright 2013 - 2022, nymea GmbH * Copyright 2013 - 2024, nymea GmbH
* Contact: contact@nymea.io * Contact: contact@nymea.io
* *
* This file is part of nymea. * This file is part of nymea.
@ -35,9 +35,11 @@ Q_DECLARE_LOGGING_CATEGORY(dcNetworkDeviceDiscovery)
namespace nymeaserver { namespace nymeaserver {
NetworkDeviceMonitorImpl::NetworkDeviceMonitorImpl(const MacAddress &macAddress, QObject *parent) : NetworkDeviceMonitorImpl::NetworkDeviceMonitorImpl(const MacAddress &macAddress, const QString &hostName, const QHostAddress &address, QObject *parent) :
NetworkDeviceMonitor(parent), NetworkDeviceMonitor{parent},
m_macAddress(macAddress) m_macAddress{macAddress},
m_hostName{hostName},
m_address{address}
{ {
} }
@ -54,6 +56,26 @@ MacAddress NetworkDeviceMonitorImpl::macAddress() const
return m_macAddress; return m_macAddress;
} }
QString NetworkDeviceMonitorImpl::hostName() const
{
return m_hostName;
}
QHostAddress NetworkDeviceMonitorImpl::address() const
{
return m_address;
}
NetworkDeviceInfo::MonitorMode NetworkDeviceMonitorImpl::monitorMode() const
{
return m_monitorMode;
}
void NetworkDeviceMonitorImpl::setMonitorMode(NetworkDeviceInfo::MonitorMode monitorMode)
{
m_monitorMode = monitorMode;
}
NetworkDeviceInfo NetworkDeviceMonitorImpl::networkDeviceInfo() const NetworkDeviceInfo NetworkDeviceMonitorImpl::networkDeviceInfo() const
{ {
return m_networkDeviceInfo; return m_networkDeviceInfo;
@ -127,4 +149,37 @@ void NetworkDeviceMonitorImpl::setLastConnectionAttempt(const QDateTime &lastCon
m_lastConnectionAttempt = lastConnectionAttempt; m_lastConnectionAttempt = lastConnectionAttempt;
} }
bool NetworkDeviceMonitorImpl::isMyNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo) const
{
bool myNetworkDevice = false;
switch (m_monitorMode) {
case NetworkDeviceInfo::MonitorModeMac:
if (!m_macAddress.isNull() && networkDeviceInfo.macAddressInfos().count() == 1 && networkDeviceInfo.macAddressInfos().hasMacAddress(m_macAddress))
myNetworkDevice = true;
break;
case NetworkDeviceInfo::MonitorModeHostName:
if (!m_hostName.isEmpty() && networkDeviceInfo.hostName() == m_hostName)
myNetworkDevice = true;
break;
case NetworkDeviceInfo::MonitorModeIp:
if (!m_address.isNull() && networkDeviceInfo.address() == m_address)
myNetworkDevice = true;
break;
}
return myNetworkDevice;
}
bool NetworkDeviceMonitorImpl::operator==(NetworkDeviceMonitorImpl *other) const
{
return m_macAddress == other->macAddress() && m_hostName == other->hostName() && m_address == other->address();
}
bool NetworkDeviceMonitorImpl::operator!=(NetworkDeviceMonitorImpl *other) const
{
return !operator==(other);
}
} }

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright 2013 - 2022, nymea GmbH * Copyright 2013 - 2024, nymea GmbH
* Contact: contact@nymea.io * Contact: contact@nymea.io
* *
* This file is part of nymea. * This file is part of nymea.
@ -34,8 +34,8 @@
#include <QObject> #include <QObject>
#include <QDateTime> #include <QDateTime>
#include "network/networkdevicemonitor.h"
#include "network/pingreply.h" #include "network/pingreply.h"
#include "network/networkdevicemonitor.h"
namespace nymeaserver { namespace nymeaserver {
@ -44,10 +44,16 @@ class NetworkDeviceMonitorImpl : public NetworkDeviceMonitor
Q_OBJECT Q_OBJECT
public: public:
explicit NetworkDeviceMonitorImpl(const MacAddress &macAddress, QObject *parent = nullptr); explicit NetworkDeviceMonitorImpl(const MacAddress &macAddress, const QString &hostName, const QHostAddress &address, QObject *parent = nullptr);
~NetworkDeviceMonitorImpl() override; ~NetworkDeviceMonitorImpl() override;
// Properties from the thing
MacAddress macAddress() const override; MacAddress macAddress() const override;
QString hostName() const override;
QHostAddress address() const override;
NetworkDeviceInfo::MonitorMode monitorMode() const override;
void setMonitorMode(NetworkDeviceInfo::MonitorMode monitorMode);
NetworkDeviceInfo networkDeviceInfo() const override; NetworkDeviceInfo networkDeviceInfo() const override;
void setNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo); void setNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo);
@ -67,14 +73,25 @@ public:
QDateTime lastConnectionAttempt() const; QDateTime lastConnectionAttempt() const;
void setLastConnectionAttempt(const QDateTime &lastConnectionAttempt); void setLastConnectionAttempt(const QDateTime &lastConnectionAttempt);
bool isMyNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo) const;
bool operator==(NetworkDeviceMonitorImpl *other) const;
bool operator!=(NetworkDeviceMonitorImpl *other) const;
private: private:
NetworkDeviceInfo m_networkDeviceInfo;
MacAddress m_macAddress; MacAddress m_macAddress;
QString m_hostName;
QHostAddress m_address;
NetworkDeviceInfo::MonitorMode m_monitorMode = NetworkDeviceInfo::MonitorModeMac;
NetworkDeviceInfo m_networkDeviceInfo;
bool m_reachable = false; bool m_reachable = false;
QDateTime m_lastSeen; QDateTime m_lastSeen;
QDateTime m_lastConnectionAttempt; QDateTime m_lastConnectionAttempt;
uint m_pingRetries = 5; uint m_pingRetries = 5;
PingReply *m_currentPingReply = nullptr; PingReply *m_currentPingReply = nullptr;
}; };

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright 2013 - 2020, nymea GmbH * Copyright 2013 - 2024, nymea GmbH
* Contact: contact@nymea.io * Contact: contact@nymea.io
* *
* This file is part of nymea. * This file is part of nymea.
@ -202,9 +202,9 @@ void PluginMetadata::parse(const QJsonObject &jsonObject)
QJsonObject thingClassObject = thingClassJson.toObject(); QJsonObject thingClassObject = thingClassJson.toObject();
/*! Returns a list of all valid JSON properties a ThingClass JSON definition can have. */ /*! Returns a list of all valid JSON properties a ThingClass JSON definition can have. */
QStringList thingClassProperties = QStringList() << "id" << "name" << "displayName" << "createMethods" << "setupMethod" QStringList thingClassProperties = QStringList() << "id" << "name" << "displayName" << "createMethods" << "setupMethod"
<< "interfaces" << "providedInterfaces" << "browsable" << "discoveryParamTypes" << "interfaces" << "providedInterfaces" << "browsable" << "discoveryParamTypes"
<< "paramTypes" << "settingsTypes" << "stateTypes" << "actionTypes" << "eventTypes" << "browserItemActionTypes" << "paramTypes" << "settingsTypes" << "stateTypes" << "actionTypes" << "eventTypes" << "browserItemActionTypes"
<< "discoveryType"; << "discoveryType";
QStringList mandatoryThingClassProperties = QStringList() << "id" << "name" << "displayName"; QStringList mandatoryThingClassProperties = QStringList() << "id" << "name" << "displayName";
QPair<QStringList, QStringList> verificationResult = verifyFields(thingClassProperties, mandatoryThingClassProperties, thingClassObject); QPair<QStringList, QStringList> verificationResult = verifyFields(thingClassProperties, mandatoryThingClassProperties, thingClassObject);
@ -703,6 +703,64 @@ void PluginMetadata::parse(const QJsonObject &jsonObject)
continue; continue;
} }
foreach (const InterfaceParamType &ifaceParamType, iface.paramTypes()) {
if (!thingClass.paramTypes().contains(ifaceParamType.name())) {
if (!ifaceParamType.optional()) {
m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() +
"\" but doesn't implement param \"" + ifaceParamType.name() + "\"");
hasError = true;
}
continue;
}
ParamType &paramType = thingClass.paramTypes()[ifaceParamType.name()];
if (ifaceParamType.type() != paramType.type()) {
m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() +
"\" but param \"" + paramType.name() + "\" has not matching type: \"" +
QVariant::typeToName(paramType.type()) + "\" != \"" + QVariant::typeToName(ifaceParamType.type()) + "\"");
hasError = true;
}
if (ifaceParamType.minValue().isValid() && !ifaceParamType.minValue().isNull()) {
if (ifaceParamType.minValue().toString() == "any") {
if (paramType.minValue().isNull()) {
m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() +
"\" but param \"" + paramType.name() + "\" has no minimum value defined.");
hasError = true;
}
} else if (ifaceParamType.minValue() != paramType.minValue()) {
m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() +
"\" but param \"" + paramType.name() + "\" has not matching minimum value: \"" +
ifaceParamType.minValue().toString() + "\" != \"" + paramType.minValue().toString() + "\"");
hasError = true;
}
}
if (ifaceParamType.maxValue().isValid() && !ifaceParamType.maxValue().isNull()) {
if (ifaceParamType.maxValue().toString() == "any") {
if (paramType.maxValue().isNull()) {
m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() +
"\" but param \"" + paramType.name() + "\" has no maximum value defined.");
hasError = true;
}
} else if (ifaceParamType.maxValue() != paramType.maxValue()) {
m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() +
"\" but param \"" + paramType.name() + "\" has not matching maximum value: \"" +
ifaceParamType.maxValue().toString() + "\" != \"" + paramType.minValue().toString() + "\"");
hasError = true;
}
}
if (!ifaceParamType.allowedValues().isEmpty() && ifaceParamType.allowedValues() != paramType.allowedValues()) {
m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but param \"" +
paramType.name() + "\" has not matching allowed values.");
hasError = true;
}
if (ifaceParamType.unit() != Types::UnitNone && ifaceParamType.unit() != paramType.unit()) {
QMetaEnum unitEnum = QMetaEnum::fromType<Types::Unit>();
m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but param \"" +
paramType.name() + "\" has not matching unit: \"" + unitEnum.valueToKey(ifaceParamType.unit()) + "\" != \"" + unitEnum.valueToKey(paramType.unit()));
hasError = true;
}
}
foreach (const InterfaceStateType &ifaceStateType, iface.stateTypes()) { foreach (const InterfaceStateType &ifaceStateType, iface.stateTypes()) {
if (!stateTypes.contains(ifaceStateType.name())) { if (!stateTypes.contains(ifaceStateType.name())) {
if (!ifaceStateType.optional()) { if (!ifaceStateType.optional()) {

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright 2013 - 2020, nymea GmbH * Copyright 2013 - 2024, nymea GmbH
* Contact: contact@nymea.io * Contact: contact@nymea.io
* *
* This file is part of nymea. * This file is part of nymea.

View File

@ -62,7 +62,7 @@ public slots:
void addThingDescriptor(const ThingDescriptor &thingDescriptor); void addThingDescriptor(const ThingDescriptor &thingDescriptor);
void addThingDescriptors(const ThingDescriptors &thingDescriptors); void addThingDescriptors(const ThingDescriptors &thingDescriptors);
void finish(Thing::ThingError status, const QString &displayMessage = QString()); void finish(Thing::ThingError status, const QString &displayMessage = QString());
signals: signals:
void finished(); void finished();

View File

@ -189,9 +189,39 @@ Interface ThingUtils::loadInterface(const QString &name)
} }
} }
InterfaceParamTypes paramTypes;
InterfaceStateTypes stateTypes; InterfaceStateTypes stateTypes;
InterfaceActionTypes actionTypes; InterfaceActionTypes actionTypes;
InterfaceEventTypes eventTypes; InterfaceEventTypes eventTypes;
foreach (const QVariant &paramVariant, content.value("params").toList()) {
QVariantMap paramMap = paramVariant.toMap();
InterfaceParamType paramType;
paramType.setName(paramMap.value("name").toString());
paramType.setType(QVariant::nameToType(paramMap.value("type").toByteArray()));
paramType.setMinValue(paramMap.value("minValue"));
paramType.setMaxValue(paramMap.value("maxValue"));
paramType.setDefaultValue(paramMap.value("defaultValue"));
paramType.setAllowedValues(paramMap.value("allowedValues").toList());
paramType.setReadOnly(paramMap.value("readOnly").toBool());
//paramType.setOptional(paramMap.value("optional", false).toBool());
if (paramMap.contains("unit")) {
QMetaEnum unitEnum = QMetaEnum::fromType<Types::Unit>();
int enumValue = unitEnum.keyToValue("Unit" + paramMap.value("unit").toByteArray());
if (enumValue == -1) {
qCWarning(dcThingManager) << "Invalid unit" << paramMap.value("unit").toString() << "in interface" << name;
} else {
paramType.setUnit(static_cast<Types::Unit>(enumValue));
}
} else {
paramType.setUnit(Types::UnitNone);
}
paramTypes.append(paramType);
}
foreach (const QVariant &stateVariant, content.value("states").toList()) { foreach (const QVariant &stateVariant, content.value("states").toList()) {
QVariantMap stateMap = stateVariant.toMap(); QVariantMap stateMap = stateVariant.toMap();
InterfaceStateType stateType; InterfaceStateType stateType;
@ -279,11 +309,17 @@ Interface ThingUtils::loadInterface(const QString &name)
eventTypes.append(eventType); eventTypes.append(eventType);
} }
return Interface(name, iface.actionTypes() << actionTypes, iface.eventTypes() << eventTypes, iface.stateTypes() << stateTypes); return Interface(name, iface.paramTypes() << paramTypes, iface.actionTypes() << actionTypes, iface.eventTypes() << eventTypes, iface.stateTypes() << stateTypes);
} }
Interface ThingUtils::mergeInterfaces(const Interface &iface1, const Interface &iface2) Interface ThingUtils::mergeInterfaces(const Interface &iface1, const Interface &iface2)
{ {
InterfaceParamTypes paramTypes = iface1.paramTypes();
foreach (const InterfaceParamType &pt, iface2.paramTypes()) {
if (paramTypes.findByName(pt.name()).name().isEmpty()) {
paramTypes.append(pt);
}
}
InterfaceEventTypes eventTypes = iface1.eventTypes(); InterfaceEventTypes eventTypes = iface1.eventTypes();
foreach (const InterfaceEventType &et, iface2.eventTypes()) { foreach (const InterfaceEventType &et, iface2.eventTypes()) {
if (eventTypes.findByName(et.name()).name().isEmpty()) { if (eventTypes.findByName(et.name()).name().isEmpty()) {
@ -302,7 +338,7 @@ Interface ThingUtils::mergeInterfaces(const Interface &iface1, const Interface &
actionTypes.append(at); actionTypes.append(at);
} }
} }
return Interface(QString(), actionTypes, eventTypes, stateTypes); return Interface(QString(), paramTypes, actionTypes, eventTypes, stateTypes);
} }
QStringList ThingUtils::generateInterfaceParentList(const QString &interface) QStringList ThingUtils::generateInterfaceParentList(const QString &interface)

View File

@ -100,5 +100,6 @@
<file>alarm.json</file> <file>alarm.json</file>
<file>firesensor.json</file> <file>firesensor.json</file>
<file>childlock.json</file> <file>childlock.json</file>
<file>networkdevice.json</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -0,0 +1,17 @@
{
"description": "Things implementing this interface make use of the network device discovery resource and the network device monitor. This interface makes sure all relevant information will be stored. Depending on the MonitorMode of the discovered NetworkDeviceInfo the params of this thing shall be filled in. This makes sure the monitor uses the correct method to monitor the network device presence and IP. If the Mode is MAC, just sotre the mac address param and leave the others empty. If the Mode is HostName, just fill in the host name parameter. If the Mode is IP, just the address.",
"params": [
{
"name": "address",
"type": "QString"
},
{
"name": "hostName",
"type": "QString"
},
{
"name": "macAddress",
"type": "QString"
}
]
}

View File

@ -57,6 +57,8 @@ HEADERS += \
network/arpsocket.h \ network/arpsocket.h \
network/macaddress.h \ network/macaddress.h \
network/macaddressdatabasereply.h \ network/macaddressdatabasereply.h \
network/macaddressinfo.h \
network/macaddressinfos.h \
network/networkdevicediscovery.h \ network/networkdevicediscovery.h \
network/networkdevicediscoveryreply.h \ network/networkdevicediscoveryreply.h \
network/networkdeviceinfo.h \ network/networkdeviceinfo.h \
@ -72,6 +74,7 @@ HEADERS += \
types/browseraction.h \ types/browseraction.h \
types/interfaceactiontype.h \ types/interfaceactiontype.h \
types/interfaceeventtype.h \ types/interfaceeventtype.h \
types/interfaceparamtype.h \
types/interfacestatetype.h \ types/interfacestatetype.h \
types/mediabrowseritem.h \ types/mediabrowseritem.h \
types/thingclass.h \ types/thingclass.h \
@ -174,6 +177,8 @@ SOURCES += \
network/arpsocket.cpp \ network/arpsocket.cpp \
network/macaddress.cpp \ network/macaddress.cpp \
network/macaddressdatabasereply.cpp \ network/macaddressdatabasereply.cpp \
network/macaddressinfo.cpp \
network/macaddressinfos.cpp \
network/networkdevicediscovery.cpp \ network/networkdevicediscovery.cpp \
network/networkdeviceinfo.cpp \ network/networkdeviceinfo.cpp \
network/networkdeviceinfos.cpp \ network/networkdeviceinfos.cpp \
@ -214,6 +219,7 @@ SOURCES += \
types/browseraction.cpp \ types/browseraction.cpp \
types/interfaceactiontype.cpp \ types/interfaceactiontype.cpp \
types/interfaceeventtype.cpp \ types/interfaceeventtype.cpp \
types/interfaceparamtype.cpp \
types/interfacestatetype.cpp \ types/interfacestatetype.cpp \
types/mediabrowseritem.cpp \ types/mediabrowseritem.cpp \
types/action.cpp \ types/action.cpp \

View File

@ -0,0 +1,103 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, 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 "macaddressinfo.h"
#include <QDebug>
MacAddressInfo::MacAddressInfo()
{
}
MacAddressInfo::MacAddressInfo(const MacAddress &macAddress)
: m_macAddress{macAddress}
{
}
MacAddressInfo::MacAddressInfo(const MacAddress &macAddress, const QString &vendorName)
: m_macAddress{macAddress},
m_vendorName{vendorName},
m_vendorNameSet{true}
{
}
MacAddress MacAddressInfo::macAddress() const
{
return m_macAddress;
}
QString MacAddressInfo::vendorName() const
{
return m_vendorName;
}
void MacAddressInfo::setVendorName(const QString &vendorName)
{
m_vendorName = vendorName;
m_vendorNameSet = true;
}
bool MacAddressInfo::isValid() const
{
return !m_macAddress.isNull();
}
bool MacAddressInfo::isComplete() const
{
return isValid() && m_vendorNameSet;
}
bool MacAddressInfo::operator==(const MacAddressInfo &other) const
{
return m_macAddress == other.macAddress() &&
m_vendorName == other.vendorName() &&
isComplete() == other.isComplete();
}
bool MacAddressInfo::operator!=(const MacAddressInfo &other) const
{
return !operator==(other);
}
QDebug operator<<(QDebug debug, const MacAddressInfo &addressInfo)
{
QDebugStateSaver saver(debug);
debug.nospace() << addressInfo.macAddress().toString() << " (";
if (addressInfo.vendorName().isEmpty()) {
debug.nospace() << "unknown";
} else {
debug.nospace() << addressInfo.vendorName();
}
debug.nospace() << ")";
return debug;
}

View File

@ -0,0 +1,64 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, 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 MACADDRESSINFO_H
#define MACADDRESSINFO_H
#include "macaddress.h"
class MacAddressInfo
{
public:
explicit MacAddressInfo();
explicit MacAddressInfo(const MacAddress &macAddress);
explicit MacAddressInfo(const MacAddress &macAddress, const QString &vendorName);
MacAddress macAddress() const;
QString vendorName() const;
void setVendorName(const QString &vendorName);
bool isValid() const;
bool isComplete() const;
bool operator==(const MacAddressInfo &other) const;
bool operator!=(const MacAddressInfo &other) const;
private:
MacAddress m_macAddress;
QString m_vendorName;
bool m_vendorNameSet = false;
};
QDebug operator<<(QDebug debug, const MacAddressInfo &addressInfo);
#endif // MACADDRESSINFO_H

View File

@ -0,0 +1,118 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, 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 "macaddressinfos.h"
MacAddressInfos::MacAddressInfos()
{
}
MacAddressInfos::MacAddressInfos(const QVector<MacAddressInfo> &other)
: QVector<MacAddressInfo>(other)
{
}
int MacAddressInfos::indexFromMacAddress(const QString &macAddress)
{
return indexFromMacAddress(MacAddress(macAddress));
}
int MacAddressInfos::indexFromMacAddress(const MacAddress &macAddress)
{
for (int i = 0; i < size(); i++) {
if (at(i).macAddress() == macAddress) {
return i;
}
}
return -1;
}
bool MacAddressInfos::hasMacAddress(const QString &macAddress)
{
return indexFromMacAddress(macAddress) >= 0;
}
bool MacAddressInfos::hasMacAddress(const MacAddress &macAddress)
{
return indexFromMacAddress(macAddress) >= 0;
}
MacAddressInfo MacAddressInfos::get(const QString &macAddress) const
{
return get(MacAddress(macAddress));
}
MacAddressInfo MacAddressInfos::get(const MacAddress &macAddress) const
{
foreach (const MacAddressInfo &info, *this) {
if (info.macAddress() == macAddress) {
return info;
}
}
return MacAddressInfo();
}
void MacAddressInfos::removeMacAddress(const QString &macAddress)
{
removeMacAddress(MacAddress(macAddress));
}
void MacAddressInfos::removeMacAddress(const MacAddress &macAddress)
{
for (int i = 0; i < size(); i++) {
if (MacAddress(at(i).macAddress()) == macAddress) {
remove(i);
}
}
}
void MacAddressInfos::sortInfos()
{
std::sort(this->begin(), this->end(), [](const MacAddressInfo& a, const MacAddressInfo& b) {
return a.macAddress().toByteArray() < b.macAddress().toByteArray();
});
}
bool MacAddressInfos::isComplete() const
{
bool complete = true;
foreach (const MacAddressInfo &info, *this) {
if (!info.isComplete()) {
complete = false;
break;
}
}
return complete;
}

View File

@ -0,0 +1,60 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, 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 MACADDRESSINFOS_H
#define MACADDRESSINFOS_H
#include <QVector>
#include "macaddressinfo.h"
class MacAddressInfos : public QVector<MacAddressInfo>
{
public:
explicit MacAddressInfos();
MacAddressInfos(const QVector<MacAddressInfo> &other);
int indexFromMacAddress(const QString &macAddress);
int indexFromMacAddress(const MacAddress &macAddress);
bool hasMacAddress(const QString &macAddress);
bool hasMacAddress(const MacAddress &macAddress);
MacAddressInfo get(const QString &macAddress) const;
MacAddressInfo get(const MacAddress &macAddress) const;
void removeMacAddress(const QString &macAddress);
void removeMacAddress(const MacAddress &macAddress);
void sortInfos();
bool isComplete() const;
};
#endif // MACADDRESSINFOS_H

View File

@ -44,6 +44,8 @@
#include "macaddressdatabasereply.h" #include "macaddressdatabasereply.h"
#include "networkdevicediscoveryreply.h" #include "networkdevicediscoveryreply.h"
#include "integrations/thing.h"
class LIBNYMEA_EXPORT NetworkDeviceDiscovery : public HardwareResource class LIBNYMEA_EXPORT NetworkDeviceDiscovery : public HardwareResource
{ {
Q_OBJECT Q_OBJECT
@ -55,19 +57,18 @@ public:
virtual bool running() const = 0; virtual bool running() const = 0;
virtual NetworkDeviceMonitor *registerMonitor(const MacAddress &macAddress) = 0; virtual NetworkDeviceMonitor *registerMonitor(Thing *thing) = 0;
virtual void unregisterMonitor(const MacAddress &macAddress) = 0;
virtual void unregisterMonitor(NetworkDeviceMonitor *networkDeviceMonitor) = 0; virtual void unregisterMonitor(NetworkDeviceMonitor *networkDeviceMonitor) = 0;
virtual PingReply *ping(const QHostAddress &address, uint retries = 3) = 0; virtual PingReply *ping(const QHostAddress &address, uint retries = 3) = 0;
virtual PingReply *ping(const QString &hostName, uint retries = 3) = 0;
virtual MacAddressDatabaseReply *lookupMacAddress(const QString &macAddress) = 0; virtual MacAddressDatabaseReply *lookupMacAddress(const QString &macAddress) = 0;
virtual MacAddressDatabaseReply *lookupMacAddress(const MacAddress &macAddress) = 0; virtual MacAddressDatabaseReply *lookupMacAddress(const MacAddress &macAddress) = 0;
virtual bool sendArpRequest(const QHostAddress &address) = 0; virtual bool sendArpRequest(const QHostAddress &address) = 0;
virtual QHash<MacAddress, NetworkDeviceInfo> cache() const = 0; virtual NetworkDeviceInfos cache() const = 0;
signals: signals:
void runningChanged(bool running); void runningChanged(bool running);

View File

@ -44,17 +44,11 @@ public:
virtual ~NetworkDeviceDiscoveryReply() = default; virtual ~NetworkDeviceDiscoveryReply() = default;
virtual NetworkDeviceInfos networkDeviceInfos() const = 0; virtual NetworkDeviceInfos networkDeviceInfos() const = 0;
virtual NetworkDeviceInfos virtualNetworkDeviceInfos() const = 0;
virtual bool isFinished() const = 0; virtual bool isFinished() const = 0;
signals: signals:
// Emitted whenever a certain host address has been pinged successfully
void hostAddressDiscovered(const QHostAddress &address); void hostAddressDiscovered(const QHostAddress &address);
// Emited whenerver a valid NetworkDeviceInfo has been added
void networkDeviceInfoAdded(const NetworkDeviceInfo &networkDeviceInfo);
void finished(); void finished();
}; };

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright 2013 - 2022, nymea GmbH * Copyright 2013 - 2024, nymea GmbH
* Contact: contact@nymea.io * Contact: contact@nymea.io
* *
* This file is part of nymea. * This file is part of nymea.
@ -36,38 +36,16 @@ NetworkDeviceInfo::NetworkDeviceInfo()
} }
NetworkDeviceInfo::NetworkDeviceInfo(const QString &macAddress): NetworkDeviceInfo::NetworkDeviceInfo(const QString &macAddress)
m_macAddress(macAddress)
{ {
m_macAddressSet = true; addMacAddress(MacAddress(macAddress));
} }
NetworkDeviceInfo::NetworkDeviceInfo(const QHostAddress &address): NetworkDeviceInfo::NetworkDeviceInfo(const QHostAddress &address):
m_address(address) m_address{address},
m_addressSet{true}
{ {
m_addressSet = true;
}
QString NetworkDeviceInfo::macAddress() const
{
return m_macAddress;
}
void NetworkDeviceInfo::setMacAddress(const QString &macAddress)
{
m_macAddress = macAddress;
m_macAddressSet = true;
}
QString NetworkDeviceInfo::macAddressManufacturer() const
{
return m_macAddressManufacturer;
}
void NetworkDeviceInfo::setMacAddressManufacturer(const QString &macAddressManufacturer)
{
m_macAddressManufacturer = macAddressManufacturer;
m_macAddressManufacturerSet = true;
} }
QHostAddress NetworkDeviceInfo::address() const QHostAddress NetworkDeviceInfo::address() const
@ -92,6 +70,33 @@ void NetworkDeviceInfo::setHostName(const QString &hostName)
m_hostNameSet = true; m_hostNameSet = true;
} }
MacAddressInfos NetworkDeviceInfo::macAddressInfos() const
{
return m_macAddressInfos;
}
void NetworkDeviceInfo::addMacAddress(const MacAddress &macAddress)
{
if (m_macAddressInfos.hasMacAddress(macAddress))
return;
m_macAddressInfos.append(MacAddressInfo(macAddress));
// Note: we have to sort them in order to compare MacAddressInfos
m_macAddressInfos.sortInfos();
}
void NetworkDeviceInfo::addMacAddress(const MacAddress &macAddress, const QString &vendorName)
{
int index = m_macAddressInfos.indexFromMacAddress(macAddress);
if (index >= 0) {
m_macAddressInfos[index].setVendorName(vendorName);
} else {
m_macAddressInfos.append(MacAddressInfo(macAddress, vendorName));
// Note: we have to sort them in order to compare MacAddressInfos
m_macAddressInfos.sortInfos();
}
}
QNetworkInterface NetworkDeviceInfo::networkInterface() const QNetworkInterface NetworkDeviceInfo::networkInterface() const
{ {
return m_networkInterface; return m_networkInterface;
@ -103,34 +108,102 @@ void NetworkDeviceInfo::setNetworkInterface(const QNetworkInterface &networkInte
m_networkInterfaceSet = true; m_networkInterfaceSet = true;
} }
NetworkDeviceInfo::MonitorMode NetworkDeviceInfo::monitorMode() const
{
return m_monitorMode;
}
void NetworkDeviceInfo::setMonitorMode(MonitorMode monitorMode)
{
m_monitorMode = monitorMode;
}
bool NetworkDeviceInfo::isValid() const bool NetworkDeviceInfo::isValid() const
{ {
return (!m_address.isNull() || !MacAddress(m_macAddress).isNull()) && m_networkInterface.isValid(); return (!m_address.isNull() || m_macAddressInfos.isEmpty()) && m_networkInterface.isValid();
} }
bool NetworkDeviceInfo::isComplete() const bool NetworkDeviceInfo::isComplete() const
{ {
return m_macAddressSet && m_macAddressManufacturerSet && m_addressSet && m_hostNameSet && m_networkInterfaceSet; if (m_forceComplete)
return true;
return !m_macAddressInfos.isEmpty() && m_macAddressInfos.isComplete() && m_addressSet && m_hostNameSet && m_networkInterfaceSet;
}
void NetworkDeviceInfo::forceComplete()
{
m_forceComplete = true;
} }
QString NetworkDeviceInfo::incompleteProperties() const QString NetworkDeviceInfo::incompleteProperties() const
{ {
QStringList list; QStringList list;
if (!m_macAddressSet) list.append("MAC not set"); if (m_macAddressInfos.isEmpty())
if (!m_macAddressManufacturerSet) list.append("MAC vendor not set"); list.append("MAC address not set");
if (!m_hostNameSet) list.append("hostname not set");
if (!m_networkInterfaceSet) list.append("nework interface not set"); if (!m_macAddressInfos.isEmpty() && !m_macAddressInfos.isComplete())
list.append("MAC infos incomplete");
if (!m_hostNameSet)
list.append("host name not set");
if (!m_networkInterfaceSet)
list.append("nework interface not set");
return list.join(", "); return list.join(", ");
} }
QString NetworkDeviceInfo::thingParamValueMacAddress() const
{
QString macString;
switch (m_monitorMode) {
case MonitorModeMac:
macString = m_macAddressInfos.constFirst().macAddress().toString();
break;
default:
// In any other case we don't want to store the mac address since we can not relai on it
break;
}
return macString;
}
QString NetworkDeviceInfo::thingParamValueHostName() const
{
QString hostNameString;
switch (m_monitorMode) {
case MonitorModeMac:
case MonitorModeHostName:
hostNameString = m_hostName;
break;
default:
break;
}
return hostNameString;
}
QString NetworkDeviceInfo::thingParamValueAddress() const
{
QString addressString;
switch (m_monitorMode) {
case MonitorModeIp:
addressString = m_address.toString();
break;
default:
// In any other case we don't want to store the IP address because we want to discover it
break;
}
return addressString;
}
bool NetworkDeviceInfo::operator==(const NetworkDeviceInfo &other) const bool NetworkDeviceInfo::operator==(const NetworkDeviceInfo &other) const
{ {
return MacAddress(m_macAddress) == MacAddress(other.macAddress()) && return m_address == other.address() &&
m_address == other.address() && m_macAddressInfos == other.macAddressInfos() &&
m_hostName == other.hostName() && m_hostName == other.hostName() &&
m_macAddressManufacturer == other.macAddressManufacturer() && m_networkInterface.name() == other.networkInterface().name() &&
m_networkInterface.name() == other.networkInterface().name() && m_monitorMode == other.monitorMode() &&
isComplete() == other.isComplete(); isComplete() == other.isComplete();
} }
bool NetworkDeviceInfo::operator!=(const NetworkDeviceInfo &other) const bool NetworkDeviceInfo::operator!=(const NetworkDeviceInfo &other) const
@ -141,20 +214,30 @@ bool NetworkDeviceInfo::operator!=(const NetworkDeviceInfo &other) const
QDebug operator<<(QDebug dbg, const NetworkDeviceInfo &networkDeviceInfo) QDebug operator<<(QDebug dbg, const NetworkDeviceInfo &networkDeviceInfo)
{ {
QDebugStateSaver saver(dbg); QDebugStateSaver saver(dbg);
dbg.nospace() << "NetworkDeviceInfo(" << networkDeviceInfo.address().toString(); dbg.nospace().noquote() << "NetworkDeviceInfo(" << networkDeviceInfo.address().toString();
if (!networkDeviceInfo.macAddress().isEmpty()) dbg.nospace().noquote() << ", Monitor mode: ";
dbg.nospace() << ", " << MacAddress(networkDeviceInfo.macAddress()).toString(); switch (networkDeviceInfo.monitorMode()) {
case NetworkDeviceInfo::MonitorModeMac:
dbg.nospace().noquote() << "MAC";
break;
case NetworkDeviceInfo::MonitorModeHostName:
dbg.nospace().noquote() << "hostname";
break;
case NetworkDeviceInfo::MonitorModeIp:
dbg.nospace().noquote() << "IP";
break;
}
if (!networkDeviceInfo.macAddressManufacturer().isEmpty()) foreach (const MacAddressInfo &macInfo, networkDeviceInfo.macAddressInfos())
dbg.nospace() << " (" << networkDeviceInfo.macAddressManufacturer() << ") "; dbg.nospace().noquote() << ", " << macInfo;
if (!networkDeviceInfo.hostName().isEmpty()) if (!networkDeviceInfo.hostName().isEmpty())
dbg.nospace() << ", hostname: " << networkDeviceInfo.hostName(); dbg.nospace().noquote() << ", hostname: " << networkDeviceInfo.hostName();
if (networkDeviceInfo.networkInterface().isValid()) if (networkDeviceInfo.networkInterface().isValid())
dbg.nospace() << ", " << networkDeviceInfo.networkInterface().name(); dbg.nospace().noquote() << ", " << networkDeviceInfo.networkInterface().name();
dbg.nospace() << ")"; dbg.nospace().noquote() << ")";
return dbg; return dbg;
} }

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright 2013 - 2022, nymea GmbH * Copyright 2013 - 2024, nymea GmbH
* Contact: contact@nymea.io * Contact: contact@nymea.io
* *
* This file is part of nymea. * This file is part of nymea.
@ -38,53 +38,70 @@
#include <QNetworkInterface> #include <QNetworkInterface>
#include "libnymea.h" #include "libnymea.h"
#include "macaddressinfos.h"
class LIBNYMEA_EXPORT NetworkDeviceInfo class LIBNYMEA_EXPORT NetworkDeviceInfo
{ {
Q_GADGET
public: public:
enum MonitorMode {
MonitorModeMac = 0x01, // Unique MAC address within the network
MonitorModeHostName = 0x02, // DNS hostname available, but no MAC address or not unique MAC available
MonitorModeIp = 0x03 // Only the IP can be used to monitor, simple ping on reachable
};
Q_ENUM(MonitorMode)
explicit NetworkDeviceInfo(); explicit NetworkDeviceInfo();
explicit NetworkDeviceInfo(const QString &macAddress); explicit NetworkDeviceInfo(const QString &macAddress);
explicit NetworkDeviceInfo(const QHostAddress &address); explicit NetworkDeviceInfo(const QHostAddress &address);
QString macAddress() const;
void setMacAddress(const QString &macAddress);
QString macAddressManufacturer() const;
void setMacAddressManufacturer(const QString &macAddressManufacturer);
QHostAddress address() const; QHostAddress address() const;
void setAddress(const QHostAddress &address); void setAddress(const QHostAddress &address);
QString hostName() const; QString hostName() const;
void setHostName(const QString &hostName); void setHostName(const QString &hostName);
MacAddressInfos macAddressInfos() const;
void addMacAddress(const MacAddress &macAddress);
void addMacAddress(const MacAddress &macAddress, const QString &vendorName);
QNetworkInterface networkInterface() const; QNetworkInterface networkInterface() const;
void setNetworkInterface(const QNetworkInterface &networkInterface); void setNetworkInterface(const QNetworkInterface &networkInterface);
MonitorMode monitorMode() const;
void setMonitorMode(MonitorMode monitorMode);
bool isValid() const; bool isValid() const;
bool isComplete() const; bool isComplete() const;
void forceComplete();
QString incompleteProperties() const; QString incompleteProperties() const;
// Helper methods for the networkdevice interface
// The fill in automaticlally the correct paramters for the
// right monitor
QString thingParamValueMacAddress() const;
QString thingParamValueHostName() const;
QString thingParamValueAddress() const;
bool operator==(const NetworkDeviceInfo &other) const; bool operator==(const NetworkDeviceInfo &other) const;
bool operator!=(const NetworkDeviceInfo &other) const; bool operator!=(const NetworkDeviceInfo &other) const;
private: private:
QHostAddress m_address; QHostAddress m_address;
QString m_macAddress; MacAddressInfos m_macAddressInfos;
QString m_macAddressManufacturer;
QString m_hostName; QString m_hostName;
QNetworkInterface m_networkInterface; QNetworkInterface m_networkInterface;
MonitorMode m_monitorMode = MonitorModeMac;
bool m_macAddressSet = false;
bool m_macAddressManufacturerSet = false;
bool m_addressSet = false; bool m_addressSet = false;
bool m_hostNameSet = false; bool m_hostNameSet = false;
bool m_networkInterfaceSet = false; bool m_networkInterfaceSet = false;
bool m_forceComplete = false;
}; };
QDebug operator<<(QDebug debug, const NetworkDeviceInfo &networkDeviceInfo); QDebug operator<<(QDebug debug, const NetworkDeviceInfo &networkDeviceInfo);
#endif // NETWORKDEVICEINFO_H #endif // NETWORKDEVICEINFO_H

View File

@ -56,15 +56,10 @@ int NetworkDeviceInfos::indexFromHostAddress(const QHostAddress &address)
return -1; return -1;
} }
int NetworkDeviceInfos::indexFromMacAddress(const QString &macAddress) int NetworkDeviceInfos::indexFromHostName(const QString &hostName)
{ {
return indexFromMacAddress(MacAddress(macAddress)); for (int i = 0; i < this->size(); i++) {
} if (at(i).hostName() == hostName) {
int NetworkDeviceInfos::indexFromMacAddress(const MacAddress &macAddress)
{
for (int i = 0; i < size(); i++) {
if (MacAddress(at(i).macAddress()) == macAddress) {
return i; return i;
} }
} }
@ -72,6 +67,23 @@ int NetworkDeviceInfos::indexFromMacAddress(const MacAddress &macAddress)
return -1; return -1;
} }
QList<int> NetworkDeviceInfos::indexFromMacAddress(const QString &macAddress)
{
return indexFromMacAddress(MacAddress(macAddress));
}
QList<int> NetworkDeviceInfos::indexFromMacAddress(const MacAddress &macAddress)
{
QList<int> indices;
for (int i = 0; i < size(); i++) {
if (at(i).macAddressInfos().hasMacAddress(macAddress)) {
indices << i;
}
}
return indices;
}
bool NetworkDeviceInfos::hasHostAddress(const QHostAddress &address) bool NetworkDeviceInfos::hasHostAddress(const QHostAddress &address)
{ {
return indexFromHostAddress(address) >= 0; return indexFromHostAddress(address) >= 0;
@ -79,12 +91,12 @@ bool NetworkDeviceInfos::hasHostAddress(const QHostAddress &address)
bool NetworkDeviceInfos::hasMacAddress(const QString &macAddress) bool NetworkDeviceInfos::hasMacAddress(const QString &macAddress)
{ {
return indexFromMacAddress(macAddress) >= 0; return !indexFromMacAddress(macAddress).isEmpty();
} }
bool NetworkDeviceInfos::hasMacAddress(const MacAddress &macAddress) bool NetworkDeviceInfos::hasMacAddress(const MacAddress &macAddress)
{ {
return indexFromMacAddress(macAddress) >= 0; return !indexFromMacAddress(macAddress).isEmpty();
} }
NetworkDeviceInfo NetworkDeviceInfos::get(const QHostAddress &address) const NetworkDeviceInfo NetworkDeviceInfos::get(const QHostAddress &address) const
@ -98,31 +110,10 @@ NetworkDeviceInfo NetworkDeviceInfos::get(const QHostAddress &address) const
return NetworkDeviceInfo(); return NetworkDeviceInfo();
} }
NetworkDeviceInfo NetworkDeviceInfos::get(const QString &macAddress) const void NetworkDeviceInfos::removeHostAddress(const QHostAddress &address)
{
foreach (const NetworkDeviceInfo &networkDeviceInfo, *this) {
if (networkDeviceInfo.macAddress() == macAddress) {
return networkDeviceInfo;
}
}
return NetworkDeviceInfo();
}
NetworkDeviceInfo NetworkDeviceInfos::get(const MacAddress &macAddress) const
{
return get(macAddress.toString());
}
void NetworkDeviceInfos::removeMacAddress(const QString &macAddress)
{
removeMacAddress(MacAddress(macAddress));
}
void NetworkDeviceInfos::removeMacAddress(const MacAddress &macAddress)
{ {
for (int i = 0; i < size(); i++) { for (int i = 0; i < size(); i++) {
if (MacAddress(at(i).macAddress()) == macAddress) { if (at(i).address() == address) {
remove(i); remove(i);
} }
} }

View File

@ -39,25 +39,21 @@
class LIBNYMEA_EXPORT NetworkDeviceInfos : public QVector<NetworkDeviceInfo> class LIBNYMEA_EXPORT NetworkDeviceInfos : public QVector<NetworkDeviceInfo>
{ {
public: public:
explicit NetworkDeviceInfos(); explicit NetworkDeviceInfos();
NetworkDeviceInfos(const QVector<NetworkDeviceInfo> &other); NetworkDeviceInfos(const QVector<NetworkDeviceInfo> &other);
int indexFromHostAddress(const QHostAddress &address); int indexFromHostAddress(const QHostAddress &address);
int indexFromMacAddress(const QString &macAddress); int indexFromHostName(const QString &hostName);
int indexFromMacAddress(const MacAddress &macAddress); QList<int> indexFromMacAddress(const QString &macAddress);
QList<int> indexFromMacAddress(const MacAddress &macAddress);
bool hasHostAddress(const QHostAddress &address); bool hasHostAddress(const QHostAddress &address);
bool hasMacAddress(const QString &macAddress); bool hasMacAddress(const QString &macAddress);
bool hasMacAddress(const MacAddress &macAddress); bool hasMacAddress(const MacAddress &macAddress);
NetworkDeviceInfo get(const QHostAddress &address) const; NetworkDeviceInfo get(const QHostAddress &address) const;
NetworkDeviceInfo get(const QString &macAddress) const; void removeHostAddress(const QHostAddress &address);
NetworkDeviceInfo get(const MacAddress &macAddress) const;
void removeMacAddress(const QString &macAddress);
void removeMacAddress(const MacAddress &macAddress);
void sortNetworkDevices(); void sortNetworkDevices();

View File

@ -40,12 +40,20 @@ NetworkDeviceMonitor::NetworkDeviceMonitor(QObject *parent) :
QDebug operator<<(QDebug dbg, NetworkDeviceMonitor *networkDeviceMonitor) QDebug operator<<(QDebug dbg, NetworkDeviceMonitor *networkDeviceMonitor)
{ {
QDebugStateSaver saver(dbg); QDebugStateSaver saver(dbg);
dbg.nospace() << "NetworkDeviceMonitor(" << networkDeviceMonitor->macAddress().toString(); dbg.nospace() << "NetworkDeviceMonitor(";
if (!networkDeviceMonitor->networkDeviceInfo().macAddressManufacturer().isEmpty()) switch (networkDeviceMonitor->monitorMode()) {
dbg.nospace() << " - " << networkDeviceMonitor->networkDeviceInfo().macAddressManufacturer(); case NetworkDeviceInfo::MonitorModeMac:
dbg.nospace() << "Mode: MAC, " << networkDeviceMonitor->macAddress().toString();
break;
case NetworkDeviceInfo::MonitorModeHostName:
dbg.nospace() << "Mode: host name, " << networkDeviceMonitor->hostName();
break;
case NetworkDeviceInfo::MonitorModeIp:
dbg.nospace() << "Mode: IP address, " << networkDeviceMonitor->address().toString();
break;
}
dbg.nospace() << ", " << networkDeviceMonitor->networkDeviceInfo().address().toString();
dbg.nospace() << ", " << (networkDeviceMonitor->reachable() ? "reachable" : "not reachable"); dbg.nospace() << ", " << (networkDeviceMonitor->reachable() ? "reachable" : "not reachable");
dbg.nospace() << ")"; dbg.nospace() << ")";
return dbg; return dbg;

View File

@ -35,7 +35,6 @@
#include <QDateTime> #include <QDateTime>
#include "libnymea.h" #include "libnymea.h"
#include "macaddress.h"
#include "networkdeviceinfo.h" #include "networkdeviceinfo.h"
class LIBNYMEA_EXPORT NetworkDeviceMonitor : public QObject class LIBNYMEA_EXPORT NetworkDeviceMonitor : public QObject
@ -46,8 +45,14 @@ public:
explicit NetworkDeviceMonitor(QObject *parent = nullptr); explicit NetworkDeviceMonitor(QObject *parent = nullptr);
virtual ~NetworkDeviceMonitor() = default; virtual ~NetworkDeviceMonitor() = default;
// Monitor parameters defining the monitor mode
virtual MacAddress macAddress() const = 0; virtual MacAddress macAddress() const = 0;
virtual QString hostName() const = 0;
virtual QHostAddress address() const = 0;
virtual NetworkDeviceInfo::MonitorMode monitorMode() const = 0;
// Actual network device information
virtual NetworkDeviceInfo networkDeviceInfo() const = 0; virtual NetworkDeviceInfo networkDeviceInfo() const = 0;
virtual bool reachable() const = 0; virtual bool reachable() const = 0;
@ -60,7 +65,7 @@ signals:
void reachableChanged(bool reachable); void reachableChanged(bool reachable);
void lastSeenChanged(const QDateTime &lastSeen); void lastSeenChanged(const QDateTime &lastSeen);
void networkDeviceInfoChanged(const NetworkDeviceInfo &networkDeviceInfo); void networkDeviceInfoChanged(const NetworkDeviceInfo &networkDeviceInfo);
void pingRetriesChanged(uint pingRetries);
}; };
QDebug operator<<(QDebug debug, NetworkDeviceMonitor *networkDeviceMonitor); QDebug operator<<(QDebug debug, NetworkDeviceMonitor *networkDeviceMonitor);

View File

@ -141,6 +141,18 @@ PingReply *Ping::ping(const QHostAddress &hostAddress, bool lookupHost, uint ret
return reply; return reply;
} }
PingReply *Ping::ping(const QString &hostName, uint retries)
{
// Make first the host lookup, then the ping the first address and save it in the reply
PingReply *reply = createReply(hostName);
reply->m_retries = retries;
// Note: due to a Qt bug < 5.9 we need to use old SLOT style and cannot make use of lambda here
int lookupId = QHostInfo::lookupHost(hostName, this, SLOT(onHostLookupFinished(QHostInfo)));
m_pendingHostAddressLookups.insert(lookupId, reply);
return reply;
}
void Ping::sendNextReply() void Ping::sendNextReply()
{ {
if (m_queueTimer->isActive()) if (m_queueTimer->isActive())
@ -150,7 +162,7 @@ void Ping::sendNextReply()
return; return;
m_currentReply = m_replyQueue.dequeue(); m_currentReply = m_replyQueue.dequeue();
qCDebug(dcPing()) << "Send next reply " << m_currentReply->targetHostAddress().toString() << QString("0x%1").arg(m_currentReply->requestId(), 4, 16, QChar('0')) << ", " << m_replyQueue.count() << "left in queue"; qCDebug(dcPing()).nospace().noquote() << "Send next reply " << m_currentReply->targetHostAddress().toString() << " ID: " << QString("0x%1").arg(m_currentReply->requestId(), 4, 16, QChar('0')) << ", " << m_replyQueue.count() << "left in queue";
m_queueTimer->start(); m_queueTimer->start();
QTimer::singleShot(0, this, [=]() { QTimer::singleShot(0, this, [=]() {
if (!m_currentReply) if (!m_currentReply)
@ -209,8 +221,8 @@ void Ping::performPing(PingReply *reply)
} }
qCDebug(dcPingTraffic()) << "Send ICMP echo request" << reply->targetHostAddress().toString() << ICMP_PACKET_SIZE << "[Bytes]" qCDebug(dcPingTraffic()) << "Send ICMP echo request" << reply->targetHostAddress().toString() << ICMP_PACKET_SIZE << "[Bytes]"
<< "ID:" << QString("0x%1").arg(reply->requestId(), 4, 16, QChar('0')) << "ID:" << QString("0x%1").arg(reply->requestId(), 4, 16, QChar('0'))
<< "Sequence:" << reply->sequenceNumber(); << "Sequence:" << reply->sequenceNumber();
// Send packet to the target ip // Send packet to the target ip
int bytesSent = sendto(m_socketDescriptor, &requestPacket, sizeof(requestPacket), 0, (struct sockaddr *)&pingAddress, sizeof(pingAddress)); int bytesSent = sendto(m_socketDescriptor, &requestPacket, sizeof(requestPacket), 0, (struct sockaddr *)&pingAddress, sizeof(pingAddress));
@ -329,14 +341,43 @@ PingReply *Ping::createReply(const QHostAddress &hostAddress)
return reply; return reply;
} }
PingReply *Ping::createReply(const QString &hostName)
{
PingReply *reply = new PingReply(this);
reply->m_hostName = hostName;
connect(reply, &PingReply::timeout, this, [=](){
// Note: this is not the ICMP timeout, here we actually got nothing from anybody...
finishReply(reply, PingReply::ErrorTimeout);
});
connect(reply, &PingReply::aborted, this, [=](){
finishReply(reply, PingReply::ErrorAborted);
});
connect(reply, &PingReply::finished, this, [=](){
cleanUpReply(reply);
reply->deleteLater();
});
// Just in case the reply get's deleted before beeing able to finish ...
connect(reply, &PingReply::destroyed, this, [this, reply](){
cleanUpReply(reply);
});
return reply;
}
void Ping::finishReply(PingReply *reply, PingReply::Error error) void Ping::finishReply(PingReply *reply, PingReply::Error error)
{ {
// Check if we should retry // Check if we should retry
if (reply->m_retryCount >= reply->m_retries || if (reply->m_retryCount >= reply->m_retries ||
error == PingReply::ErrorNoError || error == PingReply::ErrorNoError ||
error == PingReply::ErrorAborted || error == PingReply::ErrorAborted ||
error == PingReply::ErrorInvalidHostAddress || error == PingReply::ErrorInvalidHostAddress ||
error == PingReply::ErrorPermissionDenied) { error == PingReply::ErrorPermissionDenied||
error == PingReply::ErrorHostNameLookupFailed ||
error == PingReply::ErrorHostNameNotFound) {
// No retry, we are done // No retry, we are done
reply->m_error = error; reply->m_error = error;
reply->m_timer->stop(); reply->m_timer->stop();
@ -374,11 +415,11 @@ void Ping::cleanUpReply(PingReply *reply)
m_pendingReplies.remove(reply->requestId()); m_pendingReplies.remove(reply->requestId());
m_replyQueue.removeAll(reply); m_replyQueue.removeAll(reply);
if (m_pendingHostLookups.values().contains(reply)) { if (m_pendingHostNameLookups.values().contains(reply)) {
// Abort any pending host lookups, the reply has been finished // Abort any pending host lookups, the reply has been finished
int lookupId = m_pendingHostLookups.key(reply); int lookupId = m_pendingHostNameLookups.key(reply);
QHostInfo::abortHostLookup(lookupId); QHostInfo::abortHostLookup(lookupId);
m_pendingHostLookups.remove(lookupId); m_pendingHostNameLookups.remove(lookupId);
} }
} }
@ -404,19 +445,19 @@ void Ping::onSocketReadyRead(int socketDescriptor)
QHostAddress destinationAddress(qFromBigEndian(ipHeader->daddr)); QHostAddress destinationAddress(qFromBigEndian(ipHeader->daddr));
qCDebug(dcPingTraffic()) << "IP header: Lenght" << ipHeaderLength qCDebug(dcPingTraffic()) << "IP header: Lenght" << ipHeaderLength
<< "Sender:" << senderAddress.toString() << "Sender:" << senderAddress.toString()
<< "Destination:" << destinationAddress.toString() << "Destination:" << destinationAddress.toString()
<< "Size:" << htons(ipHeader->tot_len) << "B" << "Size:" << htons(ipHeader->tot_len) << "B"
<< "TTL" << ipHeader->ttl; << "TTL" << ipHeader->ttl;
struct icmp *responsePacket = reinterpret_cast<struct icmp *>(receiveBuffer + ipHeaderLength); struct icmp *responsePacket = reinterpret_cast<struct icmp *>(receiveBuffer + ipHeaderLength);
quint16 icmpId = htons(responsePacket->icmp_id); quint16 icmpId = htons(responsePacket->icmp_id);
quint16 icmpSequnceNumber = htons(responsePacket->icmp_seq); quint16 icmpSequnceNumber = htons(responsePacket->icmp_seq);
qCDebug(dcPingTraffic()) << "ICMP packt (Size:" << icmpPacketSize << "Bytes):" qCDebug(dcPingTraffic()) << "ICMP packt (Size:" << icmpPacketSize << "Bytes):"
<< "Type" << responsePacket->icmp_type << "Type" << responsePacket->icmp_type
<< "Code:" << responsePacket->icmp_code << "Code:" << responsePacket->icmp_code
<< "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0')) << "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0'))
<< "Sequence:" << icmpSequnceNumber; << "Sequence:" << icmpSequnceNumber;
if (responsePacket->icmp_type == ICMP_ECHOREPLY) { if (responsePacket->icmp_type == ICMP_ECHOREPLY) {
@ -447,14 +488,14 @@ void Ping::onSocketReadyRead(int socketDescriptor)
reply->m_duration = qRound((receiveTimeValue.tv_sec * 1000 + (double)receiveTimeValue.tv_usec / 1000) * 100) / 100.0; reply->m_duration = qRound((receiveTimeValue.tv_sec * 1000 + (double)receiveTimeValue.tv_usec / 1000) * 100) / 100.0;
qCDebug(dcPing()) << "Received ICMP response" << reply->targetHostAddress().toString() << ICMP_PACKET_SIZE << "[Bytes]" qCDebug(dcPing()) << "Received ICMP response" << reply->targetHostAddress().toString() << ICMP_PACKET_SIZE << "[Bytes]"
<< "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0')) << "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0'))
<< "Sequence:" << icmpSequnceNumber << "Sequence:" << icmpSequnceNumber
<< "Time:" << reply->duration() << "[ms]"; << "Time:" << reply->duration() << "[ms]";
if (reply->doHostLookup()) { if (reply->doHostLookup()) {
// Note: due to a Qt bug < 5.9 we need to use old SLOT style and cannot make use of lambda here // Note: due to a Qt bug < 5.9 we need to use old SLOT style and cannot make use of lambda here
int lookupId = QHostInfo::lookupHost(senderAddress.toString(), this, SLOT(onHostLookupFinished(QHostInfo))); int lookupId = QHostInfo::lookupHost(senderAddress.toString(), this, SLOT(onHostLookupFinished(QHostInfo)));
m_pendingHostLookups.insert(lookupId, reply); m_pendingHostNameLookups.insert(lookupId, reply);
// Finish the reply after the host lookup has finished // Finish the reply after the host lookup has finished
} else { } else {
finishReply(reply, PingReply::ErrorNoError); finishReply(reply, PingReply::ErrorNoError);
@ -472,20 +513,20 @@ void Ping::onSocketReadyRead(int socketDescriptor)
QHostAddress nestedDestinationAddress(qFromBigEndian(nestedIpHeader->daddr)); QHostAddress nestedDestinationAddress(qFromBigEndian(nestedIpHeader->daddr));
qCDebug(dcPingTraffic()) << "++ IP header: Lenght" << nestedIpHeaderLength qCDebug(dcPingTraffic()) << "++ IP header: Lenght" << nestedIpHeaderLength
<< "Sender:" << nestedSenderAddress.toString() << "Sender:" << nestedSenderAddress.toString()
<< "Destination:" << nestedDestinationAddress.toString() << "Destination:" << nestedDestinationAddress.toString()
<< "Size:" << htons(nestedIpHeader->tot_len) << "B" << "Size:" << htons(nestedIpHeader->tot_len) << "B"
<< "TTL" << ipHeader->ttl; << "TTL" << ipHeader->ttl;
struct icmp *nestedResponsePacket = reinterpret_cast<struct icmp *>(receiveBuffer + messageOffset + nestedIpHeaderLength); struct icmp *nestedResponsePacket = reinterpret_cast<struct icmp *>(receiveBuffer + messageOffset + nestedIpHeaderLength);
icmpId = htons(nestedResponsePacket->icmp_id); icmpId = htons(nestedResponsePacket->icmp_id);
icmpSequnceNumber = htons(nestedResponsePacket->icmp_seq); icmpSequnceNumber = htons(nestedResponsePacket->icmp_seq);
qCDebug(dcPingTraffic()) << "++ ICMP packt (Size:" << nestedIcmpPacketSize << "Bytes):" qCDebug(dcPingTraffic()) << "++ ICMP packt (Size:" << nestedIcmpPacketSize << "Bytes):"
<< "Type" << nestedResponsePacket->icmp_type << "Type" << nestedResponsePacket->icmp_type
<< "Code:" << nestedResponsePacket->icmp_code << "Code:" << nestedResponsePacket->icmp_code
<< "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0')) << "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0'))
<< "Sequence:" << icmpSequnceNumber; << "Sequence:" << icmpSequnceNumber;
qCDebug(dcPing()) << "ICMP destination unreachable" << nestedDestinationAddress.toString() qCDebug(dcPing()) << "ICMP destination unreachable" << nestedDestinationAddress.toString()
<< "Code:" << nestedResponsePacket->icmp_code << "Code:" << nestedResponsePacket->icmp_code
@ -495,9 +536,9 @@ void Ping::onSocketReadyRead(int socketDescriptor)
PingReply *reply = m_pendingReplies.value(icmpId); PingReply *reply = m_pendingReplies.value(icmpId);
if (!reply) { if (!reply) {
qCDebug(dcPingTraffic()) << "No pending reply for ping echo response unreachable with ID" qCDebug(dcPingTraffic()) << "No pending reply for ping echo response unreachable with ID"
<< QString("0x%1").arg(icmpId, 4, 16, QChar('0')) << QString("0x%1").arg(icmpId, 4, 16, QChar('0'))
<< "Sequence:" << icmpSequnceNumber << "Sequence:" << icmpSequnceNumber
<< "from" << nestedSenderAddress.toString() << "to" << nestedDestinationAddress.toString(); << "from" << nestedSenderAddress.toString() << "to" << nestedDestinationAddress.toString();
return; return;
} }
@ -508,20 +549,74 @@ void Ping::onSocketReadyRead(int socketDescriptor)
void Ping::onHostLookupFinished(const QHostInfo &info) void Ping::onHostLookupFinished(const QHostInfo &info)
{ {
PingReply *reply = m_pendingHostLookups.value(info.lookupId()); if (m_pendingHostNameLookups.contains(info.lookupId())) {
if (!reply) {
qCWarning(dcPing()) << "Could not find reply after host lookup.";
return;
}
if (info.error() != QHostInfo::NoError) { PingReply *reply = m_pendingHostNameLookups.take(info.lookupId());
qCWarning(dcPing()) << "Failed to look up hostname after successfull ping" << reply->targetHostAddress().toString() << info.error(); if (!reply) {
} else { qCWarning(dcPing()) << "Could not find ping reply after finished host lookup" << info.hostName() << info.addresses() << info.errorString();
qCDebug(dcPing()) << "Looked up hostname after successfull ping" << reply->targetHostAddress().toString() << info.hostName(); return;
if (info.hostName() != reply->targetHostAddress().toString()) {
reply->m_hostName = info.hostName();
} }
}
finishReply(reply, PingReply::ErrorNoError); PingReply::Error pingError = PingReply::ErrorNoError;
switch(info.error()) {
case QHostInfo::NoError:
qCDebug(dcPing()) << "Looked up hostname after successfull ping" << reply->targetHostAddress().toString() << info.hostName();
if (info.hostName() != reply->targetHostAddress().toString())
reply->m_hostName = info.hostName();
break;
case QHostInfo::HostNotFound:
qCWarning(dcPing()) << "Unable to find hostname:" << reply->hostName() << info.errorString();
pingError = PingReply::ErrorHostNameNotFound;
break;
default:
qCWarning(dcPing()) << "Failed to lookup hostname" << (reply->targetHostAddress().isNull() ? reply->hostName() : reply->targetHostAddress().toString()) << info.errorString();
pingError = PingReply::ErrorHostNameLookupFailed;
break;
}
finishReply(reply, pingError);
} else if (m_pendingHostAddressLookups.contains(info.lookupId())) {
PingReply *reply = m_pendingHostAddressLookups.take(info.lookupId());
if (!reply) {
qCWarning(dcPing()) << "Could not find ping reply after finished host lookup" << info.hostName() << info.addresses() << info.errorString();
return;
}
PingReply::Error pingError = PingReply::ErrorNoError;
switch(info.error()) {
case QHostInfo::NoError:
qCDebug(dcPing()) << "Looked up address for hostname finished successfully" << info.hostName() << info.addresses();
if (info.addresses().isEmpty()) {
qCWarning(dcPing()) << "Looked up address finished succesfully but there are no addresses available for" << info.hostName();
pingError = PingReply::ErrorHostNameNotFound;
} else {
reply->m_targetHostAddress = info.addresses().first();
reply->m_networkInterface = NetworkUtils::getInterfaceForHostaddress(reply->targetHostAddress());
pingError = PingReply::ErrorNoError;
}
break;
case QHostInfo::HostNotFound:
qCDebug(dcPing()) << "Unable to find hostname:" << reply->hostName() << info.errorString();
pingError = PingReply::ErrorHostNameNotFound;
break;
default:
qCWarning(dcPing()) << "Failed to lookup hostname" << (reply->targetHostAddress().isNull() ? reply->hostName() : reply->targetHostAddress().toString()) << info.errorString();
pingError = PingReply::ErrorHostNameLookupFailed;
break;
}
if (pingError != PingReply::ErrorNoError) {
// Host lookup failed, we cannot continue, nothing to ping
finishReply(reply, pingError);
} else {
// Ping the resolved host address
m_replyQueue.enqueue(reply);
sendNextReply();
}
} else {
qCWarning(dcPing()) << "Host name lookup finished but we have no ping reply for it. Ignoring looked up information" << info.hostName() << info.addresses() << info.error();
}
} }

View File

@ -67,6 +67,8 @@ public:
PingReply *ping(const QHostAddress &hostAddress, uint retries = 3); PingReply *ping(const QHostAddress &hostAddress, uint retries = 3);
PingReply *ping(const QHostAddress &hostAddress, bool lookupHost, uint retries = 3); PingReply *ping(const QHostAddress &hostAddress, bool lookupHost, uint retries = 3);
PingReply *ping(const QString &hostName, uint retries = 3);
signals: signals:
void availableChanged(bool available); void availableChanged(bool available);
@ -91,7 +93,8 @@ private:
QTimer *m_queueTimer = nullptr; QTimer *m_queueTimer = nullptr;
PingReply *m_currentReply = nullptr; PingReply *m_currentReply = nullptr;
void sendNextReply(); void sendNextReply();
QHash<int, PingReply *> m_pendingHostLookups; QHash<int, PingReply *> m_pendingHostNameLookups;
QHash<int, PingReply *> m_pendingHostAddressLookups;
//Error performPing(const QString &address); //Error performPing(const QString &address);
void performPing(PingReply *reply); void performPing(PingReply *reply);
@ -104,13 +107,13 @@ private:
quint16 calculateRequestId(); quint16 calculateRequestId();
PingReply *createReply(const QHostAddress &hostAddress); PingReply *createReply(const QHostAddress &hostAddress);
PingReply *createReply(const QString &hostName);
void finishReply(PingReply *reply, PingReply::Error error); void finishReply(PingReply *reply, PingReply::Error error);
void cleanUpReply(PingReply *reply); void cleanUpReply(PingReply *reply);
private slots: private slots:
void onSocketReadyRead(int socketDescriptor); void onSocketReadyRead(int socketDescriptor);
void onHostLookupFinished(const QHostInfo &info); void onHostLookupFinished(const QHostInfo &info);
}; };
#endif // PING_H #endif // PING_H

View File

@ -59,7 +59,9 @@ public:
ErrorSocketError, ErrorSocketError,
ErrorTimeout, ErrorTimeout,
ErrorHostUnreachable, ErrorHostUnreachable,
ErrorInvalidHostAddress ErrorInvalidHostAddress,
ErrorHostNameLookupFailed,
ErrorHostNameNotFound
}; };
Q_ENUM(Error) Q_ENUM(Error)

View File

@ -30,11 +30,12 @@
#include "interface.h" #include "interface.h"
Interface::Interface(const QString &name, const InterfaceActionTypes &actionTypes, const InterfaceEventTypes &eventTypes, const InterfaceStateTypes &stateTypes): Interface::Interface(const QString &name, const InterfaceParamTypes &paramTypes, const InterfaceActionTypes &actionTypes, const InterfaceEventTypes &eventTypes, const InterfaceStateTypes &stateTypes):
m_name(name), m_name{name},
m_actionTypes(actionTypes), m_paramTypes{paramTypes},
m_eventTypes(eventTypes), m_actionTypes{actionTypes},
m_stateTypes(stateTypes) m_eventTypes{eventTypes},
m_stateTypes{stateTypes}
{ {
} }
@ -44,6 +45,11 @@ QString Interface::name() const
return m_name; return m_name;
} }
InterfaceParamTypes Interface::paramTypes() const
{
return m_paramTypes;
}
InterfaceActionTypes Interface::actionTypes() const InterfaceActionTypes Interface::actionTypes() const
{ {
return m_actionTypes; return m_actionTypes;

View File

@ -31,6 +31,7 @@
#ifndef INTERFACE_H #ifndef INTERFACE_H
#define INTERFACE_H #define INTERFACE_H
#include "interfaceparamtype.h"
#include "interfaceeventtype.h" #include "interfaceeventtype.h"
#include "interfaceactiontype.h" #include "interfaceactiontype.h"
#include "interfacestatetype.h" #include "interfacestatetype.h"
@ -38,18 +39,20 @@
class Interface{ class Interface{
public: public:
Interface() = default; Interface() = default;
Interface(const QString &name, const InterfaceActionTypes &actionTypes, const InterfaceEventTypes &eventTypes, const InterfaceStateTypes &stateTypes); Interface(const QString &name, const InterfaceParamTypes &paramTypes, const InterfaceActionTypes &actionTypes, const InterfaceEventTypes &eventTypes, const InterfaceStateTypes &stateTypes);
QString name() const; QString name() const;
InterfaceParamTypes paramTypes() const;
InterfaceActionTypes actionTypes() const; InterfaceActionTypes actionTypes() const;
InterfaceEventTypes eventTypes() const; InterfaceEventTypes eventTypes() const;
InterfaceStateTypes stateTypes() const; InterfaceStateTypes stateTypes() const;
bool isValid() const; bool isValid() const;
private: private:
QUuid m_id;
QString m_name; QString m_name;
InterfaceParamTypes m_paramTypes;
InterfaceActionTypes m_actionTypes; InterfaceActionTypes m_actionTypes;
InterfaceEventTypes m_eventTypes; InterfaceEventTypes m_eventTypes;
InterfaceStateTypes m_stateTypes; InterfaceStateTypes m_stateTypes;

View File

@ -1,3 +1,33 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, 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 "interfaceactiontype.h" #include "interfaceactiontype.h"
InterfaceActionType::InterfaceActionType() InterfaceActionType::InterfaceActionType()

View File

@ -1,3 +1,33 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, 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 INTERFACEACTIONTYPE_H #ifndef INTERFACEACTIONTYPE_H
#define INTERFACEACTIONTYPE_H #define INTERFACEACTIONTYPE_H

View File

@ -1,3 +1,33 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, 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 "interfaceeventtype.h" #include "interfaceeventtype.h"
InterfaceEventType::InterfaceEventType() InterfaceEventType::InterfaceEventType()

View File

@ -1,3 +1,33 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, 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 INTERFACEEVENTTYPE_H #ifndef INTERFACEEVENTTYPE_H
#define INTERFACEEVENTTYPE_H #define INTERFACEEVENTTYPE_H

View File

@ -0,0 +1,64 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, 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 "interfaceparamtype.h"
InterfaceParamType::InterfaceParamType()
{
}
bool InterfaceParamType::optional() const
{
return m_optional;
}
void InterfaceParamType::setOptional(bool optional)
{
m_optional = optional;
}
InterfaceParamTypes::InterfaceParamTypes(const QList<InterfaceParamType> &other)
: QList<InterfaceParamType>(other)
{
}
InterfaceParamType InterfaceParamTypes::findByName(const QString &name)
{
foreach (const InterfaceParamType &ipt, *this) {
if (ipt.name() == name) {
return ipt;
}
}
return InterfaceParamType();
}

View File

@ -0,0 +1,58 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, 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 INTERFACEPARAMTYPE_H
#define INTERFACEPARAMTYPE_H
#include "paramtype.h"
class InterfaceParamType : public ParamType
{
public:
InterfaceParamType();
bool optional() const;
void setOptional(bool optional);
private:
bool m_optional = false;
};
class InterfaceParamTypes: public QList<InterfaceParamType>
{
public:
InterfaceParamTypes() = default;
InterfaceParamTypes(const QList<InterfaceParamType> &other);
InterfaceParamType findByName(const QString &name);
};
#endif // INTERFACEPARAMTYPE_H

View File

@ -1,3 +1,33 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, 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 "interfacestatetype.h" #include "interfacestatetype.h"
InterfaceStateType::InterfaceStateType() InterfaceStateType::InterfaceStateType()

View File

@ -1,3 +1,33 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2024, 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 INTERFACESTATETYPE_H #ifndef INTERFACESTATETYPE_H
#define INTERFACESTATETYPE_H #define INTERFACESTATETYPE_H

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright 2013 - 2020, nymea GmbH * Copyright 2013 - 2024, nymea GmbH
* Contact: contact@nymea.io * Contact: contact@nymea.io
* *
* This file is part of nymea. * This file is part of nymea.
@ -264,6 +264,26 @@ ParamTypes::ParamTypes(const QList<ParamType> &other): QList<ParamType>(other)
{ {
} }
bool ParamTypes::contains(const ParamTypeId &paramTypeId)
{
foreach (const ParamType &paramType, *this) {
if (paramType.id() == paramTypeId) {
return true;
}
}
return false;
}
bool ParamTypes::contains(const QString &name)
{
foreach (const ParamType &paramType, *this) {
if (paramType.name() == name) {
return true;
}
}
return false;
}
QVariant ParamTypes::get(int index) const QVariant ParamTypes::get(int index) const
{ {
return QVariant::fromValue(at(index)); return QVariant::fromValue(at(index));
@ -293,3 +313,15 @@ ParamType ParamTypes::findById(const ParamTypeId &id) const
} }
return ParamType(); return ParamType();
} }
ParamType &ParamTypes::operator[](const QString &name)
{
int index = -1;
for (int i = 0; i < count(); i++) {
if (at(i).name() == name) {
index = i;
break;
}
}
return QList::operator[](index);
}

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright 2013 - 2020, nymea GmbH * Copyright 2013 - 2024, nymea GmbH
* Contact: contact@nymea.io * Contact: contact@nymea.io
* *
* This file is part of nymea. * This file is part of nymea.
@ -122,10 +122,14 @@ class ParamTypes: public QList<ParamType>
public: public:
ParamTypes() = default; ParamTypes() = default;
ParamTypes(const QList<ParamType> &other); ParamTypes(const QList<ParamType> &other);
bool contains(const ParamTypeId &paramTypeId);
bool contains(const QString &name);
Q_INVOKABLE QVariant get(int index) const; Q_INVOKABLE QVariant get(int index) const;
Q_INVOKABLE void put(const QVariant &variant); Q_INVOKABLE void put(const QVariant &variant);
ParamType findByName(const QString &name) const; ParamType findByName(const QString &name) const;
ParamType findById(const ParamTypeId &id) const; ParamType findById(const ParamTypeId &id) const;
ParamType &operator[](const QString &name);
}; };
Q_DECLARE_METATYPE(QList<ParamType>) Q_DECLARE_METATYPE(QList<ParamType>)
Q_DECLARE_METATYPE(ParamTypes) Q_DECLARE_METATYPE(ParamTypes)

View File

@ -13,7 +13,7 @@ isEmpty(NYMEA_VERSION) {
JSON_PROTOCOL_VERSION_MAJOR=8 JSON_PROTOCOL_VERSION_MAJOR=8
JSON_PROTOCOL_VERSION_MINOR=2 JSON_PROTOCOL_VERSION_MINOR=2
JSON_PROTOCOL_VERSION="$${JSON_PROTOCOL_VERSION_MAJOR}.$${JSON_PROTOCOL_VERSION_MINOR}" JSON_PROTOCOL_VERSION="$${JSON_PROTOCOL_VERSION_MAJOR}.$${JSON_PROTOCOL_VERSION_MINOR}"
LIBNYMEA_API_VERSION_MAJOR=8 LIBNYMEA_API_VERSION_MAJOR=9
LIBNYMEA_API_VERSION_MINOR=0 LIBNYMEA_API_VERSION_MINOR=0
LIBNYMEA_API_VERSION_PATCH=0 LIBNYMEA_API_VERSION_PATCH=0
LIBNYMEA_API_VERSION="$${LIBNYMEA_API_VERSION_MAJOR}.$${LIBNYMEA_API_VERSION_MINOR}.$${LIBNYMEA_API_VERSION_PATCH}" LIBNYMEA_API_VERSION="$${LIBNYMEA_API_VERSION_MAJOR}.$${LIBNYMEA_API_VERSION_MINOR}.$${LIBNYMEA_API_VERSION_PATCH}"

View File

@ -232,5 +232,10 @@ extern StateTypeId virtualIoTemperatureSensorMockInputStateTypeId;
extern StateTypeId virtualIoTemperatureSensorMockTemperatureStateTypeId; extern StateTypeId virtualIoTemperatureSensorMockTemperatureStateTypeId;
extern ActionTypeId virtualIoTemperatureSensorMockInputActionTypeId; extern ActionTypeId virtualIoTemperatureSensorMockInputActionTypeId;
extern ParamTypeId virtualIoTemperatureSensorMockInputActionInputParamTypeId; extern ParamTypeId virtualIoTemperatureSensorMockInputActionInputParamTypeId;
extern ThingClassId networkDeviceMockThingClassId;
extern ParamTypeId networkDeviceMockThingMacAddressParamTypeId;
extern ParamTypeId networkDeviceMockThingHostNameParamTypeId;
extern ParamTypeId networkDeviceMockThingAddressParamTypeId;
extern ParamTypeId networkDeviceMockDiscoveryResultTypeParamTypeId;
#endif // EXTERNPLUGININFO_H #endif // EXTERNPLUGININFO_H

View File

@ -126,6 +126,27 @@ void IntegrationPluginMock::discoverThings(ThingDiscoveryInfo *info)
return; return;
} }
if (info->thingClassId() == networkDeviceMockThingClassId) {
qCDebug(dcMock()) << "starting network device mock discovery:" << info->params();
QTimer::singleShot(1000, info, [info](){
QString resultType = info->params().paramValue(networkDeviceMockDiscoveryResultTypeParamTypeId).toString();
ParamList params;
if (resultType == "MAC address") {
params.append(Param(networkDeviceMockThingMacAddressParamTypeId, "00:11:22:33:44:55"));
} else if (resultType == "Host name") {
params.append(Param(networkDeviceMockThingHostNameParamTypeId, "hostname.localhost"));
} else if (resultType == "IP address") {
params.append(Param(networkDeviceMockThingAddressParamTypeId, "127.0.0.1"));
}
ThingDescriptor descriptor(networkDeviceMockThingClassId, "Mocked Thing (networkdevice)", QString());
descriptor.setParams(params);
info->addThingDescriptor(descriptor);
info->finish(Thing::ThingErrorNoError);
});
return;
}
qCWarning(dcMock()) << "Cannot discover for ThingClassId" << info->thingClassId(); qCWarning(dcMock()) << "Cannot discover for ThingClassId" << info->thingClassId();
info->finish(Thing::ThingErrorThingNotFound); info->finish(Thing::ThingErrorThingNotFound);
} }
@ -273,6 +294,12 @@ void IntegrationPluginMock::setupThing(ThingSetupInfo *info)
return; return;
} }
if (info->thing()->thingClassId() == networkDeviceMockThingClassId) {
qCDebug(dcMock()) << "Network device mock setup complete";
info->finish(Thing::ThingErrorNoError);
return;
}
qCWarning(dcMock()) << "Unhandled thing class" << info->thing()->thingClass(); qCWarning(dcMock()) << "Unhandled thing class" << info->thing()->thingClass();
info->finish(Thing::ThingErrorThingClassNotFound); info->finish(Thing::ThingErrorThingClassNotFound);
} }

View File

@ -1198,6 +1198,59 @@
"defaultValue": -20 "defaultValue": -20
} }
] ]
},
{
"id": "cd8fad43-174b-4ca4-a225-c07e4dbba10a",
"name": "networkDeviceMock",
"displayName": "Mocked Thing (network device)",
"interfaces": ["networkdevice"],
"createMethods": ["discovery", "user"],
"setupMethod": "justAdd",
"discoveryParamTypes": [
{
"id": "762d9bc3-c07e-42e7-8dab-62c980998677",
"name": "resultType",
"displayName": "Result type",
"type": "QString",
"defaultValue": "MAC address",
"allowedValues": [
"MAC address",
"Host name",
"IP address"
]
}
],
"paramTypes": [
{
"id": "5c2462ca-883e-4fe1-91f2-7190f9363247",
"name": "macAddress",
"displayName": "MAC address",
"type": "QString",
"defaultValue":"",
"inputType": "MacAddress",
"defaultValue": "",
"readOnly": true
},
{
"id": "ae867d45-c743-4185-87d3-1a027c985f11",
"name": "hostName",
"displayName": "Host name",
"type": "QString",
"inputType": "TextLine",
"defaultValue": ""
},
{
"id": "619fb102-0ebd-497e-9960-615c5d347db9",
"name": "address",
"displayName": "IP address",
"type": "QString",
"inputType": "IPv4Address",
"defaultValue": ""
}
],
"stateTypes": [ ],
"actionTypes": [ ],
"eventTypes": [ ]
} }
] ]
} }

View File

@ -14,7 +14,7 @@
#include <QLoggingCategory> #include <QLoggingCategory>
#include <QObject> #include <QObject>
extern "C" const QString libnymea_api_version() { return QString("8.0.0");} extern "C" const QString libnymea_api_version() { return QString("9.0.0");}
Q_DECLARE_LOGGING_CATEGORY(dcMock) Q_DECLARE_LOGGING_CATEGORY(dcMock)
Q_LOGGING_CATEGORY(dcMock, "Mock") Q_LOGGING_CATEGORY(dcMock, "Mock")
@ -236,6 +236,11 @@ StateTypeId virtualIoTemperatureSensorMockInputStateTypeId = StateTypeId("{fd341
StateTypeId virtualIoTemperatureSensorMockTemperatureStateTypeId = StateTypeId("{db9cc518-1012-47e2-8212-6e616fed07a6}"); StateTypeId virtualIoTemperatureSensorMockTemperatureStateTypeId = StateTypeId("{db9cc518-1012-47e2-8212-6e616fed07a6}");
ActionTypeId virtualIoTemperatureSensorMockInputActionTypeId = ActionTypeId("{fd341f72-6d9a-4812-9f66-47197c48a935}"); ActionTypeId virtualIoTemperatureSensorMockInputActionTypeId = ActionTypeId("{fd341f72-6d9a-4812-9f66-47197c48a935}");
ParamTypeId virtualIoTemperatureSensorMockInputActionInputParamTypeId = ParamTypeId("{fd341f72-6d9a-4812-9f66-47197c48a935}"); ParamTypeId virtualIoTemperatureSensorMockInputActionInputParamTypeId = ParamTypeId("{fd341f72-6d9a-4812-9f66-47197c48a935}");
ThingClassId networkDeviceMockThingClassId = ThingClassId("{cd8fad43-174b-4ca4-a225-c07e4dbba10a}");
ParamTypeId networkDeviceMockThingMacAddressParamTypeId = ParamTypeId("{5c2462ca-883e-4fe1-91f2-7190f9363247}");
ParamTypeId networkDeviceMockThingHostNameParamTypeId = ParamTypeId("{ae867d45-c743-4185-87d3-1a027c985f11}");
ParamTypeId networkDeviceMockThingAddressParamTypeId = ParamTypeId("{619fb102-0ebd-497e-9960-615c5d347db9}");
ParamTypeId networkDeviceMockDiscoveryResultTypeParamTypeId = ParamTypeId("{762d9bc3-c07e-42e7-8dab-62c980998677}");
const QString translations[] { const QString translations[] {
//: The name of the Browser Item ActionType ({00b8f0a8-99ca-4aa4-833d-59eb8d4d6de3}) of ThingClass mock //: The name of the Browser Item ActionType ({00b8f0a8-99ca-4aa4-833d-59eb8d4d6de3}) of ThingClass mock
@ -361,6 +366,12 @@ const QString translations[] {
//: The name of the ThingClass ({f8917e12-c9cb-4ea1-a06e-1ce6db2194f3}) //: The name of the ThingClass ({f8917e12-c9cb-4ea1-a06e-1ce6db2194f3})
QT_TRANSLATE_NOOP("mock", "Generic Temperature Sensor (Mock)"), QT_TRANSLATE_NOOP("mock", "Generic Temperature Sensor (Mock)"),
//: The name of the ParamType (ThingClass: networkDeviceMock, Type: thing, ID: {ae867d45-c743-4185-87d3-1a027c985f11})
QT_TRANSLATE_NOOP("mock", "Host name"),
//: The name of the ParamType (ThingClass: networkDeviceMock, Type: thing, ID: {619fb102-0ebd-497e-9960-615c5d347db9})
QT_TRANSLATE_NOOP("mock", "IP address"),
//: The name of the ParamType (ThingClass: inputTypeMock, Type: thing, ID: {9e5f86a0-4bb3-4892-bff8-3fc4032af6e2}) //: The name of the ParamType (ThingClass: inputTypeMock, Type: thing, ID: {9e5f86a0-4bb3-4892-bff8-3fc4032af6e2})
QT_TRANSLATE_NOOP("mock", "IPv4 address"), QT_TRANSLATE_NOOP("mock", "IPv4 address"),
@ -388,6 +399,9 @@ const QString translations[] {
//: The name of the StateType ({23df3dce-bd10-4eb3-b5e3-221168440cd4}) of ThingClass inputTypeMock //: The name of the StateType ({23df3dce-bd10-4eb3-b5e3-221168440cd4}) of ThingClass inputTypeMock
QT_TRANSLATE_NOOP("mock", "Localized list"), QT_TRANSLATE_NOOP("mock", "Localized list"),
//: The name of the ParamType (ThingClass: networkDeviceMock, Type: thing, ID: {5c2462ca-883e-4fe1-91f2-7190f9363247})
QT_TRANSLATE_NOOP("mock", "MAC address"),
//: The name of the ParamType (ThingClass: inputTypeMock, Type: thing, ID: {e93db587-7919-48f3-8c88-1651de63c765}) //: The name of the ParamType (ThingClass: inputTypeMock, Type: thing, ID: {e93db587-7919-48f3-8c88-1651de63c765})
QT_TRANSLATE_NOOP("mock", "Mac address"), QT_TRANSLATE_NOOP("mock", "Mac address"),
@ -481,6 +495,9 @@ const QString translations[] {
//: The name of the ThingClass ({6fe07a77-9c07-4736-81e2-d504314bbcb9}) //: The name of the ThingClass ({6fe07a77-9c07-4736-81e2-d504314bbcb9})
QT_TRANSLATE_NOOP("mock", "Mocked Thing (User & Password)"), QT_TRANSLATE_NOOP("mock", "Mocked Thing (User & Password)"),
//: The name of the ThingClass ({cd8fad43-174b-4ca4-a225-c07e4dbba10a})
QT_TRANSLATE_NOOP("mock", "Mocked Thing (network device)"),
//: The name of the plugin mock ({727a4a9a-c187-446f-aadf-f1b2220607d1}) //: The name of the plugin mock ({727a4a9a-c187-446f-aadf-f1b2220607d1})
QT_TRANSLATE_NOOP("mock", "Mocked things"), QT_TRANSLATE_NOOP("mock", "Mocked things"),
@ -508,6 +525,9 @@ const QString translations[] {
//: The name of the ParamType (ThingClass: mock, Type: discovery, ID: {d222adb4-2f9c-4c3f-8655-76400d0fb6ce}) //: The name of the ParamType (ThingClass: mock, Type: discovery, ID: {d222adb4-2f9c-4c3f-8655-76400d0fb6ce})
QT_TRANSLATE_NOOP("mock", "Result count"), QT_TRANSLATE_NOOP("mock", "Result count"),
//: The name of the ParamType (ThingClass: networkDeviceMock, Type: discovery, ID: {762d9bc3-c07e-42e7-8dab-62c980998677})
QT_TRANSLATE_NOOP("mock", "Result type"),
//: The name of the ParamType (ThingClass: inputTypeMock, Type: thing, ID: {22add8c9-ee4f-43ad-8931-58e999313ac3}) //: The name of the ParamType (ThingClass: inputTypeMock, Type: thing, ID: {22add8c9-ee4f-43ad-8931-58e999313ac3})
QT_TRANSLATE_NOOP("mock", "Search text"), QT_TRANSLATE_NOOP("mock", "Search text"),

View File

@ -298,8 +298,8 @@ void TestIntegrations::getThingClasses_data()
QTest::addColumn<QList<ThingClassId>>("thingClassIds"); QTest::addColumn<QList<ThingClassId>>("thingClassIds");
QTest::addColumn<int>("resultCount"); QTest::addColumn<int>("resultCount");
QTest::newRow("vendor nymea") << nymeaVendorId << QList<ThingClassId>() << 16; QTest::newRow("vendor nymea") << nymeaVendorId << QList<ThingClassId>() << 17;
QTest::newRow("no filter") << VendorId() << QList<ThingClassId>() << 16; QTest::newRow("no filter") << VendorId() << QList<ThingClassId>() << 17;
QTest::newRow("invalid vendor") << VendorId("93e7d361-8025-4354-b17e-b68406c800bc") << QList<ThingClassId>() << 0; QTest::newRow("invalid vendor") << VendorId("93e7d361-8025-4354-b17e-b68406c800bc") << QList<ThingClassId>() << 0;
QTest::newRow("mockThingClassId") << VendorId() << (QList<ThingClassId>() << mockThingClassId) << 1; QTest::newRow("mockThingClassId") << VendorId() << (QList<ThingClassId>() << mockThingClassId) << 1;
QTest::newRow("invalid thingClassId") << VendorId() << (QList<ThingClassId>() << ThingClassId("6c78ec28-09b6-476d-ac27-1d6966a45c57")) << 0; QTest::newRow("invalid thingClassId") << VendorId() << (QList<ThingClassId>() << ThingClassId("6c78ec28-09b6-476d-ac27-1d6966a45c57")) << 0;

View File

@ -24,6 +24,7 @@ SOURCES += \
$$top_srcdir/libnymea/types/eventtype.cpp \ $$top_srcdir/libnymea/types/eventtype.cpp \
$$top_srcdir/libnymea/types/actiontype.cpp \ $$top_srcdir/libnymea/types/actiontype.cpp \
$$top_srcdir/libnymea/types/interface.cpp \ $$top_srcdir/libnymea/types/interface.cpp \
$$top_srcdir/libnymea/types/interfaceparamtype.cpp \
$$top_srcdir/libnymea/types/interfacestatetype.cpp \ $$top_srcdir/libnymea/types/interfacestatetype.cpp \
$$top_srcdir/libnymea/types/interfaceeventtype.cpp \ $$top_srcdir/libnymea/types/interfaceeventtype.cpp \
$$top_srcdir/libnymea/types/interfaceactiontype.cpp \ $$top_srcdir/libnymea/types/interfaceactiontype.cpp \
@ -42,6 +43,7 @@ HEADERS += \
$$top_srcdir/libnymea/types/eventtype.h \ $$top_srcdir/libnymea/types/eventtype.h \
$$top_srcdir/libnymea/types/actiontype.h \ $$top_srcdir/libnymea/types/actiontype.h \
$$top_srcdir/libnymea/types/interface.h \ $$top_srcdir/libnymea/types/interface.h \
$$top_srcdir/libnymea/types/interfaceparamtype.h \
$$top_srcdir/libnymea/types/interfacestatetype.h \ $$top_srcdir/libnymea/types/interfacestatetype.h \
$$top_srcdir/libnymea/types/interfaceeventtype.h \ $$top_srcdir/libnymea/types/interfaceeventtype.h \
$$top_srcdir/libnymea/types/interfaceactiontype.h \ $$top_srcdir/libnymea/types/interfaceactiontype.h \